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": [],
"author": "",
"license": "ISC",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@babel/core": "^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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser, faUsers, faTractor, faWindowRestore,
faDollarSign, faTimes, faAsterisk
} from '@fortawesome/free-solid-svg-icons'
faDollarSign, faTimes, faAsterisk, faExchangeAlt,
faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import SpaceNode from './SpaceNode.jsx'
@ -40,7 +40,7 @@ import Tractor from '../tractor/Tractor.jsx'
import { GAME_STATES, ALERTS } from '../../constants.js'
import { itemCard, itemCardShort, fateCard, ridgeNames } from 'game.js'
import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
nextUIAction, alert } from './actions.js'
nextUIAction, alert, alertHandled } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip } from './interface.js'
@ -69,7 +69,8 @@ function getString(id) {
}
function getInt(id) {
return parseInt(getElementValue(id));
const i = parseInt(getElementValue(id));
return isNaN(i) ? 0 : i;
}
function getChecked(id) {
@ -102,7 +103,7 @@ function tradeString(player, invert) {
var r = '';
let mult = invert ? -1 : 1;
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 + ', ';
} else if (k === 'cards') {
r += 'cards: ' + player.trade[k] + ', ';
@ -113,13 +114,15 @@ function tradeString(player, invert) {
class PlayerTradeProposed extends React.Component {
render () {
if (this.props.player.trade.player === undefined) {
return (<br />);
if (this.props.player.trade.error) {
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) {
return (<p>You proposed a trade with <b>{this.props.player.trade.player}</b> for
{'\u00A0'}<b>{tradeString(this.props.player, false)}</b>.</p>);
} 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>);
}
}
@ -159,7 +162,7 @@ class PlayerTradeButton extends React.Component {
} else {
return (<Fragment>
<Button className='tiny' type='submit'>{text} Trade</Button>
<Button className='tiny' onClick={this.submitTradeDeny}>
<Button className='tiny' onClick={this.tradeDeny}>
Deny Trade
</Button>
</Fragment>);
@ -312,7 +315,9 @@ class PlayerTurnContainer extends React.Component {
worth = netWorth(player),
auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.settings.auditThreshold) ? (
<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 = (
<Fragment>
<Button onClick={this.clickRoll}>Roll</Button>{' '}{auditButton}
@ -344,8 +349,6 @@ class PlayerTurnContainer extends React.Component {
</Fragment>);
}
}
} else if (this.props.game.state === 'finished') {
view = (<b>Game finished!</b>);
} else {
view = (
<Button onClick={() => this.props.showScreen(SCREENS.action)}>
@ -817,6 +820,10 @@ class Harvest extends React.Component {
autoSkipView: false };
}
nameText = player => {
return player === this.props.game.currentPlayer ? 'You' : player;
}
nextView = (view, preventAutoSkip) => {
const newView = view ? view :
this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1];
@ -908,19 +915,34 @@ class Harvest extends React.Component {
);
break;
case 'expense-value':
const currentExpense = this.props.expenseValue[this.props.game.currentPlayer];
view = (
<div>
<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' />
<div>
{isCurrentPlayer ? 'You' :
this.props.game.currentPlayer} {this.props.expenseValue < 0 ? 'lost ' : 'gained '}
${Math.abs(this.props.expenseValue)}!
<p>
{isCurrentPlayer ? 'You' : this.props.game.currentPlayer}
{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>
{isCurrentPlayer ? (
{isCurrentPlayer ?
(
<div className='center spacer'>
<Button onClick={this.props.nextAction}>Continue</Button>
</div>
@ -1081,25 +1103,32 @@ class Action extends React.Component {
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
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 = (
<GroupBox title={`Uncle Bert's inheritance`}>
<div className='center'>
<p>
{this.props.player.cash >= 10000 ?
{currentPlayer.cash >= 10000 ?
`You have enough cash to take over Uncle Bert's farm!` :
`You must raise another $` +
formatMoney(10000 - this.props.player.cash) +
formatMoney(10000 - currentPlayer.cash) +
` to be able to take over Uncle Berts farm!`
}
</p>
<p>
{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>
{(this.props.player.name === this.props.game.currentPlayer) ?
ffButtons : (<Fragment />)}
</p>
</div>
</GroupBox>
@ -1107,13 +1136,15 @@ class Action extends React.Component {
buttons = (<Fragment />);
break;
case 'money':
view = (<div className='game-card'>
<div className={'flex ' + (this.props.ui.actionValue < 0 ? 'red' : 'green')}>
const { amount, player } = this.props.ui.actionValue;
view = (
<div className='game-card'>
<div className={'flex ' + (amount < 0 ? 'red' : 'green')}>
<FontAwesomeIcon icon={faDollarSign} size='6x' />
<div>
{this.props.player.name === this.props.game.currentPlayer ? 'You' :
this.props.game.currentPlayer} {this.props.ui.actionValue < 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(this.props.ui.actionValue))}!
{this.props.player.name === player ? 'You' :
player} {amount <= 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(amount))}!
</div>
</div>
</div>);
@ -1211,7 +1242,7 @@ class Board extends React.Component {
render() {
const rh = this.props.height || '20px'; // height={h}
const renderSpace = (s, h, o) =>
(<SpaceNode space={s} key={s.key} orientation={o} />);
(<SpaceNode showIcons={true} space={s} key={s.key} orientation={o} />);
return (
<Fragment>
@ -1277,6 +1308,7 @@ class AlertOverlay extends React.Component {
hide = e => {
e.preventDefault();
this.setState({ visible: false });
this.props.alertHandled(this.props.id);
this.props.hideHandler();
}
@ -1284,6 +1316,7 @@ class AlertOverlay extends React.Component {
this.hide(e);
this.props.handler();
}
// <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
render() {
return (
@ -1295,7 +1328,6 @@ class AlertOverlay extends React.Component {
{this.props.children}
<br />
<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>
</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',
cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action' };
action: 'action', info: 'info' };
class BoardApp extends React.Component {
iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards,
'dollar-sign': SCREENS.loans, users: SCREENS.farms,
'exchange-alt': SCREENS.trade, asterisk: SCREENS.misc,
tractor: SCREENS.action }
tractor: SCREENS.action, 'info-circle': SCREENS.info }
unsubscribeAlert = () => null
constructor(props) {
super(props);
this.state = {
screen: SCREENS.summary
screen: SCREENS.summary,
card: props.ui.card
};
this.myRef = React.createRef();
}
showScreen = screen => {
if (!(window.innerWidth >= 1024 && screen === SCREENS.action)) {
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() {
// const midColHeight = (window.innerHeight -
@ -1360,7 +1492,10 @@ class BoardApp extends React.Component {
}
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 => {
@ -1376,27 +1511,62 @@ class BoardApp extends React.Component {
render() {
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 visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Close'
hideHandler={() => this.props.alert(false)}
hideHandler={() => 'nothing'}
handler={() => { return false; }}>
<Fragment>
<h1>Game Over!</h1>
{this.props.ui.alertContents.map((e, i) => (
{alert.contents.map((e, i) => (
<p key={i}>{e}</p>
))}
</Fragment>
</AlertOverlay>
);
} else if (!this.props.ui.alertHandled && this.state.screen !== SCREENS.action) {
switch (this.props.ui.alert) {
} else if (alert && alert.type === ALERTS.auditCalled) {
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:
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Roll'
hideHandler={() => this.props.alert(false)}
hideHandler={() => 'nothing'}
handler={() => {
roll();
this.showScreen(SCREENS.action); }}>
@ -1407,8 +1577,10 @@ class BoardApp extends React.Component {
case ALERTS.otherPlayersTurn:
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText={'Watch ' + this.props.game.currentPlayer + `'s turn`}
hideHandler={() => this.props.alert(false)}
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.action); }}>
<h1>It&apos;s {this.props.game.currentPlayer}&apos;s turn</h1>
@ -1421,11 +1593,13 @@ class BoardApp extends React.Component {
} else {
alertOverlay = (<Fragment />);
}
if (this.props.ui.alert === ALERTS.raiseMoney) {
if (alert && alert.type === ALERTS.raiseMoney && !this.props.ui.action) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Raise Money'
hideHandler={() => this.props.alert(false)}
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.loans); }}>
<Fragment>
@ -1437,7 +1611,7 @@ class BoardApp extends React.Component {
}
// faExchangeAlt -> trade icon, hidden for now
return (
<div className='game-container'>
<div className='game-container' ref={this.myRef}>
{alertOverlay}
<Board spaces={this.props.spaces}>
<InfoBar message={this.props.ui.message}
@ -1445,7 +1619,7 @@ class BoardApp extends React.Component {
players={this.props.game.otherPlayers} />
<div className='center-board-container'>
<ul className='horizontal menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk]
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faExchangeAlt, faInfoCircle]
.map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}>
<div></div>
@ -1453,7 +1627,7 @@ class BoardApp extends React.Component {
<FontAwesomeIcon icon={icon} /></a></li>))}
</ul>
<ul className='vertical menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk]
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faExchangeAlt, faInfoCircle]
.map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}>
<a onClick={this.iconOnClick(icon.iconName)}>
@ -1481,13 +1655,18 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.cards)}>
<Row>
<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 className='cell medium-auto'>
<Card ui={this.props.ui}
min={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000}
max={Math.floor(Math.min(this.props.player.cash / 1000, this.props.ui.card.total / 1000))}
cash={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000} />
playerCash={this.props.player.cash}
playerDebt={this.props.player.debt}
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>
</Row>
</div>
@ -1504,6 +1683,23 @@ class BoardApp extends React.Component {
<div className={this.tabClass(SCREENS.misc)}>
<Misc />
</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>
</Board>
@ -1514,7 +1710,7 @@ class BoardApp extends React.Component {
export default connect(
state => state.farm,
{ setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert }
{ setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert, alertHandled }
)(BoardApp)
class Card extends React.Component {
@ -1543,37 +1739,47 @@ class Card extends React.Component {
handleSubmit = e => {
e.preventDefault();
buy(this.props.ui.card.id, this.state.cash);
buy(this.props.card.id, this.state.cash);
}
render () {
const card = this.props.ui.card;
const card = this.props.card;
let action = (null);
switch (card.type) {
case 'otb': action = (
<div className='card-action'>
<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'>
<Col width='2'>
<Button className='tiny'
disabled={this.props.max < this.props.min}
type='submit'>Buy</Button>
</Col>
<Col width='4' />
<Col width='6'>
<Col width='10'>
<div className='money'>
$:
with cash $
<input id='cash-input' type='number'
step='1'
min='0'
max={Math.ceil(this.props.max)}
max={Math.floor(this.props.max)}
disabled={this.props.max < this.props.min}
onChange={this.handleInput}
value={Math.ceil(this.state.cash)} />
{'\u00A0'},000
value={Math.floor(this.state.cash)} />
{'\u00A0'}K
</div>
</Col>
</Row>
</form>
</div>); break;
case 'no-card': action =
@ -1581,7 +1787,7 @@ class Card extends React.Component {
}
return (
<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'
dangerouslySetInnerHTML={{__html: card.contents}} />
@ -1604,8 +1810,8 @@ class CardListComp extends React.Component {
const ui = this.props.ui,
cards = ui.cards,
cardOps = cards.map((c, i) =>
(<li key={i} className={c == ui.card ? 'card-select-selected' : ''}
onClick={() => this.props.setSelectedCard(c)}>
(<li key={i} className={c.id == this.props.cardId ? 'card-select-selected' : ''}
onClick={() => this.props.setCard(c)}>
{c.summary}
</li>));

@ -20,6 +20,8 @@ import React from 'react'
import { connect } from 'react-redux'
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'
@ -57,6 +59,8 @@ class SpaceNode extends React.Component {
<div className='space-description'>
{this.props.space.description}
</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>
);

@ -36,3 +36,4 @@ export const ALERT = 'alert'
export const ALERT_HANDLED = 'alert-handled'
export const AUTO_SKIP = 'auto-skip'
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,
MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION,
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,
spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace,
mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction,
markActionChangeHandled, nextUIActionSilent, alert, alertHandled,
autoSkip, message }
autoSkip, message, setHarvestTable }
function updateGame(update) {
return { type: UPDATE_GAME,
@ -107,12 +107,12 @@ function markActionChangeHandled() {
return { type: MARK_ACTION_CHANGE_HANDLED };
}
function alert(value, contents) {
return { type: ALERT, value, contents };
function alert(value, contents, id) {
return { type: ALERT, value, contents, id };
}
function alertHandled() {
return { type: ALERT_HANDLED };
function alertHandled(id) {
return { type: ALERT_HANDLED, id };
}
function autoSkip(component) {
@ -122,3 +122,7 @@ function autoSkip(component) {
function 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,
movePlayer, setOldMessages, markActionChangeHandled,
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'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
@ -47,12 +47,16 @@ function handleMessage(evt) {
if (data.player.state === GAME_STATES.preTurn &&
data.game.otherPlayers.length > 0 &&
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
if (data.event === 'update' &&
data.game.currentPlayer !== store.getState().farm.game.currentPlayer) {
@ -62,6 +66,7 @@ function handleMessage(evt) {
store.dispatch(updatePlayer(data.player));
if (data.event === 'init') {
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
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);
store.dispatch(updateGame(data.game));
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));
if (data.event === 'action') {
if (data.player.name !== data.game.currentPlayer &&
@ -94,15 +94,25 @@ function handleMessage(evt) {
if (data.player.state === GAME_STATES.midTurn &&
data.player.cash < 0 &&
!store.getState().farm.ui.nextAction) {
store.dispatch(alert(ALERTS.raiseMoney));
} else if (data.player.state === GAME_STATES.midTurn) {
store.dispatch(alertHandled());
}
store.dispatch(alert(ALERTS.raiseMoney, '', 'raiseMoney' + data.game.turn));
}// else if (data.player.state === GAME_STATES.midTurn &&
// !data.game.state === 'finished') {
// store.dispatch(alertHandled());
// }
if (data.event === 'auto-skip') {
store.dispatch(autoSkip(data.component));
}
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_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION,
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 { spaceContent, corners } from 'game.js'
const spaces =
[[corners[0], 'buy'],
['January', 'buy'],
['January', 'buy'],
['January', 'buy'],
['January', 'buy'],
['February','buy'],
['February', 'buy'],
['February', 'buy'],
['February', 'buy'],
['March', 'buy'],
['March', 'buy'],
['March', 'buy'],
['March', 'buy'],
['April', 'buy'],
[corners[1], 'buy'],
['April', 'none'],
['April', 'none'],
['May', 'none'],
['May', 'none'],
['May', 'hay'],
['May', 'hay'],
['June', 'hay'],
['June', 'hay'],
['June', 'cherry'],
['June', 'cherry'],
[corners[2], 'cherry'],
['July', 'hay'],
['July', 'hay'],
['July', 'hay'],
['July', 'wheat'],
['August', 'wheat'],
['August', 'wheat'],
['August', 'wheat'],
['August', 'wheat'],
['September', 'hay'],
['September', 'hay'],
['September', 'cows'],
[corners[3], 'cows'],
['September', 'cows'],
['October', 'cows'],
['October', 'hay'],
['October', 'hay'],
['October', 'apple'],
['November', 'apple'],
['November', 'apple'],
['November', 'apple'],
['November', 'corn'],
['December', 'corn'],
['December', 'corn']]
[[corners[0], 'buy', false],
['January', 'buy', false],
['January', 'buy', 'otb'],
['January', 'buy', false],
['January', 'buy', false],
['February','buy', false],
['February', 'buy', 'fate'],
['February', 'buy', false],
['February', 'buy', 'otb'],
['March', 'buy', false],
['March', 'buy', false],
['March', 'buy', false],
['March', 'buy', false],
['April', 'buy', 'otb'],
[corners[1], 'buy', false],
['April', 'none', false],
['April', 'none', false],
['May', 'none', false],
['May', 'none', false],
['May', 'hay', false],
['May', 'hay', 'otb'],
['June', 'hay', false],
['June', 'hay', false],
['June', 'cherry', false],
['June', 'cherry', 'fate'],
[corners[2], 'cherry', false],
['July', 'hay', false],
['July', 'hay', 'otb'],
['July', 'hay', false],
['July', 'wheat', false],
['August', 'wheat', false],
['August', 'wheat', false],
['August', 'wheat', false],
['August', 'wheat', false],
['September', 'hay', false],
['September', 'hay', 'otb'],
['September', 'cows', false],
[corners[3], 'cows', false],
['September', 'cows', false],
['October', 'cows', false],
['October', 'hay', 'fate'],
['October', 'hay', 'otb'],
['October', 'apple', 'fate'],
['November', 'apple', 'otb'],
['November', 'apple', false],
['November', 'apple', false],
['November', 'corn', false],
['December', 'corn', false],
['December', 'corn', 'fate']]
.map((s, i) => {
return { month: s[0], description: spaceContent[i],
card: s[2],
type: s[1], key: i, players: [] }});
const initialState = {
@ -112,10 +113,11 @@ const initialState = {
nextActionValue: null,
actionChangeHandled: true,
message: '',
alert: false,
alertContents: false,
alerts: {},
unhandledAlert: false,
autoSkip: false,
alertHandled: false },
playerSpaces: {},
harvestTable: false },
spaces: spaces,
space: null,
// message panel dimenions
@ -150,7 +152,10 @@ export default function(state = initialState, action) {
.filter(x => x !== action.player) };
}
return item;
})
}),
ui: { ...state.ui,
playerSpaces: { ...state.ui.playerSpaces,
[action.player]: action.newSpace }}
};
case SET_OLD_MESSAGES:
return { ...state, oldMessages: action.messages };
@ -179,16 +184,34 @@ export default function(state = initialState, action) {
case MARK_ACTION_CHANGE_HANDLED:
return { ...state, ui: { ...state.ui, actionChangeHandled: true }};
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,
alert: action.value,
alertContents: action.contents,
alertHandled: action.value === false ? true : false }};
alerts,
unhandledAlert: Object.values(alerts).find(x => !x.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:
return { ...state, ui: { ...state.ui, autoSkip: action.component }};
case MESSAGE:
return { ...state, ui: { ...state.ui, message: action.message }};
case SET_HARVEST_TABLE:
return { ...state, ui: { ...state.ui, harvestTable: action.table }};
default:
return state;
}

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

@ -32,4 +32,6 @@ export const messagePanelId = 'message-panel';
export const ALERTS = { beginTurn: 'begin-turn',
otherPlayersTurn: 'other-players-turn',
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)
(previous-space initform: 0 accessor: player-previous-space)
(state initform: 'turn-ended accessor: player-state)
(finished initform: #f accessor: player-finished)
(assets initform:
'((hay . 10) (grain . 10) (fruit . 0) (cows . 0)
(harvester . 0) (tractor . 0))
@ -94,6 +95,7 @@
(state initform: 'playing accessor: game-state)
(name initform: "game" accessor: game-name)
(turn initform: 1 accessor: game-turn)
(current-player initform: #f accessor: game-current-player)
(actions initform: '() accessor: game-actions)
(settings initform:
'((down-payment . 0.2)
@ -101,7 +103,8 @@
(max-debt . 50000)
(audit-threshold . 250000)
(starting-cash . 5000)
(starting-debt . 5000))
(starting-debt . 5000)
(trade . #t))
accessor: game-settings)))
(define (game-setting setting game)
@ -223,38 +226,35 @@
'state (if (= (length (game-players game)) 0)
'pre-turn 'turn-ended))))
(set! (game-players game) (append (game-players game) (list player)))
(when (= (length (game-players game)) 1)
(set! (game-current-player game) player))
player))
(define (all-players-finished game)
(null? (filter (lambda (p)
(not (eq? (player-state p) 'finished-game)))
(not (player-finished p)))
(game-players game))))
(define (next-player game player)
(let ((tail (cdr (filter (lambda (p)
(not (eq? (player-state p) 'finished-game)))
(find-tail (cut eq? <> player) (game-players game))))))
(if (null? tail)
(car (game-players game))
(car tail))))
(define (next-player game)
(let ((tail (filter (lambda (p)
(not (player-finished p)))
(find-tail (cut eq? <> (game-current-player game))
(game-players game)))))
(if (or (null? tail) (null? (cdr tail)))
(car (filter (lambda (p)
(not (player-finished p)))
(game-players game)))
(car (cdr tail)))))
(define (advance-turn game player)
(if (all-players-finished game)
(set! (game-state game) 'finished)
(begin (set! (player-state player) 'turn-ended)
(set! (player-state (next-player game player)) 'pre-turn)
(let ((next (next-player game)))
(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)))))
(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)
(let loop ((players (game-players game)))
(if (null? players)
@ -326,7 +326,7 @@
(define (game->list g player)
`((game . ((messages . ,(list->vector (reverse (game-messages g))))
(currentPlayer . ,(player-name (current-players-turn g)))
(currentPlayer . ,(player-name (game-current-player g)))
(otherPlayers
. ,(list->vector
(map
@ -340,7 +340,8 @@
(settings . ((downPayment . ,(game-setting 'down-payment g))
(loanInterest . ,(game-setting 'loan-interest 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)))
(if player
@ -405,6 +406,9 @@
(push-message player (conc "You bought " amount " " crop "."))
#t)))))
(define (make-player-year-rule id rule)
`((id . ,id) (rule . ,rule)))
(define (finish-year player #!optional (collect-wages #t))
(let ((game (session-ref (sid) 'game)))
(when collect-wages
@ -417,10 +421,11 @@
(?value . "You earned $5,000 from your city job!"))
(game-actions game))))
(when (game-called-audit game)
(set! (player-state player) 'finished-game)
(advance-turn game player)
;; advance turn resets state back to turn ended
(set! (player-state player) 'finished-game))
(set! (game-actions game)
(append (game-actions game)
`(((?action . end-game)
(?value . ,(lambda ()
(set! (player-finished player) #t))))))))
(set! (player-year-rules player) (player-next-year-rules player))
(set! (player-next-year-rules player) '())
(when (not (null? (player-farmers-fates player)))
@ -436,12 +441,16 @@
(set! (game-farmers-fates game)
(filter (lambda (c) (not (eq? (alist-ref 'internal-id c) 'cows-15)))
(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))
(?p cows))
(?p cows)))
(player-year-rules player))
(push! `((?d player-action ?p
,(make-remove-farmers-fate-after 'cows-15 40)))
(push! (make-player-year-rule
1
`((?d player-action ?p
,(make-remove-farmers-fate-after 'cows-15 40))))
(player-year-rules player))))))
(define (find-player-by-name game name)
@ -489,50 +498,47 @@
'()
cards)))
(cond (basics
(push-message player (conc "You don't have enough " basics " to trade!"))
#f)
(conc "You don't have enough " basics " to trade!"))
(other-basics
(push-message player (conc (player-name other-player)
(conc (player-name other-player)
" doesn't have enough " other-basics " to trade!"))
#f)
(ridges
(push-message player (conc ridges " ridge not available to trade!"))
#f)
(conc ridges " ridge not available to trade!"))
((< (+ (player-cash player) (alist-ref 'money params eqv? 0)) 0)
(push-message player "You don't have enough cash to trade!")
#f)
"You don't have enough cash to trade!")
((< (+ (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!"))
#f)
((not (null? missing-cards))
(push-message player (conc "Nobody has cards: "
(conc "Nobody has cards: "
(string-intersperse
(map number->string missing-cards)
", ") "."))
#f)
(else
other-player))))
(define *trade-number* 0)
(define (propose-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))
(not (equal? (cdr x) ""))
(cdr x)))
params)))
(push-message player
(conc "Trade proposed to " (player-name other-player) "!"))
(set! *trade-number* (+ *trade-number* 1))
(set! (player-trade other-player)
(append `((player . ,(player-name player))
(originator . ,(player-name player)))
(originator . ,(player-name player))
(trade-number . ,*trade-number*))
to-trade))
(set! (player-trade player)
(append `((player . ,(player-name other-player))
(originator . ,(player-name player)))
(originator . ,(player-name player))
(trade-number . ,*trade-number*))
to-trade))
#t)
#f)))
other-player)))
(define (otb-by-id player id)
(find (lambda (card)
@ -689,6 +695,7 @@
(when game
(set! (game-messages game) '())
(set! (player-last-cash player) (player-cash player)))
(print "message type: " type)
(cond ((string=? type "roll")
(let ((num (+ (random 6) 1)))
(when *next-roll* (set! num *next-roll*))
@ -704,8 +711,6 @@
(when (and (> (player-previous-space player) 40)
(< (player-space player) 10))
(finish-year player))
(when (eq? (game-state game) 'finished)
(do-end-of-game game)) ;; TODO check
(set! (player-harvest-mult player) 1)
(let ((resp `((from . ,(player-previous-space player))
(to . ,(player-space player)))))
@ -719,9 +724,11 @@
(create-ws-response player "action" `((action . "roll") (value . ,resp))))))
((and (string=? type "next-action")
(not (eq? (player-state player) 'turn-ended)))
(let loop ()
(if (null? (game-actions game))
(let loop ((i 0))
(if (or (null? (game-actions game))
(>= i 15))
(begin
(set! (game-actions game) '())
(message-players! game player `((action . #f) (value . #f)))
(create-ws-response player "action" '((action . #f))))
(let* ((action (car (game-actions game)))
@ -756,7 +763,7 @@
(let ((res (do-action action player)))
(set! (game-actions game) (cdr (game-actions game)))
(if (eq? res 'nothing)
(loop)
(loop (+ i 1))
(begin
(message-players!
game player
@ -772,21 +779,27 @@
(set! (game-actions game)
(cdr (game-actions game)))
(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
(message-players! game player
`((action . "money")
(value . ,(- (player-cash player)
previous-cash))))
(create-ws-response player "action"
`((action . "money")
(value . ,(- (player-cash player)
previous-cash))))))))
(value)
(set! (game-actions game) '()))
(set! (game-actions game)
(append (cdr (game-actions game))
(list (car (game-actions game))))))
(loop (+ i 1)))
((or (eq? name 'harvest-mult)
(eq? name 'player-action-post-harvest))
(set! (game-actions game) (cdr (game-actions game)))
(do-action action player)
(loop))
(loop (+ i 1)))
((eq? value 'farmers-fate)
(let ((ff (do-action action player)))
(set! (game-actions game)
@ -800,14 +813,13 @@
(value . ,(alist-ref 'contents ff))))))
((eq? name 'ff-money)
(set! (game-actions game) (cdr (game-actions game)))
(if (= value 0)
(loop)
(begin
(message-players! game player
`((action . "money") (value . ,value)))
(create-ws-response player "action"
`((action . "money")
(value . ,value))))))
(if (= (alist-ref 'amount value) 0)
(loop (+ i 1))
(let ((res `((action . "money")
(value . ((amount . ,(alist-ref 'amount value))
(player . ,(alist-ref 'name value)))))))
(message-players! game player res)
(create-ws-response player "action" res))))
((eq? name 'ff-uncle-bert)
(set! (game-actions game) (cdr (game-actions game)))
(message-players! game player
@ -838,7 +850,7 @@
((eq? name 'add-rule)
(do-action action player)
(set! (game-actions game) (cdr (game-actions game)))
(loop))
(loop (+ i 1)))
(else ;; TODO make error
(create-ws-response player "action" `((action . ,name)))))))))
((string=? type "skip")
@ -904,9 +916,13 @@
))
(create-ws-response player "loan" '()))
((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")
(create-ws-response player "trade" '()))
(begin (set! (player-trade player) `((error . ,res)))
(create-ws-response player "trade-error" `())))))
((string=? type "trade-accept")
(accept-trade game player)
(message-players! game player '() type: "update")
@ -934,11 +950,15 @@
(message-players! game player '() type: "update")
(create-ws-response player "called-audit" '()))
((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")
(if (>= (player-cash player) 0)
(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" '()))
(begin (push-message player "Cannot end a turn with negative cash!")
(create-ws-response player "update" '()))))
@ -953,17 +973,18 @@
'id (next-game-id *app*)
'otbs (setup-otbs)
'operating-expenses (setup-operating-expenses)
'farmers-fates (setup-farmers-fates)
'farmers-fates (setup-farmers-fates #t)
'settings
`((down-payment . ,(->pct (alist-ref 'downPayment msg) 0.2))
(loan-interest . ,(->pct (alist-ref 'loanInterest msg) 0.2))
`((down-payment . ,(->pct (alist-ref 'downPayment msg) 0))
(loan-interest . ,(->pct (alist-ref 'loanInterest msg) 0))
(max-debt . ,(->i (alist-ref 'maxDebt msg) 50000))
(audit-threshold . ,(->i (alist-ref 'auditThreshold msg)
250000))
(starting-cash . ,(->i (alist-ref 'startingCash msg)
5000))
0))
(starting-debt . ,(->i (alist-ref 'startingDebt msg)
5000)))))
0))
(trade . ,(or (alist-ref 'trade msg) #t)))))
(player (add-player-to-game game
color
(alist-ref 'playerName msg))))
@ -973,11 +994,14 @@
(set-startup-otbs game player 2)
(create-start-response "new-game-started")))
((string=? type "join-game")
(let* ((color (string->symbol (alist-ref 'checkedColor msg)))
(name (alist-ref 'gameName msg))
(let* ((name (alist-ref 'gameName msg))
(id (alist-ref 'gameId msg))
(game (find (lambda (g) (= (game-id g) id))
(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
color
(alist-ref 'playerName msg))))
@ -1156,7 +1180,7 @@
(define (players-with asset game)
(filter (lambda (player)
(player-has-asset? 'harvester player))
(player-has-asset? asset player))
(game-players game)))
(define (player-asset-binary-count asset game)
@ -1256,88 +1280,105 @@
(iota (list-ref spec 0))))
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
(syntax-rules (?action ff-money ?value)
((_ (player) body ...)
(let ((previous-cash (player-cash player)))
(syntax-rules ()
((_ (player game) body ...)
(let ((previous-cash (map (lambda (p)
(cons p (player-cash p))) (game-players game))))
body ...
`(((?action . ff-money)
(?value . ,(- (player-cash player) previous-cash))))))))
`(,(ff-money-response (- (player-cash player)
(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*
;; xxx multiplayer interaction
`((1 ,(lambda (player)
`((1 ,(lambda (player game)
(with-ff-money-action (player game)
(for-each (lambda (p)
(let ((roll (+ (random 6) 1)))
(if (odd? roll)
(push-message p (conc "You rolled a " roll " and escaped!"))
(begin (push-message p (conc "You rolled a " roll " and were hit!"))
((make-player-pays (* (player-acres p) 100)) p)))))
((make-player-pays (* (player-acres p) 100)) p))))
(filter (lambda (x) (not (eq? x player)))
(game-players (session-ref (sid) 'game))))
(with-ff-money-action (player)
((make-player-gains-per-unit 'hay 500) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
((make-player-gains-per-unit 'grain 100) player)))
#f)
(1 ,(lambda (player)
(push! '((?p wheat harvest-mult 0.5) (?p grain)) (player-year-rules player))
(push! `((?p wheat player-action-post-harvest
(1 ,(lambda (player game)
(push! (make-player-year-rule 2 '((?p wheat harvest-mult 0.5) (?p grain)))
(player-year-rules player))
(push! (make-player-year-rule
3
`((?p wheat player-action-post-harvest
,(make-remove-farmers-fate-from-hand 'windy-spring))
(?p grain))
(?p grain)))
(player-year-rules player))
(push! `((?d player-action ?p
,(make-remove-farmers-fate-after 'windy-spring 34)))
(push! (make-player-year-rule
4
`((?d player-action ?p
,(make-remove-farmers-fate-after 'windy-spring 34))))
(player-year-rules player))
'())
#t
windy-spring)
(1 ,(lambda (player)
(1 ,(lambda (player game)
(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)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
((make-player-gains-per-unit 'hay 100) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player) ((make-player-gains 1000) player)))
(1 ,(lambda (player game)
(with-ff-money-action (player game) ((make-player-gains 1000) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player) ((make-player-pays 7000) player)))
(1 ,(lambda (player game)
(with-ff-money-action (player game) ((make-player-pays 7000) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 500) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
(let ((to-earn (* (player-acres player) 100)))
(push-message player (conc "You earned $" to-earn "!"))
(set! (player-cash player)
(+ (player-cash player) to-earn)))))
#f)
(2 ,(lambda (player)
(2 ,(lambda (player game)
`(((?action . player-action)
(?value . ,(lambda (player) (finish-year player #f))))
((?action . goto) (?value . jan2))))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 300) player)))
#f)
;; xxx multiplayer interaction
(2 ,(lambda (player)
(with-ff-money-action (player)
(2 ,(lambda (player game)
(with-ff-money-action (player game)
(equipment-payout 'tractor player 3000 (session-ref (sid) 'game))))
#f)
;; xxx multiplayer interaction
(1 ,(lambda (player)
(1 ,(lambda (player game)
(if (player-has-asset? 'harvester player)
(with-ff-money-action (player)
(with-ff-money-action (player game)
(for-each (lambda (from-player)
(when (not (eq? player from-player))
(when (not (player-has-asset? 'harvester from-player))
@ -1348,42 +1389,49 @@
(game-players (session-ref (sid) 'game))))
'()))
#f)
(1 ,(lambda (player)
(push! '((?p ?any harvest-mult 0) (?p ?crop)) (player-year-rules player))
(1 ,(lambda (player game)
(push! (make-player-year-rule 5 '((?p ?any harvest-mult 0) (?p ?crop)))
(player-year-rules player))
'())
#t)
(1 ,(lambda (player)
(1 ,(lambda (player game)
`(((?action . ff-uncle-bert) (?value . #f))))
#f)
;; xxx multiplayer interaction
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
(equipment-payout 'harvester player 2500
(session-ref (sid) 'game))))
#f)
(1 ,(lambda (player)
(push! `((?p cows harvest-mult 1.5) (?p cows)) (player-year-rules player))
(push! `((?p cows harvest-mult 1.5) (?p cows)) (player-next-year-rules player))
(1 ,(lambda (player game)
(push! (make-player-year-rule 6 `((?p cows harvest-mult 1.5) (?p cows)))
(player-year-rules player))
(push! (make-player-year-rule 7 `((?p cows harvest-mult 1.5) (?p cows)))
(player-next-year-rules player))
'())
#t
cows-15)
(1 ,(lambda (player)
(with-ff-money-action (player) ((make-player-gains 2000) player)))
(1 ,(lambda (player game)
(with-ff-money-action (player game) ((make-player-gains 2000) player)))
#f)
(1 ,(lambda (player)
(1 ,(lambda (player game)
(when (< (player-space player) 26)
(push! '((?p cherries harvest-mult 0.5) (?p fruit)) (player-year-rules player))
(push! `((?p cherries player-action-post-harvest
(push! (make-player-year-rule 7 '((?p cherries harvest-mult 0.5) (?p fruit)))
(player-year-rules player))
(push! (make-player-year-rule
8
`((?p cherries player-action-post-harvest
,(make-remove-farmers-fate-from-hand 'cherries-05))
(?p fruit))
(?p fruit)))
(player-year-rules player)))
(push! `((?d player-action ?p
,(make-remove-farmers-fate-after 'cherries-05 26)))
(push! (make-player-year-rule
8
`((?d player-action ?p
,(make-remove-farmers-fate-after 'cherries-05 26))))
(player-year-rules player))
'())
#t
cherries-05)
(1 ,(lambda (player)
(1 ,(lambda (player game)
(let ((cows (player-asset 'cows player))
(ridge-cows (cows-on-ridges player)))
(if (> cows ridge-cows)
@ -1398,16 +1446,19 @@
" cows slaughtered on your farm.")))))
'())))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player)
(1 ,(lambda (player game)
(with-ff-money-action (player game)
((make-player-pays-per-unit 'fruit 1000) player)))
#f)
(1 ,(lambda (player)
(with-ff-money-action (player) ((make-semi-annual-interest-due) player)))
(1 ,(lambda (player game)
(with-ff-money-action (player game) ((make-semi-annual-interest-due) player)))
#f)))
(define (setup-farmers-fates)
(shuffle (farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*)))
(define (setup-farmers-fates shuffle?)
(let ((cards (farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*)))
(if shuffle?
(shuffle cards)
cards)))
(define *farmers-fates-cards*
(farmers-fate-spec-list->farmers-fate-cards *farmers-fates-specs* *ff-text*))
;; (define *farmers-fates* (setup-farmers-fates))
@ -1466,9 +1517,8 @@
(length (operating-expenses-spec-list->operating-expenses-cards
*operating-expenses-specs* *oe-text*)))
(define (draw-operating-expense)
(let* ((game (session-ref (sid) 'game))
(card (list-ref (game-operating-expenses game)
(define (draw-operating-expense game)
(let ((card (list-ref (game-operating-expenses game)
(game-operating-expense-index game))))
(if (= (+ (game-operating-expense-index game) 1) *total-operating-expenses*)
(set! (game-operating-expense-index game) 0)
@ -1497,7 +1547,7 @@
((jan1 player-action ?p ,(make-semi-annual-interest-due)))
((jan2 draw ?p otb))
((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)))
((feb2 draw ?p farmers-fate))
((feb3 goto ?p apr2))
@ -1507,7 +1557,8 @@
((mar3 goto ?p jan2))
((mar4 money ?p ,(pays 2000)) (?p fruit))
((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)))
((apr4 money ?p ,(pays 1000)))
((may1 money ?p ,(gains 500)))
@ -1574,7 +1625,7 @@
((dec2 harvest ?p corn) (?p grain))
((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 player-action-post-harvest ?p ?val) (?date harvest ?p ?crop) (?p ?crop player-action-post-harvest ?val))
@ -1647,7 +1698,8 @@
(car new-otb))))
(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)
(let ((changed ((alist-ref '?value action) 0)))
(push-message player (conc "You " (if (>= changed 0) "earned" "paid") " $"
@ -1655,10 +1707,11 @@
(set! (player-cash player)
((alist-ref '?value action) (player-cash player))))
((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)
(cons (alist-ref '?value action) (player-year-rules player)))
;; TODO handle being added multiple times
)
(cons (alist-ref '?value action) (player-year-rules player)))))
((eq? a 'goto)
(set! (player-previous-space player) (player-space player))
(set! (player-space player)
@ -1676,7 +1729,7 @@
(begin (push! (car new-ff) (player-farmers-fates player))
(set! (game-farmers-fates game) remaining-ffs))
(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)))))))
((or (eq? a 'player-action) (eq? a 'player-action-post-harvest))
((alist-ref '?value action) player))
@ -1696,22 +1749,36 @@
(player-harvest-mult player)))))
(if (not (already-harvested? (alist-ref '?value action) player))
(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)
(+ (player-cash player) income))
(set! (player-harvest-mult player) 1)
(let ((operating-expense (draw-operating-expense))
(previous-cash (player-cash player)))
(let ((operating-expense (draw-operating-expense game))
(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)
(push-message player (alist-ref 'summary operating-expense))
`((rolled . ,rolled)
(income . ,income)
(operatingExpense . ,(alist-ref 'contents operating-expense))
(operatingExpenseValue . ,(- (player-cash player)
(operatingExpenseValue . ((,(string->symbol (player-name player))
. ,(- (player-cash player)
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)))
(acres . ,acres))))
'nothing))))))
@ -1754,6 +1821,12 @@
((eq? b 'goto) #f)
(else #f))))))
(define (first-game)
(car (app-games *app*)))
(define (gp i)
(list-ref (game-players (first-game)) i))
(cond-expand
(geiser
'())
@ -1762,11 +1835,34 @@
(repl))
(compiling ;; production
(run-awful)
(thread-join! *server-thread*)))
(repl)
;; (thread-join! *server-thread*)
))
;; TODO
;; make game finished display results.
;; make sure two players can't have the same name
;; info actions should look better
;; 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; }
.player-trade-resources .resource-unit {
display: inline-block;
width: 4rem; }
.card-id {
@ -546,14 +547,26 @@ $tab-margin: 0.3rem;
max-width: 30rem; }
@include breakpoint(large) {
font-size: 23px;
max-width: 45rem; }
width: 100%; }
margin-left: auto;
margin-right: auto; }
.tab-container {
overflow-y: auto;
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-border: 0.3rem solid $primary-color;
@ -581,6 +594,11 @@ $tab-margin: 0.3rem;
.turn-container .button {
margin: 0; }
.harvest-table {
overflow: auto;
max-width: 75vw;
}
.board-heading {
z-index: -1;
position: absolute;
@ -780,3 +798,9 @@ $intro-time: 6s;
.hidden {
display: none; }
.space-icon {
position: absolute;
bottom: 0;
opacity: 0.5;
right: 4px; }

Loading…
Cancel
Save