Manage game state through object injection everywhere, fixed filling buffer and stopping clock in Game.

This commit is contained in:
2020-12-21 17:27:16 +01:00
parent a66f6e1fa4
commit eebca998cb
9 changed files with 70 additions and 75 deletions

View File

@@ -8,13 +8,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent, provide, ref, readonly} from "vue"; import {defineComponent, provide, ref} from "vue";
import Level from "./components/Level.vue"; import Level from "./components/Level.vue";
import Timer from "./components/Timer.vue"; import Timer from "./components/Timer.vue";
import Buffer from "./components/Buffer.vue"; import Buffer from "./components/Buffer.vue";
import Matrix from "./components/Matrix.vue"; import Matrix from "./components/Matrix.vue";
import { Game } from "./game/Game"; import { Game } from "./game/Game";
import {useTimer} from "./components/timer";
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -24,8 +23,7 @@
Matrix Matrix
}, },
setup() { setup() {
const {updateCountdown} = useTimer(); const game = ref(new Game({
const game = new Game({
matrix: [ matrix: [
"AA", "BB", "CC", "AA", "BB", "CC",
"DD", "AA", "BB", "DD", "AA", "BB",
@@ -36,23 +34,9 @@
], ],
maxBufferLength: 3, maxBufferLength: 3,
timeoutMilliseconds: 60_000, timeoutMilliseconds: 60_000,
}); }));
provide("game", game);
const level = ref(1); const level = ref(1);
const remainingMilliseconds = ref(game.remainingMilliseconds);
const timeoutMilliseconds = ref(game.timeoutMilliseconds);
const maxBufferLength = ref(game.maxBufferLength);
const buffer = ref(game.buffer);
const sequences = ref(game.sequences);
const size = ref(game.size);
const matrix = ref(game.matrix);
provide("remainingMilliseconds", readonly(remainingMilliseconds));
provide("timeoutMilliseconds", readonly(timeoutMilliseconds));
provide("maxBufferLength", readonly(maxBufferLength));
provide("buffer", readonly(buffer));
provide("sequences", readonly(sequences));
provide("size", readonly(size));
provide("matrix", readonly(matrix));
updateCountdown(game, remainingMilliseconds);
return { return {
level level

View File

@@ -3,7 +3,7 @@
<h2>Buffer</h2> <h2>Buffer</h2>
<div class="buffer-slots"> <div class="buffer-slots">
<div class="buffer-slots-slot buffer-slots-border" v-for="(slot, i) in maxBufferLength" :key="i"> <div class="buffer-slots-slot buffer-slots-border" v-for="(slot, i) in maxBufferLength" :key="i">
<span v-if="buffer.length > i">{{buffer[i]}}</span> <span v-if="buffer.length > i">{{buffer[i].value}}</span>
</div> </div>
</div> </div>
<Sequences /> <Sequences />
@@ -11,7 +11,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent, inject} from "vue"; import { Game } from "@/game/Game";
import {defineComponent, inject, Ref, computed} from "vue";
import Sequences from "./Sequences.vue"; import Sequences from "./Sequences.vue";
export default defineComponent({ export default defineComponent({
@@ -19,8 +20,9 @@
Sequences Sequences
}, },
setup() { setup() {
const maxBufferLength = inject("maxBufferLength"); const game = inject("game") as Ref<Game>;
const buffer = inject("buffer"); const maxBufferLength = computed(() => game.value.maxBufferLength);
const buffer = computed(() => game.value.buffer);
return { return {
maxBufferLength, maxBufferLength,

View File

@@ -2,24 +2,35 @@
<div class="matrix"> <div class="matrix">
<h2>Code-Matrix</h2> <h2>Code-Matrix</h2>
<div class="matrix-row" v-for="i in size" :key="i"> <div class="matrix-row" v-for="i in size" :key="i">
<div class="matrix-column" v-for="j in size" :key="j"> <div class="matrix-column" v-for="j in size" :key="j" v-on:click="select(i, j)">
{{matrix[(i - 1)*size + (j - 1)]}} {{matrix[(j - 1)*size + (i - 1)]}}
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent, inject} from "vue"; import { Game } from "@/game/Game";
import {defineComponent, inject, computed, Ref} from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const size = inject("size"); const game = inject("game") as Ref<Game>;
const matrix = inject("matrix"); const size = computed(() => game.value.size);
const matrix = computed(() => game.value.matrix);
function select(row: number, column: number) {
try {
game.value.pick(row-1, column-1);
} catch (e) {
console.log("nö!");
}
}
return { return {
size, size,
matrix matrix,
select
} }
} }
}); });

View File

@@ -10,11 +10,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent, inject} from "vue"; import { Game } from "@/game/Game";
import {computed, defineComponent, inject, Ref} from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const sequences = inject("sequences"); const game = inject("game") as Ref<Game>;
const sequences = computed(() => game.value.sequences);
return { return {
sequences sequences

View File

@@ -6,15 +6,31 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent, computed, inject, Ref} from "vue"; import { Game } from "@/game/Game";
import {defineComponent, computed, inject, Ref, ref} from "vue";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const remainingMilliseconds = inject("remainingMilliseconds") as Ref<number>; const game = inject("game") as Ref<Game>;
const timeoutMilliseconds = inject("timeoutMilliseconds") as Ref<number>; const remainingMilliseconds = ref(game.value.remainingMilliseconds);
const timeoutMilliseconds = computed(() => game.value.timeoutMilliseconds);
const progress = computed(() => remainingMilliseconds.value/timeoutMilliseconds.value*100); const progress = computed(() => remainingMilliseconds.value/timeoutMilliseconds.value*100);
const countdown = computed(() => (remainingMilliseconds.value/1000).toFixed(2)); const countdown = computed(() => (remainingMilliseconds.value/1000).toFixed(2));
const updateTime = () => {
remainingMilliseconds.value = game.value.remainingMilliseconds;
if (remainingMilliseconds.value > 0) {
requestAnimationFrame(() => {
updateTime();
});
}
}
requestAnimationFrame(() => {
updateTime();
});
return { return {
countdown, countdown,
progress progress

View File

@@ -1,28 +0,0 @@
import { Game } from '@/game/Game';
import { Ref } from 'vue';
interface Timer {
updateCountdown(game: Game, remainingMilliseconds: Ref<number>): void
}
export function useTimer(): Timer {
function updateCountdown(game: Game, remainingMilliseconds: Ref<number>) {
const updateTime = () => {
remainingMilliseconds.value = game.remainingMilliseconds;
if (remainingMilliseconds.value > 0) {
requestAnimationFrame(() => {
updateTime();
});
}
}
requestAnimationFrame(() => {
updateTime();
});
}
return {
updateCountdown
}
}

View File

@@ -53,6 +53,7 @@ describe("GameState", () => {
expect(() => game.pick(0, 2)).toThrow(); expect(() => game.pick(0, 2)).toThrow();
game.pick(2, 0); game.pick(2, 0);
expect(game.state).toEqual({ selectionMode: SelectionMode.ColumnPick, row: 2 }); expect(game.state).toEqual({ selectionMode: SelectionMode.ColumnPick, row: 2 });
expect(game.buffer.length).toEqual(2);
}); });
test("cannot pick cell twice", () => { test("cannot pick cell twice", () => {
@@ -64,6 +65,7 @@ describe("GameState", () => {
}); });
game.pick(0, 0); game.pick(0, 0);
expect(() => game.pick(0, 0)).toThrow(); expect(() => game.pick(0, 0)).toThrow();
expect(game.buffer.length).toEqual(1);
}); });
test("picking outside of range fails", () => { test("picking outside of range fails", () => {
@@ -95,6 +97,7 @@ describe("GameState", () => {
value: "00" value: "00"
}]); }]);
expect(game.getSequences()).toEqual([{ sequence: simpleSequence, numberOfFulfilled: 1 }]) expect(game.getSequences()).toEqual([{ sequence: simpleSequence, numberOfFulfilled: 1 }])
expect(game.buffer.length).toEqual(1);
}); });
test("picking fulfills second sequence occurence", () => { test("picking fulfills second sequence occurence", () => {
@@ -120,6 +123,7 @@ describe("GameState", () => {
expect(game.getSequences()).toEqual([{ sequence: sequence, numberOfFulfilled: 2 }]) expect(game.getSequences()).toEqual([{ sequence: sequence, numberOfFulfilled: 2 }])
game.pick(0, 2); game.pick(0, 2);
expect(game.getSequences()).toEqual([{ sequence: sequence, numberOfFulfilled: 3 }]) expect(game.getSequences()).toEqual([{ sequence: sequence, numberOfFulfilled: 3 }])
expect(game.buffer.length).toEqual(5);
}); });
}); });

