diff --git a/client/src/Board.js b/client/src/Board.js index 4f29aab..3a7dfef 100755 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -13,7 +13,7 @@ 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 'moment'; +import moment from 'moment'; /* Start of withRouter polyfill */ // https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it @@ -106,7 +106,7 @@ const Tiles = (board) => { window.requestAnimationFrame(board.drawFrame); }); image.addEventListener("error", (event) => { - alert(`Error loading ${file}`); + this.setState({message: `Error loading ${file}`}); }); image.src = `${assetsPath}/gfx/${file}`; }); @@ -123,7 +123,7 @@ const Pips = (board) => { window.requestAnimationFrame(board.drawFrame); }); image.addEventListener("error", (event) => { - alert(`Error loading ${file}`); + this.setState({message: `Error loading ${file}`}); }); image.src = `${assetsPath}/gfx/${file}`; @@ -141,7 +141,7 @@ const Border = (board, border) => { window.requestAnimationFrame(board.drawFrame); }); image.addEventListener("error", (event) => { - alert(`Error loading ${file}`); + board.setState({ message: `Error loading ${file}` }); }); image.src = `${assetsPath}/gfx/${file}`; return border; @@ -154,31 +154,12 @@ const Table = (board) => { window.requestAnimationFrame(board.drawFrame); }); image.addEventListener("error", (event) => { - alert(`Error loading ${file}`); + board.setState({ message: `Error loading ${file}` }); }); image.src = `${assetsPath}/gfx/${file}`; return image; }; -function shuffle(array) { - var currentIndex = array.length, temporaryValue, randomIndex; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; -} - class Placard extends React.Component { render() { return ( @@ -230,7 +211,7 @@ class Resource extends React.Component { } }; -const Chat = ({ game, promoteGameState }) => { +const Chat = ({ board, promoteGameState }) => { const chatInput = (event) => { }; @@ -239,7 +220,7 @@ const Chat = ({ game, promoteGameState }) => { console.log(`Send: ${event.target.value}`); promoteGameState({ chat: { - player: game.activePlayer, + player: board.game.activePlayer, message: event.target.value } }); @@ -256,7 +237,7 @@ const Chat = ({ game, promoteGameState }) => { //const timeDelta = game.timestamp - Date.now(); - const messages = game.chat.map((item, index) => { + const messages = board.game.chat.map((item, index) => { //const timestamp = moment(item.date - timeDelta).fromNow(); return ( @@ -278,7 +259,7 @@ const Chat = ({ game, promoteGameState }) => { { messages } input && input.focus()} onChange={chatInput} onKeyPress={chatKeyPress} @@ -287,18 +268,28 @@ const Chat = ({ game, promoteGameState }) => { ); } -const Action = ({ game, promoteGameState }) => { - const actionClick = (event) => { - this.setGameState("active").then((state) => { - game.state = state; +const Action = ({ board }) => { + const startClick = (event) => { + board.setGameState("active").then((state) => { + board.game.state = state; }); }; + const newBoardClick = (event) => { + return board.shuffleBoard(); + }; + + const rollClick = (event) => { + board.throwDice(); + } + return ( <> - { game.state == 'lobby' && + { board.game.state == 'lobby' && - + + + } @@ -307,7 +298,7 @@ const Action = ({ game, promoteGameState }) => { /* This needs to take in a mechanism to declare the * player's active item in the game */ -const Players = ({ game, promoteGameState }) => { +const Players = ({ board, promoteGameState }) => { const [selected, setSelected] = useState(""); const [name, setName] = useState(""); @@ -325,26 +316,26 @@ const Players = ({ game, promoteGameState }) => { useEffect(() => { const change = { players: {} }; - /* Joining: selected != "", activePlayer == "", and name != "" */ - if (selected && !game.activePlayer && name) { + /* Joining: selected != "" and name != "" */ + if (selected && name && !board.game.activePlayer) { change.players[selected] = { name: name } promoteGameState(change) return; } - /* Leaving: selected = "", name = "", activePlayer != "" */ - if (!selected && game.activePlayer) { - change.players[game.activePlayer] = { name: "" } + /* Leaving: selected = "", name = "" */ + if (!selected && board.game.activePlayer) { + change.players[board.game.activePlayer] = { name: "" } promoteGameState(change) return; } - /* Updating name: selected != "", activePlayer != "", name != "", name != activePlayer.name*/ + /* Updating name: selected != "", name != "", name != board.name*/ if (selected && - game.activePlayer && + board.player && name && - game.players[game.activePlayer].name !== name) { - change.players[game.activePlayer] = { name: name } + board.game.players[board.game.activePlayer].name !== name) { + change.players[board.game.activePlayer] = { name: name } promoteGameState(change) return; } @@ -362,8 +353,8 @@ const Players = ({ game, promoteGameState }) => { const classes = useStyles(); const players = []; - for (let key in game.players) { - const item = game.players[key]; + for (let key in board.game.players) { + const item = board.game.players[key]; players.push(( @@ -409,7 +400,8 @@ class Board extends React.Component { brick: 0, stone: 0, wheat: 0, - game: null + game: null, + message: "" }; this.componentDidMount = this.componentDidMount.bind(this); this.updateDimensions = this.updateDimensions.bind(this); @@ -419,13 +411,13 @@ class Board extends React.Component { this.drawDie = this.drawDie.bind(this); this.keyUp = this.keyUp.bind(this); this.mouseMove = this.mouseMove.bind(this); - this.randomize = this.randomize.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.shuffleBoard = this.shuffleBoard.bind(this); this.mouse = { x: 0, y: 0 }; this.radius = 0.317; @@ -448,6 +440,44 @@ class Board extends React.Component { this.id = (props.router && props.router.params.id) ? props.router.params.id : 0; } + shuffleBoard() { + 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 (`Board shuffled!`); + this.game = game; + this.setState({ game: game, message: "" }); + this.pips = Pips(this); + this.tiles = Tiles(this); + this.table = Table(this); + + this.borders = this.game.borders.map((file) => { + return Border(this, file); + }); + }).catch((error) => { + console.error(error); + this.setState({message: error.message}); + }).then(() => { + this.resetGameLoad(); + window.requestAnimationFrame(this.drawFrame); + }); + } + rollDice() { if (this.loadTimer) { window.clearTimeout(this.loadTimer); @@ -462,7 +492,7 @@ class Board extends React.Component { 'Content-Type': 'application/json' } }).then((res) => { - if (res.status > 400) { + if (res.status >= 400) { console.log(res); throw new Error(`Unable to roll dice`); } @@ -471,10 +501,10 @@ class Board extends React.Component { console.log (`Dice rolled!`); console.log(game.dice); this.game = game; - this.setState({ game: { ...this.state.game, dice: game.dice }} ); + this.setState({ game: { ...this.state.game, dice: game.dice }, message: ""} ); }).catch((error) => { console.error(error); - alert(error); + this.setState({message: error.message}); }).then(() => { this.resetGameLoad(); return this.game.dice; @@ -487,11 +517,12 @@ class Board extends React.Component { this.loadTimer = null; } - if (this.state.game) { - //return; + if (!this.state.game) { + console.error('Attempting to loadGame with no game set'); + return; } - window.fetch(`${base}/api/v1/games/${this.state.game.id}`, { + return window.fetch(`${base}/api/v1/games/${this.state.game.id}`, { method: "GET", cache: 'no-cache', credentials: 'same-origin', @@ -499,17 +530,18 @@ class Board extends React.Component { 'Content-Type': 'application/json' } }).then((res) => { - if (res.status > 400) { + if (res.status >= 400) { console.log(res); throw new Error(`Unable to load state`); } return res.json(); }).then((game) => { - console.log (`Game state loaded.`); - this.setState({ game: game }); + console.log (`Game ${game.id} loaded ${moment().format()}.`); + this.game = game; + this.setState({ game: game, message: "" }); }).catch((error) => { console.error(error); - alert(error); + this.setState({message: error.message}); }).then(() => { this.resetGameLoad(); }); @@ -531,7 +563,7 @@ class Board extends React.Component { this.loadTimer = null; } - window.fetch(`${base}/api/v1/games/${this.state.game.id}`, { + return window.fetch(`${base}/api/v1/games/${this.state.game.id}`, { method: "PUT", cache: 'no-cache', credentials: 'same-origin', @@ -540,44 +572,24 @@ class Board extends React.Component { }, body: JSON.stringify(change) }).then((res) => { - if (res.status > 400) { + if (res.status >= 400) { console.error(res); throw new Error(`Unable to change state`); } return res.json(); }).then((game) => { console.log (`Game state changed.`); - this.setState({ game: game }); + this.game = game; + this.setState({ game: game, message: "" }); }).catch((error) => { console.error(error); - alert(error); + this.setState({message: error.message}); }).then(() => { this.resetGameLoad(); }); } - randomize() { - //this.borders = shuffle(this.borders); - this.tiles = shuffle(this.tiles); - this.tiles.forEach((tile) => { - tile.roads = []; - tile.settlements = []; - tile.jitter = Math.random() - 0.5; - }); - this.closest.tile = null; - window.requestAnimationFrame(this.drawFrame); - } - keyUp(event) { - if (event.keyCode === 78) { /* n */ - this.randomize(); - return; - } - - if (event.keyCode === 32) { /* space */ - this.throwDice(); - return; - } } setGameState(state) { @@ -594,7 +606,7 @@ class Board extends React.Component { 'Content-Type': 'application/json' } }).then((res) => { - if (res.status > 400) { + if (res.status >= 400) { console.log(res); throw new Error(`Unable to set state to ${state}`); } @@ -602,10 +614,10 @@ class Board extends React.Component { }).then((game) => { console.log (`Game state set to ${game.state}!`); this.game = game; - this.setState({ game: { ...this.state.game, state: game.state }} ); + this.setState({ game: { ...this.state.game, state: game.state }, message: ""} ); }).catch((error) => { console.error(error); - alert(error); + this.setState({message: error.message}); }).then(() => { this.resetGameLoad(); return this.game.state; @@ -754,11 +766,13 @@ class Board extends React.Component { this.width = window.innerWidth; this.height = height; - this.canvas.width = this.width; - this.canvas.height = this.height; - this.canvas.style.top = `${offset}px`; - this.canvas.style.width = `${this.width}px`; - this.canvas.style.height = `${this.height}px`; + if (this.canvas) { + this.canvas.width = this.width; + this.canvas.height = this.height; + this.canvas.style.top = `${offset}px`; + this.canvas.style.width = `${this.width}px`; + this.canvas.style.height = `${this.height}px`; + } this.cards.style.top = `${offset}px`; this.cards.style.width = `${this.width}px`; this.cards.style.height = `${this.heigh}tpx`; @@ -1158,26 +1172,42 @@ class Board extends React.Component { }, // body: JSON.stringify(data) // body data type must match "Content-Type" header }).then((res) => { - if (res.status > 400) { - let message; - if (this.id) { - message = `Unable to find game ${this.id}. Starting new game.`; - } else { - message = `Starting new game.`; - } - console.log(message); + if (res.status < 400) { + return res; + } + let message; + if (!this.id) { + message = `Unable to create new game.`; throw new Error(message); } + + message = `Unable to find game ${this.id}. Starting new game.` + console.log(message); + this.setState({ message: message }); + + params.url = `${base}/api/v1/games`; + 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.`); + console.log (`Game ${game.id} loaded ${moment().format()}.`); if (!this.id) { history.push(`${gamesPath}/${game.id}`); } this.game = game; - this.setState({ game: game }); + this.setState({ game: game, message: "" }); + this.pips = Pips(this); this.tiles = Tiles(this); this.table = Table(this); @@ -1187,7 +1217,7 @@ class Board extends React.Component { }); }).catch((error) => { console.error(error); - alert(error); + this.setState({message: error.message}); }).then(() => { this.resetGameLoad(); }); @@ -1196,6 +1226,9 @@ class Board extends React.Component { } componentWillUnmount() { + if (this.loadTimer) { + clearTimeout(this.loadTimer); + } if (this.updateSizeTimer) { clearTimeout(this.updateSizeTimer); this.updateSizeTimer = 0; @@ -1214,9 +1247,10 @@ class Board extends React.Component {
this.cards = el}> { game &&
- - - + + + + { this.state.message != "" &&
{this.state.message}
}
}
In hand
diff --git a/server/routes/games.js b/server/routes/games.js index 7818443..b4944db 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -120,17 +120,45 @@ const roll = (game, player) => { } } -router.put("/:id/:action", (req, res) => { +router.put("/:id/:action/:value?", (req, res) => { console.log(`PUT games/${req.params.id}/${req.params.action}`); if (!req.params.action in games) { const error = `Game not found: ${req.params.id}`; return res.status(404).send(error); } + const game = games[req.params.id]; + + if (!req.session.activePlayer || !req.session.activePlayer in game.players) { + const error = `Invalid player: ${req.session.activePlayer}`; + return res.status(404).send(error); + } + + const player = game.players[req.session.activePlayer].name; + switch (req.params.action) { case "roll": - roll(game, req.session.activePlayer); + roll(game, player); break; + case "shuffle": + if (game.state === "lobby") { + shuffleBoard(game); + const message = `${player} requested a new board.`; + game.chat.push({ date: Date.now(), message: message }); + console.log(message); + return sendGame(res, req, game); + } else { + const error = `Game no longer in lobby (${game.state}). Can not shuffle board.`; + return res.status(400).send(error) + } + case "state": + const state = req.params.value ? req.params.value : "active"; + if (state != game.state) { + game.state = state; + const message = `${player} set game state to ${state}.`; + game.chat.push({ date: Date.now(), message: message }); + } + return sendGame(res, req, game); } return sendGame(res, req, game); @@ -230,6 +258,14 @@ router.post("/", (req, res/*, next*/) => { id: crypto.randomBytes(8).toString('hex') }; + games[game.id] = game; + req.session.activePlayer = null; + shuffleBoard(game); + console.log(`New game created: ${game.id}`); + return sendGame(res, req, game); +}); + +const shuffleBoard = (game) => { [ "tiles", "pips", "borders" ].forEach((field) => { game[field] = [] for (let i = 0; i < assetData[field].length; i++) { @@ -244,13 +280,7 @@ router.post("/", (req, res/*, next*/) => { }); shuffle(game.developmentCards) - - games[game.id] = game; - - console.log(`New game created: ${game.id}`); - return sendGame(res, req, game); -}); - +} /* return gameDB.sequelize.query("SELECT " +