Bad Move error message displayed over the board

This commit is contained in:
djmil 2023-11-30 14:42:49 +01:00
parent 3bcf5ced76
commit a2a7ea2e54
7 changed files with 71 additions and 16 deletions

View File

@ -73,11 +73,12 @@ export default function useGamesApi() {
const pushGameMove = ({ uuid, move, message }) => { const pushGameMove = ({ uuid, move, message }) => {
doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, { doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, {
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && {moveFrom: move[0], moveTo: move[1]} }), onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && { moveFrom: move[0], moveTo: move[1] } }),
onBadReq: (message) => dispatchGuide({ type: 'UUIDerror', uuid, error: { message, moveFrom: move[0], moveTo: move[1] } }),
onSuccess: (game) => { onSuccess: (game) => {
dispatchState({ type: 'update', game }); dispatchState({ type: 'update', game });
dispatchGuide({ type: 'UUIDmessage', uuid, message: ''}); dispatchGuide({ type: 'UUIDmessage', uuid, message: '' });
} },
}) })
} }

View File

@ -97,10 +97,25 @@
.GameBoard { .GameBoard {
padding-left: 30px; padding-left: 30px;
width: 275px;
} }
.Message2Opponent { .Message2Opponent {
margin: 10px; margin: 10px;
margin-left: 30px; margin-left: 30px;
width: 270px; width: 275px;
}
.BadMove {
position: fixed;
width: 275px;
top: 250px;
cursor: default; /* disable 'I beam' cursor change */
text-align: center;
border-radius: 3px;
background-color: rgba(255, 80, 80, 0.3);
}
.BadMove:hover {
background-color: rgba(255, 80, 80, 0.7);
} }

View File

