UUIDprops
- guide.selectedUUID is used to determina current uuid - all the game related midifications are stored independantly per UUID - no global indicators
This commit is contained in:
parent
e017787441
commit
df431eb4f1
@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import djmil.cordacheckers.cordaclient.CordaClient;
|
import djmil.cordacheckers.cordaclient.CordaClient;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameView;
|
import djmil.cordacheckers.cordaclient.dao.GameView;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
import djmil.cordacheckers.user.User;
|
import djmil.cordacheckers.user.User;
|
||||||
|
|
||||||
|
@ -10,9 +10,6 @@ import Games from './container/Games';
|
|||||||
import Leaderboard from './container/Leaderboard';
|
import Leaderboard from './container/Leaderboard';
|
||||||
|
|
||||||
import useConfigReducer from './reducer/config';
|
import useConfigReducer from './reducer/config';
|
||||||
import useUserReducer from './reducer/user';
|
|
||||||
import useLeaderboardReducer from './reducer/leaderboard';
|
|
||||||
import useGamesReducer from './reducer/games';
|
|
||||||
|
|
||||||
import useUserApi from './api/user';
|
import useUserApi from './api/user';
|
||||||
import useLeaderboardApi from './api/leaderboard';
|
import useLeaderboardApi from './api/leaderboard';
|
||||||
@ -21,22 +18,23 @@ import useGamesApi from './api/games';
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const [config, dispatcConfig] = useConfigReducer();
|
const [config, dispatcConfig] = useConfigReducer();
|
||||||
|
|
||||||
const user = useUserApi(useUserReducer()).getUser();
|
const user = useUserApi();
|
||||||
const leaderboard = useLeaderboardApi(useLeaderboardReducer(), config).pollTable();
|
const leaderboard = useLeaderboardApi();
|
||||||
|
const games = useGamesApi();
|
||||||
|
|
||||||
|
user.api.useGetUser();
|
||||||
|
leaderboard.api.useTablePolling(config);
|
||||||
|
games.api.useGamesPolling(config);
|
||||||
|
|
||||||
|
|
||||||
const players = {
|
const players = {
|
||||||
leaderboard,
|
user: user.state,
|
||||||
currentUser: user.username,
|
leaderboard: leaderboard.state,
|
||||||
isCurrentUser: (playerName) => user?.isCurrentUser(playerName) === true ? true : null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const gamesReducer = useGamesReducer();
|
|
||||||
const gamesApi = useGamesApi(gamesReducer, config);
|
|
||||||
const games = gamesApi.pollGamesList();
|
|
||||||
|
|
||||||
const isPolling = {
|
const isPolling = {
|
||||||
games: games.isPollingGamesList,
|
games: games.guide.isPolling,
|
||||||
leaderboard: leaderboard.isPollingTable
|
leaderboard: leaderboard.guide.isPolling
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -45,7 +43,7 @@ export default function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<About />} />
|
<Route path='/' element={<About />} />
|
||||||
<Route path='/about' element={<About />} />
|
<Route path='/about' element={<About />} />
|
||||||
<Route path='/games/*' element={<Games context={{ gamesReducer, gamesApi }} players={players} />} />
|
<Route path='/games/*' element={<Games games={games} players={players} />} />
|
||||||
<Route path='/leaderboard' element={<Leaderboard players={players} />} />
|
<Route path='/leaderboard' element={<Leaderboard players={players} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
@ -1,82 +1,101 @@
|
|||||||
import { usePolling, doPushing } from '../hook/api';
|
import { usePolling, doPushing } from '../hook/api';
|
||||||
import { gamesInitialState } from '../reducer/games';
|
import { useGamesGuideReducer, useGamesStateReducer, gamesGuideTemplate } from '../reducer/games';
|
||||||
import { Color } from '../components/Checkers';
|
|
||||||
|
|
||||||
export default function useGamesApi(gamesReducer, config) {
|
export default function useGamesApi() {
|
||||||
const [games, dispatchGames] = gamesReducer;
|
const [state, dispatchState] = useGamesStateReducer();
|
||||||
|
const [guide, dispatchGuide] = useGamesGuideReducer();
|
||||||
|
|
||||||
const usePollingGamesList = () => {
|
const useGamesPolling = (config) => {
|
||||||
const onSuccess = (gamesList) => {
|
const onPolling = (isPolling) => dispatchGuide({ type: 'next', isPolling });
|
||||||
dispatchGames({ type: 'next', gamesList });
|
const onSuccess = (games) => dispatchState({ type: 'next', games });
|
||||||
|
|
||||||
|
usePolling('/api/game', { onPolling, onSuccess }, config.intervalMode(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPollingGamesList = usePolling('/api/game', onSuccess, config.intervalMode(30));
|
const pushNewGame = ({ opponentName, opponentColor, board, message }) => {
|
||||||
if (games.isPollingGamesList !== isPollingGamesList) {
|
doPushing('/api/gameproposal', 'POST', { opponentName, opponentColor, board, message }, {
|
||||||
dispatchGames({ type: 'next', isPollingGamesList });
|
onPushing: (isPushing) => dispatchGuide({ type: 'nextNewGame', isPushing }),
|
||||||
|
onSuccess: (game) => {
|
||||||
|
dispatchState({ type: 'add', game });
|
||||||
|
dispatchGuide({ type: 'nextNewGame', ...gamesGuideTemplate.newGame });
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return games;
|
const pushGameProposalAccept = ({ uuid }) => {
|
||||||
|
doPushing(`/api/gameproposal/${uuid}/accept`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'accept' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameProposalReject = ({ uuid }) => {
|
||||||
|
doPushing(`/api/gameproposal/${uuid}/reject`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'reject' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameProposalCancel = ({ uuid }) => {
|
||||||
|
doPushing(`/api/gameproposal/${uuid}/cancel`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'cancel' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameSurrender = ({ uuid }) => {
|
||||||
|
doPushing(`/api/game/${uuid}/surrender`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'surrender' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameDrawRequest = ({ uuid }) => {
|
||||||
|
doPushing(`/api/game/${uuid}/drawreq`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_request' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameDrawAccept = ({ uuid }) => {
|
||||||
|
doPushing(`/api/game/${uuid}/drawacc`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_accept' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameDrawReject = ({ uuid }) => {
|
||||||
|
doPushing(`/api/game/${uuid}/drawrej`, 'PUT', null, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'draw_reject' }),
|
||||||
|
onSuccess: (game) => dispatchState({ type: 'update', game })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushGameMove = ({ uuid, move, message }) => {
|
||||||
|
doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, {
|
||||||
|
onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && 'move' }),
|
||||||
|
onSuccess: (game) => {
|
||||||
|
dispatchState({ type: 'update', game });
|
||||||
|
dispatchGuide({ type: 'UUIDmessage', uuid, message: ''});
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pollGamesList: usePollingGamesList,
|
state,
|
||||||
|
guide,
|
||||||
pushNewGame: ({ opponentName, myColor, board, message }) => ifNot(games.isPushingNewGame) &&
|
dispatchGuide,
|
||||||
doPushing('/api/gameproposal', 'POST', { opponentName, opponentColor: Color.opposite(myColor), board, message }, {
|
api: {
|
||||||
onPushing: (isPushingNewGame) => dispatchGames({ type: 'next', isPushingNewGame }),
|
useGamesPolling,
|
||||||
onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [game, ...games.gamesList], newGame: gamesInitialState.newGame })
|
pushNewGame,
|
||||||
}),
|
pushGameProposalAccept,
|
||||||
|
pushGameProposalReject,
|
||||||
pushGameProposalCancel: ({ uuid }) => ifNot(games.isPushingGameProposalCancel) &&
|
pushGameProposalCancel,
|
||||||
doPushing(`/api/gameproposal/${uuid}/cancel`, 'PUT', null, {
|
pushGameSurrender,
|
||||||
onPushing: (isPushingGameProposalCancel) => dispatchGames({ type: 'next', isPushingGameProposalCancel }),
|
pushGameDrawRequest,
|
||||||
onSuccess: (canceledGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(canceledGame), proposal: gamesInitialState.proposal })
|
pushGameDrawAccept,
|
||||||
}),
|
pushGameDrawReject,
|
||||||
|
pushGameMove
|
||||||
pushGameProposalReject: ({ uuid }) => ifNot(games.isPushingGameProposalReject) &&
|
}
|
||||||
doPushing(`/api/gameproposal/${uuid}/reject`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameProposalReject) => dispatchGames({ type: 'next', isPushingGameProposalReject }),
|
|
||||||
onSuccess: (rejectedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(rejectedGame), proposal: gamesInitialState.proposal })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameProposalAccept: ({ uuid }) => ifNot(games.isPushingGameProposalAccept) &&
|
|
||||||
doPushing(`/api/gameproposal/${uuid}/accept`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameProposalAccept) => dispatchGames({ type: 'next', isPushingGameProposalAccept }),
|
|
||||||
onSuccess: (acceptedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(acceptedGame), proposal: gamesInitialState.proposal })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameSurrender: ({ uuid }) => ifNot(games.isPushingGameSurrender) &&
|
|
||||||
doPushing(`/api/game/${uuid}/surrender`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameSurrender) => dispatchGames({ type: 'next', isPushingGameSurrender }),
|
|
||||||
onSuccess: (finishedGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(finishedGame), active: gamesInitialState.active })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameDrawRequest: ({ uuid }) => ifNot(games.isPushingGameDrawRequest) &&
|
|
||||||
doPushing(`/api/game/${uuid}/drawreq`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameDrawRequest) => dispatchGames({ type: 'next', isPushingGameDrawRequest }),
|
|
||||||
onSuccess: (drawReqGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawReqGame), active: gamesInitialState.active })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameDrawAccept: ({ uuid }) => ifNot(games.isPushingGameDrawAccept) &&
|
|
||||||
doPushing(`/api/game/${uuid}/drawacc`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameDrawAccept) => dispatchGames({ type: 'next', isPushingGameDrawAccept }),
|
|
||||||
onSuccess: (drawAccGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawAccGame), active: gamesInitialState.active })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameDrawReject: ({ uuid }) => ifNot(games.isPushingGameDrawReject) &&
|
|
||||||
doPushing(`/api/game/${uuid}/drawrej`, 'PUT', null, {
|
|
||||||
onPushing: (isPushingGameDrawReject) => dispatchGames({ type: 'next', isPushingGameDrawReject }),
|
|
||||||
onSuccess: (drawRejGame) => dispatchGames({ type: 'next', gamesList: games.nextGame(drawRejGame), active: gamesInitialState.active })
|
|
||||||
}),
|
|
||||||
|
|
||||||
pushGameMove: ({ uuid, move, message }) => ifNot(games.isPushingGameMove) &&
|
|
||||||
doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, {
|
|
||||||
onPushing: (isPushingGameMove) => dispatchGames({ type: 'next', isPushingGameMove }),
|
|
||||||
onSuccess: (game) => dispatchGames({ type: 'next', gamesList: games.nextGame(game), active: gamesInitialState.active })
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ifNot(expression) {
|
|
||||||
return !expression;
|
|
||||||
}
|
|
@ -1,22 +1,23 @@
|
|||||||
import { usePolling } from "../hook/api";
|
import { usePolling } from "../hook/api";
|
||||||
|
import { useLeaderboardStateReducer, useLeaderboardGuideReducer } from '../reducer/leaderboard';
|
||||||
|
|
||||||
export default function useLeaderboardApi(leaderboardReducer, config) {
|
export default function useLeaderboardApi() {
|
||||||
const [leaderboard, dispatchLeaderboard] = leaderboardReducer;
|
const [state, dispatchState] = useLeaderboardStateReducer();
|
||||||
|
const [guide, dispatchGuide] = useLeaderboardGuideReducer();
|
||||||
|
|
||||||
const usePollingTable = () => {
|
const useTablePolling = (config) => {
|
||||||
const onSuccess = (table) => {
|
const onPolling = (isPolling) => dispatchGuide({ type: 'next', isPolling });
|
||||||
dispatchLeaderboard({ type: 'next', table });
|
const onSuccess = (table) => dispatchState({type: 'next', table });
|
||||||
}
|
|
||||||
|
|
||||||
const isPollingTable = usePolling('/api/leaderboard', onSuccess, config.intervalMode(300));
|
usePolling('/api/leaderboard', { onSuccess, onPolling }, config.intervalMode(300));
|
||||||
if (leaderboard.isPollingTable !== isPollingTable) {
|
|
||||||
dispatchLeaderboard({ type: 'next', isPollingTable });
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaderboard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pollTable: usePollingTable
|
state,
|
||||||
|
guide,
|
||||||
|
dispatchGuide,
|
||||||
|
api: {
|
||||||
|
useTablePolling
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,19 +1,21 @@
|
|||||||
import { usePolling } from "../hook/api";
|
import { usePolling } from "../hook/api";
|
||||||
|
import { useUserStateReducer } from "../reducer/user";
|
||||||
|
|
||||||
export default function useUserApi(userReducer) {
|
export default function useUserApi() {
|
||||||
const [user, dispatchUser] = userReducer;
|
const [state, dispatchState] = useUserStateReducer();
|
||||||
|
|
||||||
const useGetUser = () => {
|
const useGetUser = () => {
|
||||||
const onSuccess = (userJson) => {
|
const onSuccess = (userJson) => {
|
||||||
dispatchUser({ type: "parse", userJson });
|
dispatchState({ type: "parse", userJson });
|
||||||
}
|
}
|
||||||
|
|
||||||
usePolling('/api/user', onSuccess); // <<-- fetch once
|
usePolling('/api/user', { onSuccess }); // <<-- fetch once
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUser: useGetUser
|
state,
|
||||||
|
api : {
|
||||||
|
useGetUser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -74,10 +74,7 @@ export function Player({ color, name }) {
|
|||||||
export function Board({ board, flip, onStoneClick, onStoneMove }) {
|
export function Board({ board, flip, onStoneClick, onStoneMove }) {
|
||||||
const [[moveId, moveX, moveY], setMove] = useState([0, 0, 0]);
|
const [[moveId, moveX, moveY], setMove] = useState([0, 0, 0]);
|
||||||
|
|
||||||
if (!board)
|
const isInteractive = (board && (typeof onStoneClick === 'function' || typeof onStoneMove === 'function')) ? ' interactive' : '';
|
||||||
board = [];
|
|
||||||
|
|
||||||
const isInteractive = (typeof onStoneClick === 'function' || typeof onStoneMove === 'function') ? ' interactive' : '';
|
|
||||||
|
|
||||||
const WhiteTile = ({ id }) => {
|
const WhiteTile = ({ id }) => {
|
||||||
const stone = board[id];
|
const stone = board[id];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { GamesContext } from '../context/games';
|
import { GamesStateContext, GamesGuideContext } from '../context/games';
|
||||||
import { NavLink, Routes, Route } from 'react-router-dom';
|
import { NavLink, Routes, Route } from 'react-router-dom';
|
||||||
|
|
||||||
import NewGame from './games/view/NewGame';
|
import NewGame from './games/view/NewGame';
|
||||||
@ -12,67 +12,75 @@ import Counter from '../components/Counter';
|
|||||||
|
|
||||||
import './Games.css';
|
import './Games.css';
|
||||||
|
|
||||||
export default function Games({ context: { gamesReducer, gamesApi }, players }) {
|
export default function Games({ games, players }) {
|
||||||
const [games, dispatchGames] = gamesReducer;
|
const gamesState = games.state;
|
||||||
|
const gamesDispatchGuide = games.dispatchGuide;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
gamesDispatchGuide({ type: 'sync', gamesState });
|
||||||
|
}, [gamesState, gamesDispatchGuide]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GamesContext.Provider value={games} >
|
<GamesStateContext.Provider value={gamesState} >
|
||||||
|
<GamesGuideContext.Provider value={games.guide} >
|
||||||
<div className='Games'>
|
<div className='Games'>
|
||||||
|
|
||||||
<div className='left-side'>
|
<div className='left-side'>
|
||||||
<ViewSelector games={games} />
|
<ViewSelector />
|
||||||
<ViewProvider dispatchGames={dispatchGames} players={players} />
|
<ViewProvider dispatchGuide={gamesDispatchGuide} players={players} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='right-side'>
|
<div className='right-side'>
|
||||||
<ActionPanel gamesApi={gamesApi} />
|
<ActionPanel gamesApi={games.api} />
|
||||||
|
<GameBoardRoutes dispatchGuide={gamesDispatchGuide} gamesApi={games.api} username={players.user.name} />
|
||||||
|
<Message2OpponentRoutes dispatchGuide={gamesDispatchGuide} />
|
||||||
{/* <GameMessage /> */}
|
{/* <GameMessage /> */}
|
||||||
<GameBoardRoutes gamesReducer={gamesReducer} gamesApi={gamesApi} username={players.currentUser} />
|
|
||||||
<Message2Opponent dispatchGames={dispatchGames} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div >
|
</div >
|
||||||
</GamesContext.Provider>
|
</GamesGuideContext.Provider>
|
||||||
|
</GamesStateContext.Provider>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
function ViewSelector({ games }) {
|
function ViewSelector() {
|
||||||
const awaiting = countGames(games.gamesList);
|
const guide = useContext(GamesGuideContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='ViewSelector' >
|
<nav className='ViewSelector' >
|
||||||
<div className='Container' >
|
<div className='Container' >
|
||||||
<NavLink to='new'>New</NavLink>
|
<NavLink to='new'>New</NavLink>
|
||||||
<NavLink to='proposal'>Proposal<Counter number={awaiting.proposals} /></NavLink>
|
<NavLink to='proposal'>Proposal<Counter number={guide.awaiting.proposal} /></NavLink>
|
||||||
<NavLink to='active' >Active<Counter number={awaiting.active} /></NavLink>
|
<NavLink to='active' >Active<Counter number={guide.awaiting.active} /></NavLink>
|
||||||
<NavLink to='archive' >Archive</NavLink>
|
<NavLink to='archive' >Archive</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewProvider({ dispatchGames, players }) {
|
function ViewProvider({ dispatchGuide, players }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='ViewProvider'>
|
<div className='ViewProvider'>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
||||||
<Route path='new' element={
|
<Route path='new' element={
|
||||||
<NewGame setPlayers={(opponentName, myColor) => dispatchGames({ type: 'nextNewGame', opponentName, myColor })}
|
<NewGame
|
||||||
players={players}
|
players={players}
|
||||||
|
setPlayers={(opponentName, myColor) => dispatchGuide({ type: 'nextNewGame', opponentName, myColor })}
|
||||||
/>
|
/>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path='proposal' element={
|
<Route path='proposal' element={
|
||||||
<GameProposalSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextProposal', selectedUUID })} />
|
<GameProposalSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', proposal: uuid })} />
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path='active' element={
|
<Route path='active' element={
|
||||||
<ActiveGameSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextActive', selectedUUID })} />
|
<ActiveGameSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', active: uuid })} />
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path='archive' element={
|
<Route path='archive' element={
|
||||||
<GameArchiveSelector onSelect={(selectedUUID) => dispatchGames({ type: 'nextArchive', selectedUUID })} />
|
<GameArchiveSelector onSelect={(uuid) => dispatchGuide({ type: 'selectedUUID', archive: uuid })} />
|
||||||
} />
|
} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
@ -81,25 +89,57 @@ function ViewProvider({ dispatchGames, players }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ActionPanel({ gamesApi }) {
|
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 (
|
return (
|
||||||
<div className='ActionPanel'>
|
<div className='ActionPanel'>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
||||||
<Route path='new' element={
|
<Route path='new' element={
|
||||||
<Create onClick={(reqParams) => gamesApi.pushNewGame(reqParams)} />
|
<Create
|
||||||
|
getGame={() => [guide.newGame, guide.newGame.isPushing]}
|
||||||
|
onClick={(req) => gamesApi.pushNewGame(req)}
|
||||||
|
/>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path='proposal' element={[
|
<Route path='proposal' element={[
|
||||||
<Accept key={1} onClick={(uuid) => gamesApi.pushGameProposalAccept({ uuid })} />,
|
<Accept key={1}
|
||||||
<Reject key={2} onClick={(uuid) => gamesApi.pushGameProposalReject({ uuid })} />,
|
getGame={() => fromUUID(guide.selectedUUID.proposal)}
|
||||||
<Cancel key={3} onClick={(uuid) => gamesApi.pushGameProposalCancel({ uuid })} />
|
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={[
|
<Route path='active' element={[
|
||||||
<DrawRequest key={1} onClick={(uuid) => gamesApi.pushGameDrawRequest({ uuid })} />,
|
<DrawRequest key={1}
|
||||||
<DrawAccept key={2} onClick={(uuid) => gamesApi.pushGameDrawAccept({ uuid })} />,
|
getGame={() => fromUUID(guide.selectedUUID.active)}
|
||||||
<DrawReject key={3} onClick={(uuid) => gamesApi.pushGameDrawReject({ uuid })} />,
|
onClick={(req) => gamesApi.pushGameDrawRequest(req)}
|
||||||
<Surrender key={4} onClick={(uuid) => gamesApi.pushGameSurrender({ uuid })} />
|
/>,
|
||||||
|
<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={[
|
<Route path='archive' element={[
|
||||||
@ -112,50 +152,80 @@ function ActionPanel({ gamesApi }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function GameBoardRoutes({ gamesReducer, gamesApi, username }) {
|
function GameBoardRoutes({ dispatchGuide, gamesApi, username }) {
|
||||||
const [games, dispatchGames] = gamesReducer;
|
const games = useContext(GamesStateContext);
|
||||||
|
const guide = useContext(GamesGuideContext);
|
||||||
|
|
||||||
|
const fromUUID = (uuid) => (!uuid) ? [{}, null] :
|
||||||
|
[
|
||||||
|
games.find((game) => game.uuid === uuid) || {}, // game
|
||||||
|
guide.UUIDpushing[uuid] // pushing
|
||||||
|
];
|
||||||
|
|
||||||
const onStoneClick = (uuid, cellId) => {
|
const onStoneClick = (uuid, cellId) => {
|
||||||
let board = { ...games.newGame.board };
|
let board = { ...guide.newGame.board };
|
||||||
board[cellId] = nextStone(board[cellId]);
|
board[cellId] = nextStone(board[cellId]);
|
||||||
dispatchGames({ type: 'nextNewGame', board });
|
dispatchGuide({ type: 'nextNewGame', board });
|
||||||
}
|
}
|
||||||
|
|
||||||
const onStoneMove = (uuid, move) => gamesApi.pushGameMove({ uuid, move, message: games.active.message });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='new' element={<GameBoard username={username} onStoneClick={onStoneClick} />} />
|
|
||||||
<Route path='proposal' element={<GameBoard username={username} />} />
|
<Route path='new' element={
|
||||||
<Route path='active' element={<GameBoard username={username} onStoneMove={onStoneMove} />} />
|
<GameBoard
|
||||||
<Route path='archive' element={<GameBoard username={username} />} />
|
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] })}
|
||||||
|
/>
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path='archive' element={
|
||||||
|
<GameBoard
|
||||||
|
username={username}
|
||||||
|
getGame={() => fromUUID(guide.selectedUUID.archive)}
|
||||||
|
/>
|
||||||
|
} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function countGames(gamesList) {
|
function Message2OpponentRoutes({ dispatchGuide }) {
|
||||||
|
const guide = useContext(GamesGuideContext);
|
||||||
|
|
||||||
var awaiting = {
|
const getMessage = (uuid) => !uuid ? undefined : // <<-- appears as inactive message field
|
||||||
proposals: 0,
|
guide.UUIDmessage[uuid] || '';
|
||||||
active: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!gamesList)
|
return (
|
||||||
return awaiting;
|
<Routes>
|
||||||
|
|
||||||
for (const game of gamesList) {
|
<Route path='new' element={
|
||||||
switch (game.status) {
|
<Message2Opponent
|
||||||
case 'GAME_PROPOSAL_WAIT_FOR_YOU':
|
getMessage={() => guide.newGame.message}
|
||||||
awaiting.proposals++;
|
setMessage={(message) => dispatchGuide({ type: 'nextNewGame', message })} />
|
||||||
break;
|
} />
|
||||||
case 'GAME_BOARD_WAIT_FOR_YOU':
|
|
||||||
case 'DRAW_REQUEST_WAIT_FOR_YOU':
|
|
||||||
awaiting.active++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return awaiting;
|
<Route path='active' element={
|
||||||
|
<Message2Opponent
|
||||||
|
getMessage={() => getMessage(guide.selectedUUID.active)}
|
||||||
|
setMessage={(message) => dispatchGuide({ type: 'UUIDmessage', message, uuid: guide.selectedUUID.active })} />
|
||||||
|
} />
|
||||||
|
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,26 +1,22 @@
|
|||||||
import './Leaderboard.css';
|
import './Leaderboard.css';
|
||||||
import React from "react"
|
import React from 'react';
|
||||||
import Loading from '../components/Loading';
|
|
||||||
|
|
||||||
export default function Leaderboard({ players }) {
|
export default function Leaderboard({ players }) {
|
||||||
|
const tableRows = Object.keys(players.leaderboard).map(name => {
|
||||||
|
var rank = players.leaderboard[name];
|
||||||
|
|
||||||
const table = players.leaderboard.table;
|
return (
|
||||||
if (table == null)
|
<tr key={name} className={players.user.isCurrentUser(name) && 'currentuser'}>
|
||||||
return <Loading />
|
<td>{name}</td>
|
||||||
|
|
||||||
const tableRows = Object.keys(table).map(playerName => {
|
|
||||||
var rank = table[playerName];
|
|
||||||
|
|
||||||
return <tr key={playerName} className={players.isCurrentUser(playerName) && 'currentuser'}>
|
|
||||||
<td>{playerName}</td>
|
|
||||||
<td>{rank.total}</td>
|
<td>{rank.total}</td>
|
||||||
<td>{rank.victory}</td>
|
<td>{rank.victory}</td>
|
||||||
<td>{rank.draw}</td>
|
<td>{rank.draw}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Leaderboard">
|
<div className='Leaderboard'>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { GamesContext } from '../../context/games';
|
import { Color } from '../../components/Checkers';
|
||||||
import Wobler from '../../components/Wobler';
|
import Wobler from '../../components/Wobler';
|
||||||
import './ActionPanel.css'
|
import './ActionPanel.css'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NewGame actoins
|
* NewGame actoins
|
||||||
*/
|
*/
|
||||||
export function Create({ onClick }) {
|
export function Create({ getGame, onClick }) {
|
||||||
const games = useContext(GamesContext);
|
const [game, isPushing] = getGame();
|
||||||
|
const hasPlayers = (game.opponentName && game.myColor) ? true : '';
|
||||||
|
|
||||||
const hasPlayers = games.newGame.opponentName && games.newGame.myColor;
|
const validate = () => {
|
||||||
|
|
||||||
const validateNewGame = () => {
|
|
||||||
if (!hasPlayers)
|
if (!hasPlayers)
|
||||||
return alert("You have to select an opponent");
|
return alert("You have to select an opponent");
|
||||||
|
|
||||||
onClick(games.newGame);
|
if (isPushing)
|
||||||
|
return; // busy
|
||||||
|
|
||||||
|
onClick({
|
||||||
|
opponentName: game.opponentName,
|
||||||
|
opponentColor: Color.opposite(game.myColor), // TODO: by fixing this i can simply return GAME
|
||||||
|
board: game.board,
|
||||||
|
message: game.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={'Create' + (hasPlayers ? ' ready' : '')}
|
<button className={'Create' + (hasPlayers && ' ready')} onClick={() => validate()} >
|
||||||
onClick={validateNewGame}
|
<Wobler text="Create" dance={isPushing} />
|
||||||
>
|
|
||||||
<Wobler text="Create" dance={games.isPushingNewGame} />
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -30,51 +35,71 @@ export function Create({ onClick }) {
|
|||||||
/*
|
/*
|
||||||
* GameProposal actions
|
* GameProposal actions
|
||||||
*/
|
*/
|
||||||
|
export function Accept({ getGame, onClick }) {
|
||||||
|
const [game, isPushing] = getGame();
|
||||||
|
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU') ? true : '';
|
||||||
|
|
||||||
export function Accept({ onClick }) {
|
const validate = () => {
|
||||||
const games = useContext(GamesContext);
|
if (!hasGame)
|
||||||
|
return alert('You have to select pending GameProposal');
|
||||||
|
|
||||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
if (isPushing)
|
||||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
|
return; // busy
|
||||||
|
|
||||||
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
onClick({ uuid: game.uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||||
return (
|
return (
|
||||||
<button className={'Accept' + (isReady && ' ready')}
|
<button className={'Accept' + (hasGame && ' ready')} onClick={() => validate()}>
|
||||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
|
<Wobler text="Accept" dance={isPushing === 'accept'} />
|
||||||
>
|
|
||||||
<Wobler text="Accept" dance={games.isPushingGameProposalAccept} />
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Reject({ onClick }) {
|
export function Reject({ getGame, onClick }) {
|
||||||
const games = useContext(GamesContext);
|
const [game, isPushing] = getGame();
|
||||||
|
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU') ? true : '';
|
||||||
|
|
||||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
const validate = () => {
|
||||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_YOU' ? true : '';
|
if (!hasGame)
|
||||||
|
return alert('You have to select some GameProposal');
|
||||||
|
|
||||||
if (selectedGame?.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
if (isPushing)
|
||||||
|
return; // busy
|
||||||
|
|
||||||
|
onClick({ uuid: game.uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||||
return (
|
return (
|
||||||
<button className={'Reject' + (isReady && ' ready')}
|
<button className={'Reject' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some GameProposal')}
|
<Wobler text="Reject" dance={isPushing === 'reject'} />
|
||||||
>
|
|
||||||
<Wobler text="Reject" dance={games.isPushingGameProposalReject} />
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Cancel({ onClick }) {
|
export function Cancel({ getGame, onClick }) {
|
||||||
const games = useContext(GamesContext);
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID });
|
if (game.status !== 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||||
const isReady = selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT' ? true : '';
|
return;
|
||||||
|
|
||||||
|
const hasGame = (game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT') ? true : '';
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (!hasGame)
|
||||||
|
return alert('You have to select pending GameProposal');
|
||||||
|
|
||||||
|
if (isPushing)
|
||||||
|
return; // busy
|
||||||
|
|
||||||
|
onClick({ uuid: game.uuid });
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
|
||||||
return (
|
return (
|
||||||
<button className={'Cancel' + (isReady && ' ready')}
|
<button className={'Cancel' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select pending GameProposal')}
|
<Wobler text="Cancel" dance={isPushing === 'cancel'} />
|
||||||
>
|
|
||||||
<Wobler text="Cancel" dance={games.isPushingGameProposalCancel} />
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -82,83 +107,82 @@ export function Cancel({ onClick }) {
|
|||||||
/*
|
/*
|
||||||
* Game actions
|
* Game actions
|
||||||
*/
|
*/
|
||||||
|
export function Surrender({ getGame, onClick }) {
|
||||||
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
export function Surrender({ onClick }) {
|
if (game.status === 'DRAW_REQUEST_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||||
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; // You shall not surrender if there is an active tie negotiations
|
||||||
|
|
||||||
|
const hasGame = (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (!hasGame)
|
||||||
|
return alert('You have to select a Game');
|
||||||
|
|
||||||
|
if (isPushing)
|
||||||
|
return; // busy
|
||||||
|
|
||||||
|
onClick({ uuid: game.uuid });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={'Surrender' + (isReady && ' ready')}
|
<button className={'Surrender' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||||
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select a game')}
|
<Wobler text="Surrender" dance={isPushing === 'surrender'} />
|
||||||
>
|
</ button>
|
||||||
<Wobler text="Surrender" dance={games.isPushingGameSurrender} />
|
|
||||||
</button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Game actions: Draw
|
* Game actions: Draw
|
||||||
*/
|
*/
|
||||||
|
export function DrawRequest({ getGame, onClick }) {
|
||||||
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
export function DrawRequest({ onClick }) {
|
if (game.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||||
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; // You can not send counter draw request
|
||||||
|
|
||||||
|
const hasGame = (game.status === 'GAME_BOARD_WAIT_FOR_YOU') ? true : '';
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (!hasGame)
|
||||||
|
return alert('You can ask for a draw only during your turn in a Game');
|
||||||
|
|
||||||
|
if (game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT')
|
||||||
|
return alert('A draw was alredy offered to the opponent');
|
||||||
|
|
||||||
|
onClick({ uuid: game.uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={'Draw' + (isReady && ' ready')} onClick={() => checkStatus()} >
|
<button className={'Draw' + (hasGame && ' ready')} onClick={() => validate()} >
|
||||||
<Wobler text="Draw?" dance={games.isPushingGameDrawRequest} />
|
<Wobler text="Draw?" dance={isPushing === 'draw_request'} />
|
||||||
</button >
|
</button >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DrawAccept({ onClick }) {
|
export function DrawAccept({ getGame, onClick }) {
|
||||||
const games = useContext(GamesContext);
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
if (game.status !== 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||||
const gameStatus = selectedGame?.status;
|
return;
|
||||||
|
|
||||||
if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
|
||||||
return (
|
return (
|
||||||
<button className='Draw accept' onClick={() => onClick(selectedGame.uuid)} >
|
<button className='Draw accept' onClick={() => onClick({ uuid: game.uuid })} >
|
||||||
<Wobler text="Draw accept" dance={games.isPushingGameDrawAccept} />
|
<Wobler text="Draw accept" dance={isPushing === 'draw_accept'} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DrawReject({ onClick }) {
|
export function DrawReject({ getGame, onClick }) {
|
||||||
const games = useContext(GamesContext);
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
const selectedGame = games.findGame({ uuid: games.active.selectedUUID });
|
if (game.status !== 'DRAW_REQUEST_WAIT_FOR_YOU')
|
||||||
|
return;
|
||||||
|
|
||||||
if (selectedGame?.status === 'DRAW_REQUEST_WAIT_FOR_YOU')
|
|
||||||
return (
|
return (
|
||||||
<button className='Draw reject' onClick={() => onClick(selectedGame.uuid)} >
|
<button className='Draw reject' onClick={() => onClick({ uuid: game.uuid })}>
|
||||||
<Wobler text="Reject" dance={games.isPushingGameDrawReject} />
|
<Wobler text="Reject" dance={isPushing === 'draw_reject'} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -166,13 +190,10 @@ export function DrawReject({ onClick }) {
|
|||||||
/*
|
/*
|
||||||
* GameArchive actions
|
* GameArchive actions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Backward() {
|
export function Backward() {
|
||||||
|
|
||||||
return <button className='Backward' disabled>Backward</button>
|
return <button className='Backward' disabled>Backward</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Forward() {
|
export function Forward() {
|
||||||
|
|
||||||
return <button className='Forward' disabled>Forward</button>
|
return <button className='Forward' disabled>Forward</button>
|
||||||
}
|
}
|
@ -1,12 +1,10 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { useLocation, matchPath } from 'react-router-dom';
|
|
||||||
import { GamesContext } from '../../context/games';
|
|
||||||
|
|
||||||
import { Color, Player, Board } from '../../components/Checkers';
|
import { Color, Player, Board } from '../../components/Checkers';
|
||||||
import './GameBoard.css';
|
import './GameBoard.css';
|
||||||
|
|
||||||
export default function GameBoard({ username, onStoneClick, onStoneMove }) {
|
export default function GameBoard({ username, getGame, onStoneClick, onStoneMove }) {
|
||||||
const game = useSelectedGame() || {};
|
const [game, isPushing] = getGame();
|
||||||
|
|
||||||
const myName = game.opponentName ? username : '';
|
const myName = game.opponentName ? username : '';
|
||||||
const opponentColor = Color.opposite(game.myColor);
|
const opponentColor = Color.opposite(game.myColor);
|
||||||
@ -15,37 +13,26 @@ export default function GameBoard({ username, onStoneClick, onStoneMove }) {
|
|||||||
const optionalOnStoneClick = (typeof onStoneClick !== 'function') ? null :
|
const optionalOnStoneClick = (typeof onStoneClick !== 'function') ? null :
|
||||||
(cellId) => onStoneClick(game.uuid, cellId);
|
(cellId) => onStoneClick(game.uuid, cellId);
|
||||||
|
|
||||||
const optionalOnStoneMove = (typeof onStoneMove !== 'function') ? null :
|
const optionalOnStoneMove = (typeof onStoneMove !== 'function' || isPushing) ? null :
|
||||||
(move) => onStoneMove(game.uuid, move);
|
(move) => onStoneMove(game.uuid, move);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='GameBoard'>
|
<div className='GameBoard'>
|
||||||
<Player color={opponentColor || Color.black} name={game.opponentName} />
|
<Player
|
||||||
<Board board={game.board} flip={flipBoard}
|
color={opponentColor || Color.black}
|
||||||
|
name={game.opponentName}
|
||||||
|
/>
|
||||||
|
<Board
|
||||||
|
board={game.board || []}
|
||||||
|
flip={flipBoard}
|
||||||
onStoneClick={optionalOnStoneClick}
|
onStoneClick={optionalOnStoneClick}
|
||||||
onStoneMove={optionalOnStoneMove}
|
onStoneMove={optionalOnStoneMove}
|
||||||
/>
|
/>
|
||||||
<Player color={game.myColor || Color.white} name={myName} />
|
<Player
|
||||||
{game.isPushingGameMove ? <span>Moving...</span> : null /* TODO: isPushing shall be stored per game. curernty it is global indicator */}
|
color={game.myColor || Color.white}
|
||||||
|
name={myName}
|
||||||
|
/>
|
||||||
|
{(isPushing === 'move') ? <span>Moving...</span> : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSelectedGame() {
|
|
||||||
const games = useContext(GamesContext);
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
if (matchPath('/games/new', pathname))
|
|
||||||
return games.newGame;
|
|
||||||
|
|
||||||
if (matchPath('/games/proposal', pathname))
|
|
||||||
return games.findGame({ uuid: games.proposal.selectedUUID });
|
|
||||||
|
|
||||||
if (matchPath('/games/active', pathname))
|
|
||||||
return games.findGame({ uuid: games.active.selectedUUID });
|
|
||||||
|
|
||||||
if (matchPath('/games/archive', pathname))
|
|
||||||
return games.findGame({ uuid: games.archive.selectedUUID });
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
@ -1,65 +1,38 @@
|
|||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { useLocation, matchPath } from 'react-router-dom';
|
|
||||||
import { GamesContext } from '../../context/games';
|
|
||||||
|
|
||||||
export default function Message2Opponent({ dispatchGames }) {
|
export default function Message2Opponent({ getMessage, setMessage }) {
|
||||||
const games = useContext(GamesContext);
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const syncTimeoutRef = useRef(null);
|
const syncTimeoutRef = useRef(null);
|
||||||
|
|
||||||
if (matchPath('/games/archive', pathname))
|
const message = getMessage();
|
||||||
return; // Shhh.. no chatting in the archives!
|
if (value !== message && syncTimeoutRef.current === null) {
|
||||||
|
// Absorb external value ONLY if there is no scheduled sync
|
||||||
if (matchPath('/games/proposal', pathname))
|
setValue(message);
|
||||||
return; // TODO: Enable GameProposal messages, as soon as it has been supported by the server side
|
|
||||||
|
|
||||||
if (syncTimeoutRef.current === null) { // <<--- Absorb external value ONLY if there is no scheduled sync
|
|
||||||
var externalValue = '';
|
|
||||||
|
|
||||||
if (matchPath('/games/new', pathname))
|
|
||||||
externalValue = games.newGame.message;
|
|
||||||
else if (matchPath('/games/proposal', pathname))
|
|
||||||
externalValue = games.proposal.message;
|
|
||||||
else if (matchPath('/games/active', pathname))
|
|
||||||
externalValue = games.active.message;
|
|
||||||
|
|
||||||
if (value !== externalValue)
|
|
||||||
setValue(externalValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const disabled = message === undefined;
|
||||||
|
|
||||||
/* --- */
|
/* --- */
|
||||||
|
|
||||||
const sync = (message) => {
|
const sync = (nextMessage) => {
|
||||||
syncTimeoutRef.current = null;
|
syncTimeoutRef.current = null;
|
||||||
|
setMessage(nextMessage);
|
||||||
if (matchPath('/games/new', pathname))
|
|
||||||
return dispatchGames({ type: 'nextNewGame', message });
|
|
||||||
|
|
||||||
if (matchPath('/games/proposal', pathname))
|
|
||||||
return dispatchGames({ type: 'nextProposal', message });
|
|
||||||
|
|
||||||
if (matchPath('/games/active', pathname))
|
|
||||||
return dispatchGames({ type: 'nextActive', message });
|
|
||||||
|
|
||||||
console.warn('unknown path');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = (value) => {
|
const update = (value) => {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
|
|
||||||
if (syncTimeoutRef.current)
|
|
||||||
clearTimeout(syncTimeoutRef.current); // <<--- Cancel previous sync
|
clearTimeout(syncTimeoutRef.current); // <<--- Cancel previous sync
|
||||||
|
|
||||||
syncTimeoutRef.current = setTimeout(() => sync(value), 500);
|
syncTimeoutRef.current = setTimeout(() => sync(value), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input className='Message2Opponent'
|
<input className='Message2Opponent'
|
||||||
placeholder='Message'
|
placeholder='Message'
|
||||||
value={value}
|
disabled={disabled}
|
||||||
|
value={value || ''}
|
||||||
maxLength={150}
|
maxLength={150}
|
||||||
onChange={e => update(e.target.value)}
|
onChange={(e) => update(e.target.value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,17 +1,16 @@
|
|||||||
import './GameSelector.css';
|
import './GameSelector.css';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { GamesContext } from '../../../context/games';
|
import { GamesStateContext, GamesGuideContext } from '../../../context/games';
|
||||||
|
|
||||||
import { Board, Color, Player } from '../../../components/Checkers';
|
import { Board, Color, Player } from '../../../components/Checkers';
|
||||||
import Loading from '../../../components/Loading';
|
import Loading from '../../../components/Loading';
|
||||||
import Counter from '../../../components/Counter';
|
import Counter from '../../../components/Counter';
|
||||||
|
|
||||||
export function GameProposalSelector({ onSelect }) {
|
export function GameProposalSelector({ onSelect }) {
|
||||||
const games = useContext(GamesContext);
|
const games = useContext(GamesStateContext);
|
||||||
if (games.gamesList === null)
|
const guide = useContext(GamesGuideContext);
|
||||||
return <Loading />
|
|
||||||
|
|
||||||
const isSelected = (uuid) => uuid === games.proposal.selectedUUID;
|
const isSelected = (uuid) => uuid === guide.selectedUUID.proposal;
|
||||||
|
|
||||||
const onClick = (uuid) => {
|
const onClick = (uuid) => {
|
||||||
if (isSelected(uuid))
|
if (isSelected(uuid))
|
||||||
@ -20,12 +19,19 @@ export function GameProposalSelector({ onSelect }) {
|
|||||||
onSelect(uuid);
|
onSelect(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const yoursList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU')
|
const yoursList = games.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_YOU')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const opponentsList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
const opponentsList = games.filter(game => game.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
|
if (yoursList.length === 0 && opponentsList.length === 0) {
|
||||||
|
if (guide.isPolling)
|
||||||
|
return <Loading />
|
||||||
|
else
|
||||||
|
return <>There are no pending GameProposals..</>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='GameSelector'>
|
<div className='GameSelector'>
|
||||||
{yoursList}
|
{yoursList}
|
||||||
@ -36,11 +42,10 @@ export function GameProposalSelector({ onSelect }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ActiveGameSelector({ onSelect }) {
|
export function ActiveGameSelector({ onSelect }) {
|
||||||
const games = useContext(GamesContext);
|
const games = useContext(GamesStateContext);
|
||||||
if (games.gamesList === null)
|
const guide = useContext(GamesGuideContext);
|
||||||
return <Loading />
|
|
||||||
|
|
||||||
const isSelected = (uuid) => uuid === games.active.selectedUUID;
|
const isSelected = (uuid) => uuid === guide.selectedUUID.active;
|
||||||
|
|
||||||
const onClick = (uuid) => {
|
const onClick = (uuid) => {
|
||||||
if (isSelected(uuid))
|
if (isSelected(uuid))
|
||||||
@ -49,12 +54,19 @@ export function ActiveGameSelector({ onSelect }) {
|
|||||||
onSelect(uuid);
|
onSelect(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const yoursList = games.gamesList.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_YOU'))
|
const yoursList = games.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_YOU' || game.status === 'DRAW_REQUEST_WAIT_FOR_YOU'))
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const opponentsList = games.gamesList.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT'))
|
const opponentsList = games.filter(game => (game.status === 'GAME_BOARD_WAIT_FOR_OPPONENT' || game.status === 'DRAW_REQUEST_WAIT_FOR_OPPONENT'))
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
|
if (yoursList.length === 0 && opponentsList.length === 0) {
|
||||||
|
if (guide.isPolling)
|
||||||
|
return <Loading />
|
||||||
|
else
|
||||||
|
return <>There are no pending Games..</>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='GameSelector'>
|
<div className='GameSelector'>
|
||||||
{yoursList}
|
{yoursList}
|
||||||
@ -65,11 +77,10 @@ export function ActiveGameSelector({ onSelect }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GameArchiveSelector({ onSelect }) {
|
export function GameArchiveSelector({ onSelect }) {
|
||||||
const games = useContext(GamesContext);
|
const games = useContext(GamesStateContext);
|
||||||
if (games.gamesList === null)
|
const guide = useContext(GamesGuideContext);
|
||||||
return <Loading />
|
|
||||||
|
|
||||||
const isSelected = (uuid) => uuid === games.archive.selectedUUID;
|
const isSelected = (uuid) => uuid === guide.selectedUUID.archive;
|
||||||
|
|
||||||
const onClick = (uuid) => {
|
const onClick = (uuid) => {
|
||||||
if (isSelected(uuid))
|
if (isSelected(uuid))
|
||||||
@ -78,21 +89,28 @@ export function GameArchiveSelector({ onSelect }) {
|
|||||||
onSelect(uuid);
|
onSelect(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rejectedList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_REJECTED')
|
const rejectedList = games.filter(game => game.status === 'GAME_PROPOSAL_REJECTED')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const canceledList = games.gamesList.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
|
const canceledList = games.filter(game => game.status === 'GAME_PROPOSAL_CANCELED')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const victoryList = games.gamesList.filter(game => game.status === 'GAME_RESULT_YOU_WON')
|
const victoryList = games.filter(game => game.status === 'GAME_RESULT_YOU_WON')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const defeatList = games.gamesList.filter(game => game.status === 'GAME_RESULT_YOU_LOOSE')
|
const defeatList = games.filter(game => game.status === 'GAME_RESULT_YOU_LOOSE')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
const drawList = games.gamesList.filter(game => game.status === 'GAME_RESULT_DRAW')
|
const drawList = games.filter(game => game.status === 'GAME_RESULT_DRAW')
|
||||||
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
.map(game => <Selectable game={game} key={game.uuid} selected={isSelected(game.uuid)} onClick={onClick} />)
|
||||||
|
|
||||||
|
if (rejectedList.length === 0 && canceledList.length === 0 && victoryList.length === 0 && defeatList.length === 0 && drawList.length === 0) {
|
||||||
|
if (guide.isPolling)
|
||||||
|
return <Loading />
|
||||||
|
else
|
||||||
|
return <>Finished Games will be shown here..</>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='GameSelector'>
|
<div className='GameSelector'>
|
||||||
{rejectedList.length > 0 && <Separator counter={rejectedList.length}>rejected proposals</Separator>}
|
{rejectedList.length > 0 && <Separator counter={rejectedList.length}>rejected proposals</Separator>}
|
||||||
@ -119,9 +137,15 @@ function Selectable({ game, selected, onClick }) {
|
|||||||
<div className={'Selectable' + (selected ? ' selected' : '')}
|
<div className={'Selectable' + (selected ? ' selected' : '')}
|
||||||
onClick={() => onClick(game.uuid)}
|
onClick={() => onClick(game.uuid)}
|
||||||
>
|
>
|
||||||
<Board board={game.board} flip={flipBoard} />
|
<Board
|
||||||
|
board={game.board || []}
|
||||||
|
flip={flipBoard}
|
||||||
|
/>
|
||||||
<div className='Message'>
|
<div className='Message'>
|
||||||
<Player color={opponentColor} name={opponentName} />
|
<Player
|
||||||
|
color={opponentColor}
|
||||||
|
name={opponentName}
|
||||||
|
/>
|
||||||
<q>{game.message}</q>
|
<q>{game.message}</q>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import './NewGame.css'
|
import './NewGame.css'
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { GamesContext } from '../../../context/games';
|
import { GamesGuideContext } from '../../../context/games';
|
||||||
|
|
||||||
import DropdownList from '../../../components/DropdownList';
|
import DropdownList from '../../../components/DropdownList';
|
||||||
import { Color, WhiteStone, BlackStone } from '../../../components/Checkers';
|
import { Color, WhiteStone, BlackStone } from '../../../components/Checkers';
|
||||||
|
|
||||||
export default function NewGame({ players, setPlayers }) {
|
export default function NewGame({ players, setPlayers }) {
|
||||||
const games = useContext(GamesContext);
|
const newGame = useContext(GamesGuideContext).newGame;
|
||||||
|
|
||||||
const [whitePlayer, blackPlayer] = (() => {
|
const [whitePlayer, blackPlayer] = (() => {
|
||||||
if (games.newGame.myColor === Color.white)
|
if (newGame.myColor === Color.white)
|
||||||
return [players.currentUser, games.newGame.opponentName];
|
return [players.user.name, newGame.opponentName];
|
||||||
|
|
||||||
if (games.newGame.myColor === Color.black)
|
if (newGame.myColor === Color.black)
|
||||||
return [games.newGame.opponentName, players.currentUser];
|
return [newGame.opponentName, players.user.name];
|
||||||
|
|
||||||
return ['', ''];
|
return ['', ''];
|
||||||
})(); // <<-- Execute!
|
})(); // <<-- Execute!
|
||||||
@ -21,25 +21,24 @@ export default function NewGame({ players, setPlayers }) {
|
|||||||
/*
|
/*
|
||||||
* Name options
|
* Name options
|
||||||
*/
|
*/
|
||||||
const nameOptions = !players.leaderboard.table
|
const nameOptions = !players.leaderboard ? [<option key='loading' value='…'>…loading</option>] :
|
||||||
? [<option key='loading' value='…'>…loading</option>]
|
Object.keys(players.leaderboard).map(name =>
|
||||||
: Object.keys(players.leaderboard.table).map(playerName =>
|
<option key={name} value={name}>
|
||||||
<option key={playerName} value={playerName}>
|
{players.user.isCurrentUser(name) ? 'You' : name}
|
||||||
{players.isCurrentUser(playerName) ? 'You' : playerName}
|
|
||||||
</option>)
|
</option>)
|
||||||
|
|
||||||
const whiteOptions = Array(nameOptions)
|
const whiteOptions = Array(nameOptions);
|
||||||
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>)
|
whiteOptions.push(<option key='default' value=''>{'white player …'}</option>);
|
||||||
|
|
||||||
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>);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Component
|
* The Component
|
||||||
*/
|
*/
|
||||||
const onSelect = (name, myColor) => {
|
const onSelect = (name, myColor) => {
|
||||||
if (players.isCurrentUser(name))
|
if (players.user.isCurrentUser(name))
|
||||||
setPlayers(games.newGame.opponentName, myColor);
|
setPlayers(newGame.opponentName, myColor);
|
||||||
else
|
else
|
||||||
setPlayers(name, Color.opposite(myColor));
|
setPlayers(name, Color.opposite(myColor));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export const GamesContext = createContext(null);
|
export const GamesStateContext = createContext(null);
|
||||||
|
export const GamesGuideContext = createContext(null);
|
||||||
|
|
||||||
// export const Games = React.createContext({
|
// export const Games = React.createContext({
|
||||||
// state: initialState,
|
// state: initialState,
|
||||||
|
@ -8,7 +8,7 @@ import { useState, useRef, useCallback, useEffect, } from "react"
|
|||||||
- interval_stop
|
- interval_stop
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function usePolling(uri, onSuccess, mode = null) {
|
export function usePolling(uri, { onSuccess, onPolling }, mode = null) {
|
||||||
const [isPolling, setPolling] = useState(false);
|
const [isPolling, setPolling] = useState(false);
|
||||||
|
|
||||||
const initialPollRef = useRef(true);
|
const initialPollRef = useRef(true);
|
||||||
@ -19,11 +19,16 @@ export function usePolling(uri, onSuccess, mode = null) {
|
|||||||
|
|
||||||
const pollData = useCallback(() => {
|
const pollData = useCallback(() => {
|
||||||
setPolling(true);
|
setPolling(true);
|
||||||
|
if (onPolling)
|
||||||
|
onPolling(true);
|
||||||
|
|
||||||
initialPollRef.current = false;
|
initialPollRef.current = false;
|
||||||
|
|
||||||
fetch(uri)
|
fetch(uri)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setPolling(false);
|
setPolling(false);
|
||||||
|
if (onPolling)
|
||||||
|
onPolling(false);
|
||||||
|
|
||||||
if (typeof mode?.interval_sec === 'number') {
|
if (typeof mode?.interval_sec === 'number') {
|
||||||
console.log("Schedule", uri, "fetch in", mode.interval_sec, "sec");
|
console.log("Schedule", uri, "fetch in", mode.interval_sec, "sec");
|
||||||
@ -38,7 +43,7 @@ export function usePolling(uri, onSuccess, mode = null) {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.warn(err.message);
|
console.warn(err.message);
|
||||||
})
|
})
|
||||||
}, [uri, mode, onSuccess, initialPollRef, intervalTimerIdRef]);
|
}, [uri, mode, onSuccess, onPolling, initialPollRef, intervalTimerIdRef]);
|
||||||
|
|
||||||
const stopPollInterval = useCallback(() => {
|
const stopPollInterval = useCallback(() => {
|
||||||
console.log("Cancel scheduled fetch for", uri);
|
console.log("Cancel scheduled fetch for", uri);
|
||||||
@ -55,8 +60,6 @@ export function usePolling(uri, onSuccess, mode = null) {
|
|||||||
stopPollInterval();
|
stopPollInterval();
|
||||||
}
|
}
|
||||||
}, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]);
|
}, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]);
|
||||||
|
|
||||||
return isPolling;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
|
export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
|
||||||
@ -83,7 +86,7 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) {
|
|||||||
|
|
||||||
onSuccess(content);
|
onSuccess(content);
|
||||||
}
|
}
|
||||||
// } catch (err) {
|
// } catch (err) {
|
||||||
} finally {
|
} finally {
|
||||||
if (onPushing)
|
if (onPushing)
|
||||||
onPushing(false);
|
onPushing(false);
|
||||||
|
@ -2,91 +2,143 @@ import { useReducer } from 'react';
|
|||||||
import { nextState } from '../util/StateHelper';
|
import { nextState } from '../util/StateHelper';
|
||||||
import { defaultBoard } from '../components/Checkers';
|
import { defaultBoard } from '../components/Checkers';
|
||||||
|
|
||||||
export const gamesInitialState = {
|
/*
|
||||||
gamesList: null,
|
* State
|
||||||
|
*/
|
||||||
|
const gameTemplate = {
|
||||||
|
status: '',
|
||||||
|
myColor: '',
|
||||||
|
opponentName: '',
|
||||||
|
|
||||||
|
board: null,
|
||||||
|
moveNumber: 0,
|
||||||
|
previousMove: [],
|
||||||
|
|
||||||
|
message: '',
|
||||||
|
uuid: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const gamesStateTemplate = [/* gameTemplate */];
|
||||||
|
|
||||||
|
function gamesStateReducer(state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case 'next':
|
||||||
|
return action.games;
|
||||||
|
|
||||||
|
case 'add':
|
||||||
|
return [
|
||||||
|
...state,
|
||||||
|
nextState(gameTemplate, action.game, 'Game.create')
|
||||||
|
];
|
||||||
|
|
||||||
|
case 'update':
|
||||||
|
return state.map((game) =>
|
||||||
|
game.uuid !== action.game.uuid ? game :
|
||||||
|
nextState(gameTemplate, action.game, 'Game.update')
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Error('GamesState: unknown action.type', action.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGamesStateReducer() {
|
||||||
|
return useReducer(gamesStateReducer, gamesStateTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Guide
|
||||||
|
*/
|
||||||
|
export const gamesGuideTemplate = {
|
||||||
newGame: {
|
newGame: {
|
||||||
opponentName: '',
|
opponentName: '',
|
||||||
myColor: '',
|
myColor: '',
|
||||||
board: defaultBoard,
|
board: defaultBoard,
|
||||||
message: '',
|
message: '',
|
||||||
|
isPushing: false
|
||||||
},
|
},
|
||||||
|
|
||||||
proposal: {
|
awaiting: {
|
||||||
selectedUUID: null,
|
proposal: 0,
|
||||||
message: '',
|
active: 0
|
||||||
},
|
},
|
||||||
|
|
||||||
active: {
|
selectedUUID: {
|
||||||
selectedUUID: null,
|
proposal: null,
|
||||||
message: '',
|
active: null,
|
||||||
|
archive: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
archive: {
|
UUIDmessage: { // UUIDmessage[uuid]
|
||||||
selectedUUID: null,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Network
|
UUIDpushing: { // UUIDpushing[uuid]
|
||||||
isPollingGamesList: false,
|
},
|
||||||
isPushingNewGame: false,
|
|
||||||
|
|
||||||
isPushingGameProposalCancel: false,
|
isPolling: false,
|
||||||
isPushingGameProposalReject: false,
|
|
||||||
isPushingGameProposalAccept: false,
|
|
||||||
|
|
||||||
isPushingGameSurrender: false,
|
|
||||||
isPushingGameDrawRequest: false,
|
|
||||||
isPushingGameDrawAccept: false,
|
|
||||||
isPushingGameDrawReject: false,
|
|
||||||
isPushingGameMove: false,
|
|
||||||
|
|
||||||
findGame,
|
|
||||||
nextGame,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function reducer(state, action) {
|
function gamesGuideReducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case 'next':
|
case 'next':
|
||||||
return nextState(state, action);
|
return nextState(state, action, 'GamesGuide');
|
||||||
|
|
||||||
|
case 'sync':
|
||||||
|
//console.log('sync');
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
awaiting: calcAwating(action.gamesState)
|
||||||
|
}
|
||||||
|
|
||||||
case 'nextNewGame':
|
case 'nextNewGame':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
newGame: nextState(state.newGame, action)
|
newGame: nextState(state.newGame, action, 'GamesGuide.newGame')
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'nextProposal':
|
case 'selectedUUID':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
proposal: nextState(state.proposal, action)
|
selectedUUID: nextState(state.selectedUUID, action, 'GamesGuide.selectedUUID')
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'nextActive':
|
case 'UUIDmessage': {
|
||||||
return {
|
const next = { ...state };
|
||||||
...state,
|
next.UUIDmessage[action.uuid] = action.message;
|
||||||
active: nextState(state.active, action)
|
return next;
|
||||||
};
|
}
|
||||||
|
|
||||||
case 'nextArchive':
|
case 'UUIDpushing': {
|
||||||
return {
|
const next = { ...state };
|
||||||
...state,
|
next.UUIDpushing[action.uuid] = action.what
|
||||||
archive: nextState(state.archive, action)
|
return next;
|
||||||
};
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Error('GamesReducer: unknown action.type', action.type);
|
throw Error('GamesGuide: unknown action.type: ' + action.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useGamesReducer() {
|
//const uuidRegex = new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}");
|
||||||
return useReducer(reducer, gamesInitialState);
|
|
||||||
|
export function useGamesGuideReducer() {
|
||||||
|
return useReducer(gamesGuideReducer, gamesGuideTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findGame({ uuid }) {
|
function calcAwating(games) {
|
||||||
return this.gamesList?.find((game) => game.uuid === uuid);
|
return games.reduce((awaiting, game) => {
|
||||||
}
|
switch (game.status) {
|
||||||
|
case 'GAME_PROPOSAL_WAIT_FOR_YOU':
|
||||||
function nextGame(nextGame) {
|
awaiting.proposal++;
|
||||||
return this.gamesList?.map((game) => (game.uuid === nextGame?.uuid) ? nextGame : game);
|
return awaiting;
|
||||||
|
case 'GAME_BOARD_WAIT_FOR_YOU':
|
||||||
|
case 'DRAW_REQUEST_WAIT_FOR_YOU':
|
||||||
|
awaiting.active++;
|
||||||
|
return awaiting;
|
||||||
|
default:
|
||||||
|
return awaiting;
|
||||||
|
}
|
||||||
|
}, structuredClone(gamesGuideTemplate.awaiting));
|
||||||
}
|
}
|
@ -1,24 +1,48 @@
|
|||||||
import { useReducer } from 'react';
|
import { useReducer } from 'react';
|
||||||
import { nextState } from '../util/StateHelper';
|
import { nextState } from '../util/StateHelper';
|
||||||
|
|
||||||
const initialState = {
|
/*
|
||||||
table: null,
|
* State
|
||||||
|
*/
|
||||||
// Network
|
const stateTemplate = {
|
||||||
isPollingTable: false
|
// name : { rank }
|
||||||
|
// Bobik: {total: 10, victory 5: draw: 1}
|
||||||
};
|
};
|
||||||
|
|
||||||
function reducer(state, action) {
|
function stateReducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case 'next':
|
case 'next':
|
||||||
return nextState(state, action);
|
return action.table;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Error('LeaderboardReducer: unknown action.type', action.type);
|
throw Error('LeaderboardState: unknown action.type ' +action.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useLeaderboardReducer() {
|
export function useLeaderboardStateReducer() {
|
||||||
return useReducer(reducer, initialState);
|
return useReducer(stateReducer, stateTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Guide
|
||||||
|
*/
|
||||||
|
const guideTemplate = {
|
||||||
|
|
||||||
|
isPolling: false
|
||||||
|
}
|
||||||
|
|
||||||
|
function guideReducer(state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case 'next':
|
||||||
|
return nextState(state, action, 'LeaderboardGuide');
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Error('LeaderboardGuide: unknown action.type ' +action.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLeaderboardGuideReducer() {
|
||||||
|
return useReducer(guideReducer, guideTemplate);
|
||||||
}
|
}
|
@ -1,28 +1,31 @@
|
|||||||
import { useReducer } from 'react';
|
import { useReducer } from 'react';
|
||||||
import { localeCompare } from '../util/Locale';
|
import { localeCompare } from '../util/Locale';
|
||||||
|
|
||||||
const initialState = {
|
/*
|
||||||
username: '',
|
* State
|
||||||
|
*/
|
||||||
|
const stateTemplate = {
|
||||||
|
name: '',
|
||||||
|
|
||||||
isCurrentUser: function (otherUsername) {
|
isCurrentUser: function (otherName) {
|
||||||
return localeCompare(this.username, otherUsername)
|
return localeCompare(this.name, otherName) || null; // true -or- null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function reducer(state, action) {
|
function stateReducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case 'parse':
|
case 'parse':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
username: action.userJson.holdingIdentity.name
|
name: action.userJson.holdingIdentity.name
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Error('UserReducer: unknown action.type', action.type);
|
throw Error('UserState: unknown action.type ' +action.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useUserReducer() {
|
export function useUserStateReducer() {
|
||||||
return useReducer(reducer, initialState);
|
return useReducer(stateReducer, stateTemplate);
|
||||||
}
|
}
|
@ -1,17 +1,24 @@
|
|||||||
export function nextState(state, delta) {
|
export function nextState(state, delta, coment) {
|
||||||
const nextState = { ...state };
|
const nextState = { ...state };
|
||||||
|
|
||||||
Object.keys(delta)
|
let logMsg = '';
|
||||||
.slice(1) // skip first property i.e. 'next'
|
Object.keys(delta).forEach(key => {
|
||||||
.forEach(key => {
|
if (key === 'type')
|
||||||
if (Object.hasOwn(nextState, key)) {
|
return;
|
||||||
console.log("next [", key, "] = ", delta[key]);
|
|
||||||
|
if (nextState.hasOwnProperty(key)) {
|
||||||
|
if (coment)
|
||||||
|
logMsg += '\n ' + key + ': ' + delta[key];
|
||||||
|
|
||||||
nextState[key] = delta[key];
|
nextState[key] = delta[key];
|
||||||
} else {
|
} else {
|
||||||
console.warn("nextState: bad action property\n", key + ":", delta[key]);
|
console.warn("nextState: bad action property\n", key + ":", delta[key]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (coment)
|
||||||
|
console.log('Next', coment, logMsg);
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user