Many updates.

logins
Thomas Hintz 5 years ago
parent 77a8692f71
commit c8e9ea1842

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": true,
"noEmit": true,
"checkJs": true,
"jsx": "react",
"lib": [ "dom", "es2017" ],
"include": [
"src",
"assets/game/acf"
]
}
}

@ -15,7 +15,7 @@
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "GPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.3", "@babel/core": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-class-properties": "^7.8.3",

@ -30,8 +30,8 @@ import ReactDOM from 'react-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser, faUsers, faTractor, faWindowRestore, import { faUser, faUsers, faTractor, faWindowRestore,
faDollarSign, faTimes, faAsterisk faDollarSign, faTimes, faAsterisk, faExchangeAlt,
} from '@fortawesome/free-solid-svg-icons' faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import { GroupBox, Row, Col, Button } from '../widgets.jsx' import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import SpaceNode from './SpaceNode.jsx' import SpaceNode from './SpaceNode.jsx'
@ -40,7 +40,7 @@ import Tractor from '../tractor/Tractor.jsx'
import { GAME_STATES, ALERTS } from '../../constants.js' import { GAME_STATES, ALERTS } from '../../constants.js'
import { itemCard, itemCardShort, fateCard, ridgeNames } from 'game.js' import { itemCard, itemCardShort, fateCard, ridgeNames } from 'game.js'
import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer, import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
nextUIAction, alert } from './actions.js' nextUIAction, alert, alertHandled } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept, import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip } from './interface.js' buyUncleBert, skip } from './interface.js'
@ -69,7 +69,8 @@ function getString(id) {
} }
function getInt(id) { function getInt(id) {
return parseInt(getElementValue(id)); const i = parseInt(getElementValue(id));
return isNaN(i) ? 0 : i;
} }
function getChecked(id) { function getChecked(id) {
@ -102,7 +103,7 @@ function tradeString(player, invert) {
var r = ''; var r = '';
let mult = invert ? -1 : 1; let mult = invert ? -1 : 1;
for (var k in player.trade) { for (var k in player.trade) {
if (k !== 'player' && k !== 'originator' && k !== 'cards') { if (k !== 'player' && k !== 'originator' && k !== 'cards' && k !== 'trade-number') {
r += (player.trade[k] === true ? '' : player.trade[k] * mult) + ' ' + k + ', '; r += (player.trade[k] === true ? '' : player.trade[k] * mult) + ' ' + k + ', ';
} else if (k === 'cards') { } else if (k === 'cards') {
r += 'cards: ' + player.trade[k] + ', '; r += 'cards: ' + player.trade[k] + ', ';
@ -113,13 +114,15 @@ function tradeString(player, invert) {
class PlayerTradeProposed extends React.Component { class PlayerTradeProposed extends React.Component {
render () { render () {
if (this.props.player.trade.player === undefined) { if (this.props.player.trade.error) {
return (<br />); return (<p><b>ERROR:</b> {this.props.player.trade.error}</p>);
} else if (this.props.player.trade.player === undefined) {
return (<p>You GAIN something if it is POSITIVE and you GIVE something if it is NEGATIVE.</p>);
} else if (this.props.player.trade.originator === this.props.player.name) { } else if (this.props.player.trade.originator === this.props.player.name) {
return (<p>You proposed a trade with <b>{this.props.player.trade.player}</b> for return (<p>You proposed a trade with <b>{this.props.player.trade.player}</b> for
{'\u00A0'}<b>{tradeString(this.props.player, false)}</b>.</p>); {'\u00A0'}<b>{tradeString(this.props.player, false)}</b>.</p>);
} else { } else {
return (<p><b>{this.props.player.trade.player}</b> proposed a trade for return (<p><b>{this.props.player.trade.originator}</b> proposed a trade for
{'\u00A0'}<b>{tradeString(this.props.player, true)}</b>.</p>); {'\u00A0'}<b>{tradeString(this.props.player, true)}</b>.</p>);
} }
} }
@ -159,7 +162,7 @@ class PlayerTradeButton extends React.Component {
} else { } else {
return (<Fragment> return (<Fragment>
<Button className='tiny' type='submit'>{text} Trade</Button> <Button className='tiny' type='submit'>{text} Trade</Button>
<Button className='tiny' onClick={this.submitTradeDeny}> <Button className='tiny' onClick={this.tradeDeny}>
Deny Trade Deny Trade
</Button> </Button>
</Fragment>); </Fragment>);
@ -312,7 +315,9 @@ class PlayerTurnContainer extends React.Component {
worth = netWorth(player), worth = netWorth(player),
auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.settings.auditThreshold) ? ( auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.settings.auditThreshold) ? (
<Button onClick={audit}>Call Audit</Button>) : ''; <Button onClick={audit}>Call Audit</Button>) : '';
if (player.state === GAME_STATES.preTurn) { if (this.props.game.state === 'finished') {
view = (<b>Game finished!</b>);
} else if (player.state === GAME_STATES.preTurn) {
view = ( view = (
<Fragment> <Fragment>
<Button onClick={this.clickRoll}>Roll</Button>{' '}{auditButton} <Button onClick={this.clickRoll}>Roll</Button>{' '}{auditButton}
@ -344,8 +349,6 @@ class PlayerTurnContainer extends React.Component {
</Fragment>); </Fragment>);
} }
} }
} else if (this.props.game.state === 'finished') {
view = (<b>Game finished!</b>);
} else { } else {
view = ( view = (
<Button onClick={() => this.props.showScreen(SCREENS.action)}> <Button onClick={() => this.props.showScreen(SCREENS.action)}>
@ -817,6 +820,10 @@ class Harvest extends React.Component {
autoSkipView: false }; autoSkipView: false };
} }
nameText = player => {
return player === this.props.game.currentPlayer ? 'You' : player;
}
nextView = (view, preventAutoSkip) => { nextView = (view, preventAutoSkip) => {
const newView = view ? view : const newView = view ? view :
this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1]; this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1];
@ -908,19 +915,34 @@ class Harvest extends React.Component {
); );
break; break;
case 'expense-value': case 'expense-value':
const currentExpense = this.props.expenseValue[this.props.game.currentPlayer];
view = ( view = (
<div> <div>
<div className='game-card'> <div className='game-card'>
<div className={'flex ' + (this.props.expenseValue < 0 ? 'red' : 'green')}> <div className={'flex ' + (currentExpense <= 0 ? 'red' : 'green')}>
<FontAwesomeIcon icon={faDollarSign} size='6x' /> <FontAwesomeIcon icon={faDollarSign} size='6x' />
<div> <div>
{isCurrentPlayer ? 'You' : <p>
this.props.game.currentPlayer} {this.props.expenseValue < 0 ? 'lost ' : 'gained '} {isCurrentPlayer ? 'You' : this.props.game.currentPlayer}
${Math.abs(this.props.expenseValue)}! {currentExpense <= 0 ? ' lost ' : ' gained '}
${formatMoney(Math.abs(currentExpense))}!
</p>
{Object.keys(this.props.expenseValue)
.filter(p => p !== this.props.game.currentPlayer)
.map((p, i) =>
(
<p key={i}>
{this.nameText(p) + ' '}
{this.props.expenseValue[p] <= 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(this.props.expenseValue[p]))}!
</p>
)
)}
</div> </div>
</div> </div>
</div> </div>
{isCurrentPlayer ? ( {isCurrentPlayer ?
(
<div className='center spacer'> <div className='center spacer'>
<Button onClick={this.props.nextAction}>Continue</Button> <Button onClick={this.props.nextAction}>Continue</Button>
</div> </div>
@ -1081,25 +1103,32 @@ class Action extends React.Component {
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break; break;
case 'ff-uncle-bert': case 'ff-uncle-bert':
const ffButtons = (
<Fragment>
{this.props.player.cash >= 10000 ? (
<Button onClick={() => { buyUncleBert(); this.props.showNextAction(); }}>
Yes, take over for $10,000!
</Button>
) : (<Fragment />)}
<Button onClick={() => this.props.showNextAction()}>
No, continue on
</Button>
</Fragment>
);
view = ( view = (
<GroupBox title={`Uncle Bert's inheritance`}> <GroupBox title={`Uncle Bert's inheritance`}>
<div className='center'> <div className='center'>
<p> <p>
{this.props.player.cash >= 10000 ? {currentPlayer.cash >= 10000 ?
`You have enough cash to take over Uncle Bert's farm!` : `You have enough cash to take over Uncle Bert's farm!` :
`You must raise another $` + `You must raise another $` +
formatMoney(10000 - this.props.player.cash) + formatMoney(10000 - currentPlayer.cash) +
` to be able to take over Uncle Berts farm!` ` to be able to take over Uncle Berts farm!`
} }
</p> </p>
<p> <p>
{this.props.player.cash >= 10000 ? {(this.props.player.name === this.props.game.currentPlayer) ?
(<Button onClick={() => { buyUncleBert(); this.props.showNextAction(); }}> ffButtons : (<Fragment />)}
Yes, take over for $10,000!</Button>) :
(<Fragment />)}
<Button onClick={() => this.props.showNextAction()}>
No, continue on
</Button>
</p> </p>
</div> </div>
</GroupBox> </GroupBox>
@ -1107,13 +1136,15 @@ class Action extends React.Component {
buttons = (<Fragment />); buttons = (<Fragment />);
break; break;
case 'money': case 'money':
view = (<div className='game-card'> const { amount, player } = this.props.ui.actionValue;
<div className={'flex ' + (this.props.ui.actionValue < 0 ? 'red' : 'green')}> view = (
<div className='game-card'>
<div className={'flex ' + (amount < 0 ? 'red' : 'green')}>
<FontAwesomeIcon icon={faDollarSign} size='6x' /> <FontAwesomeIcon icon={faDollarSign} size='6x' />
<div> <div>
{this.props.player.name === this.props.game.currentPlayer ? 'You' : {this.props.player.name === player ? 'You' :
this.props.game.currentPlayer} {this.props.ui.actionValue < 0 ? 'lost ' : 'gained '} player} {amount <= 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(this.props.ui.actionValue))}! ${formatMoney(Math.abs(amount))}!
</div> </div>
</div> </div>
</div>); </div>);
@ -1211,7 +1242,7 @@ class Board extends React.Component {
render() { render() {
const rh = this.props.height || '20px'; // height={h} const rh = this.props.height || '20px'; // height={h}
const renderSpace = (s, h, o) => const renderSpace = (s, h, o) =>
(<SpaceNode space={s} key={s.key} orientation={o} />); (<SpaceNode showIcons={true} space={s} key={s.key} orientation={o} />);
return ( return (
<Fragment> <Fragment>
@ -1277,6 +1308,7 @@ class AlertOverlay extends React.Component {
hide = e => { hide = e => {
e.preventDefault(); e.preventDefault();
this.setState({ visible: false }); this.setState({ visible: false });
this.props.alertHandled(this.props.id);
this.props.hideHandler(); this.props.hideHandler();
} }
@ -1284,6 +1316,7 @@ class AlertOverlay extends React.Component {
this.hide(e); this.hide(e);
this.props.handler(); this.props.handler();
} }
// <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
render() { render() {
return ( return (
@ -1295,7 +1328,6 @@ class AlertOverlay extends React.Component {
{this.props.children} {this.props.children}
<br /> <br />
<Button onClick={this.buttonClick}>{this.props.buttonText}</Button> <Button onClick={this.buttonClick}>{this.props.buttonText}</Button>
<label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
<a onClick={this.hide}>close</a> <a onClick={this.hide}>close</a>
</div> </div>
</div> </div>
@ -1317,27 +1349,127 @@ class InfoBar extends React.Component {
} }
} }
function ceilHundred(n) {
return Math.ceil(n + (n % 100));
}
class HarvestTable extends React.Component {
state = {
selected: 'hay'
}
selectCrop = e => {
this.setState({ selected: e.target.value });
}
render() {
const table = this.props.table,
selectedCrop = this.state.selected;
return (
<div className="harvest-table">
<h4>Crop</h4>
<select onChange={this.selectCrop} defaultValue={selectedCrop}>
{Object.keys(table).map((k, idx) => (
<option key={idx} value={k}>{k}</option>
))}
</select>
<table>
<thead>
<tr>
<th>Dice</th>{table[selectedCrop].slice(1).map((x, idx) => (
<th key={idx}>{(idx + 1) * table[selectedCrop][0]} {selectedCrop === 'cows' ? 'COWS' : 'ACRES'}</th>
))}
</tr>
</thead>
<tbody>
{table[selectedCrop].slice(1).map((mult, row) => (
<tr key={row}>
{new Array(7).fill(0).map((x, idx) => (
<td key={idx}>{idx === 0 ? row + 1 : formatMoney(ceilHundred(mult * idx))}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
}
class Info extends React.Component {
render() {
// TODO change $250,000 to actual value in settings
return (
<>
<h1>Game Instructions</h1>
<h2>Winner</h2>
<p>The winner is the player with the highest net worth.</p>
<h2>Game Ending</h2>
<p>When a player reaches a net worth of $250,000 or more they may 'Call Audit'. Once they call an audit all players continue playing until they finish their current year. Once all players have finished their last year the game is over and the player with the highest net worth is the winner.</p>
<h2>Harvests</h2>
<p>Harvests occur once per block on the matching asset type. For example: hay shows as green and is harvested the first time landing on a block of green spaces each time around the board.</p>
<p>When harvesting a resource the die is first rolled then the harvest amount is calculated based on the following table and how many units of the asset you own:</p>
{this.props.harvestTable ? (<HarvestTable table={this.props.harvestTable} />) : (<></>)}
<p>After a harvest an Operating Expense is drawn.</p>
<h2>Purchashing</h2>
<p>To increase harvest amounts new assets must be bought. This is done via {itemCard} cards. Click on <FontAwesomeIcon icon={faWindowRestore} />, select the card for the asset you wish to purchase, and enter the amount of cash you desire to spend to purchace the selected asset. The remainder will be taken out in debit, if available.</p>
<p>Purchashing may only take places on Purchashing spaces (Christmas - Spring Celebration).</p>
<Misc />
</>
);
}
}
const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms', const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms',
cards: 'cards', trade: 'trade', loans: 'loans', cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action' }; action: 'action', info: 'info' };
class BoardApp extends React.Component { class BoardApp extends React.Component {
iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards, iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards,
'dollar-sign': SCREENS.loans, users: SCREENS.farms, 'dollar-sign': SCREENS.loans, users: SCREENS.farms,
'exchange-alt': SCREENS.trade, asterisk: SCREENS.misc, 'exchange-alt': SCREENS.trade, asterisk: SCREENS.misc,
tractor: SCREENS.action } tractor: SCREENS.action, 'info-circle': SCREENS.info }
unsubscribeAlert = () => null unsubscribeAlert = () => null
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
screen: SCREENS.summary screen: SCREENS.summary,
card: props.ui.card
}; };
this.myRef = React.createRef();
} }
showScreen = screen => { showScreen = screen => {
if (!(window.innerWidth >= 1024 && screen === SCREENS.action)) {
this.setState({ screen: screen }); this.setState({ screen: screen });
} }
}
setCard = card => {
this.setState({ card });
}
componentDidUpdate() {
if (this.state.card.type === 'no-card' &&
this.props.ui.cards.length > 0) {
this.setState({ card: this.props.ui.cards[0] });
} else if (this.state.card.type !== 'no-card' &&
this.props.ui.cards.length === 0) {
this.setState({ card: { type: 'no-card', contents: '', total: 0 } });
} else if (this.state.card.type !== 'no-card' &&
this.props.ui.cards.map(x => x.id).indexOf(this.state.card.id) < 0) {
this.setState({ card: this.props.ui.cards[0] });
}
// we don't show this alert when on the actions screen so mark
// it as handled.
if (this.props.ui.unhandledAlert &&
this.props.ui.unhandledAlert.type === ALERTS.beginTurn &&
this.state.screen === SCREENS.action) {
this.props.alertHandled(this.props.ui.unhandledAlert.id);
}
}
componentDidMount() { componentDidMount() {
// const midColHeight = (window.innerHeight - // const midColHeight = (window.innerHeight -
@ -1360,7 +1492,10 @@ class BoardApp extends React.Component {
} }
iconClass = icon => { iconClass = icon => {
return this.iconToScreen[icon] === this.state.screen ? 'is-active' : ' '; return this.iconToScreen[icon] === this.state.screen ? 'is-active' :
(this.iconToScreen[icon] === SCREENS.trade ?
(this.props.game.settings.trade ? ' ' : ' hidden ')
: (icon === 'tractor' ? ' hide-for-large ' : ' '));
} }
tabClass = screen => { tabClass = screen => {
@ -1376,27 +1511,62 @@ class BoardApp extends React.Component {
render() { render() {
let alertOverlay; let alertOverlay;
if (!this.props.ui.alertHandled && this.props.ui.alert === ALERTS.endOfGame) { const alert = this.props.ui.unhandledAlert;
if (alert && alert.type === ALERTS.endOfGame) {
alertOverlay = ( alertOverlay = (
<AlertOverlay visible={true} <AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Close' buttonText='Close'
hideHandler={() => this.props.alert(false)} hideHandler={() => 'nothing'}
handler={() => { return false; }}> handler={() => { return false; }}>
<Fragment> <Fragment>
<h1>Game Over!</h1> <h1>Game Over!</h1>
{this.props.ui.alertContents.map((e, i) => ( {alert.contents.map((e, i) => (
<p key={i}>{e}</p> <p key={i}>{e}</p>
))} ))}
</Fragment> </Fragment>
</AlertOverlay> </AlertOverlay>
); );
} else if (!this.props.ui.alertHandled && this.state.screen !== SCREENS.action) { } else if (alert && alert.type === ALERTS.auditCalled) {
switch (this.props.ui.alert) { alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Close'
hideHandler={() => 'nothing'}
handler={() => { return false; }}>
<Fragment>
<h1>Audit Called!</h1>
<p>{alert.contents} called an audit!</p>
<p>This is your last trip around the board.</p>
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.proposedTrade) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='View Trade'
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.trade)
}}>
<Fragment>
<h1>{alert.contents} proposed a trade!</h1>
</Fragment>
</AlertOverlay>
);
} else if (alert && this.state.screen !== SCREENS.action) {
switch (alert.type) {
case ALERTS.beginTurn: case ALERTS.beginTurn:
alertOverlay = ( alertOverlay = (
<AlertOverlay visible={true} <AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Roll' buttonText='Roll'
hideHandler={() => this.props.alert(false)} hideHandler={() => 'nothing'}
handler={() => { handler={() => {
roll(); roll();
this.showScreen(SCREENS.action); }}> this.showScreen(SCREENS.action); }}>
@ -1407,8 +1577,10 @@ class BoardApp extends React.Component {
case ALERTS.otherPlayersTurn: case ALERTS.otherPlayersTurn:
alertOverlay = ( alertOverlay = (
<AlertOverlay visible={true} <AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText={'Watch ' + this.props.game.currentPlayer + `'s turn`} buttonText={'Watch ' + this.props.game.currentPlayer + `'s turn`}
hideHandler={() => this.props.alert(false)} hideHandler={() => 'nothing'}
handler={() => { handler={() => {
this.showScreen(SCREENS.action); }}> this.showScreen(SCREENS.action); }}>
<h1>It&apos;s {this.props.game.currentPlayer}&apos;s turn</h1> <h1>It&apos;s {this.props.game.currentPlayer}&apos;s turn</h1>
@ -1421,11 +1593,13 @@ class BoardApp extends React.Component {
} else { } else {
alertOverlay = (<Fragment />); alertOverlay = (<Fragment />);
} }
if (this.props.ui.alert === ALERTS.raiseMoney) { if (alert && alert.type === ALERTS.raiseMoney && !this.props.ui.action) {
alertOverlay = ( alertOverlay = (
<AlertOverlay visible={true} <AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Raise Money' buttonText='Raise Money'
hideHandler={() => this.props.alert(false)} hideHandler={() => 'nothing'}
handler={() => { handler={() => {
this.showScreen(SCREENS.loans); }}> this.showScreen(SCREENS.loans); }}>
<Fragment> <Fragment>
@ -1437,7 +1611,7 @@ class BoardApp extends React.Component {
} }
// faExchangeAlt -> trade icon, hidden for now // faExchangeAlt -> trade icon, hidden for now
return ( return (
<div className='game-container'> <div className='game-container' ref={this.myRef}>
{alertOverlay} {alertOverlay}
<Board spaces={this.props.spaces}> <Board spaces={this.props.spaces}>
<InfoBar message={this.props.ui.message} <InfoBar message={this.props.ui.message}
@ -1445,7 +1619,7 @@ class BoardApp extends React.Component {
players={this.props.game.otherPlayers} /> players={this.props.game.otherPlayers} />
<div className='center-board-container'> <div className='center-board-container'>
<ul className='horizontal menu icons icons-top'> <ul className='horizontal menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk] {[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faExchangeAlt, faInfoCircle]
.map((icon, i) => .map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}> (<li key={i} className={this.iconClass(icon.iconName)}>
<div></div> <div></div>
@ -1453,7 +1627,7 @@ class BoardApp extends React.Component {
<FontAwesomeIcon icon={icon} /></a></li>))} <FontAwesomeIcon icon={icon} /></a></li>))}
</ul> </ul>
<ul className='vertical menu icons icons-top'> <ul className='vertical menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk] {[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faExchangeAlt, faInfoCircle]
.map((icon, i) => .map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}> (<li key={i} className={this.iconClass(icon.iconName)}>
<a onClick={this.iconOnClick(icon.iconName)}> <a onClick={this.iconOnClick(icon.iconName)}>
@ -1481,13 +1655,18 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.cards)}> <div className={this.tabClass(SCREENS.cards)}>
<Row> <Row>
<div className='cell medium-auto'> <div className='cell medium-auto'>
<CardList ui={this.props.ui} /> <CardList ui={this.props.ui} cardId={this.state.card.id}
setCard={this.setCard} />
</div> </div>
<div className='cell medium-auto'> <div className='cell medium-auto'>
<Card ui={this.props.ui} <Card ui={this.props.ui}
min={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000} playerCash={this.props.player.cash}
max={Math.floor(Math.min(this.props.player.cash / 1000, this.props.ui.card.total / 1000))} playerDebt={this.props.player.debt}
cash={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000} /> card={this.state.card}
cost={this.state.card.total / 1000}
min={(this.state.card.total * this.props.game.settings.downPayment) / 1000}
max={Math.floor(Math.min(this.props.player.cash / 1000, this.state.card.total / 1000))}
cash={(this.state.card.total * this.props.game.settings.downPayment) / 1000} />
</div> </div>
</Row> </Row>
</div> </div>
@ -1504,6 +1683,23 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.misc)}> <div className={this.tabClass(SCREENS.misc)}>
<Misc /> <Misc />
</div> </div>
<div className={this.tabClass(SCREENS.info)}>
<Info harvestTable={this.props.ui.harvestTable} game={this.props.game} />
</div>
</div>
<div className='static-tab-container show-for-large'>
<div className='tab show'>
<Action
spaces={this.props.spaces}
player={this.props.player}
game={this.props.game}
movePlayer={this.props.movePlayer}
showNextAction={this.props.nextUIAction}
otherPlayersTurn={this.props.player.name !== this.props.game.currentPlayer}
screen={this.state.screen}
showScreen={screen => this.showScreen(screen)}
ui={this.props.ui} />
</div>
</div> </div>
</div> </div>
</Board> </Board>
@ -1514,7 +1710,7 @@ class BoardApp extends React.Component {
export default connect( export default connect(
state => state.farm, state => state.farm,
{ setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert } { setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert, alertHandled }
)(BoardApp) )(BoardApp)
class Card extends React.Component { class Card extends React.Component {
@ -1543,37 +1739,47 @@ class Card extends React.Component {
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
buy(this.props.ui.card.id, this.state.cash); buy(this.props.card.id, this.state.cash);
} }
render () { render () {
const card = this.props.ui.card; const card = this.props.card;
let action = (null); let action = (null);
switch (card.type) { switch (card.type) {
case 'otb': action = ( case 'otb': action = (
<div className='card-action'> <div className='card-action'>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<Row collapse='true'>
<Col width='12'>
You: <b>Cash</b>: ${formatMoney(this.props.playerCash)} <b>Debt</b>: ${formatMoney(this.props.playerDebt)}
</Col>
</Row>
<Row collapse='true'>
<Col width='12'>
Buy using: ${this.state.cash}K cash ${this.props.cost - this.state.cash}K debt
</Col>
</Row>
<Row collapse='true'> <Row collapse='true'>
<Col width='2'> <Col width='2'>
<Button className='tiny' <Button className='tiny'
disabled={this.props.max < this.props.min} disabled={this.props.max < this.props.min}
type='submit'>Buy</Button> type='submit'>Buy</Button>
</Col> </Col>
<Col width='4' /> <Col width='10'>
<Col width='6'>
<div className='money'> <div className='money'>
$: with cash $
<input id='cash-input' type='number' <input id='cash-input' type='number'
step='1' step='1'
min='0' min='0'
max={Math.ceil(this.props.max)} max={Math.floor(this.props.max)}
disabled={this.props.max < this.props.min} disabled={this.props.max < this.props.min}
onChange={this.handleInput} onChange={this.handleInput}
value={Math.ceil(this.state.cash)} /> value={Math.floor(this.state.cash)} />
{'\u00A0'},000 {'\u00A0'}K
</div> </div>
</Col> </Col>
</Row> </Row>
</form> </form>
</div>); break; </div>); break;
case 'no-card': action = case 'no-card': action =
@ -1581,7 +1787,7 @@ class Card extends React.Component {
} }
return ( return (
<div className='game-card'> <div className='game-card'>
<GroupBox title={this.props.ui.card.title}> <GroupBox title={this.props.card.title}>
<div className='card-id'>{card.id}</div> <div className='card-id'>{card.id}</div>
<div className='card' <div className='card'
dangerouslySetInnerHTML={{__html: card.contents}} /> dangerouslySetInnerHTML={{__html: card.contents}} />
@ -1604,8 +1810,8 @@ class CardListComp extends React.Component {
const ui = this.props.ui, const ui = this.props.ui,
cards = ui.cards, cards = ui.cards,
cardOps = cards.map((c, i) => cardOps = cards.map((c, i) =>
(<li key={i} className={c == ui.card ? 'card-select-selected' : ''} (<li key={i} className={c.id == this.props.cardId ? 'card-select-selected' : ''}
onClick={() => this.props.setSelectedCard(c)}> onClick={() => this.props.setCard(c)}>
{c.summary} {c.summary}
</li>)); </li>));

@ -20,6 +20,8 @@ import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PlayerIcon from './PlayerIcon.jsx' import PlayerIcon from './PlayerIcon.jsx'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCreditCard, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'
import { setMessagePanelSpace, mpMouse } from './actions.js' import { setMessagePanelSpace, mpMouse } from './actions.js'
@ -57,6 +59,8 @@ class SpaceNode extends React.Component {
<div className='space-description'> <div className='space-description'>
{this.props.space.description} {this.props.space.description}
</div> </div>
{this.props.showIcons && space.card === 'otb' ? (<div className='space-icon'><FontAwesomeIcon icon={faCreditCard} /></div>) : (<></>)}
{this.props.showIcons && space.card === 'fate' ? (<div className='space-icon'><FontAwesomeIcon icon={faQuestionCircle} /></div>) : (<></>)}
</div> </div>
</div> </div>
); );

@ -36,3 +36,4 @@ export const ALERT = 'alert'
export const ALERT_HANDLED = 'alert-handled' export const ALERT_HANDLED = 'alert-handled'
export const AUTO_SKIP = 'auto-skip' export const AUTO_SKIP = 'auto-skip'
export const MESSAGE = 'message' export const MESSAGE = 'message'
export const SET_HARVEST_TABLE = 'set-harvest-table'

@ -20,13 +20,13 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS,
SPACE_PUSH_PLAYER, SPACE_CLEAR_PLAYERS, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, SPACE_PUSH_PLAYER, SPACE_CLEAR_PLAYERS, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE,
MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION, MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION,
MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE } from './actionTypes.js' AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE } from './actionTypes.js'
export { updateGame, updatePlayer, gameState, setSelectedCard, setCards, export { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace, spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace,
mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction, mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction,
markActionChangeHandled, nextUIActionSilent, alert, alertHandled, markActionChangeHandled, nextUIActionSilent, alert, alertHandled,
autoSkip, message } autoSkip, message, setHarvestTable }
function updateGame(update) { function updateGame(update) {
return { type: UPDATE_GAME, return { type: UPDATE_GAME,
@ -107,12 +107,12 @@ function markActionChangeHandled() {
return { type: MARK_ACTION_CHANGE_HANDLED }; return { type: MARK_ACTION_CHANGE_HANDLED };
} }
function alert(value, contents) { function alert(value, contents, id) {
return { type: ALERT, value, contents }; return { type: ALERT, value, contents, id };
} }
function alertHandled() { function alertHandled(id) {
return { type: ALERT_HANDLED }; return { type: ALERT_HANDLED, id };
} }
function autoSkip(component) { function autoSkip(component) {
@ -122,3 +122,7 @@ function autoSkip(component) {
function message(message) { function message(message) {
return { type: MESSAGE, message }; return { type: MESSAGE, message };
} }
function setHarvestTable(table) {
return { type: SET_HARVEST_TABLE, table };
}

@ -24,7 +24,7 @@ import * as websocket from '../../websocket.js'
import { updateGame, updatePlayer, gameState, setSelectedCard, setCards, import { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
movePlayer, setOldMessages, markActionChangeHandled, movePlayer, setOldMessages, markActionChangeHandled,
mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert, mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert,
autoSkip, message, alertHandled } from './actions.js' autoSkip, message, alertHandled, setHarvestTable } from './actions.js'
import { itemCard, fateCard } from 'game.js' import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
@ -47,12 +47,16 @@ function handleMessage(evt) {
if (data.player.state === GAME_STATES.preTurn && if (data.player.state === GAME_STATES.preTurn &&
data.game.otherPlayers.length > 0 && data.game.otherPlayers.length > 0 &&
store.getState().farm.player.state !== GAME_STATES.preTurn) { store.getState().farm.player.state !== GAME_STATES.preTurn) {
store.dispatch(alert(ALERTS.beginTurn)); store.dispatch(alert(ALERTS.beginTurn, '', 'beginTurn' + data.game.turn));
}
if (data.game.calledAudit && !store.getState().farm.game.calledAudit) {
store.dispatch(alert(ALERTS.auditCalled, data.game.calledAudit,
'auditCalled' + data.game.turn));
}
if (data.player.trade.player && data.player.trade.originator !== data.player.name) {
store.dispatch(alert(ALERTS.proposedTrade, data.player.trade.originator,
'tradeProposed' + data.player.trade.number));
} }
// else if (data.game.otherPlayers.length > 0 &&
// data.game.currentPlayer !== store.getState().farm.game.currentPlayer) {
// store.dispatch(alert(ALERTS.otherPlayersTurn));
// }
// new turn clear actions // new turn clear actions
if (data.event === 'update' && if (data.event === 'update' &&
data.game.currentPlayer !== store.getState().farm.game.currentPlayer) { data.game.currentPlayer !== store.getState().farm.game.currentPlayer) {
@ -62,6 +66,7 @@ function handleMessage(evt) {
store.dispatch(updatePlayer(data.player)); store.dispatch(updatePlayer(data.player));
if (data.event === 'init') { if (data.event === 'init') {
store.dispatch(movePlayer(data.player.space, 0, data.player.color)); store.dispatch(movePlayer(data.player.space, 0, data.player.color));
store.dispatch(setHarvestTable(data.harvestTable));
} }
// new player(s) added to game, put them on the board // new player(s) added to game, put them on the board
if (data.game.otherPlayers.length !== store.getState().farm.game.otherPlayers.length) { if (data.game.otherPlayers.length !== store.getState().farm.game.otherPlayers.length) {
@ -75,11 +80,6 @@ function handleMessage(evt) {
const oldMessages = store.getState().farm.game.messages.slice(0, 20); const oldMessages = store.getState().farm.game.messages.slice(0, 20);
store.dispatch(updateGame(data.game)); store.dispatch(updateGame(data.game));
store.dispatch(setOldMessages(oldMessages)); store.dispatch(setOldMessages(oldMessages));
if (data.player.cards.length > 0) {
store.dispatch(setSelectedCard(data.player.cards[0]));
} else {
store.dispatch(setSelectedCard());
}
store.dispatch(setCards(data.player.cards)); store.dispatch(setCards(data.player.cards));
if (data.event === 'action') { if (data.event === 'action') {
if (data.player.name !== data.game.currentPlayer && if (data.player.name !== data.game.currentPlayer &&
@ -94,15 +94,25 @@ function handleMessage(evt) {
if (data.player.state === GAME_STATES.midTurn && if (data.player.state === GAME_STATES.midTurn &&
data.player.cash < 0 && data.player.cash < 0 &&
!store.getState().farm.ui.nextAction) { !store.getState().farm.ui.nextAction) {
store.dispatch(alert(ALERTS.raiseMoney)); store.dispatch(alert(ALERTS.raiseMoney, '', 'raiseMoney' + data.game.turn));
} else if (data.player.state === GAME_STATES.midTurn) { }// else if (data.player.state === GAME_STATES.midTurn &&
store.dispatch(alertHandled()); // !data.game.state === 'finished') {
} // store.dispatch(alertHandled());
// }
if (data.event === 'auto-skip') { if (data.event === 'auto-skip') {
store.dispatch(autoSkip(data.component)); store.dispatch(autoSkip(data.component));
} }
if (data.event === 'end-of-game') { if (data.event === 'end-of-game') {
store.dispatch(alert(ALERTS.endOfGame, data.results)); store.dispatch(alert(ALERTS.endOfGame, data.results, 'endOfGame' + data.game.turn));
}
if (data.event === 'action' && data.action === false) {
const playerSpaces = store.getState().farm.ui.playerSpaces;
data.game.otherPlayers.map(x => x.player).concat([data.player]).forEach(player => {
if (player.space !== playerSpaces[player.color]) {
store.dispatch(movePlayer(player.space, playerSpaces[player.color],
player.color));
}
});
} }
}); });
}; };

@ -21,62 +21,63 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS,
SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, MP_MOUSE, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, MP_MOUSE,
SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION, SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION,
MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED,
AUTO_SKIP, MESSAGE } from './actionTypes.js' AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE } from './actionTypes.js'
import { GAME_STATES } from '../../constants.js' import { GAME_STATES } from '../../constants.js'
import { spaceContent, corners } from 'game.js' import { spaceContent, corners } from 'game.js'
const spaces = const spaces =
[[corners[0], 'buy'], [[corners[0], 'buy', false],
['January', 'buy'], ['January', 'buy', false],
['January', 'buy'], ['January', 'buy', 'otb'],
['January', 'buy'], ['January', 'buy', false],
['January', 'buy'], ['January', 'buy', false],
['February','buy'], ['February','buy', false],
['February', 'buy'], ['February', 'buy', 'fate'],
['February', 'buy'], ['February', 'buy', false],
['February', 'buy'], ['February', 'buy', 'otb'],
['March', 'buy'], ['March', 'buy', false],
['March', 'buy'], ['March', 'buy', false],
['March', 'buy'], ['March', 'buy', false],
['March', 'buy'], ['March', 'buy', false],
['April', 'buy'], ['April', 'buy', 'otb'],
[corners[1], 'buy'], [corners[1], 'buy', false],
['April', 'none'], ['April', 'none', false],
['April', 'none'], ['April', 'none', false],
['May', 'none'], ['May', 'none', false],
['May', 'none'], ['May', 'none', false],
['May', 'hay'], ['May', 'hay', false],
['May', 'hay'], ['May', 'hay', 'otb'],
['June', 'hay'], ['June', 'hay', false],
['June', 'hay'], ['June', 'hay', false],
['June', 'cherry'], ['June', 'cherry', false],
['June', 'cherry'], ['June', 'cherry', 'fate'],
[corners[2], 'cherry'], [corners[2], 'cherry', false],
['July', 'hay'], ['July', 'hay', false],
['July', 'hay'], ['July', 'hay', 'otb'],
['July', 'hay'], ['July', 'hay', false],
['July', 'wheat'], ['July', 'wheat', false],
['August', 'wheat'], ['August', 'wheat', false],
['August', 'wheat'], ['August', 'wheat', false],
['August', 'wheat'], ['August', 'wheat', false],
['August', 'wheat'], ['August', 'wheat', false],
['September', 'hay'], ['September', 'hay', false],
['September', 'hay'], ['September', 'hay', 'otb'],
['September', 'cows'], ['September', 'cows', false],
[corners[3], 'cows'], [corners[3], 'cows', false],
['September', 'cows'], ['September', 'cows', false],
['October', 'cows'], ['October', 'cows', false],
['October', 'hay'], ['October', 'hay', 'fate'],
['October', 'hay'], ['October', 'hay', 'otb'],
['October', 'apple'], ['October', 'apple', 'fate'],
['November', 'apple'], ['November', 'apple', 'otb'],
['November', 'apple'], ['November', 'apple', false],
['November', 'apple'], ['November', 'apple', false],
['November', 'corn'], ['November', 'corn', false],
['December', 'corn'], ['December', 'corn', false],
['December', 'corn']] ['December', 'corn', 'fate']]
.map((s, i) => { .map((s, i) => {
return { month: s[0], description: spaceContent[i], return { month: s[0], description: spaceContent[i],
card: s[2],
type: s[1], key: i, players: [] }}); type: s[1], key: i, players: [] }});
const initialState = { const initialState = {
@ -112,10 +113,11 @@ const initialState = {
nextActionValue: null, nextActionValue: null,
actionChangeHandled: true, actionChangeHandled: true,
message: '', message: '',
alert: false, alerts: {},
alertContents: false, unhandledAlert: false,
autoSkip: false, autoSkip: false,
alertHandled: false }, playerSpaces: {},
harvestTable: false },
spaces: spaces, spaces: spaces,
space: null, space: null,
// message panel dimenions // message panel dimenions
@ -150,7 +152,10 @@ export default function(state = initialState, action) {
.filter(x => x !== action.player) }; .filter(x => x !== action.player) };
} }
return item; return item;
}) }),
ui: { ...state.ui,
playerSpaces: { ...state.ui.playerSpaces,
[action.player]: action.newSpace }}
}; };
case SET_OLD_MESSAGES: case SET_OLD_MESSAGES:
return { ...state, oldMessages: action.messages }; return { ...state, oldMessages: action.messages };
@ -179,16 +184,34 @@ export default function(state = initialState, action) {
case MARK_ACTION_CHANGE_HANDLED: case MARK_ACTION_CHANGE_HANDLED:
return { ...state, ui: { ...state.ui, actionChangeHandled: true }}; return { ...state, ui: { ...state.ui, actionChangeHandled: true }};
case ALERT: case ALERT:
let alerts = { ...state.ui.alerts,
[action.id]: { type: action.value,
id: action.id,
contents: action.contents,
handled: false }
};
return { ...state, ui: { ...state.ui, return { ...state, ui: { ...state.ui,
alert: action.value, alerts,
alertContents: action.contents, unhandledAlert: Object.values(alerts).find(x => !x.handled)
alertHandled: action.value === false ? true : false }}; }
};
case ALERT_HANDLED: case ALERT_HANDLED:
return { ...state, ui: { ...state.ui, alertHandled: true }}; alerts = { ...state.ui.alerts,
[action.id]: { ...state.ui.alerts[action.id],
handled: true
}
};
return { ...state, ui: { ...state.ui,
alerts,
unhandledAlert: Object.values(alerts).find(x => !x.handled)
}
};
case AUTO_SKIP: case AUTO_SKIP:
return { ...state, ui: { ...state.ui, autoSkip: action.component }}; return { ...state, ui: { ...state.ui, autoSkip: action.component }};
case MESSAGE: case MESSAGE:
return { ...state, ui: { ...state.ui, message: action.message }}; return { ...state, ui: { ...state.ui, message: action.message }};
case SET_HARVEST_TABLE:
return { ...state, ui: { ...state.ui, harvestTable: action.table }};
default: default:
return state; return state;
} }

@ -57,19 +57,23 @@ class NewGame extends React.Component {
checkedColor: props.colors[0], checkedColor: props.colors[0],
gameId: typeof props.gameId === 'undefined' ? -1 : props.gameId, gameId: typeof props.gameId === 'undefined' ? -1 : props.gameId,
gameName: props.gameName || '', gameName: props.gameName || '',
downPayment: 0.2, downPayment: 0,
loanInterest: 0.2, loanInterest: 0,
maxDebt: 50000, maxDebt: 50000,
auditThreshold: 250000, auditThreshold: 250000,
startingCash: 5000, startingCash: 5000,
startingDebt: 5000 startingDebt: 5000,
trade: true
}; };
} }
handleInputChange = e => { handleInputChange = e => {
const target = e.target, const target = e.target,
value = target.type === 'checkbox' ? target.name : target.value, value = target.type === 'checkbox' && target.name !== 'trade'
name = target.type === 'checkbox' ? 'checkedColor' : target.name; ? target.name : target.value,
name = target.type === 'checkbox' && target.name !== 'trade'
? 'checkedColor' : target.name;
this.setState({ this.setState({
[name]: value [name]: value
@ -183,6 +187,17 @@ class NewGame extends React.Component {
step={1000} step={1000}
value={this.state.startingDebt} value={this.state.startingDebt}
onChange={this.handleInputChange} /> onChange={this.handleInputChange} />
<Row>
<Col width='12'>
<label>
<input type='checkbox'
checked={this.state.trade}
onChange={this.handleInputChange}
name='trade' />
Enable Trading
</label>
</Col>
</Row>
</div> </div>
<Row> <Row>
<Col width='12'> <Col width='12'>

@ -32,4 +32,6 @@ export const messagePanelId = 'message-panel';
export const ALERTS = { beginTurn: 'begin-turn', export const ALERTS = { beginTurn: 'begin-turn',
otherPlayersTurn: 'other-players-turn', otherPlayersTurn: 'other-players-turn',
endOfGame: 'end-of-game', endOfGame: 'end-of-game',
raiseMoney: 'raise-money' } auditCalled: 'audit-called',
raiseMoney: 'raise-money',
proposedTrade: 'proposed-trade' }

@ -60,6 +60,7 @@
(space initform: 0 accessor: player-space) (space initform: 0 accessor: player-space)
(previous-space initform: 0 accessor: player-previous-space) (previous-space initform: 0 accessor: player-previous-space)
(state initform: 'turn-ended accessor: player-state) (state initform: 'turn-ended accessor: player-state)
(finished initform: #f accessor: player-finished)
(assets initform: (assets initform:
'((hay . 10) (grain . 10) (fruit . 0) (cows . 0) '((hay . 10) (grain . 10) (fruit . 0) (cows . 0)
(harvester . 0) (tractor . 0)) (harvester . 0) (tractor . 0))
@ -94,6 +95,7 @@
(state initform: 'playing accessor: game-state) (state initform: 'playing accessor: game-state)
(name initform: "game" accessor: game-name) (name initform: "game" accessor: game-name)
(turn initform: 1 accessor: game-turn) (turn initform: 1 accessor: game-turn)
(current-player initform: #f accessor: game-current-player)
(actions initform: '() accessor: game-actions) (actions initform: '() accessor: game-actions)
(settings initform: (settings initform:
'((down-payment . 0.2) '((down-payment . 0.2)
@ -101,7 +103,8 @@
(max-debt . 50000) (max-debt . 50000)
(audit-threshold . 250000) (audit-threshold . 250000)
(starting-cash . 5000) (starting-cash . 5000)
(starting-debt . 5000)) (starting-debt . 5000)
(trade . #t))
accessor: game-settings))) accessor: game-settings)))
(define (game-setting setting game) (define (game-setting setting game)
@ -223,38 +226,35 @@
'state (if (= (length (game-players game)) 0) 'state (if (= (length (game-players game)) 0)
'pre-turn 'turn-ended)))) 'pre-turn 'turn-ended))))
(set! (game-players game) (append (game-players game) (list player))) (set! (game-players game) (append (game-players game) (list player)))
(when (= (length (game-players game)) 1)
(set! (game-current-player game) player))
player)) player))
(define (all-players-finished game) (define (all-players-finished game)
(null? (filter (lambda (p) (null? (filter (lambda (p)
(not (eq? (player-state p) 'finished-game))) (not (player-finished p)))
(game-players game)))) (game-players game))))
(define (next-player game player) (define (next-player game)
(let ((tail (cdr (filter (lambda (p) (let ((tail (filter (lambda (p)
(not (eq? (player-state p) 'finished-game))) (not (player-finished p)))
(find-tail (cut eq? <> player) (game-players game)))))) (find-tail (cut eq? <> (game-current-player game))
(if (null? tail) (game-players game)))))
(car (game-players game)) (if (or (null? tail) (null? (cdr tail)))
(car tail)))) (car (filter (lambda (p)
(not (player-finished p)))
(game-players game)))
(car (cdr tail)))))
(define (advance-turn game player) (define (advance-turn game player)
(if (all-players-finished game) (if (all-players-finished game)
(set! (game-state game) 'finished) (set! (game-state game) 'finished)
(begin (set! (player-state player) 'turn-ended) (let ((next (next-player game)))
(set! (player-state (next-player game player)) 'pre-turn) (set! (player-state player) 'turn-ended)
(set! (player-state next) 'pre-turn)
(set! (game-current-player game) next)
(set! (game-turn game) (+ (game-turn game) 1))))) (set! (game-turn game) (+ (game-turn game) 1)))))
(define (current-players-turn game)
(let loop ((players (game-players game)))
(cond ((null? players) ;; game finished use player 0 as a dummy player
(car (game-players game)))
((or (eq? (player-state (car players)) 'turn-ended)
(eq? (player-state (car players)) 'finished-game))
(loop (cdr players)))
(else
(car players)))))
(define (ridge-available? game ridge) (define (ridge-available? game ridge)
(let loop ((players (game-players game))) (let loop ((players (game-players game)))
(if (null? players) (if (null? players)
@ -326,7 +326,7 @@
(define (game->list g player) (define (game->list g player)
`((game . ((messages . ,(list->vector (reverse (game-messages g)))) `((game . ((messages . ,(list->vector (reverse (game-messages g))))
(currentPlayer . ,(player-name (current-players-turn g))) (currentPlayer . ,(player-name (game-current-player g)))
(otherPlayers (otherPlayers
. ,(list->vector . ,(list->vector
(map (map
@ -340,7 +340,8 @@
(settings . ((downPayment . ,(game-setting 'down-payment g)) (settings . ((downPayment . ,(game-setting 'down-payment g))
(loanInterest . ,(game-setting 'loan-interest g)) (loanInterest . ,(game-setting 'loan-interest g))
(maxDebt . ,(game-setting 'max-debt g)) (maxDebt . ,(game-setting 'max-debt g))
(auditThreshold . ,(game-setting 'audit-threshold g)))))))) (auditThreshold . ,(game-setting 'audit-threshold g))
(trade . ,(game-setting 'trade g))))))))
(define (push-message player msg #!key (game (session-ref (sid) 'game))) (define (push-message player msg #!key (game (session-ref (sid) 'game)))
(if player (if player
@ -405,6 +406,9 @@
(push-message player (conc "You bought " amount " " crop ".")) (push-message player (conc "You bought " amount " " crop "."))
#t))))) #t)))))
(define (make-player-year-rule id rule)
`((id . ,id) (rule . ,rule)))
(define (finish-year player #!optional (collect-wages #t)) (define (finish-year player #!optional (collect-wages #t))
(let ((game (session-ref (sid) 'game))) (let ((game (session-ref (sid) 'game)))
(when collect-wages (when collect-wages
@ -417,10 +421,11 @@
(?value . "You earned $5,000 from your city job!")) (?value . "You earned $5,000 from your city job!"))
(game-actions game)))) (game-actions game))))
(when (game-called-audit game) (when (game-called-audit game)
(set! (player-state player) 'finished-game) (set! (game-actions game)
(advance-turn game player) (append (game-actions game)
;; advance turn resets state back to turn ended `(((?action . end-game)
(set! (player-state player) 'finished-game)) (?value . ,(lambda ()
(set! (player-finished player) #t))))))))
(set! (player-year-rules player) (player-next-year-rules player)) (set! (player-year-rules player) (player-next-year-rules player))
(set! (player-next-year-rules player) '()) (set! (player-next-year-rules player) '())
(when (not (null? (player-farmers-fates player))) (when (not (null? (player-farmers-fates player)))
@ -436,12 +441,16 @@
(set! (game-farmers-fates game) (set! (game-farmers-fates game)
(filter (lambda (c) (not (eq? (alist-ref 'internal-id c) 'cows-15))) (filter (lambda (c) (not (eq? (alist-ref 'internal-id c) 'cows-15)))
(game-farmers-fates game))) (game-farmers-fates game)))
(push! `((?p cows player-action-post-harvest (push! (make-player-year-rule
0
`((?p cows player-action-post-harvest
,(make-remove-farmers-fate-from-hand 'cows-15)) ,(make-remove-farmers-fate-from-hand 'cows-15))
(?p cows)) (?p cows)))
(player-year-rules player)) (player-year-rules player))
(push! `((?d player-action ?p (push! (make-player-year-rule
,(make-remove-farmers-fate-after 'cows-15 40))) 1
`((?d player-action ?p
,(make-remove-farmers-fate-after 'cows-15 40))))
(player-year-rules player)))))) (player-year-rules player))))))
(define (find-player-by-name game name) (define (find-player-by-name game name)
@ -489,50 +498,47 @@
'() '()
cards))) cards)))
(cond (basics (cond (basics
(push-message player (conc "You don't have enough " basics " to trade!")) (conc "You don't have enough " basics " to trade!"))
#f)
(other-basics (other-basics
(push-message player (conc (player-name other-player) (conc (player-name other-player)
" doesn't have enough " other-basics " to trade!")) " doesn't have enough " other-basics " to trade!"))
#f)
(ridges (ridges
(push-message player (conc ridges " ridge not available to trade!")) (conc ridges " ridge not available to trade!"))
#f)
((< (+ (player-cash player) (alist-ref 'money params eqv? 0)) 0) ((< (+ (player-cash player) (alist-ref 'money params eqv? 0)) 0)
(push-message player "You don't have enough cash to trade!") "You don't have enough cash to trade!")
#f)
((< (+ (player-cash other-player) (* (alist-ref 'money params eqv? 0) -1)) 0) ((< (+ (player-cash other-player) (* (alist-ref 'money params eqv? 0) -1)) 0)
(push-message player (conc (player-name other-player) (conc (player-name other-player)
" doesn't have enough cash to trade!")) " doesn't have enough cash to trade!"))
#f)
((not (null? missing-cards)) ((not (null? missing-cards))
(push-message player (conc "Nobody has cards: " (conc "Nobody has cards: "
(string-intersperse (string-intersperse
(map number->string missing-cards) (map number->string missing-cards)
", ") ".")) ", ") "."))
#f)
(else (else
other-player)))) other-player))))
(define *trade-number* 0)
(define (propose-trade game player params) (define (propose-trade game player params)
(let ((other-player (validate-trade game player params))) (let ((other-player (validate-trade game player params)))
(if other-player (if (not (string? other-player))
(let ((to-trade (filter (lambda (x) (and (not (equal? (cdr x) 0)) (let ((to-trade (filter (lambda (x) (and (not (equal? (cdr x) 0))
(not (equal? (cdr x) "")) (not (equal? (cdr x) ""))
(cdr x))) (cdr x)))
params))) params)))
(push-message player (set! *trade-number* (+ *trade-number* 1))
(conc "Trade proposed to " (player-name other-player) "!"))
(set! (player-trade other-player) (set! (player-trade other-player)
(append `((player . ,(player-name player)) (append `((player . ,(player-name player))
(originator . ,(player-name player))) (originator . ,(player-name player))
(trade-number . ,*trade-number*))
to-trade)) to-trade))
(set! (player-trade player) (set! (player-trade player)
(append `((player . ,(player-name other-player)) (append `((player . ,(player-name other-player))
(originator . ,(player-name player))) (originator . ,(player-name player))
(trade-number . ,*trade-number*))
to-trade)) to-trade))
#t) #t)
#f))) other-player)))
(define (otb-by-id player id) (define (otb-by-id player id)
(find (lambda (card) (find (lambda (card)
@ -689,6 +695,7 @@
(when game (when game
(set! (game-messages game) '()) (set! (game-messages game) '())
(set! (player-last-cash player) (player-cash player))) (set! (player-last-cash player) (player-cash player)))
(print "message type: " type)
(cond ((string=? type "roll") (cond ((string=? type "roll")
(let ((num (+ (random 6) 1))) (let ((num (+ (random 6) 1)))
(when *next-roll* (set! num *next-roll*)) (when *next-roll* (set! num *next-roll*))
@ -704,8 +711,6 @@
(when (and (> (player-previous-space player) 40) (when (and (> (player-previous-space player) 40)
(< (player-space player) 10)) (< (player-space player) 10))
(finish-year player)) (finish-year player))
(when (eq? (game-state game) 'finished)
(do-end-of-game game)) ;; TODO check
(set! (player-harvest-mult player) 1) (set! (player-harvest-mult player) 1)
(let ((resp `((from . ,(player-previous-space player)) (let ((resp `((from . ,(player-previous-space player))
(to . ,(player-space player))))) (to . ,(player-space player)))))
@ -719,9 +724,11 @@
(create-ws-response player "action" `((action . "roll") (value . ,resp)))))) (create-ws-response player "action" `((action . "roll") (value . ,resp))))))
((and (string=? type "next-action") ((and (string=? type "next-action")
(not (eq? (player-state player) 'turn-ended))) (not (eq? (player-state player) 'turn-ended)))
(let loop () (let loop ((i 0))
(if (null? (game-actions game)) (if (or (null? (game-actions game))
(>= i 15))
(begin (begin
(set! (game-actions game) '())
(message-players! game player `((action . #f) (value . #f))) (message-players! game player `((action . #f) (value . #f)))
(create-ws-response player "action" '((action . #f)))) (create-ws-response player "action" '((action . #f))))
(let* ((action (car (game-actions game))) (let* ((action (car (game-actions game)))
@ -756,7 +763,7 @@
(let ((res (do-action action player))) (let ((res (do-action action player)))
(set! (game-actions game) (cdr (game-actions game))) (set! (game-actions game) (cdr (game-actions game)))
(if (eq? res 'nothing) (if (eq? res 'nothing)
(loop) (loop (+ i 1))
(begin (begin
(message-players! (message-players!
game player game player
@ -772,21 +779,27 @@
(set! (game-actions game) (set! (game-actions game)
(cdr (game-actions game))) (cdr (game-actions game)))
(if (= (- (player-cash player) previous-cash) 0) (if (= (- (player-cash player) previous-cash) 0)
(loop) (loop (+ i 1))
(let ((res `((action . "money")
(value . ((amount . ,(- (player-cash player)
previous-cash))
(player . ,(player-name player)))))))
(message-players! game player res)
(create-ws-response player "action" res)))))
((eq? name 'end-game)
(if (null? (cdr (game-actions game)))
(begin (begin
(message-players! game player (value)
`((action . "money") (set! (game-actions game) '()))
(value . ,(- (player-cash player) (set! (game-actions game)
previous-cash)))) (append (cdr (game-actions game))
(create-ws-response player "action" (list (car (game-actions game))))))
`((action . "money") (loop (+ i 1)))
(value . ,(- (player-cash player)
previous-cash))))))))
((or (eq? name 'harvest-mult) ((or (eq? name 'harvest-mult)
(eq? name 'player-action-post-harvest)) (eq? name 'player-action-post-harvest))
(set! (game-actions game) (cdr (game-actions game))) (set! (game-actions game) (cdr (game-actions game)))
(do-action action player) (do-action action player)
(loop)) (loop (+ i 1)))
((eq? value 'farmers-fate) ((eq? value 'farmers-fate)
(let ((ff (do-action action player))) (let ((ff (do-action action player)))
(set! (game-actions game) (set! (game-actions game)
@ -800,14 +813,13 @@
(value . ,(alist-ref 'contents ff)))))) (value . ,(alist-ref 'contents ff))))))
((eq? name 'ff-money) ((eq? name 'ff-money)
(set! (game-actions game) (cdr (game-actions game))) (set! (game-actions game) (cdr (game-actions game)))
(if (= value 0) (if (= (alist-ref 'amount value) 0)
(loop) (loop (+ i 1))
(begin (let ((res `((action . "money")
(message-players! game player (value . ((amount . ,(alist-ref 'amount value))
`((action . "money") (value . ,value))) (player . ,(alist-ref 'name value)))))))
(create-ws-response player "action" (message-players! game player res)
`((action . "money") (create-ws-response player "action" res))))
(value . ,value))))))
((eq? name 'ff-uncle-bert) ((eq? name 'ff-uncle-bert)
(set! (game-actions game) (cdr (game-actions game))) (set! (game-actions game) (cdr (game-actions game)))
(message-players! game player (message-players! game player
@ -838,7 +850,7 @@
((eq? name 'add-rule) ((eq? name 'add-rule)
(do-action action player) (do-action action player)
(set! (game-actions game) (cdr (game-actions game))) (set! (game-actions game) (cdr (game-actions game)))
(loop)) (loop (+ i 1)))
(else ;; TODO make error (else ;; TODO make error
(create-ws-response player "action" `((action . ,name))))))))) (create-ws-response player "action" `((action . ,name)))))))))
((string=? type "skip") ((string=? type "skip")
@ -904,9 +916,13 @@
)) ))
(create-ws-response player "loan" '())) (create-ws-response player "loan" '()))
((string=? type "trade") ((string=? type "trade")
(propose-trade game player (alist-ref 'parameters msg)) (let ((res (propose-trade game player (alist-ref 'parameters msg))))
(if (eq? res #t)
(begin
(message-players! game player '() type: "update") (message-players! game player '() type: "update")
(create-ws-response player "trade" '())) (create-ws-response player "trade" '()))
(begin (set! (player-trade player) `((error . ,res)))
(create-ws-response player "trade-error" `())))))
((string=? type "trade-accept") ((string=? type "trade-accept")
(accept-trade game player) (accept-trade game player)
(message-players! game player '() type: "update") (message-players! game player '() type: "update")
@ -934,11 +950,15 @@
(message-players! game player '() type: "update") (message-players! game player '() type: "update")
(create-ws-response player "called-audit" '())) (create-ws-response player "called-audit" '()))
((string=? type "init") ((string=? type "init")
(create-ws-response player "init" '())) (create-ws-response player "init" `((harvestTable . ,(map (lambda (row)
`(,(car row) . ,(list->vector (cdr row))))
*harvest-table*)))))
((string=? type "turn-ended") ((string=? type "turn-ended")
(if (>= (player-cash player) 0) (if (>= (player-cash player) 0)
(begin (advance-turn game player) (begin (advance-turn game player)
(message-players! game player '() type: "update") (if (eq? (game-state game) 'finished)
(do-end-of-game game)
(message-players! game player '() type: "update"))
(create-ws-response player "update" '())) (create-ws-response player "update" '()))
(begin (push-message player "Cannot end a turn with negative cash!") (begin (push-message player "Cannot end a turn with negative cash!")
(create-ws-response player "update" '())))) (create-ws-response player "update" '()))))
@ -953,17 +973,18 @@
'id (next-game-id *app*) 'id (next-game-id *app*)
'otbs (setup-otbs) 'otbs (setup-otbs)
'operating-expenses (setup-operating-expenses) 'operating-expenses (setup-operating-expenses)
'farmers-fates (setup-farmers-fates) 'farmers-fates (setup-farmers-fates #t)
'settings 'settings
`((down-payment . ,(->pct (alist-ref 'downPayment msg) 0.2)) `((down-payment . ,(->pct (alist-ref 'downPayment msg) 0))
(loan-interest . ,(->pct (alist-ref 'loanInterest msg) 0.2)) (loan-interest . ,(->pct (alist-ref 'loanInterest msg) 0))
(max-debt . ,(->i (alist-ref 'maxDebt msg) 50000)) (max-debt . ,(->i (alist-ref 'maxDebt msg) 50000))
(audit-threshold . ,(->i (alist-ref 'auditThreshold msg) (audit-threshold . ,(->i (alist-ref 'auditThreshold msg)
250000)) 250000))
(starting-cash . ,(->i (alist-ref 'startingCash msg) (starting-cash . ,(->i (alist-ref 'startingCash msg)
5000)) 0))
(starting-debt . ,(->i (alist-ref 'startingDebt msg) (starting-debt . ,(->i (alist-ref 'startingDebt msg)
5000))))) 0))
(trade . ,(or (alist-ref 'trade msg) #t)))))
(player (add-player-to-game game (player (add-player-to-game game
color color
(alist-ref 'playerName msg)))) (alist-ref 'playerName msg))))
@ -973,11 +994,14 @@
(set-startup-otbs game player 2) (set-startup-otbs game player 2)
(create-start-response "new-game-started"))) (create-start-response "new-game-started")))
((string=? type "join-game") ((string=? type "join-game")
(let* ((color (string->symbol (alist-ref 'checkedColor msg))) (let* ((name (alist-ref 'gameName msg))
(name (alist-ref 'gameName msg))
(id (alist-ref 'gameId msg)) (id (alist-ref 'gameId msg))
(game (find (lambda (g) (= (game-id g) id)) (game (find (lambda (g) (= (game-id g) id))
(app-games *app*))) (app-games *app*)))
(color-raw (string->symbol (alist-ref 'checkedColor msg)))
(color (if (not (member color-raw (game-colors game)))
(car (game-colors game))
color-raw))
(player (add-player-to-game game (player (add-player-to-game game
color color
(alist-ref 'playerName msg)))) (alist-ref 'playerName msg))))
@ -1156,7 +1180,7 @@
(define (players-with asset game) (define (players-with asset game)
(filter (lambda (player) (filter (lambda (player)
(player-has-asset? 'harvester player)) (player-has-asset? asset player))
(game-players game))) (game-players game)))
(define (player-asset-binary-count asset game) (define (player-asset-binary-count asset game)
@ -1256,88 +1280,105 @@
(iota (list-ref spec 0)))) (iota (list-ref spec 0))))
farmers-fates ff-texts))) farmers-fates ff-texts)))
(define (ff-money-response amount player-name)
`((?action . ff-money)
(?value . ((amount . ,amount)
(name . ,player-name)))))
(define-syntax with-ff-money-action (define-syntax with-ff-money-action
(syntax-rules (?action ff-money ?value) (syntax-rules ()
((_ (player) body ...) ((_ (player game) body ...)
(let ((previous-cash (player-cash player))) (let ((previous-cash (map (lambda (p)
(cons p (player-cash p))) (game-players game))))
body ... body ...
`(((?action . ff-money) `(,(ff-money-response (- (player-cash player)
(?value . ,(- (player-cash player) previous-cash)))))))) (cdr (find (lambda (x) (eq? (car x) player)) previous-cash)))
(player-name player))
,@(map (lambda (x)
(ff-money-response (- (player-cash (car x)) (cdr x))
(player-name (car x))))
(filter (lambda (x)
(and (not (eq? (car x) player))
(not (= (- (player-cash (car x))
(cdr x))
0))))
previous-cash))
)))))
(define *farmers-fates-specs* (define *farmers-fates-specs*
;; xxx multiplayer interaction `((1 ,(lambda (player game)
`((1 ,(lambda (player) (with-ff-money-action (player game)
(for-each (lambda (p) (for-each (lambda (p)
(let ((roll (+ (random 6) 1))) (let ((roll (+ (random 6) 1)))
(if (odd? roll) (if (odd? roll)
(push-message p (conc "You rolled a " roll " and escaped!")) ((make-player-pays (* (player-acres p) 100)) p))))
(begin (push-message p (conc "You rolled a " roll " and were hit!"))
((make-player-pays (* (player-acres p) 100)) p)))))
(filter (lambda (x) (not (eq? x player))) (filter (lambda (x) (not (eq? x player)))
(game-players (session-ref (sid) 'game)))) (game-players (session-ref (sid) 'game))))
(with-ff-money-action (player)
((make-player-gains-per-unit 'hay 500) player))) ((make-player-gains-per-unit 'hay 500) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
((make-player-gains-per-unit 'grain 100) player))) ((make-player-gains-per-unit 'grain 100) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(push! '((?p wheat harvest-mult 0.5) (?p grain)) (player-year-rules player)) (push! (make-player-year-rule 2 '((?p wheat harvest-mult 0.5) (?p grain)))
(push! `((?p wheat player-action-post-harvest (player-year-rules player))
(push! (make-player-year-rule
3
`((?p wheat player-action-post-harvest
,(make-remove-farmers-fate-from-hand 'windy-spring)) ,(make-remove-farmers-fate-from-hand 'windy-spring))
(?p grain)) (?p grain)))
(player-year-rules player)) (player-year-rules player))
(push! `((?d player-action ?p (push! (make-player-year-rule
,(make-remove-farmers-fate-after 'windy-spring 34))) 4
`((?d player-action ?p
,(make-remove-farmers-fate-after 'windy-spring 34))))
(player-year-rules player)) (player-year-rules player))
'()) '())
#t #t
windy-spring) windy-spring)
(1 ,(lambda (player) (1 ,(lambda (player game)
(if (player-has-asset? 'cows player) (if (player-has-asset? 'cows player)
(with-ff-money-action (player) ((make-player-gains 2000) player)) (with-ff-money-action (player game) ((make-player-gains 2000) player))
'())) '()))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
((make-player-gains-per-unit 'hay 100) player))) ((make-player-gains-per-unit 'hay 100) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) ((make-player-gains 1000) player))) (with-ff-money-action (player game) ((make-player-gains 1000) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) ((make-player-pays 7000) player))) (with-ff-money-action (player game) ((make-player-pays 7000) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 500) player))) ((make-player-pays-per-unit 'fruit 500) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
(let ((to-earn (* (player-acres player) 100))) (let ((to-earn (* (player-acres player) 100)))
(push-message player (conc "You earned $" to-earn "!")) (push-message player (conc "You earned $" to-earn "!"))
(set! (player-cash player) (set! (player-cash player)
(+ (player-cash player) to-earn))))) (+ (player-cash player) to-earn)))))
#f) #f)
(2 ,(lambda (player) (2 ,(lambda (player game)
`(((?action . player-action) `(((?action . player-action)
(?value . ,(lambda (player) (finish-year player #f)))) (?value . ,(lambda (player) (finish-year player #f))))
((?action . goto) (?value . jan2)))) ((?action . goto) (?value . jan2))))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 300) player))) ((make-player-pays-per-unit 'fruit 300) player)))
#f) #f)
;; xxx multiplayer interaction (2 ,(lambda (player game)
(2 ,(lambda (player) (with-ff-money-action (player game)
(with-ff-money-action (player)
(equipment-payout 'tractor player 3000 (session-ref (sid) 'game)))) (equipment-payout 'tractor player 3000 (session-ref (sid) 'game))))
#f) #f)
;; xxx multiplayer interaction (1 ,(lambda (player game)
(1 ,(lambda (player)
(if (player-has-asset? 'harvester player) (if (player-has-asset? 'harvester player)
(with-ff-money-action (player) (with-ff-money-action (player game)
(for-each (lambda (from-player) (for-each (lambda (from-player)
(when (not (eq? player from-player)) (when (not (eq? player from-player))
(when (not (player-has-asset? 'harvester from-player)) (when (not (player-has-asset? 'harvester from-player))
@ -1348,42 +1389,49 @@
(game-players (session-ref (sid) 'game)))) (game-players (session-ref (sid) 'game))))
'())) '()))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(push! '((?p ?any harvest-mult 0) (?p ?crop)) (player-year-rules player)) (push! (make-player-year-rule 5 '((?p ?any harvest-mult 0) (?p ?crop)))
(player-year-rules player))
'()) '())
#t) #t)
(1 ,(lambda (player) (1 ,(lambda (player game)
`(((?action . ff-uncle-bert) (?value . #f)))) `(((?action . ff-uncle-bert) (?value . #f))))
#f) #f)
;; xxx multiplayer interaction (1 ,(lambda (player game)
(1 ,(lambda (player) (with-ff-money-action (player game)
(with-ff-money-action (player)
(equipment-payout 'harvester player 2500 (equipment-payout 'harvester player 2500
(session-ref (sid) 'game)))) (session-ref (sid) 'game))))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(push! `((?p cows harvest-mult 1.5) (?p cows)) (player-year-rules player)) (push! (make-player-year-rule 6 `((?p cows harvest-mult 1.5) (?p cows)))
(push! `((?p cows harvest-mult 1.5) (?p cows)) (player-next-year-rules player)) (player-year-rules player))
(push! (make-player-year-rule 7 `((?p cows harvest-mult 1.5) (?p cows)))
(player-next-year-rules player))
'()) '())
#t #t
cows-15) cows-15)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) ((make-player-gains 2000) player))) (with-ff-money-action (player game) ((make-player-gains 2000) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(when (< (player-space player) 26) (when (< (player-space player) 26)
(push! '((?p cherries harvest-mult 0.5) (?p fruit)) (player-year-rules player)) (push! (make-player-year-rule 7 '((?p cherries harvest-mult 0.5) (?p fruit)))
(push! `((?p cherries player-action-post-harvest (player-year-rules player))
(push! (make-player-year-rule
8
`((?p cherries player-action-post-harvest
,(make-remove-farmers-fate-from-hand 'cherries-05)) ,(make-remove-farmers-fate-from-hand 'cherries-05))
(?p fruit)) (?p fruit)))
(player-year-rules player))) (player-year-rules player)))
(push! `((?d player-action ?p (push! (make-player-year-rule
,(make-remove-farmers-fate-after 'cherries-05 26))) 8
`((?d player-action ?p
,(make-remove-farmers-fate-after 'cherries-05 26))))
(player-year-rules player)) (player-year-rules player))
'()) '())
#t #t
cherries-05) cherries-05)
(1 ,(lambda (player) (1 ,(lambda (player game)
(let ((cows (player-asset 'cows player)) (let ((cows (player-asset 'cows player))
(ridge-cows (cows-on-ridges player))) (ridge-cows (cows-on-ridges player)))
(if (> cows ridge-cows) (if (> cows ridge-cows)
@ -1398,16 +1446,19 @@
" cows slaughtered on your farm."))))) " cows slaughtered on your farm.")))))
'()))) '())))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) (with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 1000) player))) ((make-player-pays-per-unit 'fruit 1000) player)))
#f) #f)
(1 ,(lambda (player) (1 ,(lambda (player game)
(with-ff-money-action (player) ((make-semi-annual-interest-due) player))) (with-ff-money-action (player game) ((make-semi-annual-interest-due) player)))
#f))) #f)))
(define (setup-farmers-fates) (define (setup-farmers-fates shuffle?)
(shuffle (farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*))) (let ((cards (farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*)))
(if shuffle?
(shuffle cards)
cards)))
(define *farmers-fates-cards* (define *farmers-fates-cards*
(farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*)) (farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*))
;; (define *farmers-fates* (setup-farmers-fates)) ;; (define *farmers-fates* (setup-farmers-fates))
@ -1466,9 +1517,8 @@
(length (operating-expenses-spec-list->operating-expenses-cards (length (operating-expenses-spec-list->operating-expenses-cards
*operating-expenses-specs* *oe-text*))) *operating-expenses-specs* *oe-text*)))
(define (draw-operating-expense) (define (draw-operating-expense game)
(let* ((game (session-ref (sid) 'game)) (let ((card (list-ref (game-operating-expenses game)
(card (list-ref (game-operating-expenses game)
(game-operating-expense-index game)))) (game-operating-expense-index game))))
(if (= (+ (game-operating-expense-index game) 1) *total-operating-expenses*) (if (= (+ (game-operating-expense-index game) 1) *total-operating-expenses*)
(set! (game-operating-expense-index game) 0) (set! (game-operating-expense-index game) 0)
@ -1497,7 +1547,7 @@
((jan1 player-action ?p ,(make-semi-annual-interest-due))) ((jan1 player-action ?p ,(make-semi-annual-interest-due)))
((jan2 draw ?p otb)) ((jan2 draw ?p otb))
((jan3 money ?p ,(pays 500)) (?p cows)) ((jan3 money ?p ,(pays 500)) (?p cows))
((jan4 add-rule ?p ((?p hay harvest-mult 2) (?p hay)))) ((jan4 add-rule ?p ,(make-player-year-rule 9 '((?p hay harvest-mult 2) (?p hay)))))
((feb1 money ?p ,(gains 1000))) ((feb1 money ?p ,(gains 1000)))
((feb2 draw ?p farmers-fate)) ((feb2 draw ?p farmers-fate))
((feb3 goto ?p apr2)) ((feb3 goto ?p apr2))
@ -1507,7 +1557,8 @@
((mar3 goto ?p jan2)) ((mar3 goto ?p jan2))
((mar4 money ?p ,(pays 2000)) (?p fruit)) ((mar4 money ?p ,(pays 2000)) (?p fruit))
((apr1 draw ?p otb)) ((apr1 draw ?p otb))
((apr2 add-rule ?p ((?p corn harvest-mult 2) (?p grain)))) ((apr2 add-rule ?p ,(make-player-year-rule
10 '((?p corn harvest-mult 2) (?p grain)))))
((apr3 money ?p ,(pays 500))) ((apr3 money ?p ,(pays 500)))
((apr4 money ?p ,(pays 1000))) ((apr4 money ?p ,(pays 1000)))
((may1 money ?p ,(gains 500))) ((may1 money ?p ,(gains 500)))
@ -1574,7 +1625,7 @@
((dec2 harvest ?p corn) (?p grain)) ((dec2 harvest ?p corn) (?p grain))
((dec3 money ?p ,(gains 1000))) ((dec3 money ?p ,(gains 1000)))
,@(player-year-rules player) ,@(map (lambda (x) (alist-ref 'rule x)) (player-year-rules player))
((?date harvest-mult ?p ?val) (?date harvest ?p ?crop) (?p ?crop harvest-mult ?val)) ((?date harvest-mult ?p ?val) (?date harvest ?p ?crop) (?p ?crop harvest-mult ?val))
((?date player-action-post-harvest ?p ?val) (?date harvest ?p ?crop) (?p ?crop player-action-post-harvest ?val)) ((?date player-action-post-harvest ?p ?val) (?date harvest ?p ?crop) (?p ?crop player-action-post-harvest ?val))
@ -1647,7 +1698,8 @@
(car new-otb)))) (car new-otb))))
(define (do-action action player) (define (do-action action player)
(let ((a (alist-ref '?action action))) (let ((a (alist-ref '?action action))
(game (session-ref (sid) 'game)))
(cond ((eq? a 'money) (cond ((eq? a 'money)
(let ((changed ((alist-ref '?value action) 0))) (let ((changed ((alist-ref '?value action) 0)))
(push-message player (conc "You " (if (>= changed 0) "earned" "paid") " $" (push-message player (conc "You " (if (>= changed 0) "earned" "paid") " $"
@ -1655,10 +1707,11 @@
(set! (player-cash player) (set! (player-cash player)
((alist-ref '?value action) (player-cash player)))) ((alist-ref '?value action) (player-cash player))))
((eq? a 'add-rule) ((eq? a 'add-rule)
(when (not (member (alist-ref 'id (alist-ref '?value action))
(map (lambda (x) (alist-ref 'id x))
(player-year-rules player))))
(set! (player-year-rules player) (set! (player-year-rules player)
(cons (alist-ref '?value action) (player-year-rules player))) (cons (alist-ref '?value action) (player-year-rules player)))))
;; TODO handle being added multiple times
)
((eq? a 'goto) ((eq? a 'goto)
(set! (player-previous-space player) (player-space player)) (set! (player-previous-space player) (player-space player))
(set! (player-space player) (set! (player-space player)
@ -1676,7 +1729,7 @@
(begin (push! (car new-ff) (player-farmers-fates player)) (begin (push! (car new-ff) (player-farmers-fates player))
(set! (game-farmers-fates game) remaining-ffs)) (set! (game-farmers-fates game) remaining-ffs))
(set! (game-farmers-fates game) (append remaining-ffs new-ff))) (set! (game-farmers-fates game) (append remaining-ffs new-ff)))
`((actions . ,((alist-ref 'action (car new-ff)) player)) `((actions . ,((alist-ref 'action (car new-ff)) player game))
(contents . ,(alist-ref 'contents (car new-ff))))))) (contents . ,(alist-ref 'contents (car new-ff)))))))
((or (eq? a 'player-action) (eq? a 'player-action-post-harvest)) ((or (eq? a 'player-action) (eq? a 'player-action-post-harvest))
((alist-ref '?value action) player)) ((alist-ref '?value action) player))
@ -1696,22 +1749,36 @@
(player-harvest-mult player))))) (player-harvest-mult player)))))
(if (not (already-harvested? (alist-ref '?value action) player)) (if (not (already-harvested? (alist-ref '?value action) player))
(begin (begin
(push-message player (conc crop " Harvest! You rolled a " rolled
" and earned $" income "!"))
(when (not (= (player-harvest-mult player) 1))
(push-message player (conc "Harvest multiplied by " (player-harvest-mult player) "!")))
(set! (player-cash player) (set! (player-cash player)
(+ (player-cash player) income)) (+ (player-cash player) income))
(set! (player-harvest-mult player) 1) (set! (player-harvest-mult player) 1)
(let ((operating-expense (draw-operating-expense)) (let ((operating-expense (draw-operating-expense game))
(previous-cash (player-cash player))) (previous-cash (player-cash player))
(other-previous-cash (map (lambda (p)
(cons p (player-cash p)))
(filter
(lambda (p)
(not (string=? (player-name p)
(player-name player))))
(game-players game)))))
((alist-ref 'action operating-expense) player) ((alist-ref 'action operating-expense) player)
(push-message player (alist-ref 'summary operating-expense))
`((rolled . ,rolled) `((rolled . ,rolled)
(income . ,income) (income . ,income)
(operatingExpense . ,(alist-ref 'contents operating-expense)) (operatingExpense . ,(alist-ref 'contents operating-expense))
(operatingExpenseValue . ,(- (player-cash player) (operatingExpenseValue . ((,(string->symbol (player-name player))
. ,(- (player-cash player)
previous-cash)) previous-cash))
,@(map (lambda (p/c)
(let ((p (car p/c)))
`(,(string->symbol (player-name p))
. ,(- (player-cash p)
(cdr p/c)))))
(filter
(lambda (p/c)
(not (= 0
(- (player-cash (car p/c))
(cdr p/c)))))
other-previous-cash))))
(crop . ,(symbol->string (alist-ref '?value action))) (crop . ,(symbol->string (alist-ref '?value action)))
(acres . ,acres)))) (acres . ,acres))))
'nothing)))))) 'nothing))))))
@ -1754,6 +1821,12 @@
((eq? b 'goto) #f) ((eq? b 'goto) #f)
(else #f)))))) (else #f))))))
(define (first-game)
(car (app-games *app*)))
(define (gp i)
(list-ref (game-players (first-game)) i))
(cond-expand (cond-expand
(geiser (geiser
'()) '())
@ -1762,11 +1835,34 @@
(repl)) (repl))
(compiling ;; production (compiling ;; production
(run-awful) (run-awful)
(thread-join! *server-thread*))) (repl)
;; (thread-join! *server-thread*)
))
;; TODO ;; TODO
;; make game finished display results.
;; make sure two players can't have the same name ;; make sure two players can't have the same name
;; info actions should look better ;; info actions should look better
;; you can get $50 from harvest ;; you can get $50 from harvest
;; bug: new websocket messages should not reset IFS card selection ;; moving bug 5 from oct 2 saw by player 2
;; from harvest moon rolled 5
;; finished
;; infinite loop ((?action . end-game) (?value . #<procedure (a10302)>))
;; interface.js:172 Uncaught TypeError: Cannot read property 'toFixed' of undefined
;; at formatMoney (interface.js:172)
;; at interface.js:248
;; at Object.dispatch (redux.js:222)
;; at interface.js:94
;; at bk (react-dom.production.min.js:224)
;; at WebSocket.handleMessage (interface.js:46)
;; auto-skip loop
;; harvester / tractor don't say total price
;; trade screen: person who porposed trade is wrong
;; mark spaces
;; decling trade doesn't work
;; support trading farmers fates
;; repay loan box 1 more than max can repay
;; test tractor/harvester a lot better

@ -267,6 +267,7 @@ $tab-margin: 0.3rem;
margin: 0; } margin: 0; }
.player-trade-resources .resource-unit { .player-trade-resources .resource-unit {
display: inline-block;
width: 4rem; } width: 4rem; }
.card-id { .card-id {
@ -546,14 +547,26 @@ $tab-margin: 0.3rem;
max-width: 30rem; } max-width: 30rem; }
@include breakpoint(large) { @include breakpoint(large) {
font-size: 23px; font-size: 23px;
max-width: 45rem; } width: 100%; }
margin-left: auto; margin-left: auto;
margin-right: auto; } margin-right: auto; }
.tab-container { .tab-container {
overflow-y: auto; overflow-y: auto;
max-height: 80vh; max-height: 80vh;
flex-grow: 2; } @include breakpoint(medium down) {
flex-grow: 2;
}
@include breakpoint(large) {
width: 40rem;
}
}
.static-tab-container {
overflow-y: auto;
max-height: 80vh;
width: 26rem;
}
.tab { .tab {
$tab-border: 0.3rem solid $primary-color; $tab-border: 0.3rem solid $primary-color;
@ -581,6 +594,11 @@ $tab-margin: 0.3rem;
.turn-container .button { .turn-container .button {
margin: 0; } margin: 0; }
.harvest-table {
overflow: auto;
max-width: 75vw;
}
.board-heading { .board-heading {
z-index: -1; z-index: -1;
position: absolute; position: absolute;
@ -780,3 +798,9 @@ $intro-time: 6s;
.hidden { .hidden {
display: none; } display: none; }
.space-icon {
position: absolute;
bottom: 0;
opacity: 0.5;
right: 4px; }

Loading…
Cancel
Save