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 (
);
}
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.toString()) {
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 (
);
}
const PlayerColor = ({ color }) => {
const classes = useStyles();
return (
);
};
class Placard extends React.Component {
render() {
return (
);
}
};
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 (
{ React.Children.map(array, i => (
)) }
);
}
};
class Resource extends React.Component {
render() {
const array = new Array(Number(this.props.count ? this.props.count : 0));
return (
<>
{ array.length > 0 &&
{ React.Children.map(array, i => (
)) }
}
>
);
}
};
const Chat = ({ table }) => {
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);
}
table.sendChat(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 = item.from ? item.from : item.color,
from = name ? `${name}, ` : '';
let message;
const dice = item.message.match(/^(.*rolled )([1-6])(, ([1-6]))?(.*)$/);
if (dice) {
if (dice[4]) {
message = <>{dice[1]}, {dice[5]}>;
} else {
message = <>{dice[1]}{dice[5]}>;
}
} else {
message = item.message;
}
return (
{from}
Date.now() ?
Date.now() : item.date} interval={1000}/>>)} />
);
});
const name = table.game ? table.game.name : "Why no game?";
return (
{ messages }
)} variant="outlined"/>
);
}
const StartButton = ({ table }) => {
const startClick = (event) => {
table.setGameState("game-order").then((state) => {
table.game.state = state;
});
};
return (
);
};
const WaitingForPlayer = ({table}) => {
return (
{ table.game && table.game.turn &&
Waiting for {table.game.turn.name} to complete their turn.
}
);
}
const GameOrder = ({table}) => {
const rollClick = (event) => {
table.throwDice();
}
if (!table.game) {
return (<>>);
}
let players = [], hasRolled = true;
for (let color in table.game.players) {
const item = table.game.players[color],
name = getPlayerName(table.game.sessions, color);
if (color === table.game.color) {
hasRolled = item.orderRoll !== 0;
}
if (name) {
if (!item.orderRoll) {
item.orderRoll = 0;
}
players.push({ name: name, color: color, ...item });
}
}
players.sort((A, B) => {
if (A.order === B.order) {
if (A.orderRoll === B.orderRoll) {
return A.name.localeCompare(B.name);
}
return B.orderRoll - A.orderRoll;
}
return B.order - A.order;
});
players = players.map(item =>
{item.name}
{ item.orderRoll !== 0 && <>rolled .> }
{ item.orderRoll === 0 && <>has not rolled yet. {item.orderStatus}>}
);
return (
{ table.game &&
Game Order
{ players }
}
);
};
const Action = ({ table }) => {
const newTableClick = (event) => {
return table.shuffleTable();
};
const rollClick = (event) => {
table.throwDice();
}
const passClick = (event) => {
return table.passTurn();
}
const quitClick = (event) => {
table.setSelected("");
}
if (!table.game) {
console.log("Why no game?");
return ();
}
const inLobby = table.game.state === 'lobby';
return (
{ inLobby && <>
> }
{ table.game.state === 'normal' && <>
> }
{ !inLobby &&
}
);
}
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 = () => {
if (name !== table.game.name) {
table.setPlayerName(name);
} else {
table.setState({ pickName: false, error: "" });
}
}
const nameKeyPress = (event) => {
if (event.key === "Enter") {
sendName();
}
}
return (
);
};
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) => {
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((
{ inLobby && selectable && toggleSelected(color) }}
key={`player-${color}`}>
{ item.status + ' ' }
{ item.status !== 'Not active' && Date.now() ? Date.now() : item.lastActive}/>}
>)} />
));
}
return (
{ players }
);
}
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.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.passTurn = this.passTurn.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.sendAction = this.sendAction.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;
}
sendAction(action, value, extra) {
if (this.loadTimer) {
window.clearTimeout(this.loadTimer);
this.loadTimer = null;
}
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/${action}/${value ? value : ''}`, {
method: "PUT",
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: extra ? JSON.stringify(extra) : undefined
}).then((res) => {
if (res.status >= 400) {
throw new Error(`Unable to perform ${action}!`);
}
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();
});
}
setSelected(key) {
return this.sendAction('player-selected', key);
}
sendChat(message) {
return this.sendAction('chat', undefined, {message: message});
}
setPlayerName(name) {
return this.sendAction('player-name', name)
.then(() => {
this.setState({ pickName: false });
});
}
shuffleTable() {
return this.sendAction('shuffle')
.then(() => {
this.setState({ error: "Table shuffled!" });
});
}
passTurn() {
return this.sendAction('pass');
};
rollDice() {
return this.sendAction('roll');
}
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;
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);
}
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();
}).catch((error) => {
console.error(error);
this.setState({error: error.message});
}).then(() => {
this.resetGameLoad();
return this.game.state;
});
}
placeSettlement(settlement) {
return this.sendAction('place-settlement', settlement);
}
placeRoad(road) {
return this.sendAction('place-road', road);
}
throwDice() {
return this.rollDice();
if (0) {
if (this.game.state !== 'active') {
return;
}
const sum = 0;//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
});
}
};
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 ENTER or select SET.>;
} else {
switch (this.game && this.game.state) {
case 'lobby':
message = <>{message}You are in the lobby as {name}.>;
if (!this.game.color) {
message = <>{message}You need to pick your color.>;
} else {
message = <>{message}You have selected .>;
}
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 .>;
} else {
message = <>{message}There are enough players to start the game. Select when ready.>;
}
break;
case 'game-order':
if (!player) {
message = <>{message}This game as an observer as {name}.>;
message = <>{message}You can chat with other players below as {this.game.name}, but cannot play unless players go back to the Lobby.>;
} else {
if (!player.order) {
message = <>{message}You need to roll for game order. Click Roll Dice below.>;
} else {
message = <>{message}You rolled for game order. Waiting for all players to roll.>;
message = <>{message}
THIS IS THE END OF THE FUNCTIONALITY SO FAR>;
}
}
break;
case 'active':
if (!player) {
message = <>{message}This game is no longer in the lobby.
TODO: Override game state to allow Lobby mode while in-game>;
} else {
message = <>{message}
THIS IS THE END OF THE FUNCTIONALITY SO FAR>;
}
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 (
{ game &&
{ this.state.message }
{(this.state.pickName || !game.name) &&
}
{(!this.state.pickName && game.name) && <>
> }
}
{ game && game.state === 'game-order' &&
}
{ game && game.turn && game.turn.color !== game.color &&
(game.state === 'initial-placement' || game.state === 'normal') &&
}
{ game && game.showCards &&
{ game && game.state === "active" && <>
In hand
Available to play
Points
Stats
Points: 7
Cards: {this.state.total}
Roads remaining: 4
Longest road: 7
Cities remaining: 4
Settlements remaining: 5
> }
}
{ this.state.error && {this.state.error}
}
);
}
}
export default withRouter(props => );