Skip to content

Commit 3ff83b1

Browse files
committed
use canvas
1 parent 25a4054 commit 3ff83b1

21 files changed

+604
-348
lines changed

src/main/client/src/Game.jsx

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import {
2+
useEffect,
3+
useCallback,
4+
useState,
5+
useContext,
6+
useRef,
7+
useMemo,
8+
} from "react"
9+
import {
10+
useParams,
11+
} from "react-router-dom"
12+
import {
13+
StompContext,
14+
BLACK,
15+
ANY_TERRITORY,
16+
TERRITORY_B,
17+
REMOVED_B,
18+
ANY_REMOVED,
19+
} from "./util.js"
20+
import {
21+
isForbidden,
22+
} from "./model/board.js"
23+
import {
24+
GamePanel,
25+
} from "./feature/GamePanel.jsx"
26+
import {
27+
useAuthStore,
28+
useGameStore,
29+
} from "./store.js"
30+
31+
const kirsch = "#dfbd6d"
32+
const asch = "#a78a48"
33+
34+
export const Game = () => {
35+
let width = 750
36+
let margin = 80
37+
let [cursor_x, setCursor_x] = useState(-1)
38+
let [cursor_y, setCursor_y] = useState(-1)
39+
let { gameId } = useParams()
40+
let stompClient = useContext(StompContext)
41+
let auth = useAuthStore(state => state.auth)
42+
let setGameState = useGameStore(state => state.setGameState)
43+
let { board, currentColor, currentPlayer, counting } = useGameStore(state => state.gameState)
44+
let initialized = useRef()
45+
let canvasRef = useRef()
46+
let context = useMemo(() => {
47+
if (!board.length) {
48+
return
49+
}
50+
let dim = board.length
51+
let step = (width - margin - margin) / (dim - 1)
52+
let grid = []
53+
for (let y = 0; y < dim; y++) {
54+
grid[y] = []
55+
for (let x = 0; x < dim; x++) {
56+
grid[y][x] = [
57+
Math.trunc(margin + (x * step)),
58+
Math.trunc(margin + (y * step)),
59+
]
60+
}
61+
}
62+
return { width, margin, dim, step, grid, canvasRef }
63+
}, [width, margin, board.length, canvasRef])
64+
let onMouseMove = useCallback((e) => {
65+
if (!board.length) {
66+
return
67+
}
68+
if (!counting && currentPlayer !== auth.name) {
69+
return
70+
}
71+
let cursor_x = Math.round((e.nativeEvent.offsetX - context.margin) / context.step)
72+
let cursor_y = Math.round((e.nativeEvent.offsetY - context.margin) / context.step)
73+
setCursor_x(cursor_x + 0)
74+
setCursor_y(cursor_y + 0)
75+
}, [context, currentPlayer, auth, board.length, counting])
76+
let countingGroup = counting ? getCountingGroup(board, cursor_x, cursor_y) : undefined
77+
let onClick = useCallback((e) => {
78+
if (!board.length) {
79+
return
80+
}
81+
if (!counting && currentPlayer !== auth.name) {
82+
return
83+
}
84+
let cursor_x = Math.round((e.nativeEvent.offsetX - context.margin) / context.step)
85+
let cursor_y = Math.round((e.nativeEvent.offsetY - context.margin) / context.step)
86+
if (cursor_x >= 0 &&
87+
cursor_x < context.dim &&
88+
cursor_y >= 0 &&
89+
cursor_y < context.dim &&
90+
(counting && board[cursor_y][cursor_x].hasStone || !isForbidden(board, board[cursor_y][cursor_x], currentColor))) {
91+
stompClient.publish({
92+
destination: "/app/game/move",
93+
body: JSON.stringify({
94+
id: gameId,
95+
x: cursor_x,
96+
y: cursor_y,
97+
}),
98+
})
99+
}
100+
}, [context, currentPlayer, currentColor, auth, board, gameId, stompClient, counting])
101+
useEffect(() => {
102+
if (!board.length) {
103+
return
104+
}
105+
paintGrid(context)
106+
if (counting) {
107+
paintStonesCounting(context, board, countingGroup)
108+
} else {
109+
paintStones(context, board)
110+
}
111+
if (!counting &&
112+
currentPlayer === auth.name &&
113+
cursor_x >= 0 &&
114+
cursor_x < context.dim &&
115+
cursor_y >= 0 &&
116+
cursor_y < context.dim &&
117+
!isForbidden(board, board[cursor_y][cursor_x], currentColor)) {
118+
let [x, y] = context.grid[cursor_y][cursor_x]
119+
let style = currentColor === BLACK ?
120+
"rgba(0,0,0,0.25)" :
121+
"rgba(255,255,255,0.25)"
122+
showShadow(context, x, y, style)
123+
}
124+
}, [cursor_x, cursor_y, width, context, canvasRef, auth, currentColor, board, currentPlayer, counting, countingGroup])
125+
useEffect(() => {
126+
if (initialized.current) {
127+
return
128+
}
129+
initialized.current = true
130+
let sub1 = stompClient.subscribe("/topic/game/" + gameId, (message) => {
131+
let game = JSON.parse(message.body)
132+
setGameState(game)
133+
})
134+
stompClient.publish({
135+
destination: "/app/game/hello",
136+
body: JSON.stringify({
137+
id: gameId,
138+
}),
139+
})
140+
return () => {
141+
sub1.unsubscribe()
142+
}
143+
}, [setGameState, initialized, stompClient, gameId])
144+
if (!board.length) {
145+
return <div>Loading...</div>
146+
}
147+
return (
148+
<div className="grid justify-center mt-8">
149+
<canvas ref={canvasRef}
150+
onMouseMove={onMouseMove}
151+
onClick={onClick}
152+
width={width} height={width}>
153+
</canvas>
154+
<GamePanel />
155+
</div>
156+
)
157+
}
158+
159+
function showTerritory({ canvasRef, step }, x, y, style) {
160+
let ctx = canvasRef.current.getContext("2d")
161+
ctx.fillStyle = style
162+
ctx.beginPath()
163+
ctx.arc(x, y, step * 0.125, 0, 2 * Math.PI)
164+
ctx.fill()
165+
}
166+
167+
function showStone({ canvasRef, step }, x, y, style) {
168+
let ctx = canvasRef.current.getContext("2d")
169+
ctx.fillStyle = style
170+
ctx.beginPath()
171+
ctx.arc(x, y, step * 0.475, 0, 2 * Math.PI)
172+
ctx.fill()
173+
}
174+
175+
function showShadow({ canvasRef, step }, x, y, style) {
176+
let ctx = canvasRef.current.getContext("2d")
177+
ctx.fillStyle = style
178+
ctx.beginPath()
179+
ctx.arc(x, y, step * 0.475, 0, 2 * Math.PI)
180+
ctx.fill()
181+
}
182+
183+
function paintGrid({ width, canvasRef, grid }) {
184+
let ctx = canvasRef.current.getContext("2d")
185+
ctx.fillStyle = kirsch
186+
ctx.fillRect(0, 0, width, width)
187+
ctx.strokeStyle = asch
188+
for (let y = 0; y < grid.length; y++) {
189+
let [source_x, source_y] = grid[y][0]
190+
let [target_x, target_y] = grid[y][grid.length - 1]
191+
ctx.beginPath()
192+
ctx.moveTo(source_x + 0.5, source_y + 0.5)
193+
ctx.lineTo(target_x + 0.5, target_y + 0.5)
194+
ctx.stroke()
195+
}
196+
for (let x = 0; x < grid.length; x++) {
197+
let [source_x, source_y] = grid[0][x]
198+
let [target_x, target_y] = grid[grid.length - 1][x]
199+
ctx.beginPath()
200+
ctx.moveTo(source_x + 0.5, source_y + 0.5)
201+
ctx.lineTo(target_x + 0.5, target_y + 0.5)
202+
ctx.stroke()
203+
}
204+
}
205+
206+
function paintStones(context, board) {
207+
for (let logical_y = 0; logical_y < board.length; logical_y++) {
208+
for (let logical_x = 0; logical_x < board.length; logical_x++) {
209+
let { hasStone, color } = board[logical_y][logical_x]
210+
if (hasStone) {
211+
let [x, y] = context.grid[logical_y][logical_x]
212+
let style = color === BLACK ?
213+
"rgba(0,0,0)" :
214+
"rgba(255,255,255)"
215+
showStone(context, x, y, style)
216+
}
217+
}
218+
}
219+
}
220+
221+
function paintStonesCounting(context, board, countingGroup) {
222+
for (let logical_y = 0; logical_y < board.length; logical_y++) {
223+
for (let logical_x = 0; logical_x < board.length; logical_x++) {
224+
let { hasStone, color } = board[logical_y][logical_x]
225+
let [x, y] = context.grid[logical_y][logical_x]
226+
if (hasStone) {
227+
if (countingGroup && countingGroup(logical_x, logical_y)) {
228+
let style = color & BLACK ?
229+
"rgba(0,0,0,0.25)" :
230+
"rgba(255,255,255,0.25)"
231+
showShadow(context, x, y, style)
232+
} else {
233+
let style = color & BLACK ?
234+
"rgba(0,0,0)" :
235+
"rgba(255,255,255)"
236+
showStone(context, x, y, style)
237+
}
238+
} else if (color & ANY_TERRITORY) {
239+
if (color & ANY_REMOVED) {
240+
let style = (color & ANY_REMOVED) === REMOVED_B ?
241+
"rgba(0,0,0,0.25)" :
242+
"rgba(255,255,255,0.25)"
243+
showShadow(context, x, y, style)
244+
}
245+
let style = (color & ANY_TERRITORY) === TERRITORY_B ?
246+
"rgba(0,0,0)" :
247+
"rgba(255,255,255)"
248+
showTerritory(context, x, y, style)
249+
}
250+
}
251+
}
252+
}
253+
254+
function getCountingGroup(board, cursor_x, cursor_y) {
255+
if (cursor_x < 0 ||
256+
cursor_x >= board.length ||
257+
cursor_y < 0 ||
258+
cursor_y >= board.length ) {
259+
return undefined
260+
}
261+
let { has, hasStone } = board[cursor_y][cursor_x]
262+
if (!hasStone) {
263+
return undefined
264+
}
265+
return has
266+
}

