Skip to content

Commit 36a5e37

Browse files
committed
client side counting
1 parent f0799bb commit 36a5e37

File tree

13 files changed

+379
-20
lines changed

13 files changed

+379
-20
lines changed

src/main/client/src/Game.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const Game = () => {
4545
let auth = useAuthStore(state => state.auth)
4646
let setGameState = useGameStore(state => state.setGameState)
4747
let queueStatus = useGameStore(state => state.queueStatus)
48+
let queueLength = useGameStore(state => state.queueLength)
4849
let addMove = useGameStore(state => state.addMove)
4950
let currentPlayer = useGameStore(state => state.currentPlayer)
5051
let { board, currentColor, counting, forbidden } = useGameStore(state => state.gameState)
@@ -157,11 +158,12 @@ export const Game = () => {
157158
destination: "/app/game/move",
158159
body: JSON.stringify({
159160
id: gameId,
161+
n: queueLength(),
160162
x: cursor_x,
161163
y: cursor_y,
162164
}),
163165
})
164-
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y])
166+
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y, queueLength])
165167

166168
useEffect(() => {
167169
if (!board.length) {

src/main/client/src/feature/GamePanel.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function Panel({zoom, setZoom}) {
4848
let auth = useAuthStore(state => state.auth)
4949
let black = useGameStore(state => state.black)
5050
let white = useGameStore(state => state.white)
51+
let queueLength = useGameStore(state => state.queueLength)
5152
let currentPlayer = useGameStore(state => state.currentPlayer)
5253
let { board, counting } = useGameStore(state => state.gameState)
5354
let navigate = useNavigate()
@@ -59,19 +60,21 @@ function Panel({zoom, setZoom}) {
5960
destination: "/app/game/move",
6061
body: JSON.stringify({
6162
id: gameId,
63+
n: queueLength(),
6264
pass: true,
6365
}),
6466
})
65-
}, [stompClient, gameId])
67+
}, [stompClient, gameId, queueLength])
6668
let onResetCounting = useCallback(() => {
6769
stompClient.publish({
6870
destination: "/app/game/move",
6971
body: JSON.stringify({
7072
id: gameId,
73+
n: queueLength(),
7174
resetCounting: true,
7275
}),
7376
})
74-
}, [stompClient, gameId])
77+
}, [stompClient, gameId, queueLength])
7578
if (!board.length) {
7679
return <span>Loading...</span>
7780
}

