Skip to content

Commit

Permalink
client side counting
Browse files Browse the repository at this point in the history
  • Loading branch information
h908714124 committed Aug 5, 2024
1 parent f0799bb commit 36a5e37
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 20 deletions.
4 changes: 3 additions & 1 deletion src/main/client/src/Game.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const Game = () => {
let auth = useAuthStore(state => state.auth)
let setGameState = useGameStore(state => state.setGameState)
let queueStatus = useGameStore(state => state.queueStatus)
let queueLength = useGameStore(state => state.queueLength)
let addMove = useGameStore(state => state.addMove)
let currentPlayer = useGameStore(state => state.currentPlayer)
let { board, currentColor, counting, forbidden } = useGameStore(state => state.gameState)
Expand Down Expand Up @@ -157,11 +158,12 @@ export const Game = () => {
destination: "/app/game/move",
body: JSON.stringify({
id: gameId,
n: queueLength(),
x: cursor_x,
y: cursor_y,
}),
})
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y])
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y, queueLength])

useEffect(() => {
if (!board.length) {
Expand Down
7 changes: 5 additions & 2 deletions src/main/client/src/feature/GamePanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ function Panel({zoom, setZoom}) {
let auth = useAuthStore(state => state.auth)
let black = useGameStore(state => state.black)
let white = useGameStore(state => state.white)
let queueLength = useGameStore(state => state.queueLength)
let currentPlayer = useGameStore(state => state.currentPlayer)
let { board, counting } = useGameStore(state => state.gameState)
let navigate = useNavigate()
Expand All @@ -59,19 +60,21 @@ function Panel({zoom, setZoom}) {
destination: "/app/game/move",
body: JSON.stringify({
id: gameId,
n: queueLength(),
pass: true,
}),
})
}, [stompClient, gameId])
}, [stompClient, gameId, queueLength])
let onResetCounting = useCallback(() => {
stompClient.publish({
destination: "/app/game/move",
body: JSON.stringify({
id: gameId,
n: queueLength(),
resetCounting: true,
}),
})
}, [stompClient, gameId])
}, [stompClient, gameId, queueLength])
if (!board.length) {
return <span>Loading...</span>
}
Expand Down
237 changes: 237 additions & 0 deletions src/main/client/src/model/count.js
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
}
94 changes: 94 additions & 0 deletions src/main/client/src/model/count.test.js
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
}
Loading

0 comments on commit 36a5e37

Please sign in to comment.