Chatting works!
Signed-off-by: James Ketrenos <james.p.ketrenos@intel.com>
This commit is contained in:
parent
28746f41c9
commit
10c3020b8e
@ -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",
|
||||||
|
@ -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
|
||||||
}));
|
}));
|
||||||
|
@ -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
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
BIN
sessions.db
BIN
sessions.db
Binary file not shown.
@ -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;
|
||||||
}
|
}
|
||||||
|
204
src/Board.js
204
src/Board.js
@ -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}/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user