Skip to content

incremental updates v1 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 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,19 +193,11 @@ 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)
setGameState(game)
})
let sub2 = stompClient.subscribe("/topic/move/" + gameId, (message) => {
let move = JSON.parse(message.body)
console.log(move) // TODO
})
doTry(async () => {
let game = await tfetch("/api/game/" + gameId, {
headers: {
Expand All @@ -208,14 +206,24 @@ export const Game = () => {
})
setGameState(game)
})
return () => {
sub1.unsubscribe()
sub2.unsubscribe()
}, [setGameState, queueStatus, auth, gameId])

useEffect(() => {
if (initialized.current) {
return
}
}, [setGameState, initialized, stompClient, gameId, auth])
initialized.current = true
let sub = stompClient.subscribe("/topic/move/" + gameId, (message) => {
let move = JSON.parse(message.body)
addMove(move)
})
return sub.unsubscribe
}, [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
Loading