clone

minesweeper as an mcp server: agents start games, play moves in batches and watch the board

main
mcpsweeper / test / game.test.ts
game.test.ts 3.5K
1import { describe, expect, it } from "vitest";2import { applyBatch, createNoGuessGame, toAgentPosition, toObservation, type GameState } from "../src/game.ts";34describe("MCPSweeper engine", () => {5 it("generates a certified board with a visible opening", () => {6 const state = createNoGuessGame({7 id: "74bd6db1-64e0-46fa-93c6-ed8b41957c67",8 seed: "engine-test",9 width: 16,10 height: 16,11 density: 0.1,12 maxAttempts: 500,13 });14 const observation = toObservation(state);15 expect(state.certifiedNoGuess).toBe(true);16 expect(observation.revealed_count).toBeGreaterThan(0);17 expect(observation.rows).toHaveLength(16);18 });1920 it("loses when a hidden mine is revealed", () => {21 const state = createNoGuessGame({22 id: "ec2a5195-6c00-48a8-b338-dff59e54a151",23 seed: "detonation-test",24 width: 16,25 height: 16,26 density: 0.1,27 maxAttempts: 500,28 });29 const mineIndex = state.mines.findIndex((value) => value === 1);30 const event = applyBatch(state, {31 reveal: [{ row: Math.floor(mineIndex / state.width), col: mineIndex % state.width }],32 flag: [],33 unflag: [],34 intent: "Deliberate test detonation",35 });36 expect(event.status).toBe("lost");37 expect(event.exploded).not.toBeNull();38 expect(event.revealedValues).toEqual([]);39 expect(state.revision).toBe(1);40 });4142 it("records clue values needed to replay a batch", () => {43 const state = createNoGuessGame({44 id: "f2e03895-c572-4d4b-bcaf-718ff469b18d",45 seed: "replay-test",46 width: 16,47 height: 16,48 density: 0.1,49 maxAttempts: 500,50 });51 const hiddenSafe = state.revealed.findIndex((value, index) => value === 0 && state.mines[index] === 0);52 const event = applyBatch(state, {53 reveal: [{ row: Math.floor(hiddenSafe / state.width), col: hiddenSafe % state.width }],54 flag: [],55 unflag: [],56 intent: "Replay metadata test",57 });58 expect(event.revealedValues.length).toBeGreaterThan(0);59 expect(event.revealedValues.every((cell) => /^[0-8]$/.test(cell.value))).toBe(true);60 });6162 it("counts submitted tiles rather than flood-revealed tiles", () => {63 const state: GameState = {64 id: "9ba1f45f-e2f0-4292-94da-52146c89579c",65 seed: "submitted-count-test",66 width: 3,67 height: 3,68 mines: new Uint8Array(9),69 revealed: new Uint8Array(9),70 initialRevealed: new Uint8Array(9),71 flags: new Uint8Array(9),72 status: "playing",73 revision: 0,74 explodedIndex: null,75 certifiedNoGuess: true,76 createdAt: "2026-08-12T00:00:00.000Z",77 updatedAt: "2026-08-12T00:00:00.000Z",78 };7980 const event = applyBatch(state, {81 reveal: [{ row: 1, col: 1 }],82 flag: [],83 unflag: [],84 intent: "Reveal one empty tile",85 });8687 expect(event.submittedCount).toBe(1);88 expect(event.revealed).toHaveLength(9);89 });9091 it("encodes the agent frontier more compactly than the visual board", () => {92 const state = createNoGuessGame({93 id: "ee3feea4-077c-4a58-9071-92368a92ed7f",94 seed: "compact-position-test",95 width: 64,96 height: 64,97 density: 0.1,98 maxAttempts: 500,99 });100 const position = toAgentPosition(state);101 let previous = 0;102 const frontier = position.frontier_deltas.map((delta) => (previous += delta));103 expect(frontier).toEqual([...frontier].sort((left, right) => left - right));104 expect(JSON.stringify(position).length).toBeLessThan(JSON.stringify(toObservation(state)).length);105 });106});