import { describe, it, expect } from "vitest"; import { parseShapeString } from "@/samples/slay-the-spire-like/system/utils/parse-shape"; import { checkCollision, checkBoardCollision, checkBounds, validateShapePlacement, transformShape, getOccupiedCells, IDENTITY_TRANSFORM, } from "@/samples/slay-the-spire-like/system/utils/shape-collision"; describe("parseShapeString", () => { it("should parse a single cell with o", () => { const result = parseShapeString("o"); expect(result.grid).toEqual([[true]]); expect(result.width).toBe(1); expect(result.height).toBe(1); expect(result.count).toBe(1); expect(result.originX).toBe(0); expect(result.originY).toBe(0); }); it("should parse a horizontal line", () => { const result = parseShapeString("oee"); expect(result.width).toBe(3); expect(result.height).toBe(1); expect(result.count).toBe(3); expect(result.grid).toEqual([[true, true, true]]); expect(result.originX).toBe(0); expect(result.originY).toBe(0); }); it("should parse a vertical line", () => { const result = parseShapeString("oss"); expect(result.width).toBe(1); expect(result.height).toBe(3); expect(result.count).toBe(3); expect(result.grid).toEqual([[true], [true], [true]]); expect(result.originX).toBe(0); expect(result.originY).toBe(0); }); it("should parse an L shape", () => { const result = parseShapeString("oes"); expect(result.width).toBe(2); expect(result.height).toBe(2); expect(result.count).toBe(3); expect(result.grid).toEqual([ [true, true], [false, true], ]); }); it("should handle return command", () => { const result = parseShapeString("oeerww"); expect(result.width).toBe(4); expect(result.height).toBe(1); expect(result.count).toBe(4); expect(result.grid).toEqual([[true, true, true, true]]); }); it("should handle case insensitivity", () => { const resultLower = parseShapeString("oes"); const resultUpper = parseShapeString("OES"); expect(resultLower.grid).toEqual(resultUpper.grid); expect(resultLower.count).toBe(resultUpper.count); }); it("should return empty grid for empty input", () => { const result = parseShapeString(""); expect(result.grid).toEqual([[]]); expect(result.width).toBe(0); expect(result.height).toBe(1); expect(result.count).toBe(0); }); it("should track origin correctly", () => { // eeso: e(1,0), e(2,0), s(2,1), o sets origin at (2,1) // After normalization: minX=1, minY=0, so originX = 2-1 = 1, originY = 1-0 = 1 const result = parseShapeString("eeso"); expect(result.originX).toBe(1); expect(result.originY).toBe(1); }); it("should track origin at first o only", () => { const result = parseShapeString("oes"); expect(result.originX).toBe(0); expect(result.originY).toBe(0); }); it("should handle complex T shape", () => { // oewers: o(0,0), e(1,0), w(0,0), e(1,0), r->(0,0), s(0,1) // Filled: (0,0), (1,0), (0,1) - 3 cells const result = parseShapeString("oewers"); expect(result.width).toBe(2); expect(result.height).toBe(2); expect(result.count).toBe(3); expect(result.grid).toEqual([ [true, true], [true, false], ]); }); }); describe("shape-collision", () => { describe("getOccupiedCells", () => { it("should return cells for a single cell shape", () => { const shape = parseShapeString("o"); const cells = getOccupiedCells(shape); expect(cells).toEqual([{ x: 0, y: 0 }]); }); it("should return cells for a horizontal line", () => { const shape = parseShapeString("oe"); const cells = getOccupiedCells(shape); expect(cells).toEqual([ { x: 0, y: 0 }, { x: 1, y: 0 }, ]); }); it("should return cells for an L shape", () => { const shape = parseShapeString("oes"); const cells = getOccupiedCells(shape); expect(cells).toEqual([ { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, ]); }); }); describe("checkCollision", () => { it("should detect collision between overlapping shapes", () => { const shapeA = parseShapeString("o"); const shapeB = parseShapeString("o"); const result = checkCollision( shapeA, { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, shapeB, { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, ); expect(result).toBe(true); }); it("should not detect collision between non-overlapping shapes", () => { const shapeA = parseShapeString("o"); const shapeB = parseShapeString("o"); const result = checkCollision( shapeA, { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, shapeB, { ...IDENTITY_TRANSFORM, offset: { x: 2, y: 0 } }, ); expect(result).toBe(false); }); it("should detect collision with adjacent shapes", () => { const shapeA = parseShapeString("o"); const shapeB = parseShapeString("o"); const result = checkCollision( shapeA, { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, shapeB, { ...IDENTITY_TRANSFORM, offset: { x: 1, y: 0 } }, ); expect(result).toBe(false); }); it("should detect collision with rotation", () => { const shapeA = parseShapeString("oe"); const shapeB = parseShapeString("os"); // shapeA is horizontal at (0,0)-(1,0) // shapeB rotated 90° becomes vertical at (0,0)-(0,1) // They should collide at (0,0) const result = checkCollision( shapeA, { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, shapeB, { ...IDENTITY_TRANSFORM, rotation: 90, offset: { x: 0, y: 0 } }, ); expect(result).toBe(true); }); }); describe("checkBoardCollision", () => { it("should detect collision with occupied cells", () => { const shape = parseShapeString("oe"); const occupied = new Set(["0,0", "1,0"]); const result = checkBoardCollision(shape, IDENTITY_TRANSFORM, occupied); expect(result).toBe(true); }); it("should not detect collision with empty board", () => { const shape = parseShapeString("oe"); const occupied = new Set(); const result = checkBoardCollision(shape, IDENTITY_TRANSFORM, occupied); expect(result).toBe(false); }); it("should detect collision after translation", () => { const shape = parseShapeString("oe"); const occupied = new Set(["5,5", "6,5"]); const result = checkBoardCollision( shape, { ...IDENTITY_TRANSFORM, offset: { x: 5, y: 5 } }, occupied, ); expect(result).toBe(true); }); }); describe("checkBounds", () => { it("should return true for shape within bounds", () => { const shape = parseShapeString("oe"); const result = checkBounds(shape, IDENTITY_TRANSFORM, 10, 10); expect(result).toBe(true); }); it("should return false for shape outside bounds", () => { const shape = parseShapeString("oe"); const result = checkBounds( shape, { ...IDENTITY_TRANSFORM, offset: { x: 9, y: 0 } }, 10, 10, ); expect(result).toBe(false); }); it("should return false for negative coordinates", () => { const shape = parseShapeString("oe"); const result = checkBounds( shape, { ...IDENTITY_TRANSFORM, offset: { x: -1, y: 0 } }, 10, 10, ); expect(result).toBe(false); }); it("should return true for shape at boundary edge", () => { const shape = parseShapeString("o"); const result = checkBounds( shape, { ...IDENTITY_TRANSFORM, offset: { x: 9, y: 9 } }, 10, 10, ); expect(result).toBe(true); }); }); describe("validatePlacement", () => { it("should return valid for good placement", () => { const shape = parseShapeString("oe"); const occupied = new Set(); const result = validateShapePlacement( shape, IDENTITY_TRANSFORM, 10, 10, occupied, ); expect(result).toEqual({ valid: true }); }); it("should return invalid for out of bounds", () => { const shape = parseShapeString("oe"); const occupied = new Set(); const result = validateShapePlacement( shape, { ...IDENTITY_TRANSFORM, offset: { x: 9, y: 0 } }, 10, 10, occupied, ); expect(result).toEqual({ valid: false, reason: "超出边界" }); }); it("should return invalid for collision", () => { const shape = parseShapeString("oe"); const occupied = new Set(["0,0", "1,0"]); const result = validateShapePlacement( shape, IDENTITY_TRANSFORM, 10, 10, occupied, ); expect(result).toEqual({ valid: false, reason: "与已有形状重叠" }); }); }); describe("transformShape", () => { it("should apply translation correctly", () => { const shape = parseShapeString("o"); const transform = { ...IDENTITY_TRANSFORM, offset: { x: 5, y: 3 } }; const cells = transformShape(shape, transform); expect(cells).toEqual([{ x: 5, y: 3 }]); }); it("should apply 90° rotation correctly", () => { const shape = parseShapeString("oe"); const transform = { ...IDENTITY_TRANSFORM, rotation: 90 }; const cells = transformShape(shape, transform); expect(cells).toEqual([ { x: 0, y: 0 }, { x: 0, y: -1 }, ]); }); it("should apply horizontal flip correctly", () => { const shape = parseShapeString("oe"); const transform = { ...IDENTITY_TRANSFORM, flipX: true }; const cells = transformShape(shape, transform); expect(cells).toEqual([ { x: 1, y: 0 }, { x: 0, y: 0 }, ]); }); it("should apply vertical flip correctly", () => { const shape = parseShapeString("os"); const transform = { ...IDENTITY_TRANSFORM, flipY: true }; const cells = transformShape(shape, transform); expect(cells).toEqual([ { x: 0, y: 1 }, { x: 0, y: 0 }, ]); }); it("should combine rotation and translation", () => { const shape = parseShapeString("os"); const transform = { ...IDENTITY_TRANSFORM, rotation: 90, offset: { x: 10, y: 10 }, }; const cells = transformShape(shape, transform); expect(cells).toEqual([ { x: 10, y: 10 }, { x: 11, y: 10 }, ]); }); }); });