Skip to content

Commit e901491

Browse files
committed
kibitz feature: show move numbers on ctrl hold
1 parent 053f198 commit e901491

File tree

6 files changed

+92
-45
lines changed

6 files changed

+92
-45
lines changed

src/main/client/src/component/Chat.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export const Chat = ({chatId, className}) => {
112112
{message.type === "start" && <>
113113
<div className="w-full grid grid-cols-2 text-stone-100">
114114
{Object.keys(message.rows).map((key) => (
115-
<div className="content" key={key}>
115+
<div className="contents" key={key}>
116116
<div>{key}</div>
117117
<div>{message.rows[key]}</div>
118118
</div>

src/main/client/src/feature/game/Game.jsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import {
3434
import {
3535
useAuthStore,
3636
useMuteStore,
37-
useGameTicker,
3837
} from "src/store.js"
3938
import {
4039
useLayoutStore,
@@ -57,6 +56,7 @@ import {
5756
currentColor,
5857
gameHasEnded,
5958
addMove,
59+
isKibitz,
6060
createGameState,
6161
isReviewing,
6262
} from "./state.js"
@@ -78,12 +78,14 @@ export function Game() {
7878
function Board({gameState, setGameState}) {
7979
let [cursor_x, setCursor_x] = useState(-1)
8080
let [cursor_y, setCursor_y] = useState(-1)
81+
let [ctrlKeyDown, setCtrlKeyDown] = useState(false)
8182
let zoom = useViewStateStore(state => state.zoom)
8283
let {gameId} = useParams()
83-
let toggleGameTicker = useGameTicker(state => state.toggleGameTicker)
8484
let navigate = useNavigate()
8585
let stompClient = useContext(StompContext)
8686
let auth = useAuthStore(state => state.auth)
87+
let showMoveNumbersRef = useRef()
88+
showMoveNumbersRef.current = ctrlKeyDown && isKibitz(gameState, auth)
8789
let id = gameState.id
8890
let lastMove = gameState.lastMove
8991
let queueStatus = gameState.queueStatus
@@ -112,6 +114,25 @@ function Board({gameState, setGameState}) {
112114
howler.current.play()
113115
}, [howler, muted])
114116

117+
useEffect(() => {
118+
let onKeyDown = (e) => {
119+
if (e.ctrlKey) {
120+
setCtrlKeyDown(true)
121+
}
122+
}
123+
let onKeyUp = (e) => {
124+
if (!e.ctrlKey) {
125+
setCtrlKeyDown(false)
126+
}
127+
}
128+
window.addEventListener("keydown", onKeyDown)
129+
window.addEventListener("keyup", onKeyUp)
130+
return () => {
131+
window.removeEventListener("keydown", onKeyDown)
132+
window.removeEventListener("keyup", onKeyUp)
133+
}
134+
}, [setCtrlKeyDown])
135+
115136
let context = useMemo(() => {
116137
let dim = board.length
117138
if (!dim) {
@@ -161,6 +182,7 @@ function Board({gameState, setGameState}) {
161182
step,
162183
grid,
163184
canvasRef,
185+
showMoveNumbersRef,
164186
isCursorInBounds: function(x, y) {
165187
return x >= 0 && x < dim && y >= 0 && y < dim
166188
},
@@ -181,6 +203,7 @@ function Board({gameState, setGameState}) {
181203
if (!board.length) {
182204
return
183205
}
206+
setCtrlKeyDown(e.ctrlKey)
184207
if (!counting && currentPlayer(gameState) !== auth.name) {
185208
return
186209
}
@@ -229,14 +252,13 @@ function Board({gameState, setGameState}) {
229252
}
230253
if (!isSelfPlay(gameState)) { // myColor is 0 in self play
231254
setGameState(addMove(gameState, {...move, color: myColor}))
232-
toggleGameTicker()
233255
}
234256
playClickSound()
235257
stompClient.publish({
236258
destination: "/app/game/move",
237259
body: JSON.stringify(move),
238260
})
239-
}, [gameState, setGameState, toggleGameTicker, context, auth, board, stompClient, counting, forbidden_x, forbidden_y, movesLength, myColor, playClickSound])
261+
}, [gameState, setGameState, context, auth, board, stompClient, counting, forbidden_x, forbidden_y, movesLength, myColor, playClickSound])
240262

241263
useEffect(() => {
242264
if (!board.length) {
@@ -275,7 +297,7 @@ function Board({gameState, setGameState}) {
275297
"rgba(0,0,0,0.25)" :
276298
"rgba(255,255,255,0.25)"
277299
paintShadow(context, cursor_x, cursor_y, style)
278-
}, [gameState, cursor_x, cursor_y, context, canvasRef, auth, board, counting, countingGroup, forbidden_x, forbidden_y, lastMove])
300+
}, [gameState, cursor_x, cursor_y, context, ctrlKeyDown, canvasRef, auth, board, counting, countingGroup, forbidden_x, forbidden_y, lastMove])
279301

280302
useEffect(() => {
281303
if (id === gameId && queueStatus === "up_to_date") {
@@ -288,18 +310,16 @@ function Board({gameState, setGameState}) {
288310
},
289311
})
290312
setGameState(createGameState(game, auth))
291-
toggleGameTicker()
292313
}, () => navigate(base + "/lobby"))
293-
}, [setGameState, toggleGameTicker, queueStatus, auth, id, gameId, navigate])
314+
}, [setGameState, queueStatus, auth, id, gameId, navigate])
294315

295316
useEffect(() => {
296317
let sub = stompClient.subscribe("/topic/move/" + gameId, (message) => {
297318
let move = JSON.parse(message.body)
298319
setGameState(addMove(gameState, move))
299-
toggleGameTicker()
300320
})
301321
return sub.unsubscribe
302-
}, [gameState, setGameState, toggleGameTicker, stompClient, gameId])
322+
}, [gameState, setGameState, stompClient, gameId])
303323

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

src/main/client/src/feature/game/paint.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,27 @@ export function paintShadow({canvasRef, grid, stoneRadius}, grid_x, grid_y, styl
7676
}
7777

7878
export function paintStones(context, board, lastMove) {
79+
let showMoveNumbers = context.showMoveNumbersRef.current
7980
for (let grid_y = 0; grid_y < board.length; grid_y++) {
8081
for (let grid_x = 0; grid_x < board.length; grid_x++) {
81-
let {hasStone, color} = board[grid_y][grid_x]
82+
let {hasStone, color, n} = board[grid_y][grid_x]
8283
if (hasStone) {
8384
let style = color === BLACK ?
8485
"rgba(0,0,0)" :
8586
"rgba(255,255,255)"
8687
paintStone(context, grid_x, grid_y, style)
88+
if (showMoveNumbers) {
89+
let antiStyle = color === BLACK ?
90+
"rgba(255,255,255)" :
91+
"rgba(0,0,0)"
92+
paintMoveNumber(context, grid_x, grid_y, antiStyle, n + 1)
93+
}
8794
}
8895
}
8996
}
90-
paintLastMove(context, lastMove)
97+
if (!showMoveNumbers) {
98+
paintLastMove(context, lastMove)
99+
}
91100
}
92101

93102
export function paintStonesCounting(context, board, countingGroup) {
@@ -141,6 +150,17 @@ function paintStone({canvasRef, grid, stoneRadius}, grid_x, grid_y, style) {
141150
ctx.fill()
142151
}
143152

153+
function paintMoveNumber({stoneRadius, canvasRef, grid}, grid_x, grid_y, style, n) {
154+
let [x, y] = grid[grid_y][grid_x]
155+
let size = Math.trunc(stoneRadius * 0.75)
156+
let ctx = canvasRef.current.getContext("2d")
157+
ctx.font = size + "px sans-serif"
158+
ctx.textBaseline = "middle"
159+
ctx.textAlign = "center"
160+
ctx.fillStyle = style
161+
ctx.fillText(n, x, y)
162+
}
163+
144164
function paintLastMove({isCursorInBounds, canvasRef, grid, stoneRadius}, lastMove) {
145165
if (!lastMove) {
146166
return

src/main/client/src/feature/game/state.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function initialState() {
3131
id: "",
3232
moves: [],
3333
baseBoard: [],
34+
countBoard: [],
3435
viewPos: Number.NaN,
3536
dim: 0,
3637
handicap: 0,
@@ -109,17 +110,17 @@ export function isReviewing(baseState) {
109110
}
110111

111112
export function moveBack(baseState) {
113+
let viewPos = baseState.viewPos - 1
114+
if (viewPos < 0) {
115+
return baseState
116+
}
117+
let moves = baseState.moves
118+
let move = moves[viewPos]
119+
let baseBoard = unApply(baseState.baseBoard, move)
120+
let countBoard = baseState.countBoard
112121
return produce(baseState, (draft) => {
113-
let moves = baseState.moves
114-
let viewPos = baseState.viewPos
115-
viewPos--
116-
if (viewPos < 0) {
117-
return
118-
}
119-
let move = moves[viewPos]
120-
let baseBoard = unApply(baseState.baseBoard, move)
121122
draft.baseBoard = baseBoard
122-
draft.board = cheapRehydrate(baseBoard)
123+
draft.board = cheapRehydrate(baseBoard, countBoard)
123124
draft.viewPos = viewPos
124125
if (viewPos) {
125126
let previous = moves[viewPos - 1]
@@ -138,9 +139,10 @@ export function moveForward(baseState) {
138139
let moves = baseState.moves
139140
let move = moves[viewPos]
140141
let [, updated] = updateBoard(baseState.baseBoard, move)
142+
let countBoard = baseState.countBoard
141143
return produce(baseState, (draft) => {
142144
draft.baseBoard = updated
143-
draft.board = cheapRehydrate(updated)
145+
draft.board = cheapRehydrate(updated, countBoard)
144146
draft.viewPos = viewPos + 1
145147
draft.lastMove = move.action === "pass" ? undefined : move
146148
})
@@ -150,20 +152,21 @@ function goToEnd(baseState) {
150152
let moves = baseState.moves
151153
let queueLength = baseState.queueLength
152154
let baseBoard = baseState.baseBoard
155+
let countBoard = baseState.countBoard
153156
for (let i = queueLength; i < moves.length; i++) {
154157
let move = moves[i]
155158
let previousMove = getMove(moves, i - 1)
156159
let [, updated] = updateBoardState(baseBoard, previousMove, move, true)
157160
baseBoard = updated
158161
}
159162
return produce(baseState, (draft) => {
160-
draft.board = rehydrate(baseBoard)
163+
draft.board = rehydrate(baseBoard, countBoard)
161164
})
162165
}
163166

164167
export function addMove(baseState, move) {
165168
let {action, n} = move
166-
let {moves, baseBoard, counting, queueLength} = baseState
169+
let {moves, baseBoard, countBoard, counting, queueLength} = baseState
167170
let previousMove = getMove(moves, n - 1)
168171
if (n < moves.length) {
169172
return baseState
@@ -188,7 +191,9 @@ export function addMove(baseState, move) {
188191
draft.moves.push(storedMove)
189192
draft.lastMove = action === "pass" ? undefined : storedMove
190193
draft.baseBoard = updated
191-
draft.board = rehydrate(updated)
194+
let updatedCountBoard = updateCountBoard(countBoard, move)
195+
draft.countBoard = updatedCountBoard
196+
draft.board = rehydrate(updated, updatedCountBoard)
192197
draft.forbidden = forbidden
193198
if (action === "pass" && previousMove?.action === "pass") {
194199
draft.counting = true
@@ -198,8 +203,10 @@ export function addMove(baseState, move) {
198203

199204
export function createGameState(game, auth) {
200205
let baseBoard = Array(game.dim)
206+
let countBoard = Array(game.dim)
201207
for (let y = 0; y < game.dim; y++) {
202208
baseBoard[y] = new Int32Array(game.dim)
209+
countBoard[y] = new Int32Array(game.dim)
203210
}
204211
let moves = []
205212
let forbidden = [-1, -1]
@@ -224,6 +231,7 @@ export function createGameState(game, auth) {
224231
}
225232
let previousMove = getMove(moves, i - 1)
226233
let [storedMove, updated, newForbidden] = updateBoardState(baseBoard, previousMove, move, counting)
234+
countBoard[move.y][move.x] = move.n
227235
moves.push(storedMove)
228236
forbidden = newForbidden
229237
baseBoard = updated
@@ -236,8 +244,9 @@ export function createGameState(game, auth) {
236244
handicap: game.handicap,
237245
counting: counting,
238246
baseBoard: baseBoard,
247+
countBoard: countBoard,
239248
moves: moves,
240-
board: rehydrate(baseBoard),
249+
board: rehydrate(baseBoard, countBoard),
241250
forbidden: forbidden,
242251
viewPos: queueLength || moves.length,
243252
queueLength: queueLength || moves.length,
@@ -304,7 +313,7 @@ function unApply(board, move) {
304313
return result
305314
}
306315

307-
function cheapRehydrate(board) {
316+
function cheapRehydrate(board, countBoard) {
308317
let dim = board.length
309318
let result = Array(dim)
310319
for (let i = 0; i < board.length; i++) {
@@ -313,6 +322,7 @@ function cheapRehydrate(board) {
313322
for (let y = 0; y < board.length; y++) {
314323
for (let x = 0; x < board[y].length; x++) {
315324
result[y][x] = {
325+
n: countBoard[y][x],
316326
x: x,
317327
y: y,
318328
color: board[y][x],
@@ -335,3 +345,10 @@ function getMove(moves, i) {
335345
}
336346
return moves[i]
337347
}
348+
349+
function updateCountBoard(countBoard, {x, y, n}) {
350+
let updated = countBoard.slice()
351+
updated[y] = countBoard[y].slice()
352+
updated[y][x] = n
353+
return updated
354+
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export function getGroupInfo(board, xx, yy) {
1616
let dim = board.length
1717
if (!hasStone(color)) {
1818
return {
19-
x: xx,
20-
y: yy,
2119
color: color,
2220
hasStone: false,
2321
liberties: 0,
@@ -77,8 +75,6 @@ export function getGroupInfo(board, xx, yy) {
7775
}
7876
let set = points.toSet()
7977
return {
80-
x: xx,
81-
y: yy,
8278
color: color,
8379
hasStone: true,
8480
liberties: liberties,
@@ -87,7 +83,7 @@ export function getGroupInfo(board, xx, yy) {
8783
}
8884
}
8985

90-
export function rehydrate(board) {
86+
export function rehydrate(board, countBoard) {
9187
let dim = board.length
9288
let result = Array(dim)
9389
for (let i = 0; i < board.length; i++) {
@@ -96,12 +92,16 @@ export function rehydrate(board) {
9692
for (let y = 0; y < board.length; y++) {
9793
for (let x = 0; x < board[y].length; x++) {
9894
if (result[y][x]) {
99-
result[y][x] = {...result[y][x], x: x, y: y}
10095
continue
10196
}
10297
let groupInfo = getGroupInfo(board, x, y)
103-
groupInfo.points.forEach((_x, _y) => {
104-
result[_y][_x] = groupInfo
98+
groupInfo.points.forEach((xx, yy) => {
99+
result[yy][xx] = {
100+
...groupInfo,
101+
x: xx,
102+
y: yy,
103+
n: countBoard[yy][xx],
104+
}
105105
})
106106
}
107107
}

src/main/client/src/store.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,3 @@ export const useMuteStore = create(
4545
{ name: "mute-storage" },
4646
)
4747
)
48-
49-
// trick to re-render chat after each move
50-
export const useGameTicker = create((set, get) => ({
51-
gameTicker: false,
52-
toggleGameTicker: () =>
53-
set(() => ({
54-
gameTicker: !get().gameTicker
55-
}))
56-
}),
57-
)

0 commit comments

Comments
 (0)