diff --git a/client/src/App.css b/client/src/App.css index 22150fb..97c6b52 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -15,10 +15,26 @@ body { width: 100%; bottom: 0; flex-direction: row; - justify-content: space-between; /* left-justify 'board', right-justify 'game' */ background-image: url("./assets/tabletop.png"); } +.Table .ErrorDialog { + display: flex; + justify-content: space-around; + align-items: center; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: #00000060; +} + +.Table .ErrorDialog .Error { + display: flex; + padding: 1rem; +} + .Table .Game { display: flex; flex-direction: column; @@ -34,8 +50,6 @@ body { .Table .Hand { display: flex; position: relative; - border: 1px solid orange; - margin: 1rem; height: 11rem; } @@ -51,8 +65,6 @@ body { .Table .Board { display: flex; flex-grow: 1; - margin: 1rem; - border: 1px solid purple; } .Table .Sidebar .Chat { diff --git a/client/src/App.js b/client/src/App.js index b20a8d5..7973bc6 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -7,8 +7,8 @@ import { Routes, useParams } from "react-router-dom"; -import { base, gamesPath } from './Common.js'; -import history from "./history.js"; + +import Paper from '@material-ui/core/Paper'; import { GlobalContext } from "./GlobalContext.js"; import { PingPong } from "./PingPong.js"; @@ -17,7 +17,8 @@ import { Chat } from "./Chat.js"; import { MediaAgent } from "./MediaControl.js"; import { Board } from "./Board.js"; import { Actions } from "./Actions.js"; - +import { base, gamesPath, debounce } from './Common.js'; +import history from "./history.js"; import "./App.css"; const Table = () => { @@ -29,6 +30,7 @@ const Table = () => { const [ error, setError ] = useState(undefined); const [ peers, setPeers ] = useState({}); const [loaded, setLoaded] = useState(false); + const [connecting, setConnecting] = useState(false); useEffect(() => { console.log(peers); @@ -41,7 +43,8 @@ const Table = () => { /* We do not set the socket as bound until the 'open' message * comes through */ setWs(event.target); - + setConnecting(false); + /* Request a game-update */ event.target.send(JSON.stringify({ type: 'get', @@ -75,18 +78,32 @@ const Table = () => { } }; + const resetConnection = (function () { + let timer = 0; + function reset() { + timer = 0; + setConnecting(false); + }; + return _ => { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(reset, 5000); + }; + })(); + const onWsError = (event) => { console.log(`ws: error`, event); const error = `Connection to Ketr Ketran game server failed! ` + - `Try refreshing in a few seconds.`; + `Connection attempt will be retried every 5 seconds.`; console.error(error); + resetConnection(); setError(error); }; const onWsClose = (event) => { console.log(`ws: close`); - setWs(undefined); - global.ws = undefined; + resetConnection(); }; /* callback refs are used to provide correct state reference @@ -125,8 +142,8 @@ const Table = () => { }, }).then((res) => { if (res.status >= 400) { - const error = `Unable to connect to Ketr Ketran game server!` + - `Try refreshing in a few seconds.`; + const error = `Unable to connect to Ketr Ketran game server! ` + + `Try refreshing your browser in a few seconds.`; console.error(error); setError(error); throw error; @@ -163,6 +180,7 @@ const Table = () => { new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${gameId}`; console.log(`Attempting WebSocket connection to ${new_uri}`); socket = new WebSocket(new_uri); + setConnecting(true); } console.log('table - bind'); @@ -185,11 +203,7 @@ const Table = () => { socket.removeEventListener('message', cbMessage); } } - }, [ setWs, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]); - - if (error) { - return
{ error }
; - } + }, [ setWs, connecting, setConnecting, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]); console.log(`Loaded: ${loaded}`); @@ -198,6 +212,7 @@ const Table = () => {
+ { error &&
{ error }
}
todo: player's hand
diff --git a/client/src/Common.js b/client/src/Common.js index b8f928d..a37125d 100644 --- a/client/src/Common.js +++ b/client/src/Common.js @@ -9,9 +9,8 @@ const getPlayerName = (sessions, color) => { return null; } - function debounce(fn, ms) { - let timer + let timer; return _ => { clearTimeout(timer) timer = setTimeout(_ => { @@ -19,7 +18,7 @@ function debounce(fn, ms) { fn.apply(this, arguments) }, ms) }; -} +}; const base = process.env.PUBLIC_URL; diff --git a/server/routes/games.js b/server/routes/games.js index 4c69451..c842bc3 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -2746,29 +2746,7 @@ router.put("/:id/:action/:value?", async (req, res) => { break; - case "state": - const state = value; - if (!state) { - error = `Invalid state.`; - break; - } - if (state === game.state) { - break; - } - - switch (state) { - case "game-order": - if (game.state !== 'lobby') { - error = `You cannot start a game from other than the lobby.`; - break; - } - - addChatMessage(game, null, `${name} requested to start the game.`); - game.state = state; - break; - } - break; } return sendGame(req, res, game, error); @@ -2804,6 +2782,30 @@ const wsInactive = (game, req) => { } } +const setGameState = (game, session, state) => { + if (!state) { + return `Invalid state.`; + } + + if (!session.color) { + return `You must have an active player to start the game.`; + } + + if (state === game.state) { + return; + } + + switch (state) { + case "game-order": + if (game.state !== 'lobby') { + return `You cannot start a game from other than the lobby.`; + } + addChatMessage(game, null, `${session.name} requested to start the game.`); + game.state = state; + break; + } +} + const resetDisconnectCheck = (game, req) => { if (req.disconnectCheck) { clearTimeout(req.disconnectCheck); @@ -3122,6 +3124,15 @@ router.ws("/ws/:id", async (ws, req) => { console.log(`${id}:${getName(session)} - ${data.type} ${data.field} = ${data.value}`); update = {}; switch (data.field) { + case 'state': + error = setGameState(game, session, data.value); + if (error) { + console.warn(error); + session.ws.send(JSON.stringify({ type: 'error', error })); + break; + } + sendToPlayers(game, { state: game.state, chat }); + break; case 'color': error = setPlayerColor(game, session, data.value); if (error) { @@ -3129,6 +3140,8 @@ router.ws("/ws/:id", async (ws, req) => { session.ws.send(JSON.stringify({ type: 'error', error })); break; } + /* Can't use sendToPlayers as the player name is a top level field + * and is unique to each player */ for (let key in game.sessions) { const _session = game.sessions[key]; if (!_session.ws) { @@ -3143,7 +3156,7 @@ router.ws("/ws/:id", async (ws, req) => { await saveGame(game); break; default: - console.warn(`WARNING: Requested SET unsupported field: ${field}`); + console.warn(`WARNING: Requested SET unsupported field: ${data.field}`); break; } break;