Skip to content

Commit

Permalink
Merge pull request #255 from govariantsteam/add_board_patterns_to_baduk
Browse files Browse the repository at this point in the history
Add new boards to Baduk variant (part 1)
  • Loading branch information
merowin committed May 11, 2024
2 parents 16a6bb1 + fdb2b4c commit 089225e
Show file tree
Hide file tree
Showing 25 changed files with 407 additions and 122 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,4 @@
import { Baduk } from "./variants/baduk";
import { GridBaduk } from "./variants/baduk";
import { AbstractGame } from "./abstract_game";
import { BadukWithAbstractBoard } from "./variants/badukWithAbstractBoard";
import { Phantom } from "./variants/phantom";
Expand All @@ -19,7 +19,7 @@ export const game_map: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[variant: string]: new (config?: any) => AbstractGame;
} = {
baduk: Baduk,
baduk: GridBaduk,
badukWithAbstractBoard: BadukWithAbstractBoard,
phantom: Phantom,
parallel: ParallelGo,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ export {
} from "./variants/fractional";
export { type KeimaState } from "./variants/keima";
export * from "./variants/drift";
export * from "./variants/baduk_utils";

export const SITE_NAME = "Go Variants";
10 changes: 10 additions & 0 deletions packages/shared/src/lib/abstractBoard/boardFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CreatePolygonalBoard as createPolygonalBoard } from "../../variants/bad
import { CreateCircularBoard } from "../../variants/badukWithAbstractBoard/abstractBoard/CircularBoardHelper";
import { TrihexagonalBoardHelper } from "../../variants/badukWithAbstractBoard/abstractBoard/TrihexagonalBoardHelper";
import { createSierpinskyBoard as createSierpinskyBoardExternal } from "../../variants/badukWithAbstractBoard/abstractBoard/SierpinskyBoard";
import { Graph } from "../graph";

