跳转到内容

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 2048
Agent → 游戏卡(4x4 棋盘 + 分数)
用户滑动方向 → 客户端发 artifact_response {action:"move", direction:"up"}
Agent → updateArtifact 推新棋盘

限制

  • 单进程内存:崩溃丢局
  • 100 update 上限:300+ 步的长局会触发 — 见 Artifact 高级 的”100 update 上限”workaround

进阶