@my-games/2048-core 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,43 @@
1
- # Temporary Holding Version
1
+ # @my-games/2048-core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > 2048 游戏的引擎核心,纯 TypeScript,无框架依赖。
4
+
5
+ 本包是 `game-2048` monorepo 的引擎层,`@my-games/2048-vue` 与 `@my-games/2048-react`
6
+ 两个组件适配器都基于本包。**如果你要用的是 Vue 或 React,直接装对应的适配器包更省事。**
7
+
8
+ ## 安装
9
+
10
+ ```bash
11
+ pnpm add @my-games/2048-core
12
+ ```
13
+
14
+ ## 快速开始
15
+
16
+ ```ts
17
+ import { Game2048 } from '@my-games/2048-core';
18
+
19
+ const game = new Game2048({ size: 4 });
20
+ const unsub = game.subscribe((state) => {
21
+ // state = { size, tiles, score, bestScore, status, keepPlaying, moveCount, historySize }
22
+ console.log(state.score, state.tiles.length);
23
+ });
24
+
25
+ game.move('left');
26
+ game.undo();
27
+ unsub();
28
+ ```
29
+
30
+ ## API 概览
31
+
32
+ - `Game2048` 类:`subscribe / getState / getGridValues / move / newGame / undo / continuePlaying / canUndo / clearSaved`
33
+ - AI:`bestMove(grid, { depth })` / `evaluate(grid, weights)` / `simulateUntilEnd(start, opts)`
34
+ - 纯函数原语:`slideGrid` / `slideLineLeft` / `canMove` / `emptyCells` / `gridsEqual` / `hasValue` / `maxValue`
35
+ - 存储:`loadSave` / `writeSave` / `loadBest` / `writeBest` / `tryGetGlobalStorage`
36
+ - 样式:`@my-games/2048-core/styles.css`(组件层用;纯逻辑场景不需要)
37
+
38
+ 完整玩法、Props / Events / ref API、设计取舍见仓库根 README:
39
+ <https://github.com/my-gam/2048#readme>
40
+
41
+ ## License
42
+
43
+ MIT.
package/dist/ai.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { Cell, Direction } from './types';
2
+ /** 静态评估:值越靠近「蛇头」越大越好;空位多给额外奖励。 */
3
+ export declare function evaluate(grid: Cell[][], weights: number[][]): number;
4
+ export interface BestMoveOptions {
5
+ /** 搜索深度(ply),默认 3。 */
6
+ depth?: number;
7
+ }
8
+ /** 返回当前局面下最优方向;无解时 null。 */
9
+ export declare function bestMove(grid: Cell[][], opts?: BestMoveOptions): Direction | null;
10
+ /**
11
+ * 让 AI 自动跑完整局,返回结束时的评估值。用于单测/基准。
12
+ *
13
+ * 起点若为完全空盘(如 `createEmptyGrid`),会先按真实开局规则补两枚 2/4,
14
+ * 否则空盘 canMove=false 会立刻退出。
15
+ */
16
+ export declare function simulateUntilEnd(startGrid: Cell[][], opts?: {
17
+ maxSteps?: number;
18
+ spawn?: (empties: Array<[number, number]>) => {
19
+ r: number;
20
+ c: number;
21
+ v: Cell;
22
+ } | null;
23
+ depth?: number;
24
+ }): {
25
+ grid: Cell[][];
26
+ steps: number;
27
+ };
28
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AA2D/C,kCAAkC;AAClC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAqBpE;AAkCD,MAAM,WAAW,eAAe;IAC9B,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4BAA4B;AAC5B,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAE,eAAoB,GAAG,SAAS,GAAG,IAAI,CAgBrF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,IAAI,EAAE,EAAE,EACnB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACvI;IAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAuCnC"}
package/dist/ai.js ADDED
@@ -0,0 +1,186 @@
1
+ import { canMove, cloneGrid, emptyCells, gridsEqual, slideGrid } from './board';
2
+ /**
3
+ * 2048 AI 求解器(expectimax + 蛇形权值评估)。
4
+ *
5
+ * 结构:
6
+ * - Player 节点:4 个方向里挑 best(跳过非法方向)。
7
+ * - Chance 节点:对每个空位分别按 0.9 / 0.1 概率注入 2 / 4,取期望值。
8
+ * - 叶子:静态评估函数(蛇形权值 + 空位数奖励)。
9
+ *
10
+ * 4×4 棋盘 depth=3 单步耗时 <10ms,能稳定打出 5 位数分数;depth=4 更聪明但
11
+ * 单次可能上百 ms,UI 层若要防卡帧可放到 Worker 里跑。
12
+ */
13
+ const DIRS = ['up', 'left', 'down', 'right'];
14
+ /**
15
+ * 4×4 蛇形权重(top-left 为蛇头 = 最大):
16
+ * ```
17
+ * path (0,0)=15 (0,1)=14 (0,2)=13 (0,3)=12
18
+ * (1,0)= 8 (1,1)= 9 (1,2)=10 (1,3)=11
19
+ * (2,0)= 7 (2,1)= 6 (2,2)= 5 (2,3)= 4
20
+ * (3,0)= 0 (3,1)= 1 (3,2)= 2 (3,3)= 3
21
+ * ```
22
+ * 权重 = 2^path,越接近蛇头权越大。AI 由此把最大 tile 锁在左上角,
23
+ * 形成「left/up 收拢、right/down 保持单调」的稳定策略。
24
+ */
25
+ const SNAKE_WEIGHTS_4 = [
26
+ [32768, 16384, 8192, 4096],
27
+ [256, 512, 1024, 2048],
28
+ [128, 64, 32, 16],
29
+ [1, 2, 4, 8]
30
+ ];
31
+ const NEW_TILE_PROBS = [
32
+ [2, 0.9],
33
+ [4, 0.1]
34
+ ];
35
+ /** 通用蛇形权重(3/4/5/6 尺寸;沿 S 形折返,蛇头在 (0, 0))。 */
36
+ function buildWeights(size) {
37
+ if (size === 4)
38
+ return SNAKE_WEIGHTS_4;
39
+ const cells = size * size;
40
+ const out = Array.from({ length: size }, () => Array(size).fill(0));
41
+ // path index:从蛇头 (0,0) 起 = cells-1(最大权重),蛇尾 = 0(最小)。
42
+ let path = cells - 1;
43
+ for (let r = 0; r < size; r++) {
44
+ const leftToRight = r % 2 === 0;
45
+ for (let i = 0; i < size; i++) {
46
+ const c = leftToRight ? i : size - 1 - i;
47
+ out[r][c] = path;
48
+ path--;
49
+ }
50
+ }
51
+ // 除以格子数避免过大;evaluate 里再乘 1000 恢复量级。
52
+ for (let r = 0; r < size; r++)
53
+ for (let c = 0; c < size; c++)
54
+ out[r][c] = (out[r][c] ?? 0) / cells;
55
+ return out;
56
+ }
57
+ /** 静态评估:值越靠近「蛇头」越大越好;空位多给额外奖励。 */
58
+ export function evaluate(grid, weights) {
59
+ let s = 0;
60
+ let empty = 0;
61
+ let max = 0;
62
+ for (let r = 0; r < grid.length; r++) {
63
+ const row = grid[r];
64
+ const w = weights[r];
65
+ if (!row || !w)
66
+ continue;
67
+ for (let c = 0; c < row.length; c++) {
68
+ const v = row[c] ?? 0;
69
+ if (v === 0) {
70
+ empty++;
71
+ }
72
+ else {
73
+ // 用 log2 抑制数值膨胀,让权重矩阵主导排序;避免一个大数完全压过形状好坏。
74
+ s += Math.log2(v) * (w[c] ?? 0);
75
+ if (v > max)
76
+ max = v;
77
+ }
78
+ }
79
+ }
80
+ // 空位奖励:越多越活泛,权重相对 log2 结果稍高,鼓励「先别把棋盘塞满」。
81
+ return s * 1000 + empty * 200 + Math.log2(max || 1);
82
+ }
83
+ /** 单步的期望值搜索(player 视角)。 */
84
+ function expectimax(grid, depth, myTurn, weights) {
85
+ if (!canMove(grid))
86
+ return -Infinity;
87
+ if (depth === 0)
88
+ return evaluate(grid, weights);
89
+ if (myTurn) {
90
+ let best = -Infinity;
91
+ for (const dir of DIRS) {
92
+ const { grid: next } = slideGrid(grid, dir);
93
+ if (gridsEqual(grid, next))
94
+ continue;
95
+ const v = expectimax(next, depth - 1, false, weights);
96
+ if (v > best)
97
+ best = v;
98
+ }
99
+ // 无合法方向由前置 canMove 拦截;这里防御性兜底
100
+ return best === -Infinity ? evaluate(grid, weights) : best;
101
+ }
102
+ const empties = emptyCells(grid);
103
+ if (empties.length === 0)
104
+ return evaluate(grid, weights);
105
+ let sum = 0;
106
+ const perCell = 1 / empties.length;
107
+ for (const [r, c] of empties) {
108
+ for (const [v, p] of NEW_TILE_PROBS) {
109
+ const g2 = cloneGrid(grid);
110
+ g2[r][c] = v;
111
+ sum += perCell * p * expectimax(g2, depth - 1, true, weights);
112
+ }
113
+ }
114
+ return sum;
115
+ }
116
+ /** 返回当前局面下最优方向;无解时 null。 */
117
+ export function bestMove(grid, opts = {}) {
118
+ const depth = opts.depth ?? 3;
119
+ if (!canMove(grid))
120
+ return null;
121
+ const weights = buildWeights(grid.length);
122
+ let best = null;
123
+ let bestScore = -Infinity;
124
+ for (const dir of DIRS) {
125
+ const { grid: next } = slideGrid(grid, dir);
126
+ if (gridsEqual(grid, next))
127
+ continue;
128
+ const v = expectimax(next, depth - 1, false, weights);
129
+ if (v > bestScore) {
130
+ bestScore = v;
131
+ best = dir;
132
+ }
133
+ }
134
+ return best;
135
+ }
136
+ /**
137
+ * 让 AI 自动跑完整局,返回结束时的评估值。用于单测/基准。
138
+ *
139
+ * 起点若为完全空盘(如 `createEmptyGrid`),会先按真实开局规则补两枚 2/4,
140
+ * 否则空盘 canMove=false 会立刻退出。
141
+ */
142
+ export function simulateUntilEnd(startGrid, opts = {}) {
143
+ const grid = cloneGrid(startGrid);
144
+ const depth = opts.depth ?? 3;
145
+ const defaultSpawn = (empties) => {
146
+ if (empties.length === 0)
147
+ return null;
148
+ const idx = Math.floor(Math.random() * empties.length);
149
+ const cell = empties[idx];
150
+ if (!cell)
151
+ return null;
152
+ const [r, c] = cell;
153
+ return { r, c, v: Math.random() < 0.9 ? 2 : 4 };
154
+ };
155
+ const spawnFn = opts.spawn ?? defaultSpawn;
156
+ const placeInitial = () => {
157
+ const empties = emptyCells(grid);
158
+ const pick = spawnFn(empties);
159
+ if (pick)
160
+ grid[pick.r][pick.c] = pick.v;
161
+ };
162
+ if (emptyCells(grid).length === grid.length * grid.length) {
163
+ placeInitial();
164
+ placeInitial();
165
+ }
166
+ let steps = 0;
167
+ const cap = opts.maxSteps ?? 5000;
168
+ while (steps < cap) {
169
+ const dir = bestMove(grid, { depth });
170
+ if (!dir)
171
+ break;
172
+ const { grid: after } = slideGrid(grid, dir);
173
+ const empties = emptyCells(after);
174
+ if (empties.length > 0) {
175
+ const pick = spawnFn(empties);
176
+ if (pick)
177
+ after[pick.r][pick.c] = pick.v;
178
+ }
179
+ grid.splice(0, grid.length, ...after);
180
+ steps++;
181
+ if (!canMove(grid))
182
+ break;
183
+ }
184
+ return { grid, steps };
185
+ }
186
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { Cell, Direction } from './types';
2
+ /**
3
+ * 纯函数棋盘工具:只用 `Cell[][]`(0 = 空),不涉及 tile id。
4
+ *
5
+ * 设计原则:所有会「改变棋盘」的函数都返回新数组,不原地修改。
6
+ * Game2048 与 AI 都建立在这套底层原语上。
7
+ */
8
+ export declare function createEmptyGrid(size: number): Cell[][];
9
+ export declare function cloneGrid(grid: Cell[][]): Cell[][];
10
+ /** 生成所有空位坐标(row-major 顺序)。 */
11
+ export declare function emptyCells(grid: Cell[][]): Array<[number, number]>;
12
+ /**
13
+ * 把一行「向左压缩 + 相邻等值合并」。
14
+ * 每个 tile 只允许在一次 slide 中参与一次合并,符合 2048 官方规则。
15
+ * 返回:新 line(长度不变)+ 本行得分增量。
16
+ */
17
+ export declare function slideLineLeft(line: readonly Cell[]): {
18
+ line: Cell[];
19
+ gained: number;
20
+ };
21
+ /** 沿方向向量把整个棋盘压缩一次;返回新 grid 与得分。 */
22
+ export declare function slideGrid(grid: Cell[][], dir: Direction): {
23
+ grid: Cell[][];
24
+ gained: number;
25
+ };
26
+ export declare function gridsEqual(a: Cell[][], b: Cell[][]): boolean;
27
+ /** 该方向是否有可移动的空间(含合并)。用于判定是否 game over。 */
28
+ export declare function canMove(grid: Cell[][]): boolean;
29
+ /** 是否达到目标值(默认 2048)。 */
30
+ export declare function hasValue(grid: Cell[][], target: number): boolean;
31
+ /** 把值网格内的最大值取出来(调试用)。 */
32
+ export declare function maxValue(grid: Cell[][]): number;
33
+ //# sourceMappingURL=board.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;GAKG;AAEH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,CAEtD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,CAElD;AAED,8BAA8B;AAC9B,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAUlE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,GAAG;IAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAoBrF;AAMD,mCAAmC;AACnC,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAqB5F;AA0CD,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,CAW5D;AAED,0CAA0C;AAC1C,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,CAO/C;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGhE;AAED,yBAAyB;AACzB,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,MAAM,CAI/C"}
package/dist/board.js ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 纯函数棋盘工具:只用 `Cell[][]`(0 = 空),不涉及 tile id。
3
+ *
4
+ * 设计原则:所有会「改变棋盘」的函数都返回新数组,不原地修改。
5
+ * Game2048 与 AI 都建立在这套底层原语上。
6
+ */
7
+ export function createEmptyGrid(size) {
8
+ return Array.from({ length: size }, () => Array(size).fill(0));
9
+ }
10
+ export function cloneGrid(grid) {
11
+ return grid.map((row) => row.slice());
12
+ }
13
+ /** 生成所有空位坐标(row-major 顺序)。 */
14
+ export function emptyCells(grid) {
15
+ const out = [];
16
+ for (let r = 0; r < grid.length; r++) {
17
+ const row = grid[r];
18
+ if (!row)
19
+ continue;
20
+ for (let c = 0; c < row.length; c++) {
21
+ if (row[c] === 0)
22
+ out.push([r, c]);
23
+ }
24
+ }
25
+ return out;
26
+ }
27
+ /**
28
+ * 把一行「向左压缩 + 相邻等值合并」。
29
+ * 每个 tile 只允许在一次 slide 中参与一次合并,符合 2048 官方规则。
30
+ * 返回:新 line(长度不变)+ 本行得分增量。
31
+ */
32
+ export function slideLineLeft(line) {
33
+ const source = line.filter((v) => v !== 0);
34
+ const result = [];
35
+ let gained = 0;
36
+ let i = 0;
37
+ while (i < source.length) {
38
+ const a = source[i];
39
+ const b = source[i + 1];
40
+ if (b !== undefined && a === b) {
41
+ const merged = a * 2;
42
+ result.push(merged);
43
+ gained += merged;
44
+ i += 2;
45
+ }
46
+ else {
47
+ result.push(a);
48
+ i += 1;
49
+ }
50
+ }
51
+ while (result.length < line.length)
52
+ result.push(0);
53
+ return { line: result, gained };
54
+ }
55
+ function reverseLine(line) {
56
+ return line.slice().reverse();
57
+ }
58
+ /** 沿方向向量把整个棋盘压缩一次;返回新 grid 与得分。 */
59
+ export function slideGrid(grid, dir) {
60
+ const size = grid.length;
61
+ const out = createEmptyGrid(size);
62
+ let gained = 0;
63
+ // 将「读取顺序」抽象成:取每条「line」——按 dir 头部开始往尾部读——
64
+ // 交给 slideLineLeft 处理,再写回对应位置。
65
+ for (let k = 0; k < size; k++) {
66
+ const line = [];
67
+ for (let i = 0; i < size; i++) {
68
+ line.push(readCell(grid, dir, k, i));
69
+ }
70
+ const reversed = dir === 'right' || dir === 'down';
71
+ const input = reversed ? reverseLine(line) : line;
72
+ const { line: processed, gained: g } = slideLineLeft(input);
73
+ gained += g;
74
+ const output = reversed ? reverseLine(processed) : processed;
75
+ for (let i = 0; i < size; i++) {
76
+ writeCell(out, dir, k, i, output[i]);
77
+ }
78
+ }
79
+ return { grid: out, gained };
80
+ }
81
+ /**
82
+ * 将 (k=线序号, i=线内序号) 映射到具体 cell 坐标:
83
+ * - left : row=k, col=i (每行从左侧读)
84
+ * - right: row=k, col=i (读时按 i 反向由 slideGrid 处理)
85
+ * - up : row=i, col=k (每列从上读)
86
+ * - down : row=i, col=k
87
+ * 注意:这里 readCell 只按「物理方向」取格子;反向压缩由 slideGrid 外面 reverseLine 承担。
88
+ */
89
+ function readCell(grid, dir, k, i) {
90
+ switch (dir) {
91
+ case 'left':
92
+ return grid[k]?.[i] ?? 0;
93
+ case 'right':
94
+ return grid[k]?.[i] ?? 0;
95
+ case 'up':
96
+ return grid[i]?.[k] ?? 0;
97
+ case 'down':
98
+ return grid[i]?.[k] ?? 0;
99
+ }
100
+ }
101
+ function writeCell(grid, dir, k, i, v) {
102
+ switch (dir) {
103
+ case 'left':
104
+ case 'right':
105
+ {
106
+ const row = grid[k];
107
+ if (row)
108
+ row[i] = v;
109
+ }
110
+ break;
111
+ case 'up':
112
+ case 'down':
113
+ {
114
+ const row = grid[i];
115
+ if (row)
116
+ row[k] = v;
117
+ }
118
+ break;
119
+ }
120
+ }
121
+ export function gridsEqual(a, b) {
122
+ if (a.length !== b.length)
123
+ return false;
124
+ for (let r = 0; r < a.length; r++) {
125
+ const ra = a[r];
126
+ const rb = b[r];
127
+ if (!ra || !rb)
128
+ return false;
129
+ for (let c = 0; c < ra.length; c++) {
130
+ if (ra[c] !== rb[c])
131
+ return false;
132
+ }
133
+ }
134
+ return true;
135
+ }
136
+ /** 该方向是否有可移动的空间(含合并)。用于判定是否 game over。 */
137
+ export function canMove(grid) {
138
+ const dirs = ['up', 'down', 'left', 'right'];
139
+ for (const d of dirs) {
140
+ const { grid: next } = slideGrid(grid, d);
141
+ if (!gridsEqual(grid, next))
142
+ return true;
143
+ }
144
+ return false;
145
+ }
146
+ /** 是否达到目标值(默认 2048)。 */
147
+ export function hasValue(grid, target) {
148
+ for (const row of grid)
149
+ for (const v of row)
150
+ if (v === target)
151
+ return true;
152
+ return false;
153
+ }
154
+ /** 把值网格内的最大值取出来(调试用)。 */
155
+ export function maxValue(grid) {
156
+ let m = 0;
157
+ for (const row of grid)
158
+ for (const v of row)
159
+ if (v > m)
160
+ m = v;
161
+ return m;
162
+ }
163
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"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"}
package/dist/game.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { BoardSize, Cell, Direction, GameState, RandomSource, SaveStorage } from './types';
2
+ /**
3
+ * Game2048 —— 2048 状态机与规则实现。
4
+ *
5
+ * 与 UI 完全解耦:
6
+ * - 通过 `subscribe(fn)` 推完整 `GameState` 快照(不推 diff,避免框架选择困难)。
7
+ * - 每次公开操作(move/newGame/undo/continuePlaying)成功后同步推送一次。
8
+ * - `Tile.id` 跨帧稳定,UI 用它做 diff 与动画 key;合并中被吞掉的那块直接消失。
9
+ *
10
+ * 存档:如果注入 storage(默认尝试 globalThis.localStorage),会在每次操作后
11
+ * 写入当前 save;构造时自动加载。想禁用:`storage: null`。
12
+ *
13
+ * 撤销:move 前压栈快照,最多保留 undoLimit 步(默认 20)。
14
+ */
15
+ export interface GameOptions {
16
+ /** 棋盘边长,默认 4。 */
17
+ size?: BoardSize;
18
+ /** 随机源([0, 1));单测注入固定序列以获得可复现结果。 */
19
+ rng?: RandomSource;
20
+ /**
21
+ * 存储后端。`undefined` → 尝试取 globalThis.localStorage;
22
+ * `null` → 明确禁用;也可以注入自己实现 SaveStorage 的 mock。
23
+ */
24
+ storage?: SaveStorage | null;
25
+ saveKey?: string;
26
+ bestKey?: string;
27
+ /** 构造时忽略已有存档,直接开新局。 */
28
+ freshStart?: boolean;
29
+ /** 胜利阈值,默认 2048。 */
30
+ target?: number;
31
+ /** 撤销栈深度,默认 20。 */
32
+ undoLimit?: number;
33
+ }
34
+ export declare class Game2048 {
35
+ private readonly size;
36
+ private readonly target;
37
+ private readonly undoLimit;
38
+ private readonly rng;
39
+ private readonly storage;
40
+ private readonly saveKey;
41
+ private readonly bestKey;
42
+ private grid;
43
+ private score;
44
+ private bestScore;
45
+ private status;
46
+ private keepPlaying;
47
+ private moveCount;
48
+ private nextTileId;
49
+ private history;
50
+ private listeners;
51
+ private notifying;
52
+ constructor(opts?: GameOptions);
53
+ /** 订阅状态变化。返回退订函数。注册后立刻收到一次当前快照。 */
54
+ subscribe(fn: (state: GameState) => void): () => void;
55
+ getState(): GameState;
56
+ /** 拿到当前值网格(AI / 单测用);不影响游戏内部。 */
57
+ getGridValues(): Cell[][];
58
+ /** 是否还可以撤销。 */
59
+ canUndo(): boolean;
60
+ /** 沿方向滑动一次。返回 true = 棋盘真的发生了变化;false = 该方向无解。 */
61
+ move(dir: Direction): boolean;
62
+ /** 达到 2048 后用户点击「继续游戏」。 */
63
+ continuePlaying(): void;
64
+ /** 开启新局:清空 board + 重置 score;保留 bestScore。 */
65
+ newGame(): void;
66
+ /** 撤销一步;返回是否成功(历史为空时 false)。 */
67
+ undo(): boolean;
68
+ /** 清除当前存档(不影响 bestScore)。 */
69
+ clearSaved(): void;
70
+ private startNewGame;
71
+ private spawnRandomTile;
72
+ private executeSlide;
73
+ private cellAt;
74
+ private collectTiles;
75
+ private toValueGrid;
76
+ private hasValueAtLeast;
77
+ private takeSnapshot;
78
+ private restoreSnapshot;
79
+ private pushHistory;
80
+ private loadFromSave;
81
+ private commit;
82
+ private notify;
83
+ }
84
+ //# sourceMappingURL=game.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game.d.ts","sourceRoot":"","sources":["../src/game.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,SAAS,EACT,IAAI,EACJ,SAAS,EACT,SAAS,EAET,YAAY,EACZ,WAAW,EAIZ,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,iBAAiB;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,oCAAoC;IACpC,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyBD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAkB;IAEjC,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,GAAE,WAAgB;IAuBlC,mCAAmC;IACnC,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAcrD,QAAQ,IAAI,SAAS;IAarB,iCAAiC;IACjC,aAAa,IAAI,IAAI,EAAE,EAAE;IAIzB,eAAe;IACf,OAAO,IAAI,OAAO;IAIlB,iDAAiD;IACjD,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO;IA4B7B,2BAA2B;IAC3B,eAAe,IAAI,IAAI;IAOvB,6CAA6C;IAC7C,OAAO,IAAI,IAAI;IAKf,gCAAgC;IAChC,IAAI,IAAI,OAAO;IAQf,6BAA6B;IAC7B,UAAU,IAAI,IAAI;IAMlB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,YAAY;IAiEpB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,MAAM;CAmBf"}