src/main/client/src/model/count.js

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import {
2+
PointQueue,
3+
} from "./PointQueue.js"
4+
import {
5+
PointList,
6+
} from "./PointList.js"
7+
import {
8+
PointSet,
9+
} from "./PointSet.js"
10+
import {
11+
hasStone,
12+
BLACK,
13+
WHITE,
14+
COLORS,
15+
TERRITORY_B,
16+
TERRITORY_W,
17+
TERRITORY,
18+
REMOVED_B,
19+
REMOVED_W,
20+
TOGGLE_B,
21+
TOGGLE_W,
22+
TOGGLE_STUFF,
23+
} from "../util.js"
24+
25+
export function count(board) {
26+
let acc = createAcc(board.length)
27+
for (let y = 0; y < board.length; y++) {
28+
let row = board[y]
29+
for (let x = 0; x < row.length; x++) {
30+
if (acc[y][x] === -1) {
31+
colorEmptyTerritory(board, acc, x, y)
32+
}
33+
}
34+
}
35+
return acc
36+
}
37+
38+
function updateToggleStonesAt(board, x, y, color, pointsChecked, pointsToCheck) {
39+
let dim = board.length
40+
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) {
41+
return
42+
}
43+
let c = board[y][x]
44+
if (c !== color) {
45+
return
46+
}
47+
if (pointsChecked.has(x, y)) {
48+
return
49+
}
50+
pointsChecked.add(x, y)
51+
pointsToCheck.offer(x, y)
52+
}
53+
54+
export function toggleStonesAt(board, xx, yy) {
55+
let color = board[yy][xx]
56+
if (!(color & TOGGLE_STUFF)) {
57+
return board
58+
}
59+
let dim = board.length
60+
let result = new PointList(dim)
61+
let pointsToCheck = new PointQueue(dim)
62+
let pointsChecked = new PointSet(dim)
63+
pointsChecked.add(xx, yy)
64+
pointsToCheck.offer(xx, yy)
65+
while (!pointsToCheck.isEmpty()) {
66+
let ptId = pointsToCheck.poll()
67+
let y = Math.trunc(ptId / dim)
68+
let x = ptId % dim
69+
result.add(x, y)
70+
updateToggleStonesAt(board, x, y - 1, color, pointsChecked, pointsToCheck)
71+
updateToggleStonesAt(board, x, y + 1, color, pointsChecked, pointsToCheck)
72+
updateToggleStonesAt(board, x - 1, y, color, pointsChecked, pointsToCheck)
73+
updateToggleStonesAt(board, x + 1, y, color, pointsChecked, pointsToCheck)
74+
}
75+
let updated = board.slice()
76+
result.forEach((x, y) => {
77+
if (updated[y] === board[y]) {
78+
updated[y] = board[y].slice()
79+
}
80+
updated[y][x] = toggleRemoved(updated[y][x])
81+
})
82+
return updated
83+
}
84+
85+
export function resetCounting(board) {
86+
let dim = board.length
87+
let updated = board.slice()
88+
for (let y = 0; y < dim; y++) {
89+
updated[y] = board[y].slice()
90+
for (let x = 0; x < dim; x++) {
91+
updated[y][x] = resurrect(board[y][x]) & COLORS
92+
}
93+
}
94+
return updated
95+
}
96+
97+
function updateFindColor(dim, x, y, pointsChecked, pointsToCheck) {
98+
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) {
99+
return
100+
}
101+
if (pointsChecked.has(x, y)) {
102+
return
103+
}
104+
pointsChecked.add(x, y)
105+
pointsToCheck.offer(x, y)
106+
}
107+
108+
function findColor(board, xx, yy) {
109+
if (hasStone(board[yy][xx])) {
110+
return board[yy][xx]
111+
}
112+
let dim = board.length
113+
let pointsChecked = new PointSet(dim)
114+
let color = -1
115+
pointsChecked.add(xx, yy)
116+
let pointsToCheck = new PointQueue(dim)
117+
pointsToCheck.offer(xx, yy)
118+
while (!pointsToCheck.isEmpty()) {
119+
let ptId = pointsToCheck.poll()
120+
let y = Math.trunc(ptId / dim)
121+
let x = ptId % dim
122+
if (hasStone(board[y][x])) {
123+
if (color === -1 || color === board[y][x]) {
124+
color = board[y][x]
125+
} else {
126+
return 0 // disputed area
127+
}
128+
}
129+
updateFindColor(dim, x, y - 1, pointsChecked, pointsToCheck)
130+
updateFindColor(dim, x, y + 1, pointsChecked, pointsToCheck)
131+
updateFindColor(dim, x - 1, y, pointsChecked, pointsToCheck)
132+
updateFindColor(dim, x + 1, y, pointsChecked, pointsToCheck)
133+
}
134+
return color
135+
}
136+
137+
function updateColorEmptyTerritory(board, x, y, found, acc, pointsToCheck) {
138+
let dim = board.length
139+
if (Math.min(x, y) < 0 || Math.max(x, y) >= dim) {
140+
return
141+
}
142+
if (acc[y][x] !== -1) {
143+
return
144+
}
145+
let color = board[y][x]
146+
if (!isEmpty(color)) {
147+
return
148+
}
149+
acc[y][x] = !found ? 0 : getTerritoryMarker(found, color)
150+
pointsToCheck.offer(x, y)
151+
}
152+
153+
function colorEmptyTerritory(board, acc, xx, yy) {
154+
if (hasStone(board[yy][xx])) {
155+
acc[yy][xx] = board[yy][xx]
156+
return
157+
}
158+
let found = findColor(board, xx, yy)
159+
if (found === -1) { // empty board
160+
for (let row of acc) {
161+
row.fill(0)
162+
}
163+
return
164+
}
165+
let dim = board.length
166+
let pointsToCheck = new PointQueue(dim)
167+
acc[yy][xx] = getTerritoryMarker(found, board[yy][xx])
168+
pointsToCheck.offer(xx, yy)
169+
while (!pointsToCheck.isEmpty()) {
170+
let ptId = pointsToCheck.poll()
171+
let y = Math.trunc(ptId / dim)
172+
let x = ptId % dim
173+
updateColorEmptyTerritory(board, x, y - 1, found, acc, pointsToCheck)
174+
updateColorEmptyTerritory(board, x, y + 1, found, acc, pointsToCheck)
175+
updateColorEmptyTerritory(board, x - 1, y, found, acc, pointsToCheck)
176+
updateColorEmptyTerritory(board, x + 1, y, found, acc, pointsToCheck)
177+
}
178+
}
179+
180+
function getTerritoryMarker(found, empty) {
181+
if ((empty & asRemoved(found)) !== 0) {
182+
return found // resurrect
183+
}
184+
return asTerritory(found) | (empty & ~TERRITORY)
185+
}
186+
187+
function asTerritory(color) {
188+
if (color === BLACK) {
189+
return TERRITORY_B
190+
}
191+
if (color === WHITE) {
192+
return TERRITORY_W
193+
}
194+
return color
195+
}
196+
197+
function asRemoved(color) {
198+
if (color === BLACK) {
199+
return REMOVED_B
200+
}
201+
if (color === WHITE) {
202+
return REMOVED_W
203+
}
204+
return color
205+
}
206+
207+
function toggleRemoved(color) {
208+
if (color & TOGGLE_B) {
209+
return color ^ TOGGLE_B
210+
}
211+
if (color & TOGGLE_W) {
212+
return color ^ TOGGLE_W
213+
}
214+
return color
215+
}
216+
217+
function resurrect(color) {
218+
if (color & REMOVED_B) {
219+
return color ^ TOGGLE_B
220+
}
221+
if (color & REMOVED_W) {
222+
return color ^ TOGGLE_W
223+
}
224+
return color
225+
}
226+
227+
function createAcc(dim) {
228+
let result = Array(dim)
229+
for (let y = 0; y < dim; y++) {
230+
result[y] = new Int32Array(dim).fill(-1)
231+
}
232+
return result
233+
}
234+
235+
function isEmpty(color) {
236+
return (color & COLORS) === 0
237+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {
2+
expect,
3+
test,
4+
} from "vitest"
5+
import {
6+
count,
7+
toggleStonesAt,
8+
resetCounting,
9+
} from "./count.js"
10+
import {
11+
BLACK,
12+
WHITE,
13+
TERRITORY_B,
14+
TERRITORY_W,
15+
REMOVED_W,
16+
REMOVED_B,
17+
} from "../util.js"
18+
19+
test("territoryChangeOwner", () => {
20+
let B = BLACK
21+
let t = TERRITORY_W
22+
let r = TERRITORY_W | REMOVED_B
23+
let v = TERRITORY_B
24+
let k = REMOVED_W
25+
let s = REMOVED_W | TERRITORY_B
26+
let position = [
27+
[t, r, k, 0, 0],
28+
[r, r, k, 0, 0],
29+
[k, k, k, B, 0],
30+
[0, 0, 0, 0, 0],
31+
[0, 0, 0, 0, 0],
32+
]
33+
let counted = count(position)
34+
expect(counted).toEqual(mapInt([
35+
[v, B, s, v, v],
36+
[B, B, s, v, v],
37+
[s, s, s, B, v],
38+
[v, v, v, v, v],
39+
[v, v, v, v, v],
40+
]))
41+
})
42+
43+
test("toggle", () => {
44+
let B = BLACK
45+
let W = WHITE
46+
let t = TERRITORY_W
47+
let r = TERRITORY_W | REMOVED_B
48+
let k = REMOVED_W
49+
let position = [
50+
[t, r, W, 0, 0],
51+
[r, r, W, 0, 0],
52+
[W, W, W, B, 0],
53+
[0, 0, 0, 0, 0],
54+
[0, 0, 0, 0, 0],
55+
]
56+
let toggled = toggleStonesAt(position, 0, 2)
57+
expect(toggled).toEqual([
58+
[t, r, k, 0, 0],
59+
[r, r, k, 0, 0],
60+
[k, k, k, B, 0],
61+
[0, 0, 0, 0, 0],
62+
[0, 0, 0, 0, 0],
63+
])
64+
})
65+
66+
test("reset", () => {
67+
let B = BLACK
68+
let W = WHITE
69+
let f = REMOVED_W | TERRITORY_B
70+
let t = TERRITORY_B
71+
let position = [
72+
[t, B, f, t, t],
73+
[B, B, f, t, t],
74+
[f, f, f, t, t],
75+
[t, t, t, t, t],
76+
[t, t, t, t, t],
77+
]
78+
let result = resetCounting(position)
79+
expect(result).toEqual([
80+
[0, B, W, 0, 0],
81+
[B, B, W, 0, 0],
82+
[W, W, W, 0, 0],
83+
[0, 0, 0, 0, 0],
84+
[0, 0, 0, 0, 0],
85+
])
86+
})
87+
88+
function mapInt(ar) {
89+
let result = Array(ar.length)
90+
for (let y = 0; y < ar.length; y++) {
91+
result[y] = new Int32Array(ar[y])
92+
}
93+
return result
94+
}

0 commit comments

Comments
 (0)