Skip to content

Commit ecee6c7

Browse files
committed
refactoring: split off Board.jsx
1 parent fd8c706 commit ecee6c7

File tree

4 files changed

+330
-281
lines changed

4 files changed

+330
-281
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import {
2+
twJoin,
3+
} from "tailwind-merge"
4+
import {
5+
useEffect,
6+
useCallback,
7+
useState,
8+
useContext,
9+
useRef,
10+
} from "react"
11+
import {
12+
Howl,
13+
} from "howler"
14+
import {
15+
base,
16+
StompContext,
17+
} from "src/util.js"
18+
import {
19+
useAuthStore,
20+
useMuteStore,
21+
} from "src/store.js"
22+
import {
23+
useLayoutStore,
24+
} from "src/layout.js"
25+
import {
26+
paintShadow,
27+
paintGrid,
28+
paintBoardDecorations,
29+
paintStones,
30+
paintStonesCounting,
31+
paintLastMove,
32+
paintNumber,
33+
paintMoveNumbers,
34+
} from "./paint.js"
35+
import {
36+
currentPlayer,
37+
isSelfPlay,
38+
currentColor,
39+
gameHasEnded,
40+
addMove,
41+
isKibitz,
42+
isReviewing,
43+
isCounting,
44+
teleport,
45+
} from "./state.js"
46+
47+
export function Board({
48+
gameState,
49+
setGameState,
50+
cursor_x,
51+
setCursor_x,
52+
cursor_y,
53+
setCursor_y,
54+
context,
55+
resetCountdown,
56+
timeRemaining,
57+
}) {
58+
let stompClient = useContext(StompContext)
59+
let [ctrlKeyDown, setCtrlKeyDown] = useState(false)
60+
let auth = useAuthStore(state => state.auth)
61+
let lastMove = gameState.lastMove
62+
let myColor = gameState.myColor
63+
let counting = isCounting(gameState)
64+
let board = gameState.board
65+
let [forbidden_x, forbidden_y] = gameState.forbidden
66+
let dragging = useLayoutStore(state => state.dragging)
67+
let muted = useMuteStore(state => state.muted)
68+
let howler = useRef()
69+
let end = gameHasEnded(gameState)
70+
let showMoveNumbers = ctrlKeyDown && (isKibitz(gameState, auth) || end)
71+
72+
let playClickSound = useCallback(() => {
73+
if (muted) {
74+
return
75+
}
76+
if (!howler.current) {
77+
howler.current = new Howl({
78+
src: [base + "/stone1.wav"],
79+
onloaderror: (id, error) => {
80+
throw new Error(id + ": " + error)
81+
},
82+
})
83+
}
84+
howler.current.play()
85+
}, [howler, muted])
86+
87+
useEffect(() => {
88+
let onKeyDown = (e) => {
89+
let activeElement = window.document.activeElement
90+
if (e.shiftKey && activeElement?.id === "chat-input") {
91+
return
92+
}
93+
if (e.ctrlKey || e.shiftKey) {
94+
setCtrlKeyDown(true)
95+
}
96+
}
97+
let onKeyUp = (e) => {
98+
if (!e.shiftKey && !e.shiftKey) {
99+
setCtrlKeyDown(false)
100+
}
101+
}
102+
window.addEventListener("keydown", onKeyDown)
103+
window.addEventListener("keyup", onKeyUp)
104+
return () => {
105+
window.removeEventListener("keydown", onKeyDown)
106+
window.removeEventListener("keyup", onKeyUp)
107+
}
108+
}, [setCtrlKeyDown])
109+
110+
let isCursorInBounds = useCallback(() => {
111+
let dim = board.length
112+
let x = context.cursorXref.current
113+
let y = context.cursorYref.current
114+
return x >= 0 && x < dim && y >= 0 && y < dim
115+
}, [context.cursorXref, context.cursorYref, board.length])
116+
117+
let getCountingGroup = useCallback(() => {
118+
if (end || !counting) {
119+
return undefined
120+
}
121+
if (!isCursorInBounds()) {
122+
return undefined
123+
}
124+
let x = context.cursorXref.current
125+
let y = context.cursorYref.current
126+
let {has, hasStone} = board[y][x]
127+
if (!hasStone) {
128+
return undefined
129+
}
130+
return has
131+
}, [context, counting, board, isCursorInBounds, end])
132+
133+
useEffect(() => {
134+
if (!context.canvasRef.current) {
135+
return
136+
}
137+
if (!showMoveNumbers && !counting && !end) {
138+
paintLastMove(context, lastMove, timeRemaining)
139+
}
140+
}, [showMoveNumbers, context, lastMove, timeRemaining, counting, end])
141+
142+
let onMouseMove = useCallback((e) => {
143+
if (dragging) {
144+
return
145+
}
146+
if (!context.canvasRef.current) {
147+
return
148+
}
149+
let dim = board.length
150+
setCtrlKeyDown(e.shiftKey || e.ctrlKey)
151+
let cursor_x = Math.round((e.nativeEvent.offsetX - context.margin) / context.step)
152+
let cursor_y = Math.round((e.nativeEvent.offsetY - context.margin) / context.step)
153+
if (cursor_x >= 0 && cursor_x < dim && cursor_y >= 0 && cursor_y < dim) {
154+
setCursor_x(cursor_x + 0)
155+
setCursor_y(cursor_y + 0)
156+
} else {
157+
setCursor_x(-1)
158+
setCursor_y(-1)
159+
}
160+
}, [context, setCursor_x, setCursor_y, board.length, dragging])
161+
162+
let onClick = useCallback(() => {
163+
if (!context.canvasRef.current) {
164+
return
165+
}
166+
let cursor_x = context.cursorXref.current
167+
let cursor_y = context.cursorYref.current
168+
if (showMoveNumbers) {
169+
let historyEntry = board[cursor_y][cursor_x].historyEntry
170+
if (historyEntry.n !== -1) {
171+
setGameState(teleport(gameState, historyEntry.n + 1))
172+
}
173+
return
174+
}
175+
if (end) {
176+
return
177+
}
178+
if (!isCursorInBounds()) {
179+
return
180+
}
181+
if (counting) {
182+
if (!board[cursor_y][cursor_x].hasStone) {
183+
return
184+
}
185+
} else {
186+
if (board[cursor_y][cursor_x].isForbidden(currentColor(gameState))) {
187+
return
188+
}
189+
if (cursor_x == forbidden_x && cursor_y == forbidden_y) {
190+
return
191+
}
192+
if (currentPlayer(gameState) !== auth.name) {
193+
return
194+
}
195+
}
196+
let move = {
197+
x: cursor_x,
198+
y: cursor_y,
199+
}
200+
if (!isSelfPlay(gameState)) { // can't add early in self play; myColor is 0
201+
// early add move
202+
setGameState(addMove(gameState, {
203+
...move,
204+
color: myColor,
205+
n: gameState.moves.length,
206+
}))
207+
}
208+
resetCountdown()
209+
playClickSound()
210+
stompClient.publish({
211+
destination: "/app/game/move",
212+
body: JSON.stringify(move),
213+
})
214+
}, [context, gameState, setGameState, auth, board, stompClient, counting, forbidden_x, forbidden_y, myColor, playClickSound, isCursorInBounds, showMoveNumbers, resetCountdown, end])
215+
216+
useEffect(() => {
217+
if (!context.canvasRef.current) {
218+
return
219+
}
220+
paintBoardDecorations(context)
221+
}, [context, board.length])
222+
223+
useEffect(() => {
224+
if (!context.canvasRef.current) {
225+
return
226+
}
227+
paintGrid(context)
228+
if (counting && !showMoveNumbers && !isReviewing(gameState)) {
229+
paintStonesCounting(context, board, getCountingGroup())
230+
return
231+
}
232+
paintStones(context, board, showMoveNumbers)
233+
if (showMoveNumbers) {
234+
paintMoveNumbers(context, board)
235+
} else if (!counting && !end) {
236+
paintLastMove(context, lastMove, timeRemaining)
237+
} else if (lastMove && !lastMove.action) {
238+
let bold = !isReviewing(gameState)
239+
paintNumber(context, lastMove.x, lastMove.y, lastMove.n + 1, lastMove.color, bold)
240+
}
241+
if (currentPlayer(gameState) !== auth.name) {
242+
return
243+
}
244+
if (!isCursorInBounds()) {
245+
return
246+
}
247+
if (board[cursor_y][cursor_x].hasStone) {
248+
return
249+
}
250+
if (board[cursor_y][cursor_x].isForbidden(currentColor(gameState))) {
251+
return
252+
}
253+
if (cursor_x === forbidden_x && cursor_y === forbidden_y) {
254+
return
255+
}
256+
if (!showMoveNumbers && !counting && !end) {
257+
paintShadow(context, cursor_x, cursor_y, currentColor(gameState))
258+
}
259+
}, [gameState, timeRemaining, context, cursor_x, cursor_y, ctrlKeyDown, auth, board, counting, forbidden_x, forbidden_y, lastMove, isCursorInBounds, getCountingGroup, showMoveNumbers, end])
260+
261+
return (
262+
<div className="grid h-full">
263+
<canvas className={twJoin(
264+
"place-self-center",
265+
isCursorInBounds() && showMoveNumbers && board[cursor_y][cursor_x].historyEntry.n !== -1 && "cursor-pointer",
266+
)}
267+
ref={context.canvasRef}
268+
onMouseLeave={() => {
269+
setCursor_x(-1)
270+
setCursor_y(-1)
271+
}}
272+
onMouseMove={onMouseMove}
273+
onClick={onClick}
274+
width={context.width} height={context.width}>
275+
</canvas>
276+
</div>
277+
)
278+
}

0 commit comments

Comments
 (0)