|
|
@ -1,38 +1,40 @@ |
|
|
|
import t, { type Test } from "tap"; |
|
|
|
import t, { type Test } from "tap"; |
|
|
|
|
|
|
|
|
|
|
|
import { Board } from "./board.ts"; |
|
|
|
import { Board } from "./board.ts"; |
|
|
|
import { SquareState } from "./types.js"; |
|
|
|
import { SquareState } from "./types.ts"; |
|
|
|
|
|
|
|
|
|
|
|
const createAndCheckBoard = (t: Test, serialized: string) => { |
|
|
|
void t.test("Serialize / deserialize", async (t) => { |
|
|
|
const board = new Board(serialized); |
|
|
|
const createAndCheckBoard = (t: Test, serialized: string) => { |
|
|
|
|
|
|
|
const board = Board.fromSerialized(serialized); |
|
|
|
t.equal(board.serialize(), serialized); |
|
|
|
t.equal(board.serialize(), serialized); |
|
|
|
return board; |
|
|
|
return board; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
void t.test("0x0 board", async (t) => { |
|
|
|
void t.test("0x0 board", async (t) => { |
|
|
|
const board = createAndCheckBoard(t, ""); |
|
|
|
const board = createAndCheckBoard(t, ""); |
|
|
|
t.throws(() => board.get(0, 0), { message: "Out of bounds: 0:0" }); |
|
|
|
t.throws(() => board.get(0, 0), { message: "Out of bounds: 0:0" }); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
void t.test("Empty 1x1 board", async (t) => { |
|
|
|
void t.test("Empty 1x1 board", async (t) => { |
|
|
|
const board = createAndCheckBoard(t, "_"); |
|
|
|
const board = createAndCheckBoard(t, "_"); |
|
|
|
t.equal(board.get(0, 0), SquareState.Unoccupied); |
|
|
|
t.equal(board.get(0, 0), SquareState.Unoccupied); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
void t.test("1x1 board with X", async (t) => { |
|
|
|
void t.test("1x1 board with X", async (t) => { |
|
|
|
const board = createAndCheckBoard(t, "X"); |
|
|
|
const board = createAndCheckBoard(t, "X"); |
|
|
|
t.equal(board.get(0, 0), SquareState.X); |
|
|
|
t.equal(board.get(0, 0), SquareState.X); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(0, 1), { message: "Out of bounds: 0:1" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 0), { message: "Out of bounds: 1:0" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" }); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
void t.test("Half-full 3x4 board", async (t) => { |
|
|
|
void t.test("Half-full 3x4 board", async (t) => { |
|
|
|
const board = createAndCheckBoard(t, "XO_|O_X|OX_|_XO"); |
|
|
|
const board = createAndCheckBoard(t, "XO_|O_X|OX_|_XO"); |
|
|
|
t.equal(board.get(0, 0), SquareState.X); |
|
|
|
t.equal(board.get(0, 0), SquareState.X); |
|
|
|
t.equal(board.get(0, 1), SquareState.O); |
|
|
|
t.equal(board.get(0, 1), SquareState.O); |
|
|
@ -54,10 +56,57 @@ void t.test("Half-full 3x4 board", async (t) => { |
|
|
|
t.throws(() => board.get(4, 1), { message: "Out of bounds: 4:1" }); |
|
|
|
t.throws(() => board.get(4, 1), { message: "Out of bounds: 4:1" }); |
|
|
|
t.throws(() => board.get(4, 2), { message: "Out of bounds: 4:2" }); |
|
|
|
t.throws(() => board.get(4, 2), { message: "Out of bounds: 4:2" }); |
|
|
|
t.throws(() => board.get(4, 3), { message: "Out of bounds: 4:3" }); |
|
|
|
t.throws(() => board.get(4, 3), { message: "Out of bounds: 4:3" }); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
void t.test("Throws error on incorrect serialized value", async (t) => { |
|
|
|
void t.test("Validation", async (t) => { |
|
|
|
t.throws(() => new Board("abc"), { |
|
|
|
t.throws(() => Board.fromSerialized("abc"), { |
|
|
|
message: "Unsupported square character: a", |
|
|
|
message: "Unsupported square character: a", |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
const board = new Board([[undefined as unknown as SquareState]]); |
|
|
|
|
|
|
|
t.throws(() => board.serialize(), { |
|
|
|
|
|
|
|
message: "Unsupported square state: undefined", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
const board = new Board([["test" as unknown as SquareState]]); |
|
|
|
|
|
|
|
t.throws(() => board.serialize(), { |
|
|
|
|
|
|
|
message: "Unsupported square state: test", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void t.test("Derived boards creation", async (t) => { |
|
|
|
|
|
|
|
const board = Board.fromSerialized("_X|O_|_O"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t.equal(board.with(0, 0, SquareState.Unoccupied).serialize(), "_X|O_|_O"); |
|
|
|
|
|
|
|
t.equal(board.with(0, 0, SquareState.O).serialize(), "OX|O_|_O"); |
|
|
|
|
|
|
|
t.equal(board.with(0, 0, SquareState.X).serialize(), "XX|O_|_O"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t.throws(() => board.with(0, 1, SquareState.X), { |
|
|
|
|
|
|
|
message: "Cannot update occupied square: 0:1", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.throws(() => board.with(0, 2, SquareState.X), { |
|
|
|
|
|
|
|
message: "Out of bounds: 0:2", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.throws(() => board.with(1, 0, SquareState.X), { |
|
|
|
|
|
|
|
message: "Cannot update occupied square: 1:0", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.equal(board.with(1, 1, SquareState.X).serialize(), "_X|OX|_O"); |
|
|
|
|
|
|
|
t.throws(() => board.with(1, 2, SquareState.X), { |
|
|
|
|
|
|
|
message: "Out of bounds: 1:2", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.equal(board.with(2, 0, SquareState.X).serialize(), "_X|O_|XX"); |
|
|
|
|
|
|
|
t.throws(() => board.with(2, 1, SquareState.X), { |
|
|
|
|
|
|
|
message: "Cannot update occupied square: 2:1", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.throws(() => board.with(2, 2, SquareState.X), { |
|
|
|
|
|
|
|
message: "Out of bounds: 2:2", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
t.throws(() => board.with(3, 0, SquareState.X), { |
|
|
|
|
|
|
|
message: "Out of bounds: 3:0", |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|