src/main/client/src/Lobby.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,16 @@ export function Lobby() {
7373
return (
7474
<div className="m-4">
7575
<div>
76-
<Button type="Button"
76+
<Button
7777
onClick={() => matchRequest(true)}>
7878
Create
7979
</Button>
8080
</div>
8181
<div className="mt-2">
82-
<button type="button"
83-
className="p-2 border border-black"
82+
<Button
8483
onClick={() => matchRequest(false)}>
8584
Find match
86-
</button>
85+
</Button>
8786
</div>
8887
<div className="mt-2">
8988
{users.map(user => (

src/main/client/src/Login.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import {
33
} from "./Form.jsx"
44
import {
55
Input,
6-
} from "./Input.jsx"
6+
} from "./component/Input.jsx"
7+
import {
8+
Button,
9+
} from "./component/Button.jsx"
710
import {
811
useNavigate,
912
} from "react-router-dom"
@@ -45,11 +48,11 @@ export function Login() {
4548
<div>
4649
<Input name="name" />
4750
</div>
48-
<button
49-
className="mt-2 p-2 border border-black"
51+
<Button
52+
className="mt-2"
5053
type="submit">
5154
Join
52-
</button>
55+
</Button>
5356
</Form>
5457
<Toaster position="top-right"/>
5558
</>

0 commit comments

Comments
 (0)