-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f0799bb
commit 36a5e37
Showing
13 changed files
with
379 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import { | ||
PointQueue, | ||
} from "./PointQueue.js" | ||
import { | ||
PointList, | ||
} from "./PointList.js" | ||
import { | ||
PointSet, | ||
} from "./PointSet.js" | ||
import { | ||
hasStone, | ||
BLACK, | ||
WHITE, | ||
COLORS, | ||
TERRITORY_B, | ||
TERRITORY_W, | ||
TERRITORY, | ||
REMOVED_B, | ||
REMOVED_W, | ||
TOGGLE_B, | ||
TOGGLE_W, | ||
TOGGLE_STUFF, | ||
} from "../util.js" | ||
|
||
export function count(board) { | ||
let acc = createAcc(board.length) | ||
for (let y = 0; y < board.length; y++) { | ||
let row = board[y] | ||
for (let x = 0; x < row.length; x++) { | ||
if (acc[y][x] === -1) { | ||
colorEmptyTerritory(board, acc, x, y) | ||
} | ||
} | ||
} | ||
return acc | ||
} | ||
|
||
function updateToggleStonesAt(board, x, y, color, pointsChecked, pointsToCheck) { | ||
let dim = board.length | ||
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) { | ||
return | ||
} | ||
let c = board[y][x] | ||
if (c !== color) { | ||
return | ||
} | ||
if (pointsChecked.has(x, y)) { | ||
return | ||
} | ||
pointsChecked.add(x, y) | ||
pointsToCheck.offer(x, y) | ||
} | ||
|
||
export function toggleStonesAt(board, xx, yy) { | ||
let color = board[yy][xx] | ||
if (!(color & TOGGLE_STUFF)) { | ||
return board | ||
} | ||
let dim = board.length | ||
let result = new PointList(dim) | ||
let pointsToCheck = new PointQueue(dim) | ||
let pointsChecked = new PointSet(dim) | ||
pointsChecked.add(xx, yy) | ||
pointsToCheck.offer(xx, yy) | ||
while (!pointsToCheck.isEmpty()) { | ||
let ptId = pointsToCheck.poll() | ||
let y = Math.trunc(ptId / dim) | ||
let x = ptId % dim | ||
result.add(x, y) | ||
updateToggleStonesAt(board, x, y - 1, color, pointsChecked, pointsToCheck) | ||
updateToggleStonesAt(board, x, y + 1, color, pointsChecked, pointsToCheck) | ||
updateToggleStonesAt(board, x - 1, y, color, pointsChecked, pointsToCheck) | ||
updateToggleStonesAt(board, x + 1, y, color, pointsChecked, pointsToCheck) | ||
} | ||
let updated = board.slice() | ||
result.forEach((x, y) => { | ||
if (updated[y] === board[y]) { | ||
updated[y] = board[y].slice() | ||
} | ||
updated[y][x] = toggleRemoved(updated[y][x]) | ||
}) | ||
return updated | ||
} | ||
|
||
export function resetCounting(board) { | ||
let dim = board.length | ||
let updated = board.slice() | ||
for (let y = 0; y < dim; y++) { | ||
updated[y] = board[y].slice() | ||
for (let x = 0; x < dim; x++) { | ||
updated[y][x] = resurrect(board[y][x]) & COLORS | ||
} | ||
} | ||
return updated | ||
} | ||
|
||
function updateFindColor(dim, x, y, pointsChecked, pointsToCheck) { | ||
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) { | ||
return | ||
} | ||
if (pointsChecked.has(x, y)) { | ||
return | ||
} | ||
pointsChecked.add(x, y) | ||
pointsToCheck.offer(x, y) | ||
} | ||
|
||
function findColor(board, xx, yy) { | ||
if (hasStone(board[yy][xx])) { | ||
return board[yy][xx] | ||
} | ||
let dim = board.length | ||
let pointsChecked = new PointSet(dim) | ||
let color = -1 | ||
pointsChecked.add(xx, yy) | ||
let pointsToCheck = new PointQueue(dim) | ||
pointsToCheck.offer(xx, yy) | ||
while (!pointsToCheck.isEmpty()) { | ||
let ptId = pointsToCheck.poll() | ||
let y = Math.trunc(ptId / dim) | ||
let x = ptId % dim | ||
if (hasStone(board[y][x])) { | ||
if (color === -1 || color === board[y][x]) { | ||
color = board[y][x] | ||
} else { | ||
return 0 // disputed area | ||
} | ||
} | ||
updateFindColor(dim, x, y - 1, pointsChecked, pointsToCheck) | ||
updateFindColor(dim, x, y + 1, pointsChecked, pointsToCheck) | ||
updateFindColor(dim, x - 1, y, pointsChecked, pointsToCheck) | ||
updateFindColor(dim, x + 1, y, pointsChecked, pointsToCheck) | ||
} | ||
return color | ||
} | ||
|
||
function updateColorEmptyTerritory(board, x, y, found, acc, pointsToCheck) { | ||
let dim = board.length | ||
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) { | ||
return | ||
} | ||
if (acc[y][x] !== -1) { | ||
return | ||
} | ||
let color = board[y][x] | ||
if (!isEmpty(color)) { | ||
return | ||
} | ||
acc[y][x] = !found ? 0 : getTerritoryMarker(found, color) | ||
pointsToCheck.offer(x, y) | ||
} | ||
|
||
function colorEmptyTerritory(board, acc, xx, yy) { | ||
if (hasStone(board[yy][xx])) { | ||
acc[yy][xx] = board[yy][xx] | ||
return | ||
} | ||
let found = findColor(board, xx, yy) | ||
if (found === -1) { // empty board | ||
for (let row of acc) { | ||
row.fill(0) | ||
} | ||
return | ||
} | ||
let dim = board.length | ||
let pointsToCheck = new PointQueue(dim) | ||
acc[yy][xx] = getTerritoryMarker(found, board[yy][xx]) | ||
pointsToCheck.offer(xx, yy) | ||
while (!pointsToCheck.isEmpty()) { | ||
let ptId = pointsToCheck.poll() | ||
let y = Math.trunc(ptId / dim) | ||
let x = ptId % dim | ||
updateColorEmptyTerritory(board, x, y - 1, found, acc, pointsToCheck) | ||
updateColorEmptyTerritory(board, x, y + 1, found, acc, pointsToCheck) | ||
updateColorEmptyTerritory(board, x - 1, y, found, acc, pointsToCheck) | ||
updateColorEmptyTerritory(board, x + 1, y, found, acc, pointsToCheck) | ||
} | ||
} | ||
|
||
function getTerritoryMarker(found, empty) { | ||
if ((empty & asRemoved(found)) !== 0) { | ||
return found // resurrect | ||
} | ||
return asTerritory(found) | (empty & ~TERRITORY) | ||
} | ||
|
||
function asTerritory(color) { | ||
if (color === BLACK) { | ||
return TERRITORY_B | ||
} | ||
if (color === WHITE) { | ||
return TERRITORY_W | ||
} | ||
return color | ||
} | ||
|
||
function asRemoved(color) { | ||
if (color === BLACK) { | ||
return REMOVED_B | ||
} | ||
if (color === WHITE) { | ||
return REMOVED_W | ||
} | ||
return color | ||
} | ||
|
||
function toggleRemoved(color) { | ||
if (color & TOGGLE_B) { | ||
return color ^ TOGGLE_B | ||
} | ||
if (color & TOGGLE_W) { | ||
return color ^ TOGGLE_W | ||
} | ||
return color | ||
} | ||
|
||
function resurrect(color) { | ||
if (color & REMOVED_B) { | ||
return color ^ TOGGLE_B | ||
} | ||
if (color & REMOVED_W) { | ||
return color ^ TOGGLE_W | ||
} | ||
return color | ||
} | ||
|
||
function createAcc(dim) { | ||
let result = Array(dim) | ||
for (let y = 0; y < dim; y++) { | ||
result[y] = new Int32Array(dim).fill(-1) | ||
} | ||
return result | ||
} | ||
|
||
function isEmpty(color) { | ||
return (color & COLORS) === 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { | ||
expect, | ||
test, | ||
} from "vitest" | ||
import { | ||
count, | ||
toggleStonesAt, | ||
resetCounting, | ||
} from "./count.js" | ||
import { | ||
BLACK, | ||
WHITE, | ||
TERRITORY_B, | ||
TERRITORY_W, | ||
REMOVED_W, | ||
REMOVED_B, | ||
} from "../util.js" | ||
|
||
test("territoryChangeOwner", () => { | ||
let B = BLACK | ||
let t = TERRITORY_W | ||
let r = TERRITORY_W | REMOVED_B | ||
let v = TERRITORY_B | ||
let k = REMOVED_W | ||
let s = REMOVED_W | TERRITORY_B | ||
let position = [ | ||
[t, r, k, 0, 0], | ||
[r, r, k, 0, 0], | ||
[k, k, k, B, 0], | ||
[0, 0, 0, 0, 0], | ||
[0, 0, 0, 0, 0], | ||
] | ||
let counted = count(position) | ||
expect(counted).toEqual(mapInt([ | ||
[v, B, s, v, v], | ||
[B, B, s, v, v], | ||
[s, s, s, B, v], | ||
[v, v, v, v, v], | ||
[v, v, v, v, v], | ||
])) | ||
}) | ||
|
||
test("toggle", () => { | ||
let B = BLACK | ||
let W = WHITE | ||
let t = TERRITORY_W | ||
let r = TERRITORY_W | REMOVED_B | ||
let k = REMOVED_W | ||
let position = [ | ||
[t, r, W, 0, 0], | ||
[r, r, W, 0, 0], | ||
[W, W, W, B, 0], | ||
[0, 0, 0, 0, 0], | ||
[0, 0, 0, 0, 0], | ||
] | ||
let toggled = toggleStonesAt(position, 0, 2) | ||
expect(toggled).toEqual([ | ||
[t, r, k, 0, 0], | ||
[r, r, k, 0, 0], | ||
[k, k, k, B, 0], | ||
[0, 0, 0, 0, 0], | ||
[0, 0, 0, 0, 0], | ||
]) | ||
}) | ||
|
||
test("reset", () => { | ||
let B = BLACK | ||
let W = WHITE | ||
let f = REMOVED_W | TERRITORY_B | ||
let t = TERRITORY_B | ||
let position = [ | ||
[t, B, f, t, t], | ||
[B, B, f, t, t], | ||
[f, f, f, t, t], | ||
[t, t, t, t, t], | ||
[t, t, t, t, t], | ||
] | ||
let result = resetCounting(position) | ||
expect(result).toEqual([ | ||
[0, B, W, 0, 0], | ||
[B, B, W, 0, 0], | ||
[W, W, W, 0, 0], | ||
[0, 0, 0, 0, 0], | ||
[0, 0, 0, 0, 0], | ||
]) | ||
}) | ||
|
||
function mapInt(ar) { | ||
let result = Array(ar.length) | ||
for (let y = 0; y < ar.length; y++) { | ||
result[y] = new Int32Array(ar[y]) | ||
} | ||
return result | ||
} |
Oops, something went wrong.