Skip to content

Commit

Permalink
incremental updates v1
Browse files Browse the repository at this point in the history
todo: client side ko rule
  • Loading branch information
h908714124 committed Aug 3, 2024
1 parent 9225bd6 commit fc250d4
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 25 deletions.
34 changes: 26 additions & 8 deletions src/main/client/src/Game.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ export const Game = () => {
let stompClient = useContext(StompContext)
let auth = useAuthStore(state => state.auth)
let setGameState = useGameStore(state => state.setGameState)
let queueStatus = useGameStore(state => state.queueStatus)
let addMove = useGameStore(state => state.addMove)
let { board, currentColor, currentPlayer, counting, forbidden } = useGameStore(state => state.gameState)
let [forbidden_x, forbidden_y] = forbidden
let initialized = useRef()
let canvasRef = useRef()
let countingGroup = counting ? getCountingGroup(board, cursor_x, cursor_y) : undefined

let context = useMemo(() => {
let dim = board.length
if (!dim) {
Expand Down Expand Up @@ -111,6 +115,7 @@ export const Game = () => {
lastStoneYref: lastStoneYref,
}
}, [board.length, canvasRef, zoom])

let onMouseMove = useCallback((e) => {
if (!board.length) {
return
Expand All @@ -123,7 +128,7 @@ export const Game = () => {
setCursor_x(cursor_x + 0)
setCursor_y(cursor_y + 0)
}, [context, currentPlayer, auth, board.length, counting])
let countingGroup = counting ? getCountingGroup(board, cursor_x, cursor_y) : undefined

let onClick = useCallback((e) => {
if (!board.length) {
return
Expand Down Expand Up @@ -159,6 +164,7 @@ export const Game = () => {
}),
})
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting, forbidden_x, forbidden_y])

useEffect(() => {
if (!board.length) {
return
Expand Down Expand Up @@ -187,18 +193,29 @@ export const Game = () => {
"rgba(255,255,255,0.25)"
showShadow(context, cursor_x, cursor_y, style)
}, [cursor_x, cursor_y, context, canvasRef, auth, currentColor, board, currentPlayer, counting, countingGroup, forbidden_x, forbidden_y])

useEffect(() => {
if (initialized.current) {
if (queueStatus === "up_to_date") {
return
}
initialized.current = true
let sub1 = stompClient.subscribe("/topic/game/" + gameId, (message) => {
let game = JSON.parse(message.body)
doTry(async () => {
let game = await tfetch("/api/game/" + gameId, {
headers: {
"Authorization": "Bearer " + auth.token,
},
})
setGameState(game)
})
}, [setGameState, queueStatus, auth, gameId])

useEffect(() => {
if (initialized.current) {
return
}
initialized.current = true
let sub2 = stompClient.subscribe("/topic/move/" + gameId, (message) => {
let move = JSON.parse(message.body)
console.log(move) // TODO
addMove(move)
})
doTry(async () => {
let game = await tfetch("/api/game/" + gameId, {
Expand All @@ -209,13 +226,14 @@ export const Game = () => {
setGameState(game)
})
return () => {
sub1.unsubscribe()
sub2.unsubscribe()
}
}, [setGameState, initialized, stompClient, gameId, auth])
}, [setGameState, addMove, initialized, stompClient, gameId, auth])

if (!board.length) {
return <div>Loading...</div>
}

return (
<div className="grid justify-center mt-8">
<canvas ref={canvasRef}
Expand Down
3 changes: 2 additions & 1 deletion src/main/client/src/feature/GamePanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ function Panel({zoom, setZoom}) {
let { gameId } = useParams()
let stompClient = useContext(StompContext)
let auth = useAuthStore(state => state.auth)
let { black, white} = useGameStore(state => state)
let black = useGameStore(state => state.black)
let white = useGameStore(state => state.white)
let { board, currentPlayer, counting } = useGameStore(state => state.gameState)
let navigate = useNavigate()
let onExit = useCallback(() => {
Expand Down
108 changes: 104 additions & 4 deletions src/main/client/src/model/board.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from "./PointSet.js"
import {
hasStone,
BLACK,
WHITE,
} from "../util.js"

export function getGroup(board, xx, yy) {
Expand Down Expand Up @@ -117,7 +119,7 @@ export function isForbidden(board, groupInfo, currentColor) {
return true
}
if (y > 0) {
let { color, liberties, hasStone } = board[y - 1][x]
let {color, liberties, hasStone} = board[y - 1][x]
if (!hasStone) {
return false
}
Expand All @@ -129,7 +131,7 @@ export function isForbidden(board, groupInfo, currentColor) {
}
}
if (y < dim - 1) {
let { color, liberties, hasStone } = board[y + 1][x]
let {color, liberties, hasStone} = board[y + 1][x]
if (!hasStone) {
return false
}
Expand All @@ -141,7 +143,7 @@ export function isForbidden(board, groupInfo, currentColor) {
}
}
if (x > 0) {
let { color, liberties, hasStone } = board[y][x - 1]
let {color, liberties, hasStone} = board[y][x - 1]
if (!hasStone) {
return false
}
Expand All @@ -153,7 +155,7 @@ export function isForbidden(board, groupInfo, currentColor) {
}
}
if (x < dim - 1) {
let { color, liberties, hasStone } = board[y][x + 1]
let {color, liberties, hasStone} = board[y][x + 1]
if (!hasStone) {
return false
}
Expand All @@ -166,3 +168,101 @@ export function isForbidden(board, groupInfo, currentColor) {
}
return true
}

export function updateBoard(board, move) {
let {pass, x, y, color} = move
if (pass) {
return board
}
board = applyMove(board, move)
let oppositeColor = color ^ (WHITE | BLACK)
board = removeDeadGroup(board, x, y - 1, oppositeColor)
board = removeDeadGroup(board, x, y + 1, oppositeColor)
board = removeDeadGroup(board, x - 1, y, oppositeColor)
board = removeDeadGroup(board, x + 1, y, oppositeColor)
return board
}

function removeDeadGroup(board, xx, yy, color) {
let dim = board.length
if (Math.min(xx, yy) < 0 || Math.max(xx, yy) >= dim) {
return board
}
if (board[yy][xx] !== color) {
return board
}
if (yy > 0 && board[yy - 1][xx] == 0) {
return board
}
if (yy < dim - 1 && board[yy + 1][xx] == 0) {
return board
}
if (xx > 0 && board[yy][xx - 1] == 0) {
return board
}
if (xx < dim - 1 && board[yy][xx + 1] == 0) {
return board
}
let acc = new PointList(dim)
let pointsChecked = new PointSet(dim)
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
acc.add(x, y)
if (y > 0) {
let bpt = board[y - 1][x]
if (bpt === 0) {
return board
} else if (bpt === color && !pointsChecked.has(x, y - 1)) {
pointsChecked.add(x, y - 1)
pointsToCheck.offer(x, y - 1)
}
}
if (y < dim - 1) {
let bpt = board[y + 1][x]
if (bpt === 0) {
return board
} else if (bpt === color && !pointsChecked.has(x, y + 1)) {
pointsChecked.add(x, y + 1)
pointsToCheck.offer(x, y + 1)
}
}
if (x > 0) {
let bpt = board[y][x - 1]
if (bpt === 0) {
return board
} else if (bpt === color && !pointsChecked.has(x - 1, y)) {
pointsChecked.add(x - 1, y)
pointsToCheck.offer(x - 1, y)
}
}
if (x < dim - 1) {
let bpt = board[y][x + 1]
if (bpt === 0) {
return board
} else if (bpt === color && !pointsChecked.has(x + 1, y)) {
pointsChecked.add(x + 1, y)
pointsToCheck.offer(x + 1, y)
}
}
}
let result = board.slice()
acc.forEach((x, y) => {
if (result[y] === board[y]) {
result[y] = board[y].slice()
}
result[y][x] = 0
})
return result
}

function applyMove(board, {color, x, y}) {
let result = board.slice()
result[y] = board[y].slice()
result[y][x] = color
return result
}
32 changes: 31 additions & 1 deletion src/main/client/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
} from "immer"
import {
BLACK,
WHITE,
} from "./util.js"
import {
rehydrate,
updateBoard,
} from "./model/board.js"

export const useAuthStore = create((set) => ({
Expand All @@ -31,7 +33,10 @@ export const useAuthStore = create((set) => ({
},
}))

export const useGameStore = create((set) => ({
export const useGameStore = create((set, get) => ({
moves: [],
baseBoard: [],
queueStatus: "behind",
editMode: false,
black: {
name: "",
Expand All @@ -52,16 +57,41 @@ export const useGameStore = create((set) => ({
counting: false,
forbidden: [-1, -1],
},
addMove: (move) => {
set(produce(state => {
if (get().moves.length < move.n) {
state.queueStatus = "behind"
return
}
state.queueStatus = "up_to_date"
state.moves.push(move)
if (move.counting) {
state.gameState.counting = true
state.baseBoard = move.board
state.gameState.board = rehydrate(move.board)
return
}
let updated = updateBoard(get().baseBoard, move)
state.baseBoard = updated
state.gameState.board = rehydrate(updated)
state.gameState.currentColor = get().gameState.currentColor ^ (BLACK | WHITE)
state.gameState.currentPlayer = get().gameState.currentPlayer === get().black.name ? get().white.name : get().black.name
state.gameState.forbidden = move.forbidden
}))
},
setGameState: (game) => {
set(produce(state => {
state.black = game.black
state.white = game.white
state.editMode = game.editMode
state.baseBoard = game.board
state.moves = game.moves
state.gameState.board = rehydrate(game.board)
state.gameState.currentPlayer = game.currentPlayer
state.gameState.currentColor = game.currentColor
state.gameState.counting = game.counting
state.gameState.forbidden = game.forbidden
state.queueStatue = "up_to_date"
}))
},
}))
11 changes: 8 additions & 3 deletions src/main/java/com/bernd/GameController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import com.bernd.model.AcceptRequest;
import com.bernd.model.ActiveGame;
import com.bernd.model.CountingMove;
import com.bernd.model.Game;
import com.bernd.model.Move;
import com.bernd.model.OpenGame;
import com.bernd.model.ViewGame;
import com.bernd.util.Auth;
import com.bernd.util.RandomString;
import java.security.Principal;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.messaging.handler.annotation.MessageMapping;
Expand All @@ -19,6 +19,8 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.Principal;

@Controller
public class GameController {

Expand Down Expand Up @@ -61,8 +63,11 @@ public void action(Move move, Principal principal) {
}
Game updated = game.update(move);
games.put(updated);
operations.convertAndSend("/topic/move/" + game.id(), move.toView(color, moveNumber));
operations.convertAndSend("/topic/game/" + game.id(), updated.toView());
if (updated.counting()) {
operations.convertAndSend("/topic/move/" + game.id(), CountingMove.create(color, moveNumber, updated.board()));
} else {
operations.convertAndSend("/topic/move/" + game.id(), move.toView(color, moveNumber, updated.forbidden()));
}
}

@ResponseBody
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/com/bernd/game/MoveList.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.bernd.model.GameMove;
import com.bernd.model.Move;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -24,7 +25,7 @@ public final class MoveList {
private MoveList(
int dim,
int[] buffer) {
this.capacity = dim * dim;
this.capacity = 2 * buffer.length;
this.dim = dim;
this.buffer = buffer;
}
Expand All @@ -39,7 +40,8 @@ public static MoveList create(int dim) {

public void add(int color, Move move) {
if (pos >= capacity) {
int newCapacity = 2 * capacity;
int boardSize = dim * dim;
int newCapacity = capacity < boardSize ? boardSize : capacity + boardSize;
buffer = Arrays.copyOf(buffer, divUp(newCapacity, 2));
capacity = newCapacity;
}
Expand All @@ -61,12 +63,12 @@ public GameMove get(int i) {
int ptId = i % 2 == 0 ? code & LO : (code >> 16);
int color = (ptId & WHITE) != 0 ? Board.W : Board.B;
if ((ptId & PASS) != 0) {
return new GameMove(i, color, true, -1, -1);
return new GameMove(i, color, true, -1, -1, new int[]{-1, -1});
} else {
int data = ptId & DATA;
int x = data % dim;
int y = data / dim;
return new GameMove(i, color, true, x, y);
return new GameMove(i, color, true, x, y, new int[]{-1, -1});
}
}

Expand Down
Loading

0 comments on commit fc250d4

Please sign in to comment.