1
0

Added more Robber processing

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-10 18:17:56 -08:00
parent e264f0ae8d
commit 37467c7377
7 changed files with 282 additions and 30 deletions

View File

@ -33,13 +33,15 @@
background-size: 600% auto; /* pip-numbers is a 6x6 grid of pip images */ background-size: 600% auto; /* pip-numbers is a 6x6 grid of pip images */
width: 2em; width: 2em;
height: 2em; height: 2em;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.Pip.Active { .Pip.Active {
filter: drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9)); filter: drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
} }
.Pips[disabled],
.Tiles[disabled],
.Roads[disabled], .Roads[disabled],
.Corners[disabled] { .Corners[disabled] {
pointer-events: none; pointer-events: none;
@ -107,19 +109,40 @@
right: 0px; right: 0px;
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
clip-path: circle(50%); /* clip-path: circle(50%);*/
} }
.Tile-Shape {
display: flex;
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
clip-path: polygon(25% 0%,75% 0%,100% 50%,75% 100%,25% 100%,0% 50%);
}
.Option { .Option {
cursor: pointer;
pointer-events: all; pointer-events: all;
} }
.Option .Pip-Shape,
.Option .Tile-Shape,
.Option .Corner-Shape, .Option .Corner-Shape,
.Option .Road-Shape { .Option .Road-Shape {
background-color: rgba(255, 255, 255, 0.5); background-color: rgba(255, 255, 255, 0.5);
} }
.Robber .Pip-Shape,
.Pip-Shape:hover {
clip-path: circle(45%); /* show through the border */
background-size: contain;
background-position: center;
background-image:url("./assets/woman-robber.png");
}
.Tile-Shape:hover,
.Corner-Shape:hover, .Corner-Shape:hover,
.Road-Shape:hover { .Road-Shape:hover {
background-color: white; background-color: white;

View File

@ -47,8 +47,8 @@ const Board = ({ table, game }) => {
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 className="Tile-Shape"/></div>;
}; };
const Road = ({road}) => { const Road = ({road}) => {
@ -85,6 +85,27 @@ const Board = ({ table, game }) => {
><div className="Corner-Shape"/></div>; ><div className="Corner-Shape"/></div>;
}; };
const Pip = ({pip}) => {
const onClick = (event) => {
console.log(`Pip ${pip.index}:`, game.layout.corners[pip.index]);
table.placeRobber(pip.index);
return;
};
return <div className={`Pip${pip.robber ? ' Robber' : ''}`}
onClick={onClick}
data-roll={pip.roll}
data-index={pip.index}
style={{
top: `${pip.top}px`,
left: `${pip.left}px`,
backgroundImage: `url(${assetsPath}/gfx/pip-numbers.png)`,
backgroundPositionX: `${ 100. * (pip.order % 6) / 5.}%`,
backgroundPositionY: `${ 100 * Math.floor(pip.order / 6) / 5. }%`
}}
><div className="Pip-Shape"/></div>;
};
const generateRoads = () => { const generateRoads = () => {
let row = 0, rowCount = 0; let row = 0, rowCount = 0;
let y = -2.5 + tileHalfWidth - (rows.length - 1) * 0.5 * tileWidth, let y = -2.5 + tileHalfWidth - (rows.length - 1) * 0.5 * tileWidth,
@ -229,20 +250,21 @@ const Board = ({ table, game }) => {
let row = 0, rowCount = 0; let row = 0, rowCount = 0;
let y = tileHalfWidth - (rows.length - 1) * 0.5 * tileWidth, let y = tileHalfWidth - (rows.length - 1) * 0.5 * tileWidth,
x = -(rows[row] - 1) * 0.5 * tileHeight; x = -(rows[row] - 1) * 0.5 * tileHeight;
let index = 0;
let pip;
return game.pipOrder.map(order => { return game.pipOrder.map(order => {
const pip = game.pips[order]; pip = {
const div = <div roll: game.pips[order].roll,
data-roll={pip.roll} robber: index === game.robber,
index: index++,
top: y,
left: x,
order: order
};
const div = <Pip
pip={pip}
key={`pip-${order}`} key={`pip-${order}`}
className="Pip" />;
style={{
top: `${y}px`,
left: `${x}px`,
backgroundImage: `url(${assetsPath}/gfx/pip-numbers.png)`,
backgroundPositionX: `${ 100. * (order % 6) / 5.}%`,
backgroundPositionY: `${ 100 * Math.floor(order / 6) / 5. }%`
}}
><div className="Pip-Shape"/></div>;
if (++rowCount === rows[row]) { if (++rowCount === rows[row]) {
row++; row++;
@ -389,10 +411,11 @@ const Board = ({ table, game }) => {
} }
}); });
/* Clear all 'Option' targets */ /* Clear all 'Option' targets */
const nodes = document.querySelectorAll(`.Option`); let nodes = document.querySelectorAll(`.Option`);
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
nodes[i].classList.remove('Option'); nodes[i].classList.remove('Option');
} }
/* Add 'Option' based on game.turn.limits */ /* Add 'Option' based on game.turn.limits */
if (game.turn && game.turn.limits) { if (game.turn && game.turn.limits) {
if (game.turn.limits['roads']) { if (game.turn.limits['roads']) {
@ -413,6 +436,38 @@ const Board = ({ table, game }) => {
el.classList.add('Option'); el.classList.add('Option');
}); });
} }
if (game.turn.limits['tiles']) {
game.turn.limits['tiles'].forEach(index => {
const el = document.querySelector(`.Tile[data-index="${index}"]`);
if (!el) {
return;
}
el.classList.add('Option');
});
}
if (game.turn.limits['pips']) {
game.turn.limits['pips'].forEach(index => {
const el = document.querySelector(`.Pip[data-index="${index}"]`);
if (!el) {
return;
}
el.classList.add('Option');
});
}
}
}
if (game) {
let nodes = document.querySelectorAll(`.Pip .Robber`);
for (let i = 0; i < nodes.length; i++) {
nodes[i].classList.remove('Robber');
}
if (game.robber) {
const el = document.querySelector(`.Pip[data-index="${game.robber}"]`);
if (el) {
el.classList.add('Robber');
}
} }
} }
@ -424,9 +479,13 @@ const Board = ({ table, game }) => {
<div className="Board"> <div className="Board">
<div className="BoardBox"> <div className="BoardBox">
{ borders } { borders }
{ tiles }
{ pips }
{ game && <> { game && <>
<div className="Tiles" disabled>
{ tiles }
</div>
<div className="Pips" disabled={!canAction('place-robber') || game.turn.color !== game.color || (game.state !== 'initial-placement' && game.state !== 'normal')}>
{ pips }
</div>
<div className="Corners" disabled={!canAction('place-settlement') || game.turn.color !== game.color || (game.state !== 'initial-placement' && game.state !== 'normal')}> <div className="Corners" disabled={!canAction('place-settlement') || game.turn.color !== game.color || (game.state !== 'initial-placement' && game.state !== 'normal')}>
{ corners } { corners }
</div> </div>

View File

@ -306,9 +306,15 @@
} }
.Hand { .Hand {
display: flex;
min-height: calc(7.2em + 0.5em); min-height: calc(7.2em + 0.5em);
} }
.Hand > button {
align-self: center;
justify-self: center;
}
.Hand:hover .Stack:hover > *:not(:first-child) { .Hand:hover .Stack:hover > *:not(:first-child) {
margin-left: -2em; margin-left: -2em;
} }

View File

@ -136,6 +136,7 @@ const Resource = ({ type, count }) => {
<div className="Stack"> <div className="Stack">
{ React.Children.map(array, i => ( { React.Children.map(array, i => (
<div className="Resource" <div className="Resource"
data-type={type}
onClick={select} onClick={select}
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}> style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
</div> </div>
@ -336,6 +337,16 @@ const GameOrder = ({table}) => {
}; };
const Action = ({ table }) => { const Action = ({ table }) => {
const discardClick = (event) => {
const nodes = document.querySelectorAll('.Hand .Selected'),
discarding = { wheat: 0, brick: 0, sheep: 0, stone: 0, wood: 0 };
for (let i = 0; i < nodes.length; i++) {
discarding[nodes[i].getAttribute("data-type")]++;
nodes[i].classList.remove('Selected');
}
return table.discard(discarding);
}
const newTableClick = (event) => { const newTableClick = (event) => {
return table.shuffleTable(); return table.shuffleTable();
}; };
@ -357,7 +368,9 @@ const Action = ({ table }) => {
return (<Paper className="Action"/>); return (<Paper className="Action"/>);
} }
const inLobby = table.game.state === 'lobby'; const inLobby = table.game.state === 'lobby',
player = table.game ? table.game.player : undefined;
return ( return (
<Paper className="Action"> <Paper className="Action">
{ inLobby && <> { inLobby && <>
@ -368,6 +381,9 @@ const Action = ({ table }) => {
<Button disabled={(table.game.turn.color !== table.game.color || table.game.turn.roll) ? true : false} onClick={rollClick}>Roll Dice</Button> <Button disabled={(table.game.turn.color !== table.game.color || table.game.turn.roll) ? true : false} onClick={rollClick}>Roll Dice</Button>
<Button disabled>Trade</Button> <Button disabled>Trade</Button>
<Button disabled>Build</Button> <Button disabled>Build</Button>
{ table.game.turn.roll === 7 && player && player.mustDiscard > 0 &&
<Button onClick={discardClick}>Discard</Button>
}
<Button disabled={(table.game.turn.color !== table.game.color || !table.game.turn.roll) ? true : false} onClick={passClick}>Done</Button> <Button disabled={(table.game.turn.color !== table.game.color || !table.game.turn.roll) ? true : false} onClick={passClick}>Done</Button>
</> } </> }
{ !inLobby && { !inLobby &&
@ -493,6 +509,7 @@ class Table extends React.Component {
this.rollDice = this.rollDice.bind(this); this.rollDice = this.rollDice.bind(this);
this.setGameState = this.setGameState.bind(this); this.setGameState = this.setGameState.bind(this);
this.shuffleTable = this.shuffleTable.bind(this); this.shuffleTable = this.shuffleTable.bind(this);
this.discard = this.discard.bind(this);
this.passTurn = this.passTurn.bind(this); this.passTurn = this.passTurn.bind(this);
this.updateGame = this.updateGame.bind(this); this.updateGame = this.updateGame.bind(this);
this.setPlayerName = this.setPlayerName.bind(this); this.setPlayerName = this.setPlayerName.bind(this);
@ -577,6 +594,10 @@ class Table extends React.Component {
}); });
} }
discard(resources) {
return this.sendAction('discard', undefined, resources);
}
passTurn() { passTurn() {
return this.sendAction('pass'); return this.sendAction('pass');
}; };
@ -662,6 +683,10 @@ class Table extends React.Component {
}); });
} }
placeRobber(robber) {
return this.sendAction('place-robber', robber);
};
placeSettlement(settlement) { placeSettlement(settlement) {
return this.sendAction('place-settlement', settlement); return this.sendAction('place-settlement', settlement);
} }
@ -813,6 +838,24 @@ class Table extends React.Component {
case '': case '':
message = <>{message}The game is in a wonky state. Sorry :(</>; message = <>{message}The game is in a wonky state. Sorry :(</>;
break; break;
case 'normal':
if (this.game && this.game.turn) {
if (this.game.turn.roll === 7) {
message = <>{message}Robber was rolled!</>;
let move = true;
for (let color in this.game.players) {
const discard = this.game.players[color].mustDiscard;
if (discard) {
move = false;
message = <>{message}<PlayerColor color={color}/> needs to discard {discard} resources.</>;
}
}
if (move) {
message = <>{message}<PlayerColor color={this.game.turn.color}/> {this.game.turn.name} needs to move the robber.</>
}
}
}
break;
default: default:
message = <>{message}Game state is: {this.game.state}</>; message = <>{message}Game state is: {this.game.state}</>;
break; break;
@ -909,15 +952,16 @@ class Table extends React.Component {
<div className="Table"> <div className="Table">
<div style={{display: "inline-flex", flex: 1, flexDirection: "column"}}> <div style={{display: "inline-flex", flex: 1, flexDirection: "column"}}>
<Board table={this} game={game}/> <Board table={this} game={game}/>
{ player !== undefined && <div className="Hand'"> { player !== undefined && <div className="Hand">
<Resource type="wood" count={player.wood}/> <Resource type="wood" count={player.wood}/>
<Resource type="wheat" count={player.wheat}/> <Resource type="wheat" count={player.wheat}/>
<Resource type="stone" count={player.stone}/> <Resource type="stone" count={player.stone}/>
<Resource type="brick" count={player.brick}/> <Resource type="brick" count={player.brick}/>
<Resource type="sheep" count={player.sheep}/> <Resource type="sheep" count={player.sheep}/>
</div> } </div> }
</div> </div>
{ 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>
{(this.state.pickName || !game.name) && <PlayerName table={this}/> } {(this.state.pickName || !game.name) && <PlayerName table={this}/> }
@ -934,7 +978,7 @@ class Table extends React.Component {
{ game && game.turn && game.turn.color !== game.color && { game && game.turn && game.turn.color !== game.color &&
(game.state === 'initial-placement' || game.state === 'normal') && (game.state === 'initial-placement' || game.state === 'normal') &&
<WaitingForPlayer table={this}/> (!game.player || !game.player.mustDiscard) && <WaitingForPlayer table={this}/>
} }
{ game && game.showCards && { game && game.showCards &&

BIN
client/src/assets/man-robber.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -291,7 +291,11 @@ const distributeResources = (game, roll) => {
for (let i = 0; i < game.pipOrder.length; i++) { for (let i = 0; i < game.pipOrder.length; i++) {
let index = game.pipOrder[i]; let index = game.pipOrder[i];
if (assetData.pips[index].roll === roll) { if (assetData.pips[index].roll === roll) {
tiles.push(i); if (game.robber === i) {
addChatMessage(game, null, `That pesky Robber stole resources!`);
} else {
tiles.push(i);
}
} }
} }
@ -350,10 +354,40 @@ const processRoll = (game, dice) => {
game.turn.roll = game.dice[0] + game.dice[1]; game.turn.roll = game.dice[0] + game.dice[1];
if (game.turn.roll === 7) { if (game.turn.roll === 7) {
addChatMessage(game, null, `ROBBER! ROBBER! ROBBER!`); addChatMessage(game, null, `ROBBER! ROBBER! ROBBER!`);
for (let id in game.sessions) {
const player = game.sessions[id].player;
if (player) {
let discard = player.stone + player.wheat + player.brick + player.wood + player.sheep;
if (discard > 7) {
discard = Math.floor(discard / 2);
player.mustDiscard = discard;
addChatMessage(game, null, `${game.sessions[id].name} must discard ${discard} resource cards.`);
} else {
delete player.mustDiscard;
}
}
}
/*
if you roll a 7, no one receives any resource cards.
instead, every player who has more than 7 resource cards must select
half (rounded down) of their
resource cards and return them to the bank.
then you muyst move the robber:
1. you must move the robber immediately to the number token of any other
terrain ohex or to the desert hex,
2. you then steal 1 (random) resourcde card from an opponent who has a settlement or city
adjacent to the target terrain hex. the player who is robbed holds their resource cards
face down. you then take 1 card at random. if the target hex is adjacent to 2 or
more player's settlements or cities, you choose which one you want to steal from.
If the production number for the hex containing the robber is rolled, the owners of
adjacent settlements and citieis do not receive resourcres. The robber prevents it.
*/
} else { } else {
distributeResources(game, game.turn.roll); distributeResources(game, game.turn.roll);
} }
} }
const getPlayer = (game, color) => { const getPlayer = (game, color) => {
if (!game) { if (!game) {
return { return {
@ -846,6 +880,34 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, null, `It is ${next}'s turn.`); addChatMessage(game, null, `It is ${next}'s turn.`);
} }
break; break;
case 'place-robber':
if (game.state !== 'normal' && game.turn.roll !== 7) {
error = `You cannot place robber unless 7 was rolled!`;
break;
}
if (game.turn.name !== name) {
error = `You cannot place the robber when it isn't your turn.`;
break;
}
for (let color in game.players) {
if (game.players[color].status === 'Not active') {
continue;
}
if (game.players[color].mustDiscard > 0) {
error = `You cannot place the robber until everyone has discarded!`;
break;
}
}
const robber = parseInt(value);
if (game.robber === robber) {
error = `You must move the robber to a new location!`;
break;
}
game.robber = robber;
game.turn.placedRobber = true;
addChatMessage(game, session, `Robber has been moved!`);
addChatMessage(game, null, 'TODO: Look up which players are on tile, then allow robber-roller to select which player to steal from.');
break;
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.`;
@ -979,6 +1041,33 @@ router.put("/:id/:action/:value?", async (req, res) => {
case 'place-city': case 'place-city':
error = `City placement not yet implemented!`; error = `City placement not yet implemented!`;
break; break;
case 'discard':
if (game.turn.roll !== 7) {
error = `You can only discard due to the Robber!`;
break;
}
const discards = req.body, player = session.player;
let sum = 0;
for (let type in discards) {
if (player[type] < parseInt(discards[type])) {
error = `You have requested to discard more ${type} than you have.`
break;
}
sum += parseInt(discards[type]);
}
if (sum > player.mustDiscard) {
error = `You have requested to discard more cards than you are allowed!`;
break;
}
for (let type in discards) {
player[type] -= parseInt(discards[type]);
player.mustDiscard -= parseInt(discards[type])
}
addChatMessage(game, null, `${session.name} discarded ${sum} resource cards.`);
if (player.mustDiscard) {
addChatMessage(game, null, `${session.name} must discard ${player.mustDiscard} more cards.`);
}
break;
case "state": case "state":
const state = value; const state = value;
if (!state) { if (!state) {
@ -1048,6 +1137,28 @@ const sendGame = async (req, res, game, error) => {
} }
game.active = active; game.active = active;
/* If the current turn is a robber placement, and everyone has
* discarded, set the limits for where the robber can be placed */
if (game.turn.roll === 7) {
let move = true;
for (let color in game.players) {
const discard = game.players[color].mustDiscard;
if (discard) {
move = false;
}
}
if (move && !game.turn.placedRobber) {
game.turn.actions = [ 'place-robber' ];
game.turn.limits = { pips: [] };
for (let i = 0; i < 19; i++) {
if (i === game.robber) {
continue;
}
game.turn.limits.pips.push(i);
}
}
}
/* Update the session lastActive clock */ /* Update the session lastActive clock */
let session; let session;
if (req.session) { if (req.session) {
@ -1117,6 +1228,14 @@ const resetGame = (game) => {
roads: [] roads: []
}; };
for (let key in game.players) {
game.players[key].wheat =
game.players[key].sheep =
game.players[key].stone =
game.players[key].brick =
game.players[key].wood = 0;
}
for (let i = 0; i < layout.corners.length; i++) { for (let i = 0; i < layout.corners.length; i++) {
game.placements.corners[i] = { game.placements.corners[i] = {
color: undefined, color: undefined,
@ -1247,6 +1366,7 @@ const shuffleBoard = (game) => {
* the target pip value to the currently incremeneting * the target pip value to the currently incremeneting
* pip value. */ * pip value. */
if (game.tiles[game.tileOrder[target]].type === 'desert') { if (game.tiles[game.tileOrder[target]].type === 'desert') {
game.robber = target;
game.pipOrder[target] = 18; game.pipOrder[target] = 18;
} else { } else {
game.pipOrder[target] = p++; game.pipOrder[target] = p++;