1
0

Updating to working state

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-01-28 21:52:55 -08:00
parent 7de9b3ab64
commit fd2795892f
3 changed files with 328 additions and 264 deletions

View File

@ -209,9 +209,7 @@ const Chat = ({ board, promoteGameState }) => {
console.log(`Send: ${event.target.value}`); console.log(`Send: ${event.target.value}`);
promoteGameState({ promoteGameState({
chat: { chat: {
player: board.game.activePlayer ? player: board.game.color ? board.game.color : undefined,
board.game.activePlayer :
undefined,
message: event.target.value message: event.target.value
} }
}); });
@ -246,7 +244,7 @@ const Chat = ({ board, promoteGameState }) => {
{ messages } { messages }
</List> </List>
<TextField className="chatInput" <TextField className="chatInput"
disabled={!board.game.activePlayerName} disabled={!board.game.name}
onChange={chatInput} onChange={chatInput}
onKeyPress={chatKeyPress} onKeyPress={chatKeyPress}
label={(<Moment format="h:mm:ss" interval={1000}/>)} variant="outlined"/> label={(<Moment format="h:mm:ss" interval={1000}/>)} variant="outlined"/>
@ -283,7 +281,7 @@ const Action = ({ board }) => {
} }
const PlayerName = ({board}) => { const PlayerName = ({board}) => {
const [name, setName] = useState((board && board.game && board.game.activePlayerName) ? board.game.activePlayerName : ""); const [name, setName] = useState((board && board.game && board.game.name) ? board.game.name : "");
const nameChange = (event) => { const nameChange = (event) => {
setName(event.target.value); setName(event.target.value);
@ -292,7 +290,7 @@ const PlayerName = ({board}) => {
const nameKeyPress = (event) => { const nameKeyPress = (event) => {
if (event.key === "Enter") { if (event.key === "Enter") {
console.log(`Send: ${name}`); console.log(`Send: ${name}`);
if (name != board.game.activePlayerName) { if (name != board.game.name) {
board.setPlayerName(name); board.setPlayerName(name);
} }
} }
@ -312,38 +310,48 @@ const PlayerName = ({board}) => {
); );
}; };
const getPlayerName = (sessions, color) => {
for (let i = 0; i < sessions.length; i++) {
const session = sessions[i];
if (session.color === color) {
return session.name;
}
}
return null;
}
/* This needs to take in a mechanism to declare the /* This needs to take in a mechanism to declare the
* player's active item in the game */ * player's active item in the game */
const Players = ({ board }) => { const Players = ({ board }) => {
const selected = board.game && board.game.activePlayer ? board.game.activePlayer : "";
const toggleSelected = (key) => { const toggleSelected = (key) => {
board.setSelected(selected === key ? "" : key); board.setSelected(board.game.color === key ? "" : key);
} }
const classes = useStyles(); const classes = useStyles();
const players = []; const players = [];
for (let key in board.game.players) { console.log(`Player ${board.game.name} is ${board.game.color}`);
const item = board.game.players[key]; for (let color in board.game.players) {
const item = board.game.players[color];
if (board.game.state !== "lobby" && item.status === 'Not active') { if (board.game.state !== "lobby" && item.status === 'Not active') {
continue; continue;
} }
const name = getPlayerName(board.game.sessions, color);
item.lastActive = item.lastActive > Date.now() ? Date.now() : item.lastActive; item.lastActive = item.lastActive > Date.now() ? Date.now() : item.lastActive;
players.push(( players.push((
<ListItem key={`player-${key}`}> <ListItem key={`player-${color}`}>
<ListItemAvatar> <ListItemAvatar>
<Avatar className={classes[key]}>{key}</Avatar> <Avatar className={classes[color]}>{color}</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={item.name} secondary={( <ListItemText primary={name} secondary={(
<> <>
{ item.status + ' ' } { item.status + ' ' }
{ item.status != 'Not active' && <Moment fromNow date={item.lastActive}/>} { item.status != 'Not active' && <Moment fromNow date={item.lastActive}/>}
</>)} /> </>)} />
{ (item.status === 'Not active' || selected === key) && { (item.status === 'Not active' || board.game.color === color) &&
<Switch edge="end" <Switch edge="end"
checked={selected === key} checked={board.game.color === color}
onChange={() => toggleSelected(key)}/> onChange={() => toggleSelected(color)}/>
} }
</ListItem> </ListItem>
)); ));
@ -449,13 +457,7 @@ class Board extends React.Component {
return res.json(); return res.json();
}).then((game) => { }).then((game) => {
let message; let message;
if (game.status == 'success') { if (game.status !== 'success') {
if (key) {
message = `Player selected ${key}!`;
} else {
message = `Player deselected ${game.activePlayer}!`;
}
} else {
message = game.status; message = game.status;
} }
@ -490,13 +492,7 @@ class Board extends React.Component {
return res.json(); return res.json();
}).then((game) => { }).then((game) => {
let message; let message;
if (game.status == 'success') { if (game.status !== 'success') {
if (name) {
message = `Player name set to ${name}`;
} else {
message = `Player name cleared.`;
}
} else {
message = game.status; message = game.status;
} }
this.updateGame(game); this.updateGame(game);
@ -561,9 +557,10 @@ class Board extends React.Component {
} }
return res.json(); return res.json();
}).then((game) => { }).then((game) => {
let message = game.status != "success" ? game.status : "Dice rolled!" let message;
if (game.status != "success") { if (game.status !== "success") {
game.dice = []; game.dice = [];
message = game.status;
} }
this.updateGame(game); this.updateGame(game);
this.setState({ game: { ...this.state.game, dice: game.dice }, message: message } ); this.setState({ game: { ...this.state.game, dice: game.dice }, message: message } );
@ -1323,10 +1320,10 @@ class Board extends React.Component {
<div className="Cards" ref={el => this.cards = el}> <div className="Cards" ref={el => this.cards = el}>
{ game && { game &&
<div className={'Game ' + game.state}> <div className={'Game ' + game.state}>
{game.activePlayer === "" && <PlayerName board={this}/> } {!game.color && <PlayerName board={this}/> }
<Players board={this} promoteGameState={this.promoteGameState}/> <Players board={this} promoteGameState={this.promoteGameState}/>
<Chat board={this} promoteGameState={this.promoteGameState}/> <Chat board={this} promoteGameState={this.promoteGameState}/>
{ game.activePlayer !== "" && { game.color &&
<Action board={this}/> <Action board={this}/>
} }
{ this.state.message != "" && <Paper><div style={{align:"left",fontSize:"8pt"}}>{this.state.message}</div></Paper> } { this.state.message != "" && <Paper><div style={{align:"left",fontSize:"8pt"}}>{this.state.message}</div></Paper> }

View File

@ -1,15 +1,15 @@
#!/bin/bash #!/bin/bash
ADMIN=$(jq .admin config/local.json) ADMIN=$(jq -r .admin config/local.json)
if [[ "${ADMIN}" == "" ]]; then if [[ "${ADMIN}" == "" ]]; then
echo "You need to set your { 'admin': 'secret' } in config/local.json" echo "You need to set your { 'admin': 'secret' } in config/local.json"
exit 1 exit 1
fi fi
id=$1 id=$1
user=$2 color=$2
if [[ "${id}" == "" ]] || [[ "${user}" == "" ]]; then if [[ "${id}" == "" ]] || [[ "${color}" == "" ]]; then
echo "Usage: kick GAME-ID USER-NAME" echo "Usage: kick GAME-ID red|white|blue|orange"
exit 1 exit 1
fi fi
@ -17,6 +17,6 @@ curl --noproxy '*' -s -L \
--request PUT \ --request PUT \
--header "PRIVATE-TOKEN: ${ADMIN}" \ --header "PRIVATE-TOKEN: ${ADMIN}" \
--header "Content-Type: application/json" \ --header "Content-Type: application/json" \
http://localhost:8930/ketr.ketran/api/v1/games/${id}/kick/${user} | http://localhost:8930/ketr.ketran/api/v1/games/${id}/kick/${color} |
jq .status jq -r .status

View File

@ -1,11 +1,7 @@
"use strict"; "use strict";
const express = require("express"), const express = require("express"),
config = require("config"),
moment = require("moment"),
crypto = require("crypto"), crypto = require("crypto"),
util = require("util"),
Promise = require("bluebird"),
{ readFile, writeFile } = require("fs").promises; { readFile, writeFile } = require("fs").promises;
let gameDB; let gameDB;
@ -100,34 +96,68 @@ for (let i = 0; i < 5; i++) {
const games = {}; const games = {};
const roll = (game, player) => { const roll = (game, session) => {
let error; let message;
if (!player) {
error = "No player active; roll has no action";
console.log(error);
return error;
}
const name = player.name; const player = session.player,
name = session.name ? session.name : "Unnamed";
switch (game.state) { switch (game.state) {
case "lobby": case "lobby":
if (player.order) { return `Rolling dice in the lobby is not allowed!`;
error = `Player ${name} already rolled for order.`;
console.log(error); case "game-order":
return error; if (player.order) {
} retrun = `Player ${name} already rolled for order.`;
game.dice = [ Math.ceil(Math.random() * 6) ]; }
player.order = game.dice[0]; player.order = game.dice[0];
const message = `${name} rolled ${game.dice[0]} for play order.`; message = `${name} rolled ${game.dice[0]} for play order.`;
game.chat.push({ date: Date.now(), message: message }); game.chat.push({ date: Date.now(), message: message });
console.log(message); break;
return;
case "in-game":
game.dice = [ Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6) ];
message = `${name} rolled ${game.dice[0] + game.dice[1]}.`;
game.chat.push({ date: Date.now(), message: message });
return;
default:
return `Invalid game state (${game.state}) in roll.`;
}
};
const getPlayer = (game, color) => {
if (!game) {
return {
roads: 15,
cities: 4,
settlements: 5,
points: 0,
status: "Not active",
lastActive: 0,
order: 0
};
} }
error = `Invalid game state (${game.state}) in roll.`; return game.players[color];
return error; };
}
const getSession = (game, session) => {
if (!game.sessions) {
game.sessions = {};
}
/* If this session is not yet in the game,
* add it and set the player's name */
if (!(session in game.sessions)) {
game.sessions[session] = {
name: undefined,
player: undefined
};
}
return game.sessions[session];
};
const loadGame = async (id) => { const loadGame = async (id) => {
if (/^\.|\//.exec(id)) { if (/^\.|\//.exec(id)) {
@ -138,27 +168,165 @@ const loadGame = async (id) => {
return games[id]; return games[id];
} }
const game = await readFile(`games/${id}`) let game = await readFile(`games/${id}`)
.catch(() => { .catch(() => {
return; return;
}); });
if (!game) { if (!game) {
games[id] = createGame(id); game = createGame(id);
} else { } else {
try { try {
games[id] = JSON.parse(game); game = JSON.parse(game);
} catch (error) { } catch (error) {
console.error(error, game); console.error(error, game);
return null; return null;
} }
} }
return games[id]; /* Reconnect session player colors to the player objects */
for (let id in game.sessions) {
const session = game.sessions[id];
if (session.color && session.color in game.players) {
session.player = game.players[session.color];
} else {
session.color = undefined;
session.player = undefined;
}
}
games[id] = game;
return game;
};
const adminActions = (game, action, value) => {
switch (action) {
case "kick":
let color;
switch (value) {
case 'orange': color = 'O'; break;
case 'red': color = 'R'; break;
case 'blue': color = 'B'; break;
case 'white': color = 'W'; break;
}
if (!color) {
return `Unable to find player ${value}`
}
const player = game.players[color];
for (let id in game.sessions) {
const session = game.sessions[id];
if (session.player !== player) {
continue;
}
console.log(`Kicking ${value} from ${game.id}.`);
const name = session.name ? `${session.name} (${color})` : color;
game.chat.push({
date: Date.now(),
message: `${name} has been kicked from game.`
});
session.player = undefined;
return;
}
return `Unable to find active session for ${color} (${value})`;
default:
return `Invalid admin action ${action}.`;
}
};
const setPlayerName = (game, session, name) => {
if (session.color) {
return `You cannot change your name while you are in game.`;
}
/* Check to ensure name is not already in use */
if (game && name) for (let key in game.sessions) {
const tmp = game.sessions[key];
if (tmp.name && tmp.name.toLowerCase() === name.toLowerCase()) {
return `${name} is already taken.`;
}
}
const old = session.name ? session.name : "Unknown";
let message;
session.name = name;
if (name) {
message = `${old} is now known as ${name}.`;
} else {
message = `${old} no longer has a name.`;
}
game.chat.push({
date: Date.now(),
message: message
});
}
const setPlayerColor = (game, session, color) => {
if (!game) {
return `No game found`;
}
const name = session.name, player = session.player;
/* Selecting the same color is a NO-OP */
if (session.color === color) {
return;
}
if (player) {
/* Deselect currently active player for this session */
player.status = 'Not active';
player.lastActive = 0;
game.chat.push({
date: Date.now(),
message: `${session.color} is no longer claimed.`
});
session.player = undefined;
session.color = undefined;
}
/* Verify the player has a name set */
if (!name) {
return `You may only select a player when you have set your name.`;
}
/* If the player is not selecting a color, then return */
if (!color) {
return;
}
/* Verify selection is valid */
if (!(color in game.players)) {
return `An invalid player selection was attempted.`;
}
/* Verify selection is not already taken */
for (let key in game.sessions) {
const tmp = game.sessions[key].player;
if (tmp && tmp.color === color) {
return `${game.sessions[key].name} already has ${color}`;
}
}
/* All good -- set this player to requested selection */
session.player = getPlayer(game, color);
session.player.status = `Active`;
session.player.lastActive = Date.now();
session.color = color;
game.chat.push({
date: Date.now(),
message: `${color} is now '${session.name}'.`
});
}; };
router.put("/:id/:action/:value?", async (req, res) => { router.put("/:id/:action/:value?", async (req, res) => {
const { action, id, value } = req.params; const { action, id } = req.params,
value = req.params.value ? req.params.value : "";
console.log(`PUT games/${id}/${action}/${value}`); console.log(`PUT games/${id}/${action}/${value}`);
const game = await loadGame(id); const game = await loadGame(id);
@ -167,159 +335,38 @@ router.put("/:id/:action/:value?", async (req, res) => {
return res.status(404).send(error); return res.status(404).send(error);
} }
const color = req.session && req.session.playerColor in game.players ?
req.session.playerColor : undefined;
let player;
let error; let error;
if ('private-token' in req.headers) { if ('private-token' in req.headers) {
if (req.headers['private-token'] !== req.app.get('admin')) { if (req.headers['private-token'] !== req.app.get('admin')) {
error = `Invalid admin credentials.`; error = `Invalid admin credentials.`;
}
switch (action) {
case "kick":
error = `Unable to find player ${value}`
for (let color in game.players) {
player = game.players[color];
if (player.name.toLowerCase() === value.toLowerCase()) {
console.log(`Kicking ${value} from ${id}.`);
game.chat.push({
date: Date.now(),
message: `${player.name} has been kicked from game.`
});
player.session = '';
player.name = '';
player.status = 'Not active';
player.lastActive = 0;
error = undefined;
break;
}
}
break;
}
return sendGame(res, req, game, error);
}
if (action == "player-name") {
const name = value ? value : "";
if (color) {
error = `You cannot change your name while you are in game.`;
} else { } else {
if (game) for (let key in game.players) { error = adminActions(game, action, value);
player = game.players[key];
if (player.name && player.name.toLowerCase() === name.toLowerCase()) {
error = `${name} is already taken by ${key}`;
}
}
} }
return sendGame(req, res, game, error);
if (!error) {
const old = req.session.playerName ? req.session.PlayerName : "Unknown";
let message;
req.session.playerName = name;
if (name) {
message = `${old} is now known as ${name}.`;
} else {
message = `${old} no longer has a name.`;
}
game.chat.push({
date: Date.now(),
message: message
});
}
return sendGame(res, req, game, error);
} }
if (action == "player-selected") { const session = getSession(game, req.session.id);
if (!game) {
error = `No game found`;
return sendGame(res, req, game, error);
}
const selected = req.params.value, switch (action) {
name = req.session.playerName; case 'player-name':
error = setPlayerName(game, session, value);
console.log(`player-selected requested for ${selected} by ${name}`); return sendGame(req, res, game, error);
case 'player-selected':
/* Deselect currently active player for this session */ error = setPlayerColor(game, session, value);
for (let key in game.players) { return sendGame(req, res, game, error);
if (key === color && selected !== key) {
player = game.players[key];
if (player.session === req.session.id) {
player.session = '';
player.name = '';
player.status = 'Not active';
req.session.playerColor = '';
game.chat.push({
date: Date.now(),
message: `${key} is no longer claimed.`
});
break;
}
}
}
/* Verify the player has a name set */
if (!name) {
error = `You may only select a player when you have set your name.`;
}
/* Verify this player's name is not already active in the game */
if (!error) {
for (let key in game.players) {
player = game.players[key];
if (key === color && selected !== key && player.name == name) {
error = `This name is already taken by ${key}`;
break;
}
}
}
/* Verify selection is valid */
if (!error && !(selected in game.players)) {
error = `An invalid player selection was attempted.`;
}
/* Verify selection is not already taken */
if (!error) {
player = game.players[selected];
if (player.session) {
error = `${player.name} already has ${selected}`;
}
}
/* All good -- set this player to requested selection */
if (!error) {
player.session = req.session.id;
player.name = req.session.playerName;
player.status = `Active`;
player.lastActive = Date.now();
req.session.playerColor = selected;
game.chat.push({
date: Date.now(),
message: `${selected} is now '${player.name}'.`
});
}
return sendGame(res, req, game, error);
} }
if (!req.session.playerColor || if (!session.player) {
!(req.session.playerColor in game.players)) { error = `Player must have an active color.`;
error = `Invalid player: ${req.session.playerColor}`; return sendGame(req, res, game, error);
return sendGame(res, req, game, error);
} }
player = game.players[req.session.playerColor]; const name = session.name;
const name = player.name;
switch (action) { switch (action) {
case "roll": case "roll":
error = roll(game, player); error = roll(game, session);
break; break;
case "shuffle": case "shuffle":
if (game.state !== "lobby") { if (game.state !== "lobby") {
@ -336,8 +383,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
} }
break break
case "state": case "state":
const state = req.params.value ? req.params.value : "active"; const state = value;
if (state != game.state) { if (!state) {
error = `Invalid state.`;
} else if (state != game.state) {
game.state = state; game.state = state;
const message = `${name} set game state to ${state}.`; const message = `${name} set game state to ${state}.`;
game.chat.push({ date: Date.now(), message: message }); game.chat.push({ date: Date.now(), message: message });
@ -345,7 +394,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
break; break;
} }
return sendGame(res, req, game, error); return sendGame(req, res, game, error);
}) })
router.get("/:id", async (req, res/*, next*/) => { router.get("/:id", async (req, res/*, next*/) => {
@ -354,52 +403,48 @@ router.get("/:id", async (req, res/*, next*/) => {
let game = await loadGame(id); let game = await loadGame(id);
if (game) { if (game) {
return sendGame(res, req, game) return sendGame(req, res, game)
} }
game = createGame(id); game = createGame(id);
req.session.playerColor = null;
return sendGame(res, req, game); return sendGame(req, res, game);
}); });
router.put("/:id", (req, res/*, next*/) => { router.put("/:id", (req, res/*, next*/) => {
console.log("PUT games/" + req.params.id); const { id } = req.params;
if (req.params.id in games) {
const game = games[req.params.id],
changes = req.body;
console.log(req.session.id, req.session.playerColor); console.log("PUT games/" + id);
console.log(JSON.stringify(changes, null, 2)); if (!(id in games)) {
for (let change in changes) {
switch (change) {
case "players":
break;
case "chat":
console.log("Chat change.");
game.chat.push({
from: changes.chat.player,
date: Date.now(),
message: changes.chat.message
});
if (game.chat.length > 10) {
game.chat.splice(0, game.chat.length - 10);
}
break;
}
}
return sendGame(res, req, game);
} else {
const error = `Game not found: ${req.params.id}`; const error = `Game not found: ${req.params.id}`;
return res.status(404).send(error); return res.status(404).send(error);
} }
const game = games[id],
changes = req.body;
for (let change in changes) {
switch (change) {
case "chat":
console.log("Chat change.");
game.chat.push({
from: changes.chat.player,
date: Date.now(),
message: changes.chat.message
});
if (game.chat.length > 10) {
game.chat.splice(0, game.chat.length - 10);
}
break;
}
}
return sendGame(req, res, game);
}); });
const sendGame = async (res, req, game, error) => { const sendGame = async (req, res, game, error) => {
let active = 0; let active = 0;
for (let player in game.players) { for (let color in game.players) {
player = game.players[player]; const player = game.players[color];
active += ((player.status && player.status != 'Not active') ? 1 : 0); active += ((player.status && player.status != 'Not active') ? 1 : 0);
} }
if (active < 2 && game.state != 'lobby' && game.state != 'invalid') { if (active < 2 && game.state != 'lobby' && game.state != 'invalid') {
@ -408,26 +453,49 @@ const sendGame = async (res, req, game, error) => {
console.log(message); console.log(message);
game.state = 'lobby'; game.state = 'lobby';
} }
const playerColor = (req.session && req.session.playerColor) ? req.session.playerColor : "",
playerName = (req.session && req.session.playerName) ? req.session.playerName : ""; let session;
if (playerColor in game.players) { if (req.session) {
game.players[playerColor].lastActive = Date.now(); session = getSession(game, req.session.id);
session.lastActive = Date.now();
} else {
session = {
name: "command line"
};
} }
await writeFile(`games/${game.id}`, JSON.stringify(game, null, 2)) /* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then
* delete the player field from them */
const reducedGame = Object.assign({}, game, { sessions: {} });
for (let id in game.sessions) {
const reduced = Object.assign({}, game.sessions[id]);
if (reduced.player) {
delete reduced.player;
}
reducedGame.sessions[id] = reduced;
}
await writeFile(`games/${game.id}`, JSON.stringify(reducedGame, null, 2))
.catch((error) => { .catch((error) => {
console.error(`Unable to write to games/${game.id}`); console.error(`Unable to write to games/${game.id}`);
console.error(error); console.error(error);
}); });
const playerGame = Object.assign({}, game, {
/* Do not send session-id as those are secrets */
const sessions = [];
for (let id in reducedGame.sessions) {
const reduced = reducedGame.sessions[id];
sessions.push(reduced);
}
const playerGame = Object.assign({}, reducedGame, {
timestamp: Date.now(), timestamp: Date.now(),
status: error ? error : "success", status: error ? error : "success",
activePlayerName: playerName, name: session.name,
activePlayer: playerColor color: session.color,
sessions: sessions
}); });
if (game.id == 'b3c4bd15efe212a2') {
// console.log(req.session);
}
return res.status(200).send(playerGame); return res.status(200).send(playerGame);
} }
@ -442,10 +510,10 @@ const createGame = (id) => {
borders: [], borders: [],
tokens: [], tokens: [],
players: { players: {
R: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" }, R: getPlayer(),
O: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" }, O: getPlayer(),
B: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" }, B: getPlayer(),
W: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" } W: getPlayer()
}, },
developmentCards: assetData.developmentCards.slice(), developmentCards: assetData.developmentCards.slice(),
dice: [ 0, 0 ], dice: [ 0, 0 ],
@ -475,9 +543,8 @@ router.post("/:id?", (req, res/*, next*/) => {
} }
const game = createGame(id); const game = createGame(id);
req.session.playerColor = null;
return sendGame(res, req, game); return sendGame(req, res, game);
}); });
const shuffleBoard = (game) => { const shuffleBoard = (game) => {