implemented board representation

feature/modern-browsers
Inga 🏳‍🌈 1 month ago
parent 830b6734a1
commit b582f6c118
  1. 14
      eslint.config.js
  2. 63
      src/lib/board.spec.ts
  3. 57
      src/lib/board.ts
  4. 0
      src/lib/stub.spec.ts
  5. 0
      src/lib/stub.ts
  6. 5
      src/lib/types.ts
  7. 4
      src/lib/utils.ts
  8. 8
      tsconfig.build.json
  9. 17
      tsconfig.json

@ -15,4 +15,18 @@ export default tsEslint.config(
},
},
},
{
rules: {
"@typescript-eslint/restrict-template-expressions": [
"error",
{ allowNumber: true, allowNever: true },
],
},
},
{
files: ["**/*.spec.*"],
rules: {
"@typescript-eslint/require-await": "off",
},
},
);

@ -0,0 +1,63 @@
import t, { type Test } from "tap";
import { Board } from "./board.ts";
import { SquareState } from "./types.js";
const createAndCheckBoard = (t: Test, serialized: string) => {
const board = new Board(serialized);
t.equal(board.serialize(), serialized);
return board;
};
void t.test("0x0 board", async (t) => {
const board = createAndCheckBoard(t, "");
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(1, 0), { message: "Out of bounds: 1:0" });
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" });
});
void t.test("Empty 1x1 board", async (t) => {
const board = createAndCheckBoard(t, "_");
t.equal(board.get(0, 0), SquareState.Unoccupied);
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, 1), { message: "Out of bounds: 1:1" });
});
void t.test("1x1 board with X", async (t) => {
const board = createAndCheckBoard(t, "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(1, 0), { message: "Out of bounds: 1:0" });
t.throws(() => board.get(1, 1), { message: "Out of bounds: 1:1" });
});
void t.test("Half-full 3x4 board", async (t) => {
const board = createAndCheckBoard(t, "XO_|O_X|OX_|_XO");
t.equal(board.get(0, 0), SquareState.X);
t.equal(board.get(0, 1), SquareState.O);
t.equal(board.get(0, 2), SquareState.Unoccupied);
t.throws(() => board.get(0, 3), { message: "Out of bounds: 0:3" });
t.equal(board.get(1, 0), SquareState.O);
t.equal(board.get(1, 1), SquareState.Unoccupied);
t.equal(board.get(1, 2), SquareState.X);
t.throws(() => board.get(1, 3), { message: "Out of bounds: 1:3" });
t.equal(board.get(2, 0), SquareState.O);
t.equal(board.get(2, 1), SquareState.X);
t.equal(board.get(2, 2), SquareState.Unoccupied);
t.throws(() => board.get(2, 3), { message: "Out of bounds: 2:3" });
t.equal(board.get(3, 0), SquareState.Unoccupied);
t.equal(board.get(3, 1), SquareState.X);
t.equal(board.get(3, 2), SquareState.O);
t.throws(() => board.get(3, 3), { message: "Out of bounds: 3:3" });
t.throws(() => board.get(4, 0), { message: "Out of bounds: 4:0" });
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, 3), { message: "Out of bounds: 4:3" });
});
void t.test("Throws error on incorrect serialized value", async (t) => {
t.throws(() => new Board("abc"), {
message: "Unsupported square character: a",
});
});

@ -0,0 +1,57 @@
import { SquareState } from "./types.ts";
import { unreachable } from "./utils.ts";
export class Board {
private readonly state: SquareState[][];
constructor(serialized: string) {
this.state = serialized.split("|").map((line) =>
line.split("").map((char) => {
switch (char) {
case "_":
return SquareState.Unoccupied;
case "O":
return SquareState.O;
case "X":
return SquareState.X;
default:
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) =>
row
.map((squareState) => {
switch (squareState) {
case SquareState.Unoccupied:
return "_";
case SquareState.O:
return "O";
case SquareState.X:
return "X";
/* c8 ignore start */
default:
throw new Error(
`Unsupported square state: ${unreachable(squareState)}`,
);
/* c8 ignore stop */
}
})
.join(""),
)
.join("|");
}
}

@ -0,0 +1,5 @@
export enum SquareState {
Unoccupied = 1, // so that all SquareState values are truthy
X,
O,
}

@ -0,0 +1,4 @@
// 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 */

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"exclude": ["src/**/*.spec.*"],
"compilerOptions": {
"module": "Preserve"
}
}

@ -1,10 +1,11 @@
{
"extends": "@tsconfig/strictest",
"include": ["src/**/*"],
"compilerOptions": {
"target": "ES2020",
"module": "Preserve",
"forceConsistentCasingInFileNames": true
}
"extends": "@tsconfig/strictest",
"include": ["src/**/*"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"noEmit": true,
"target": "ES2020",
"module": "NodeNext",
"forceConsistentCasingInFileNames": true
}
}

Loading…
Cancel
Save