1014 lines
29 KiB
JavaScript
Executable File
1014 lines
29 KiB
JavaScript
Executable File
import React, { useState, useEffect } from "react";
|
|
import "./Table.css";
|
|
import history from "./history.js";
|
|
import Paper from '@material-ui/core/Paper';
|
|
import Button from '@material-ui/core/Button';
|
|
import TextField from '@material-ui/core/TextField';
|
|
import List from '@material-ui/core/List';
|
|
import ListItem from '@material-ui/core/ListItem';
|
|
import ListItemText from '@material-ui/core/ListItemText';
|
|
import { makeStyles } from '@material-ui/core/styles';
|
|
import { orange,lightBlue, red, grey } from '@material-ui/core/colors';
|
|
import Avatar from '@material-ui/core/Avatar';
|
|
import Moment from 'react-moment';
|
|
import Board from './Board.js'
|
|
|
|
//import moment from 'moment';
|
|
|
|
/* Start of withRouter polyfill */
|
|
// https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it
|
|
import {
|
|
useLocation,
|
|
useNavigate,
|
|
useParams
|
|
} from "react-router-dom";
|
|
|
|
function withRouter(Component) {
|
|
function ComponentWithRouterProp(props) {
|
|
let location = useLocation();
|
|
let navigate = useNavigate();
|
|
let params = useParams();
|
|
return (
|
|
<Component
|
|
{...props}
|
|
router={{ location, navigate, params }}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return ComponentWithRouterProp;
|
|
}
|
|
/* end of withRouter polyfill */
|
|
|
|
const base = process.env.PUBLIC_URL;
|
|
|
|
const assetsPath = `${base}/assets`;
|
|
const gamesPath = `${base}/games`;
|
|
|
|
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(orange[500]),
|
|
backgroundColor: orange[500],
|
|
},
|
|
W: {
|
|
color: theme.palette.getContrastText(grey[500]),
|
|
backgroundColor: grey[500],
|
|
},
|
|
B: {
|
|
color: theme.palette.getContrastText(lightBlue[500]),
|
|
backgroundColor: lightBlue[500],
|
|
},
|
|
}));
|
|
|
|
const Dice = ({ pips }) => {
|
|
let name;
|
|
switch (pips) {
|
|
case 1: name = 'one'; break;
|
|
case 2: name = 'two'; break;
|
|
case 3: name = 'three'; break;
|
|
case 4: name = 'four'; break;
|
|
case 5: name = 'five'; break;
|
|
default:
|
|
case 6: name = 'six'; break;
|
|
}
|
|
return (
|
|
<img alt={name} className="Dice" src={`${assetsPath}/dice-six-faces-${name}.svg`}/>
|
|
);
|
|
}
|
|
|
|
const PlayerColor = ({ color }) => {
|
|
const classes = useStyles();
|
|
return (
|
|
<Avatar className={['PlayerColor', classes[color]].join(' ')}/>
|
|
);
|
|
};
|
|
|
|
const diceSize = 0.05,
|
|
dice = [ {
|
|
pips: 0,
|
|
jitter: 0,
|
|
angle: 0
|
|
}, {
|
|
pips: 0,
|
|
jitter: 0,
|
|
angle: 0
|
|
} ];
|
|
|
|
|
|
class Placard extends React.Component {
|
|
render() {
|
|
return (
|
|
<div className="Placard"
|
|
style={{backgroundImage:`url(${assetsPath}/gfx/placard-${this.props.type}.png)`}}>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
class Development extends React.Component {
|
|
render() {
|
|
const array = [];
|
|
for (let i = 0; i < this.props.count; i++) {
|
|
if (this.props.type.match(/-$/)) {
|
|
array.push(i + 1);//Math.ceil(Math.random() * this.props.max));
|
|
} else {
|
|
array.push("");
|
|
}
|
|
}
|
|
return (
|
|
<div className="Stack">
|
|
{ React.Children.map(array, i => (
|
|
<div className="Development"
|
|
style={{backgroundImage:`url(${assetsPath}/gfx/card-${this.props.type}${i}.png)`}}>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
class Resource extends React.Component {
|
|
render() {
|
|
const array = new Array(Number(this.props.count ? this.props.count : 0));
|
|
return (
|
|
<>
|
|
{ array.length > 0 &&
|
|
<div className="Stack">
|
|
{ React.Children.map(array, i => (
|
|
<div className="Resource"
|
|
style={{backgroundImage:`url(${assetsPath}/gfx/card-${this.props.type}.png)`}}>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
}
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
const Chat = ({ table, promoteGameState }) => {
|
|
const [lastTop, setLastTop] = useState(0),
|
|
[autoScroll, setAutoscroll] = useState(true),
|
|
[scrollTime, setScrollTime] = useState(0);
|
|
|
|
const chatInput = (event) => {
|
|
};
|
|
|
|
const chatKeyPress = (event) => {
|
|
if (event.key === "Enter") {
|
|
if (!autoScroll) {
|
|
setAutoscroll(true);
|
|
}
|
|
|
|
promoteGameState({
|
|
chat: {
|
|
player: table.game.color ? table.game.color : undefined,
|
|
message: event.target.value
|
|
}
|
|
});
|
|
event.target.value = "";
|
|
}
|
|
};
|
|
|
|
const chatScroll = (event) => {
|
|
const chatList = event.target,
|
|
fromBottom = Math.round(Math.abs((chatList.scrollHeight - chatList.offsetHeight) - chatList.scrollTop));
|
|
|
|
/* If scroll is within 20 pixels of the bottom, turn on auto-scroll */
|
|
const shouldAutoscroll = (fromBottom < 20);
|
|
|
|
if (shouldAutoscroll !== autoScroll) {
|
|
setAutoscroll(shouldAutoscroll);
|
|
}
|
|
|
|
/* If the list should not auto scroll, then cache the current
|
|
* top of the list and record when we did this so we honor
|
|
* the auto-scroll for at least 500ms */
|
|
if (!shouldAutoscroll) {
|
|
const target = Math.round(chatList.scrollTop);
|
|
if (target !== lastTop) {
|
|
setLastTop(target);
|
|
setScrollTime(Date.now());
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const chatList = document.getElementById("ChatList"),
|
|
currentTop = Math.round(chatList.scrollTop);
|
|
|
|
if (autoScroll) {
|
|
/* Auto-scroll to the bottom of the chat window */
|
|
const target = Math.round(chatList.scrollHeight - chatList.offsetHeight);
|
|
if (currentTop !== target) {
|
|
chatList.scrollTop = target;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Maintain current position in scrolled view if the user hasn't
|
|
* been scrolling in the past 0.5s */
|
|
if ((Date.now() - scrollTime) > 500 && currentTop !== lastTop) {
|
|
chatList.scrollTop = lastTop;
|
|
}
|
|
});
|
|
|
|
//const timeDelta = game.timestamp - Date.now();
|
|
if (!table.game) {
|
|
console.log("Why no game?");
|
|
}
|
|
|
|
const messages = table.game && table.game.chat.map((item, index) => {
|
|
/* If the date is in the future, set it to now */
|
|
const name = getPlayerName(table.game.sessions, item.from),
|
|
from = name ? `${name}, ` : '';
|
|
return (
|
|
<ListItem key={`msg-${item.date}`}>
|
|
<PlayerColor color={item.from}/>
|
|
<ListItemText primary={item.message}
|
|
secondary={(<>{from}
|
|
<Moment fromNow date={item.date > Date.now() ?
|
|
Date.now() : item.date} interval={1000}/></>)} />
|
|
</ListItem>
|
|
);
|
|
});
|
|
|
|
const name = table.game ? table.game.name : "Why no game?";
|
|
|
|
return (
|
|
<Paper className="Chat">
|
|
<List id="ChatList" onScroll={chatScroll}>
|
|
{ messages }
|
|
</List>
|
|
<TextField className="chatInput"
|
|
disabled={!name}
|
|
onChange={chatInput}
|
|
onKeyPress={chatKeyPress}
|
|
label={(<Moment format="h:mm:ss" interval={1000}/>)} variant="outlined"/>
|
|
</Paper>
|
|
);
|
|
}
|
|
|
|
const StartButton = ({ table }) => {
|
|
const startClick = (event) => {
|
|
table.setGameState("game-order").then((state) => {
|
|
table.game.state = state;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Button disabled={!table.game.color || table.game.active < 2} onClick={startClick}>Start game</Button>
|
|
);
|
|
};
|
|
|
|
const Action = ({ table }) => {
|
|
const newTableClick = (event) => {
|
|
return table.shuffleTable();
|
|
};
|
|
|
|
const rollClick = (event) => {
|
|
table.throwDice();
|
|
}
|
|
|
|
const passClick = (event) => {
|
|
}
|
|
|
|
if (!table.game) {
|
|
console.log("Why no game?");
|
|
return (<Paper className="Action"/>);
|
|
}
|
|
|
|
return (
|
|
<Paper className="Action">
|
|
{ table.game.state === 'lobby' && <>
|
|
<StartButton table={table}/>
|
|
<Button disabled={!table.game.color} onClick={newTableClick}>New table</Button>
|
|
<Button disabled={table.game.color ? true : false} onClick={() => {table.setState({ pickName: true})}}>Change name</Button> </> }
|
|
{ table.game.state === 'game-order' &&
|
|
<Button disabled={table.game.order !== 0} onClick={rollClick}>Roll Dice</Button> }
|
|
{ table.game.state === 'active' && <>
|
|
<Button onClick={rollClick}>Roll Dice</Button>
|
|
<Button disabled onClick={passClick}>Pass Turn</Button>
|
|
</> }
|
|
</Paper>
|
|
);
|
|
}
|
|
|
|
const PlayerName = ({table}) => {
|
|
const [name, setName] = useState((table && table.game && table.game.name) ? table.game.name : "");
|
|
|
|
const nameChange = (event) => {
|
|
setName(event.target.value);
|
|
}
|
|
|
|
const sendName = () => {
|
|
console.log(`Send: ${name}`);
|
|
if (name !== table.game.name) {
|
|
table.setPlayerName(name);
|
|
} else {
|
|
table.setState({ pickName: false, error: "" });
|
|
}
|
|
}
|
|
|
|
const nameKeyPress = (event) => {
|
|
if (event.key === "Enter") {
|
|
sendName();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Paper className="PlayerName">
|
|
<TextField className="nameInput"
|
|
onChange={nameChange}
|
|
onKeyPress={nameKeyPress}
|
|
label="Enter your name"
|
|
variant="outlined"
|
|
value={name}
|
|
/>
|
|
<Button onClick={() => sendName()}>Set</Button>
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
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
|
|
* player's active item in the game */
|
|
const Players = ({ table }) => {
|
|
const toggleSelected = (key) => {
|
|
console.log('toggle');
|
|
table.setSelected(table.game.color === key ? "" : key);
|
|
}
|
|
|
|
const players = [];
|
|
|
|
if (!table.game) {
|
|
return (<></>);
|
|
}
|
|
for (let color in table.game.players) {
|
|
const item = table.game.players[color], inLobby = table.game.state === 'lobby';
|
|
if (!inLobby && item.status === 'Not active') {
|
|
continue;
|
|
}
|
|
const name = getPlayerName(table.game.sessions, color),
|
|
selectable = table.game.state === 'lobby' && (item.status === 'Not active' || table.game.color === color);
|
|
let toggleText = name ? name : "Available";
|
|
players.push((
|
|
<div
|
|
data-selectable={selectable}
|
|
data-selected={table.game.color === color}
|
|
className="PlayerEntry"
|
|
onClick={() => { inLobby && selectable && toggleSelected(color) }}
|
|
key={`player-${color}`}>
|
|
<PlayerColor color={color}/>
|
|
<ListItemText primary={toggleText} secondary={(
|
|
<>
|
|
{ item.status + ' ' }
|
|
{ item.status !== 'Not active' && <Moment fromNow date={item.lastActive > Date.now() ? Date.now() : item.lastActive}/>}
|
|
</>)} />
|
|
{ !inLobby && table.game.color === color &&
|
|
<Button onClick={() => toggleSelected(color)}>Quit</Button>
|
|
}
|
|
</div>
|
|
));
|
|
}
|
|
|
|
return (
|
|
<Paper className="Players">
|
|
<List className="PlayerSelector">
|
|
{ players }
|
|
</List>
|
|
</Paper>
|
|
);
|
|
}
|
|
|
|
console.log("TODO: Convert this to a function component!!!!");
|
|
|
|
class Table extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
total: 0,
|
|
wood: 0,
|
|
sheep: 0,
|
|
brick: 0,
|
|
stone: 0,
|
|
wheat: 0,
|
|
game: null,
|
|
message: "",
|
|
error: "",
|
|
signature: ""
|
|
};
|
|
this.componentDidMount = this.componentDidMount.bind(this);
|
|
this.updateDimensions = this.updateDimensions.bind(this);
|
|
this.throwDice = this.throwDice.bind(this);
|
|
this.promoteGameState = this.promoteGameState.bind(this);
|
|
this.resetGameLoad = this.resetGameLoad.bind(this);
|
|
this.loadGame = this.loadGame.bind(this);
|
|
this.rollDice = this.rollDice.bind(this);
|
|
this.setGameState = this.setGameState.bind(this);
|
|
this.shuffleTable = this.shuffleTable.bind(this);
|
|
this.updateGame = this.updateGame.bind(this);
|
|
this.setPlayerName = this.setPlayerName.bind(this);
|
|
this.setSelected = this.setSelected.bind(this);
|
|
this.updateMessage = this.updateMessage.bind(this);
|
|
this.gameSignature = this.gameSignature.bind(this);
|
|
|
|
this.mouse = { x: 0, y: 0 };
|
|
this.radius = 0.317;
|
|
|
|
this.loadTimer = null;
|
|
|
|
this.game = null;
|
|
this.pips = [];
|
|
this.tiles = [];
|
|
this.borders = [];
|
|
this.tabletop = null;
|
|
this.closest = {
|
|
info: {},
|
|
tile: null,
|
|
road: null,
|
|
tradeToken: null,
|
|
settlement: null
|
|
};
|
|
|
|
this.id = (props.router && props.router.params.id) ? props.router.params.id : 0;
|
|
}
|
|
|
|
setSelected(key) {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/player-selected/${key}`, {
|
|
method: "PUT",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
throw new Error(`Unable to set selected player!`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
const error = (game.status !== 'success') ? game.status : undefined;
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
this.setState({ error: error });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
}
|
|
|
|
setPlayerName(name) {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/player-name/${name}`, {
|
|
method: "PUT",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
throw new Error(`Unable to set player name!`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
let message;
|
|
if (game.status !== 'success') {
|
|
message = game.status;
|
|
} else {
|
|
this.setState({ pickName: false });
|
|
}
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
|
|
this.setState({ error: message});
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
}
|
|
|
|
shuffleTable() {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/shuffle`, {
|
|
method: "PUT",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
throw new Error(`Unable to shuffle!`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
console.log (`Table shuffled!`);
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
this.setState({ error: "Table shuffled!" });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
}
|
|
|
|
rollDice() {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/roll`, {
|
|
method: "PUT",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
console.log(res);
|
|
throw new Error(`Unable to roll dice`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
const error = (game.status !== 'success') ? game.status : undefined;
|
|
if (error) {
|
|
game.dice = [ game.order ];
|
|
}
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
this.setState({ /*game: { ...this.state.game, dice: game.dice },*/ error: error } );
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
return this.game.dice;
|
|
});
|
|
}
|
|
|
|
loadGame() {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
if (!this.state.game) {
|
|
console.error('Attempting to loadGame with no game set');
|
|
return;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}`, {
|
|
method: "GET",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
console.log(res);
|
|
throw new Error(`Server temporarily unreachable.`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
const error = (game.status !== 'success') ? game.status : undefined;
|
|
|
|
//console.log (`Game ${game.id} loaded ${moment().format()}.`);
|
|
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
this.setState({ error: error });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
}
|
|
|
|
resetGameLoad() {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = 0;
|
|
}
|
|
this.loadTimer = window.setTimeout(this.loadGame, 1000);
|
|
}
|
|
|
|
promoteGameState(change) {
|
|
console.log("Requesting state change: ", change);
|
|
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/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);
|
|
throw new Error(`Unable to change state`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
this.updateGame(game);
|
|
this.setState({ error: "" });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
}
|
|
|
|
setGameState(state) {
|
|
if (this.loadTimer) {
|
|
window.clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
|
|
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/state/${state}`, {
|
|
method: "PUT",
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then((res) => {
|
|
if (res.status >= 400) {
|
|
console.log(res);
|
|
throw new Error(`Unable to set state to ${state}`);
|
|
}
|
|
return res.json();
|
|
}).then((game) => {
|
|
console.log (`Game state set to ${game.state}!`);
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
this.setState({ /*game: { ...this.state.game, state: game.state }, */error: `Game state now ${game.state}.` });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
return this.game.state;
|
|
});
|
|
}
|
|
|
|
throwDice() {
|
|
dice[0].pips = dice[1].pips = 0;
|
|
|
|
return this.rollDice().then((roll) => {
|
|
roll.forEach((value, index) => {
|
|
dice[index] = {
|
|
pips: value,
|
|
angle: Math.random() * Math.PI * 2,
|
|
jitter: (Math.random() - 0.5) * diceSize * 0.125
|
|
};
|
|
});
|
|
|
|
if (this.game.state !== 'active') {
|
|
return;
|
|
}
|
|
|
|
const sum = dice[0].pips + dice[1].pips;
|
|
if (sum === 7) { /* Robber! */
|
|
if (this.state.total > 7) {
|
|
let half = Math.ceil(this.state.total * 0.5);
|
|
this.setState({ total: this.state.total - half});
|
|
while (half) {
|
|
switch (Math.floor(Math.random() * 5)) {
|
|
case 0: if (this.state.wood) { this.setState({ wood: this.state.wood - 1}); half--; } break;
|
|
case 1: if (this.state.sheep) { this.setState({ sheep: this.state.sheep - 1}); half--; } break;
|
|
case 2: if (this.state.stone) { this.setState({ stone: this.state.stone - 1}); half--; } break;
|
|
case 3: if (this.state.brick) { this.setState({ brick: this.state.brick - 1}); half--; } break;
|
|
case 4:
|
|
default: if (this.state.wheat) { this.setState({ wheat: this.state.wheat - 1}); half--; } break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this.tiles.forEach((tile) => {
|
|
if (tile.pip.roll !== sum) {
|
|
return;
|
|
}
|
|
this.setState({ [tile.type]: this.state[tile.type] + 1});
|
|
this.setState({ total: this.state.total + 1 });
|
|
});
|
|
}
|
|
|
|
this.setState({
|
|
total: this.state.total,
|
|
wood: this.state.wood,
|
|
sheep: this.state.sheep,
|
|
stone: this.state.stone,
|
|
brick: this.state.brick,
|
|
wheat: this.state.wheat
|
|
});
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
});
|
|
}
|
|
|
|
updateDimensions() {
|
|
const hasToolbar = false;
|
|
|
|
if (this.updateSizeTimer) {
|
|
clearTimeout(this.updateSizeTimer);
|
|
}
|
|
|
|
this.updateSizeTimer = setTimeout(() => {
|
|
const container = document.getElementById("root"),
|
|
offset = hasToolbar
|
|
? container.firstChild.offsetHeight
|
|
: 0,
|
|
height = window.innerHeight - offset;
|
|
|
|
this.offsetY = offset;
|
|
this.width = window.innerWidth;
|
|
this.height = height;
|
|
|
|
if (this.cards && this.cards.style) {
|
|
this.cards.style.top = `${offset}px`;
|
|
this.cards.style.width = `${this.width}px`;
|
|
this.cards.style.height = `${this.heigh}tpx`;
|
|
}
|
|
|
|
this.updateSizeTimer = 0;
|
|
}, 250);
|
|
}
|
|
|
|
|
|
gameSignature(game) {
|
|
if (!game) {
|
|
return "";
|
|
}
|
|
const signature =
|
|
game.borderOrder.map(border => Number(border).toString(16)).join('') + '-' +
|
|
game.pipOrder.map(pip => Number(pip).toString(16)).join('') + '-' +
|
|
game.tileOrder.map(tile => Number(tile).toString(16)).join('');
|
|
|
|
return signature;
|
|
};
|
|
|
|
updateGame(game) {
|
|
if (this.state.signature !== this.gameSignature(game)) {
|
|
game.signature = this.gameSignature(game);
|
|
}
|
|
// console.log("Update Game", game);
|
|
this.setState( { game: game });
|
|
this.game = game;
|
|
}
|
|
|
|
updateMessage() {
|
|
const player = (this.game && this.game.color) ? this.game.players[this.game.color] : undefined,
|
|
name = this.game ? this.game.name : "";
|
|
|
|
let message = <></>;
|
|
if (this.state.pickName || !name) {
|
|
message = <>{message}Enter the name you would like to be known by, then press <b>ENTER</b> or select <b>SET</b>.</>;
|
|
} else {
|
|
switch (this.game && this.game.state) {
|
|
case 'lobby':
|
|
message = <>{message}You are in the lobby as <b>{name}</b>.</>;
|
|
if (!this.game.color) {
|
|
message = <>{message}You need to pick your color.</>;
|
|
} else {
|
|
message = <>{message}You have selected <PlayerColor color={this.game.color}/>.</>;
|
|
}
|
|
message = <>{message}You can chat with other players below.</>;
|
|
if (this.game.active < 2) {
|
|
message = <>{message}Once there are two or more players, you can select <StartButton table={this}/>.</>;
|
|
} else {
|
|
message = <>{message}There are enough players to start the game. Select <StartButton table={this}/> when ready.</>;
|
|
}
|
|
break;
|
|
case 'game-order':
|
|
if (!player) {
|
|
message = <>{message}This game as an observer as <b>{name}</b>.</>;
|
|
message = <>{message}You can chat with other players below as <b>{this.game.name}</b>, but cannot play unless players go back to the Lobby.</>;
|
|
} else {
|
|
if (!player.order) {
|
|
message = <>{message}You need to roll for game order. Click <b>Roll Dice</b> below.</>;
|
|
} else {
|
|
message = <>{message}You rolled <Dice pips={player.order}/> for game order. Waiting for all players to roll.</>;
|
|
message = <>{message}<br/><b>THIS IS THE END OF THE FUNCTIONALITY SO FAR</b></>;
|
|
}
|
|
}
|
|
break;
|
|
case 'active':
|
|
if (!player) {
|
|
message = <>{message}This game is no longer in the lobby.<br/><b>TODO: Override game state to allow Lobby mode while in-game</b></>;
|
|
} else {
|
|
message = <>{message}<br/><b>THIS IS THE END OF THE FUNCTIONALITY SO FAR</b></>;
|
|
}
|
|
break;
|
|
case null:
|
|
case undefined:
|
|
case '':
|
|
message = <>{message}The game is in a wonky state. Sorry :(</>;
|
|
break;
|
|
default:
|
|
message = <>{message}Game state is: {this.game.state}</>;
|
|
break;
|
|
}
|
|
}
|
|
this.setState({ message: message });
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.start = new Date();
|
|
|
|
const params = {};
|
|
if (this.id) {
|
|
console.log(`Loading game: ${this.id}`);
|
|
params.url = `${base}/api/v1/games/${this.id}`;
|
|
params.method = "GET"
|
|
} else {
|
|
console.log("Requesting new game.");
|
|
params.url = `${base}/api/v1/games/`;
|
|
params.method = "POST";
|
|
}
|
|
|
|
window.fetch(params.url, {
|
|
method: params.method,
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
// body: JSON.stringify(data) // body data type must match "Content-Type" header
|
|
}).then((res) => {
|
|
if (res.status < 400) {
|
|
return res;
|
|
}
|
|
let error;
|
|
if (!this.id) {
|
|
error = `Unable to create new game.`;
|
|
throw new Error(error);
|
|
}
|
|
|
|
error = `Unable to find game ${this.id}. Starting new game.`
|
|
console.log(error);
|
|
this.setState({ error: error });
|
|
|
|
params.url = `${base}/api/v1/games/${this.id}`;
|
|
params.method = "POST";
|
|
|
|
return window.fetch(params.url, {
|
|
method: params.method,
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
}).then((res) => {
|
|
return res.json();
|
|
}).then((game) => {
|
|
// console.log (`Game ${game.id} loaded ${moment().format()}.`);
|
|
|
|
if (!this.id) {
|
|
history.push(`${gamesPath}/${game.id}`);
|
|
}
|
|
|
|
this.updateGame(game);
|
|
this.updateMessage();
|
|
|
|
this.setState({ error: "" });
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
this.setState({error: error.message});
|
|
}).then(() => {
|
|
this.resetGameLoad();
|
|
});
|
|
|
|
setTimeout(this.updateDimensions, 1000);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.loadTimer) {
|
|
clearTimeout(this.loadTimer);
|
|
}
|
|
if (this.updateSizeTimer) {
|
|
clearTimeout(this.updateSizeTimer);
|
|
this.updateSizeTimer = 0;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const game = this.state.game;
|
|
|
|
return (
|
|
<div className="Table" ref={el => this.el = el}>
|
|
<Board game={game}/>
|
|
{ game && <div className={'Game ' + game.state}>
|
|
<Paper className="Message">{ this.state.message }</Paper>
|
|
{(this.state.pickName || !game.name) && <PlayerName table={this}/> }
|
|
{(!this.state.pickName && game.name) && <>
|
|
<Players table={this}/>
|
|
<Chat table={this} promoteGameState={this.promoteGameState}/>
|
|
<Action table={this}/>
|
|
</> }
|
|
</div> }
|
|
|
|
<div className="Cards" ref={el => this.cards = el}>
|
|
{ game && game.state === "active" && <>
|
|
<div>In hand</div>
|
|
<div className="Hand">
|
|
<Resource type="wood" count={this.state.wood}/>
|
|
<Resource type="wheat" count={this.state.wheat}/>
|
|
<Resource type="stone" count={this.state.stone}/>
|
|
<Resource type="brick" count={this.state.brick}/>
|
|
<Resource type="sheep" count={this.state.sheep}/>
|
|
</div>
|
|
<div>Available to play</div>
|
|
<div className="Hand">
|
|
<Development type="monopoly" count="1"/>
|
|
<Development type="army-" max="14" count="4"/>
|
|
<div className="Stack">
|
|
<Development type="vp-library" count="1"/>
|
|
<Development type="vp-market" count="1"/>
|
|
</div>
|
|
</div>
|
|
<div>Points</div>
|
|
<div className="Hand">
|
|
<div className="Stack">
|
|
<Development type="vp-library" count="1"/>
|
|
<Development type="vp-palace" count="1"/>
|
|
<Development type="army-" max="14" count="6"/>
|
|
</div>
|
|
</div>
|
|
<div className="Hand">
|
|
<Placard type="largest-army" count="1"/>
|
|
<Placard type="longest-road" count="1"/>
|
|
</div>
|
|
<div className="Statistics">
|
|
<div>Stats</div>
|
|
<div>
|
|
<div>Points: 7</div>
|
|
<div>Cards: {this.state.total} </div>
|
|
<div>Roads remaining: 4</div>
|
|
<div>Longest road: 7</div>
|
|
<div>Cities remaining: 4</div>
|
|
<div>Settlements remaining: 5</div>
|
|
</div>
|
|
</div>
|
|
</> }
|
|
</div>
|
|
|
|
{ this.state.error && <Paper className="Error"><div>{this.state.error}</div></Paper> }
|
|
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
export default withRouter(props => <Table {...props}/>);
|