GameBoard to react on selected player

- some refactoring
This commit is contained in:
djmil 2023-11-16 18:19:40 +01:00
parent 671e13a41d
commit 8611ede4b4
17 changed files with 247 additions and 279 deletions

View File

@ -26,6 +26,7 @@ export default function App() {
const players = { const players = {
leaderboard, leaderboard,
currentUser: user.username,
isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null
}; };

View File

@ -26,6 +26,9 @@ export function Stone({ color }) {
case Color.black: case Color.black:
return BlackStone(); return BlackStone();
case '':
return; // no color :)
default: default:
console.warn("Unknown color: ", color) console.warn("Unknown color: ", color)
} }

View File

@ -4,16 +4,7 @@ import { NavLink, Routes, Route } from 'react-router-dom';
import NewGame from './games/view/NewGame'; import NewGame from './games/view/NewGame';
import { GameProposalSelector, ActiveGameSelector, GameArchiveSelector } from './games/view/GameSelector'; import { GameProposalSelector, ActiveGameSelector, GameArchiveSelector } from './games/view/GameSelector';
import { Create, Accept, Reject, Cancel, DrawRequest, DrawAccept, DrawReject, Surrender, Backward, Forward } from './games/ActionPanel';
import Create from './games/action/Create';
import Reject from './games/action/Reject';
import Cancel from './games/action/Cancel';
import Accept from './games/action/Accept';
import Surrender from './games/action/Surrender';
import { DrawRequest, DrawAccept, DrawReject } from './games/action/Draw';
import Backward from './games/action/Backward';
import Forward from './games/action/Forward';
import GameBoard from './games/GameBoard'; import GameBoard from './games/GameBoard';
import Message2Opponent from './games/Message2Opponent'; import Message2Opponent from './games/Message2Opponent';
import Counter from '../components/Counter'; import Counter from '../components/Counter';
@ -29,11 +20,11 @@ export default function Games({ context: { gamesReducer, gamesApi }, players })
<div className='left-side'> <div className='left-side'>
<ViewSelector games={games} /> <ViewSelector games={games} />
<ViewProvider gamesReducer={gamesReducer} players={players} /> <ViewProvider dispatchGames={dispatchGames} players={players} />
</div> </div>
<div className='right-side'> <div className='right-side'>
<ActionPanel gamesApi={gamesApi} players={players} /> <ActionPanel gamesApi={gamesApi} />
{/* <GameMessage /> */} {/* <GameMessage /> */}
<GameBoard /> <GameBoard />
<Message2Opponent dispatchGames={dispatchGames} /> <Message2Opponent dispatchGames={dispatchGames} />
@ -57,15 +48,14 @@ function ViewSelector({ games }) {
) )
} }
function ViewProvider({ gamesReducer, players }) { function ViewProvider({ dispatchGames, players }) {
const [/*games*/, dispatchGames] = gamesReducer;
return ( return (
<div className='ViewProvider'> <div className='ViewProvider'>
<Routes> <Routes>
<Route path='new' element={ <Route path='new' element={
<NewGame onSelectPlayer={(whitePlayer, blackPlayer) => dispatchGames({ type: 'nextNewGame', whitePlayer, blackPlayer })} <NewGame setOpponent={(opponentName, opponentColor) => dispatchGames({ type: 'nextNewGame', opponentName, opponentColor })}
players={players} players={players}
/> />
} /> } />
@ -87,15 +77,13 @@ function ViewProvider({ gamesReducer, players }) {
) )
} }
function ActionPanel({ players, gamesApi }) { function ActionPanel({ gamesApi }) {
return ( return (
<div className='ActionPanel'> <div className='ActionPanel'>
<Routes> <Routes>
<Route path='new' element={ <Route path='new' element={
<Create isCurrentUser={players.isCurrentUser} <Create onClick={(reqParams) => gamesApi.pushNewGame(reqParams)} />
onClick={(reqParams) => gamesApi.pushNewGame(reqParams)}
/>
} /> } />
<Route path='proposal' element={[ <Route path='proposal' element={[

View File

@ -0,0 +1,184 @@
import React, { useContext } from 'react';
import { GamesContext } from '../../context/games';
import Wobler from '../../components/Wobler';
/*
* NewGame actoins
*/
export function Create({ onClick }) {
const games = useContext(GamesContext);
const hasOpponent = games.newGame.opponentName && games.newGame.opponentColor;
const prepareNewGameRequest = () => {
if (!hasOpponent)
return alert("You have to select an opponent");
const reqParams = {
opponentName: games.newGame.opponentName,
opponentColor: games.newGame.opponentColor,
board: null, // default board configuration
message: games.newGame.message
}
onClick(reqParams);
}
return (
<button className={'Create' + (hasOpponent ? ' ready' : '')}
onClick={prepareNewGameRequest}
>
<Wobler text="Create" dance={games.isPushingNewGame} />
</button>
)
}
/*
* GameProposal actions
*/
export function Accept({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Accept' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
>
<Wobler text="Accept" dance={games.isPushingGameProposalAccept} />
</button>
)
}
export function Reject({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Reject' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
>
<Wobler text="Reject" dance={games.isPushingGameProposalReject} />
</button>
)
}
export function Cancel({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT' ? true : '';
if (selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Cancel' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select pending GameProposal')}
>
<Wobler text="Cancel" dance={games.isPushingGameProposalCancel} />
</button>
)
}
/*
* Game actions
*/
export function Surrender({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
const isReady = (gameStatus === 'GAME_BOARD_WAIT_FOR_OPPONENT' || gameStatus === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU' || gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
return; // You shall not surrender if there is an active tie negotiations
return (
<button className={'Surrender' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select a game')}
>
<Wobler text="Surrender" dance={games.isPushingGameSurrender} />
</button>
)
}
/*
* Game actions: Draw
*/
export function DrawRequest({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
const isReady = gameStatus === 'GAME_BOARD_WAIT_FOR_YOU' ? true : '';
const checkStatus = () => {
if (!selectedGame)
return alert('You have to select a game');
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
return alert('A draw was alredy offered to the opponent');
if (!isReady)
return alert('You can ask for a draw only during your turn');
onClick(selectedGame.uuid);
}
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
return; // You can not send counter draw request
return (
<button className={'Draw' + (isReady && ' ready')} onClick={() => checkStatus()} >
<Wobler text="Draw?" dance={games.isPushingGameDrawRequest} />
</button >
)
}
export function DrawAccept({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
return (
<button className='Draw accept' onClick={() => onClick(selectedGame.uuid)} >
<Wobler text="Draw accept" dance={games.isPushingGameDrawAccept} />
</button>
)
}
export function DrawReject({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
if (selectedGame?.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
return (
<button className='Draw reject' onClick={() => onClick(selectedGame.uuid)} >
<Wobler text="Reject" dance={games.isPushingGameDrawReject} />
</button>
)
}
/*
* GameArchive actions
*/
export function Backward() {
return <button className='Backward' disabled>Backward</button>
}
export function Forward() {
return <button className='Forward' disabled>Forward</button>
}

View File

@ -9,19 +9,30 @@ export default function GameBoard() {
const games = useContext(GamesContext); const games = useContext(GamesContext);
const {pathname} = useLocation(); const {pathname} = useLocation();
var whiteName = ''; let opponentName = '';
var blackName = ''; let opponentColor = Color.white; // defaut color
if (matchPath('/games/new', pathname)) { if (matchPath('/games/new', pathname)) {
whiteName = games.newGame.whitePlayer; opponentName = games.newGame.opponentName;
blackName = games.newGame.blackPlayer; opponentColor = games.newGame.opponentColor;
} }
// if (matchPath('/games/proposal', pathname)) {
// const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
// const opponentColor = selectedGame
// whiteName = selectedGame.newGame.whitePlayer;
// blackName = games.newGame.blackPlayer;
// }
const myColor = Color.opposite(opponentColor);
return ( return (
<div className='GameBoard'> <div className='GameBoard'>
<Player color={Color.white} name={whiteName} /> <Player color={opponentColor} name={opponentName} />
<Board /> <Board />
<Player color={Color.black} name={blackName} /> <Player color={myColor} name='MyName' />
</div> </div>
) )
} }

View File

@ -18,11 +18,11 @@ export default function Message2Opponent({ dispatchGames }) {
var externalValue = ''; var externalValue = '';
if (matchPath('/games/new', pathname)) if (matchPath('/games/new', pathname))
externalValue = games.newGame.message2opponent; externalValue = games.newGame.message;
else if (matchPath('/games/proposal', pathname)) else if (matchPath('/games/proposal', pathname))
externalValue = games.proposal.message2opponent; externalValue = games.proposal.message;
else if (matchPath('/games/active', pathname)) else if (matchPath('/games/active', pathname))
externalValue = games.active.message2opponent; externalValue = games.active.message;
if (value !== externalValue) if (value !== externalValue)
setValue(externalValue); setValue(externalValue);
@ -30,17 +30,17 @@ export default function Message2Opponent({ dispatchGames }) {
/* --- */ /* --- */
const sync = (message2opponent) => { const sync = (message) => {
syncTimeoutRef.current = null; syncTimeoutRef.current = null;
if (matchPath('/games/new', pathname)) if (matchPath('/games/new', pathname))
return dispatchGames({ type: 'nextNewGame', message2opponent }); return dispatchGames({ type: 'nextNewGame', message });
if (matchPath('/games/proposal', pathname)) if (matchPath('/games/proposal', pathname))
return dispatchGames({ type: 'nextProposal', message2opponent }); return dispatchGames({ type: 'nextProposal', message });
if (matchPath('/games/active', pathname)) if (matchPath('/games/active', pathname))
return dispatchGames({ type: 'nextActive', message2opponent }); return dispatchGames({ type: 'nextActive', message });
console.warn('unknown path'); console.warn('unknown path');
} }

View File

@ -1,19 +0,0 @@
import React, { useContext } from 'react';
import Wobler from '../../../components/Wobler';
import { GamesContext } from '../../../context/games';
export default function Accept({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Accept' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
>
<Wobler text="Accept" dance={games.isPushingGameProposalAccept} />
</button>
)
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function Backward() {
return <button className='Backward' disabled>Backward</button>
}

View File

@ -1,19 +0,0 @@
import React, { useContext } from 'react';
import Wobler from '../../../components/Wobler';
import { GamesContext } from '../../../context/games';
export default function Cancel({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT' ? true : '';
if (selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Cancel' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select pending GameProposal')}
>
<Wobler text="Cancel" dance={games.isPushingGameProposalCancel} />
</button>
)
}

View File

@ -1,63 +0,0 @@
import React, { useContext } from 'react';
import { GamesContext } from '../../../context/games';
import Wobler from '../../../components/Wobler';
import { Color } from '../../../components/Checkers';
export default function Create({ isCurrentUser, onClick }) {
const games = useContext(GamesContext);
const hasPlayers = checkPlayers(games.newGame);
const hasCurrentUser = checkCurrentUser(games.newGame, isCurrentUser);
const prepareNewGameRequest = () => {
if (!hasPlayers)
return alert("Black and White players must be selected for the game");
if (!hasCurrentUser)
return alert("You must be one of the selected players");
/*
* Prepare & send NewGame request
*/
const [opponentName, opponentColor] = getOpponent(games.newGame, isCurrentUser);
const reqParams = {
opponentName,
opponentColor,
board: null, // default board configuration
message: games.newGame.message2opponent
}
onClick(reqParams);
}
return (
<button className={'Create' + (hasPlayers && hasCurrentUser ? ' ready' : '')}
onClick={prepareNewGameRequest}
>
<Wobler text="Create" dance={games.isPushingNewGame} />
</button>
)
}
function checkPlayers({ whitePlayer, blackPlayer }) {
return whitePlayer && blackPlayer
&& whitePlayer !== blackPlayer;
}
function checkCurrentUser({ whitePlayer, blackPlayer }, isCurrentUser) {
return isCurrentUser(whitePlayer) || isCurrentUser(blackPlayer);
}
function getOpponent({whitePlayer, blackPlayer}, isCurrentUser) {
if (isCurrentUser(whitePlayer)) {
return [blackPlayer, Color.black];
}
if (isCurrentUser(blackPlayer)) {
return [whitePlayer, Color.white];
}
return ['', ''];
}

View File

@ -1,60 +0,0 @@
import React, { useContext } from 'react';
import Wobler from '../../../components/Wobler';
import { GamesContext } from '../../../context/games';
export function DrawRequest({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
const isReady = gameStatus === 'GAME_BOARD_WAIT_FOR_YOU' ? true : '';
const checkStatus = () => {
if (!selectedGame)
return alert('You have to select a game');
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
return alert('A draw was alredy offered to the opponent');
if (!isReady)
return alert('You can ask for a draw only during your turn');
onClick(selectedGame.uuid);
}
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
return; // You can not send counter draw request
return (
<button className={'Draw' + (isReady && ' ready')} onClick={() => checkStatus()} >
<Wobler text="Draw?" dance={games.isPushingGameDrawRequest} />
</button >
)
}
export function DrawAccept({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
return (
<button className='Draw accept' onClick={() => onClick(selectedGame.uuid)} >
<Wobler text="Draw accept" dance={games.isPushingGameDrawAccept} />
</button>
)
}
export function DrawReject({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
if (selectedGame?.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
return (
<button className='Draw reject' onClick={() => onClick(selectedGame.uuid)} >
<Wobler text="Reject" dance={games.isPushingGameDrawReject} />
</button>
)
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function Forward() {
return <button className='Forward' disabled>Forward</button>
}

View File

@ -1,19 +0,0 @@
import React, { useContext } from 'react';
import Wobler from '../../../components/Wobler';
import { GamesContext } from '../../../context/games';
export default function Reject({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
return (
<button className={'Reject' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
>
<Wobler text="Reject" dance={games.isPushingGameProposalReject} />
</button>
)
}

View File

@ -1,22 +0,0 @@
import React, { useContext } from 'react';
import Wobler from '../../../components/Wobler';
import { GamesContext } from '../../../context/games';
export default function Surrender({ onClick }) {
const games = useContext(GamesContext);
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
const gameStatus = selectedGame?.status;
const isReady = (gameStatus === 'GAME_BOARD_WAIT_FOR_OPPONENT' || gameStatus === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU' || gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
return; // You shall not surrender if there is an active tie negotiations
return (
<button className={'Surrender' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select a game')}
>
<Wobler text="Surrender" dance={games.isPushingGameSurrender} />
</button>
)
}

View File

@ -3,11 +3,21 @@ import React, { useContext } from 'react';
import { GamesContext } from '../../../context/games'; import { GamesContext } from '../../../context/games';
import DropdownList from '../../../components/DropdownList'; import DropdownList from '../../../components/DropdownList';
import { WhiteStone, BlackStone } from '../../../components/Checkers'; import { Color, WhiteStone, BlackStone } from '../../../components/Checkers';
export default function NewGame({ players, onSelectPlayer }) { export default function NewGame({ players, setOpponent }) {
const games = useContext(GamesContext); const games = useContext(GamesContext);
const [whitePlayer, blackPlayer] = (() => {
if (games.newGame.opponentColor === Color.white)
return [games.newGame.opponentName, players.currentUser];
if (games.newGame.opponentColor === Color.black)
return [players.currentUser, games.newGame.opponentName];
return ['', ''];
})(); // <<-- Execute!
/* /*
* Name options * Name options
*/ */
@ -24,37 +34,22 @@ export default function NewGame({ players, onSelectPlayer }) {
const blackOptions = Array(nameOptions) const blackOptions = Array(nameOptions)
blackOptions.push(<option key='default' value=''>{'black player …'}</option>) blackOptions.push(<option key='default' value=''>{'black player …'}</option>)
/*
* Radiobutton
*/
const radioButton = (whitePlayer, blackPlayer) => {
if (whitePlayer !== '' && whitePlayer === games.newGame.blackPlayer) {
blackPlayer = '';
}
if (blackPlayer !== '' && blackPlayer === games.newGame.whitePlayer) {
whitePlayer = '';
}
onSelectPlayer(whitePlayer, blackPlayer);
}
const setWhitePlayer = (name) => {
radioButton(name, games.newGame.blackPlayer);
}
const setBlackPlayer = (name) => {
radioButton(games.newGame.whitePlayer, name);
}
/* /*
* The Component * The Component
*/ */
const onSelect = (name, color) => {
if (players.isCurrentUser(name))
setOpponent(games.newGame.opponentName, Color.opposite(color));
else
setOpponent(name, color);
}
return ( return (
<div className='NewGame'> <div className='NewGame'>
<WhiteStone /> <WhiteStone />
<DropdownList selected={games.newGame.whitePlayer} onSelect={setWhitePlayer} optionsList={whiteOptions} /> <DropdownList selected={whitePlayer} onSelect={(name) => onSelect(name, Color.white)} optionsList={whiteOptions} />
<i>- vs -</i> <i>- vs -</i>
<DropdownList selected={games.newGame.blackPlayer} onSelect={setBlackPlayer} optionsList={blackOptions} /> <DropdownList selected={blackPlayer} onSelect={(name) => onSelect(name, Color.black)} optionsList={blackOptions} />
<BlackStone /> <BlackStone />
</div> </div>
) )

View File

@ -5,19 +5,19 @@ export const gamesInitialState = {
gamesList: null, gamesList: null,
newGame: { newGame: {
whitePlayer: '', opponentName: '',
blackPlayer: '', opponentColor: '',
message2opponent: '', message: '',
}, },
proposal: { proposal: {
selectedUUID: null, selectedUUID: null,
message2opponent: '', message: '',
}, },
active: { active: {
selectedUUID: null, selectedUUID: null,
message2opponent: '', message: '',
}, },
archive: { archive: {

View File

@ -15,7 +15,7 @@ function reducer(state, action) {
case 'parse': case 'parse':
return { return {
...state, ...state,
username: action.userJson.username username: action.userJson.holdingIdentity.name
}; };
default: default: