1
0

Turn notice!

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-15 11:50:34 -07:00
parent 083e3c3a6f
commit 391e438990
6 changed files with 311 additions and 227 deletions

View File

@ -25,9 +25,3 @@
align-self: stretch;
}
.Actions button {
margin: 0.25em;
background-color: white;
border: 1px solid black !important;
}

View File

@ -19,7 +19,7 @@ body {
background-image: url("./assets/tabletop.png");
}
.Table .ErrorDialog {
.Table .Dialogs {
z-index: 10000;
display: flex;
justify-content: space-around;
@ -29,46 +29,44 @@ body {
left: 0;
bottom: 0;
right: 0;
}
.Table .Dialogs .Dialog {
display: flex;
position: absolute;
flex-shrink: 1;
flex-direction: column;
padding: 0.25rem;
left: 0;
right: 0;
top: 0;
bottom: 0;
justify-content: space-around;
align-items: center;
}
.Table .Dialogs .Dialog > div {
display: flex;
padding: 1rem;
flex-direction: column;
}
.Table .Dialogs .Dialog > div > div:first-child {
padding: 1rem;
}
.Table .Dialogs .TurnNoticeDialog {
background-color: #7a680060;
}
.Table .Dialogs .ErrorDialog {
background-color: #40000060;
}
.Table .ErrorDialog .Error {
display: flex;
flex-direction: column;
padding: 0.25rem;
}
.Table .ErrorDialog .Error > div {
display: flex;
padding: 1rem;
}
.Table .WarningDialog {
z-index: 10000;
display: flex;
justify-content: space-around;
align-items: center;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
.Table .Dialogs .WarningDialog {
background-color: #00000060;
}
.Table .WarningDialog .Warning {
display: flex;
flex-direction: column;
padding: 0.25rem;
}
.Table .WarningDialog .Warning > div {
display: flex;
padding: 1rem;
}
.Table .PlayersStatus {
z-index: 5000;
}
@ -161,3 +159,15 @@ body {
right: 0;
bottom: 0;
}
.Table button {
margin: 0.25em;
background-color: white;
border: 1px solid black !important;
}
.Table button:disabled {
opacity: 0.5;
border: 1px solid #ccc !important;
}

View File

@ -29,6 +29,7 @@ import { Winner } from "./Winner.js";
import history from "./history.js";
import "./App.css";
import equal from "fast-deep-equal";
const Table = () => {
const params = useParams();
@ -39,14 +40,14 @@ const Table = () => {
const [ warning, setWarning ] = useState(undefined);
const [ peers, setPeers ] = useState({});
const [loaded, setLoaded] = useState(false);
const [connecting, setConnecting] = useState(undefined);
const [connection, setConnection] = useState(undefined);
const [state, setState] = useState(undefined);
const [color, setColor] = useState(undefined);
const [players, setPlayers] = useState(undefined);
const [player, setPlayer] = useState(undefined);
const [setPlayers] = useState(undefined);
const [priv, setPriv] = useState(undefined);
const [buildActive, setBuildActive] = useState(false);
const [cardActive, setCardActive] = useState(undefined);
const fields = [ 'id', 'state', 'color', 'name' ];
const fields = [ 'id', 'state', 'color', 'name', 'private' ];
useEffect(() => {
console.log(`app - media-agent - peers`, peers);
@ -58,7 +59,7 @@ const Table = () => {
/* We do not set the socket as connected until the 'open' message
* comes through */
setConnecting(event.target);
setConnection(ws);
/* Request a full game-update
* We only need gameId and name for App.js, however in the event
@ -95,31 +96,22 @@ const Table = () => {
setLoaded(true);
}
console.log(`ws: message - ${data.type}`, data.update);
if ('player' in data.update) {
const player = data.update.player;
if (player.name !== name) {
console.log(`App - setting name (via player): ${data.update.name}`);
setName(data.update.name);
if ('private' in data.update && !equal(priv, data.update.private)) {
const priv = data.update.private;
if (priv.name !== name) {
console.log(`App - setting name (via private): ${priv.name}`);
setName(priv.name);
}
if (player.color !== color) {
console.log(`App - setting color (via player): ${data.update.color}`);
setColor(data.update.color);
if (priv.color !== color) {
console.log(`App - setting color (via private): ${priv.color}`);
setColor(priv.color);
}
setPriv(priv);
}
if ('players' in data.update) {
setPlayers(data.update.players);
if (color in data.update.players) {
if (player !== data.update.players[color]) {
setPlayer(data.update.players[color]);
}
} else {
if (player) {
setPlayer(undefined);
}
if (color) {
setColor(undefined);
}
}
}
if ('name' in data.update && data.update.name !== name) {
console.log(`App - setting name: ${data.update.name}`);
@ -136,21 +128,21 @@ const Table = () => {
if ('color' in data.update && data.update.color !== color) {
console.log(`App - setting color: ${color}`);
setColor(data.update.color);
if (players && players[data.update.color] !== player) {
setPlayer(players[data.update.color]);
}
}
break;
default:
break;
}
};
const sendUpdate = (update) => {
ws.send(JSON.stringify(update));
};
const cbResetConnection = useCallback(() => {
let timer = 0;
function reset() {
timer = 0;
setConnecting(undefined);
setConnection(undefined);
};
return _ => {
if (timer) {
@ -158,7 +150,7 @@ const Table = () => {
}
timer = setTimeout(reset, 5000);
};
}, [setConnecting]);
}, [setConnection]);
const resetConnection = cbResetConnection();
@ -252,7 +244,7 @@ const Table = () => {
console.log(`table - bind`);
if (!ws && !connecting) {
if (!ws && !connection) {
let loc = window.location, new_uri;
if (loc.protocol === "https:") {
new_uri = "wss";
@ -262,7 +254,7 @@ const Table = () => {
new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${gameId}`;
console.log(`Attempting WebSocket connection to ${new_uri}`);
setWs(new WebSocket(new_uri));
setConnecting(undefined);
setConnection(undefined);
return unbind;
}
@ -287,9 +279,9 @@ const Table = () => {
ws.removeEventListener('error', cbError);
ws.removeEventListener('message', cbMessage);
}
}, [ setWs, connecting, setConnecting, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]);
}, [ setWs, connection, setConnection, gameId, ws, refWsOpen, refWsMessage, refWsClose, refWsError ]);
return <GlobalContext.Provider value={{ ws: connecting, name, gameId, peers, setPeers }}>
return <GlobalContext.Provider value={{ ws: connection, name, gameId, peers, setPeers }}>
<MediaAgent/>
{ /* <PingPong/> */ }
<div className="Table">
@ -297,13 +289,20 @@ const Table = () => {
<Trade/>
<div className="Game">
<div className="Dialogs">
{ error && <div className="ErrorDialog">
{ error && <div className="Dialog ErrorDialog">
<Paper className="Error">
<div>{ error }</div>
<Button onClick={() => { setError("")}}>dismiss</Button>
</Paper>
</div> }
{ warning && <div className="WarningDialog">
{ priv && priv.turnNotice && <div className="Dialog TurnNoticeDialog">
<Paper className="TurnNotice">
<div>{ priv.turnNotice }</div>
<Button onClick={() => { sendUpdate({type: 'pass'}) }}>done</Button>
<Button onClick={() => { sendUpdate({type: 'turn-notice'}) }}>dismiss</Button>
</Paper>
</div> }
{ warning && <div className="Dialog WarningDialog">
<Paper className="Warning">
<div>{ warning }</div>
<Button onClick={() => { setWarning("")}}>dismiss</Button>

View File

@ -114,7 +114,6 @@
height: 50px;
transform-origin: 50% 0;
z-index: 11; /* Above Tile, below Corner */
pointer-events: none;
}
.Road-Shape {
@ -153,47 +152,42 @@
pointer-events: none;
}
@keyframes blink {
from {
filter: brightness(90%) drop-shadow(0 0 5px black);
opacity: 0.8;
}
to {
filter: brightness(100%) drop-shadow(0 0 10px black);
opacity: 1;
}
}
.Board .Option {
cursor: pointer;
pointer-events: all;
filter: brightness(100%) drop-shadow(0 0 10px black);
/*
opacity: 0.7;
filter: brightness(150%) drop-shadow(0 0 10px black);
transition-timing-function: ease-in-out;
animation-duration: 0.5s;
animation-name: blink;
animation-iteration-count: infinite;
animation-direction: alternate;
*/
}
.Tile-Shape:hover,
.Corner-Shape:hover,
.Road-Shape:hover {
background-color: white;
background-color: white !important;
}
.Board .Option .Tile-Shape,
.Board .Option .Corner-Shape,
.Board .Option .Road-Shape {
background-color: rgba(255, 255, 255, 0.75);
background-color: rgb(185, 185, 185);
}
.Board .Option:hover {
filter: brightness(150%) drop-shadow(0 0 5px white);
}
.Board .Option:hover {
/* opacity: 1;
filter: brightness(150%) drop-shadow(0 0 2px black);
animation-play-state: paused;
*/
}
.Robber .Pip-Shape {
top: -40px;
left: -40px;
@ -304,43 +298,3 @@
}
*/
/*
@keyframes blink {
from {
filter: brightness(90%);
opacity: 1;
}
50% {
filter: brightness(150%);
}
to {
filter: brightness(150%);
opacity: 0.5;
}
}
*/
.Board .Option:hover {
/* opacity: 1;
animation-play-state: paused;
*/
}
.Board .Option {
/*
opacity: 0.7;
transition-timing-function: ease-in-out;
animation-duration: 0.5s;
animation-name: blink;
animation-iteration-count: infinite;
animation-direction: alternate;
*/
}

View File

@ -24,6 +24,8 @@ const
borderImageWidth = (2 + 2/3) * tileImageWidth, /* 2.667 * .Tile.width */
borderImageHeight = borderImageWidth * 0.29; /* 0.29 * .Border.height */
let hack = undefined;
const Board = () => {
const { ws } = useContext(GlobalContext);
const board = useRef();
@ -57,6 +59,13 @@ const Board = () => {
console.log(`board - render ws is ${!ws ? 'NULL' : (ws.readyState === ws.OPEN ? 'OPEN' : '!OPEN')}`);
const onWsMessage = (event) => {
if (ws !== event.target) {
console.error(`Disconnect occur?`);
}
if (hack !== ws) {
console.error(`Setting hack`)
hack = ws;
}
const data = JSON.parse(event.data);
switch (data.type) {
case 'game-update':
@ -131,10 +140,12 @@ const Board = () => {
break;
}
};
const refWs = useRef(ws);
const refWsMessage = useRef(onWsMessage);
useEffect(() => { refWsMessage.current = onWsMessage; });
useEffect(() => {
if (!ws) { return; }
refWs.current = ws;
console.log('board - bind');
const cbMessage = e => refWsMessage.current(e);
ws.addEventListener('message', cbMessage);
@ -195,7 +206,6 @@ const Board = () => {
onResize();
const Tile = ({tile}) => {
const onClick = (event) => {
console.log(`Tile clicked: ${tile.index}`);
@ -214,7 +224,8 @@ const Board = () => {
}}
><div className="Tile-Shape"/></div>;
};
/*
useRef didn't work...
const staticSendCallback = (type, index) => {
ws.send(JSON.stringify({
type, index
@ -222,14 +233,40 @@ const Board = () => {
};
const refStaticSendCallback = useRef(staticSendCallback);
useEffect(() => { refStaticSendCallback.current = staticSendCallback; });
const sendPlacement = refStaticSendCallback.current;
const sendPlacement = function(...) doesn't work.
*/
/*
* const sendPlacement = useCallabck(...) doesn't work.
*
* However it is required for dependency tracking.
*/
const sendPlacement = useCallback((type, index) => {
console.log(`board - sendPlacement - ws is ${!ws ? 'NULL' : (ws.readyState === ws.OPEN ? 'OPEN' : '!OPEN')}`);
if (ws.readyState !== ws.OPEN) {
console.error(`ws is not OPEN in sendPlacement`);
}
if (ws !== hack) {
console.error(`hack and ws are different!`);
if (refWs.current === hack) {
console.log(`refWs is correct!`);
}
}
refWs.current.send(JSON.stringify({
type, index
}));
}, [ws, refWs]);
const onRoadClicked = useCallback((road) => {
console.log(`Road clicked: ${road.index}`);
sendPlacement('place-road', road.index);
}, [sendPlacement]);
const Road = ({road}) => {
const Road = useCallback(({road}) => {
return <div className="Road"
onClick={() => { onRoadClicked(road) }}
data-index={road.index}
@ -239,7 +276,7 @@ const Board = () => {
left: `${road.left}px`
}}
><div className="Road-Shape"/></div>;
};
}, [onRoadClicked]);
const onCornerClicked = useCallback((event, corner) => {
let type;

View File

@ -103,12 +103,6 @@ const games = {};
const audio = {};
const processTies = (players) => {
players.sort((A, B) => {
if (A.order === B.order) {
return B.orderRoll - A.orderRoll;
}
return B.order - A.order;
});
/* Sort the players into buckets based on their
* order, and their current roll. If a resulting
@ -119,58 +113,61 @@ const processTies = (players) => {
if (!slots[player.order]) {
slots[player.order] = [];
}
if (!(player.orderRoll in slots[player.order])) {
slots[player.order][player.orderRoll] = [];
}
slots[player.order][player.orderRoll].push(player);
slots[player.order].push(player);
});
let ties = false, order = 0;
let ties = false, position = 1;
const irstify = (position) => {
switch (position) {
case 1: return `1st`;
case 2: return `2nd`;
case 3: return `3rd`;
case 4: return `4th`;
default: return position;
}
}
console.log(`Slots: `, slots);
/* Reverse from high to low */
slots.reverse().forEach((slot) => {
slot.forEach(dice => {
if (dice.length !== 1) {
ties = true;
dice.forEach(player => {
player.orderRoll = 0;
player.order = order;
player.orderStatus = `Tied.`;
player.tied = true;
});
} else {
dice[0].order = order;
dice[0].tied = false;
dice[0].orderStatus = `Placed in ${order+1}.`;
}
order += dice.length
})
if (slot.length !== 1) {
ties = true;
slot.forEach(player => {
player.orderRoll = 0; /* Ties have to be re-rolled */
player.position = irstify(position);
player.orderStatus = `Tied for ${irstify(position)}`;
player.tied = true;
});
} else {
slot[0].tied = false;
slot[0].position = irstify(position);
slot[0].orderStatus = `Placed in ${irstify(position)}.`;
}
position += slot.length
});
return !ties;
return ties;
}
const processGameOrder = (game, player, dice) => {
if (player.orderRoll) {
return `You have already rolled for game order and are not in a tie.`;
}
player.orderRoll = dice;
player.order = player.order * 6 + dice;
const players = [];
let doneRolling = true;
for (let key in game.players) {
const tmp = game.players[key];
if (tmp.status === 'Not active') {
continue;
}
if (!tmp.orderRoll) {
if (!game.players[key].orderRoll) {
doneRolling = false;
}
players.push(tmp);
players.push(game.players[key]);
}
/* If 'doneRolling' is FALSE then there are still players to roll */
if (!doneRolling || !processTies(players)) {
if (!doneRolling) {
sendUpdateToPlayers(game, {
players: getFilteredPlayers(game),
chat: game.chat
@ -178,10 +175,25 @@ const processGameOrder = (game, player, dice) => {
return;
}
addChatMessage(game, null, `Player order set to ${players
.map((player, index) => {
return `${index+1}. ${player.name}`;
}).join(', ')}.`);
/* sort updated player.order into the array */
players.sort((A, B) => {
return B.order - A.order;
});
console.log(`Pre process ties: `, players);
if (processTies(players)) {
console.log(`${info}: There are ties in player rolls:`, players);
sendUpdateToPlayers(game, {
players: getFilteredPlayers(game),
chat: game.chat
});
return;
}
addChatMessage(game, null, `Player order set to ` +
players.map((player) => `${player.position}: ${player.name}`)
.join(', ') + `.`);
game.playerOrder = players.map(player => player.color);
game.state = 'initial-placement';
@ -210,18 +222,13 @@ const roll = (game, session) => {
name = session.name ? session.name : "Unnamed";
switch (game.state) {
case "lobby":
case "lobby": /* currently not available as roll is only after color is
* set for players */
addChatMessage(game, session, `${name} rolled ${Math.ceil(Math.random() * 6)}.`);
sendUpdateToPlayers(game, { chat: game.chat });
return;
case "game-order":
if (!player) {
return `This player is not active!`;
}
if (player.order && player.orderRoll) {
return `Player ${name} has already rolled for player order.`;
}
game.startTime = Date.now();
const dice = Math.ceil(Math.random() * 6);
addChatMessage(game, session, `${name} rolled ${dice}.`);
@ -730,14 +737,15 @@ const adminActions = (game, action, value) => {
case "pass":
let name = game.turn.name;
const next = getNextPlayer(game, name);
const next = getNextPlayerSession(game, name);
game.turn = {
name: next.player,
color: next.color
};
game.turns++;
startTurnTimer(game, next);
addChatMessage(game, null, `The admin skipped ${name}'s turn.`);
addChatMessage(game, null, `It is ${next}'s turn.`);
addChatMessage(game, null, `It is ${next.name}'s turn.`);
break;
case "kick":
@ -1065,7 +1073,7 @@ const getFirstPlayerName = (game) => {
return '';
}
const getNextPlayer = (game, name) => {
const getNextPlayerSession = (game, name) => {
let color;
for (let id in game.sessions) {
if (game.sessions[id].name === name) {
@ -1073,25 +1081,20 @@ const getNextPlayer = (game, name) => {
break;
}
}
console.log(`current player is ${color}`);
let index = game.playerOrder.indexOf(color);
console.log(`current player is ${color} ${index}`);
index = (index + 1) % game.playerOrder.length;
console.log(`current player is ${color} ${index}`);
color = game.playerOrder[index];
console.log(`current player is ${color} ${index}`);
for (let id in game.sessions) {
if (game.sessions[id].color === color) {
return game.sessions[id].player;
return game.sessions[id];
}
}
console.log(`nothing found... returning current player.`);
console.error(`getNextPlayerSession -- no player found!`);
console.log(game.players);
return player;
}
const getPrevPlayer = (game, name) => {
const getPrevPlayerSession = (game, name) => {
let color;
for (let id in game.sessions) {
if (game.sessions[id].name === name) {
@ -1103,10 +1106,11 @@ const getPrevPlayer = (game, name) => {
index = (index - 1) % game.playerOrder.length;
for (let id in game.sessions) {
if (game.sessions[id].color === game.playerOrder[index]) {
return game.sessions[id].player;
return game.sessions[id];
}
}
return player;
console.error(`getNextPlayerSession -- no player found!`);
console.log(game.players);
}
const processCorner = (game, color, cornerIndex, placedCorner) => {
@ -1834,6 +1838,44 @@ const trade = (game, session, action, offer) => {
}
}
const clearTimeNotice= (game, session) => {
if (!session.player.turnNotice) {
return `You have not been idle.`;
}
session.player.turnNotice = "";
sendUpdateToPlayer(game, session, {
private: session.player
});
};
const startTurnTimer = (game, session) => {
const timeout = 30;
console.log(`${session.id}: (Re)setting turn timer for ${session.name} to ${timeout} seconds.`);
if (game.turnTimer) {
clearTimeout(game.turnTimer);
}
game.turnTimer = setTimeout(() => {
console.log(`${session.id}: Turn timer expired for ${session.name}`);
session.player.turnNotice = 'It is still your turn.';
sendUpdateToPlayer(game, session, {
private: session.player
});
resetTurnTimer(game, session);
}, timeout * 1000);
}
const resetTurnTimer = (game, session) => {
startTurnTimer(game, session);
}
const stopTurnTimer = (game) => {
if (game.turnTimer) {
console.log(`${info}: Stopping turn timer.`);
clearTimeout(game.turnTimer);
game.turnTimer = 0;
}
}
const shuffle = (game, session) => {
const name = session.name;
@ -1861,17 +1903,21 @@ const pass = (game, session) => {
return `Robber is in action. Turn can not stop until all Robber tasks are resolved.`;
}
const next = getNextPlayer(game, session.name);
const next = getNextPlayerSession(game, session.name);
session.player.totalTime += Date.now() - session.player.turnStart;
session.player.turnNotice = "";
game.turn = {
name: next.name,
color: next.color
};
next.turnStart = Date.now();
game.turnTimer = startTurnTimer(game, next);
game.turns++;
addActivity(game, session, `${name} passed their turn.`);
addChatMessage(game, null, `It is ${next.name}'s turn.`);
sendUpdateToPlayer(game, session, {
private: session.player
});
sendUpdateToPlayers(game, {
turns: game.turns,
turn: game.turn,
@ -2406,11 +2452,9 @@ const placeRoad = (game, session, index) => {
delete game.direction;
} else {
if (game.direction === 'forward') {
next = getNextPlayer(game, session.name);
console.log(`advance from ${session.name} to `, next);
next = getNextPlayerSession(game, session.name);
} else {
next = getPrevPlayer(game, session.name);
console.log(`devance from ${session.name} to `, next);
next = getPrevPlayerSession(game, session.name);
}
}
if (next) {
@ -2418,6 +2462,7 @@ const placeRoad = (game, session, index) => {
name: next.name,
color: next.color
};
startTurnTimer(game, next);
setForSettlementPlacement(game, getValidCorners(game));
calculateRoadLengths(game, session);
addChatMessage(game, null, `It is ${next.name}'s turn to place a settlement.`);
@ -2863,6 +2908,14 @@ const setGameState = (game, session, state) => {
if (active < 2) {
return `You need at least two players to start the game.`;
}
/* Delete any non-played colors from the player map; reduces all
* code that would otherwise have to filter out players by checking
* the 'Not active' state of player.status */
for (let key in game.players) {
if (game.players[key].status !== 'Active') {
delete game.players[key];
}
}
addChatMessage(game, null, `${session.name} requested to start the game.`);
game.state = state;
@ -2968,12 +3021,14 @@ const saveGame = async (game) => {
if (reduced.keepAlive) {
delete reduced.keepAlive;
}
reducedGame.sessions[id] = reduced;
/* Do not send session-id as those are secrets */
reducedSessions.push(reduced);
}
delete reducedGame.turnTimer;
delete reducedGame.unselected;
/* Save per turn while debugging... */
@ -3062,15 +3117,15 @@ const sendUpdateToPlayers = async (game, update) => {
for (let key in game.sessions) {
const _session = game.sessions[key];
if (!_session.ws) {
continue;
console.log(`${_session.id}: -> sendUpdateToPlayers: Currently no connection.`);
} else {
_session.ws.send(message);
}
_session.ws.send(message);
}
await saveGame(game);
}
const sendUpdateToPlayer = async (game, session, update) => {
/* Ensure clearing of a field actually gets sent by setting
* undefined to 'false'
*/
@ -3093,7 +3148,12 @@ const sendUpdateToPlayer = async (game, session, update) => {
type: 'game-update',
update
});
session.ws.send(message);
if (!session.ws) {
console.log(`${session.id}: -> sendUpdateToPlayer: Currently no connection.`);
} else {
session.ws.send(message);
}
}
const getFilteredUnselected = (game) => {
@ -3216,6 +3276,7 @@ const calculatePoints = (game, update) => {
game.winner = update.winner;
game.state = 'winner';
game.waiting = [];
stopTurnTimer(game);
}
}
}
@ -3463,7 +3524,8 @@ router.ws("/ws/:id", async (ws, req) => {
case 'startTime':
case 'state':
case 'turns':
update[field] = game[field];
case 'winner':
update[field] = game[field];
break;
case 'name':
update.name = session.name;
@ -3509,13 +3571,6 @@ router.ws("/ws/:id", async (ws, req) => {
sendUpdateToPlayers(game, { chat: game.chat });
break;
case 'roll':
console.log(`${short}: <- roll:${getName(session)}`);
warning = roll(game, session);
if (warning) {
sendWarning(session, warning);
}
break;
default:
processed = false;
break;
@ -3534,7 +3589,15 @@ router.ws("/ws/:id", async (ws, req) => {
return;
}
processed = true;
switch (data.type) {
case 'roll':
console.log(`${short}: <- roll:${getName(session)}`);
warning = roll(game, session);
if (warning) {
sendWarning(session, warning);
}
break;
case 'shuffle':
console.log(`${short}: <- shuffle:${getName(session)}`);
warning = shuffle(game, session);
@ -3634,7 +3697,7 @@ router.ws("/ws/:id", async (ws, req) => {
}
break;
case 'trade':
console.log(`${short}: <- trade:${getName(session)} - ${data.action} - `, data.offer);
console.log(`${short}: <- trade:${getName(session)} - ${data.action} - `, data.offer ? data.offer : 'no trade yet');
warning = trade(game, session, data.action, data.offer);
if (warning) {
sendWarning(session, warning);
@ -3658,6 +3721,13 @@ router.ws("/ws/:id", async (ws, req) => {
});
}
break;
case 'turn-notice':
console.log(`${short}: <- turn-notice:${getName(session)}`);
warning = clearTimeNotice(game, session);
if (warning) {
sendWarning(session, warning);
}
break;
case 'goto-lobby':
console.log(`${short}: <- goto-lobby:${getName(session)}`);
warning = gotoLobby(game, session);
@ -3667,10 +3737,16 @@ router.ws("/ws/:id", async (ws, req) => {
break;
default:
console.warn(`Unsupported request: ${data.type}`);
processed = false;
break;
}
if (processed && session.color === game.turn.color) {
resetTurnTimer(game, session);
}
});
/* This will result in the node tick moving forward; if we haven't already
* setup the event handlers, a 'message' could come through prior to this
* completing */
@ -3694,6 +3770,11 @@ router.ws("/ws/:id", async (ws, req) => {
});
}
/* If the current turn player just rejoined, set their turn timer */
if (game.turn && game.turn.color === session.color) {
resetTurnTimer(game, session);
}
if (session.name) {
if (session.color) {
addChatMessage(game, null, `${session.name} has reconnected to the game.`);
@ -3774,6 +3855,9 @@ const getFilteredGameForPlayer = (game, session) => {
/* Strip out data that should not be shared with players */
delete reducedGame.developmentCards;
/* Delete the game timer */
delete reducedGame.turnTimer;
reducedGame.unselected = getFilteredUnselected(game);
return Object.assign(reducedGame, {
@ -3840,6 +3924,8 @@ const resetGame = (game) => {
active: 0
});
stopTurnTimer(game);
/* Populate the game corner and road placement data as cleared */
for (let i = 0; i < layout.corners.length; i++) {
game.placements.corners[i] = {
@ -3885,10 +3971,14 @@ const resetGame = (game) => {
shuffleArray(game.developmentCards);
/* Reset all player data */
for (let color in game.players) {
clearPlayer(game.players[color]);
}
/* Reset all player data, and add in any missing colors */
[ 'R', 'B', 'W', 'O' ].forEach(color => {
if (color in game.players) {
clearPlayer(game.players[color]);
} else {
game.players.color = newPlayer(color);
}
});
/* Ensure sessions are connected to player objects */
for (let key in game.sessions) {