extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
1import tenuki from 'tenuki';
2import type { MoveRecord } from '$lib/types';
3
4const { Game } = tenuki;
5
6/**
7 * Calculate liberties for all stones on the board.
8 * Returns a 2D array where each position contains the liberty count for that stone's group.
9 */
10export function calculateLiberties(
11 moves: MoveRecord[],
12 boardSize: number = 19
13): Array<Array<number>> {
14 const game = new Game({
15 boardSize,
16 });
17
18 // Replay all moves
19 for (const move of moves) {
20 game.playAt(move.y, move.x);
21 }
22
23 // Get current board state
24 const state: Array<Array<'empty' | 'black' | 'white'>> = [];
25 for (let y = 0; y < boardSize; y++) {
26 const row: Array<'empty' | 'black' | 'white'> = [];
27 for (let x = 0; x < boardSize; x++) {
28 const intersection = game.intersectionAt(y, x);
29 row.push(intersection.value as 'empty' | 'black' | 'white');
30 }
31 state.push(row);
32 }
33
34 // Initialize liberty counts
35 const liberties: Array<Array<number>> = Array(boardSize).fill(0).map(() => Array(boardSize).fill(0));
36 const visited: Array<Array<boolean>> = Array(boardSize).fill(false).map(() => Array(boardSize).fill(false));
37
38 // Helper to get adjacent coordinates
39 const getAdjacent = (y: number, x: number): Array<[number, number]> => {
40 const adj: Array<[number, number]> = [];
41 if (y > 0) adj.push([y - 1, x]);
42 if (y < boardSize - 1) adj.push([y + 1, x]);
43 if (x > 0) adj.push([y, x - 1]);
44 if (x < boardSize - 1) adj.push([y, x + 1]);
45 return adj;
46 };
47
48 // Find all stones in a group and their liberties using flood fill
49 const findGroup = (startY: number, startX: number): { stones: Array<[number, number]>, libertyCount: number } => {
50 const color = state[startY][startX];
51 if (color === 'empty') return { stones: [], libertyCount: 0 };
52
53 const stones: Array<[number, number]> = [];
54 const libertySet = new Set<string>();
55 const queue: Array<[number, number]> = [[startY, startX]];
56 const groupVisited = new Set<string>();
57
58 while (queue.length > 0) {
59 const [y, x] = queue.shift()!;
60 const key = `${y},${x}`;
61
62 if (groupVisited.has(key)) continue;
63 groupVisited.add(key);
64 stones.push([y, x]);
65
66 for (const [ny, nx] of getAdjacent(y, x)) {
67 const adjKey = `${ny},${nx}`;
68 if (state[ny][nx] === 'empty') {
69 libertySet.add(adjKey);
70 } else if (state[ny][nx] === color && !groupVisited.has(adjKey)) {
71 queue.push([ny, nx]);
72 }
73 }
74 }
75
76 return { stones, libertyCount: libertySet.size };
77 };
78
79 // Calculate liberties for each group
80 for (let y = 0; y < boardSize; y++) {
81 for (let x = 0; x < boardSize; x++) {
82 if (state[y][x] !== 'empty' && !visited[y][x]) {
83 const { stones, libertyCount } = findGroup(y, x);
84
85 // Set the liberty count for all stones in this group
86 for (const [sy, sx] of stones) {
87 liberties[sy][sx] = libertyCount;
88 visited[sy][sx] = true;
89 }
90 }
91 }
92 }
93
94 return liberties;
95}