🦄 refactor: 暂存2048
All checks were successful
ci / build (push) Successful in 51s

This commit is contained in:
Litrix 2025-03-07 14:47:32 +08:00
parent 72074f79af
commit 14525db49c
6 changed files with 67 additions and 39 deletions

View File

@ -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);

View File

@ -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());

View File

@ -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,
() => {

View File

@ -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
View 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;
}
}

View File

@ -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();