Skip to content

Commit dec6656

Browse files
committed
incremental updates v1
todo: client side ko rule
1 parent 9225bd6 commit dec6656

File tree

10 files changed

+203
-32
lines changed

10 files changed

+203
-32
lines changed

src/main/client/src/Game.jsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ export const Game = () => {
4747
let stompClient = useContext(StompContext)
4848
let auth = useAuthStore(state => state.auth)
4949
let setGameState = useGameStore(state => state.setGameState)
50+
let queueStatus = useGameStore(state => state.queueStatus)
51+
let addMove = useGameStore(state => state.addMove)
5052
let { board, currentColor, currentPlayer, counting, forbidden } = useGameStore(state => state.gameState)
5153
let [forbidden_x, forbidden_y] = forbidden
5254
let initialized = useRef()
5355
let canvasRef = useRef()
56+
let countingGroup = counting ? getCountingGroup(board, cursor_x, cursor_y) : undefined
57+
5458
let context = useMemo(() => {
5559
let dim = board.length
5660
if (!dim) {
@@ -111,6 +115,7 @@ export const Game = () => {
111115
lastStoneYref: lastStoneYref,
112116
}
113117
}, [board.length, canvasRef, zoom])
118+
114119
let onMouseMove = useCallback((e) => {
115120
if (!board.length) {
116121
return
@@ -123,7 +128,7 @@ export const Game = () => {
123128
setCursor_x(cursor_x + 0)
124129
setCursor_y(cursor_y + 0)
125130
}, [context, currentPlayer, auth, board.length, counting])
126-
let countingGroup = counting ? getCountingGroup(board, cursor_x, cursor_y) : undefined
131+
127132
let onClick = useCallback((e) => {
128133
if (!board.length) {
129134
return
@@ -159,6 +164,7 @@ export const Game = () => {
159164
}),
160165
})
161166
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y])
167+
162168
useEffect(() => {
163169
if (!board.length) {
164170
return
@@ -187,19 +193,11 @@ export const Game = () => {
187193
"rgba(255,255,255,0.25)"
188194
showShadow(context, cursor_x, cursor_y, style)
189195
}, [cursor_x, cursor_y, context, canvasRef, auth, currentColor, board, currentPlayer, counting, countingGroup, forbidden_x, forbidden_y])
196+
190197
useEffect(() => {
191-
if (initialized.current) {
198+
if (queueStatus === "up_to_date") {
192199
return
193200
}
194-
initialized.current = true
195-
let sub1 = stompClient.subscribe("/topic/game/" + gameId, (message) => {
196-
let game = JSON.parse(message.body)
197-
setGameState(game)
198-
})
199-
let sub2 = stompClient.subscribe("/topic/move/" + gameId, (message) => {
200-
let move = JSON.parse(message.body)
201-
console.log(move) // TODO
202-
})
203201
doTry(async () => {
204202
let game = await tfetch("/api/game/" + gameId, {
205203
headers: {
@@ -208,14 +206,24 @@ export const Game = () => {
208206
})
209207
setGameState(game)
210208
})
211-
return () => {
212-
sub1.unsubscribe()
213-
sub2.unsubscribe()
209+
}, [setGameState, queueStatus, auth, gameId])
210+
211+
useEffect(() => {
212+
if (initialized.current) {
213+
return
214214
}
215-
}, [setGameState, initialized, stompClient, gameId, auth])
215+
initialized.current = true
216+
let sub = stompClient.subscribe("/topic/move/" + gameId, (message) => {
217+
let move = JSON.parse(message.body)
218+
addMove(move)
219+
})
220+
return sub.unsubscribe
221+
}, [setGameState, addMove, initialized, stompClient, gameId, auth])
222+
216223
if (!board.length) {
217224
return <div>Loading...</div>
218225
}
226+
219227
return (
220228
<div className="grid justify-center mt-8">
221229
<canvas ref={canvasRef}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function Panel({zoom, setZoom}) {
4646
let { gameId } = useParams()
4747
let stompClient = useContext(StompContext)
4848
let auth = useAuthStore(state => state.auth)
49-
let { black, white} = useGameStore(state => state)
49+
let black = useGameStore(state => state.black)
50+
let white = useGameStore(state => state.white)
5051
let { board, currentPlayer, counting } = useGameStore(state => state.gameState)
5152
let navigate = useNavigate()
5253
let onExit = useCallback(() => {

src/main/client/src/model/board.js

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from "./PointSet.js"
1010
import {
1111
hasStone,
12+
BLACK,
13+
WHITE,
1214
} from "../util.js"
1315

1416
export function getGroup(board, xx, yy) {
@@ -117,7 +119,7 @@ export function isForbidden(board, groupInfo, currentColor) {
117119
return true
118120
}
119121
if (y > 0) {
120-
let { color, liberties, hasStone } = board[y - 1][x]
122+
let {color, liberties, hasStone} = board[y - 1][x]
121123
if (!hasStone) {
122124
return false
123125
}
@@ -129,7 +131,7 @@ export function isForbidden(board, groupInfo, currentColor) {
129131
}
130132
}
131133
if (y < dim - 1) {
132-
let { color, liberties, hasStone } = board[y + 1][x]
134+
let {color, liberties, hasStone} = board[y + 1][x]
133135
if (!hasStone) {
134136
return false
135137
}
@@ -141,7 +143,7 @@ export function isForbidden(board, groupInfo, currentColor) {
141143
}
142144
}
143145
if (x > 0) {
144-
let { color, liberties, hasStone } = board[y][x - 1]
146+
let {color, liberties, hasStone} = board[y][x - 1]
145147
if (!hasStone) {
146148
return false
147149
}
@@ -153,7 +155,7 @@ export function isForbidden(board, groupInfo, currentColor) {
153155
}
154156
}
155157
if (x < dim - 1) {
156-
let { color, liberties, hasStone } = board[y][x + 1]
158+
let {color, liberties, hasStone} = board[y][x + 1]
157159
if (!hasStone) {
158160
return false
159161
}
@@ -166,3 +168,101 @@ export function isForbidden(board, groupInfo, currentColor) {
166168
}
167169
return true
168170
}
171+
172+
export function updateBoard(board, move) {
173+
let {pass, x, y, color} = move
174+
if (pass) {
175+
return board
176+
}
177+
board = applyMove(board, move)
178+
let oppositeColor = color ^ (WHITE | BLACK)
179+
board = removeDeadGroup(board, x, y - 1, oppositeColor)
180+
board = removeDeadGroup(board, x, y + 1, oppositeColor)
181+
board = removeDeadGroup(board, x - 1, y, oppositeColor)
182+
board = removeDeadGroup(board, x + 1, y, oppositeColor)
183+
return board
184+
}
185+
186+
function removeDeadGroup(board, xx, yy, color) {
187+
let dim = board.length
188+
if (Math.min(xx, yy) < 0 || Math.max(xx, yy) >= dim) {
189+
return board
190+
}
191+
if (board[yy][xx] !== color) {
192+
return board
193+
}
194+
if (yy > 0 && board[yy - 1][xx] == 0) {
195+
return board
196+
}
197+
if (yy < dim - 1 && board[yy + 1][xx] == 0) {
198+
return board
199+
}
200+
if (xx > 0 && board[yy][xx - 1] == 0) {
201+
return board
202+
}
203+
if (xx < dim - 1 && board[yy][xx + 1] == 0) {
204+
return board
205+
}
206+
let acc = new PointList(dim)
207+
let pointsChecked = new PointSet(dim)
208+
pointsChecked.add(xx, yy)
209+
let pointsToCheck = new PointQueue(dim)
210+
pointsToCheck.offer(xx, yy)
211+
while (!pointsToCheck.isEmpty()) {
212+
let ptId = pointsToCheck.poll()
213+
let y = Math.trunc(ptId / dim)
214+
let x = ptId % dim
215+
acc.add(x, y)
216+
if (y > 0) {
217+
let bpt = board[y - 1][x]
218+
if (bpt === 0) {
219+
return board
220+
} else if (bpt === color && !pointsChecked.has(x, y - 1)) {
221+
pointsChecked.add(x, y - 1)
222+
pointsToCheck.offer(x, y - 1)
223+
}
224+
}
225+
if (y < dim - 1) {
226+
let bpt = board[y + 1][x]
227+
if (bpt === 0) {
228+
return board
229+
} else if (bpt === color && !pointsChecked.has(x, y + 1)) {
230+
pointsChecked.add(x, y + 1)
231+
pointsToCheck.offer(x, y + 1)
232+
}
233+
}
234+
if (x > 0) {
235+
let bpt = board[y][x - 1]
236+
if (bpt === 0) {
237+
return board
238+
} else if (bpt === color && !pointsChecked.has(x - 1, y)) {
239+
pointsChecked.add(x - 1, y)
240+
pointsToCheck.offer(x - 1, y)
241+
}
242+
}
243+
if (x < dim - 1) {
244+
let bpt = board[y][x + 1]
245+
if (bpt === 0) {
246+
return board
247+
} else if (bpt === color && !pointsChecked.has(x + 1, y)) {
248+
pointsChecked.add(x + 1, y)
249+
pointsToCheck.offer(x + 1, y)
250+
}
251+
}
252+
}
253+
let result = board.slice()
254+
acc.forEach((x, y) => {
255+
if (result[y] === board[y]) {
256+
result[y] = board[y].slice()
257+
}
258+
result[y][x] = 0
259+
})
260+
return result
261+
}
262+
263+
function applyMove(board, {color, x, y}) {
264+
let result = board.slice()
265+
result[y] = board[y].slice()
266+
result[y][x] = color
267+
return result
268+
}

src/main/client/src/store.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
} from "immer"
77
import {
88
BLACK,
9+
WHITE,
910
} from "./util.js"
1011
import {
1112
rehydrate,
13+
updateBoard,
1214
} from "./model/board.js"
1315

1416
export const useAuthStore = create((set) => ({
@@ -31,7 +33,10 @@ export const useAuthStore = create((set) => ({
3133
},
3234
}))
3335

34-
export const useGameStore = create((set) => ({
36+
export const useGameStore = create((set, get) => ({
37+
moves: [],
38+
baseBoard: [],
39+
queueStatus: "behind",
3540
editMode: false,
3641
black: {
3742
name: "",
@@ -52,16 +57,41 @@ export const useGameStore = create((set) => ({
5257
counting: false,
5358
forbidden: [-1, -1],
5459
},
60+
addMove: (move) => {
61+
set(produce(state => {
62+
if (get().moves.length < move.n) {
63+
state.queueStatus = "behind"
64+
return
65+
}
66+
state.queueStatus = "up_to_date"
67+
state.moves.push(move)
68+
if (move.counting) {
69+
state.gameState.counting = true
70+
state.baseBoard = move.board
71+
state.gameState.board = rehydrate(move.board)
72+
return
73+
}
74+
let updated = updateBoard(get().baseBoard, move)
75+
state.baseBoard = updated
76+
state.gameState.board = rehydrate(updated)
77+
state.gameState.currentColor = get().gameState.currentColor ^ (BLACK | WHITE)
78+
state.gameState.currentPlayer = get().gameState.currentPlayer === get().black.name ? get().white.name : get().black.name
79+
state.gameState.forbidden = move.forbidden
80+
}))
81+
},
5582
setGameState: (game) => {
5683
set(produce(state => {
5784
state.black = game.black
5885
state.white = game.white
5986
state.editMode = game.editMode
87+
state.baseBoard = game.board
88+
state.moves = game.moves
6089
state.gameState.board = rehydrate(game.board)
6190
state.gameState.currentPlayer = game.currentPlayer
6291
state.gameState.currentColor = game.currentColor
6392
state.gameState.counting = game.counting
6493
state.gameState.forbidden = game.forbidden
94+
state.queueStatue = "up_to_date"
6595
}))
6696
},
6797
}))

src/main/java/com/bernd/GameController.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import com.bernd.model.AcceptRequest;
44
import com.bernd.model.ActiveGame;
5+
import com.bernd.model.CountingMove;
56
import com.bernd.model.Game;
67
import com.bernd.model.Move;
78
import com.bernd.model.OpenGame;
89
import com.bernd.model.ViewGame;
910
import com.bernd.util.Auth;
1011
import com.bernd.util.RandomString;
11-
import java.security.Principal;
1212
import org.springframework.http.ResponseEntity;
1313
import org.springframework.messaging.core.MessageSendingOperations;
1414
import org.springframework.messaging.handler.annotation.MessageMapping;
@@ -19,6 +19,8 @@
1919
import org.springframework.web.bind.annotation.RequestBody;
2020
import org.springframework.web.bind.annotation.ResponseBody;
2121

22+
import java.security.Principal;
23+
2224
@Controller
2325
public class GameController {
2426

@@ -61,8 +63,11 @@ public void action(Move move, Principal principal) {
6163
}
6264
Game updated = game.update(move);
6365
games.put(updated);
64-
operations.convertAndSend("/topic/move/" + game.id(), move.toView(color, moveNumber));
65-
operations.convertAndSend("/topic/game/" + game.id(), updated.toView());
66+
if (updated.counting()) {
67+
operations.convertAndSend("/topic/move/" + game.id(), CountingMove.create(color, moveNumber, updated.board()));
68+
} else {
69+
operations.convertAndSend("/topic/move/" + game.id(), move.toView(color, moveNumber, updated.forbidden()));
70+
}
6671
}
6772

6873
@ResponseBody

src/main/java/com/bernd/game/MoveList.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.bernd.model.GameMove;
44
import com.bernd.model.Move;
5+
56
import java.util.ArrayList;
67
import java.util.Arrays;
78
import java.util.List;
@@ -24,7 +25,7 @@ public final class MoveList {
2425
private MoveList(
2526
int dim,
2627
int[] buffer) {
27-
this.capacity = dim * dim;
28+
this.capacity = 2 * buffer.length;
2829
this.dim = dim;
2930
this.buffer = buffer;
3031
}
@@ -39,7 +40,8 @@ public static MoveList create(int dim) {
3940

4041
public void add(int color, Move move) {
4142
if (pos >= capacity) {
42-
int newCapacity = 2 * capacity;
43+
int boardSize = dim * dim;
44+
int newCapacity = capacity < boardSize ? boardSize : capacity + boardSize;
4345
buffer = Arrays.copyOf(buffer, divUp(newCapacity, 2));
4446
capacity = newCapacity;
4547
}
@@ -61,12 +63,12 @@ public GameMove get(int i) {
6163
int ptId = i % 2 == 0 ? code & LO : (code >> 16);
6264
int color = (ptId & WHITE) != 0 ? Board.W : Board.B;
6365
if ((ptId & PASS) != 0) {
64-
return new GameMove(i, color, true, -1, -1);
66+
return new GameMove(i, color, true, -1, -1, new int[]{-1, -1});
6567
} else {
6668
int data = ptId & DATA;
6769
int x = data % dim;
6870
int y = data / dim;
69-
return new GameMove(i, color, true, x, y);
71+
return new GameMove(i, color, true, x, y, new int[]{-1, -1});
7072
}
7173
}
7274

0 commit comments

Comments
 (0)