corda-checkers/webapp/src/container/Games.jsx

232 lines
7.1 KiB
JavaScript

import React, { useContext, useEffect } from 'react';
import { GamesStateContext, GamesGuideContext } from '../context/games';
import { Routes, Route, Link } from 'react-router-dom';
import NewGame from './games/NewGame';
import { GameProposalSelector, ActiveGameSelector, GameArchiveSelector } from './games/GameSelector';
import { Create, Accept, Reject, Cancel, DrawRequest, DrawAccept, DrawReject, Surrender, Backward, Forward } from './games/ActionPanel';
import GameBoard from './games/GameBoard';
import { nextStone } from '../components/Checkers';
import Message2Opponent from './games/Message2Opponent';
import Counter from '../components/Counter';
import './Games.css';
export default function Games({ games, players }) {
const gamesState = games.state;
const gamesDispatchGuide = games.dispatchGuide;
useEffect(() => {
gamesDispatchGuide({ type: 'sync', gamesState });
}, [gamesState, gamesDispatchGuide]);
return (
<GamesStateContext.Provider value={gamesState} >
<GamesGuideContext.Provider value={games.guide} >
<div className='Games'>
<div className='left-side'>
<ViewSelector />
<ViewProvider dispatchGuide={gamesDispatchGuide} players={players} />
</div>
<div className='right-side'>
<ActionPanel gamesApi={games.api} />
<GameBoardRoutes dispatchGuide={gamesDispatchGuide} gamesApi={games.api} username={players.user.name} />
<Message2OpponentRoutes dispatchGuide={gamesDispatchGuide} />
</div>
</div >
</GamesGuideContext.Provider>
</GamesStateContext.Provider>
)
};
function ViewSelector() {
const guide = useContext(GamesGuideContext);
return (
<nav className='ViewSelector'>
<div className='Container' >
<Link to='new'>New</Link>
<Link to='proposal'>Proposal<Counter number={guide.awaiting.proposal} /></Link>
<Link to='active' >Active<Counter number={guide.awaiting.active} /></Link>
<Link to='archive' >Archive</Link>
</div>
</nav>
)
}
function ViewProvider({ dispatchGuide, players }) {
return (
<div className='ViewProvider'>
<Routes>
<Route path='new' element={
<NewGame
players={players}
setPlayers={(opponentName, myColor) => dispatchGuide({ type: 'nextNewGame', opponentName, myColor })}
/>
} />
<Route path='proposal' element={
<GameProposalSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', proposal: uuid })} />
} />
<Route path='active' element={
<ActiveGameSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', active: uuid })} />
} />
<Route path='archive' element={
<GameArchiveSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', archive: uuid })} />
} />
</Routes>
</div>
)
}
function ActionPanel({ gamesApi }) {
const games = useContext(GamesStateContext);
const guide = useContext(GamesGuideContext);
const fromUUID = (uuid) => (!uuid) ? [{}, null] :
[
games.find((game) => game.uuid === uuid) || {}, // game
guide.UUIDpushing[uuid] // pushing
];
return (
<div className='ActionPanel'>
<Routes>
<Route path='new' element={
<Create
getGame={() => [guide.newGame, guide.newGame.isPushing]}
onClick={(req) => gamesApi.pushNewGame(req)}
/>
} />
<Route path='proposal' element={[
<Accept key={1}
getGame={() => fromUUID(guide.selectedUUID.proposal)}
onClick={(req) => gamesApi.pushGameProposalAccept(req)}
/>,
<Reject key={2}
getGame={() => fromUUID(guide.selectedUUID.proposal)}
onClick={(req) => gamesApi.pushGameProposalReject(req)}
/>,
<Cancel key={3}
getGame={() => fromUUID(guide.selectedUUID.proposal)}
onClick={(req) => gamesApi.pushGameProposalCancel(req)}
/>
]} />
<Route path='active' element={[
<DrawRequest key={1}
getGame={() => fromUUID(guide.selectedUUID.active)}
onClick={(req) => gamesApi.pushGameDrawRequest(req)}
/>,
<DrawAccept key={2}
getGame={() => fromUUID(guide.selectedUUID.active)}
onClick={(req) => gamesApi.pushGameDrawAccept(req)}
/>,
<DrawReject key={3}
getGame={() => fromUUID(guide.selectedUUID.active)}
onClick={(req) => gamesApi.pushGameDrawReject(req)}
/>,
<Surrender key={4}
getGame={() => fromUUID(guide.selectedUUID.active)}
onClick={(req) => gamesApi.pushGameSurrender(req)} />
]} />
<Route path='archive' element={[
<Backward key={1} />,
<Forward key={2} />
]} />
</Routes>
</div>
)
}
function GameBoardRoutes({ dispatchGuide, gamesApi, username }) {
const games = useContext(GamesStateContext);
const guide = useContext(GamesGuideContext);
const fromUUID = (uuid) => (!uuid) ? [{}, null, null] :
[
games.find((game) => game.uuid === uuid) || {}, // game
guide.UUIDpushing[uuid], // pushing
guide.UUIDerror[uuid] // error (aka bad move)
];
const onStoneClick = (uuid, cellId) => {
let board = { ...guide.newGame.board };
board[cellId] = nextStone(board[cellId]);
dispatchGuide({ type: 'nextNewGame', board });
}
return (
<Routes>
<Route path='new' element={
<GameBoard
username={username}
getGame={() => [guide.newGame, null]}
onStoneClick={onStoneClick}
/>
} />
<Route path='proposal' element={
<GameBoard
username={username}
getGame={() => fromUUID(guide.selectedUUID.proposal)}
/>
} />
<Route path='active' element={
<GameBoard
username={username}
getGame={() => fromUUID(guide.selectedUUID.active)}
onStoneMove={(uuid, move) => gamesApi.pushGameMove({ uuid, move, message: guide.UUIDmessage[uuid] })}
dispatchGuide={dispatchGuide}
/>
} />
<Route path='archive' element={
<GameBoard
username={username}
getGame={() => fromUUID(guide.selectedUUID.archive)}
/>
} />
</Routes>
)
}
function Message2OpponentRoutes({ dispatchGuide }) {
const guide = useContext(GamesGuideContext);
const getMessage = (uuid) => !uuid ? undefined : // <<-- appears as inactive message field
guide.UUIDmessage[uuid] || '';
return (
<Routes>
<Route path='new' element={
<Message2Opponent
getMessage={() => guide.newGame.message}
setMessage={(message) => dispatchGuide({ type: 'nextNewGame', message })} />
} />
<Route path='active' element={
<Message2Opponent
getMessage={() => getMessage(guide.selectedUUID.active)}
setMessage={(message) => dispatchGuide({ type: 'UUIDmessage', message, uuid: guide.selectedUUID.active })} />
} />
</Routes>
)
}