Skip to content

Commit

Permalink
Merge pull request #258 from govariantsteam/add_board_patterns_to_baduk
Browse files Browse the repository at this point in the history
add components for new board patterns that work with baduk
  • Loading branch information
merowin committed May 13, 2024
2 parents 089225e + 8ccd58e commit 5ec2597
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 90 deletions.
4 changes: 2 additions & 2 deletions packages/shared/src/game_map.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { GridBaduk } from "./variants/baduk";
import { AbstractGame } from "./abstract_game";
import { BadukWithAbstractBoard } from "./variants/badukWithAbstractBoard";
import { Phantom } from "./variants/phantom";
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
29 changes: 29 additions & 0 deletions packages/shared/src/lib/__tests__/board.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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);

expect(
graph.reduce<number>(
(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]);
});
7 changes: 5 additions & 2 deletions packages/shared/src/lib/abstractBoard/boardFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,14 @@ function createGridBoard<TIntersection extends Intersection>(

export function createGraph<TIntersection extends Intersection, TColor>(
intersections: TIntersection[],
startColor: TColor,
): Graph<TColor> {
const adjacencyMatrix = intersections.map((intersection) =>
intersection.neighbours.map((_neighbour, index) => index),
intersection.neighbours.map((neighbour) =>
intersections.indexOf(neighbour),
),
);
return new Graph<TColor>(adjacencyMatrix);
return new Graph<TColor>(adjacencyMatrix).fill(startColor);
}

function createRthBoard<TIntersection extends Intersection>(
Expand Down
40 changes: 32 additions & 8 deletions packages/shared/src/variants/__tests__/quantum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down Expand Up @@ -37,22 +40,31 @@ 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");
expect(() => game.playMove(0, "bb")).toThrow();
});

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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
51 changes: 28 additions & 23 deletions packages/shared/src/variants/baduk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import { getGroup, getOuterBorder } from "../lib/group_utils";
import { SuperKoDetector } from "../lib/ko_detector";
import { AbstractGame } from "../abstract_game";
import {
BoardConfig,
BoardPattern,
createBoard,
createGraph,
} from "../lib/abstractBoard/boardFactory";
import { Intersection } from "../lib/abstractBoard/intersection";
import { GraphWrapper } from "../lib/graph";
import {
GridBadukConfig,
LegacyBadukConfig,
getWidthAndHeight,
NewBadukConfig,
NewGridBadukConfig,
isGridBadukConfig,
isLegacyBadukConfig,
mapToNewConfig,
} from "./baduk_utils";

export enum Color {
Expand All @@ -24,12 +25,7 @@ export enum Color {
WHITE = 2,
}

export type BadukConfig =
| LegacyBadukConfig
| {
komi: number;
board: BoardConfig;
};
export type BadukConfig = LegacyBadukConfig | NewBadukConfig;

export interface BadukState {
board: Color[][];
Expand All @@ -41,11 +37,10 @@ export interface BadukState {

export type BadukMove = { 0: string } | { 1: string };

// export declare type BadukBoardType<TColor> = Fillable<CoordinateLike, TColor>;
// Grid | GraphWrapper, so we have a better idea of serialize() return type
export declare type BadukBoard<TColor> = Grid<TColor> | GraphWrapper<TColor>;

export class Baduk extends AbstractGame<BadukConfig, BadukState> {
export class Baduk extends AbstractGame<NewBadukConfig, BadukState> {
protected captures = { 0: 0, 1: 0 };
private ko_detector = new SuperKoDetector();
protected score_board?: BadukBoard<Color>;
Expand All @@ -56,19 +51,20 @@ export class Baduk extends AbstractGame<BadukConfig, BadukState> {
public numeric_result?: number;

constructor(config?: BadukConfig) {
super(config);
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<Color>(width, height).fill(Color.EMPTY);
this.board = new Grid<Color>(
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));
}
}

Expand All @@ -86,6 +82,14 @@ export class Baduk extends AbstractGame<BadukConfig, BadukState> {
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!`);
Expand All @@ -104,7 +108,8 @@ export class Baduk extends AbstractGame<BadukConfig, BadukState> {
}

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) {
Expand Down Expand Up @@ -215,7 +220,7 @@ export class Baduk extends AbstractGame<BadukConfig, BadukState> {
this.phase = "gameover";
}

defaultConfig(): GridBadukConfig {
defaultConfig(): NewBadukConfig {
return {
komi: 6.5,
board: {
Expand Down Expand Up @@ -246,15 +251,15 @@ export class GridBaduk extends Baduk {
// ! isn't typesafe, but we know board will be assigned in super()
declare board: Grid<Color>;
protected declare score_board?: Grid<Color>;
declare config: GridBadukConfig;
constructor(config?: GridBadukConfig) {
declare config: NewGridBadukConfig;
constructor(config?: BadukConfig) {
if (config && !isGridBadukConfig(config)) {
throw "GridBaduk requires a GridBadukConfig";
}
super(config);
}

override defaultConfig(): GridBadukConfig {
override defaultConfig(): NewGridBadukConfig {
return {
komi: 6.5,
board: {
Expand Down
40 changes: 34 additions & 6 deletions packages/shared/src/variants/baduk_utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BoardConfig,
BoardPattern,
GridBoardConfig,
} from "../lib/abstractBoard/boardFactory";
Expand All @@ -10,12 +11,17 @@ export type LegacyBadukConfig = {
komi: number;
};

export type GridBadukConfig =
| LegacyBadukConfig
| {
komi: number;
board: GridBoardConfig;
};
export type NewBadukConfig = {
komi: number;
board: BoardConfig;
};

export type NewGridBadukConfig = {
komi: number;
board: GridBoardConfig;
};

export type GridBadukConfig = LegacyBadukConfig | NewGridBadukConfig;

export function isGridBadukConfig(
config: BadukConfig,
Expand All @@ -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,
},
};
}

0 comments on commit 5ec2597

Please sign in to comment.