This commit is contained in:
parent
14525db49c
commit
bbe310bc77
2
.env
2
.env
@ -2,4 +2,4 @@ VITE_REQUEST_BASE_URL=https://wzpmc.cn:18080
|
||||
# VITE_REQUEST_BASE_URL=http://172.16.114.84:58082
|
||||
VITE_WEBSOCKET_BASE_URL=wss://wzpmc.cn:18080
|
||||
# VITE_WEBSOCKET_BASE_URL=ws://172.16.114.84:58082
|
||||
VITE_GAME_2048_DEBUG=true
|
||||
VITE_GAME_2048_DEBUG=false
|
||||
|
@ -295,8 +295,8 @@ let enteredBackgroundTileCount = 0;
|
||||
*/
|
||||
let maxId = 0;
|
||||
const tileGrid = reactive<OverlappedTileLine[]>([]);
|
||||
const tileAddFutureMap = new FutureMap();
|
||||
const tileTransitionFutureMap = new FutureMap();
|
||||
const tileAddFutureMap = new FutureMap<number>();
|
||||
const tileTransitionFutureMap = new FutureMap<number>();
|
||||
const tileBackgroundColorMapping: Record<number, string | undefined> = {
|
||||
2: 'rgb(238, 228, 218)',
|
||||
4: 'rgb(237, 224, 200)',
|
||||
|
@ -4,7 +4,7 @@
|
||||
<slot>最高数字</slot>
|
||||
</div>
|
||||
<div class="score-text-outer">
|
||||
<div ref="scoreDiv" class="score-text">{{ displayedScore }}</div>
|
||||
<div ref="scoreDiv" class="score-text">{{ score }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -43,13 +43,15 @@ import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
const props = defineProps<{
|
||||
score: number;
|
||||
}>();
|
||||
const displayedScore = ref(props.score);
|
||||
const score = ref(props.score);
|
||||
const scoreDiv = ref<HTMLDivElement>();
|
||||
const scoreQueue = new AsyncQueue<number>();
|
||||
let isUnmounted = false;
|
||||
watch(
|
||||
() => props.score,
|
||||
(score) => {
|
||||
console.log(score);
|
||||
|
||||
scoreQueue.push(score);
|
||||
},
|
||||
);
|
||||
@ -57,13 +59,13 @@ onMounted(async () => {
|
||||
let continuouslyRolling = false;
|
||||
while (!isUnmounted) {
|
||||
try {
|
||||
const score = await scoreQueue.shift();
|
||||
const s = await scoreQueue.shift();
|
||||
await scoreDiv.value?.animate([{ transform: 'none' }, { transform: 'translateY(-100%)' }], {
|
||||
easing: continuouslyRolling ? 'linear' : 'ease-in',
|
||||
fill: 'forwards',
|
||||
duration: 100 / (scoreQueue.size + 1 + Number(continuouslyRolling)),
|
||||
}).finished;
|
||||
displayedScore.value = score;
|
||||
score.value = s;
|
||||
await nextTick();
|
||||
continuouslyRolling = scoreQueue.size > 0;
|
||||
await scoreDiv.value?.animate([{ transform: 'translateY(100%)' }, { transform: 'none' }], {
|
||||
|
72
src/components/game2048/Game2048ScoreDigit.vue
Normal file
72
src/components/game2048/Game2048ScoreDigit.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="digit">
|
||||
<div ref="div">{{ digit }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<script lang="ts" setup>
|
||||
import { GAME_2048_DEBUG } from '@/env';
|
||||
import { onBeforeUnmount, ref, useTemplateRef, watch } from 'vue';
|
||||
|
||||
const { leaveDuration, enterDuration, linearEnter, reversed, ...props } = defineProps<{
|
||||
digit: number;
|
||||
leaveDuration: number;
|
||||
enterDuration: number;
|
||||
linearEnter: boolean;
|
||||
reversed: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
up: [number];
|
||||
finish: [number];
|
||||
}>();
|
||||
const digit = ref(props.digit);
|
||||
const div = useTemplateRef('div');
|
||||
const up = 'translateY(-100%)',
|
||||
down = 'translateY(100%)';
|
||||
let linearLeave = false;
|
||||
watch(
|
||||
() => linearEnter,
|
||||
(v) => {
|
||||
if (v) {
|
||||
linearLeave = true;
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
watch(
|
||||
() => reversed,
|
||||
() => {
|
||||
linearLeave = false;
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
watch(
|
||||
() => props.digit,
|
||||
async (d) => {
|
||||
if (GAME_2048_DEBUG) {
|
||||
console.log('c1', linearLeave);
|
||||
}
|
||||
await div.value?.animate([{ transform: 'none' }, { transform: reversed ? down : up }], {
|
||||
easing: linearLeave ? 'linear' : 'ease-in',
|
||||
fill: 'forwards',
|
||||
duration: leaveDuration,
|
||||
}).finished;
|
||||
linearLeave = linearEnter;
|
||||
digit.value = d;
|
||||
emit('up', d);
|
||||
if (GAME_2048_DEBUG) {
|
||||
console.log('c2', linearEnter);
|
||||
}
|
||||
await div.value?.animate([{ transform: reversed ? up : down }, { transform: 'none' }], {
|
||||
easing: linearEnter ? 'linear' : 'ease-out',
|
||||
fill: 'forwards',
|
||||
duration: enterDuration,
|
||||
}).finished;
|
||||
emit('finish', d);
|
||||
},
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
div.value?.getAnimations().forEach((animation) => animation.finish());
|
||||
});
|
||||
</script>
|
135
src/components/game2048/Game2048ScoreV2.vue
Normal file
135
src/components/game2048/Game2048ScoreV2.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="score-container">
|
||||
<div class="score-description">
|
||||
<slot>最高数字</slot>
|
||||
</div>
|
||||
<div class="score-text-outer flex items-center">
|
||||
<transition-group :css="false" @enter="onEnter" @leave="onLeave">
|
||||
<game2048-score-digit
|
||||
ref="digitRefs"
|
||||
v-for="[i, d] of getDigits()"
|
||||
:key="i"
|
||||
:digit="Number(d)"
|
||||
:leave-duration="leaveDuration"
|
||||
:enter-duration="enterDuration"
|
||||
:linear-enter="linearEnter"
|
||||
:reversed="reversed"
|
||||
@up="futureMap.resolve('leave')"
|
||||
@finish="futureMap.resolve('enter')"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.score-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 120px;
|
||||
height: 55px;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
padding: 0 25px;
|
||||
margin-left: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: rgb(187, 173, 160);
|
||||
}
|
||||
|
||||
.score-description {
|
||||
font-size: 14px;
|
||||
color: rgb(238, 228, 218);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.score-text-outer {
|
||||
font-size: 25px;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
export const baseDuration = (tileMoveDuration + tileShowDuration) / 2;
|
||||
const fadeOutKeyframe = { fontSize: 0, scale: 0 };
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { GAME_2048_DEBUG } from '@/env';
|
||||
import { AsyncQueue } from '@/utils/async-queue';
|
||||
import { FutureMap } from '@/utils/future';
|
||||
import { noop } from 'lodash-es';
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import Game2048ScoreDigit from './Game2048ScoreDigit.vue';
|
||||
import { tileMoveDuration, tileShowDuration } from './Game2048V2.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
score: number;
|
||||
}>();
|
||||
const score = ref(props.score);
|
||||
const getDigits = (n: number = score.value) => {
|
||||
const s = String(n);
|
||||
return s.split('').map((d, i): [i: number, d: number] => [s.length - 1 - i, Number(d)]);
|
||||
};
|
||||
const futureMap = new FutureMap<'leave' | 'enter'>();
|
||||
const scoreDiv = ref<HTMLDivElement>();
|
||||
const scoreQueue = new AsyncQueue<number>();
|
||||
const leaveDuration = ref(baseDuration);
|
||||
const enterDuration = ref(baseDuration);
|
||||
let isUnmounted = false;
|
||||
const linearEnter = ref(false);
|
||||
scoreQueue.watchPush(props, 'score');
|
||||
const onEnter = async (el: Element, done: () => void) => {
|
||||
await el.animate(fadeOutKeyframe, { duration: 0, fill: 'forwards' }).finished;
|
||||
await futureMap.get('leave')?.promise;
|
||||
await el.animate([{ fontSize: '1em', scale: 1 }], {
|
||||
duration: enterDuration.value,
|
||||
easing: 'ease-out',
|
||||
fill: 'forwards',
|
||||
}).finished;
|
||||
futureMap.resolve('enter');
|
||||
done();
|
||||
};
|
||||
const onLeave = async (el: Element, done: () => void) => {
|
||||
await el.animate(fadeOutKeyframe, {
|
||||
duration: leaveDuration.value,
|
||||
easing: 'ease-out',
|
||||
fill: 'forwards',
|
||||
}).finished;
|
||||
futureMap.resolve('leave');
|
||||
done();
|
||||
};
|
||||
const reversed = ref(false);
|
||||
onMounted(async () => {
|
||||
let delta = 0;
|
||||
while (!isUnmounted) {
|
||||
const s = await scoreQueue.shift().catch(noop);
|
||||
if (s === undefined) break;
|
||||
const old = score.value;
|
||||
score.value = s;
|
||||
const newDelta = s - old;
|
||||
reversed.value = newDelta < 0;
|
||||
linearEnter.value = !!scoreQueue.size && (delta >= 0 ? newDelta >= 0 : newDelta < 0);
|
||||
if (GAME_2048_DEBUG) {
|
||||
console.log('score', s);
|
||||
console.log(linearEnter.value);
|
||||
}
|
||||
delta = newDelta;
|
||||
leaveDuration.value = baseDuration / (scoreQueue.size + 1);
|
||||
enterDuration.value = baseDuration / (scoreQueue.size + 1 + Number(linearEnter.value));
|
||||
futureMap.clear();
|
||||
if (!new RegExp(`.+${old}`).test(String(s))) {
|
||||
futureMap.add('leave');
|
||||
}
|
||||
if (!new RegExp(`.+${s}$`).test(String(old))) {
|
||||
futureMap.add('enter');
|
||||
}
|
||||
await nextTick();
|
||||
await futureMap.waitAll();
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
isUnmounted = true;
|
||||
scoreQueue.cancelGetters();
|
||||
scoreDiv.value?.getAnimations().forEach((animation) => animation.finish());
|
||||
});
|
||||
</script>
|
@ -57,6 +57,25 @@
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
const tileWidth = 105;
|
||||
const borderWidth = 15;
|
||||
const tileBackgroundColorMapping: Record<number, string | undefined> = {
|
||||
2: 'rgb(238, 228, 218)',
|
||||
4: 'rgb(237, 224, 200)',
|
||||
8: 'rgb(242, 177, 121)',
|
||||
16: 'rgb(242, 177, 121)',
|
||||
32: 'rgb(246, 124, 95)',
|
||||
64: 'rgb(246, 94, 59)',
|
||||
128: 'rgb(237, 207, 114)',
|
||||
256: 'rgb(237, 204, 97)',
|
||||
512: 'rgb(237, 200, 80)',
|
||||
1024: 'rgb(237, 197, 63)',
|
||||
2048: 'rgb(237, 194, 46)',
|
||||
};
|
||||
export const tileMoveDuration = 110,
|
||||
tileShowDuration = 150;
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { GAME_2048_DEBUG } from '@/env';
|
||||
import { useGame2048Store } from '@/stores/2048';
|
||||
@ -69,10 +88,14 @@ import { Directions } from '@/views/Game2048PageV2.vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { sample, shuffle } from 'lodash-es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, reactive, ref, useTemplateRef } from 'vue';
|
||||
import { onMounted, reactive, ref, useTemplateRef, watch } from 'vue';
|
||||
const game2048Store = useGame2048Store();
|
||||
const { width, height } = storeToRefs(game2048Store);
|
||||
const table = new TableView<Tile>([game2048Store.height, game2048Store.width]);
|
||||
const tileWidthStyle = `${tileWidth}px`;
|
||||
const gameContainerStyle = {
|
||||
minWidth: `${width.value * tileWidth + (width.value + 1) * borderWidth}px`,
|
||||
minHeight: `${height.value * tileWidth + (height.value + 1) * borderWidth}px`,
|
||||
};
|
||||
type TileRenderState = {
|
||||
number: number;
|
||||
fromPos: Pos;
|
||||
@ -182,9 +205,16 @@ class Tile {
|
||||
return `Tile#${this.id}`;
|
||||
}
|
||||
}
|
||||
const table = new TableView<Tile>([game2048Store.height, game2048Store.width]);
|
||||
const tiles = reactive<Exposed<Tile>[]>(
|
||||
Array.from({ length: game2048Store.width * game2048Store.height }, () => new Tile()),
|
||||
);
|
||||
watch(
|
||||
() => Math.max(...tiles.map((t) => t.number)),
|
||||
(v) => {
|
||||
game2048Store.maxNumber = v;
|
||||
},
|
||||
);
|
||||
const idleTiles = new Set(tiles);
|
||||
const createShowKeyframeCommon = (
|
||||
pos: Pos | undefined,
|
||||
@ -215,28 +245,6 @@ const createKeyframeOptions = ({
|
||||
delay: !breaking ? delay : 0,
|
||||
...options,
|
||||
});
|
||||
const tileWidth = 105;
|
||||
const borderWidth = 15;
|
||||
const tileWidthStyle = `${tileWidth}px`;
|
||||
const gameContainerStyle = {
|
||||
minWidth: `${width.value * tileWidth + (width.value + 1) * borderWidth}px`,
|
||||
minHeight: `${height.value * tileWidth + (height.value + 1) * borderWidth}px`,
|
||||
};
|
||||
const tileBackgroundColorMapping: Record<number, string | undefined> = {
|
||||
2: 'rgb(238, 228, 218)',
|
||||
4: 'rgb(237, 224, 200)',
|
||||
8: 'rgb(242, 177, 121)',
|
||||
16: 'rgb(242, 177, 121)',
|
||||
32: 'rgb(246, 124, 95)',
|
||||
64: 'rgb(246, 94, 59)',
|
||||
128: 'rgb(237, 207, 114)',
|
||||
256: 'rgb(237, 204, 97)',
|
||||
512: 'rgb(237, 200, 80)',
|
||||
1024: 'rgb(237, 197, 63)',
|
||||
2048: 'rgb(237, 194, 46)',
|
||||
};
|
||||
const tileMoveDuration = 125,
|
||||
tileShowDuration = 150;
|
||||
const getTilePositionStyle = ([y, x]: Pos) => ({
|
||||
left: `${borderWidth + x * (tileWidth + borderWidth)}px`,
|
||||
top: `${borderWidth + y * (tileWidth + borderWidth)}px`,
|
||||
@ -370,9 +378,9 @@ const move = async (key: Directions) => {
|
||||
tilesToHide = new Set<Tile>(),
|
||||
tilesToMove = new Set<Tile>(),
|
||||
relationMap = new Map<Tile, Tile[]>();
|
||||
let changed = false;
|
||||
let finished: boolean;
|
||||
for (const f of first) {
|
||||
let scoreDelta = 0;
|
||||
let prev: Tile | undefined;
|
||||
do {
|
||||
finished = true;
|
||||
@ -385,18 +393,19 @@ const move = async (key: Directions) => {
|
||||
}
|
||||
if (prev.number === tile.number) {
|
||||
finished = false;
|
||||
changed = true;
|
||||
prev.remove();
|
||||
tile.number *= 2;
|
||||
setDefault(relationMap, tile, []).push(prev);
|
||||
tilesToReshow.add(tile);
|
||||
tilesToHide.add(prev);
|
||||
prev = once ? undefined : tile;
|
||||
scoreDelta += tile.number;
|
||||
} else {
|
||||
prev = tile;
|
||||
}
|
||||
}
|
||||
} while (!once && !finished);
|
||||
game2048Store.score += scoreDelta;
|
||||
let i = 0;
|
||||
let zIndex = second.length - 1;
|
||||
for (const s of second) {
|
||||
@ -405,7 +414,6 @@ const move = async (key: Directions) => {
|
||||
const dumpIndex = second[i++],
|
||||
pos = toPos(f, dumpIndex, xFirst);
|
||||
if (s !== dumpIndex) {
|
||||
changed = true;
|
||||
tile.zIndex = zIndex--;
|
||||
tilesToMove.add(tile);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const DEV = import.meta.env.DEV;
|
||||
export const REQUEST_BASE_URL = import.meta.env.VITE_REQUEST_BASE_URL;
|
||||
export const WEBSOCKET_BASE_URL = import.meta.env.VITE_WEBSOCKET_BASE_URL;
|
||||
export const GAME_2048_DEBUG = import.meta.env.VITE_GAME_2048_DEBUG;
|
||||
export const GAME_2048_DEBUG = import.meta.env.VITE_GAME_2048_DEBUG === 'true';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { watch } from 'vue';
|
||||
import { DoubleQueue } from './double-queue';
|
||||
import type { Future } from './future';
|
||||
import type { ValidKey } from './types';
|
||||
export class AsyncQueue<T> {
|
||||
protected _queue: DoubleQueue<T>;
|
||||
protected _getters = new DoubleQueue<Future<T>>();
|
||||
@ -24,7 +26,7 @@ export class AsyncQueue<T> {
|
||||
push(...values: T[]): this {
|
||||
for (const value of values) {
|
||||
const future = this._getters.shift();
|
||||
if (future !== undefined) {
|
||||
if (future) {
|
||||
future.resolve(value);
|
||||
} else {
|
||||
this._queue.push(value);
|
||||
@ -38,8 +40,20 @@ export class AsyncQueue<T> {
|
||||
future.reject(new AsyncQueue.CancelledError());
|
||||
}
|
||||
}
|
||||
watchPush<O extends Record<string, unknown>>(obj: O, key: ValidKey<T, O>) {
|
||||
watch(
|
||||
() => obj[key],
|
||||
(value) => {
|
||||
this.push(value as T);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace AsyncQueue {
|
||||
export class CancelledError extends Error {}
|
||||
|
||||
export function shift() {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface Node<T> {
|
||||
value: T;
|
||||
prev?: Node<T>;
|
||||
@ -6,7 +8,7 @@ interface Node<T> {
|
||||
export class DoubleQueue<T> {
|
||||
protected _head?: Node<T>;
|
||||
protected _tail?: Node<T>;
|
||||
protected _size = 0;
|
||||
protected _size = ref(0);
|
||||
constructor(it?: Iterable<T>) {
|
||||
if (it) {
|
||||
for (const item of it) {
|
||||
@ -15,17 +17,19 @@ export class DoubleQueue<T> {
|
||||
}
|
||||
}
|
||||
get size() {
|
||||
return this._size;
|
||||
return this._size.value;
|
||||
}
|
||||
push(item: T) {
|
||||
const node: Node<T> = { value: item };
|
||||
if (this.size) {
|
||||
(this._tail!.next = node).prev = this._tail;
|
||||
this._tail = node;
|
||||
} else {
|
||||
this._head = this._tail = node;
|
||||
push(...items: T[]) {
|
||||
for (const item of items) {
|
||||
const node: Node<T> = { value: item };
|
||||
if (this.size) {
|
||||
(this._tail!.next = node).prev = this._tail;
|
||||
this._tail = node;
|
||||
} else {
|
||||
this._head = this._tail = node;
|
||||
}
|
||||
this._size.value++;
|
||||
}
|
||||
this._size++;
|
||||
}
|
||||
pop(): T | undefined {
|
||||
if (!this.size) return undefined;
|
||||
@ -35,19 +39,22 @@ export class DoubleQueue<T> {
|
||||
} else {
|
||||
node.prev!.next = undefined;
|
||||
}
|
||||
this._size--;
|
||||
this._tail = node.prev;
|
||||
this._size.value--;
|
||||
if (this.size === 1) this._head = this._tail;
|
||||
return node.value;
|
||||
}
|
||||
unshift(item: T) {
|
||||
const node: Node<T> = { value: item };
|
||||
if (this.size) {
|
||||
(this._head!.prev = node).next = this._head;
|
||||
this._head = node;
|
||||
} else {
|
||||
this._head = this._tail = node;
|
||||
unshift(...items: T[]) {
|
||||
for (const item of items) {
|
||||
const node: Node<T> = { value: item };
|
||||
if (this.size) {
|
||||
(this._head!.prev = node).next = this._head;
|
||||
this._head = node;
|
||||
} else {
|
||||
this._head = this._tail = node;
|
||||
}
|
||||
this._size.value++;
|
||||
}
|
||||
this._size++;
|
||||
}
|
||||
shift(): T | undefined {
|
||||
if (!this.size) return undefined;
|
||||
@ -57,7 +64,8 @@ export class DoubleQueue<T> {
|
||||
} else {
|
||||
node.next!.prev = undefined;
|
||||
}
|
||||
this._size--;
|
||||
this._head = node.next;
|
||||
this._size.value--;
|
||||
if (this.size === 1) this._tail = this._head;
|
||||
return node.value;
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
export type Future<T = void> = PromiseWithResolvers<T>;
|
||||
export class FutureMap<T = void> extends Map<number, Future<T>> {
|
||||
add(tileId: number) {
|
||||
const future = Promise.withResolvers<T>();
|
||||
this.set(tileId, future);
|
||||
export class FutureMap<K, V = void> extends Map<K, Future<V>> {
|
||||
add(key: K) {
|
||||
const future = Promise.withResolvers<V>();
|
||||
this.set(key, future);
|
||||
return future.promise;
|
||||
}
|
||||
|
||||
resolve(tileId: number, value: T) {
|
||||
this.get(tileId)?.resolve(value);
|
||||
this.delete(tileId);
|
||||
resolve(key: K, value: V) {
|
||||
this.get(key)?.resolve(value);
|
||||
this.delete(key);
|
||||
}
|
||||
|
||||
reject(tileId: number, reason: unknown) {
|
||||
this.get(tileId)?.reject(reason);
|
||||
this.delete(tileId);
|
||||
reject(key: K, reason: unknown) {
|
||||
this.get(key)?.reject(reason);
|
||||
this.delete(key);
|
||||
}
|
||||
waitAll() {
|
||||
return Promise.all(this.values().map((f) => f.promise));
|
||||
}
|
||||
}
|
||||
|
@ -19,3 +19,6 @@ export type ComparablePrimitive = Exclude<Primitive, symbol>;
|
||||
export type Exposed<T> = {
|
||||
[P in keyof T]: T[P];
|
||||
};
|
||||
export type ValidKey<T, O extends Record<string, unknown>> = {
|
||||
[P in keyof O]: O[P] extends T ? P : never;
|
||||
}[keyof O];
|
||||
|
@ -11,12 +11,8 @@
|
||||
DEBUG
|
||||
</div>
|
||||
</div>
|
||||
<game2048-score :key="game2048Store.gameKey" :score="game2048Store.maxNumber">
|
||||
最高数字
|
||||
</game2048-score>
|
||||
<game2048-score :key="game2048Store.gameKey" :score="game2048Store.score">
|
||||
得分
|
||||
</game2048-score>
|
||||
<game2048-score-v2 :score="game2048Store.maxNumber">最高数字</game2048-score-v2>
|
||||
<game2048-score-v2 :score="game2048Store.score">得分</game2048-score-v2>
|
||||
</div>
|
||||
<div class="game-header">
|
||||
<div class="game-description">
|
||||
@ -93,7 +89,7 @@ export type SwipeEventMap = {
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import Game2048Button from '@/components/game2048/Game2048Button.vue';
|
||||
import Game2048Score from '@/components/game2048/Game2048Score.vue';
|
||||
import Game2048ScoreV2 from '@/components/game2048/Game2048ScoreV2.vue';
|
||||
import Game2048V2 from '@/components/game2048/Game2048V2.vue';
|
||||
import { GAME_2048_DEBUG } from '@/env';
|
||||
import { useGame2048Store } from '@/stores/2048';
|
||||
@ -117,6 +113,5 @@ useEventListener(
|
||||
|
||||
function click() {
|
||||
game2048Store.$reset();
|
||||
console.log(game2048Store.gameKey);
|
||||
}
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user