implemented derived boards creation

main
Inga 🏳‍🌈 5 days ago
parent b582f6c118
commit b47cfb8916
  1. 57
      src/lib/board.spec.ts
  2. 41
      src/lib/board.ts
  3. 2
      src/lib/utils.ts

@ -1,9 +1,11 @@
import t, { type Test } from "tap";
import { Board } from "./board.ts";
import { SquareState } from "./types.js";
import { SquareState } from "./types.ts";
void t.test("Serialize / deserialize", async (t) => {
const createAndCheckBoard = (t: Test, serialized: string) => {
const board = new Board(serialized);
const board = Board.fromSerialized(serialized);
t.equal(board.serialize(), serialized);
return board;
};
@ -56,8 +58,55 @@ void t.test("Half-full 3x4 board", async (t) => {
t.throws(() => board.get(4, 3), { message: "Out of bounds: 4:3" });
});
void t.test("Throws error on incorrect serialized value", async (t) => {
t.throws(() => new Board("abc"), {
void t.test("Validation", async (t) => {
t.throws(() => Board.fromSerialized("abc"), {
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",
});
});

@ -2,10 +2,33 @@ import { SquareState } from "./types.ts";
import { unreachable } from "./utils.ts";
export class Board {
private readonly state: SquareState[][];
constructor(private readonly state: SquareState[][]) {}
constructor(serialized: string) {
this.state = serialized.split("|").map((line) =>
get(row: number, column: number) {
const result = this.state[row]?.[column];
if (!result) {
throw new Error(`Out of bounds: ${row}:${column}`);
}
return result;
}
with(row: number, column: number, newSquareState: SquareState) {
if (this.get(row, column) !== SquareState.Unoccupied) {
throw new Error(`Cannot update occupied square: ${row}:${column}`);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed to exist at this stage because otherwise `get` would throw an error
const newRowState = [...this.state[column]!];
newRowState[column] = newSquareState;
const newState = [...this.state];
newState[row] = newRowState;
return new Board(newState);
}
static fromSerialized(serialized: string) {
return new Board(
serialized.split("|").map((line) =>
line.split("").map((char) => {
switch (char) {
case "_":
@ -18,18 +41,10 @@ export class Board {
throw new Error(`Unsupported square character: ${char}`);
}
}),
),
);
}
get(row: number, column: number) {
const result = this.state[row]?.[column];
if (!result) {
throw new Error(`Out of bounds: ${row}:${column}`);
}
return result;
}
serialize() {
return this.state
.map((row) =>
@ -42,12 +57,10 @@ export class Board {
return "O";
case SquareState.X:
return "X";
/* c8 ignore start */
default:
throw new Error(
`Unsupported square state: ${unreachable(squareState)}`,
);
/* c8 ignore stop */
}
})
.join(""),

@ -1,4 +1,2 @@
// To simplify switches and provide some typecheck-time guarantees
/* c8 ignore start */
export const unreachable = (value: never) => value as unknown as string;
/* c8 ignore stop */

Loading…
Cancel
Save