@my-games/2048-core 0.1.0 → 0.3.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,MAAM,CAAC;AAG5D,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,UAAU,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,SAAS,EACT,SAAS,EACV,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/board.ts","../src/storage.ts","../src/game.ts","../src/ai.ts"],"sourcesContent":["import type { Cell, Direction } from './types';\n\n/**\n * 纯函数棋盘工具:只用 `Cell[][]`(0 = 空),不涉及 tile id。\n *\n * 设计原则:所有会「改变棋盘」的函数都返回新数组,不原地修改。\n * Game2048 与 AI 都建立在这套底层原语上。\n */\n\nexport function createEmptyGrid(size: number): Cell[][] {\n return Array.from({ length: size }, () => Array<Cell>(size).fill(0));\n}\n\nexport function cloneGrid(grid: Cell[][]): Cell[][] {\n return grid.map((row) => row.slice());\n}\n\n/** 生成所有空位坐标(row-major 顺序)。 */\nexport function emptyCells(grid: Cell[][]): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n for (let r = 0; r < grid.length; r++) {\n const row = grid[r];\n if (!row) continue;\n for (let c = 0; c < row.length; c++) {\n if (row[c] === 0) out.push([r, c]);\n }\n }\n return out;\n}\n\n/**\n * 把一行「向左压缩 + 相邻等值合并」。\n * 每个 tile 只允许在一次 slide 中参与一次合并,符合 2048 官方规则。\n * 返回:新 line(长度不变)+ 本行得分增量。\n */\nexport function slideLineLeft(line: readonly Cell[]): { line: Cell[]; gained: number } {\n const source = line.filter((v) => v !== 0);\n const result: Cell[] = [];\n let gained = 0;\n let i = 0;\n while (i < source.length) {\n const a = source[i] as Cell;\n const b = source[i + 1];\n if (b !== undefined && a === b) {\n const merged = a * 2;\n result.push(merged);\n gained += merged;\n i += 2;\n } else {\n result.push(a);\n i += 1;\n }\n }\n while (result.length < line.length) result.push(0);\n return { line: result, gained };\n}\n\nfunction reverseLine(line: readonly Cell[]): Cell[] {\n return line.slice().reverse();\n}\n\n/** 沿方向向量把整个棋盘压缩一次;返回新 grid 与得分。 */\nexport function slideGrid(grid: Cell[][], dir: Direction): { grid: Cell[][]; gained: number } {\n const size = grid.length;\n const out = createEmptyGrid(size);\n let gained = 0;\n // 将「读取顺序」抽象成:取每条「line」——按 dir 头部开始往尾部读——\n // 交给 slideLineLeft 处理,再写回对应位置。\n for (let k = 0; k < size; k++) {\n const line: Cell[] = [];\n for (let i = 0; i < size; i++) {\n line.push(readCell(grid, dir, k, i));\n }\n const reversed = dir === 'right' || dir === 'down';\n const input = reversed ? reverseLine(line) : line;\n const { line: processed, gained: g } = slideLineLeft(input);\n gained += g;\n const output = reversed ? reverseLine(processed) : processed;\n for (let i = 0; i < size; i++) {\n writeCell(out, dir, k, i, output[i] as Cell);\n }\n }\n return { grid: out, gained };\n}\n\n/**\n * 将 (k=线序号, i=线内序号) 映射到具体 cell 坐标:\n * - left : row=k, col=i (每行从左侧读)\n * - right: row=k, col=i (读时按 i 反向由 slideGrid 处理)\n * - up : row=i, col=k (每列从上读)\n * - down : row=i, col=k\n * 注意:这里 readCell 只按「物理方向」取格子;反向压缩由 slideGrid 外面 reverseLine 承担。\n */\nfunction readCell(grid: Cell[][], dir: Direction, k: number, i: number): Cell {\n switch (dir) {\n case 'left':\n return grid[k]?.[i] ?? 0;\n case 'right':\n return grid[k]?.[i] ?? 0;\n case 'up':\n return grid[i]?.[k] ?? 0;\n case 'down':\n return grid[i]?.[k] ?? 0;\n }\n}\n\nfunction writeCell(grid: Cell[][], dir: Direction, k: number, i: number, v: Cell): void {\n switch (dir) {\n case 'left':\n case 'right':\n {\n const row = grid[k];\n if (row) row[i] = v;\n }\n break;\n case 'up':\n case 'down':\n {\n const row = grid[i];\n if (row) row[k] = v;\n }\n break;\n }\n}\n\nexport function gridsEqual(a: Cell[][], b: Cell[][]): boolean {\n if (a.length !== b.length) return false;\n for (let r = 0; r < a.length; r++) {\n const ra = a[r];\n const rb = b[r];\n if (!ra || !rb) return false;\n for (let c = 0; c < ra.length; c++) {\n if (ra[c] !== rb[c]) return false;\n }\n }\n return true;\n}\n\n/** 该方向是否有可移动的空间(含合并)。用于判定是否 game over。 */\nexport function canMove(grid: Cell[][]): boolean {\n const dirs: Direction[] = ['up', 'down', 'left', 'right'];\n for (const d of dirs) {\n const { grid: next } = slideGrid(grid, d);\n if (!gridsEqual(grid, next)) return true;\n }\n return false;\n}\n\n/** 是否达到目标值(默认 2048)。 */\nexport function hasValue(grid: Cell[][], target: number): boolean {\n for (const row of grid) for (const v of row) if (v === target) return true;\n return false;\n}\n\n/** 把值网格内的最大值取出来(调试用)。 */\nexport function maxValue(grid: Cell[][]): number {\n let m = 0;\n for (const row of grid) for (const v of row) if (v > m) m = v;\n return m;\n}\n","import type { SaveStorage, SerializedSave } from './types';\n\n/**\n * 存档字段与版本控制。\n *\n * 版本演进策略:读到的 version 与当前 SAVE_VERSION 不一致时,走 `migrate()`;\n * 无法识别的直接丢弃并让 Game 开新局。目前只有 v1。\n */\nexport const SAVE_VERSION = 1 as const;\nexport const DEFAULT_SAVE_KEY = 'game-2048:save';\nexport const DEFAULT_BEST_KEY = 'game-2048:best';\n\nexport function isSerializedSave(x: unknown): x is SerializedSave {\n if (typeof x !== 'object' || x === null) return false;\n const o = x as Record<string, unknown>;\n return (\n o['version'] === SAVE_VERSION &&\n typeof o['size'] === 'number' &&\n Array.isArray(o['tiles']) &&\n typeof o['score'] === 'number'\n );\n}\n\nexport function loadSave(storage: SaveStorage | null, key: string): SerializedSave | null {\n if (!storage) return null;\n const raw = storage.getItem(key);\n if (!raw) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isSerializedSave(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport function writeSave(storage: SaveStorage | null, key: string, save: SerializedSave): void {\n if (!storage) return;\n try {\n storage.setItem(key, JSON.stringify(save));\n } catch {\n /* quota / 隐私模式静默忽略;不阻断游戏 */\n }\n}\n\nexport function clearSave(storage: SaveStorage | null, key: string): void {\n if (!storage) return;\n try {\n storage.removeItem(key);\n } catch {\n /* ignore */\n }\n}\n\nexport function loadBest(storage: SaveStorage | null, key: string): number {\n if (!storage) return 0;\n const raw = storage.getItem(key);\n if (!raw) return 0;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;\n}\n\nexport function writeBest(storage: SaveStorage | null, key: string, best: number): void {\n if (!storage) return;\n try {\n storage.setItem(key, String(Math.floor(best)));\n } catch {\n /* ignore */\n }\n}\n\n/** 浏览器环境下的默认 Storage(找不到返回 null,让 core 在无 DOM 环境也能跑)。 */\nexport function tryGetGlobalStorage(): SaveStorage | null {\n if (typeof globalThis === 'undefined') return null;\n const s = (globalThis as { localStorage?: SaveStorage }).localStorage;\n return s ?? null;\n}\n","import { canMove } from './board';\nimport {\n DEFAULT_BEST_KEY,\n DEFAULT_SAVE_KEY,\n clearSave,\n loadBest,\n loadSave,\n tryGetGlobalStorage,\n writeBest,\n writeSave\n} from './storage';\nimport type {\n BoardSize,\n Cell,\n Direction,\n GameState,\n GameStatus,\n RandomSource,\n SaveStorage,\n SerializedSave,\n Snapshot,\n Tile\n} from './types';\n\n/**\n * Game2048 —— 2048 状态机与规则实现。\n *\n * 与 UI 完全解耦:\n * - 通过 `subscribe(fn)` 推完整 `GameState` 快照(不推 diff,避免框架选择困难)。\n * - 每次公开操作(move/newGame/undo/continuePlaying)成功后同步推送一次。\n * - `Tile.id` 跨帧稳定,UI 用它做 diff 与动画 key;合并中被吞掉的那块直接消失。\n *\n * 存档:如果注入 storage(默认尝试 globalThis.localStorage),会在每次操作后\n * 写入当前 save;构造时自动加载。想禁用:`storage: null`。\n *\n * 撤销:move 前压栈快照,最多保留 undoLimit 步(默认 20)。\n */\n\nexport interface GameOptions {\n /** 棋盘边长,默认 4。 */\n size?: BoardSize;\n /** 随机源([0, 1));单测注入固定序列以获得可复现结果。 */\n rng?: RandomSource;\n /**\n * 存储后端。`undefined` → 尝试取 globalThis.localStorage;\n * `null` → 明确禁用;也可以注入自己实现 SaveStorage 的 mock。\n */\n storage?: SaveStorage | null;\n saveKey?: string;\n bestKey?: string;\n /** 构造时忽略已有存档,直接开新局。 */\n freshStart?: boolean;\n /** 胜利阈值,默认 2048。 */\n target?: number;\n /** 撤销栈深度,默认 20。 */\n undoLimit?: number;\n}\n\nconst DEFAULT_SIZE: BoardSize = 4;\nconst DEFAULT_TARGET = 2048;\nconst DEFAULT_UNDO_LIMIT = 20;\n\n/** 每个方向的「起点角 + 步长」——line(k, i) 从起点开始沿步长走 i 步。 */\ninterface LineCursors {\n /** 遍历 k(第几条 line)时,取起始 cell。 */\n start: (k: number, size: number) => { r: number; c: number };\n /** 沿 line 每前进一格的增量。 */\n step: { dr: number; dc: number };\n}\n\nconst CURSORS: Record<Direction, LineCursors> = {\n left: { start: (k) => ({ r: k, c: 0 }), step: { dr: 0, dc: 1 } },\n right: { start: (k, size) => ({ r: k, c: size - 1 }), step: { dr: 0, dc: -1 } },\n up: { start: (k) => ({ r: 0, c: k }), step: { dr: 1, dc: 0 } },\n down: { start: (k, size) => ({ r: size - 1, c: k }), step: { dr: -1, dc: 0 } }\n};\n\nfunction makeGrid(size: BoardSize): (Tile | null)[][] {\n return Array.from({ length: size }, () => Array<Tile | null>(size).fill(null));\n}\n\nexport class Game2048 {\n private readonly size: BoardSize;\n private readonly target: number;\n private readonly undoLimit: number;\n private readonly rng: RandomSource;\n private readonly storage: SaveStorage | null;\n private readonly saveKey: string;\n private readonly bestKey: string;\n\n private grid: (Tile | null)[][];\n private score = 0;\n private bestScore = 0;\n private status: GameStatus = 'playing';\n private keepPlaying = false;\n private moveCount = 0;\n private nextTileId = 1;\n private history: Snapshot[] = [];\n\n private listeners = new Set<(state: GameState) => void>();\n private notifying = false;\n\n constructor(opts: GameOptions = {}) {\n this.size = opts.size ?? DEFAULT_SIZE;\n this.target = opts.target ?? DEFAULT_TARGET;\n this.undoLimit = opts.undoLimit ?? DEFAULT_UNDO_LIMIT;\n this.rng = opts.rng ?? Math.random;\n this.storage = opts.storage === undefined ? tryGetGlobalStorage() : opts.storage;\n this.saveKey = opts.saveKey ?? DEFAULT_SAVE_KEY;\n this.bestKey = opts.bestKey ?? DEFAULT_BEST_KEY;\n this.bestScore = loadBest(this.storage, this.bestKey);\n this.grid = makeGrid(this.size);\n\n const saved = opts.freshStart ? null : loadSave(this.storage, this.saveKey);\n if (saved && saved.size === this.size) {\n this.loadFromSave(saved);\n // 载入后若棋盘已无可动,标记 lost,让 UI 直接显示终局面板。\n if (!canMove(this.toValueGrid())) this.status = 'lost';\n } else {\n this.startNewGame();\n }\n }\n\n // ---------- 公共 API ----------\n\n /** 订阅状态变化。返回退订函数。注册后立刻收到一次当前快照。 */\n subscribe(fn: (state: GameState) => void): () => void {\n this.listeners.add(fn);\n try {\n fn(this.getState());\n } catch (err) {\n // 与 notify 保持一致:一个 listener 抛错不影响后续订阅者注册。\n // eslint-disable-next-line no-console\n console.error('[game-2048] listener threw on subscribe:', err);\n }\n return () => {\n this.listeners.delete(fn);\n };\n }\n\n getState(): GameState {\n return {\n size: this.size,\n tiles: this.collectTiles().map((t) => ({ ...t })),\n score: this.score,\n bestScore: this.bestScore,\n status: this.status,\n keepPlaying: this.keepPlaying,\n moveCount: this.moveCount,\n historySize: this.history.length\n };\n }\n\n /** 拿到当前值网格(AI / 单测用);不影响游戏内部。 */\n getGridValues(): Cell[][] {\n return this.toValueGrid();\n }\n\n /** 是否还可以撤销。 */\n canUndo(): boolean {\n return this.history.length > 0;\n }\n\n /** 沿方向滑动一次。返回 true = 棋盘真的发生了变化;false = 该方向无解。 */\n move(dir: Direction): boolean {\n if (this.status === 'lost') return false;\n if (this.status === 'won' && !this.keepPlaying) return false;\n\n const snap = this.takeSnapshot();\n\n // 每一 move 开始时清掉上一帧的动画标记,让 UI 只对「本次」变化响应。\n for (const row of this.grid) for (const t of row) if (t) { t.isNew = false; t.merged = false; }\n\n const { moved, gained } = this.executeSlide(dir);\n if (!moved) return false;\n\n this.score += gained;\n this.pushHistory(snap);\n this.moveCount += 1;\n\n if (!this.keepPlaying && this.hasValueAtLeast(this.target)) {\n this.status = 'won';\n }\n\n this.spawnRandomTile();\n\n if (!canMove(this.toValueGrid())) this.status = 'lost';\n\n this.commit();\n return true;\n }\n\n /** 达到 2048 后用户点击「继续游戏」。 */\n continuePlaying(): void {\n if (this.status !== 'won') return;\n this.keepPlaying = true;\n this.status = 'playing';\n this.commit();\n }\n\n /** 开启新局:清空 board + 重置 score;保留 bestScore。 */\n newGame(): void {\n this.startNewGame();\n this.commit();\n }\n\n /** 撤销一步;返回是否成功(历史为空时 false)。 */\n undo(): boolean {\n const s = this.history.pop();\n if (!s) return false;\n this.restoreSnapshot(s);\n this.commit();\n return true;\n }\n\n /** 清除当前存档(不影响 bestScore)。 */\n clearSaved(): void {\n clearSave(this.storage, this.saveKey);\n }\n\n // ---------- 内部 ----------\n\n private startNewGame(): void {\n this.grid = makeGrid(this.size);\n this.score = 0;\n this.status = 'playing';\n this.keepPlaying = false;\n this.moveCount = 0;\n this.nextTileId = 1;\n this.history = [];\n this.spawnRandomTile();\n this.spawnRandomTile();\n }\n\n private spawnRandomTile(): void {\n const empties: Array<[number, number]> = [];\n for (let r = 0; r < this.size; r++) {\n for (let c = 0; c < this.size; c++) {\n if (!this.grid[r]?.[c]) empties.push([r, c]);\n }\n }\n if (empties.length === 0) return;\n const idx = Math.min(empties.length - 1, Math.floor(this.rng() * empties.length));\n const slot = empties[idx];\n if (!slot) return;\n const [r, c] = slot;\n const value = this.rng() < 0.9 ? 2 : 4;\n const tile: Tile = { id: this.nextTileId++, value, row: r, col: c, isNew: true, merged: false };\n const row = this.grid[r];\n if (row) row[c] = tile;\n }\n\n private executeSlide(dir: Direction): { moved: boolean; gained: number } {\n const cursors = CURSORS[dir];\n let moved = false;\n let gained = 0;\n\n for (let k = 0; k < this.size; k++) {\n // 按「从起点出发」的顺序读取本 line 上的所有非空 tile。\n const lineTiles: Tile[] = [];\n const slotOfTile = new Map<Tile, number>(); // 记录原本位于 line 的第几格\n for (let i = 0; i < this.size; i++) {\n const { r, c } = this.cellAt(cursors, k, i);\n const t = this.grid[r]?.[c];\n if (t) {\n lineTiles.push(t);\n slotOfTile.set(t, i);\n }\n }\n\n // compact + 相邻等值合并(每 tile 只能参与一次合并)\n const result: Tile[] = [];\n let idx = 0;\n while (idx < lineTiles.length) {\n const cur = lineTiles[idx];\n const next = lineTiles[idx + 1];\n if (!cur) break;\n if (next && cur.value === next.value) {\n cur.value *= 2;\n cur.merged = true;\n gained += cur.value;\n result.push(cur);\n idx += 2;\n moved = true; // 至少发生了一次合并\n } else {\n result.push(cur);\n idx += 1;\n }\n }\n\n // 检测「仅滑动、未合并」的情况:任一 tile 落位与原位不同。\n for (let i = 0; i < result.length; i++) {\n const t = result[i];\n if (!t) continue;\n const originalSlot = slotOfTile.get(t);\n if (originalSlot !== i) moved = true;\n }\n\n // 清空 line 再按新顺序写回;被吞掉的 tile 不出现在 result 中即视为消失。\n for (let i = 0; i < this.size; i++) {\n const { r, c } = this.cellAt(cursors, k, i);\n const row = this.grid[r];\n if (row) row[c] = null;\n }\n for (let i = 0; i < result.length; i++) {\n const t = result[i];\n if (!t) continue;\n const { r, c } = this.cellAt(cursors, k, i);\n const row = this.grid[r];\n if (row) row[c] = t;\n t.row = r;\n t.col = c;\n }\n }\n return { moved, gained };\n }\n\n private cellAt(cursors: LineCursors, k: number, i: number): { r: number; c: number } {\n const start = cursors.start(k, this.size);\n return { r: start.r + i * cursors.step.dr, c: start.c + i * cursors.step.dc };\n }\n\n private collectTiles(): Tile[] {\n const out: Tile[] = [];\n for (const row of this.grid) for (const t of row) if (t) out.push(t);\n return out;\n }\n\n private toValueGrid(): Cell[][] {\n const g: Cell[][] = Array.from({ length: this.size }, () => Array<Cell>(this.size).fill(0));\n for (const t of this.collectTiles()) g[t.row]![t.col] = t.value;\n return g;\n }\n\n private hasValueAtLeast(v: number): boolean {\n for (const t of this.collectTiles()) if (t.value >= v) return true;\n return false;\n }\n\n private takeSnapshot(): Snapshot {\n return {\n tiles: this.collectTiles().map((t) => ({ ...t })),\n score: this.score,\n status: this.status,\n keepPlaying: this.keepPlaying,\n moveCount: this.moveCount\n };\n }\n\n private restoreSnapshot(s: Snapshot): void {\n this.grid = makeGrid(this.size);\n let maxId = 0;\n for (const t of s.tiles) {\n const clone: Tile = { ...t };\n const row = this.grid[clone.row];\n if (row) row[clone.col] = clone;\n if (clone.id > maxId) maxId = clone.id;\n }\n this.nextTileId = maxId + 1;\n this.score = s.score;\n this.status = s.status;\n this.keepPlaying = s.keepPlaying;\n this.moveCount = s.moveCount;\n }\n\n private pushHistory(s: Snapshot): void {\n this.history.push(s);\n if (this.history.length > this.undoLimit) this.history.shift();\n }\n\n private loadFromSave(save: SerializedSave): void {\n this.grid = makeGrid(this.size);\n let maxId = 0;\n for (const raw of save.tiles) {\n const t: Tile = {\n id: raw.id,\n value: raw.value,\n row: raw.row,\n col: raw.col,\n isNew: false,\n merged: false\n };\n if (t.row >= 0 && t.row < this.size && t.col >= 0 && t.col < this.size) {\n const row = this.grid[t.row];\n if (row) row[t.col] = t;\n }\n if (t.id > maxId) maxId = t.id;\n }\n this.score = save.score;\n this.status = save.status === 'won' ? 'won' : save.status === 'lost' ? 'lost' : 'playing';\n this.keepPlaying = save.keepPlaying;\n this.moveCount = save.moveCount;\n this.nextTileId = Math.max(maxId + 1, save.nextTileId ?? maxId + 1);\n }\n\n private commit(): void {\n if (this.score > this.bestScore) {\n this.bestScore = this.score;\n writeBest(this.storage, this.bestKey, this.bestScore);\n }\n writeSave(this.storage, this.saveKey, {\n version: 1,\n size: this.size,\n tiles: this.collectTiles().map((t) => ({ ...t })),\n score: this.score,\n status: this.status,\n keepPlaying: this.keepPlaying,\n moveCount: this.moveCount,\n nextTileId: this.nextTileId\n });\n this.notify();\n }\n\n private notify(): void {\n if (this.notifying) return;\n this.notifying = true;\n try {\n const snapshot = this.getState();\n for (const fn of this.listeners) {\n try {\n fn(snapshot);\n } catch (err) {\n // 一个 listener 抛错不影响其他 listener;交给 console.error,让 UI 层自己决定\n // 要不要用 subscribe 上层包一层错误处理。\n // eslint-disable-next-line no-console\n console.error('[game-2048] listener threw:', err);\n }\n }\n } finally {\n this.notifying = false;\n }\n }\n}\n","import { canMove, cloneGrid, emptyCells, gridsEqual, slideGrid } from './board';\nimport type { Cell, Direction } from './types';\n\n/**\n * 2048 AI 求解器(expectimax + 蛇形权值评估)。\n *\n * 结构:\n * - Player 节点:4 个方向里挑 best(跳过非法方向)。\n * - Chance 节点:对每个空位分别按 0.9 / 0.1 概率注入 2 / 4,取期望值。\n * - 叶子:静态评估函数(蛇形权值 + 空位数奖励)。\n *\n * 4×4 棋盘 depth=3 单步耗时 <10ms,能稳定打出 5 位数分数;depth=4 更聪明但\n * 单次可能上百 ms,UI 层若要防卡帧可放到 Worker 里跑。\n */\n\nconst DIRS: Direction[] = ['up', 'left', 'down', 'right'];\n\n/**\n * 4×4 蛇形权重(top-left 为蛇头 = 最大):\n * ```\n * path (0,0)=15 (0,1)=14 (0,2)=13 (0,3)=12\n * (1,0)= 8 (1,1)= 9 (1,2)=10 (1,3)=11\n * (2,0)= 7 (2,1)= 6 (2,2)= 5 (2,3)= 4\n * (3,0)= 0 (3,1)= 1 (3,2)= 2 (3,3)= 3\n * ```\n * 权重 = 2^path,越接近蛇头权越大。AI 由此把最大 tile 锁在左上角,\n * 形成「left/up 收拢、right/down 保持单调」的稳定策略。\n */\nconst SNAKE_WEIGHTS_4: number[][] = [\n [32768, 16384, 8192, 4096],\n [256, 512, 1024, 2048],\n [128, 64, 32, 16],\n [1, 2, 4, 8]\n];\n\nconst NEW_TILE_PROBS: ReadonlyArray<readonly [number, number]> = [\n [2, 0.9],\n [4, 0.1]\n] as const;\n\n/** 通用蛇形权重(3/4/5/6 尺寸;沿 S 形折返,蛇头在 (0, 0))。 */\nfunction buildWeights(size: number): number[][] {\n if (size === 4) return SNAKE_WEIGHTS_4;\n const cells = size * size;\n const out: number[][] = Array.from({ length: size }, () => Array<number>(size).fill(0));\n // path index:从蛇头 (0,0) 起 = cells-1(最大权重),蛇尾 = 0(最小)。\n let path = cells - 1;\n for (let r = 0; r < size; r++) {\n const leftToRight = r % 2 === 0;\n for (let i = 0; i < size; i++) {\n const c = leftToRight ? i : size - 1 - i;\n out[r]![c] = path;\n path--;\n }\n }\n // 除以格子数避免过大;evaluate 里再乘 1000 恢复量级。\n for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) out[r]![c] = (out[r]![c] ?? 0) / cells;\n return out;\n}\n\n/** 静态评估:值越靠近「蛇头」越大越好;空位多给额外奖励。 */\nexport function evaluate(grid: Cell[][], weights: number[][]): number {\n let s = 0;\n let empty = 0;\n let max = 0;\n for (let r = 0; r < grid.length; r++) {\n const row = grid[r];\n const w = weights[r];\n if (!row || !w) continue;\n for (let c = 0; c < row.length; c++) {\n const v = row[c] ?? 0;\n if (v === 0) {\n empty++;\n } else {\n // 用 log2 抑制数值膨胀,让权重矩阵主导排序;避免一个大数完全压过形状好坏。\n s += Math.log2(v) * (w[c] ?? 0);\n if (v > max) max = v;\n }\n }\n }\n // 空位奖励:越多越活泛,权重相对 log2 结果稍高,鼓励「先别把棋盘塞满」。\n return s * 1000 + empty * 200 + Math.log2(max || 1);\n}\n\n/** 单步的期望值搜索(player 视角)。 */\nfunction expectimax(grid: Cell[][], depth: number, myTurn: boolean, weights: number[][]): number {\n if (!canMove(grid)) return -Infinity;\n if (depth === 0) return evaluate(grid, weights);\n\n if (myTurn) {\n let best = -Infinity;\n for (const dir of DIRS) {\n const { grid: next } = slideGrid(grid, dir);\n if (gridsEqual(grid, next)) continue;\n const v = expectimax(next, depth - 1, false, weights);\n if (v > best) best = v;\n }\n // 无合法方向由前置 canMove 拦截;这里防御性兜底\n return best === -Infinity ? evaluate(grid, weights) : best;\n }\n\n const empties = emptyCells(grid);\n if (empties.length === 0) return evaluate(grid, weights);\n\n let sum = 0;\n const perCell = 1 / empties.length;\n for (const [r, c] of empties) {\n for (const [v, p] of NEW_TILE_PROBS) {\n const g2 = cloneGrid(grid);\n g2[r]![c] = v;\n sum += perCell * p * expectimax(g2, depth - 1, true, weights);\n }\n }\n return sum;\n}\n\nexport interface BestMoveOptions {\n /** 搜索深度(ply),默认 3。 */\n depth?: number;\n}\n\n/** 返回当前局面下最优方向;无解时 null。 */\nexport function bestMove(grid: Cell[][], opts: BestMoveOptions = {}): Direction | null {\n const depth = opts.depth ?? 3;\n if (!canMove(grid)) return null;\n const weights = buildWeights(grid.length);\n let best: Direction | null = null;\n let bestScore = -Infinity;\n for (const dir of DIRS) {\n const { grid: next } = slideGrid(grid, dir);\n if (gridsEqual(grid, next)) continue;\n const v = expectimax(next, depth - 1, false, weights);\n if (v > bestScore) {\n bestScore = v;\n best = dir;\n }\n }\n return best;\n}\n\n/**\n * 让 AI 自动跑完整局,返回结束时的评估值。用于单测/基准。\n *\n * 起点若为完全空盘(如 `createEmptyGrid`),会先按真实开局规则补两枚 2/4,\n * 否则空盘 canMove=false 会立刻退出。\n */\nexport function simulateUntilEnd(\n startGrid: Cell[][],\n opts: { maxSteps?: number; spawn?: (empties: Array<[number, number]>) => { r: number; c: number; v: Cell } | null; depth?: number } = {}\n): { grid: Cell[][]; steps: number } {\n const grid = cloneGrid(startGrid);\n const depth = opts.depth ?? 3;\n const defaultSpawn = (empties: Array<[number, number]>): { r: number; c: number; v: Cell } | null => {\n if (empties.length === 0) return null;\n const idx = Math.floor(Math.random() * empties.length);\n const cell = empties[idx];\n if (!cell) return null;\n const [r, c] = cell;\n return { r, c, v: Math.random() < 0.9 ? 2 : 4 };\n };\n const spawnFn = opts.spawn ?? defaultSpawn;\n\n const placeInitial = () => {\n const empties = emptyCells(grid);\n const pick = spawnFn(empties);\n if (pick) grid[pick.r]![pick.c] = pick.v;\n };\n if (emptyCells(grid).length === grid.length * grid.length) {\n placeInitial();\n placeInitial();\n }\n\n let steps = 0;\n const cap = opts.maxSteps ?? 5000;\n while (steps < cap) {\n const dir = bestMove(grid, { depth });\n if (!dir) break;\n const { grid: after } = slideGrid(grid, dir);\n const empties = emptyCells(after);\n if (empties.length > 0) {\n const pick = spawnFn(empties);\n if (pick) after[pick.r]![pick.c] = pick.v;\n }\n grid.splice(0, grid.length, ...after);\n steps++;\n if (!canMove(grid)) break;\n }\n return { grid, steps };\n}\n"],"mappings":"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"}