diff --git a/client/src/Board.css b/client/src/Board.css index 39dc7c6..c325d97 100644 --- a/client/src/Board.css +++ b/client/src/Board.css @@ -89,15 +89,19 @@ background-color: rgba(0, 0, 0, 0.5); box-shadow: 5px 5px 5px black; } -.Selected[data-color='R'] { + +[data-color='R'] { background-color: rgba(255, 0, 0, 0.5); } -.Selected[data-color='O'] { + +[data-color='O'] { background-color: rgba(255, 196, 0, 0.5); } -.Selected[data-color='W'] { + +[data-color='W'] { background-color: rgba(255, 255, 255, 0.5); } -.Selected[data-color='B'] { + +[data-color='B'] { background-color: rgba(0, 0, 255, 0.5); } \ No newline at end of file diff --git a/client/src/Board.js b/client/src/Board.js index 341c856..4d0baa7 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -4,7 +4,7 @@ import "./Board.css"; const base = process.env.PUBLIC_URL; const assetsPath = `${base}/assets`; -const Board = ({ game }) => { +const Board = ({ table, game }) => { const rows = [3, 4, 5, 4, 3, 2]; /* The final row of 2 is to place roads and corners */ const [signature, setSignature] = useState(""); const [pips, setPips] = useState(<>); @@ -35,46 +35,6 @@ const Board = ({ game }) => { const Tile = ({tile}) => { const onClick = (event) => { console.log(`Tile clicked: ${tile.index}`); - let nodes = document.querySelectorAll('.Tile.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - nodes = document.querySelectorAll('.Corner.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - nodes = document.querySelectorAll('.Road.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - game.layout.tiles[tile.index].corners.forEach(index => { - const el = document.querySelector(`.Corner[data-index="${index}"]`); - if (!el) { - console.log(`Unable to find corner[${index}]`); - } else { - el.classList.add('Selected'); - } - }); - game.layout.tiles[tile.index].roads.forEach(index => { - const el = document.querySelector(`.Road[data-index="${index}"]`); - if (!el) { - console.log(`Unable to find corner[${index}]`); - } else { - el.classList.add('Selected'); - } - }); - - event.target.setAttribute("data-color", game.color); - event.target.classList.toggle('Selected'); }; return
{ left: `${tile.left}px`, width: `${tileImageWidth}px`, height: `${tileImageHeight}px`, - backgroundImage: `url(${assetsPath}/gfx/tiles-${tile.type}.png)`, + backgroundImage: `url(${assetsPath}/gfx/tiles-${tile.type}.png)`, backgroundPositionY: `-${tile.card*tileHeight}px` }} >
; @@ -94,33 +54,9 @@ const Board = ({ game }) => { const Road = ({road}) => { const onClick = (event) => { console.log(`Road clicked: ${road.index}`); - let nodes = document.querySelectorAll('.Road.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - nodes = document.querySelectorAll('.Corner.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - - game.layout.roads[road.index].corners.forEach(index => { - const el = document.querySelector(`.Corner[data-index="${index}"]`); - if (!el) { - console.log(`Unable to find corner[${index}]`); - } else { - el.classList.add('Selected'); - } - }); - - event.target.setAttribute("data-color", game.color); - event.target.classList.toggle('Selected'); + table.placeRoad(road.index); }; + return
{ const Corner = ({corner}) => { const onClick = (event) => { console.log(`Corner ${corner.index}:`, game.layout.corners[corner.index]); - let nodes = document.querySelectorAll('.Corner.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - nodes = document.querySelectorAll('.Road.Selected'); - for (let i = 0; i < nodes.length; i++) { - const el = nodes[i]; - if (el !== event.target) { - el.classList.remove('Selected'); - } - } - - game.layout.corners[corner.index].roads.forEach(index => { - const el = document.querySelector(`.Road[data-index="${index}"]`); - if (!el) { - console.log(`Unable to find road[${index}]`); - } else { - el.classList.add('Selected'); - } - }); - - event.target.classList.toggle('Selected'); - event.target.setAttribute("data-color", game.color); + table.placeSettlement(corner.index); + return; }; + return
{ } } + if (game && game.placements) { + game.placements.corners.forEach((corner, index) => { + const el = document.querySelector(`.Corner[data-index="${index}"]`); + if (!el) { + return; + } + if (!corner.color) { + el.removeAttribute('data-color'); + el.removeAttribute('data-type'); + } else { + el.setAttribute('data-color', corner.color); + el.setAttribute('data-type', corner.type); + } + }); + game.placements.roads.forEach((road, index) => { + const el = document.querySelector(`.Road[data-index="${index}"]`); + if (!el) { + return; + } + if (!road.color) { + el.removeAttribute('data-color'); + } else { + el.setAttribute('data-color', road.color); + } + }); + } + const canAction = (action) => { - console.log(game.turn); return (game && game.turn && Array.isArray(game.turn.actions) && game.turn.actions.indexOf(action) !== -1); }; diff --git a/client/src/Table.js b/client/src/Table.js index 5fec9ff..c3b3f5f 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -92,18 +92,6 @@ const PlayerColor = ({ color }) => { ); }; - -const diceSize = 0.05, - dice = [ { - pips: 0, - jitter: 0, - angle: 0 - }, { - pips: 0, - jitter: 0, - angle: 0 - } ]; - class Placard extends React.Component { render() { @@ -507,6 +495,7 @@ class Table extends React.Component { 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; @@ -529,54 +518,23 @@ class Table extends React.Component { this.id = (props.router && props.router.params.id) ? props.router.params.id : 0; } - setSelected(key) { + 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}/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(); - }); - } - - sendChat(message) { - if (this.loadTimer) { - window.clearTimeout(this.loadTimer); - this.loadTimer = null; - } - - return window.fetch(`${base}/api/v1/games/${this.state.game.id}/chat`, { + 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: JSON.stringify({ message: message }) + body: extra ? JSON.stringify(extra) : undefined }).then((res) => { if (res.status >= 400) { - throw new Error(`Unable to send chat message!`); + throw new Error(`Unable to perform ${action}!`); } return res.json(); }).then((game) => { @@ -592,138 +550,35 @@ class Table extends React.Component { }); } + setSelected(key) { + return this.sendAction('player-selected', key); + } + + sendChat(message) { + return this.sendAction('chat', undefined, {message: message}); + } + 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(); + return this.sendAction('player-name', name) + .then(() => { + this.setState({ pickName: false }); }); } 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(); + return this.sendAction('shuffle') + .then(() => { this.setState({ error: "Table shuffled!" }); - }).catch((error) => { - console.error(error); - this.setState({error: error.message}); - }).then(() => { - this.resetGameLoad(); }); } passTurn() { - if (this.loadTimer) { - window.clearTimeout(this.loadTimer); - this.loadTimer = null; - } - - return window.fetch(`${base}/api/v1/games/${this.state.game.id}/pass`, { - method: "PUT", - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - } - }).then((res) => { - if (res.status >= 400) { - throw new Error(`Unable to pass!`); - } - return res.json(); - }).then((game) => { - this.updateGame(game); - this.updateMessage(); - }).catch((error) => { - console.error(error); - this.setState({error: error.message}); - }).then(() => { - this.resetGameLoad(); - }); - } + return this.sendAction('pass'); + }; 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; - }); + return this.sendAction('roll'); } loadGame() { @@ -752,9 +607,6 @@ class Table extends React.Component { 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 }); @@ -806,23 +658,23 @@ class Table extends React.Component { }); } - throwDice() { - dice[0].pips = dice[1].pips = 0; + placeSettlement(settlement) { + return this.sendAction('place-settlement', settlement); + } + + placeRoad(road) { + return this.sendAction('place-road', road); + } - 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 - }; - }); - + throwDice() { + return this.rollDice(); + + if (0) { if (this.game.state !== 'active') { return; } - const sum = dice[0].pips + dice[1].pips; + 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); @@ -856,10 +708,8 @@ class Table extends React.Component { brick: this.state.brick, wheat: this.state.wheat }); - }).catch((error) => { - console.error(error); - }); - } + } + }; updateDimensions() { const hasToolbar = false; @@ -1053,7 +903,7 @@ class Table extends React.Component { return (
- + { game &&
{ this.state.message } diff --git a/server/routes/games.js b/server/routes/games.js index 5624fba..9b31f28 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -165,6 +165,15 @@ const getPlayerColor = (game, player) => { return ''; } +const playerNameFromColor = (game, color) => { + for (let id in game.sessions) { + if (game.sessions[id].color === color) { + return game.sessions[id].name; + } + } + return ''; +}; + const processGameOrder = (game, player, dice) => { let message; @@ -202,7 +211,7 @@ const processGameOrder = (game, player, dice) => { color: getPlayerColor(game, players[0]) }; addChatMessage(game, null, message); - message = `It is ${game.turn.name}'s turn.`; + message = `It is ${game.turn.name}'s turn to place a settlement.`; } else { message = `There are still ties for player order!`; } @@ -341,7 +350,7 @@ const loadGame = async (id) => { delete game.turn; } - if (!game.status) { + if (!game.placements) { resetGame(game); } @@ -547,7 +556,7 @@ const addChatMessage = (game, session, message) => { const getColorFromName = (game, name) => { for (let id in game.sessions) { if (game.sessions[id].name === name) { - return color; + return game.sessions[id].color; } } return ''; @@ -617,7 +626,7 @@ router.put("/:id/:action/:value?", async (req, res) => { } const name = session.name; - let message; + let message, index; switch (action) { case "roll": @@ -652,33 +661,70 @@ router.put("/:id/:action/:value?", async (req, res) => { addChatMessage(game, null, `It is ${next}'s turn.`); } case 'place-settlement': - if (game.state !== 'initial-placement' || game.state !== 'normal') { + if (game.state !== 'initial-placement' && game.state !== 'normal') { error = `You cannot place an item unless the game is active.`; break; } - if (session.color !== game.turn) { - error = `It is not your turn!`; + if (session.color !== game.turn.color) { + error = `It is not your turn! It is ${game.turn.name}'s turn.`; break; } - const index = value; - if (game.corners[index] === undefined) { + index = value; + if (game.placements.corners[index] === undefined) { error = `You have requested to place a settlement illegally!`; break; } - const corner = game.corners[index]; + const corner = game.placements.corners[index]; if (corner.color) { error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`; break; } - corner.color = game.color; + corner.color = session.color; corner.type = 'settlement'; if (game.state === 'initial-placement') { game.turn.actions = ['place-road']; game.turn.limits = { corner: index }; /* road placement is limited to be near this corner index */ addChatMessage(game, session, `Placed a settlement. Next, they need to place a road.`); + } else { + error = `Settlement placement not enabled for normal game play.`; + break; } break; case 'place-road': + if (game.state !== 'initial-placement' && game.state !== 'normal') { + error = `You cannot place an item unless the game is active.`; + break; + } + if (session.color !== game.turn.color) { + error = `It is not your turn! It is ${game.turn.name}'s turn.`; + break; + } + index = value; + if (game.placements.roads[index] === undefined) { + error = `You have requested to place a road illegally!`; + break; + } + const road = game.placements.roads[index]; + if (road.color) { + error = `This location already has a road belonging to ${playerNameFromColor(game, road.color)}!`; + break; + } + if (game.state === 'initial-placement') { + console.log('TODO: Make sure this road is connected to the settlement!'); + road.color = session.color; + const next = getNextPlayer(game, name); + game.turn = { + actions: ['place-settlement'], + name: next, + color: getColorFromName(game, next) + }; + addChatMessage(game, session, `${name} placed a road.`); + addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`); + } else { + error = `Road placement not enabled for normal game play.`; + break; + } + break; error = `Road placement not yet implemented!`; break; case 'place-city': @@ -816,20 +862,20 @@ const resetGame = (game) => { game.state = 'lobby'; - game.status = { + game.placements = { corners: [], roads: [] }; for (let i = 0; i < layout.corners.length; i++) { - game.status.corners[i] = { + game.placements.corners[i] = { color: undefined, type: undefined }; } for (let i = 0; i < layout.roads.length; i++) { - game.status.roads[i] = { + game.placements.roads[i] = { color: undefined, type: undefined };