@ -34,7 +34,6 @@ export default function Games({ games, players }) {
<ActionPanel gamesApi={games.api} /> <ActionPanel gamesApi={games.api} />
<GameBoardRoutes dispatchGuide={gamesDispatchGuide} gamesApi={games.api} username={players.user.name} /> <GameBoardRoutes dispatchGuide={gamesDispatchGuide} gamesApi={games.api} username={players.user.name} />
<Message2OpponentRoutes dispatchGuide={gamesDispatchGuide} /> <Message2OpponentRoutes dispatchGuide={gamesDispatchGuide} />
{/* <GameMessage /> */}
</div> </div>
</div > </div >
@ -156,10 +155,11 @@ function GameBoardRoutes({ dispatchGuide, gamesApi, username }) {
const games = useContext(GamesStateContext); const games = useContext(GamesStateContext);
const guide = useContext(GamesGuideContext); const guide = useContext(GamesGuideContext);
const fromUUID = (uuid) => (!uuid) ? [{}, null] : const fromUUID = (uuid) => (!uuid) ? [{}, null, null] :
[ [
games.find((game) => game.uuid === uuid) || {}, // game games.find((game) => game.uuid === uuid) || {}, // game
guide.UUIDpushing[uuid] // pushing guide.UUIDpushing[uuid], // pushing
guide.UUIDerror[uuid] // error (aka bad move)
]; ];
const onStoneClick = (uuid, cellId) => { const onStoneClick = (uuid, cellId) => {
@ -191,6 +191,7 @@ function GameBoardRoutes({ dispatchGuide, gamesApi, username }) {
username={username} username={username}
getGame={() => fromUUID(guide.selectedUUID.active)} getGame={() => fromUUID(guide.selectedUUID.active)}
onStoneMove={(uuid, move) => gamesApi.pushGameMove({ uuid, move, message: guide.UUIDmessage[uuid] })} onStoneMove={(uuid, move) => gamesApi.pushGameMove({ uuid, move, message: guide.UUIDmessage[uuid] })}
dispatchGuide={dispatchGuide}
/> />
} /> } />

View File

@ -0,0 +1,14 @@
import React from 'react';
export default function BadMove({ dispatchGuide, uuid, message }) {
if (!dispatchGuide || !uuid || !message)
return;
return (
<div className='BadMove'
onClick={() => dispatchGuide({ type: 'UUIDerror', uuid, error: {} })}
>
{message}
</div>
)
}

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { Color, Player, Board } from '../../components/Checkers'; import { Color, Player, Board } from '../../components/Checkers';
import './GameBoard.css'; import './GameBoard.css';
import BadMove from './BadMove';
export default function GameBoard({ username, getGame, onStoneClick, onStoneMove }) { export default function GameBoard({ dispatchGuide, username, getGame, onStoneClick, onStoneMove }) {
const [game, isPushing] = getGame(); const [game, isPushing, error] = getGame();
const myName = game.opponentName ? username : ''; const myName = game.opponentName ? username : '';
const opponentColor = Color.opposite(game.myColor); const opponentColor = Color.opposite(game.myColor);
@ -14,11 +14,17 @@ export default function GameBoard({ username, getGame, onStoneClick, onStoneMove
(cellId) => onStoneClick(game.uuid, cellId); (cellId) => onStoneClick(game.uuid, cellId);
const optionalOnStoneMove = (typeof onStoneMove !== 'function' || isPushing) ? null : const optionalOnStoneMove = (typeof onStoneMove !== 'function' || isPushing) ? null :
(move) => { (move) => {
if (move[0] !== move[1] && game.board[move[1]] === undefined) if (move[0] !== move[1] && game.board[move[1]] === undefined)
onStoneMove(game.uuid, move) onStoneMove(game.uuid, move)
}; };
const currMove = (() => {
if (isPushing) return [isPushing.moveFrom, isPushing.moveTo];
if (error?.moveFrom && error?.moveTo) return [error.moveFrom, error.moveTo];
return [];
})(); // <<-- execute
return ( return (
<div className='GameBoard'> <div className='GameBoard'>
<Player <Player
@ -29,7 +35,7 @@ export default function GameBoard({ username, getGame, onStoneClick, onStoneMove
board={game.board} board={game.board}
flip={flipBoard} flip={flipBoard}
prevMove={game.previousMove} prevMove={game.previousMove}
currMove={[isPushing?.moveFrom, isPushing?.moveTo]} currMove={currMove}
onStoneClick={optionalOnStoneClick} onStoneClick={optionalOnStoneClick}
onStoneMove={optionalOnStoneMove} onStoneMove={optionalOnStoneMove}
/> />
@ -37,6 +43,7 @@ export default function GameBoard({ username, getGame, onStoneClick, onStoneMove
color={game.myColor || Color.white} color={game.myColor || Color.white}
name={myName} name={myName}
/> />
<BadMove dispatchGuide={dispatchGuide} uuid={game?.uuid} message={error?.message} />
</div> </div>
) )
} }

View File

@ -62,7 +62,7 @@ export function usePolling(uri, { onSuccess, onPolling }, mode = null) {
}, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]); }, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]);
} }
export async function doPushing(uri, method, data, { onSuccess, onPushing }) { export async function doPushing(uri, method, data, { onSuccess, onBadReq, onPushing }) {
if (onPushing) if (onPushing)
onPushing(true); onPushing(true);
@ -75,14 +75,22 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
body: JSON.stringify(data), // body data type must match "Content-Type" header body: JSON.stringify(data), // body data type must match "Content-Type" header
}); });
if (response.status === 400 && onBadReq) {
const content = (response.headers.get('Content-Type') === "application/json")
? await response.json()
: {};
return onBadReq(content.message);
}
if (!response.ok) { if (!response.ok) {
return console.warn(`Unexpected response status: ${response.status}`, response); return console.warn(`Unexpected response status: ${response.status}`, response);
} }
if (onSuccess) { if (onSuccess) {
var content = (response.headers.get('Content-Type') === "application/json") const content = (response.headers.get('Content-Type') === "application/json")
? await response.json() ? await response.json()
: null; : {};
onSuccess(content); onSuccess(content);
} }

View File

@ -76,6 +76,9 @@ export const gamesGuideTemplate = {
UUIDpushing: { // UUIDpushing[uuid] UUIDpushing: { // UUIDpushing[uuid]
}, },
UUIDerror: { // UUIDerror[uuid]
},
isPolling: false, isPolling: false,
}; };
@ -116,6 +119,12 @@ function gamesGuideReducer(state, action) {
return next; return next;
} }
case 'UUIDerror': {
const next = { ...state };
next.UUIDerror[action.uuid] = action.error;
return next;
}
default: default:
throw Error('GamesGuide: unknown action.type: ' + action.type); throw Error('GamesGuide: unknown action.type: ' + action.type);
} }