This commit is contained in:
parent
72074f79af
commit
14525db49c
@ -190,7 +190,7 @@
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup>
|
||||
import { useGame2048Store, type GameState, type Tile } from '@/stores/2048';
|
||||
import { useGame2048Store, type GameState } from '@/stores/2048';
|
||||
import { chainIterables } from '@/utils';
|
||||
import { get2DArrayItem } from '@/utils/array';
|
||||
import { type Future, FutureMap } from '@/utils/future';
|
||||
@ -201,6 +201,11 @@ import { add, pick, sample, shuffle } from 'lodash-es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, useTemplateRef, watch } from 'vue';
|
||||
const game2048Store = useGame2048Store();
|
||||
interface Tile {
|
||||
id: number;
|
||||
number: number;
|
||||
fromOthers: boolean;
|
||||
}
|
||||
type SingleTileLine = (Tile | undefined)[];
|
||||
type OverlappedTileLine = Tile[][];
|
||||
/**
|
||||
@ -311,7 +316,7 @@ const tileBackgroundColorMapping: Record<number, string | undefined> = {
|
||||
*/
|
||||
function storeTileGrid() {
|
||||
game2048Store.rawTileGrid = tileGrid.map((row) =>
|
||||
row.map((tiles) => tiles.map((tile) => pick(tile, 'number', 'removed'))),
|
||||
row.map((tiles) => tiles.map((tile) => pick(tile, 'number'))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -356,7 +361,6 @@ function addRandomTiles(randomCount: number, randomNumbers: number[] = [2, 4]) {
|
||||
const tile: Tile = {
|
||||
id: maxId++,
|
||||
number: sample(randomNumbers) ?? 2,
|
||||
removed: false,
|
||||
fromOthers: false,
|
||||
};
|
||||
tiles.push(tile);
|
||||
@ -396,12 +400,9 @@ function mergeLineImpl(tileRelationMap: TileRelationMap, line: SingleTileLine, o
|
||||
if (prevTile.number === tile.number) {
|
||||
finished = false;
|
||||
changed = true;
|
||||
prevTile.removed = true;
|
||||
tile.removed = true;
|
||||
const newTile: Tile = {
|
||||
id: maxId++,
|
||||
number: tile.number * 2,
|
||||
removed: false,
|
||||
fromOthers: true,
|
||||
};
|
||||
tileRelationMap.set(
|
||||
@ -723,7 +724,6 @@ useEventListener(gameContainerRef, 'touchend', (e) => {
|
||||
triggerMove(x, y);
|
||||
startPos = { x: -1, y: -1 };
|
||||
});
|
||||
|
||||
useEventListener(document, 'keydown', async (e) => {
|
||||
e.preventDefault();
|
||||
move(e.key);
|
||||
|
@ -419,7 +419,7 @@ const move = async (key: Directions) => {
|
||||
}
|
||||
const tilesToAdd: Exposed<Tile>[] = [];
|
||||
if (tilesToMove.size) {
|
||||
tilesToAdd.push(...addRandomTiles(2));
|
||||
tilesToAdd.push(...addRandomTiles(1));
|
||||
}
|
||||
await Promise.all(Iterator.from(tilesToMove).map((t) => t.move()));
|
||||
tilesToHide.forEach((t) => t.hide());
|
||||
|
@ -3,20 +3,14 @@ import { create2DArray } from '@/utils/array';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { defineStore } from 'pinia';
|
||||
import { readonly, watch } from 'vue';
|
||||
|
||||
export interface Tile {
|
||||
id: number;
|
||||
number: number;
|
||||
removed: boolean;
|
||||
fromOthers: boolean;
|
||||
}
|
||||
export type TileSerialized = { number: number };
|
||||
|
||||
export type GameState = 'playing' | 'succeed' | 'failed';
|
||||
export const useGame2048Store = defineStore('2048', () => {
|
||||
const { key: gameKey, refresh: refreshGame } = useRefresh();
|
||||
|
||||
function create() {
|
||||
return create2DArray<Tile[]>(height.value, width.value, () => []);
|
||||
return create2DArray<TileSerialized[]>(height.value, width.value, () => []);
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
@ -38,10 +32,7 @@ export const useGame2048Store = defineStore('2048', () => {
|
||||
height.value = 4;
|
||||
successNumber.value = 2048;
|
||||
watch([width, height], $reset, { flush: 'sync' });
|
||||
const rawTileGrid = useLocalStorage<Pick<Tile, 'number' | 'removed'>[][][]>(
|
||||
'2048-tile-grid',
|
||||
create(),
|
||||
);
|
||||
const rawTileGrid = useLocalStorage<TileSerialized[][][]>('2048-tile-grid', create());
|
||||
watch(
|
||||
rawTileGrid,
|
||||
() => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { MaybeArray } from './types';
|
||||
import type { MaybeArray } from '@/utils/types';
|
||||
|
||||
export * from './zip';
|
||||
export function create2DArray<T>(
|
||||
height: number,
|
||||
width: number,
|
||||
@ -16,22 +17,6 @@ export function* iter2DArray<T>(grid: T[][]): Generator<[number, number, T], voi
|
||||
}
|
||||
}
|
||||
}
|
||||
export type Zip<T extends Iterable<unknown>[]> = T extends [
|
||||
infer R,
|
||||
...infer S extends Iterable<unknown>[],
|
||||
]
|
||||
? R extends Iterable<infer U>
|
||||
? [U, ...Zip<S>]
|
||||
: []
|
||||
: [];
|
||||
export function* zip<T extends Iterable<unknown>[]>(...its: T): Generator<Zip<T>, void, unknown> {
|
||||
const iterators = its.map((it) => it[Symbol.iterator]());
|
||||
while (true) {
|
||||
const nextValues = iterators.map((it) => it.next());
|
||||
if (nextValues.some(({ done }) => done)) break;
|
||||
yield nextValues.map(({ value }) => value) as any;
|
||||
}
|
||||
}
|
||||
export function* entries<T>(it: Iterable<T>): Generator<[number, T], void, unknown> {
|
||||
let i = 0;
|
||||
for (const value of it) {
|
46
src/utils/array/zip.ts
Normal file
46
src/utils/array/zip.ts
Normal file
@ -0,0 +1,46 @@
|
||||
type ZipSimple<T extends readonly Iterable<unknown>[]> = {
|
||||
[P in keyof T]: T[P] extends Iterable<infer U> ? U : never;
|
||||
};
|
||||
type FixedTuple<T = unknown> = (readonly [T, ...T[]] & readonly [...T[], T]) | readonly [];
|
||||
type ZipTuple1<T extends FixedTuple[]> = {
|
||||
[I in keyof T]: T[I] extends FixedTuple
|
||||
? T[I] extends readonly [infer R, ...infer S extends FixedTuple]
|
||||
? [R, S]
|
||||
: never
|
||||
: never;
|
||||
};
|
||||
type IndexGetter<T extends unknown[][], I extends keyof T[number]> = {
|
||||
[I2 in keyof T]: T[I2][I];
|
||||
};
|
||||
type ZipTuple2<T extends [unknown, FixedTuple][]> = true extends {
|
||||
[I in keyof T]: [T[I]] extends [never]
|
||||
? true
|
||||
: true extends {
|
||||
[I2 in keyof T[I]]: [T[I][I2]] extends [never] ? true : false;
|
||||
}[keyof T[I]]
|
||||
? true
|
||||
: false;
|
||||
}[keyof T]
|
||||
? never
|
||||
: [IndexGetter<T, 0>, IndexGetter<T, 1>];
|
||||
type ZipTuple3<T extends [unknown[], FixedTuple[]]> = [T] extends [never]
|
||||
? []
|
||||
: T extends [[], []]
|
||||
? []
|
||||
: [T[0], ...ZipTuple<T[1]>];
|
||||
type ZipTuple<T extends FixedTuple[]> = ZipTuple3<ZipTuple2<ZipTuple1<T>>>;
|
||||
type Zip<T extends readonly Iterable<unknown>[]> = T extends FixedTuple[]
|
||||
? ZipTuple<T>
|
||||
: ZipSimple<T>;
|
||||
export function* zip<T extends readonly Iterable<unknown>[]>(
|
||||
...iterables: T
|
||||
): Generator<Zip<T>, void, unknown> {
|
||||
const its = iterables.map((it) => Iterator.from(it));
|
||||
while (true) {
|
||||
const nexts = its.map((it) => it.next());
|
||||
if (nexts.some(({ done }) => done)) {
|
||||
return;
|
||||
}
|
||||
yield nexts.map(({ value }) => value) as any;
|
||||
}
|
||||
}
|
@ -40,8 +40,14 @@ export class TableView<T> {
|
||||
yield this.getLine(i, xFirst);
|
||||
}
|
||||
}
|
||||
toArray(xFirst: boolean = false) {
|
||||
return Array.from(this.iter(xFirst).map((line) => line.toArray()));
|
||||
toArray<R = T | undefined>(xFirst: boolean = false, converter?: (v: T | undefined) => R) {
|
||||
return Array.from(
|
||||
this.iter(xFirst).map((line) =>
|
||||
Iterator.from(line)
|
||||
.map((v) => (converter ? converter(v) : (v as unknown as R)))
|
||||
.toArray(),
|
||||
),
|
||||
);
|
||||
}
|
||||
clear() {
|
||||
this.map.clear();
|
||||
|
Loading…
x
Reference in New Issue
Block a user