2048 小游戏(精简起步版)
最精简的可玩 2048。游戏状态在 Agent 内存(demo 用,进程重启丢)。 完整生产版(含排行榜 + 持久化)见 2048 完整教程。
代码
import { HasheeAgent } from "@hasheeai/agent-sdk-ts";import { ulid } from "ulid";
const agent = await HasheeAgent.init({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, baseUrl: "https://api.hashee.ai", connectionMode: "websocket",});
type Game = { artifactId: string; convId: string; board: number[][]; score: number; over: boolean; revision: number;};const games = new Map<string, Game>(); // key = `${convId}:${userId}`
function newBoard(): number[][] { const b: number[][] = Array.from({ length: 4 }, () => Array(4).fill(0)); spawn(b); spawn(b); return b;}
function spawn(b: number[][]): boolean { const empty: [number, number][] = []; for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) if (b[r][c] === 0) empty.push([r, c]); if (empty.length === 0) return false; const [r, c] = empty[Math.floor(Math.random() * empty.length)]; b[r][c] = Math.random() < 0.9 ? 2 : 4; return true;}
function move(b: number[][], dir: "up" | "down" | "left" | "right"): { gain: number; moved: boolean } { const rotate = (m: number[][], n: number) => { let r = m; for (let i = 0; i < n; i++) r = r[0].map((_, c) => r.map((row) => row[c]).reverse()); return r; }; const compact = (row: number[]) => { const arr = row.filter((x) => x !== 0); let g = 0; for (let i = 0; i < arr.length - 1; i++) { if (arr[i] === arr[i + 1]) { arr[i] *= 2; g += arr[i]; arr.splice(i + 1, 1); } } while (arr.length < 4) arr.push(0); return { row: arr, gain: g }; };
const times = { left: 0, up: 1, right: 2, down: 3 }[dir]; let rotated = rotate(b, times); let totalGain = 0; let moved = false; for (let r = 0; r < 4; r++) { const { row, gain } = compact(rotated[r]); if (row.some((v, i) => v !== rotated[r][i])) moved = true; rotated[r] = row; totalGain += gain; } rotated = rotate(rotated, (4 - times) % 4); for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) b[r][c] = rotated[r][c]; return { gain: totalGain, moved };}
function canMove(b: number[][]): boolean { for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) { if (b[r][c] === 0) return true; if (c < 3 && b[r][c] === b[r][c + 1]) return true; if (r < 3 && b[r][c] === b[r + 1][c]) return true; } return false;}
agent.addMessageHandler(async (msg) => { if (msg.payload?.type !== "text") return; if (msg.payload.text.trim() !== "/play 2048") return;
const game: Game = { artifactId: ulid(), convId: msg.conversation_id, board: newBoard(), score: 0, over: false, revision: 0, }; games.set(`${msg.conversation_id}:${msg.sender_id}`, game);
await agent.sendArtifact(msg.conversation_id, { artifact: { artifact_id: game.artifactId, subtype: "app", title: "2048", payload: { ui: "2048", board: game.board, score: game.score, game_over: false }, capability_flags: { allows_response: true }, }, });});
agent.addEventHandler(async (event) => { if (event.type !== "artifact_response") return; if (event.payload.action !== "move") return;
const game = [...games.values()].find((g) => g.artifactId === event.payload.ref_artifact); if (!game || game.over) return; const dir = event.payload.payload?.direction; if (!["up", "down", "left", "right"].includes(dir)) return;
const { gain, moved } = move(game.board, dir); if (!moved) return; game.score += gain; spawn(game.board); if (!canMove(game.board)) game.over = true; game.revision++;
await agent.updateArtifact(game.convId, { ref_artifact: game.artifactId, based_on_revision: game.revision - 1, artifact: { payload: { ui: "2048", board: game.board, score: game.score, game_over: game.over }, }, });});
console.log("[2048] up");触发
用户:/play 2048Agent → 游戏卡(4x4 棋盘 + 分数)用户滑动方向 → 客户端发 artifact_response {action:"move", direction:"up"}Agent → updateArtifact 推新棋盘限制
- 单进程内存:崩溃丢局
- 100 update 上限:300+ 步的长局会触发 — 见 Artifact 高级 的”100 update 上限”workaround