Skip to content

Commit b7e5615

Browse files
committed
add review buttons
1 parent d6627c7 commit b7e5615

File tree

6 files changed

+163
-42
lines changed

6 files changed

+163
-42
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
gameHasEnded,
5959
addMove,
6060
createGameState,
61+
isReviewing,
6162
} from "./state.js"
6263

6364
export function Game() {
@@ -69,7 +70,7 @@ export function Game() {
6970
className="h-full">
7071
<MuteIcon />
7172
<Board gameState={gameState} setGameState={setGameState} />
72-
<GamePanel gameState={gameState} />
73+
<GamePanel gameState={gameState} setGameState={setGameState} />
7374
</div>
7475
)
7576
}
@@ -249,7 +250,7 @@ function Board({gameState, setGameState}) {
249250
return
250251
}
251252
paintGrid(context)
252-
if (counting) {
253+
if (counting && !isReviewing(gameState)) {
253254
paintStonesCounting(context, board, countingGroup)
254255
return
255256
} else {

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,32 @@ import {
4343
countingComplete,
4444
currentPlayer,
4545
isSelfPlay,
46+
isKibitz,
47+
moveBack,
48+
moveForward,
4649
countingAgreed,
4750
gameHasEnded,
51+
isReviewing,
4852
} from "./state.js"
4953

50-
export const GamePanel = ({gameState}) => {
54+
export const GamePanel = ({gameState, setGameState}) => {
5155
return (
5256
<SideBar page="game">
5357
<div className="pr-3 pt-4 pl-2 h-full flex flex-col">
54-
<Panel gameState={gameState} />
58+
<Panel gameState={gameState} setGameState={setGameState} />
5559
</div>
5660
</SideBar>
5761
)
5862
}
5963

60-
function Panel({gameState}) {
64+
function Panel({gameState, setGameState}) {
6165
let {gameId} = useParams()
6266
let zoom = useViewStateStore(state => state.zoom)
6367
let setZoom = useViewStateStore(state => state.setZoom)
6468
let stompClient = useContext(StompContext)
6569
let auth = useAuthStore(state => state.auth)
66-
let black = gameState.black
67-
let white = gameState.white
68-
let queueLength = gameState.queueLength
70+
let {black, white, viewPos, counting, board} = gameState
6971
let movesLength = gameState.moves.length
70-
let counting = gameState.counting
71-
let board = gameState.board
7272
let navigate = useNavigate()
7373
let onExit = useCallback(() => {
7474
navigate(base + "/lobby")
@@ -106,7 +106,7 @@ function Panel({gameState}) {
106106
let result = gameHasEnded(gameState) ? getScore(board) : undefined
107107
return (
108108
<>
109-
<div className="flex-none grid w-full grid-cols-[min-content_max-content_min-content_auto] gap-x-2">
109+
<div className="flex-none flex w-full gap-x-2">
110110
<button
111111
onClick={() => setZoom(zoom - 1)}>
112112
<IconContext.Provider value={{
@@ -128,8 +128,8 @@ function Panel({gameState}) {
128128
<FaAngleRight />
129129
</IconContext.Provider>
130130
</button>
131-
<button title="Leave the game" onClick={onExit}
132-
className="justify-self-end">
131+
<div className="grow" />
132+
<button title="Leave the game" onClick={onExit}>
133133
<IconContext.Provider value={{
134134
size: "1.5em",
135135
className: "pr-[4px]",
@@ -143,18 +143,38 @@ function Panel({gameState}) {
143143
<div>vs</div>
144144
<div>{black}</div>
145145
</div>
146-
<div className="flex-none">
147-
Move {queueLength}
148-
</div>
149-
<div className="flex-none">
150-
<Button
151-
onClick={onPass}
152-
className="py-1 px-4"
153-
disabled={gameHasEnded(gameState) || counting || currentPlayer(gameState) !== auth.name}>
154-
Pass
155-
</Button>
156-
</div>
157-
{counting && <>
146+
{(gameHasEnded(gameState) || isKibitz(gameState, auth)) ? (
147+
<div className="flex-none flex gap-x-1 items-center">
148+
<Button
149+
onClick={() => setGameState(moveBack(gameState))}
150+
className="py-1 px-2">
151+
Back
152+
</Button>
153+
<div className="flex-none">
154+
<div>Move {viewPos}</div>
155+
</div>
156+
<Button
157+
onClick={() => setGameState(moveForward(gameState))}
158+
className="py-1 px-2">
159+
Forward
160+
</Button>
161+
</div>
162+
) : (
163+
<div className="flex-none">
164+
<div>Move {viewPos}</div>
165+
</div>
166+
)}
167+
{!isKibitz(gameState, auth) && !gameHasEnded(gameState) && (
168+
<div className="flex-none">
169+
<Button
170+
onClick={onPass}
171+
className="py-1 px-4"
172+
disabled={counting || currentPlayer(gameState) !== auth.name}>
173+
Pass
174+
</Button>
175+
</div>
176+
)}
177+
{!isKibitz(gameState, auth) && !gameHasEnded(gameState) && counting && <>
158178
<div className="flex-none">
159179
<Button
160180
className="py-1 px-4"
@@ -165,14 +185,14 @@ function Panel({gameState}) {
165185
</div>
166186
<div className="flex-none">
167187
<Button
168-
disabled={(!isSelfPlay(gameState) && countingAgreed(gameState)) || gameHasEnded(gameState) || !countingComplete(gameState)}
188+
disabled={(!isSelfPlay(gameState) && countingAgreed(gameState)) || !countingComplete(gameState)}
169189
className="py-1 px-4"
170190
onClick={onCountingAgree}>
171191
OK
172192
</Button>
173193
</div>
174194
</>}
175-
{result && (
195+
{result && !isReviewing(gameState) && (
176196
<div className="flex-none">
177197
{(result.w > result.b ? "W+" : "B+") + Math.abs(result.b - result.w)}
178198
</div>

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

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
BLACK,
66
WHITE,
77
COLORS,
8+
hasStone,
9+
resurrect,
810
} from "src/util.js"
911
import {
1012
rehydrate,
@@ -20,12 +22,16 @@ import {
2022
toggleStonesAt,
2123
resetCounting,
2224
} from "src/model/count.js"
25+
import {
26+
PointList,
27+
} from "src/model/PointList.js"
2328

2429
export function initialState() {
2530
return {
2631
id: "",
2732
moves: [],
2833
baseBoard: [],
34+
viewPos: Number.NaN,
2935
dim: 0,
3036
handicap: 0,
3137
queueStatus: "behind",
@@ -68,6 +74,10 @@ export function isSelfPlay({black, white}) {
6874
return black === white
6975
}
7076

77+
export function isKibitz({black, white}, auth) {
78+
return black !== auth.name && white !== auth.name
79+
}
80+
7181
export function currentColor({moves, handicap}) {
7282
if (handicap > moves.length) {
7383
return BLACK
@@ -78,7 +88,7 @@ export function currentColor({moves, handicap}) {
7888
return moves[moves.length - 1].color ^ COLORS
7989
}
8090

81-
export function countingAgreed ({moves, myColor}) {
91+
export function countingAgreed({moves, myColor}) {
8292
if (!moves.length) {
8393
return false
8494
}
@@ -94,6 +104,48 @@ export function gameHasEnded({moves}) {
94104
return move.action === "end"
95105
}
96106

107+
export function isReviewing(baseState) {
108+
return baseState.viewPos < baseState.queueLength
109+
}
110+
111+
export function moveBack(baseState) {
112+
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)
121+
draft.baseBoard = baseBoard
122+
draft.board = cheapRehydrate(baseBoard)
123+
draft.viewPos = viewPos
124+
if (viewPos) {
125+
let previous = moves[viewPos - 1]
126+
draft.lastMove = previous.action === "pass" ? undefined : previous
127+
} else {
128+
draft.lastMove = undefined
129+
}
130+
})
131+
}
132+
133+
export function moveForward(baseState) {
134+
return produce(baseState, (draft) => {
135+
let moves = baseState.moves
136+
let viewPos = baseState.viewPos
137+
if (viewPos - baseState.queueLength >= 0) {
138+
return
139+
}
140+
let move = moves[viewPos]
141+
let [, updated] = updateBoard(baseState.baseBoard, move)
142+
draft.baseBoard = updated
143+
draft.board = cheapRehydrate(updated)
144+
draft.viewPos = viewPos + 1
145+
draft.lastMove = move.action === "pass" ? undefined : move
146+
})
147+
}
148+
97149
export function addMove(baseState, move) {
98150
return produce(baseState, (draft) => {
99151
let {action, n} = move
@@ -112,6 +164,7 @@ export function addMove(baseState, move) {
112164
draft.queueStatus = "up_to_date"
113165
if (!counting) {
114166
draft.queueLength = queueLength + 1
167+
draft.viewPos = queueLength + 1
115168
}
116169
let [storedMove, updated, forbidden] = createMoveData(baseBoard, moves, move, counting)
117170
draft.moves.push(storedMove)
@@ -166,6 +219,7 @@ export function createGameState(game, auth) {
166219
moves: moves,
167220
board: rehydrate(baseBoard),
168221
forbidden: forbidden,
222+
viewPos: queueLength || moves.length,
169223
queueLength: queueLength || moves.length,
170224
queueStatus: "up_to_date",
171225
}
@@ -199,7 +253,52 @@ function createMoveData(baseBoard, moves, move, counting) {
199253
return [move, count(updated), [-1, -1]]
200254
}
201255
let [dead, updated] = updateBoard(baseBoard, move)
202-
let storedMove = {...move, dead}
256+
let storedMove = {...move, dead: dead}
203257
let forbidden = getForbidden(baseBoard, updated, storedMove)
204258
return [storedMove, updated, forbidden]
205259
}
260+
261+
function unApply(board, move) {
262+
let dim = board.length
263+
let result = Array(dim)
264+
for (let i = 0; i < board.length; i++) {
265+
result[i] = Array(dim)
266+
}
267+
for (let y = 0; y < board.length; y++) {
268+
for (let x = 0; x < board[y].length; x++) {
269+
if (move.action !== "pass" && move.x === x && move.y === y) {
270+
result[y][x] = 0
271+
} else {
272+
result[y][x] = (resurrect(board[y][x]) & COLORS)
273+
}
274+
}
275+
}
276+
if (move.dead) {
277+
move.dead.forEach((x, y) => {
278+
result[y][x] = (move.color ^ COLORS)
279+
})
280+
}
281+
return result
282+
}
283+
284+
function cheapRehydrate(board) {
285+
let dim = board.length
286+
let result = Array(dim)
287+
for (let i = 0; i < board.length; i++) {
288+
result[i] = Array(dim)
289+
}
290+
for (let y = 0; y < board.length; y++) {
291+
for (let x = 0; x < board[y].length; x++) {
292+
result[y][x] = {
293+
x: x,
294+
y: y,
295+
color: board[y][x],
296+
hasStone: hasStone(board[y][x]),
297+
liberties: 0,
298+
has: () => false,
299+
points: PointList.empty(),
300+
}
301+
}
302+
}
303+
return result
304+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {
22
PointQueue,
3-
} from "./PointQueue.js"
3+
} from "src/model/PointQueue.js"
44
import {
55
PointList,
6-
} from "./PointList.js"
6+
} from "src/model/PointList.js"
77
import {
88
PointSet,
9-
} from "./PointSet.js"
9+
} from "src/model/PointSet.js"
1010
import {
1111
hasStone,
1212
} from "src/util.js"

src/main/client/src/model/count.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "./PointSet.js"
77
import {
88
hasStone,
9+
resurrect,
910
BLACK,
1011
WHITE,
1112
COLORS,
@@ -208,16 +209,6 @@ function toggleRemoved(color) {
208209
return color
209210
}
210211

211-
function resurrect(color) {
212-
if (color & REMOVED_B) {
213-
return color ^ TOGGLE_B
214-
}
215-
if (color & REMOVED_W) {
216-
return color ^ TOGGLE_W
217-
}
218-
return color
219-
}
220-
221212
function createAcc(dim) {
222213
let result = Array(dim)
223214
for (let y = 0; y < dim; y++) {

src/main/client/src/util.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,13 @@ export function getRemInPixel() {
118118
let fontSize = window.getComputedStyle(document.documentElement).fontSize
119119
return parseFloat(fontSize)
120120
}
121+
122+
export function resurrect(color) {
123+
if (color & REMOVED_B) {
124+
return color ^ TOGGLE_B
125+
}
126+
if (color & REMOVED_W) {
127+
return color ^ TOGGLE_W
128+
}
129+
return color
130+
}

0 commit comments

Comments
 (0)