Almost done!
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
parent
8cb3efc70f
commit
dc2d97196e
@ -175,7 +175,7 @@
|
|||||||
|
|
||||||
[data-color='O'] > .Corner-Shape,
|
[data-color='O'] > .Corner-Shape,
|
||||||
[data-color='O'] > .Road-Shape {
|
[data-color='O'] > .Road-Shape {
|
||||||
background-color: rgba(255, 196, 0, 1);
|
background-color: rgb(255, 128, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-color='W'] > .Corner-Shape,
|
[data-color='W'] > .Corner-Shape,
|
||||||
|
@ -70,7 +70,7 @@ const Board = ({ table, 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]);
|
||||||
if (event.currentTarget.getAttribute('data-type') === 'settlement') {
|
if (event.currentTarget.getAttribute('data-type') === 'settlement') {
|
||||||
table.placeCity(corner.index);
|
table.placeCity(corner.index);
|
||||||
} else {
|
} else {
|
||||||
@ -91,7 +91,7 @@ const Board = ({ table, game }) => {
|
|||||||
|
|
||||||
const Pip = ({pip}) => {
|
const Pip = ({pip}) => {
|
||||||
const onClick = (event) => {
|
const onClick = (event) => {
|
||||||
console.log(`Pip ${pip.index}:`, game.layout.corners[pip.index]);
|
// console.log(`Pip ${pip.index}:`, game.layout.corners[pip.index]);
|
||||||
table.placeRobber(pip.index);
|
table.placeRobber(pip.index);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -377,14 +377,23 @@ const Board = ({ table, game }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game && game.turn && game.turn.roll) {
|
if (game && game.turn) {
|
||||||
let nodes = document.querySelectorAll('.Pip.Active');
|
let nodes = document.querySelectorAll('.Active');
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
nodes[i].classList.remove('Active');
|
nodes[i].classList.remove('Active');
|
||||||
}
|
}
|
||||||
nodes = document.querySelectorAll(`.Pip[data-roll="${game.turn.roll}"]`);
|
if (game.turn.roll) {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
nodes = document.querySelectorAll(`.Pip[data-roll="${game.turn.roll}"]`);
|
||||||
nodes[i].classList.add('Active');
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const index = nodes[i].getAttribute('data-index');
|
||||||
|
if (index !== null) {
|
||||||
|
const tile = document.querySelector(`.Tile[data-index="${index}"]`);
|
||||||
|
if (tile) {
|
||||||
|
tile.classList.add('Active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes[i].classList.add('Active');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
.Resource {
|
.Resource {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 4.9em;
|
height: 7em;
|
||||||
height: 7.2em;
|
width: 5em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
margin: 0.25em;
|
margin: 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resource:hover {
|
.Resource:hover {
|
||||||
|
@ -196,6 +196,11 @@
|
|||||||
filter: brightness(150%);
|
filter: brightness(150%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Development.Selected {
|
||||||
|
filter: brightness(150%);
|
||||||
|
top: -1em;
|
||||||
|
}
|
||||||
|
|
||||||
.Game {
|
.Game {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -219,9 +224,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Game.lobby {
|
.Game.lobby {
|
||||||
max-width: 100vw;
|
|
||||||
width: 100vw;
|
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -366,7 +368,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-items: space-between;
|
justify-items: space-between;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Placard > div {
|
.Placard > div {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 0.9em;
|
margin: 0 0.9em;
|
||||||
@ -407,6 +411,7 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
margin: 0.25em;
|
margin: 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Action {
|
.Action {
|
||||||
|
@ -14,8 +14,7 @@ import { assetsPath, base, getPlayerName, gamesPath } from './Common.js';
|
|||||||
import PlayerColor from './PlayerColor.js';
|
import PlayerColor from './PlayerColor.js';
|
||||||
import Dice from './Dice.js';
|
import Dice from './Dice.js';
|
||||||
import Resource from './Resource.js';
|
import Resource from './Resource.js';
|
||||||
|
import ViewCard from './ViewCard.js';
|
||||||
//import moment from 'moment';
|
|
||||||
|
|
||||||
/* Start of withRouter polyfill */
|
/* Start of withRouter polyfill */
|
||||||
// https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it
|
// https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it
|
||||||
@ -106,9 +105,10 @@ const Placard = ({table, type, active}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Development = ({table, type}) => {
|
const Development = ({table, type, card, onClick}) => {
|
||||||
return (
|
return (
|
||||||
<div className="Development"
|
<div className={`Development ${card.played ? 'Selected' : ''}`}
|
||||||
|
onClick={onClick}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`
|
backgroundImage:`url(${assetsPath}/gfx/card-${type}.png)`
|
||||||
}}/>
|
}}/>
|
||||||
@ -293,7 +293,7 @@ const GameOrder = ({table}) => {
|
|||||||
<div className="GameOrderPlayer" key={`player-${item.color}`}>
|
<div className="GameOrderPlayer" key={`player-${item.color}`}>
|
||||||
<PlayerColor color={item.color}/>
|
<PlayerColor color={item.color}/>
|
||||||
<div>{item.name}</div>
|
<div>{item.name}</div>
|
||||||
{ item.orderRoll !== 0 && <>rolled <Dice pips={item.orderRoll}/>.</> }
|
{ item.orderRoll !== 0 && <>rolled <Dice pips={item.orderRoll}/>. {item.orderStatus}</> }
|
||||||
{ item.orderRoll === 0 && <>has not rolled yet. {item.orderStatus}</>}
|
{ item.orderRoll === 0 && <>has not rolled yet. {item.orderStatus}</>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -352,7 +352,7 @@ const Action = ({ table }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const discardClick = (event) => {
|
const discardClick = (event) => {
|
||||||
const nodes = document.querySelectorAll('.Hand .Selected'),
|
const nodes = document.querySelectorAll('.Hand .Resource.Selected'),
|
||||||
discarding = { wheat: 0, brick: 0, sheep: 0, stone: 0, wood: 0 };
|
discarding = { wheat: 0, brick: 0, sheep: 0, stone: 0, wood: 0 };
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
discarding[nodes[i].getAttribute("data-type")]++;
|
discarding[nodes[i].getAttribute("data-type")]++;
|
||||||
@ -386,23 +386,26 @@ const Action = ({ table }) => {
|
|||||||
return (<Paper className="Action"/>);
|
return (<Paper className="Action"/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inLobby = table.game.state === 'lobby',
|
const game = table.game,
|
||||||
player = table.game ? table.game.player : undefined,
|
inLobby = game.state === 'lobby',
|
||||||
hasRolled = (table.game && table.game.turn && table.game.turn.roll) ? true : false,
|
player = game ? game.player : undefined,
|
||||||
isTurn = (table.game && table.game.turn && table.game.turn.color === table.game.color) ? true : false,
|
hasRolled = (game && game.turn && game.turn.roll) ? true : false,
|
||||||
robberActions = (table.game && table.game.turn && table.game.turn.roll === 7 && !table.game.turn.robberDone);
|
isTurn = (game && game.turn && game.turn.color === game.color) ? true : false,
|
||||||
|
robberActions = (game && game.turn && game.turn.roll === 7 &&
|
||||||
|
!game.turn.robberDone),
|
||||||
|
haveResources = player ? player.haveResources : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="Action">
|
<Paper className="Action">
|
||||||
{ inLobby && <>
|
{ inLobby && <>
|
||||||
<StartButton table={table}/>
|
<StartButton table={table}/>
|
||||||
<Button disabled={table.game.color ? false : true} onClick={newTableClick}>New table</Button>
|
<Button disabled={game.color ? false : true} onClick={newTableClick}>New table</Button>
|
||||||
<Button disabled={table.game.color ? true : false} onClick={() => {table.setState({ pickName: true})}}>Change name</Button> </> }
|
<Button disabled={game.color ? true : false} onClick={() => {table.setState({ pickName: true})}}>Change name</Button> </> }
|
||||||
{ table.game.state === 'normal' && <>
|
{ game.state === 'normal' && <>
|
||||||
<Button disabled={robberActions || !isTurn || hasRolled} onClick={rollClick}>Roll Dice</Button>
|
<Button disabled={robberActions || !isTurn || hasRolled} onClick={rollClick}>Roll Dice</Button>
|
||||||
<Button disabled={robberActions || !isTurn || !hasRolled} onClick={tradeClick}>Trade</Button>
|
<Button disabled={robberActions || !isTurn || !hasRolled || !haveResources} onClick={tradeClick}>Trade</Button>
|
||||||
<Button disabled={robberActions || !isTurn || !hasRolled} onClick={buildClicked}>Build</Button>
|
<Button disabled={robberActions || !isTurn || !hasRolled || !haveResources} onClick={buildClicked}>Build</Button>
|
||||||
{ table.game.turn.roll === 7 && player && player.mustDiscard > 0 &&
|
{ game.turn.roll === 7 && player && player.mustDiscard > 0 &&
|
||||||
<Button onClick={discardClick}>Discard</Button>
|
<Button onClick={discardClick}>Discard</Button>
|
||||||
}
|
}
|
||||||
<Button disabled={robberActions || !isTurn || !hasRolled} onClick={passClick}>Done</Button>
|
<Button disabled={robberActions || !isTurn || !hasRolled} onClick={passClick}>Done</Button>
|
||||||
@ -468,7 +471,12 @@ const Players = ({ table }) => {
|
|||||||
}
|
}
|
||||||
const name = getPlayerName(table.game.sessions, color),
|
const name = getPlayerName(table.game.sessions, color),
|
||||||
selectable = table.game.state === 'lobby' && (item.status === 'Not active' || table.game.color === color);
|
selectable = table.game.state === 'lobby' && (item.status === 'Not active' || table.game.color === color);
|
||||||
let toggleText = name ? name : "Available";
|
let toggleText;
|
||||||
|
if (name) {
|
||||||
|
toggleText = `${name} has ${item.points} VP`;
|
||||||
|
} else {
|
||||||
|
toggleText = "Available";
|
||||||
|
}
|
||||||
players.push((
|
players.push((
|
||||||
<div
|
<div
|
||||||
data-selectable={selectable}
|
data-selectable={selectable}
|
||||||
@ -511,7 +519,8 @@ class Table extends React.Component {
|
|||||||
message: "",
|
message: "",
|
||||||
error: "",
|
error: "",
|
||||||
signature: "",
|
signature: "",
|
||||||
buildActive: false
|
buildActive: false,
|
||||||
|
cardActive: undefined
|
||||||
};
|
};
|
||||||
this.componentDidMount = this.componentDidMount.bind(this);
|
this.componentDidMount = this.componentDidMount.bind(this);
|
||||||
this.updateDimensions = this.updateDimensions.bind(this);
|
this.updateDimensions = this.updateDimensions.bind(this);
|
||||||
@ -524,6 +533,7 @@ class Table extends React.Component {
|
|||||||
this.startTrading = this.startTrading.bind(this);
|
this.startTrading = this.startTrading.bind(this);
|
||||||
this.offerTrade = this.offerTrade.bind(this);
|
this.offerTrade = this.offerTrade.bind(this);
|
||||||
this.acceptTrade = this.acceptTrade.bind(this);
|
this.acceptTrade = this.acceptTrade.bind(this);
|
||||||
|
this.rejectTrade = this.rejectTrade.bind(this);
|
||||||
this.cancelTrading = this.cancelTrading.bind(this);
|
this.cancelTrading = this.cancelTrading.bind(this);
|
||||||
this.discard = this.discard.bind(this);
|
this.discard = this.discard.bind(this);
|
||||||
this.passTurn = this.passTurn.bind(this);
|
this.passTurn = this.passTurn.bind(this);
|
||||||
@ -534,6 +544,8 @@ class Table extends React.Component {
|
|||||||
this.gameSignature = this.gameSignature.bind(this);
|
this.gameSignature = this.gameSignature.bind(this);
|
||||||
this.sendAction = this.sendAction.bind(this);
|
this.sendAction = this.sendAction.bind(this);
|
||||||
this.buildClicked = this.buildClicked.bind(this);
|
this.buildClicked = this.buildClicked.bind(this);
|
||||||
|
this.closeCard = this.closeCard.bind(this);
|
||||||
|
this.playCard = this.playCard.bind(this);
|
||||||
|
|
||||||
this.mouse = { x: 0, y: 0 };
|
this.mouse = { x: 0, y: 0 };
|
||||||
this.radius = 0.317;
|
this.radius = 0.317;
|
||||||
@ -556,6 +568,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeCard() {
|
||||||
|
this.setState({cardActive: undefined});
|
||||||
|
}
|
||||||
|
|
||||||
sendAction(action, value, extra) {
|
sendAction(action, value, extra) {
|
||||||
if (this.loadTimer) {
|
if (this.loadTimer) {
|
||||||
window.clearTimeout(this.loadTimer);
|
window.clearTimeout(this.loadTimer);
|
||||||
@ -596,6 +612,11 @@ class Table extends React.Component {
|
|||||||
return this.sendAction('chat', undefined, {message: message});
|
return this.sendAction('chat', undefined, {message: message});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playCard(card) {
|
||||||
|
this.setState({ cardActive: undefined });
|
||||||
|
return this.sendAction('play-card', undefined, card);
|
||||||
|
}
|
||||||
|
|
||||||
setPlayerName(name) {
|
setPlayerName(name) {
|
||||||
return this.sendAction('player-name', name)
|
return this.sendAction('player-name', name)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -626,6 +647,10 @@ class Table extends React.Component {
|
|||||||
return this.sendAction('trade', 'accept', trade);
|
return this.sendAction('trade', 'accept', trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rejectTrade(trade) {
|
||||||
|
return this.sendAction('trade', 'reject', trade);
|
||||||
|
}
|
||||||
|
|
||||||
discard(resources) {
|
discard(resources) {
|
||||||
return this.sendAction('discard', undefined, resources);
|
return this.sendAction('discard', undefined, resources);
|
||||||
}
|
}
|
||||||
@ -833,7 +858,7 @@ class Table extends React.Component {
|
|||||||
break;
|
break;
|
||||||
case 'game-order':
|
case 'game-order':
|
||||||
if (!player) {
|
if (!player) {
|
||||||
message = <>{message}This game as an observer as <b>{name}</b>.</>;
|
message = <>{message}You are an observer in this game as <b>{name}</b>.</>;
|
||||||
message = <>{message}You can chat with other players below as <b>{this.game.name}</b>, but cannot play unless players go back to the Lobby.</>;
|
message = <>{message}You can chat with other players below as <b>{this.game.name}</b>, but cannot play unless players go back to the Lobby.</>;
|
||||||
} else {
|
} else {
|
||||||
if (!player.order) {
|
if (!player.order) {
|
||||||
@ -967,6 +992,14 @@ class Table extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cardClicked(card) {
|
||||||
|
const game = this.state.game;
|
||||||
|
if (!game) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({cardActive: card });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const game = this.state.game,
|
const game = this.state.game,
|
||||||
player = game ? game.player : undefined
|
player = game ? game.player : undefined
|
||||||
@ -980,15 +1013,25 @@ class Table extends React.Component {
|
|||||||
let development;
|
let development;
|
||||||
if (player) {
|
if (player) {
|
||||||
let stacks = {};
|
let stacks = {};
|
||||||
game.player.development.forEach(item => (item.type in stacks) ? stacks[item.type].push(item.card) : stacks[item.type] = [item.card]);
|
game.player.development.forEach(card =>
|
||||||
|
(card.type in stacks)
|
||||||
|
? stacks[card.type].push(card)
|
||||||
|
: stacks[card.type] = [card]);
|
||||||
|
|
||||||
development = [];
|
development = [];
|
||||||
for (let type in stacks) {
|
for (let type in stacks) {
|
||||||
const cards = stacks[type].map(card => <Development table={this} key={`${type}-${card}`} type={`${type}-${card}`}/>);
|
const cards = stacks[type].map(card => <Development
|
||||||
|
onClick={() => this.cardClicked(card)}
|
||||||
|
card={card}
|
||||||
|
table={this}
|
||||||
|
key={`${type}-${card.card}`}
|
||||||
|
type={`${type}-${card.card}`}/>);
|
||||||
development.push(<div key={type} className="Stack">{ cards }</div>);
|
development.push(<div key={type} className="Stack">{ cards }</div>);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
development = <>/</>;
|
development = <>/</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Table">
|
<div className="Table">
|
||||||
|
|
||||||
@ -1030,6 +1073,10 @@ class Table extends React.Component {
|
|||||||
</> }
|
</> }
|
||||||
</div> }
|
</div> }
|
||||||
|
|
||||||
|
{ this.state.cardActive &&
|
||||||
|
<ViewCard table={this} card={this.state.cardActive}/>
|
||||||
|
}
|
||||||
|
|
||||||
{ game && game.state === 'game-order' &&
|
{ game && game.state === 'game-order' &&
|
||||||
<GameOrder table={this}/>
|
<GameOrder table={this}/>
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
background-color:rgba(224, 224, 224);
|
background-color:rgba(224, 224, 224);
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Trade > * {
|
.Trade > * {
|
||||||
min-width: 40em;
|
min-width: 40em;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -29,6 +30,11 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Trade .Resource {
|
||||||
|
width: 3.75em; /* 5x7 aspect ratio */
|
||||||
|
height: 5.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.Trade .PlayerColor {
|
.Trade .PlayerColor {
|
||||||
width: 0.5em;
|
width: 0.5em;
|
||||||
height: 0.5em;
|
height: 0.5em;
|
||||||
|
@ -6,14 +6,13 @@ import Paper from '@material-ui/core/Paper';
|
|||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Resource from './Resource.js';
|
import Resource from './Resource.js';
|
||||||
|
|
||||||
const ResourceCounter = ({type, onCount, max}) => {
|
const ResourceCounter = ({type, count, onCount, max}) => {
|
||||||
const [count, setCount] = useState(0);
|
count = count ? count : 0;
|
||||||
const plusClicked = (event) => {
|
const plusClicked = (event) => {
|
||||||
if (max === undefined || max > count) {
|
if (max === undefined || max > count) {
|
||||||
if (onCount) {
|
if (onCount) {
|
||||||
onCount(type, count+1);
|
onCount(type, count+1);
|
||||||
}
|
}
|
||||||
setCount(count+1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const minusClicked = (event) => {
|
const minusClicked = (event) => {
|
||||||
@ -21,7 +20,6 @@ const ResourceCounter = ({type, onCount, max}) => {
|
|||||||
if (onCount) {
|
if (onCount) {
|
||||||
onCount(type, count-1);
|
onCount(type, count-1);
|
||||||
}
|
}
|
||||||
setCount(count-1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,7 +75,7 @@ const Trade = ({table}) => {
|
|||||||
} else {
|
} else {
|
||||||
setGiveLine(items.join(', '));
|
setGiveLine(items.join(', '));
|
||||||
}
|
}
|
||||||
}, [setGiveLine, setGives]);
|
}, [setGiveLine, setGives, gives]);
|
||||||
|
|
||||||
const getCount = useCallback((type, count) => {
|
const getCount = useCallback((type, count) => {
|
||||||
gets[type] = count;
|
gets[type] = count;
|
||||||
@ -94,12 +92,25 @@ const Trade = ({table}) => {
|
|||||||
} else {
|
} else {
|
||||||
setGetLine(items.join(', '));
|
setGetLine(items.join(', '));
|
||||||
}
|
}
|
||||||
}, [setGetLine, setGets]);
|
}, [setGetLine, setGets, gets]);
|
||||||
|
|
||||||
|
const meetClicked = useCallback((offer) => {
|
||||||
|
const trade = {
|
||||||
|
gives: offer.gets.slice(),
|
||||||
|
gets: offer.gives.slice()
|
||||||
|
};
|
||||||
|
console.log(trade);
|
||||||
|
trade.gives.forEach(give => giveCount(give.type, give.count));
|
||||||
|
trade.gets.forEach(get => getCount(get.type, get.count));
|
||||||
|
table.offerTrade(trade);
|
||||||
|
}, [giveCount, getCount]);
|
||||||
|
|
||||||
if (!table.game) {
|
if (!table.game) {
|
||||||
return (<></>);
|
return (<></>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const game = table.game;
|
||||||
|
|
||||||
const isTurn = (table.game.turn && table.game.turn.color === table.game.color) ? true : false;
|
const isTurn = (table.game.turn && table.game.turn.color === table.game.color) ? true : false;
|
||||||
|
|
||||||
const offerClicked = (event) => {
|
const offerClicked = (event) => {
|
||||||
@ -124,6 +135,12 @@ const Trade = ({table}) => {
|
|||||||
table.cancelTrading();
|
table.cancelTrading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Non-current player has rejected the active player's
|
||||||
|
* bid */
|
||||||
|
const rejectClicked = (trade) => {
|
||||||
|
table.rejectTrade(trade);
|
||||||
|
}
|
||||||
|
|
||||||
let players = [];
|
let players = [];
|
||||||
for (let color in table.game.players) {
|
for (let color in table.game.players) {
|
||||||
const item = table.game.players[color],
|
const item = table.game.players[color],
|
||||||
@ -134,7 +151,8 @@ const Trade = ({table}) => {
|
|||||||
color: color,
|
color: color,
|
||||||
valid: false,
|
valid: false,
|
||||||
gets: item.gets ? item.gets : [],
|
gets: item.gets ? item.gets : [],
|
||||||
gives: item.gives ? item.gives : []
|
gives: item.gives ? item.gives : [],
|
||||||
|
offerRejected: item.offerRejected ? true : false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +182,13 @@ const Trade = ({table}) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const player = (table.game && table.game.player) ? table.game.player : undefined;
|
||||||
|
if (!player) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canAccept = false;
|
||||||
|
|
||||||
if (table.game.turn.offer) {
|
if (table.game.turn.offer) {
|
||||||
players.forEach(trade => {
|
players.forEach(trade => {
|
||||||
let valid = trade.gets.length && trade.gives.length;
|
let valid = trade.gets.length && trade.gives.length;
|
||||||
@ -187,9 +212,29 @@ const Trade = ({table}) => {
|
|||||||
});
|
});
|
||||||
trade.valid = valid;
|
trade.valid = valid;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
canAccept = true;
|
||||||
|
table.game.turn.offer.gets.forEach(item => {
|
||||||
|
if (!canAccept) {
|
||||||
|
canAccept = (item.type in game.player);
|
||||||
|
}
|
||||||
|
if (!canAccept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canAccept = (game.player[item.type] >= item.count);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
players = players.map((item, index) => {
|
players = players.map((item, index) => {
|
||||||
|
if (item.offerRejected) {
|
||||||
|
return <div className="TradePlayer" key={`player-${item.name}-${index}`}>
|
||||||
|
<PlayerColor color={item.color}/>
|
||||||
|
<div>{item.name}</div>
|
||||||
|
<div className='TradeLine'>
|
||||||
|
has rejected your offer.
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
const gets = item.gets.map(get =>
|
const gets = item.gets.map(get =>
|
||||||
`${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`)
|
`${get.count} ${(get.type === 'bank') ? 'of any one resource' : get.type}`)
|
||||||
.join(', '),
|
.join(', '),
|
||||||
@ -211,16 +256,18 @@ const Trade = ({table}) => {
|
|||||||
}
|
}
|
||||||
{ isTurn && <Button disabled={!item.valid}
|
{ isTurn && <Button disabled={!item.valid}
|
||||||
onClick={() => acceptClicked(item)}>accept</Button> }
|
onClick={() => acceptClicked(item)}>accept</Button> }
|
||||||
|
{ !isTurn && item.color === table.game.turn.color && <>
|
||||||
|
<Button disabled={!canAccept}
|
||||||
|
onClick={() => meetClicked(item)}>meet</Button>
|
||||||
|
<Button disabled={!item.gets.length ||
|
||||||
|
!item.gives.length || player.offerRejected}
|
||||||
|
onClick={() => rejectClicked(item)}>reject</Button>
|
||||||
|
</> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const player = (table.game && table.game.player) ? table.game.player : undefined;
|
|
||||||
if (!player) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Trade">
|
<div className="Trade">
|
||||||
<Paper>
|
<Paper>
|
||||||
@ -230,32 +277,35 @@ const Trade = ({table}) => {
|
|||||||
<div className="PlayerList">
|
<div className="PlayerList">
|
||||||
{ players }
|
{ players }
|
||||||
</div>
|
</div>
|
||||||
<div className='TradeLine'
|
{ !player.haveResources && <b>You have no resources to participate in this trade.</b> }
|
||||||
style={{
|
{ player.haveResources &&
|
||||||
flexDirection: 'column',
|
<div className='TradeLine'
|
||||||
alignItems: 'flex-start'
|
style={{
|
||||||
}}>
|
flexDirection: 'column',
|
||||||
<div style={{display: 'flex' }}>
|
alignItems: 'flex-start'
|
||||||
<b>You want to receive {getLine}.</b>
|
}}>
|
||||||
|
<div style={{display: 'flex' }}>
|
||||||
|
<b>You want to receive {getLine}.</b>
|
||||||
|
</div>
|
||||||
|
<div style={{display: 'flex' }}>
|
||||||
|
<ResourceCounter count={gets.brick} onCount={getCount} type='brick'/>
|
||||||
|
<ResourceCounter count={gets.wood} onCount={getCount} type='wood'/>
|
||||||
|
<ResourceCounter count={gets.wheat} onCount={getCount} type='wheat'/>
|
||||||
|
<ResourceCounter count={gets.sheep} onCount={getCount} type='sheep'/>
|
||||||
|
<ResourceCounter count={gets.stone} onCount={getCount} type='stone'/>
|
||||||
|
</div>
|
||||||
|
<div style={{display: 'flex' }}>
|
||||||
|
<b>You are willing to give {giveLine}.</b>
|
||||||
|
</div>
|
||||||
|
<div style={{display: 'flex' }}>
|
||||||
|
{ player.brick > 0 && <ResourceCounter count={gives.brick} onCount={giveCount} type='brick' max={player.brick}/> }
|
||||||
|
{ player.wood > 0 && <ResourceCounter count={gives.wood} onCount={giveCount} type='wood' max={player.wood}/> }
|
||||||
|
{ player.wheat > 0 && <ResourceCounter count={gives.wheat} onCount={giveCount} type='wheat' max={player.wheat}/> }
|
||||||
|
{ player.sheep > 0 && <ResourceCounter count={gives.sheep} onCount={giveCount} type='sheep' max={player.sheep}/> }
|
||||||
|
{ player.stone > 0 && <ResourceCounter count={gives.stone} onCount={giveCount} type='stone' max={player.stone}/> }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{display: 'flex' }}>
|
}
|
||||||
<ResourceCounter onCount={getCount} type='brick'/>
|
|
||||||
<ResourceCounter onCount={getCount} type='wood'/>
|
|
||||||
<ResourceCounter onCount={getCount} type='wheat'/>
|
|
||||||
<ResourceCounter onCount={getCount} type='sheep'/>
|
|
||||||
<ResourceCounter onCount={getCount} type='stone'/>
|
|
||||||
</div>
|
|
||||||
<div style={{display: 'flex' }}>
|
|
||||||
<b>You are willing to give {giveLine}.</b>
|
|
||||||
</div>
|
|
||||||
<div style={{display: 'flex' }}>
|
|
||||||
{ player.brick > 0 && <ResourceCounter onCount={giveCount} type='brick' max={player.brick}/> }
|
|
||||||
{ player.wood > 0 && <ResourceCounter onCount={giveCount} type='wood' max={player.wood}/> }
|
|
||||||
{ player.wheat > 0 && <ResourceCounter onCount={giveCount} type='wheat' max={player.wheat}/> }
|
|
||||||
{ player.sheep > 0 && <ResourceCounter onCount={giveCount} type='sheep' max={player.sheep}/> }
|
|
||||||
{ player.stone > 0 && <ResourceCounter onCount={giveCount} type='stone' max={player.stone}/> }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button disabled={getLine === 'nothing' || giveLine === 'nothing'}
|
<Button disabled={getLine === 'nothing' || giveLine === 'nothing'}
|
||||||
onClick={offerClicked}>Offer</Button>
|
onClick={offerClicked}>Offer</Button>
|
||||||
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }
|
{ isTurn && <Button onClick={cancelClicked}>cancel</Button> }
|
||||||
|
37
client/src/ViewCard.css
Normal file
37
client/src/ViewCard.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
.ViewCard {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 40vw;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewCard .Title {
|
||||||
|
align-self: center;
|
||||||
|
padding: 2px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewCard .Description {
|
||||||
|
padding: 1em;
|
||||||
|
max-width: 20vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewCard > * {
|
||||||
|
/* min-width: 40em;*/
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0.5em;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewCard .Resource {
|
||||||
|
width: 10em; /* 5x7 aspect ratio */
|
||||||
|
height: 14em;
|
||||||
|
}
|
83
client/src/ViewCard.js
Normal file
83
client/src/ViewCard.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { useState, useCallback } from "react";
|
||||||
|
import "./ViewCard.css";
|
||||||
|
import Paper from '@material-ui/core/Paper';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Resource from './Resource.js';
|
||||||
|
|
||||||
|
const ViewCard = ({table, card}) => {
|
||||||
|
const playCard = (event) => {
|
||||||
|
table.playCard(card);
|
||||||
|
}
|
||||||
|
const close = (event) => {
|
||||||
|
table.closeCard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const capitalize = (string) => {
|
||||||
|
if (string === 'vp') {
|
||||||
|
return 'Victory Point';
|
||||||
|
}
|
||||||
|
if (string === 'army') {
|
||||||
|
return 'Knight';
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const descriptions = {
|
||||||
|
army: <>When played, you <b>must</b> move the robber.
|
||||||
|
<p>Steal <b>1</b> resource card from the owner of an adjacent settlement or city.</p>
|
||||||
|
<p>You may only play one development card during your turn -- either one
|
||||||
|
knight or one progress card.</p></>,
|
||||||
|
vp: <><b>1</b> victory point.
|
||||||
|
<p>You only reveal your victory point cards when the game is over, either
|
||||||
|
when you or an opponent reaches <b>10+</b> victory points on their turn and declares
|
||||||
|
victory!</p></>
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = descriptions[card.type];
|
||||||
|
|
||||||
|
let canPlay = false;
|
||||||
|
if (card.type === 'vp') {
|
||||||
|
let points = table.game.player.points;
|
||||||
|
table.game.player.development.forEach(item => {
|
||||||
|
if (item.type === 'vp') {
|
||||||
|
points++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
canPlay = points >= 10;
|
||||||
|
if (!canPlay && !card.played) {
|
||||||
|
description = <>{description}<p>You do not have enough victory points to play this card yet.</p></>;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
canPlay = card.turn < table.game.turns;
|
||||||
|
if (!canPlay) {
|
||||||
|
description = <>{description}<p>You can not play this card until your next turn.</p></>;
|
||||||
|
}
|
||||||
|
if (canPlay) {
|
||||||
|
canPlay = table.game.player.playedCard !== table.game.turns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.played) {
|
||||||
|
description = <>{description}<p>You have already played this card.</p></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ViewCard">
|
||||||
|
<Paper>
|
||||||
|
<div className="Title">{capitalize(card.type)}</div>
|
||||||
|
<div style={{display: 'flex', flexDirection: 'row'}}>
|
||||||
|
<Resource type={`${card.type}-${card.card}`} disabled count={1}/>
|
||||||
|
<div className="Description">{description}</div>
|
||||||
|
</div>
|
||||||
|
{ !card.played &&
|
||||||
|
<Button disabled={!canPlay}
|
||||||
|
onClick={playCard}>play</Button>
|
||||||
|
}
|
||||||
|
<Button onClick={close}>close</Button>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewCard;
|
@ -10,6 +10,10 @@ const express = require("express"),
|
|||||||
const { corners } = require("./layout.js");
|
const { corners } = require("./layout.js");
|
||||||
const layout = require('./layout.js');
|
const layout = require('./layout.js');
|
||||||
|
|
||||||
|
const MAX_SETTLEMENTS = 5;
|
||||||
|
const MAX_CITIES = 4;
|
||||||
|
const MAX_ROADS = 15;
|
||||||
|
|
||||||
let gameDB;
|
let gameDB;
|
||||||
|
|
||||||
require("../db/games").then(function(db) {
|
require("../db/games").then(function(db) {
|
||||||
@ -115,14 +119,13 @@ const processTies = (players) => {
|
|||||||
if (A.order === B.order) {
|
if (A.order === B.order) {
|
||||||
return B.orderRoll - A.orderRoll;
|
return B.orderRoll - A.orderRoll;
|
||||||
}
|
}
|
||||||
return A.order - B.order;
|
return B.order - A.order;
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Sort the players into buckets based on their
|
/* Sort the players into buckets based on their
|
||||||
* order, and their current roll. If a resulting
|
* order, and their current roll. If a resulting
|
||||||
* roll array has more than one element, then there
|
* roll array has more than one element, then there
|
||||||
* is a tie that must be resolved */
|
* is a tie that must be resolved */
|
||||||
|
|
||||||
let slots = [];
|
let slots = [];
|
||||||
players.forEach(player => {
|
players.forEach(player => {
|
||||||
if (!slots[player.order]) {
|
if (!slots[player.order]) {
|
||||||
@ -135,14 +138,15 @@ const processTies = (players) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let ties = false, order = 0;
|
let ties = false, order = 0;
|
||||||
slots.forEach((slot) => {
|
/* Reverse from high to low */
|
||||||
|
slots.reverse().forEach((slot) => {
|
||||||
slot.forEach(pips => {
|
slot.forEach(pips => {
|
||||||
if (pips.length !== 1) {
|
if (pips.length !== 1) {
|
||||||
ties = true;
|
ties = true;
|
||||||
pips.forEach(player => {
|
pips.forEach(player => {
|
||||||
player.orderRoll = 0;
|
player.orderRoll = 0;
|
||||||
player.order = order;
|
player.order = order;
|
||||||
player.orderStatus = `Tied for ${order+1}.`;
|
player.orderStatus = `Tied.`;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
pips[0].order = order;
|
pips[0].order = order;
|
||||||
@ -258,7 +262,7 @@ const roll = (game, session) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.order || player.orderRoll) {
|
if (player.order && player.orderRoll) {
|
||||||
error = `Player ${name} has already rolled for player order.`;
|
error = `Player ${name} has already rolled for player order.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -402,9 +406,9 @@ const processRoll = (game, dice) => {
|
|||||||
const getPlayer = (game, color) => {
|
const getPlayer = (game, color) => {
|
||||||
if (!game) {
|
if (!game) {
|
||||||
return {
|
return {
|
||||||
roads: 15,
|
roads: MAX_ROADS,
|
||||||
cities: 4,
|
cities: MAX_CITIES,
|
||||||
settlements: 5,
|
settlements: MAX_SETTLEMENTS,
|
||||||
points: 0,
|
points: 0,
|
||||||
status: "Not active",
|
status: "Not active",
|
||||||
lastActive: 0,
|
lastActive: 0,
|
||||||
@ -414,6 +418,7 @@ const getPlayer = (game, color) => {
|
|||||||
sheep: 0,
|
sheep: 0,
|
||||||
wood: 0,
|
wood: 0,
|
||||||
brick: 0,
|
brick: 0,
|
||||||
|
army: 0,
|
||||||
development: []
|
development: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -459,17 +464,34 @@ const loadGame = async (id) => {
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!game) {
|
if (game) {
|
||||||
game = createGame(id);
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
game = JSON.parse(game);
|
game = JSON.parse(game);
|
||||||
|
console.log(`Creating backup of games/${id}`);
|
||||||
|
await writeFile(`games/${id}.bk`, JSON.stringify(game));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error, game);
|
console.log(`Attempting to load backup from games/${id}.bk`);
|
||||||
return null;
|
game = await readFile(`games/${id}.bk`)
|
||||||
|
.catch(() => {
|
||||||
|
console.error(error, game);
|
||||||
|
});
|
||||||
|
if (game) {
|
||||||
|
try {
|
||||||
|
game = JSON.parse(game);
|
||||||
|
console.log(`Restoring backup to games/${id}`);
|
||||||
|
await writeFile(`games/${id}`, JSON.stringify(game, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
game = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!game) {
|
||||||
|
game = createGame(id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!game.pipOrder || !game.borderOrder || !game.tileOrder) {
|
if (!game.pipOrder || !game.borderOrder || !game.tileOrder) {
|
||||||
console.log("Shuffling old save file");
|
console.log("Shuffling old save file");
|
||||||
shuffleBoard(game);
|
shuffleBoard(game);
|
||||||
@ -508,6 +530,9 @@ const loadGame = async (id) => {
|
|||||||
if (!game.players[color].development) {
|
if (!game.players[color].development) {
|
||||||
game.players[color].development = [];
|
game.players[color].development = [];
|
||||||
}
|
}
|
||||||
|
if (!game.players[color].army) {
|
||||||
|
game.players[color].army = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
games[id] = game;
|
games[id] = game;
|
||||||
@ -595,6 +620,7 @@ const adminActions = (game, action, value) => {
|
|||||||
name: next,
|
name: next,
|
||||||
color: getColorFromName(game, next)
|
color: getColorFromName(game, next)
|
||||||
};
|
};
|
||||||
|
game.turns++;
|
||||||
addChatMessage(game, null, `The admin skipped ${name}'s turn.`);
|
addChatMessage(game, null, `The admin skipped ${name}'s turn.`);
|
||||||
addChatMessage(game, null, `It is ${next}'s turn.`);
|
addChatMessage(game, null, `It is ${next}'s turn.`);
|
||||||
break;
|
break;
|
||||||
@ -965,8 +991,7 @@ const calculateRoadLengths = (game, session) => {
|
|||||||
checkForTies = true;
|
checkForTies = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let longest = game.longestRoad ? game.players[game.longestRoad].roadLength : 4,
|
let longest = 4, longestPlayers = [];
|
||||||
longestPlayers = [];
|
|
||||||
for (let key in game.players) {
|
for (let key in game.players) {
|
||||||
if (game.players[key].status === 'Not active') {
|
if (game.players[key].status === 'Not active') {
|
||||||
continue;
|
continue;
|
||||||
@ -1120,12 +1145,43 @@ const isCompatibleOffer = (player, offer) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
valid = offer.gets.find(item =>
|
valid = offer.gets.find(item =>
|
||||||
item.type === give.type &&
|
(item.type === give.type || item.type === 'bank') &&
|
||||||
item.count === give.count) !== undefined;
|
item.count === give.count) !== undefined;
|
||||||
});
|
});
|
||||||
return valid;
|
return valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSameOffer = (player, offer) => {
|
||||||
|
const isBank = offer.name === 'The bank';
|
||||||
|
if (isBank) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let same = player.gets && player.gives &&
|
||||||
|
player.gets.length === offer.gets.length &&
|
||||||
|
player.gives.length === offer.gives.length;
|
||||||
|
|
||||||
|
if (!same) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.gets.forEach(get => {
|
||||||
|
if (!same) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
same = offer.gets.find(item =>
|
||||||
|
item.type === get.type && item.count === get.count) !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (same) player.gives.forEach(give => {
|
||||||
|
if (!same) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
same = offer.gives.find(item =>
|
||||||
|
item.type === give.type && item.count === give.count) !== undefined;
|
||||||
|
});
|
||||||
|
return same;
|
||||||
|
};
|
||||||
|
|
||||||
const checkOffer = (player, offer) => {
|
const checkOffer = (player, offer) => {
|
||||||
let error = undefined;
|
let error = undefined;
|
||||||
offer.gives.forEach(give => {
|
offer.gives.forEach(give => {
|
||||||
@ -1212,7 +1268,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
const name = session.name;
|
const name = session.name;
|
||||||
let message, index;
|
let message, index;
|
||||||
|
|
||||||
let corners, corner;
|
let corners, corner, card;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "trade":
|
case "trade":
|
||||||
@ -1229,6 +1285,11 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
}
|
}
|
||||||
game.turn.actions = [ 'trade' ];
|
game.turn.actions = [ 'trade' ];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
|
for (let key in game.players) {
|
||||||
|
game.players[key].gives = [];
|
||||||
|
game.players[key].gets = [];
|
||||||
|
delete game.players[key].offerRejected;
|
||||||
|
}
|
||||||
addChatMessage(game, session, `${name} requested to begin trading negotiations.`);
|
addChatMessage(game, session, `${name} requested to begin trading negotiations.`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1254,15 +1315,36 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
if (error) {
|
if (error) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSameOffer(session.player, offer)) {
|
||||||
|
console.log(session.player);
|
||||||
|
error = `You already have a pending offer submitted for ${offerToString(offer)}.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
session.player.gives = offer.gives;
|
session.player.gives = offer.gives;
|
||||||
session.player.gets = offer.gets;
|
session.player.gets = offer.gets;
|
||||||
|
|
||||||
if (game.turn.name === name) {
|
if (game.turn.name === name) {
|
||||||
|
/* This is a new offer from the active player -- reset everyone's
|
||||||
|
* 'offerRejected' flag */
|
||||||
|
for (let key in game.players) {
|
||||||
|
delete game.players[key].offerRejected;
|
||||||
|
}
|
||||||
game.turn.offer = offer;
|
game.turn.offer = offer;
|
||||||
}
|
}
|
||||||
addChatMessage(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
|
addChatMessage(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Any player can reject an offer */
|
||||||
|
if (value === 'reject') {
|
||||||
|
const offer = req.body;
|
||||||
|
session.player.offerRejected = true;
|
||||||
|
addChatMessage(game, session, `${session.name} rejected ${game.turn.name}'s offer.`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Only the active player can accept an offer */
|
/* Only the active player can accept an offer */
|
||||||
if (value === 'accept') {
|
if (value === 'accept') {
|
||||||
if (game.turn.name !== name) {
|
if (game.turn.name !== name) {
|
||||||
@ -1273,6 +1355,11 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
const offer = req.body;
|
const offer = req.body;
|
||||||
let target;
|
let target;
|
||||||
|
|
||||||
|
error = checkOffer(session.player, offer);
|
||||||
|
if (error) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Verify that the offer sent by the active player matches what
|
/* Verify that the offer sent by the active player matches what
|
||||||
* the latest offer was that was received by the requesting player */
|
* the latest offer was that was received by the requesting player */
|
||||||
if (!offer.name || offer.name !== 'The bank') {
|
if (!offer.name || offer.name !== 'The bank') {
|
||||||
@ -1321,6 +1408,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
player[item.type] -= item.count;
|
player[item.type] -= item.count;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addChatMessage(game, session, `${session.name} has accepted a trade ` +
|
||||||
|
`offer for ${offerToString(session.player)} ` +
|
||||||
|
`from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`);
|
||||||
|
|
||||||
delete game.turn.offer;
|
delete game.turn.offer;
|
||||||
if (target) {
|
if (target) {
|
||||||
delete target.gives;
|
delete target.gives;
|
||||||
@ -1331,8 +1422,6 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
|
|
||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
|
|
||||||
addChatMessage(game, session, `${session.name} has accepted a trade ` +
|
|
||||||
`offer from ${(offer.name === 'The bank') ? 'the bank' : offer.name}.`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1375,6 +1464,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
name: next,
|
name: next,
|
||||||
color: getColorFromName(game, next)
|
color: getColorFromName(game, next)
|
||||||
};
|
};
|
||||||
|
game.turns++;
|
||||||
addChatMessage(game, session, `${name} passed their turn.`);
|
addChatMessage(game, session, `${name} passed their turn.`);
|
||||||
addChatMessage(game, null, `It is ${next}'s turn.`);
|
addChatMessage(game, null, `It is ${next}'s turn.`);
|
||||||
break;
|
break;
|
||||||
@ -1444,7 +1534,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (cards.length === 0) {
|
if (cards.length === 0) {
|
||||||
addChatMessage(game, session, `Victim did not have any cards to steal.`);
|
addChatMessage(game, session, `${playerNameFromColor(game, value)} did not have any cards to steal.`);
|
||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
} else {
|
} else {
|
||||||
@ -1473,6 +1563,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `You cannot build until you have rolled.`;
|
error = `You cannot build until you have rolled.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.turn && game.turn.roll === 7 && !game.turn.robberDone) {
|
||||||
|
error = `Robber is in action. You can not purchase until all Robber tasks are resolved.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (player.stone < 1 || player.wheat < 1 || player.sheep < 1) {
|
if (player.stone < 1 || player.wheat < 1 || player.sheep < 1) {
|
||||||
error = `You have insufficient resources to purchase a development card.`;
|
error = `You have insufficient resources to purchase a development card.`;
|
||||||
break;
|
break;
|
||||||
@ -1488,7 +1584,76 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
player.stone--;
|
player.stone--;
|
||||||
player.wheat--;
|
player.wheat--;
|
||||||
player.sheep--;
|
player.sheep--;
|
||||||
player.development.push(game.developmentCards.pop());
|
card = game.developmentCards.pop();
|
||||||
|
card.turn = game.turns;
|
||||||
|
player.development.push(card);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'play-card':
|
||||||
|
if (game.state !== 'normal') {
|
||||||
|
error = `You cannot purchase a settlement 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 play a card until you have rolled.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.turn && game.turn.roll === 7 && !game.turn.robberDone) {
|
||||||
|
error = `Robber is in action. You can not play a card until all Robber tasks are resolved.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
card = req.body;
|
||||||
|
card = player.development.find(item => item.type == card.type && item.card == card.card);
|
||||||
|
if (!card) {
|
||||||
|
error = `The card you want to play was not found in your hand!`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.playedCard === game.turns && card.type !== 'vp') {
|
||||||
|
error = `You can only play one development card per turn!`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.played) {
|
||||||
|
error = `You have already played this card.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if this is a victory point */
|
||||||
|
if (card.type === 'vp') {
|
||||||
|
let points = player.points;
|
||||||
|
player.development.forEach(item => {
|
||||||
|
if (item.type === 'vp') {
|
||||||
|
points++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (points < 10) {
|
||||||
|
error = `You can not play victory point cards until you can reach 10!`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
card.played = true;
|
||||||
|
player.playedCard = game.turns;
|
||||||
|
addChatMessage(game, session, `${session.name} played a ${card.type}-${card.card} development card.`);
|
||||||
|
|
||||||
|
if (card.type === 'army') {
|
||||||
|
player.army++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.army > 2 &&
|
||||||
|
(!game.largestArmy || game.players[game.largestArmy].army < player.army)) {
|
||||||
|
if (game.largestArmy !== session.color) {
|
||||||
|
game.largestArmy = session.color;
|
||||||
|
addChatMessage(game, session, `${session.name} now has the largest army (${player.army})!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'buy-settlement':
|
case 'buy-settlement':
|
||||||
@ -1504,6 +1669,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `You cannot build until you have rolled.`;
|
error = `You cannot build until you have rolled.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.turn && game.turn.roll === 7 && !game.turn.robberDone) {
|
||||||
|
error = `Robber is in action. You can not purchase until all Robber tasks are resolved.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) {
|
if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) {
|
||||||
error = `You have insufficient resources to build a settlement.`;
|
error = `You have insufficient resources to build a settlement.`;
|
||||||
break;
|
break;
|
||||||
@ -1606,6 +1777,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
player.settlements--;
|
||||||
player.maritime = player.banks.map(bank => game.borders[Math.floor(bank / 3) + bank % 3]);
|
player.maritime = player.banks.map(bank => game.borders[Math.floor(bank / 3) + bank % 3]);
|
||||||
game.turn.actions = ['place-road'];
|
game.turn.actions = ['place-road'];
|
||||||
game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */
|
game.turn.limits = { roads: layout.corners[index].roads }; /* road placement is limited to be near this corner */
|
||||||
@ -1630,6 +1802,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `You have insufficient resources to build a city.`;
|
error = `You have insufficient resources to build a city.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.turn && game.turn.roll === 7 && !game.turn.robberDone) {
|
||||||
|
error = `Robber is in action. You can not purchase until all Robber tasks are resolved.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (player.city < 1) {
|
if (player.city < 1) {
|
||||||
error = `You have already built all of your cities.`;
|
error = `You have already built all of your cities.`;
|
||||||
break;
|
break;
|
||||||
@ -1704,6 +1882,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `You cannot build until you have rolled.`;
|
error = `You cannot build until you have rolled.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.turn && game.turn.roll === 7 && !game.turn.robberDone) {
|
||||||
|
error = `Robber is in action. You can not purchase until all Robber tasks are resolved.`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (player.brick < 1 || player.wood < 1) {
|
if (player.brick < 1 || player.wood < 1) {
|
||||||
error = `You have insufficient resources to build a road.`;
|
error = `You have insufficient resources to build a road.`;
|
||||||
break;
|
break;
|
||||||
@ -1792,7 +1976,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
color: getColorFromName(game, next)
|
color: getColorFromName(game, next)
|
||||||
};
|
};
|
||||||
calculateRoadLengths(game, session);
|
calculateRoadLengths(game, session);
|
||||||
addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`);
|
addChatMessage(game, null, `It is ${next}'s turn to place a settlement.`);
|
||||||
} else {
|
} else {
|
||||||
game.turn = {
|
game.turn = {
|
||||||
actions: [],
|
actions: [],
|
||||||
@ -1922,12 +2106,8 @@ const sendGame = async (req, res, game, error) => {
|
|||||||
/* Enforce game limit of >= 2 players */
|
/* Enforce game limit of >= 2 players */
|
||||||
if (active < 2 && game.state != 'lobby' && game.state != 'invalid') {
|
if (active < 2 && game.state != 'lobby' && game.state != 'invalid') {
|
||||||
let message = "Insufficient players in game. Setting back to lobby."
|
let message = "Insufficient players in game. Setting back to lobby."
|
||||||
console.log(game);
|
|
||||||
addChatMessage(game, null, message);
|
addChatMessage(game, null, message);
|
||||||
console.log(message);
|
resetGame(game);
|
||||||
/* It is no one's turn in the lobby */
|
|
||||||
delete game.turn;
|
|
||||||
game.state = 'lobby';
|
|
||||||
}
|
}
|
||||||
game.active = active;
|
game.active = active;
|
||||||
|
|
||||||
@ -1950,11 +2130,6 @@ const sendGame = async (req, res, game, error) => {
|
|||||||
}
|
}
|
||||||
game.turn.limits.pips.push(i);
|
game.turn.limits.pips.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
game.turn.limits = {};
|
|
||||||
game.turn.actions = [];
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1981,6 +2156,30 @@ const sendGame = async (req, res, game, error) => {
|
|||||||
lastTime = message.date;
|
lastTime = message.date;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* Calculate points and determine if there is a winner */
|
||||||
|
for (let key in game.players) {
|
||||||
|
const player = game.players[key];
|
||||||
|
if (player.status === 'Not active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
player.points = 0;
|
||||||
|
if (key === game.longestRoad) {
|
||||||
|
player.points += 2;
|
||||||
|
}
|
||||||
|
if (key === game.largestArmy) {
|
||||||
|
player.points += 2;
|
||||||
|
}
|
||||||
|
player.points += MAX_SETTLEMENTS - player.settlements;
|
||||||
|
player.points += 2 * (MAX_CITIES - player.cities);
|
||||||
|
|
||||||
|
if (!game.winner && player.points > 10 && session.color === key) {
|
||||||
|
addChatMessage(game, null, `${playerNameFromColor(game, key)} won the game with ${player.points} victory points!`);
|
||||||
|
game.winner = player;
|
||||||
|
game.state = 'winner';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then
|
/* Shallow copy game, filling its sessions with a shallow copy of sessions so we can then
|
||||||
* delete the player field from them */
|
* delete the player field from them */
|
||||||
const reducedGame = Object.assign({}, game, { sessions: {} }),
|
const reducedGame = Object.assign({}, game, { sessions: {} }),
|
||||||
@ -2003,13 +2202,25 @@ const sendGame = async (req, res, game, error) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const player = session.player ? session.player : undefined;
|
||||||
|
if (player) {
|
||||||
|
player.haveResources = player.wheat > 0 ||
|
||||||
|
player.brick > 0 ||
|
||||||
|
player.sheep > 0 ||
|
||||||
|
player.stone > 0 ||
|
||||||
|
player.wood > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip out data that should not be shared with players */
|
||||||
|
delete reducedGame.developmentCards;
|
||||||
|
|
||||||
const playerGame = Object.assign({}, reducedGame, {
|
const playerGame = Object.assign({}, reducedGame, {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
status: error ? error : "success",
|
status: error ? error : "success",
|
||||||
name: session.name,
|
name: session.name,
|
||||||
color: session.color,
|
color: session.color,
|
||||||
order: (session.color in game.players) ? game.players[session.color].order : 0,
|
order: (session.color in game.players) ? game.players[session.color].order : 0,
|
||||||
player: session.player,
|
player: player,
|
||||||
sessions: reducedSessions,
|
sessions: reducedSessions,
|
||||||
layout: layout
|
layout: layout
|
||||||
});
|
});
|
||||||
@ -2018,9 +2229,9 @@ const sendGame = async (req, res, game, error) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resetGame = (game) => {
|
const resetGame = (game) => {
|
||||||
delete game.turn;
|
|
||||||
|
|
||||||
game.state = 'lobby';
|
game.state = 'lobby';
|
||||||
|
game.turns = 0;
|
||||||
|
|
||||||
game.placements = {
|
game.placements = {
|
||||||
corners: [],
|
corners: [],
|
||||||
@ -2045,15 +2256,14 @@ const resetGame = (game) => {
|
|||||||
stone: 0,
|
stone: 0,
|
||||||
brick: 0,
|
brick: 0,
|
||||||
wood: 0,
|
wood: 0,
|
||||||
roads: 15,
|
roads: MAX_ROADS,
|
||||||
cities: 4,
|
cities: MAX_CITIES,
|
||||||
settlements: 5,
|
settlements: MAX_SETTLEMENTS,
|
||||||
points: 0,
|
points: 0,
|
||||||
development: []
|
development: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
game.developmentCards = assetData.developmentCards.slice();
|
|
||||||
shuffle(game.developmentCards);
|
shuffle(game.developmentCards);
|
||||||
|
|
||||||
for (let i = 0; i < layout.corners.length; i++) {
|
for (let i = 0; i < layout.corners.length; i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user