1
0

Chatting works!

Signed-off-by: James Ketrenos <james.p.ketrenos@intel.com>
This commit is contained in:
James Ketrenos 2020-04-25 09:54:15 -07:00
parent 28746f41c9
commit 10c3020b8e
6 changed files with 282 additions and 47 deletions

View File

@ -15,6 +15,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.9.11", "@material-ui/core": "^4.9.11",
"@material-ui/lab": "^4.0.0-alpha.50",
"animakit-expander": "^2.1.4", "animakit-expander": "^2.1.4",
"bluebird": "^3.5.5", "bluebird": "^3.5.5",
"bootstrap": "^4.4.1", "bootstrap": "^4.4.1",

View File

@ -85,7 +85,7 @@ app.use(function(req, res, next){
app.use(session({ app.use(session({
store: new SQLiteStore({ db: config.get("sessions.db") }), store: new SQLiteStore({ db: config.get("sessions.db") }),
secret: config.get("sessions.store-secret"), secret: config.get("sessions.store-secret"),
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, // 1 week cookie: { maxAge: 21 * 24 * 60 * 60 * 1000 }, // 3 weeks
saveUninitialized: false, saveUninitialized: false,
resave: true resave: true
})); }));

View File

@ -102,7 +102,60 @@ const games = {};
router.get("/:id", (req, res/*, next*/) => { router.get("/:id", (req, res/*, next*/) => {
console.log("GET games/" + req.params.id); console.log("GET games/" + req.params.id);
if (req.params.id in games) { if (req.params.id in games) {
return res.status(200).send(games[req.params.id]); const game = games[req.params.id];
return res.status(200).send(Object.assign({}, game, {
timestamp: Date.now(),
activePlayer: req.session.activePlayer
}));
} else {
const error = `Game not found: ${req.params.id}`;
return res.status(404).send(error);
}
});
router.put("/:id", (req, res/*, next*/) => {
console.log("PUT games/" + req.params.id);
if (req.params.id in games) {
const game = games[req.params.id],
changes = req.body;
console.log(JSON.stringify(changes, null, 2));
for (let change in changes) {
switch (change) {
case "players":
console.log("Player change.");
for (let player in changes.players) {
const playerChange = changes.players[player];
if (playerChange.name != "") {
game.chat.push({ from: player, date: Date.now(), message: `${player} is now '${playerChange.name}'.` });
req.session.activePlayer = player;
game.players[player].status = `Just joined`;
} else {
game.chat.push({ from: player, date: Date.now(), message: `${player} is no longer claimed.` });
req.session.activePlayer = "";
game.players[player].status = `Not active`;
}
game.players[player].name = playerChange.name;
}
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 res.status(200).send(Object.assign({}, game, {
timestamp: Date.now(),
activePlayer: req.session.activePlayer
}));
} else { } 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);
@ -117,12 +170,13 @@ router.post("/", (req, res/*, next*/) => {
tiles: [], tiles: [],
pips: [], pips: [],
borders: [], borders: [],
tokens: [ tokens: [],
{ player: "R", roads: 15, cities: 4, settlements: 5, points: 0 }, players: {
{ player: "O", roads: 15, cities: 4, settlements: 5, points: 0 }, R: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" },
{ player: "B", roads: 15, cities: 4, settlements: 5, points: 0 }, O: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" },
{ player: "W", roads: 15, cities: 4, settlements: 5, points: 0 } B: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" },
], W: { roads: 15, cities: 4, settlements: 5, points: 0, name: "", status: "Not active" }
},
developmentCards: assetData.developmentCards.slice(), developmentCards: assetData.developmentCards.slice(),
dice: [ 0, 0 ], dice: [ 0, 0 ],
sheep: 19, sheep: 19,
@ -154,7 +208,10 @@ router.post("/", (req, res/*, next*/) => {
games[game.id] = game; games[game.id] = game;
console.log(`New game created: ${game.id}`); console.log(`New game created: ${game.id}`);
return res.status(200).send(game); return res.status(200).send(Object.assign({}, game, {
timestamp: Date.now(),
activePlayer: req.session.activePlayer
}));
}); });

Binary file not shown.

View File

@ -43,8 +43,6 @@
} }
.Chat { .Chat {
right: 0;
bottom: 0;
padding: 0.5em; padding: 0.5em;
width: 30vmax; width: 30vmax;
display: inline-block; display: inline-block;
@ -60,6 +58,25 @@
overflow-y: scroll; overflow-y: scroll;
} }
.Players {
padding: 0.5em;
width: 30vmax;
display: inline-block;
opacity: 0.7;
}
#ChatList {
scroll-behavior: smooth;
}
.Players > * {
width: 100%;
}
.Players .nameInput {
flex-grow: 1;
}
.Stack > *:not(:first-child) { .Stack > *:not(:first-child) {
margin-left: -4.5em; margin-left: -4.5em;
} }

View File

@ -7,9 +7,38 @@ import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import { makeStyles } from '@material-ui/core/styles';
import { deepOrange, lightBlue, red, grey } from '@material-ui/core/colors';
import Avatar from '@material-ui/core/Avatar';
import Switch from '@material-ui/core/Switch';
import Moment from 'react-moment'; import Moment from 'react-moment';
import moment from 'moment'; import moment from 'moment';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
'& > *': {
margin: theme.spacing(1),
},
},
R: {
color: theme.palette.getContrastText(red[500]),
backgroundColor: red[500],
},
O: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
},
W: {
color: theme.palette.getContrastText(grey[100]),
backgroundColor: grey[100],
},
B: {
color: theme.palette.getContrastText(lightBlue[500]),
backgroundColor: lightBlue[500],
},
}));
const hexagonRatio = 1.1547005, const hexagonRatio = 1.1547005,
tileHeight = 0.16, tileHeight = 0.16,
tileWidth = tileHeight * hexagonRatio, tileWidth = tileHeight * hexagonRatio,
@ -179,49 +208,145 @@ class Resource extends React.Component {
} }
}; };
class Chat extends React.Component { const Chat = ({ game, promoteGameState }) => {
constructor(props) { const chatInput = (event) => {
super(props);
this.chatInput = this.chatInput.bind(this);
}
chatInput(event) {
console.log(event.target.value); console.log(event.target.value);
} };
chatKeyPress(event) { const chatKeyPress = (event) => {
if (event.key == "Enter") { if (event.key == "Enter") {
console.log(`Send: ${event.target.value}`); console.log(`Send: ${event.target.value}`);
promoteGameState({ chat: { player: game.activePlayer, message: event.target.value }});
event.target.value = ""; event.target.value = "";
} }
} };
render() { const classes = useStyles();
//this.props.game.messages
const messages =[ { from: "R", date: Date.now(), message: "Hello, world!" } ].map((item, index) => { useEffect(() => {
const timestamp = moment(item.date).fromNow(); const chatList = document.getElementById("ChatList");
chatList.scrollTop = chatList.scrollHeight - chatList.offsetHeight;
})
console.log(JSON.stringify(game, null, 2));
const timeDelta = game.timestamp - Date.now();
const messages = game.chat.map((item, index) => {
const timestamp = moment(item.date - timeDelta).fromNow();
return ( return (
<ListItem key={`msg-${index}`}> <ListItem key={`msg-${index}`}>
<ListItemAvatar> <ListItemAvatar>
{item.from} <Avatar className={classes[item.from]}>{item.from}</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={item.message} secondary={timestamp} /> <ListItemText primary={item.message} secondary={timestamp} />
</ListItem> </ListItem>
); );
}); });
return ( return (
<Paper className="Chat"> <Paper className="Chat">
<List> <List id="ChatList">
{ messages } { messages }
</List> </List>
<TextField id="chat-input" <TextField className="chatInput"
onChange={this.chatInput} disabled={!game.activePlayer}
onKeyPress={this.chatKeyPress} onChange={chatInput}
onKeyPress={chatKeyPress}
label={(<Moment format="h:mm:ss" interval={1000}/>)} variant="outlined"/> label={(<Moment format="h:mm:ss" interval={1000}/>)} variant="outlined"/>
</Paper> </Paper>
); );
}
/* This needs to take in a mechanism to declare the
* player's active item in the game */
const Players = ({ game, promoteGameState }) => {
const [selected, setSelected] = useState("");
const [name, setName] = useState("");
const nameInput = (event) => {
console.log(event.target.value);
};
const nameKeyPress = (event) => {
if (event.key == "Enter") {
console.log(`Send: ${event.target.value}`);
setName(event.target.value);
}
} }
useEffect(() => {
const change = { players: {} };
/* Joining: selected != "", activePlayer == "", and name != "" */
if (selected && !game.activePlayer && name != "") {
change.players[selected] = { name: name }
promoteGameState(change)
return;
}
/* Leaving: selected = "", name = "", activePlayer != "" */
if (!selected && game.activePlayer != "") {
change.players[game.activePlayer] = { name: "" }
promoteGameState(change)
}
/* Updating name: selected != "", activePlayer != "", name != "", name != activePlayer.name*/
if (selected != "" &&
game.activePlayer != "" &&
name != "" &&
game.players[game.activePlayer].name != name) {
change.players[game.activePlayer] = { name: name }
promoteGameState(change)
}
});
const toggleSelected = (key) => {
if (selected == key) {
setSelected("");
setName("");
} else {
setSelected(key);
}
}
const classes = useStyles();
const players = [];
for (let key in game.players) {
const item = game.players[key];
players.push((
<ListItem key={`player-${key}`}>
<ListItemAvatar>
<Avatar className={classes[key]}>{key}</Avatar>
</ListItemAvatar>
<> { /* so flex-grow works we put in a fragment */ }
{ selected == key && item.name == "" &&
<TextField className="nameInput"
onChange={nameInput}
onKeyPress={nameKeyPress}
inputRef={input => input && input.focus()}
disabled={name != item.name}
label="Name"
variant="outlined" autoFocus/>
}
{ (selected != key || item.name != "") &&
<ListItemText primary={item.name} secondary={item.status} />
}
</>
<Switch edge="end"
disabled={selected && selected != key} checked={selected == key}
onChange={() => toggleSelected(key)}/>
</ListItem>
));
}
return (
<Paper className="Players">
<List>
{ players }
</List>
</Paper>
);
} }
class Board extends React.Component { class Board extends React.Component {
@ -233,7 +358,8 @@ class Board extends React.Component {
sheep: 0, sheep: 0,
brick: 0, brick: 0,
stone: 0, stone: 0,
wheat: 0 wheat: 0,
game: null
}; };
this.componentDidMount = this.componentDidMount.bind(this); this.componentDidMount = this.componentDidMount.bind(this);
this.updateDimensions = this.updateDimensions.bind(this); this.updateDimensions = this.updateDimensions.bind(this);
@ -245,6 +371,7 @@ class Board extends React.Component {
this.mouseMove = this.mouseMove.bind(this); this.mouseMove = this.mouseMove.bind(this);
this.randomize = this.randomize.bind(this); this.randomize = this.randomize.bind(this);
this.throwDice = this.throwDice.bind(this); this.throwDice = this.throwDice.bind(this);
this.promoteGameState = this.promoteGameState.bind(this);
this.mouse = { x: 0, y: 0 }; this.mouse = { x: 0, y: 0 };
this.radius = 0.317; this.radius = 0.317;
@ -284,7 +411,10 @@ class Board extends React.Component {
// body: JSON.stringify(data) // body data type must match "Content-Type" header // body: JSON.stringify(data) // body data type must match "Content-Type" header
}).then((res) => { }).then((res) => {
if (res.status > 400) { if (res.status > 400) {
throw `Unable to load game ${props.match.params.id}`; const base = document.querySelector("base");
window.location.href = base ? base.href : "/";
console.log(`Unable to find game ${props.match.params.id}`);
throw `Unable to find requested game. Staring new one.`;
} }
return res.json(); return res.json();
}).then((game) => { }).then((game) => {
@ -295,6 +425,7 @@ class Board extends React.Component {
} }
this.game = game; this.game = game;
this.setState({ game: game });
this.pips = Pips(this); this.pips = Pips(this);
this.tiles = Tiles(this); this.tiles = Tiles(this);
this.table = Table(this); this.table = Table(this);
@ -308,6 +439,29 @@ class Board extends React.Component {
}); });
} }
promoteGameState(change) {
console.log("Requesting state change: ", change);
window.fetch(`api/v1/games/${this.state.game.id}`, {
method: "PUT",
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(change)
}).then((res) => {
if (res.status > 400) {
console.error(res);
alert(`Unable to change state`);
}
return res.json();
}).then((game) => {
console.log (`Game state changed.`);
this.setState({ game: game });
});
}
randomize() { randomize() {
//this.borders = shuffle(this.borders); //this.borders = shuffle(this.borders);
this.tiles = shuffle(this.tiles); this.tiles = shuffle(this.tiles);
@ -853,11 +1007,17 @@ class Board extends React.Component {
} }
render() { render() {
const game = this.state.game;
return ( return (
<div className="Board" ref={el => this.el = el}> <div className="Board" ref={el => this.el = el}>
<canvas className="Display" ref={el => this.canvas = el}></canvas> <canvas className="Display" ref={el => this.canvas = el}></canvas>
<div className="Cards" ref={el => this.cards = el}> <div className="Cards" ref={el => this.cards = el}>
<Chat/> { game &&
<>
<Chat game={game} promoteGameState={this.promoteGameState}/>
<Players game={game} promoteGameState={this.promoteGameState}/>
</>
}
<div>In hand</div> <div>In hand</div>
<div className="Hand"> <div className="Hand">
<Resource type="wood" count={this.state.wood}/> <Resource type="wood" count={this.state.wood}/>