import { describe, test, expect } from "vitest"; import { parseLevelJson } from "../src/game/levels/LevelParser"; import { ArrowDirection } from "../src/game/levels/LevelTypes"; import type { RawLevelJson, ParsedLevel } from "../src/game/levels/LevelTypes"; // ─── 测试数据 ──────────────────────────────────── const simpleLevelRaw: RawLevelJson = { name: 1, row: 4, col: 4, arrows: [ [[0, 2], [1, 2], [2, 2]], // 水平路径,箭头朝右 [[3, 1], [2, 1], [1, 1]], // 水平路径,箭头朝左 ], levelTime: 0, }; const blockedLevelRaw: RawLevelJson = { name: 2, row: 5, col: 5, arrows: [ [[0, 2], [1, 2], [2, 2]], // 朝右 [[3, 2], [3, 3], [3, 4]], // 在右边挡住第一条路径 ], levelTime: 60, }; // ─── LevelParser 测试 ──────────────────────────── describe("LevelParser", () => { test("解析基础关卡结构", () => { const level = parseLevelJson(simpleLevelRaw); expect(level.index).toBe(1); expect(level.rows).toBe(4); expect(level.cols).toBe(4); expect(level.paths.length).toBe(2); expect(level.timeSeconds).toBe(0); }); test("Cocos → Phaser 坐标翻转 (y 轴反转)", () => { const level = parseLevelJson(simpleLevelRaw); // Cocos y=2 在 4 行棋盘中 → Phaser y = 4-1-2 = 1 const firstPath = level.paths[0]; expect(firstPath.points[0].y).toBe(1); // rows-1-cocosY = 4-1-2 = 1 }); test("路径方向由最后两点推导", () => { const level = parseLevelJson(simpleLevelRaw); // 第一条路径:[0,2] → [1,2] → [2,2],最后两点 x 递增 → right expect(level.paths[0].direction).toBe(ArrowDirection.Right); // 第二条路径:[3,1] → [2,1] → [1,1],最后两点 x 递减 → left expect(level.paths[1].direction).toBe(ArrowDirection.Left); }); test("head 为路径最后一个点", () => { const level = parseLevelJson(simpleLevelRaw); expect(level.paths[0].head).toEqual({ x: 2, y: 1 }); // Phaser 坐标 }); test("indices 包含路径覆盖的所有格子", () => { const level = parseLevelJson(simpleLevelRaw); const path0 = level.paths[0]; // 水平路径 x: 0→1→2, y: 1 (Phaser), cols=4 // indices: 1*4+0=4, 1*4+1=5, 1*4+2=6 expect(path0.indices).toContain(4); expect(path0.indices).toContain(5); expect(path0.indices).toContain(6); expect(path0.indices.length).toBe(3); }); test("带时间限制的关卡", () => { const level = parseLevelJson(blockedLevelRaw); expect(level.timeSeconds).toBe(60); }); }); // ─── 碰撞检测测试 ────────────────────────────────── describe("碰撞检测", () => { function isCellOccupied( level: ParsedLevel, col: number, row: number, removedPaths: Set, ignorePathIndex?: number, ): boolean { const idx = row * level.cols + col; return level.paths.some((p, i) => { if (removedPaths.has(i)) return false; if (ignorePathIndex !== undefined && i === ignorePathIndex) return false; return p.indices.includes(idx); }); } test("路径覆盖的格子返回 occupied", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set(); // 第一条路径覆盖 (0,1), (1,1), (2,1) expect(isCellOccupied(level, 0, 1, removed)).toBe(true); expect(isCellOccupied(level, 1, 1, removed)).toBe(true); }); test("空格子返回 not occupied", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set(); expect(isCellOccupied(level, 3, 3, removed)).toBe(false); }); test("已消除路径不参与碰撞", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set([0]); // 第一条路径已消除 expect(isCellOccupied(level, 0, 1, removed)).toBe(false); }); test("ignorePathIndex 排除自身", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set(); // 格子 (0,1) 被路径0占据,但 ignore 路径0 → 返回 false expect(isCellOccupied(level, 0, 1, removed, 0)).toBe(false); }); }); // ─── 消除规则测试 ────────────────────────────────── describe("消除规则", () => { function canEliminate( level: ParsedLevel, pathIndex: number, removedPaths: Set, ): boolean { const path = level.paths[pathIndex]; const dx = path.direction === "right" ? 1 : path.direction === "left" ? -1 : 0; const dy = path.direction === "down" ? 1 : path.direction === "up" ? -1 : 0; let x = path.head.x; let y = path.head.y; const maxSteps = Math.max(level.cols, level.rows) + 1; for (let step = 0; step < maxSteps; step++) { x += dx; y += dy; if (x < 0 || y < 0 || x >= level.cols || y >= level.rows) { return true; // 到达边界,可消除 } const idx = y * level.cols + x; const blocked = level.paths.some((p, i) => { if (removedPaths.has(i) || i === pathIndex) return false; return p.indices.includes(idx); }); if (blocked) return false; } return false; } test("方向无阻挡 → 可消除", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set(); // 第一条路径朝右 head=(2,1),右边 (3,1) 无路径 → 可消除 expect(canEliminate(level, 0, removed)).toBe(true); }); test("方向有阻挡 → 不可消除", () => { const level = parseLevelJson(blockedLevelRaw); const removed = new Set(); // 第一条路径朝右,但 col=3 有第二条路径占据 → 不可消除 expect(canEliminate(level, 0, removed)).toBe(false); }); test("消除阻挡路径后 → 原路径可消除", () => { const level = parseLevelJson(blockedLevelRaw); const removed = new Set([1]); // 移除阻挡的第二条路径 expect(canEliminate(level, 0, removed)).toBe(true); }); }); // ─── 胜负条件测试 ────────────────────────────────── describe("胜负条件", () => { test("所有路径消除 → 胜利", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set([0, 1]); const isWin = removed.size >= level.paths.length; expect(isWin).toBe(true); }); test("部分路径消除 → 未胜利", () => { const level = parseLevelJson(simpleLevelRaw); const removed = new Set([0]); const isWin = removed.size >= level.paths.length; expect(isWin).toBe(false); }); test("生命归零 → 失败", () => { let lives = 3; lives--; // 第一次扣命 lives--; // 第二次扣命 lives--; // 第三次扣命 expect(lives <= 0).toBe(true); }); });