From f7671e162b1f05a964b75a4fa6160511345e17db Mon Sep 17 00:00:00 2001 From: merowin Date: Sat, 11 May 2024 19:04:57 +0200 Subject: [PATCH 1/3] add components for new board patterns that work with baduk --- packages/shared/src/game_map.ts | 4 +- packages/shared/src/index.ts | 2 + .../src/lib/abstractBoard/boardFactory.ts | 11 +- .../src/variants/__tests__/quantum.test.ts | 40 ++++-- packages/shared/src/variants/baduk.ts | 68 ++++----- packages/shared/src/variants/baduk_utils.ts | 28 ++++ packages/shared/src/variants/drift.ts | 41 +++++- packages/shared/src/variants/keima.ts | 18 ++- packages/shared/src/variants/pyramid.ts | 22 ++- packages/shared/src/variants/quantum.ts | 39 ++++-- packages/vue-client/src/board_map.ts | 18 +-- .../GameCreation/BadukConfigForm.vue | 17 ++- .../GameCreation/DriftGoConfigForm.vue | 9 +- .../src/components/boards/BadukBoard.vue | 4 +- .../components/boards/BadukBoardSelector.vue | 41 ++++++ .../src/components/boards/BadukGraphBoard.vue | 45 ++++++ .../boards/MulticolorGraphBoard.vue | 129 ++++++++++++++++++ .../src/components/boards/QuantumBoard.vue | 2 +- 18 files changed, 434 insertions(+), 104 deletions(-) create mode 100644 packages/vue-client/src/components/boards/BadukBoardSelector.vue create mode 100644 packages/vue-client/src/components/boards/BadukGraphBoard.vue create mode 100644 packages/vue-client/src/components/boards/MulticolorGraphBoard.vue diff --git a/packages/shared/src/game_map.ts b/packages/shared/src/game_map.ts index 6367ec31..cba1a612 100644 --- a/packages/shared/src/game_map.ts +++ b/packages/shared/src/game_map.ts @@ -1,4 +1,3 @@ -import { GridBaduk } from "./variants/baduk"; import { AbstractGame } from "./abstract_game"; import { BadukWithAbstractBoard } from "./variants/badukWithAbstractBoard"; import { Phantom } from "./variants/phantom"; @@ -14,12 +13,13 @@ import { Keima } from "./variants/keima"; import { OneColorGo } from "./variants/one_color"; import { DriftGo } from "./variants/drift"; import { QuantumGo } from "./variants/quantum"; +import { Baduk } from "./variants/baduk"; export const game_map: { // eslint-disable-next-line @typescript-eslint/no-explicit-any [variant: string]: new (config?: any) => AbstractGame; } = { - baduk: GridBaduk, + baduk: Baduk, badukWithAbstractBoard: BadukWithAbstractBoard, phantom: Phantom, parallel: ParallelGo, diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 9ffb7fda..5388e605 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -21,5 +21,7 @@ export { export { type KeimaState } from "./variants/keima"; export * from "./variants/drift"; export * from "./variants/baduk_utils"; +export * from "./lib/abstractBoard/boardFactory"; +export * from "./lib/abstractBoard/intersection"; export const SITE_NAME = "Go Variants"; diff --git a/packages/shared/src/lib/abstractBoard/boardFactory.ts b/packages/shared/src/lib/abstractBoard/boardFactory.ts index 1d807aea..cd5b123f 100644 --- a/packages/shared/src/lib/abstractBoard/boardFactory.ts +++ b/packages/shared/src/lib/abstractBoard/boardFactory.ts @@ -123,11 +123,18 @@ function createGridBoard( export function createGraph( intersections: TIntersection[], + startColor: TColor, ): Graph { const adjacencyMatrix = intersections.map((intersection) => - intersection.neighbours.map((_neighbour, index) => index), + intersection.neighbours.map((neighbour) => + intersections.indexOf(neighbour), + ), + ); + const graph = new Graph(adjacencyMatrix); + intersections.forEach((intersection) => + graph.set(intersection.id, startColor), ); - return new Graph(adjacencyMatrix); + return graph; } function createRthBoard( diff --git a/packages/shared/src/variants/__tests__/quantum.test.ts b/packages/shared/src/variants/__tests__/quantum.test.ts index 723d2b7f..d3a5c5bd 100644 --- a/packages/shared/src/variants/__tests__/quantum.test.ts +++ b/packages/shared/src/variants/__tests__/quantum.test.ts @@ -7,7 +7,10 @@ const B = Color.BLACK; const _ = Color.EMPTY; test("Quantum stone placement", () => { - const game = new QuantumGo({ width: 2, height: 2, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 2, height: 2 }, + komi: 0.5, + }); game.playMove(0, "aa"); expect(game.exportState().boards).toEqual([ @@ -37,7 +40,10 @@ test("Quantum stone placement", () => { }); test("Test throws if incorrect player in the quantum stone phase", () => { - const game = new QuantumGo({ width: 2, height: 2, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 2, height: 2 }, + komi: 0.5, + }); expect(() => game.playMove(1, "aa")).toThrow(); game.playMove(0, "aa"); @@ -45,14 +51,20 @@ test("Test throws if incorrect player in the quantum stone phase", () => { }); test("Test throws stone played on top of stone in quantum phase", () => { - const game = new QuantumGo({ width: 2, height: 2, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 2, height: 2 }, + komi: 0.5, + }); game.playMove(0, "aa"); expect(() => game.playMove(1, "aa")).toThrow(); }); test("Capture quantum stone", () => { - const game = new QuantumGo({ width: 5, height: 3, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 5, height: 3 }, + komi: 0.5, + }); // .{B}. . W // B{W}B . W @@ -102,7 +114,10 @@ test("Capture quantum stone", () => { }); test("Capture non-quantum stone", () => { - const game = new QuantumGo({ width: 5, height: 3, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 5, height: 3 }, + komi: 0.5, + }); // .{B}. . W // B W B .{W} @@ -152,7 +167,10 @@ test("Capture non-quantum stone", () => { }); test("Two passes ends the game", () => { - const game = new QuantumGo({ width: 4, height: 2, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 4, height: 2 }, + komi: 0.5, + }); game.playMove(0, "ba"); game.playMove(1, "ca"); @@ -195,7 +213,10 @@ test("Two passes ends the game", () => { }); test("Placing a stone in a captured quantum position", () => { - const game = new QuantumGo({ width: 9, height: 9, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 9, height: 9 }, + komi: 0.5, + }); // https://www.govariants.com/game/660248dd5e01aefcbd63df6a @@ -238,7 +259,10 @@ test("Placing a stone in a captured quantum position", () => { }); test("Placing a white stone in a captured quantum position", () => { - const game = new QuantumGo({ width: 9, height: 9, komi: 0.5 }); + const game = new QuantumGo({ + board: { type: "grid", width: 9, height: 9 }, + komi: 0.5, + }); // https://www.govariants.com/game/660248dd5e01aefcbd63df6a diff --git a/packages/shared/src/variants/baduk.ts b/packages/shared/src/variants/baduk.ts index 99da4c73..b0be0754 100644 --- a/packages/shared/src/variants/baduk.ts +++ b/packages/shared/src/variants/baduk.ts @@ -14,8 +14,10 @@ import { GraphWrapper } from "../lib/graph"; import { GridBadukConfig, LegacyBadukConfig, - getWidthAndHeight, + NewBadukConfig, isGridBadukConfig, + isLegacyBadukConfig, + mapToNewConfig, } from "./baduk_utils"; export enum Color { @@ -24,12 +26,7 @@ export enum Color { WHITE = 2, } -export type BadukConfig = - | LegacyBadukConfig - | { - komi: number; - board: BoardConfig; - }; +export type BadukConfig = LegacyBadukConfig | NewBadukConfig; export interface BadukState { board: Color[][]; @@ -41,11 +38,10 @@ export interface BadukState { export type BadukMove = { 0: string } | { 1: string }; -// export declare type BadukBoardType = Fillable; // Grid | GraphWrapper, so we have a better idea of serialize() return type export declare type BadukBoard = Grid | GraphWrapper; -export class Baduk extends AbstractGame { +export class Baduk extends AbstractGame { protected captures = { 0: 0, 1: 0 }; private ko_detector = new SuperKoDetector(); protected score_board?: BadukBoard; @@ -55,20 +51,21 @@ export class Baduk extends AbstractGame { /** after game ends, this is black points - white points */ public numeric_result?: number; - constructor(config?: BadukConfig) { - super(config); + constructor(config?: LegacyBadukConfig | BadukConfig) { + super(isLegacyBadukConfig(config) ? mapToNewConfig(config) : config); if (isGridBadukConfig(this.config)) { - const { width, height } = getWidthAndHeight(this.config); - - if (width >= 52 || height >= 52) { + if (this.config.board.width >= 52 || this.config.board.height >= 52) { throw new Error("Baduk does not support sizes greater than 52"); } - this.board = new Grid(width, height).fill(Color.EMPTY); + this.board = new Grid( + this.config.board.width, + this.config.board.height, + ).fill(Color.EMPTY); } else { - const intersections = createBoard(this.config.board!, Intersection); - this.board = new GraphWrapper(createGraph(intersections)); + const intersections = createBoard(this.config.board, Intersection); + this.board = new GraphWrapper(createGraph(intersections, Color.EMPTY)); } } @@ -86,6 +83,14 @@ export class Baduk extends AbstractGame { return this.phase === "gameover" ? [] : [this.next_to_play]; } + private decodeMove(move: string): Coordinate { + if (isGridBadukConfig(this.config)) { + return Coordinate.fromSgfRepr(move); + } + // graph boards encode moves with the unique identifier number + return new Coordinate(Number(move), 0); + } + override playMove(player: number, move: string): void { if (player != this.next_to_play) { throw Error(`It's not player ${player}'s turn!`); @@ -104,7 +109,8 @@ export class Baduk extends AbstractGame { } if (move != "pass") { - const decoded_move = Coordinate.fromSgfRepr(move); + const decoded_move = this.decodeMove(move); + const { x, y } = decoded_move; const color = this.board.at(decoded_move); if (color === undefined) { @@ -215,7 +221,7 @@ export class Baduk extends AbstractGame { this.phase = "gameover"; } - defaultConfig(): GridBadukConfig { + defaultConfig(): NewBadukConfig { return { komi: 6.5, board: { @@ -241,27 +247,3 @@ export function groupHasLiberties( function count_color(value: T) { return (total: number, color: T) => total + (color === value ? 1 : 0); } - -export class GridBaduk extends Baduk { - // ! isn't typesafe, but we know board will be assigned in super() - declare board: Grid; - protected declare score_board?: Grid; - declare config: GridBadukConfig; - constructor(config?: GridBadukConfig) { - if (config && !isGridBadukConfig(config)) { - throw "GridBaduk requires a GridBadukConfig"; - } - super(config); - } - - override defaultConfig(): GridBadukConfig { - return { - komi: 6.5, - board: { - type: BoardPattern.Grid, - width: 19, - height: 19, - }, - }; - } -} diff --git a/packages/shared/src/variants/baduk_utils.ts b/packages/shared/src/variants/baduk_utils.ts index c1352a1c..72940001 100644 --- a/packages/shared/src/variants/baduk_utils.ts +++ b/packages/shared/src/variants/baduk_utils.ts @@ -1,4 +1,5 @@ import { + BoardConfig, BoardPattern, GridBoardConfig, } from "../lib/abstractBoard/boardFactory"; @@ -10,6 +11,11 @@ export type LegacyBadukConfig = { komi: number; }; +export type NewBadukConfig = { + komi: number; + board: BoardConfig; +}; + export type GridBadukConfig = | LegacyBadukConfig | { @@ -31,3 +37,25 @@ export function getWidthAndHeight(config: GridBadukConfig): { const height = "board" in config ? config.board.height : config.height; return { width: width, height: height }; } + +export function isLegacyBadukConfig( + config: object | undefined, +): config is LegacyBadukConfig { + return ( + config !== undefined && + "width" in config && + "height" in config && + "komi" in config + ); +} + +export function mapToNewConfig(config: LegacyBadukConfig): NewBadukConfig { + return { + ...config, + board: { + type: BoardPattern.Grid, + width: config.width, + height: config.height, + }, + }; +} diff --git a/packages/shared/src/variants/drift.ts b/packages/shared/src/variants/drift.ts index 1266cdc1..b73fa5f5 100644 --- a/packages/shared/src/variants/drift.ts +++ b/packages/shared/src/variants/drift.ts @@ -1,23 +1,50 @@ +import { GridBoardConfig } from "../lib/abstractBoard/boardFactory"; import { Coordinate } from "../lib/coordinate"; +import { Grid } from "../lib/grid"; import { getGroup } from "../lib/group_utils"; -import { Color, GridBaduk, groupHasLiberties } from "./baduk"; -import { GridBadukConfig } from "./baduk_utils"; +import { Baduk, Color, groupHasLiberties } from "./baduk"; +import { LegacyBadukConfig, NewBadukConfig } from "./baduk_utils"; -export type DriftGoConfig = GridBadukConfig & { +export type DriftGoConfig = { komi: number; board: GridBoardConfig } & { yShift: number; xShift: number; }; -export class DriftGo extends GridBaduk { +export type LegacyDriftGoConfig = LegacyBadukConfig & { + yShift: number; + xShift: number; +}; + +function isDriftGoConfig(config: object): config is DriftGoConfig { + return ( + "komi" in config && + "board" in config && + "xShift" in config && + "yShift" in config + ); +} + +export class DriftGo extends Baduk { private typedConfig: DriftGoConfig; + declare board: Grid; - constructor(config?: DriftGoConfig) { + constructor(config?: DriftGoConfig | LegacyDriftGoConfig) { super(config); - this.typedConfig = config ?? this.defaultConfig(); + if (!isDriftGoConfig(this.config)) { + throw Error( + `Drift accepts only grid board config. Received config: ${JSON.stringify(config)}`, + ); + } + this.typedConfig = this.config ?? this.defaultConfig(); } defaultConfig(): DriftGoConfig { - return { ...super.defaultConfig(), xShift: 0, yShift: 1 }; + return { + komi: 6.5, + board: { type: "grid", width: 19, height: 19 }, + xShift: 0, + yShift: 1, + }; } protected prepareForNextMove(move: string): void { diff --git a/packages/shared/src/variants/keima.ts b/packages/shared/src/variants/keima.ts index e40b9660..cf23d831 100644 --- a/packages/shared/src/variants/keima.ts +++ b/packages/shared/src/variants/keima.ts @@ -1,13 +1,27 @@ import { Coordinate } from "../lib/coordinate"; -import { BadukState, GridBaduk } from "./baduk"; +import { Baduk, BadukState } from "./baduk"; +import { + GridBadukConfig, + LegacyBadukConfig, + isGridBadukConfig, +} from "./baduk_utils"; export interface KeimaState extends BadukState { keima?: string; } -export class Keima extends GridBaduk { +export class Keima extends Baduk { private move_number = 0; + constructor(config?: GridBadukConfig | LegacyBadukConfig) { + super(config); + if (!isGridBadukConfig(this.config)) { + throw Error( + `Keima accepts only grid board config. Received config: ${JSON.stringify(config)}`, + ); + } + } + playMove(player: number, move: string): void { super.playMove(player, move); this.increment_next_to_play(); diff --git a/packages/shared/src/variants/pyramid.ts b/packages/shared/src/variants/pyramid.ts index a18657b3..c7691dfd 100644 --- a/packages/shared/src/variants/pyramid.ts +++ b/packages/shared/src/variants/pyramid.ts @@ -1,15 +1,27 @@ -import { Color, GridBaduk } from "./baduk"; +import { Baduk, Color } from "./baduk"; import { Grid } from "../lib/grid"; -import { GridBadukConfig, getWidthAndHeight } from "./baduk_utils"; +import { + GridBadukConfig, + LegacyBadukConfig, + isGridBadukConfig, +} from "./baduk_utils"; -export class PyramidGo extends GridBaduk { +export class PyramidGo extends Baduk { private weights: Grid; - constructor(config?: GridBadukConfig) { + declare board: Grid; + declare score_board?: Grid; + + constructor(config?: GridBadukConfig | LegacyBadukConfig) { super(config); + if (!isGridBadukConfig(this.config)) { + throw Error( + `Pyramid accepts only grid board config. Received config: ${JSON.stringify(config)}`, + ); + } // Note: config may be undefined, but this.config is // defined after the AbstractGame constructor is called. - const { width, height } = getWidthAndHeight(this.config); + const { width, height } = this.config.board; this.weights = new Grid(width, height) .fill(0) diff --git a/packages/shared/src/variants/quantum.ts b/packages/shared/src/variants/quantum.ts index 93b26c59..678ebc8c 100644 --- a/packages/shared/src/variants/quantum.ts +++ b/packages/shared/src/variants/quantum.ts @@ -1,8 +1,15 @@ import { AbstractGame } from "../abstract_game"; +import { BoardPattern } from "../lib/abstractBoard/boardFactory"; import { Coordinate, CoordinateLike } from "../lib/coordinate"; import { Grid } from "../lib/grid"; import { Baduk, BadukBoard, BadukConfig, Color } from "./baduk"; -import { GridBadukConfig, getWidthAndHeight } from "./baduk_utils"; +import { + GridBadukConfig, + LegacyBadukConfig, + isGridBadukConfig, + isLegacyBadukConfig, + mapToNewConfig, +} from "./baduk_utils"; export interface QuantumGoState { // length=2 @@ -40,12 +47,18 @@ class BadukHelper { } } -export class QuantumGo extends AbstractGame { +export class QuantumGo extends AbstractGame { subgames: BadukHelper[] = []; quantum_stones: Coordinate[] = []; - constructor(config: GridBadukConfig) { - super(config); + constructor(config: BadukConfig | LegacyBadukConfig) { + super(isLegacyBadukConfig(config) ? mapToNewConfig(config) : config); + + if (config && !isGridBadukConfig(this.config)) { + throw Error( + `Drift accepty only grid board config. Received config: ${JSON.stringify(config)}`, + ); + } } playMove(player: number, move: string): void { @@ -87,8 +100,11 @@ export class QuantumGo extends AbstractGame { const pos = Coordinate.fromSgfRepr(move); // TODO: add a Dimensions class (#216) and look at that instead of // building a grid for no reason - const { width, height } = getWidthAndHeight(this.config); - if (!new Grid(width, height).isInBounds(pos)) { + if ( + !new BadukHelper({ ...this.config, komi: 0 }).badukGame.board.isInBounds( + pos, + ) + ) { throw new Error("Out of bounds!"); } @@ -154,12 +170,12 @@ export class QuantumGo extends AbstractGame { pos: Coordinate | undefined, color: Color, ) => { - const { width, height } = getWidthAndHeight(this.config); - const board = new Grid(width, height).fill(Color.EMPTY); + const board = new BadukHelper({ ...this.config, komi: 0 }).badukGame + .board; if (pos != null) { board.set(pos, color); } - return board.to2DArray(); + return board.serialize(); }; const first = this.quantum_stones[0]; return { @@ -186,7 +202,10 @@ export class QuantumGo extends AbstractGame { return 2; } defaultConfig(): GridBadukConfig { - return { width: 9, height: 9, komi: 7.5 }; + return { + board: { type: BoardPattern.Grid, width: 9, height: 9 }, + komi: 7.5, + }; } specialMoves(): { [key: string]: string } { diff --git a/packages/vue-client/src/board_map.ts b/packages/vue-client/src/board_map.ts index ba1e613c..fe37b87b 100644 --- a/packages/vue-client/src/board_map.ts +++ b/packages/vue-client/src/board_map.ts @@ -1,4 +1,3 @@ -import Baduk from "@/components/boards/BadukBoard.vue"; import BadukBoardAbstract from "@/components/boards/BadukBoardAbstract.vue"; import ParallelGoBoard from "@/components/boards/ParallelGoBoard.vue"; import FreezeGoBoard from "@/components/boards/FreezeGoBoard.vue"; @@ -7,23 +6,24 @@ import FractionalBoard from "@/components/boards/FractionalBoard.vue"; import KeimaBoard from "@/components/boards/KeimaBoard.vue"; import type { Component } from "vue"; import QuantumBoard from "@/components/boards/QuantumBoard.vue"; +import BadukBoardSelector from "./components/boards/BadukBoardSelector.vue"; export const board_map: { [variant: string]: Component<{ config: unknown; gamestate: unknown }>; } = { - baduk: Baduk, - phantom: Baduk, + baduk: BadukBoardSelector, + phantom: BadukBoardSelector, badukWithAbstractBoard: BadukBoardAbstract, parallel: ParallelGoBoard, - capture: Baduk, + capture: BadukBoardSelector, chess: ChessBoard, - tetris: Baduk, - pyramid: Baduk, - "thue-morse": Baduk, + tetris: BadukBoardSelector, + pyramid: BadukBoardSelector, + "thue-morse": BadukBoardSelector, freeze: FreezeGoBoard, fractional: FractionalBoard, keima: KeimaBoard, - "one color": Baduk, - drift: Baduk, + "one color": BadukBoardSelector, + drift: BadukBoardSelector, quantum: QuantumBoard, }; diff --git a/packages/vue-client/src/components/GameCreation/BadukConfigForm.vue b/packages/vue-client/src/components/GameCreation/BadukConfigForm.vue index 1d59c4eb..5048f470 100644 --- a/packages/vue-client/src/components/GameCreation/BadukConfigForm.vue +++ b/packages/vue-client/src/components/GameCreation/BadukConfigForm.vue @@ -1,17 +1,20 @@ import { Grid } from '../../../../shared/src/lib/grid'; + + diff --git a/packages/vue-client/src/components/boards/BadukGraphBoard.vue b/packages/vue-client/src/components/boards/BadukGraphBoard.vue new file mode 100644 index 00000000..22131381 --- /dev/null +++ b/packages/vue-client/src/components/boards/BadukGraphBoard.vue @@ -0,0 +1,45 @@ + + + diff --git a/packages/vue-client/src/components/boards/MulticolorGraphBoard.vue b/packages/vue-client/src/components/boards/MulticolorGraphBoard.vue new file mode 100644 index 00000000..d4c8d44e --- /dev/null +++ b/packages/vue-client/src/components/boards/MulticolorGraphBoard.vue @@ -0,0 +1,129 @@ + + + + + + + diff --git a/packages/vue-client/src/components/boards/QuantumBoard.vue b/packages/vue-client/src/components/boards/QuantumBoard.vue index 43b0b0fc..b89e8f67 100644 --- a/packages/vue-client/src/components/boards/QuantumBoard.vue +++ b/packages/vue-client/src/components/boards/QuantumBoard.vue @@ -7,8 +7,8 @@ import MulticolorGridBoard from "./MulticolorGridBoard.vue"; import { Coordinate, type QuantumGoState, - getWidthAndHeight, GridBadukConfig, + getWidthAndHeight, } from "@ogfcommunity/variants-shared"; import { Grid } from "@ogfcommunity/variants-shared"; import { computed } from "vue"; From 283712084c54bcb6bcbeee51474307db5c34a3af Mon Sep 17 00:00:00 2001 From: merowin Date: Sun, 12 May 2024 09:41:54 +0200 Subject: [PATCH 2/3] add back adapter class, change types, test case --- .../shared/src/lib/__tests__/board.test.ts | 22 +++++++++++ .../src/lib/abstractBoard/boardFactory.ts | 5 +-- packages/shared/src/variants/baduk.ts | 29 ++++++++++++-- packages/shared/src/variants/baduk_utils.ts | 12 +++--- packages/shared/src/variants/drift.ts | 39 ++++--------------- packages/shared/src/variants/keima.ts | 18 +-------- packages/shared/src/variants/pyramid.ts | 17 ++------ packages/shared/src/variants/quantum.ts | 18 +++------ .../src/components/boards/BadukBoard.vue | 2 +- .../components/boards/BadukBoardSelector.vue | 3 +- 10 files changed, 76 insertions(+), 89 deletions(-) create mode 100644 packages/shared/src/lib/__tests__/board.test.ts diff --git a/packages/shared/src/lib/__tests__/board.test.ts b/packages/shared/src/lib/__tests__/board.test.ts new file mode 100644 index 00000000..07d1bca9 --- /dev/null +++ b/packages/shared/src/lib/__tests__/board.test.ts @@ -0,0 +1,22 @@ +import { Color } from "../../variants/badukWithAbstractBoard"; +import { + BoardPattern, + createBoard, + createGraph, +} from "../abstractBoard/boardFactory"; +import { Intersection } from "../abstractBoard/intersection"; + +test("create rhombitrihexagonal board", () => { + const intersections = createBoard( + { + type: BoardPattern.Polygonal, + size: 2, + }, + Intersection, + ); + const graph = createGraph(intersections, Color.EMPTY); + + // Remark: This test case is not independent of implementation + // Also see PolygonalBoardHelper lines 4 - 28 + expect(graph.neighbors(0)).toEqual([1, 5, 6, 17]); +}); diff --git a/packages/shared/src/lib/abstractBoard/boardFactory.ts b/packages/shared/src/lib/abstractBoard/boardFactory.ts index cd5b123f..9235d719 100644 --- a/packages/shared/src/lib/abstractBoard/boardFactory.ts +++ b/packages/shared/src/lib/abstractBoard/boardFactory.ts @@ -130,10 +130,7 @@ export function createGraph( intersections.indexOf(neighbour), ), ); - const graph = new Graph(adjacencyMatrix); - intersections.forEach((intersection) => - graph.set(intersection.id, startColor), - ); + const graph = new Graph(adjacencyMatrix).fill(startColor); return graph; } diff --git a/packages/shared/src/variants/baduk.ts b/packages/shared/src/variants/baduk.ts index b0be0754..52a5231e 100644 --- a/packages/shared/src/variants/baduk.ts +++ b/packages/shared/src/variants/baduk.ts @@ -4,7 +4,6 @@ import { getGroup, getOuterBorder } from "../lib/group_utils"; import { SuperKoDetector } from "../lib/ko_detector"; import { AbstractGame } from "../abstract_game"; import { - BoardConfig, BoardPattern, createBoard, createGraph, @@ -12,9 +11,9 @@ import { import { Intersection } from "../lib/abstractBoard/intersection"; import { GraphWrapper } from "../lib/graph"; import { - GridBadukConfig, LegacyBadukConfig, NewBadukConfig, + NewGridBadukConfig, isGridBadukConfig, isLegacyBadukConfig, mapToNewConfig, @@ -51,7 +50,7 @@ export class Baduk extends AbstractGame { /** after game ends, this is black points - white points */ public numeric_result?: number; - constructor(config?: LegacyBadukConfig | BadukConfig) { + constructor(config?: BadukConfig) { super(isLegacyBadukConfig(config) ? mapToNewConfig(config) : config); if (isGridBadukConfig(this.config)) { @@ -247,3 +246,27 @@ export function groupHasLiberties( function count_color(value: T) { return (total: number, color: T) => total + (color === value ? 1 : 0); } + +export class GridBaduk extends Baduk { + // ! isn't typesafe, but we know board will be assigned in super() + declare board: Grid; + protected declare score_board?: Grid; + declare config: NewGridBadukConfig; + constructor(config?: BadukConfig) { + if (config && !isGridBadukConfig(config)) { + throw "GridBaduk requires a GridBadukConfig"; + } + super(config); + } + + override defaultConfig(): NewGridBadukConfig { + return { + komi: 6.5, + board: { + type: BoardPattern.Grid, + width: 19, + height: 19, + }, + }; + } +} diff --git a/packages/shared/src/variants/baduk_utils.ts b/packages/shared/src/variants/baduk_utils.ts index 72940001..49dbef1c 100644 --- a/packages/shared/src/variants/baduk_utils.ts +++ b/packages/shared/src/variants/baduk_utils.ts @@ -16,12 +16,12 @@ export type NewBadukConfig = { board: BoardConfig; }; -export type GridBadukConfig = - | LegacyBadukConfig - | { - komi: number; - board: GridBoardConfig; - }; +export type NewGridBadukConfig = { + komi: number; + board: GridBoardConfig; +}; + +export type GridBadukConfig = LegacyBadukConfig | NewGridBadukConfig; export function isGridBadukConfig( config: BadukConfig, diff --git a/packages/shared/src/variants/drift.ts b/packages/shared/src/variants/drift.ts index b73fa5f5..2a0fb564 100644 --- a/packages/shared/src/variants/drift.ts +++ b/packages/shared/src/variants/drift.ts @@ -1,43 +1,18 @@ -import { GridBoardConfig } from "../lib/abstractBoard/boardFactory"; import { Coordinate } from "../lib/coordinate"; import { Grid } from "../lib/grid"; import { getGroup } from "../lib/group_utils"; -import { Baduk, Color, groupHasLiberties } from "./baduk"; -import { LegacyBadukConfig, NewBadukConfig } from "./baduk_utils"; +import { Color, GridBaduk, groupHasLiberties } from "./baduk"; +import { NewGridBadukConfig } from "./baduk_utils"; -export type DriftGoConfig = { komi: number; board: GridBoardConfig } & { +export type DriftGoConfig = NewGridBadukConfig & { yShift: number; xShift: number; }; -export type LegacyDriftGoConfig = LegacyBadukConfig & { - yShift: number; - xShift: number; -}; - -function isDriftGoConfig(config: object): config is DriftGoConfig { - return ( - "komi" in config && - "board" in config && - "xShift" in config && - "yShift" in config - ); -} - -export class DriftGo extends Baduk { - private typedConfig: DriftGoConfig; +export class DriftGo extends GridBaduk { + declare config: DriftGoConfig; declare board: Grid; - constructor(config?: DriftGoConfig | LegacyDriftGoConfig) { - super(config); - if (!isDriftGoConfig(this.config)) { - throw Error( - `Drift accepts only grid board config. Received config: ${JSON.stringify(config)}`, - ); - } - this.typedConfig = this.config ?? this.defaultConfig(); - } - defaultConfig(): DriftGoConfig { return { komi: 6.5, @@ -81,8 +56,8 @@ export class DriftGo extends Baduk { private shift(coord: Coordinate): Coordinate { return new Coordinate( - (coord.x - this.typedConfig.xShift) % this.board.width, - (coord.y - this.typedConfig.yShift) % this.board.height, + (coord.x - this.config.xShift) % this.board.width, + (coord.y - this.config.yShift) % this.board.height, ); } diff --git a/packages/shared/src/variants/keima.ts b/packages/shared/src/variants/keima.ts index cf23d831..e40b9660 100644 --- a/packages/shared/src/variants/keima.ts +++ b/packages/shared/src/variants/keima.ts @@ -1,27 +1,13 @@ import { Coordinate } from "../lib/coordinate"; -import { Baduk, BadukState } from "./baduk"; -import { - GridBadukConfig, - LegacyBadukConfig, - isGridBadukConfig, -} from "./baduk_utils"; +import { BadukState, GridBaduk } from "./baduk"; export interface KeimaState extends BadukState { keima?: string; } -export class Keima extends Baduk { +export class Keima extends GridBaduk { private move_number = 0; - constructor(config?: GridBadukConfig | LegacyBadukConfig) { - super(config); - if (!isGridBadukConfig(this.config)) { - throw Error( - `Keima accepts only grid board config. Received config: ${JSON.stringify(config)}`, - ); - } - } - playMove(player: number, move: string): void { super.playMove(player, move); this.increment_next_to_play(); diff --git a/packages/shared/src/variants/pyramid.ts b/packages/shared/src/variants/pyramid.ts index c7691dfd..0494dfe4 100644 --- a/packages/shared/src/variants/pyramid.ts +++ b/packages/shared/src/variants/pyramid.ts @@ -1,23 +1,14 @@ -import { Baduk, Color } from "./baduk"; +import { Color, GridBaduk } from "./baduk"; import { Grid } from "../lib/grid"; -import { - GridBadukConfig, - LegacyBadukConfig, - isGridBadukConfig, -} from "./baduk_utils"; +import { GridBadukConfig } from "./baduk_utils"; -export class PyramidGo extends Baduk { +export class PyramidGo extends GridBaduk { private weights: Grid; declare board: Grid; declare score_board?: Grid; - constructor(config?: GridBadukConfig | LegacyBadukConfig) { + constructor(config?: GridBadukConfig) { super(config); - if (!isGridBadukConfig(this.config)) { - throw Error( - `Pyramid accepts only grid board config. Received config: ${JSON.stringify(config)}`, - ); - } // Note: config may be undefined, but this.config is // defined after the AbstractGame constructor is called. diff --git a/packages/shared/src/variants/quantum.ts b/packages/shared/src/variants/quantum.ts index 678ebc8c..c987bc84 100644 --- a/packages/shared/src/variants/quantum.ts +++ b/packages/shared/src/variants/quantum.ts @@ -1,12 +1,10 @@ import { AbstractGame } from "../abstract_game"; import { BoardPattern } from "../lib/abstractBoard/boardFactory"; import { Coordinate, CoordinateLike } from "../lib/coordinate"; -import { Grid } from "../lib/grid"; import { Baduk, BadukBoard, BadukConfig, Color } from "./baduk"; import { - GridBadukConfig, - LegacyBadukConfig, - isGridBadukConfig, + NewBadukConfig, + NewGridBadukConfig, isLegacyBadukConfig, mapToNewConfig, } from "./baduk_utils"; @@ -47,18 +45,12 @@ class BadukHelper { } } -export class QuantumGo extends AbstractGame { +export class QuantumGo extends AbstractGame { subgames: BadukHelper[] = []; quantum_stones: Coordinate[] = []; - constructor(config: BadukConfig | LegacyBadukConfig) { + constructor(config: BadukConfig) { super(isLegacyBadukConfig(config) ? mapToNewConfig(config) : config); - - if (config && !isGridBadukConfig(this.config)) { - throw Error( - `Drift accepty only grid board config. Received config: ${JSON.stringify(config)}`, - ); - } } playMove(player: number, move: string): void { @@ -201,7 +193,7 @@ export class QuantumGo extends AbstractGame { numPlayers(): number { return 2; } - defaultConfig(): GridBadukConfig { + defaultConfig(): NewGridBadukConfig { return { board: { type: BoardPattern.Grid, width: 9, height: 9 }, komi: 7.5, diff --git a/packages/vue-client/src/components/boards/BadukBoard.vue b/packages/vue-client/src/components/boards/BadukBoard.vue index 55433afe..17f3869c 100644 --- a/packages/vue-client/src/components/boards/BadukBoard.vue +++ b/packages/vue-client/src/components/boards/BadukBoard.vue @@ -16,7 +16,7 @@ const props = defineProps<{ }>(); const width = computed(() => getWidthAndHeight(props.config).width); -const height = computed(() => getWidthAndHeight(props.config).width); +const height = computed(() => getWidthAndHeight(props.config).height); const positions = computed(positionsGetter(width, height)); function colorToClassString(color: Color): string { diff --git a/packages/vue-client/src/components/boards/BadukBoardSelector.vue b/packages/vue-client/src/components/boards/BadukBoardSelector.vue index e89eecbd..5bd72934 100644 --- a/packages/vue-client/src/components/boards/BadukBoardSelector.vue +++ b/packages/vue-client/src/components/boards/BadukBoardSelector.vue @@ -3,6 +3,7 @@ import { BadukConfig, BadukState, GridBadukConfig, + NewBadukConfig, isGridBadukConfig, } from "@ogfcommunity/variants-shared"; import { computed } from "vue"; @@ -29,7 +30,7 @@ const isGridBoard = computed(() => isGridBadukConfig(props.config)); Date: Mon, 13 May 2024 09:45:04 +0200 Subject: [PATCH 3/3] add test assertion --- packages/shared/src/lib/__tests__/board.test.ts | 9 ++++++++- packages/shared/src/lib/abstractBoard/boardFactory.ts | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/lib/__tests__/board.test.ts b/packages/shared/src/lib/__tests__/board.test.ts index 07d1bca9..6017ef1b 100644 --- a/packages/shared/src/lib/__tests__/board.test.ts +++ b/packages/shared/src/lib/__tests__/board.test.ts @@ -16,7 +16,14 @@ test("create rhombitrihexagonal board", () => { ); const graph = createGraph(intersections, Color.EMPTY); - // Remark: This test case is not independent of implementation + expect( + graph.reduce( + (prev, _, index, g) => prev + g.neighbors(index).length, + 0, + ), + ).toEqual(6 * 4 + 12 * 3); + + // Remark: This assertion is not independent of implementation // Also see PolygonalBoardHelper lines 4 - 28 expect(graph.neighbors(0)).toEqual([1, 5, 6, 17]); }); diff --git a/packages/shared/src/lib/abstractBoard/boardFactory.ts b/packages/shared/src/lib/abstractBoard/boardFactory.ts index 9235d719..46944ebf 100644 --- a/packages/shared/src/lib/abstractBoard/boardFactory.ts +++ b/packages/shared/src/lib/abstractBoard/boardFactory.ts @@ -130,8 +130,7 @@ export function createGraph( intersections.indexOf(neighbour), ), ); - const graph = new Graph(adjacencyMatrix).fill(startColor); - return graph; + return new Graph(adjacencyMatrix).fill(startColor); } function createRthBoard(