@my-games/2048-core 0.0.0-stage → 0.2.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 +42 -2
- package/dist/ai.d.ts +28 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/board.d.ts +33 -0
- package/dist/board.d.ts.map +1 -0
- package/dist/game.d.ts +84 -0
- package/dist/game.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +729 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +60 -3
- package/src/ai.ts +189 -0
- package/src/board.ts +160 -0
- package/src/game.ts +433 -0
- package/src/index.ts +52 -0
- package/src/storage.ts +76 -0
- package/src/styles.css +316 -0
- package/src/types.ts +80 -0
package/README.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @my-games/2048-core
|
|
2
2
|
|
|
3
|
-
|
|
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 { 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
|
package/dist/ai.d.ts.map
ADDED
|
@@ -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/board.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { 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/game.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { 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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @my-games/2048-core
|
|
3
|
+
*
|
|
4
|
+
* 无框架依赖的 2048 引擎。三个入口:
|
|
5
|
+
* - `Game2048` 类:状态机 + 订阅 + 存档 + 撤销。
|
|
6
|
+
* - `bestMove` / `evaluate` / `simulateUntilEnd`:expectimax AI。
|
|
7
|
+
* - 纯函数原语 `slideGrid` / `canMove` / `emptyCells` ...:给测试与自定义玩法用。
|
|
8
|
+
*/
|
|
9
|
+
export { Game2048 } from './game';
|
|
10
|
+
export type { GameOptions } from './game';
|
|
11
|
+
export { bestMove, evaluate, simulateUntilEnd } from './ai';
|
|
12
|
+
export type { BestMoveOptions } from './ai';
|
|
13
|
+
export { canMove, cloneGrid, createEmptyGrid, emptyCells, gridsEqual, hasValue, maxValue, slideGrid, slideLineLeft } from './board';
|
|
14
|
+
export { DEFAULT_BEST_KEY, DEFAULT_SAVE_KEY, SAVE_VERSION, clearSave, isSerializedSave, loadBest, loadSave, tryGetGlobalStorage, writeBest, writeSave } from './storage';
|
|
15
|
+
export type { BoardSize, Cell, Direction, GameStatus, GameState, RandomSource, SaveStorage, SerializedSave, Snapshot, Tile } from './types';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,MAAM,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAE5C,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;AAEnB,YAAY,EACV,SAAS,EACT,IAAI,EACJ,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,IAAI,EACL,MAAM,SAAS,CAAC"}
|