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 */
width: 2em;
height: 2em;
transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.Pip.Active {
filter: drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
}
.Pips[disabled],
.Tiles[disabled],
.Roads[disabled],
.Corners[disabled] {
pointer-events: none;
@ -107,19 +109,40 @@
right: 0px;
top: 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 {
cursor: pointer;
pointer-events: all;
}
.Option .Pip-Shape,
.Option .Tile-Shape,
.Option .Corner-Shape,
.Option .Road-Shape {
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,
.Road-Shape:hover {
background-color: white;

View File

@ -47,8 +47,8 @@ const Board = ({ table, game }) => {
height: `${tileImageHeight}px`,
backgroundImage: `url(${assetsPath}/gfx/tiles-${tile.type}.png)`,
backgroundPositionY: `-${tile.card*tileHeight}px`
}}
/>;
}}
><div className="Tile-Shape"/></div>;
};
const Road = ({road}) => {
@ -85,6 +85,27 @@ const Board = ({ table, game }) => {
><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 = () => {
let row = 0, rowCount = 0;
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 y = tileHalfWidth - (rows.length - 1) * 0.5 * tileWidth,
x = -(rows[row] - 1) * 0.5 * tileHeight;
let index = 0;
let pip;
return game.pipOrder.map(order => {
const pip = game.pips[order];
const div = <div
data-roll={pip.roll}
pip = {
roll: game.pips[order].roll,
robber: index === game.robber,
index: index++,
top: y,
left: x,
order: order
};
const div = <Pip
pip={pip}
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]) {
row++;
@ -389,10 +411,11 @@ const Board = ({ table, game }) => {
}
});
/* Clear all 'Option' targets */
const nodes = document.querySelectorAll(`.Option`);
let nodes = document.querySelectorAll(`.Option`);
for (let i = 0; i < nodes.length; i++) {
nodes[i].classList.remove('Option');
}
/* Add 'Option' based on game.turn.limits */
if (game.turn && game.turn.limits) {
if (game.turn.limits['roads']) {
@ -413,6 +436,38 @@ const Board = ({ table, game }) => {
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="BoardBox">
{ borders }
{ tiles }
{ pips }
{ 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')}>
{ corners }
</div>

View File

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

View File

@ -136,6 +136,7 @@ const Resource = ({ type, count }) => {
<div className="Stack">
{ React.Children.map(array, i => (
<div className="Resource"
data-type={type}
onClick={select}
style={{backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`}}>
</div>
@ -336,6 +337,16 @@ const GameOrder = ({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) => {
return table.shuffleTable();
};
@ -357,7 +368,9 @@ const Action = ({ table }) => {
return (<Paper className="Action"/>);
}
const inLobby = table.game.state === 'lobby';
const inLobby = table.game.state === 'lobby',
player = table.game ? table.game.player : undefined;
return (
<Paper className="Action">
{ 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>Trade</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>
</> }
{ !inLobby &&
@ -493,6 +509,7 @@ class Table extends React.Component {
this.rollDice = this.rollDice.bind(this);
this.setGameState = this.setGameState.bind(this);
this.shuffleTable = this.shuffleTable.bind(this);
this.discard = this.discard.bind(this);
this.passTurn = this.passTurn.bind(this);
this.updateGame = this.updateGame.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() {
return this.sendAction('pass');
};
@ -662,6 +683,10 @@ class Table extends React.Component {
});
}
placeRobber(robber) {
return this.sendAction('place-robber', robber);
};
placeSettlement(settlement) {
return this.sendAction('place-settlement', settlement);
}
@ -813,6 +838,24 @@ class Table extends React.Component {
case '':
message = <>{message}The game is in a wonky state. Sorry :(</>;
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:
message = <>{message}Game state is: {this.game.state}</>;
break;
@ -909,15 +952,16 @@ class Table extends React.Component {
<div className="Table">
<div style={{display: "inline-flex", flex: 1, flexDirection: "column"}}>
<Board table={this} game={game}/>
{ player !== undefined && <div className="Hand'">
<Resource type="wood" count={player.wood}/>
<Resource type="wheat" count={player.wheat}/>
<Resource type="stone" count={player.stone}/>
<Resource type="brick" count={player.brick}/>
<Resource type="sheep" count={player.sheep}/>
</div> }
<Board table={this} game={game}/>
{ player !== undefined && <div className="Hand">
<Resource type="wood" count={player.wood}/>
<Resource type="wheat" count={player.wheat}/>
<Resource type="stone" count={player.stone}/>
<Resource type="brick" count={player.brick}/>
<Resource type="sheep" count={player.sheep}/>
</div> }
</div>
{ game && <div className={'Game ' + game.state}>
<Paper className="Message">{ this.state.message }</Paper>
{(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.state === 'initial-placement' || game.state === 'normal') &&
<WaitingForPlayer table={this}/>
(!game.player || !game.player.mustDiscard) && <WaitingForPlayer table={this}/>
}
{ 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++) {
let index = game.pipOrder[i];
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];
if (game.turn.roll === 7) {
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 {
distributeResources(game, game.turn.roll);
}
}
const getPlayer = (game, color) => {
if (!game) {
return {
@ -846,6 +880,34 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, null, `It is ${next}'s turn.`);
}
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':
if (game.state !== 'initial-placement' && game.state !== 'normal') {
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':
error = `City placement not yet implemented!`;
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":
const state = value;
if (!state) {
@ -1048,6 +1137,28 @@ const sendGame = async (req, res, game, error) => {
}
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 */
let session;
if (req.session) {
@ -1117,6 +1228,14 @@ const resetGame = (game) => {
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++) {
game.placements.corners[i] = {
color: undefined,
@ -1247,6 +1366,7 @@ const shuffleBoard = (game) => {
* the target pip value to the currently incremeneting
* pip value. */
if (game.tiles[game.tileOrder[target]].type === 'desert') {
game.robber = target;
game.pipOrder[target] = 18;
} else {
game.pipOrder[target] = p++;