View File

@@ -156,11 +156,9 @@ export class Game {
if (this.getSequences().every(isSequenceFulfilled)) { if (this.getSequences().every(isSequenceFulfilled)) {
this.stopClock() this.stopClock()
this.state = EndState.Won this.state = EndState.Won
} else { } else if (this.buffer.length >= this.config.maxBufferLength) {
this.stopClock() this.stopClock()
if (this.buffer.length >= this.config.maxBufferLength) { this.state = EndState.Lost
this.state = EndState.Lost
}
} }
} }
@@ -174,15 +172,10 @@ export class Game {
Lost: () => {throw new IllegalMoveError()}, Lost: () => {throw new IllegalMoveError()},
InProgress: (selectionMode) => { InProgress: (selectionMode) => {
const cell = this.getCell(row, column) const cell = this.getCell(row, column)
if (cell.isUsed) {
throw new IllegalMoveError()
}
this.buffer.push({ if (cell.isUsed) {
value: cell.value, throw new IllegalMoveError();
positionInMatrixRow: row, }
positionInMatrixColumn: column,
})
matchSelectionState({ matchSelectionState({
Free: () => { Free: () => {
@@ -212,6 +205,12 @@ export class Game {
} }
}, },
})(selectionMode) })(selectionMode)
this.buffer.push({
value: cell.value,
positionInMatrixRow: row,
positionInMatrixColumn: column,
})
} }
})(this.state) })(this.state)

View File

@@ -107,6 +107,11 @@ main {
height: 32px; height: 32px;
margin: 0 5px 5px 0; margin: 0 5px 5px 0;
cursor: pointer; cursor: pointer;
&:hover {
background: $yellow;
color: $background;
}
} }
} }