From 40a7258a5a6eb8ddd6ea2de1d0b9a90c64aa4282 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sat, 5 Aug 2023 20:13:43 -0700 Subject: [PATCH] Merge Baduk and abstractAlternatingOnGrid --- packages/shared/src/index.ts | 1 - .../abstractAlternatingOnGrid.ts | 117 ------------------ .../lib/abstractAlternatingOnGrid/index.ts | 1 - .../src/variants/__tests__/baduk.test.ts | 3 +- .../src/variants/__tests__/capture.test.ts | 2 +- .../src/variants/__tests__/keima.test.ts | 2 +- .../src/variants/__tests__/one_color.test.ts | 2 +- .../src/variants/__tests__/phantom.test.ts | 2 +- .../src/variants/__tests__/tetris.test.ts | 2 +- .../src/variants/__tests__/thue_morse.test.ts | 2 +- packages/shared/src/variants/baduk.ts | 96 +++++++++++--- packages/shared/src/variants/freeze.ts | 6 +- packages/shared/src/variants/one_color.ts | 3 +- packages/shared/src/variants/phantom.ts | 3 +- packages/shared/src/variants/pyramid.ts | 3 +- packages/shared/src/variants/tetris.ts | 3 +- 16 files changed, 92 insertions(+), 156 deletions(-) delete mode 100644 packages/shared/src/lib/abstractAlternatingOnGrid/abstractAlternatingOnGrid.ts delete mode 100644 packages/shared/src/lib/abstractAlternatingOnGrid/index.ts diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b4334fd9..b43d9f4f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,5 +1,4 @@ export * from "./variants/baduk"; -export * from "./lib/abstractAlternatingOnGrid"; export * from "./variants/phantom"; export * from "./variants/capture"; export * from "./variants/parallel"; diff --git a/packages/shared/src/lib/abstractAlternatingOnGrid/abstractAlternatingOnGrid.ts b/packages/shared/src/lib/abstractAlternatingOnGrid/abstractAlternatingOnGrid.ts deleted file mode 100644 index f3b5aa28..00000000 --- a/packages/shared/src/lib/abstractAlternatingOnGrid/abstractAlternatingOnGrid.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { AbstractGame } from "../../abstract_game"; -import { Coordinate, CoordinateLike } from "../coordinate"; -import { Grid } from "../grid"; - -export enum Color { - EMPTY = 0, - BLACK = 1, - WHITE = 2, -} - -export interface AbstractAlternatingOnGridConfig { - width: number; - height: number; -} - -export interface AbstractAlternatingOnGridState { - board: Color[][]; - next_to_play: 0 | 1; - last_move: string; -} - -export abstract class AbstractAlternatingOnGrid< - TConfig extends AbstractAlternatingOnGridConfig, - TState extends AbstractAlternatingOnGridState, -> extends AbstractGame { - protected board: Grid; - protected next_to_play: 0 | 1 = 0; - protected last_move = ""; - - constructor(config?: AbstractAlternatingOnGridConfig) { - super(config as TConfig); - this.board = new Grid(this.config.width, this.config.height).fill( - Color.EMPTY, - ); - } - - override exportState(): TState { - return { - board: this.board.to2DArray(), - next_to_play: this.next_to_play, - last_move: this.last_move, - } as TState; - } - - override nextToPlay(): number[] { - return [this.next_to_play]; - } - - override playMove(player: number, move: string): void { - if (player != this.next_to_play) { - throw Error(`It's not player ${player}'s turn!`); - } - - this.preValidateMove(move); - // TODO: preValidateMove() needs more considerations what it is meant for. - // Initially it was meant to ensure moves can be applied to the game state, - // but with decodeMove() happening later those checks currently also happen later. - // As it is now preValidateMove() allows to for example check whether passing is allowed. - - if (move === "resign") { - this.phase = "gameover"; - this.result = this.next_to_play === 0 ? "W+R" : "B+R"; - return; - } - - if (move != "pass") { - const decoded_move = Coordinate.fromSgfRepr(move); - const { x, y } = decoded_move; - const color = this.board.at(decoded_move); - if (color === undefined) { - throw Error( - `Move out of bounds. (move: ${decoded_move}, board dimensions: ${this.config.width}x${this.config.height}`, - ); - } - if (color !== Color.EMPTY) { - throw Error( - `Cannot place a stone on top of an existing stone. (${color} at (${x}, ${y}))`, - ); - } - - this.playMoveInternal(decoded_move); - this.postValidateMove(decoded_move); - this.prepareForNextMove(move, decoded_move); - } else { - this.prepareForNextMove(move); - } - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - protected preValidateMove(_move: string): void {} - // eslint-disable-next-line @typescript-eslint/no-empty-function - protected postValidateMove(_move: Coordinate): void {} - - protected playMoveInternal(move: Coordinate): void { - this.board.set(move, this.next_to_play === 0 ? Color.BLACK : Color.WHITE); - } - - protected prepareForNextMove(move: string, _decoded_move?: Coordinate): void { - this.next_to_play = this.next_to_play === 0 ? 1 : 0; - this.last_move = move; - } - - override numPlayers(): number { - return 2; - } - - override specialMoves() { - return { pass: "Pass", resign: "Resign" }; - } -} - -export function isOutOfBounds( - pos: CoordinateLike, - board: Grid, -): boolean { - return board.at(pos) === undefined; -} diff --git a/packages/shared/src/lib/abstractAlternatingOnGrid/index.ts b/packages/shared/src/lib/abstractAlternatingOnGrid/index.ts deleted file mode 100644 index 702b17fe..00000000 --- a/packages/shared/src/lib/abstractAlternatingOnGrid/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./abstractAlternatingOnGrid"; diff --git a/packages/shared/src/variants/__tests__/baduk.test.ts b/packages/shared/src/variants/__tests__/baduk.test.ts index 1eb0021f..39dbfc52 100644 --- a/packages/shared/src/variants/__tests__/baduk.test.ts +++ b/packages/shared/src/variants/__tests__/baduk.test.ts @@ -1,5 +1,4 @@ -import { Baduk } from "../baduk"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Baduk, Color } from "../baduk"; import { KoError } from "../../lib/ko_detector"; const B = Color.BLACK; diff --git a/packages/shared/src/variants/__tests__/capture.test.ts b/packages/shared/src/variants/__tests__/capture.test.ts index a55fa6b4..9b0306fe 100644 --- a/packages/shared/src/variants/__tests__/capture.test.ts +++ b/packages/shared/src/variants/__tests__/capture.test.ts @@ -1,5 +1,5 @@ import { Capture } from "../capture"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; test("White wins by capture", () => { const game = new Capture({ width: 2, height: 2, komi: 0.5 }); diff --git a/packages/shared/src/variants/__tests__/keima.test.ts b/packages/shared/src/variants/__tests__/keima.test.ts index e9a738ac..62a5a0d9 100644 --- a/packages/shared/src/variants/__tests__/keima.test.ts +++ b/packages/shared/src/variants/__tests__/keima.test.ts @@ -1,5 +1,5 @@ import { Keima } from "../keima"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; const B = Color.BLACK; const _ = Color.EMPTY; diff --git a/packages/shared/src/variants/__tests__/one_color.test.ts b/packages/shared/src/variants/__tests__/one_color.test.ts index 1a784005..d34411ed 100644 --- a/packages/shared/src/variants/__tests__/one_color.test.ts +++ b/packages/shared/src/variants/__tests__/one_color.test.ts @@ -1,4 +1,4 @@ -import { Color } from "../../lib/abstractAlternatingOnGrid/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; import { OneColorGo } from "../one_color"; const B = Color.BLACK; diff --git a/packages/shared/src/variants/__tests__/phantom.test.ts b/packages/shared/src/variants/__tests__/phantom.test.ts index aa30aadb..b11a60e2 100644 --- a/packages/shared/src/variants/__tests__/phantom.test.ts +++ b/packages/shared/src/variants/__tests__/phantom.test.ts @@ -1,5 +1,5 @@ import { Phantom } from "../phantom"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; test("Play a game", () => { const game = new Phantom({ width: 4, height: 2, komi: 0.5 }); diff --git a/packages/shared/src/variants/__tests__/tetris.test.ts b/packages/shared/src/variants/__tests__/tetris.test.ts index 8da4c3aa..425e63db 100644 --- a/packages/shared/src/variants/__tests__/tetris.test.ts +++ b/packages/shared/src/variants/__tests__/tetris.test.ts @@ -1,5 +1,5 @@ import { TetrisGo } from "../tetris"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; test("Play a game", () => { const game = new TetrisGo({ width: 4, height: 2, komi: 0.5 }); diff --git a/packages/shared/src/variants/__tests__/thue_morse.test.ts b/packages/shared/src/variants/__tests__/thue_morse.test.ts index 18e1d388..a3f21b3a 100644 --- a/packages/shared/src/variants/__tests__/thue_morse.test.ts +++ b/packages/shared/src/variants/__tests__/thue_morse.test.ts @@ -1,5 +1,5 @@ import { ThueMorse } from "../thue_morse"; -import { Color } from "../../lib/abstractAlternatingOnGrid"; +import { Color } from "../baduk"; const B = Color.BLACK; const _ = Color.EMPTY; diff --git a/packages/shared/src/variants/baduk.ts b/packages/shared/src/variants/baduk.ts index baf12fda..fac1d013 100644 --- a/packages/shared/src/variants/baduk.ts +++ b/packages/shared/src/variants/baduk.ts @@ -1,19 +1,24 @@ -import { - AbstractAlternatingOnGrid, - AbstractAlternatingOnGridConfig, - AbstractAlternatingOnGridState, - Color, -} from "../lib/abstractAlternatingOnGrid"; import { Coordinate, CoordinateLike } from "../lib/coordinate"; import { Grid } from "../lib/grid"; import { getGroup, getOuterBorder } from "../lib/group_utils"; import { SuperKoDetector } from "../lib/ko_detector"; +import { AbstractGame } from "../abstract_game"; -export interface BadukConfig extends AbstractAlternatingOnGridConfig { +export enum Color { + EMPTY = 0, + BLACK = 1, + WHITE = 2, +} + +export interface BadukConfig { + width: number; + height: number; komi: number; } -export interface BadukState extends AbstractAlternatingOnGridState { +export interface BadukState { + board: Color[][]; + next_to_play: 0 | 1; captures: { 0: number; 1: number }; last_move: string; score_board?: Color[][]; @@ -21,25 +26,80 @@ export interface BadukState extends AbstractAlternatingOnGridState { export type BadukMove = { 0: string } | { 1: string }; -export class Baduk extends AbstractAlternatingOnGrid { +export class Baduk extends AbstractGame { protected captures = { 0: 0, 1: 0 }; private ko_detector = new SuperKoDetector(); protected score_board?: Grid; + protected board: Grid; + protected next_to_play: 0 | 1 = 0; + protected last_move = ""; constructor(config?: BadukConfig) { super(config); + this.board = new Grid(this.config.width, this.config.height).fill( + Color.EMPTY, + ); } override exportState(): BadukState { return { - ...super.exportState(), - ...(this.score_board && { score_board: this.score_board.to2DArray() }), + board: this.board.to2DArray(), + next_to_play: this.next_to_play, + last_move: this.last_move, captures: { 0: this.captures[0], 1: this.captures[1] }, + ...(this.score_board && { score_board: this.score_board.to2DArray() }), }; } - protected override playMoveInternal(move: Coordinate): void { - super.playMoveInternal(move); + override nextToPlay(): number[] { + return [this.next_to_play]; + } + + override playMove(player: number, move: string): void { + if (player != this.next_to_play) { + throw Error(`It's not player ${player}'s turn!`); + } + + if (move === "resign") { + this.phase = "gameover"; + this.result = this.next_to_play === 0 ? "W+R" : "B+R"; + return; + } + + if (move != "pass") { + const decoded_move = Coordinate.fromSgfRepr(move); + const { x, y } = decoded_move; + const color = this.board.at(decoded_move); + if (color === undefined) { + throw Error( + `Move out of bounds. (move: ${decoded_move}, board dimensions: ${this.config.width}x${this.config.height}`, + ); + } + if (color !== Color.EMPTY) { + throw Error( + `Cannot place a stone on top of an existing stone. (${color} at (${x}, ${y}))`, + ); + } + + this.playMoveInternal(decoded_move); + this.postValidateMove(decoded_move); + this.prepareForNextMove(move); + } else { + this.prepareForNextMove(move); + } + } + + override numPlayers(): number { + return 2; + } + + override specialMoves() { + return { pass: "Pass", resign: "Resign" }; + } + + private playMoveInternal(move: Coordinate): void { + this.board.set(move, this.next_to_play === 0 ? Color.BLACK : Color.WHITE); + const opponent_color = this.next_to_play === 0 ? Color.WHITE : Color.BLACK; this.board.neighbors(move).forEach((pos) => { const neighbor_color = this.board.at(pos); @@ -54,7 +114,7 @@ export class Baduk extends AbstractAlternatingOnGrid { }); } - protected override postValidateMove(move: Coordinate): void { + protected postValidateMove(move: Coordinate): void { // Detect suicide if (!groupHasLiberties(getGroup(move, this.board), this.board)) { throw Error("Move is suicidal!"); @@ -67,14 +127,12 @@ export class Baduk extends AbstractAlternatingOnGrid { }); } - protected override prepareForNextMove( - move: string, - decoded_move?: Coordinate, - ): void { + private prepareForNextMove(move: string): void { if (move == "pass" && this.last_move === "pass") { this.finalizeScore(); } else { - super.prepareForNextMove(move, decoded_move); + this.next_to_play = this.next_to_play === 0 ? 1 : 0; + this.last_move = move; } } diff --git a/packages/shared/src/variants/freeze.ts b/packages/shared/src/variants/freeze.ts index 65da40f8..9cf43ed0 100644 --- a/packages/shared/src/variants/freeze.ts +++ b/packages/shared/src/variants/freeze.ts @@ -1,8 +1,7 @@ -import { Color } from "../lib/abstractAlternatingOnGrid"; import { Coordinate, CoordinateLike } from "../lib/coordinate"; import { Grid } from "../lib/grid"; import { getGroup, getOuterBorder } from "../lib/group_utils"; -import { Baduk, BadukState } from "./baduk"; +import { Baduk, BadukState, Color } from "./baduk"; export interface FreezeGoState extends BadukState { frozen: boolean; @@ -15,6 +14,9 @@ export class FreezeGo extends Baduk { const captures_before = this.captures[player as 0 | 1]; super.playMove(player, move); const captures_after = this.captures[player as 0 | 1]; + + console.log("before, after", captures_before, captures_after); + if (this.frozen && captures_before !== captures_after) { throw new Error("Cannot capture after opponent ataris"); } diff --git a/packages/shared/src/variants/one_color.ts b/packages/shared/src/variants/one_color.ts index 4a6cffad..ac85fca7 100644 --- a/packages/shared/src/variants/one_color.ts +++ b/packages/shared/src/variants/one_color.ts @@ -1,5 +1,4 @@ -import { Baduk, BadukState } from "./baduk"; -import { Color } from "../lib/abstractAlternatingOnGrid"; +import { Baduk, BadukState, Color } from "./baduk"; export class OneColorGo extends Baduk { exportState(): BadukState { diff --git a/packages/shared/src/variants/phantom.ts b/packages/shared/src/variants/phantom.ts index f091048a..22571fdb 100644 --- a/packages/shared/src/variants/phantom.ts +++ b/packages/shared/src/variants/phantom.ts @@ -1,5 +1,4 @@ -import { Baduk, BadukState } from "./baduk"; -import { Color } from "../lib/abstractAlternatingOnGrid"; +import { Baduk, BadukState, Color } from "./baduk"; import { Grid } from "../lib/grid"; export class Phantom extends Baduk { diff --git a/packages/shared/src/variants/pyramid.ts b/packages/shared/src/variants/pyramid.ts index 14faac1a..650a5a71 100644 --- a/packages/shared/src/variants/pyramid.ts +++ b/packages/shared/src/variants/pyramid.ts @@ -1,6 +1,5 @@ -import { Baduk, BadukConfig } from "./baduk"; +import { Baduk, BadukConfig, Color } from "./baduk"; import { Grid } from "../lib/grid"; -import { Color } from "../lib/abstractAlternatingOnGrid"; export class PyramidGo extends Baduk { private weights: Grid; diff --git a/packages/shared/src/variants/tetris.ts b/packages/shared/src/variants/tetris.ts index 517a8a62..073f17a8 100644 --- a/packages/shared/src/variants/tetris.ts +++ b/packages/shared/src/variants/tetris.ts @@ -1,5 +1,4 @@ -import { Color } from "../lib/abstractAlternatingOnGrid"; -import { Baduk } from "./baduk"; +import { Baduk, Color } from "./baduk"; import { Coordinate, CoordinateLike } from "../lib/coordinate"; import { Grid } from "../lib/grid";