1
0

Players can place roads and settlements

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-05 17:29:46 -08:00
parent 0df5160428
commit d8c8b74e53
4 changed files with 138 additions and 299 deletions

View File

@ -89,15 +89,19 @@
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
box-shadow: 5px 5px 5px black; box-shadow: 5px 5px 5px black;
} }
.Selected[data-color='R'] {
[data-color='R'] {
background-color: rgba(255, 0, 0, 0.5); background-color: rgba(255, 0, 0, 0.5);
} }
.Selected[data-color='O'] {
[data-color='O'] {
background-color: rgba(255, 196, 0, 0.5); background-color: rgba(255, 196, 0, 0.5);
} }
.Selected[data-color='W'] {
[data-color='W'] {
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
} }
.Selected[data-color='B'] {
[data-color='B'] {
background-color: rgba(0, 0, 255, 0.5); background-color: rgba(0, 0, 255, 0.5);
} }

View File

@ -4,7 +4,7 @@ import "./Board.css";
const base = process.env.PUBLIC_URL; const base = process.env.PUBLIC_URL;
const assetsPath = `${base}/assets`; 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 rows = [3, 4, 5, 4, 3, 2]; /* The final row of 2 is to place roads and corners */
const [signature, setSignature] = useState(""); const [signature, setSignature] = useState("");
const [pips, setPips] = useState(<></>); const [pips, setPips] = useState(<></>);
@ -35,46 +35,6 @@ const Board = ({ game }) => {
const Tile = ({tile}) => { const Tile = ({tile}) => {
const onClick = (event) => { const onClick = (event) => {
console.log(`Tile clicked: ${tile.index}`); 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 <div className="Tile" return <div className="Tile"
@ -85,7 +45,7 @@ const Board = ({ game }) => {
left: `${tile.left}px`, left: `${tile.left}px`,
width: `${tileImageWidth}px`, width: `${tileImageWidth}px`,
height: `${tileImageHeight}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` backgroundPositionY: `-${tile.card*tileHeight}px`
}} }}
></div>; ></div>;
@ -94,33 +54,9 @@ const Board = ({ game }) => {
const Road = ({road}) => { const Road = ({road}) => {
const onClick = (event) => { const onClick = (event) => {
console.log(`Road clicked: ${road.index}`); console.log(`Road clicked: ${road.index}`);
let nodes = document.querySelectorAll('.Road.Selected'); table.placeRoad(road.index);
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');
}; };
return <div className="Road" return <div className="Road"
onClick={onClick} onClick={onClick}
data-index={road.index} data-index={road.index}
@ -135,33 +71,10 @@ const Board = ({ game }) => {
const Corner = ({corner}) => { const Corner = ({corner}) => {
const onClick = (event) => { const onClick = (event) => {
console.log(`Corner ${corner.index}:`, game.layout.corners[corner.index]); console.log(`Corner ${corner.index}:`, game.layout.corners[corner.index]);
let nodes = document.querySelectorAll('.Corner.Selected'); table.placeSettlement(corner.index);
for (let i = 0; i < nodes.length; i++) { return;
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);
}; };
return <div className="Corner" return <div className="Corner"
onClick={onClick} onClick={onClick}
data-index={corner.index} data-index={corner.index}
@ -438,8 +351,34 @@ const Board = ({ game }) => {
} }
} }
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) => { const canAction = (action) => {
console.log(game.turn);
return (game && game.turn && Array.isArray(game.turn.actions) && game.turn.actions.indexOf(action) !== -1); return (game && game.turn && Array.isArray(game.turn.actions) && game.turn.actions.indexOf(action) !== -1);
}; };

View File

@ -92,18 +92,6 @@ const PlayerColor = ({ color }) => {
<Avatar className={['PlayerColor', classes[color]].join(' ')}/> <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 { class Placard extends React.Component {
render() { render() {
@ -507,6 +495,7 @@ class Table extends React.Component {
this.setSelected = this.setSelected.bind(this); this.setSelected = this.setSelected.bind(this);
this.updateMessage = this.updateMessage.bind(this); this.updateMessage = this.updateMessage.bind(this);
this.gameSignature = this.gameSignature.bind(this); this.gameSignature = this.gameSignature.bind(this);
this.sendAction = this.sendAction.bind(this);
this.mouse = { x: 0, y: 0 }; this.mouse = { x: 0, y: 0 };
this.radius = 0.317; 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; this.id = (props.router && props.router.params.id) ? props.router.params.id : 0;
} }
setSelected(key) { sendAction(action, value, extra) {
if (this.loadTimer) { if (this.loadTimer) {
window.clearTimeout(this.loadTimer); window.clearTimeout(this.loadTimer);
this.loadTimer = null; this.loadTimer = null;
} }
return window.fetch(`${base}/api/v1/games/${this.state.game.id}/player-selected/${key}`, { 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'
}
}).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`, {
method: "PUT", method: "PUT",
cache: 'no-cache', cache: 'no-cache',
credentials: 'same-origin', credentials: 'same-origin',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ message: message }) body: extra ? JSON.stringify(extra) : undefined
}).then((res) => { }).then((res) => {
if (res.status >= 400) { if (res.status >= 400) {
throw new Error(`Unable to send chat message!`); throw new Error(`Unable to perform ${action}!`);
} }
return res.json(); return res.json();
}).then((game) => { }).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) { setPlayerName(name) {
if (this.loadTimer) { return this.sendAction('player-name', name)
window.clearTimeout(this.loadTimer); .then(() => {
this.loadTimer = null; this.setState({ pickName: false });
}
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() { shuffleTable() {
if (this.loadTimer) { return this.sendAction('shuffle')
window.clearTimeout(this.loadTimer); .then(() => {
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!" }); this.setState({ error: "Table shuffled!" });
}).catch((error) => {
console.error(error);
this.setState({error: error.message});
}).then(() => {
this.resetGameLoad();
}); });
} }
passTurn() { passTurn() {
if (this.loadTimer) { return this.sendAction('pass');
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();
});
}
rollDice() { rollDice() {
if (this.loadTimer) { return this.sendAction('roll');
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() { loadGame() {
@ -752,9 +607,6 @@ class Table extends React.Component {
return res.json(); return res.json();
}).then((game) => { }).then((game) => {
const error = (game.status !== 'success') ? game.status : undefined; const error = (game.status !== 'success') ? game.status : undefined;
//console.log (`Game ${game.id} loaded ${moment().format()}.`);
this.updateGame(game); this.updateGame(game);
this.updateMessage(); this.updateMessage();
this.setState({ error: error }); this.setState({ error: error });
@ -806,23 +658,23 @@ class Table extends React.Component {
}); });
} }
throwDice() { placeSettlement(settlement) {
dice[0].pips = dice[1].pips = 0; return this.sendAction('place-settlement', settlement);
}
placeRoad(road) {
return this.sendAction('place-road', road);
}
return this.rollDice().then((roll) => { throwDice() {
roll.forEach((value, index) => { return this.rollDice();
dice[index] = {
pips: value, if (0) {
angle: Math.random() * Math.PI * 2,
jitter: (Math.random() - 0.5) * diceSize * 0.125
};
});
if (this.game.state !== 'active') { if (this.game.state !== 'active') {
return; return;
} }
const sum = dice[0].pips + dice[1].pips; const sum = 0;//dice[0].pips + dice[1].pips;
if (sum === 7) { /* Robber! */ if (sum === 7) { /* Robber! */
if (this.state.total > 7) { if (this.state.total > 7) {
let half = Math.ceil(this.state.total * 0.5); let half = Math.ceil(this.state.total * 0.5);
@ -856,10 +708,8 @@ class Table extends React.Component {
brick: this.state.brick, brick: this.state.brick,
wheat: this.state.wheat wheat: this.state.wheat
}); });
}).catch((error) => { }
console.error(error); };
});
}
updateDimensions() { updateDimensions() {
const hasToolbar = false; const hasToolbar = false;
@ -1053,7 +903,7 @@ class Table extends React.Component {
return ( return (
<div className="Table"> <div className="Table">
<Board game={game}/> <Board table={this} game={game}/>
{ game && <div className={'Game ' + game.state}> { game && <div className={'Game ' + game.state}>
<Paper className="Message">{ this.state.message }</Paper> <Paper className="Message">{ this.state.message }</Paper>

View File

@ -165,6 +165,15 @@ const getPlayerColor = (game, player) => {
return ''; 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) => { const processGameOrder = (game, player, dice) => {
let message; let message;
@ -202,7 +211,7 @@ const processGameOrder = (game, player, dice) => {
color: getPlayerColor(game, players[0]) color: getPlayerColor(game, players[0])
}; };
addChatMessage(game, null, message); 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 { } else {
message = `There are still ties for player order!`; message = `There are still ties for player order!`;
} }
@ -341,7 +350,7 @@ const loadGame = async (id) => {
delete game.turn; delete game.turn;
} }
if (!game.status) { if (!game.placements) {
resetGame(game); resetGame(game);
} }
@ -547,7 +556,7 @@ const addChatMessage = (game, session, message) => {
const getColorFromName = (game, name) => { const getColorFromName = (game, name) => {
for (let id in game.sessions) { for (let id in game.sessions) {
if (game.sessions[id].name === name) { if (game.sessions[id].name === name) {
return color; return game.sessions[id].color;
} }
} }
return ''; return '';
@ -617,7 +626,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
} }
const name = session.name; const name = session.name;
let message; let message, index;
switch (action) { switch (action) {
case "roll": case "roll":
@ -652,33 +661,70 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, null, `It is ${next}'s turn.`); addChatMessage(game, null, `It is ${next}'s turn.`);
} }
case 'place-settlement': 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.`; error = `You cannot place an item unless the game is active.`;
break; break;
} }
if (session.color !== game.turn) { if (session.color !== game.turn.color) {
error = `It is not your turn!`; error = `It is not your turn! It is ${game.turn.name}'s turn.`;
break; break;
} }
const index = value; index = value;
if (game.corners[index] === undefined) { if (game.placements.corners[index] === undefined) {
error = `You have requested to place a settlement illegally!`; error = `You have requested to place a settlement illegally!`;
break; break;
} }
const corner = game.corners[index]; const corner = game.placements.corners[index];
if (corner.color) { if (corner.color) {
error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`; error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`;
break; break;
} }
corner.color = game.color; corner.color = session.color;
corner.type = 'settlement'; corner.type = 'settlement';
if (game.state === 'initial-placement') { if (game.state === 'initial-placement') {
game.turn.actions = ['place-road']; game.turn.actions = ['place-road'];
game.turn.limits = { corner: index }; /* road placement is limited to be near this corner index */ 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.`); 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; break;
case 'place-road': 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!`; error = `Road placement not yet implemented!`;
break; break;
case 'place-city': case 'place-city':
@ -816,20 +862,20 @@ const resetGame = (game) => {
game.state = 'lobby'; game.state = 'lobby';
game.status = { game.placements = {
corners: [], corners: [],
roads: [] roads: []
}; };
for (let i = 0; i < layout.corners.length; i++) { for (let i = 0; i < layout.corners.length; i++) {
game.status.corners[i] = { game.placements.corners[i] = {
color: undefined, color: undefined,
type: undefined type: undefined
}; };
} }
for (let i = 0; i < layout.roads.length; i++) { for (let i = 0; i < layout.roads.length; i++) {
game.status.roads[i] = { game.placements.roads[i] = {
color: undefined, color: undefined,
type: undefined type: undefined
}; };