Skip to content

Commit

Permalink
Fix Voronoi
Browse files Browse the repository at this point in the history
It turns out the voronoi pattern is not the same as just getting
the "closest" color. This re-implementation leaves much less empty
space on the board.
  • Loading branch information
benjaminpjones committed Nov 8, 2023
1 parent 439110f commit 30572d2
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 58 deletions.
4 changes: 2 additions & 2 deletions src/__tests__/ScoreEstimator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ describe("ScoreEstimator", () => {
const se = new ScoreEstimator(undefined, engine, 10, 0.5, false);

expect(se.ownership).toEqual([
[0, 0, 0, -1, 1, 1, 1, 1, 1],
[0, 0, 0, -1, 1, 1, 1, 1, 1],
[1, 0, -1, -1, 1, 1, 1, 1, 1],
[1, 1, 0, -1, 1, 1, 1, 1, 1],
[1, 1, 1, -1, 0, 1, 1, 1, 1],
[0, 0, 0, -1, 0, 1, 1, 1, 1],
[-1, -1, -1, 0, 1, 1, 1, 1, 1],
Expand Down
38 changes: 21 additions & 17 deletions src/local_estimators/__tests__/voronoi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,7 @@
* limitations under the License.
*/

import { distanceMap, estimateScoreVoronoi } from "../voronoi";

test("distanceMap", async () => {
const board = [
[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
];

expect(distanceMap(board, 1)).toEqual([
[2, 1, 2, 3, 4],
[1, 0, 1, 2, 3],
[2, 1, 0, 1, 2],
[3, 2, 1, 2, 3],
]);
});
import { estimateScoreVoronoi } from "../voronoi";

test("one color only scores board for that color", () => {
const board = [
Expand All @@ -45,3 +29,23 @@ test("one color only scores board for that color", () => {
[1, 1, 1],
]);
});

test("border is one stone wide", () => {
const board = [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, -1, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
];

expect(estimateScoreVoronoi(board)).toEqual([
[1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 0, -1],
[1, 1, 1, 0, -1, -1],
[1, 1, 0, -1, -1, -1],
[1, 0, -1, -1, -1, -1],
[0, -1, -1, -1, -1, -1],
]);
});
60 changes: 21 additions & 39 deletions src/local_estimators/voronoi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,42 @@
* limitations under the License.
*/

import { makeEmptyObjectMatrix, makeMatrix } from "../GoMath";
import { dup } from "../GoUtil";

/**
* This estimator simply marks territory for whichever color has a
* closer stone (Manhattan distance). See discussion at
* https://forums.online-go.com/t/weak-score-estimator-and-japanese-rules/41041/70
*/
export function estimateScoreVoronoi(board: number[][]) {
const black_distance_map = distanceMap(board, 1);
const white_distance_map = distanceMap(board, -1);

const { width, height } = get_dims(board);

const ownership = makeMatrix(width, height);

for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (white_distance_map[y][x] < black_distance_map[y][x]) {
ownership[y][x] = -1;
} else if (white_distance_map[y][x] > black_distance_map[y][x]) {
ownership[y][x] = 1;
} else {
ownership[y][x] = 0;
}
}
const ownership: number[][] = dup(board);
let points = getPoints(board, (pt) => pt !== 0);
while (points.length) {
const unvisited = points
.flatMap((pt) => getNeighbors(width, height, pt))
.filter((pt) => ownership[pt.y][pt.x] === 0);
unvisited
.map((pt) => ({ x: pt.x, y: pt.y, color: getOwningColor(ownership, pt) }))
.forEach(({ x, y, color }) => {
ownership[y][x] = color;
});
points = unvisited.filter(({ x, y }) => ownership[y][x] !== 0);
}

return ownership;
}

export function distanceMap(board: number[][], color: -1 | 1) {
function getOwningColor(board: number[][], pt: Coordinate): -1 | 0 | 1 {
const { width, height } = get_dims(board);
let points = getPoints(board, (pt) => pt === color);
if (points.length === 0) {
return makeMatrix(width, height, Infinity);
const neighbors = getNeighbors(width, height, pt);
const non_neutral_neighbors = neighbors.filter((pt) => board[pt.y][pt.x] !== 0);
if (non_neutral_neighbors.every((pt) => board[pt.y][pt.x] === 1)) {
return 1;
}

let i = 0;
const distance_map = makeEmptyObjectMatrix<number>(width, height);
while (points.length) {
const next_points: Coordinate[] = [];
for (const pt of points) {
if (distance_map[pt.y][pt.x] !== undefined) {
continue;
}
distance_map[pt.y][pt.x] = i;
for (const n of getNeighbors(width, height, pt)) {
next_points.push(n);
}
}
points = next_points;
i++;
if (non_neutral_neighbors.every((pt) => board[pt.y][pt.x] === -1)) {
return -1;
}
return distance_map;
return 0;
}

type Coordinate = { x: number; y: number };
Expand Down

0 comments on commit 30572d2

Please sign in to comment.