export const BoardPattern = {
Grid: "grid",
Expand Down Expand Up @@ -120,6 +121,15 @@ function createGridBoard<TIntersection extends Intersection>(
return intersections;
}

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

function createRthBoard<TIntersection extends Intersection>(
config: RhombitrihexagonalBoardConfig,
intersectionConstructor: IntersectionConstructor<TIntersection>,
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/lib/coordinate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class Coordinate implements CoordinateLike {
return new Coordinate(decodeChar(move[0]), decodeChar(move[1]));
}

equals(other: Coordinate) {
equals(other: CoordinateLike) {
return this.x === other.x && this.y === other.y;
}
}
Expand Down
167 changes: 167 additions & 0 deletions packages/shared/src/lib/graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Coordinate, CoordinateLike } from "./coordinate";
import { Fillable } from "./group_utils";

export type SerializedGraph<T> = { adjacency_array: number[][]; data: T[] };

export class Graph<T> implements Fillable<number, T> {
private data: T[];
private adjacency_array: number[][];
constructor(adjacency_array: number[][]) {
this.adjacency_array = copy2D(adjacency_array);
this.data = new Array(adjacency_array.length);
}

at(index: number): T | undefined {
return this.data.at(index);
}

set(index: number, value: T): void {
this.data[index] = value;
}

map<S>(
callbackfn: (value: T, index: number, grid: Graph<T>) => S,
thisArg?: this,
): Graph<S> {
const ret = new Graph<S>(this.adjacency_array);
ret.data = this.data.map(
(value: T, index: number) => callbackfn(value, index, this),
thisArg,
);
return ret;
}

forEach(
callbackfn: (value: T, index: number, grid: Graph<T>) => void,
thisArg?: this,
): void {
this.data.forEach((value: T, index: number) => {
callbackfn(value, index, this);
}, thisArg);
}

static fromPlainObject<T>(o: SerializedGraph<T>) {
if (o.adjacency_array.length != o.data.length) {
throw new Error("Adjacency array must be the same length as data array");
}
const ret = new Graph<T>(o.adjacency_array);
ret.data = copyArray(o.data);
return ret;
}

export(): SerializedGraph<T> {
return {
adjacency_array: copy2D(this.adjacency_array),
data: copyArray(this.data),
};
}

fill(val: T, start?: number, end?: number): Graph<T> {
this.data.fill(val, start, end);
return this;
}

neighbors(index: number) {
return copyArray(this.adjacency_array[index]);
}

isInBounds(index: number) {
return index >= 0 && index < this.data.length;
}

reduce<OutT>(
callbackfn: (
previousValue: OutT,
currentValue: T,
index: number,
array: Graph<T>,
) => OutT,
initialValue: OutT,
): OutT {
return this.data.reduce(
(previousValue, currentValue, index) =>
callbackfn(previousValue, currentValue, index, this),
initialValue,
);
}

serialize() {
return [...this.data];
}
}

function identity<T>(x: T): T {
return x;
}
function copyArray<T>(a: T[]): T[] {
return a.map(identity);
}
function copy2D<T>(a: T[][]): T[][] {
return a.map(copyArray);
}

export class GraphWrapper<T> implements Fillable<CoordinateLike, T> {
private graph: Graph<T>;

constructor(graph: Graph<T>) {
this.graph = graph;
}

private packNumber(num: number): Coordinate {
return new Coordinate(num, 0);
}

private unpackCoordinate(coord: CoordinateLike): number {
if (coord.y !== 0) {
throw Error(
"Implementation Error: Graph Wrapper accepty only coordinates with y = 0.",
);
}

return coord.x;
}

at(index: CoordinateLike): T | undefined {
return this.graph.at(this.unpackCoordinate(index));
}
set(index: CoordinateLike, val: T): void {
this.graph.set(this.unpackCoordinate(index), val);
}
map<TDest>(f: (val: T) => TDest): GraphWrapper<TDest> {
return new GraphWrapper(this.graph.map(f));
}
neighbors(index: CoordinateLike): Coordinate[] {
return this.graph
.neighbors(this.unpackCoordinate(index))
.map(this.packNumber);
}
isInBounds(index: CoordinateLike): boolean {
return this.graph.isInBounds(this.unpackCoordinate(index));
}
forEach(f: (value: T, index: CoordinateLike) => void): void {
return this.graph.forEach((value: T, index: number) =>
f(value, this.packNumber(index)),
);
}

serialize() {
// Return a 2D array, since this looks a lot like a Grid.
return [this.graph.serialize()];
}

reduce<OutT>(
callbackfn: (
previousValue: OutT,
currentValue: T,
index: CoordinateLike,
array: GraphWrapper<T>,
) => OutT,
initialValue: OutT,
): OutT {
return this.graph.reduce(
(previousValue, currentValue, index) =>
callbackfn(previousValue, currentValue, this.packNumber(index), this),
initialValue,
);
}
}
9 changes: 7 additions & 2 deletions packages/shared/src/lib/grid.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Coordinate, type CoordinateLike } from "./coordinate";
import { Fillable } from "./group_utils";

/**
* A 2D analog to the native JavaScript array. As much as possible,
* the API should match that of a regular Array.
*/
export class Grid<T> {
export class Grid<T> implements Fillable<CoordinateLike, T> {
private arr: Array<T>;
constructor(
public readonly width: number,
Expand Down Expand Up @@ -68,7 +69,7 @@ export class Grid<T> {
}, thisArg);
}

static from2DArray<T>(array: T[][]) {
static from2DArray<T>(array: T[][]): Grid<T> {
const height = array.length;

if (!height) {
Expand Down Expand Up @@ -143,6 +144,10 @@ export class Grid<T> {
initialValue,
);
}

serialize() {
return this.to2DArray();
}
}

function flat_index_to_coordinate(index: number, width: number): Coordinate {
Expand Down
7 changes: 6 additions & 1 deletion packages/shared/src/lib/group_utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// Perhaps we can remove some of these functions if it becomes cumbersome to
// implement these functions every time. For example, `forEach` can be replaced
// by map if we ignore the output
interface Fillable<K, V> {
export interface Fillable<K, V> {
at(index: K): V | undefined;
set(index: K, val: V): void;
map<V2>(f: (val: V) => V2): Fillable<K, V2>;
neighbors(index: K): K[];
isInBounds(index: K): boolean;
forEach(f: (value: V, index: K) => void): void;
// It could be interesting to have this be some generic type that is
// parameterized by V, but unfortunately it's not supported by TS
// https://github.com/microsoft/TypeScript/issues/1213
// eslint-disable-next-line @typescript-eslint/no-explicit-any
serialize(): unknown;
}

export function getGroup<K, V>(index: K, g: Fillable<K, V>): K[] {
Expand Down

0 comments on commit 089225e

Please sign in to comment.