1
0

Development cards can be purchased

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-14 16:46:45 -08:00
parent b890551cd6
commit 6fa528ade3
8 changed files with 275 additions and 101 deletions

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -71,7 +71,11 @@ const Board = ({ table, game }) => {
const Corner = ({corner}) => {
const onClick = (event) => {
console.log(`Corner ${corner.index}:`, game.layout.corners[corner.index]);
if (event.currentTarget.getAttribute('data-type') === 'settlement') {
table.placeCity(corner.index);
} else {
table.placeSettlement(corner.index);
}
return;
};

View File

@ -115,10 +115,12 @@ const Placard = ({table, type, active}) => {
};
const cityClicked = (event) => {
table.buyCity();
table.setState({ buildActive: false });
};
const developmentClicked = (event) => {
table.buyDevelopment();
table.setState({ buildActive: false });
};
@ -153,26 +155,13 @@ const Placard = ({table, type, active}) => {
);
};
class Development extends React.Component {
render() {
const array = [];
for (let i = 0; i < this.props.count; i++) {
if (this.props.type.match(/-$/)) {
array.push(i + 1);//Math.ceil(Math.random() * this.props.max));
} else {
array.push("");
}
}
const Development = ({table, type}) => {
return (
<div className="Stack">
{ React.Children.map(array, i => (
<div className="Development"
style={{backgroundImage:`url(${assetsPath}/gfx/card-${this.props.type}${i}.png)`}}>
</div>
)) }
</div>
style={{
backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`
}}/>
);
}
};
const Resource = ({ type, count }) => {
@ -466,14 +455,14 @@ const Action = ({ table }) => {
const inLobby = table.game.state === 'lobby',
player = table.game ? table.game.player : undefined,
hasRolled = table.game && table.game.turn && table.game.turn.roll,
isTurn = table.game && table.game.turn && table.game.turn.color === table.game.color;
hasRolled = (table.game && table.game.turn && table.game.turn.roll) ? true : false,
isTurn = (table.game && table.game.turn && table.game.turn.color === table.game.color) ? true : false;
return (
<Paper className="Action">
{ inLobby && <>
<StartButton table={table}/>
<Button disabled={!table.game.color} onClick={newTableClick}>New table</Button>
<Button disabled={table.game.color ? false : true} onClick={newTableClick}>New table</Button>
<Button disabled={table.game.color ? true : false} onClick={() => {table.setState({ pickName: true})}}>Change name</Button> </> }
{ table.game.state === 'normal' && <>
<Button disabled={!isTurn || hasRolled} onClick={rollClick}>Roll Dice</Button>
@ -792,13 +781,25 @@ class Table extends React.Component {
return this.sendAction('place-robber', robber);
};
buyDevelopment() {
return this.sendAction('buy-development');
}
buySettlement() {
return this.sendAction('buy-settlement');
}
placeSettlement(settlement) {
return this.sendAction('place-settlement', settlement);
}
buyCity() {
return this.sendAction('buy-city');
}
placeCity(city) {
return this.sendAction('place-city', city);
}
buyRoad() {
return this.sendAction('buy-road');
}
@ -812,49 +813,8 @@ class Table extends React.Component {
throwDice() {
return this.rollDice();
if (0) {
if (this.game.state !== 'active') {
return;
}
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);
this.setState({ total: this.state.total - half});
while (half) {
switch (Math.floor(Math.random() * 5)) {
case 0: if (this.state.wood) { this.setState({ wood: this.state.wood - 1}); half--; } break;
case 1: if (this.state.sheep) { this.setState({ sheep: this.state.sheep - 1}); half--; } break;
case 2: if (this.state.stone) { this.setState({ stone: this.state.stone - 1}); half--; } break;
case 3: if (this.state.brick) { this.setState({ brick: this.state.brick - 1}); half--; } break;
case 4:
default: if (this.state.wheat) { this.setState({ wheat: this.state.wheat - 1}); half--; } break;
}
}
}
} else {
this.tiles.forEach((tile) => {
if (tile.pip.roll !== sum) {
return;
}
this.setState({ [tile.type]: this.state[tile.type] + 1});
this.setState({ total: this.state.total + 1 });
});
}
this.setState({
total: this.state.total,
wood: this.state.wood,
sheep: this.state.sheep,
stone: this.state.stone,
brick: this.state.brick,
wheat: this.state.wheat
});
}
};
updateDimensions() {
const hasToolbar = false;
@ -937,15 +897,12 @@ class Table extends React.Component {
message = <>{message}You need to roll for game order. Click&nbsp;<b>Roll Dice</b>&nbsp;below.</>;
} else {
message = <>{message}You rolled <Dice pips={player.order}/> for game order. Waiting for all players to roll.</>;
message = <>{message}<br/><b>THIS IS THE END OF THE FUNCTIONALITY SO FAR</b></>;
}
}
break;
case 'active':
if (!player) {
message = <>{message}This game is no longer in the lobby.<br/><b>TODO: Override game state to allow Lobby mode while in-game</b></>;
} else {
message = <>{message}<br/><b>THIS IS THE END OF THE FUNCTIONALITY SO FAR</b></>;
}
break;
case null:
@ -974,6 +931,8 @@ class Table extends React.Component {
if (move && (this.game.turn && !this.game.turn.placedRobber)) {
message = <>{message}<PlayerColor color={this.game.turn.color}/> {this.game.turn.name} needs to move the robber.</>
}
} else {
message = <>It is <PlayerColor color={this.game.turn.color}/> {this.game.turn.name}'s turn.</>;
}
}
break;
@ -1075,6 +1034,20 @@ class Table extends React.Component {
case "B": color = "blue"; break;
case "W": color = "white"; break;
}
let development;
if (player) {
let stacks = {};
game.player.development.forEach(item => (item.type in stacks) ? stacks[item.type].push(item.card) : stacks[item.type] = [item.card]);
console.log(stacks);
development = [];
for (let type in stacks) {
console.log(type, stacks[type]);
const cards = stacks[type].map(card => <Development table={this} type={`${type}-${card}`}/>);
development.push(<div key={type} className="Stack">{ cards }</div>);
}
} else {
development = <>/</>;
}
return (
<div className="Table">
@ -1089,6 +1062,9 @@ class Table extends React.Component {
<Resource type="brick" count={player.brick}/>
<Resource type="sheep" count={player.sheep}/>
</div>
<div className="Hand">
{ development }
</div>
<Placard
active={this.state.buildActive}
disabled={!game || !game.turn || !game.turn.roll}

22
server/give Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
ADMIN=$(jq -r .admin config/local.json)
if [[ "${ADMIN}" == "" ]]; then
echo "You need to set your { 'admin': 'secret' } in config/local.json"
exit 1
fi
id=$1
give=$2
if [[ "${id}" == "" ]] || [[ "${give}" == "" ]]; then
echo "Usage: give GAME-ID (wheat|wood|stone|brick|sheep)-COUNT"
exit 1
fi
curl --noproxy '*' -s -L \
--request PUT \
--header "PRIVATE-TOKEN: ${ADMIN}" \
--header "Content-Type: application/json" \
http://localhost:8930/ketr.ketran/api/v1/games/${id}/give/${give} |
jq -r .status

View File

@ -88,18 +88,26 @@ const assetData = {
{ left: "sheep", right: "bank" },
{ center: "bank" }
],
developmentCards: []
developmentCards: [
]
};
for (let i = 0; i < 14; i++) {
assetData.developmentCards.push("knight");
}
for (let i = 0; i < 6; i++) {
assetData.developmentCards.push("progress");
}
for (let i = 0; i < 5; i++) {
assetData.developmentCards.push("victoryPoint");
for (let i = 1; i <= 14; i++) {
assetData.developmentCards.push({
type: 'army',
card: i
});
}
[ 'monopoly', 'road-1', 'road-2', 'yeard-of-plenty'].forEach(card => assetData.developmentCards.push({
type: 'progress',
card: card
}));
[ 'market', 'library', 'palace', 'university'].forEach(card => assetData.developmentCards.push({
type: 'vp',
card: card
}));
const games = {};
const processTies = (players) => {
@ -405,7 +413,8 @@ const getPlayer = (game, color) => {
wheat: 0,
sheep: 0,
wood: 0,
brick: 0
brick: 0,
development: []
};
}
@ -495,6 +504,12 @@ const loadGame = async (id) => {
}
}
for (let color in game.players) {
if (!game.players[color].development) {
game.players[color].development = [];
}
}
games[id] = game;
return game;
};
@ -790,10 +805,13 @@ const getPrevPlayer = (game, name) => {
return name;
}
const getValidCorners = (game, color) => {
const getValidCorners = (game, color, type) => {
const limits = [];
/* For each corner, if the corner already has a color set, skip it
/* For each corner, if the corner already has a color set, skip it if type
* isn't set. If type is set, if it is a match, and the color is a match,
* add it to the list.
*
* If we are limiting based on active player, a corner is only valid
* if it connects to a road that is owned by that player.
* If no color is set, walk each road that leaves that corner and
@ -801,9 +819,18 @@ const getValidCorners = (game, color) => {
* If so, this location cannot have a settlement.
*/
layout.corners.forEach((corner, cornerIndex) => {
if (game.placements.corners[cornerIndex].color) {
const placement = game.placements.corners[cornerIndex];
if (type) {
if (placement.color === color && placement.type === type) {
limits.push(cornerIndex);
}
return;
}
if (placement.color) {
return;
}
let valid;
if (!color) {
valid = true; /* Not filtering based on current player */
@ -918,10 +945,13 @@ router.put("/:id/:action/:value?", async (req, res) => {
const name = session.name;
let message, index;
let corners, corner;
switch (action) {
case "roll":
error = roll(game, session);
break;
case "shuffle":
if (game.state !== "lobby") {
error = `Game no longer in lobby (${game.state}). Can not shuffle board.`;
@ -936,6 +966,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
console.log(message);
}
break;
case 'pass':
if (game.turn.name !== name) {
error = `You cannot pass when it isn't your turn.`
@ -957,6 +988,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, session, `${name} passed their turn.`);
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!`;
@ -1004,6 +1036,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
break;
case 'steal-resource':
if (game.turn.actions.indexOf('steal-resource') === -1) {
error = `You can only steal a resource when it is valid to do so!`;
@ -1034,6 +1067,38 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
game.turn.robberDone = true;
break;
case 'buy-development':
if (game.state !== 'normal') {
error = `You cannot purchase a development card 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;
}
if (!game.turn.roll) {
error = `You cannot build until you have rolled.`;
break;
}
if (player.stone < 1 || player.wheat < 1 || player.sheep < 1) {
error = `You have insufficient resources to purchase a development card.`;
break;
}
if (game.developmentCards.length < 1) {
error = `There are no more development cards!`;
break;
}
if (game.turn.developmentPurchased) {
error = `You have already purchased a development card this turn.`;
}
addChatMessage(game, session, `Purchased a development card.`);
player.stone--;
player.wheat--;
player.sheep--;
player.development.push(game.developmentCards.pop());
break;
case 'buy-settlement':
if (game.state !== 'normal') {
error = `You cannot purchase a settlement unless the game is active.`;
@ -1043,6 +1108,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `It is not your turn! It is ${game.turn.name}'s turn.`;
break;
}
if (!game.turn.roll) {
error = `You cannot build until you have rolled.`;
break;
}
if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) {
error = `You have insufficient resources to build a settlement.`;
break;
@ -1051,20 +1120,16 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `You have already built all of your settlements.`;
break;
}
let corners = getValidCorners(game, session.color);
corners = getValidCorners(game, session.color);
if (corners.length === 0) {
error = `There are no valid locations for you to place a settlement.`;
break;
}
player.settlements--;
player.brick--;
player.wood--;
player.wheat--;
player.sheep--;
game.turn.actions = ['place-settlement'];
game.turn.limits = { corners };
addChatMessage(game, session, `Purchased a settlement. Next, they need to place it.`);
addChatMessage(game, session, `${game.turn.name} is considering placing a settlement.`);
break;
case 'place-settlement':
if (game.state !== 'initial-placement' && game.state !== 'normal') {
error = `You cannot place an item unless the game is active.`;
@ -1084,14 +1149,27 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `You tried to cheat! You should not try to break the rules.`;
break;
}
const corner = game.placements.corners[index];
corner = game.placements.corners[index];
if (corner.color) {
error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`;
break;
}
if (game.state === 'normal') {
if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) {
error = `You have insufficient resources to build a settlement.`;
break;
}
if (player.settlements < 1) {
error = `You have already built all of your settlements.`;
break;
}
player.settlements--;
player.brick--;
player.wood--;
player.wheat--;
player.sheep--;
corner.color = session.color;
corner.type = 'settlement';
if (game.state === 'normal') {
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} placed a settlement.`);
@ -1099,11 +1177,92 @@ router.put("/:id/:action/:value?", async (req, res) => {
if (game.direction && game.direction === 'backward') {
session.initialSettlement = index;
}
corner.color = session.color;
corner.type = 'settlement';
game.turn.actions = ['place-road'];
game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */
addChatMessage(game, session, `Placed a settlement. Next, they need to place a road.`);
}
break;
case 'buy-city':
if (game.state !== 'normal') {
error = `You cannot purchase a city 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;
}
if (!game.turn.roll) {
error = `You cannot build until you have rolled.`;
break;
}
if (player.wheat < 3 || player.stone < 2) {
error = `You have insufficient resources to build a city.`;
break;
}
if (player.city < 1) {
error = `You have already built all of your cities.`;
break;
}
corners = getValidCorners(game, session.color, 'settlement');
if (corners.length === 0) {
error = `There are no valid locations for you to place a city.`;
break;
}
game.turn.actions = ['place-city'];
game.turn.limits = { corners };
addChatMessage(game, session, `${game.turn.name} is considering upgrading a settlement to a city.`);
break;
case 'place-city':
if (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 = parseInt(value);
if (game.placements.corners[index] === undefined) {
error = `You have requested to place a city illegally!`;
break;
}
/* If this is not a placement the turn limits, discard it */
if (game.turn && game.turn.limits && game.turn.limits.corners && game.turn.limits.corners.indexOf(index) === -1) {
error = `You tried to cheat! You should not try to break the rules.`;
break;
}
corner = game.placements.corners[index];
if (corner.color !== session.color) {
error = `This location already has a settlement belonging to ${playerNameFromColor(game, corner.color)}!`;
break;
}
if (corner.type !== 'settlement') {
error = `This location already has a city!`;
break;
}
if (player.wheat < 3 || player.stone < 2) {
error = `You have insufficient resources to build a city.`;
break;
}
if (player.city < 1) {
error = `You have already built all of your cities.`;
break;
}
corner.color = session.color;
corner.type = 'city';
player.cities--;
player.settlements++;
player.wheat -= 3;
player.stone -= 2;
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} upgraded a settlement to a city!`);
break;
case 'buy-road':
if (game.state !== 'normal') {
error = `You cannot purchase a road unless the game is active.`;
@ -1113,6 +1272,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `It is not your turn! It is ${game.turn.name}'s turn.`;
break;
}
if (!game.turn.roll) {
error = `You cannot build until you have rolled.`;
break;
}
if (player.brick < 1 || player.wood < 1) {
error = `You have insufficient resources to build a road.`;
break;
@ -1126,13 +1289,11 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `There are no valid locations for you to place a road.`;
break;
}
player.roads--;
player.brick--;
player.wood--;
game.turn.actions = ['place-road'];
game.turn.limits = { roads };
addChatMessage(game, session, `Purchased a road. Next, they need to place it.`);
addChatMessage(game, session, `${game.turn.name} is considering building a road.`);
break;
case 'place-road':
if (game.state !== 'initial-placement' && game.state !== 'normal') {
error = `You cannot place an item unless the game is active.`;
@ -1157,13 +1318,25 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `This location already has a road belonging to ${playerNameFromColor(game, road.color)}!`;
break;
}
road.color = session.color;
if (game.state === 'normal') {
if (player.brick < 1 || player.wood < 1) {
error = `You have insufficient resources to build a road.`;
break;
}
if (player.roads < 1) {
error = `You have already built all of your roads.`;
break;
}
player.roads--;
player.brick--;
player.wood--;
road.color = session.color;
game.turn.actions = [];
game.turn.limits = {};
addChatMessage(game, session, `${name} placed a road.`);
} else if (game.state === 'initial-placement') {
road.color = session.color;
addChatMessage(game, session, `${name} placed a road.`);
let next;
@ -1228,9 +1401,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
}
}
break;
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!`;
@ -1258,6 +1429,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
addChatMessage(game, null, `${session.name} must discard ${player.mustDiscard} more cards.`);
}
break;
case "state":
const state = value;
if (!state) {
@ -1568,7 +1740,7 @@ const shuffleBoard = (game) => {
}
}
shuffle(game.developmentCards)
shuffle(game.developmentCards);
}
/*