Adding multiplayer info bar, game settings, cleaning up loans.

logins
Thomas Hintz 5 years ago
parent a22cf21662
commit 77a8692f71

@ -310,7 +310,7 @@ class PlayerTurnContainer extends React.Component {
let view; let view;
const player = this.props.player, const player = this.props.player,
worth = netWorth(player), worth = netWorth(player),
auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.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 (player.state === GAME_STATES.preTurn) {
view = ( view = (
@ -505,7 +505,22 @@ class Messages extends React.Component {
class Loans extends React.Component { class Loans extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { repay: 0, takeOut: 0 }; this.state = { repay: 0, takeOut: 0,
maxLoan: this.getMaxLoan(props) };
}
getMaxLoan = props => {
let i = 0,
maxLoan = 0,
loanInterest = props.game.settings.loanInterest,
maxDebt = props.game.settings.maxDebt;
const max = maxDebt - props.player.debt;
while (maxLoan <= max) {
maxLoan = i * (loanInterest + 1);
i += 1000;
}
i -= 1000;
return i - 1000;
} }
handleInput = e => { handleInput = e => {
@ -520,10 +535,10 @@ class Loans extends React.Component {
} else { } else {
takeOut = value * 1000; takeOut = value * 1000;
} }
this.setState({ repay: Math.max(0, Math.floor( this.setState({ repay: Math.max(0, Math.ceil(
Math.min(repay, this.props.player.debt, this.props.player.cash) / 1000)), Math.min(repay, this.props.player.debt + 900, this.props.player.cash) / 1000)),
takeOut: Math.max(0, Math.floor( takeOut: Math.max(0, Math.floor(
Math.min(takeOut, 50000 - this.props.player.debt) / 1000)) }); Math.min(takeOut, this.state.maxLoan) / 1000)) });
} }
handleSubmit = e => { handleSubmit = e => {
@ -532,11 +547,22 @@ class Loans extends React.Component {
this.setState({ repay: 0, takeOut: 0 }); this.setState({ repay: 0, takeOut: 0 });
} }
componentDidUpdate(prevProps) {
if (this.props.player.debt !== prevProps.player.debt ||
this.props.game.settings.loanInterest !== prevProps.game.settings.loanInterest ||
this.props.game.settings.maxDebt !== prevProps.game.settings.maxDebt) {
this.setState({ maxLoan: this.getMaxLoan(this.props) });
}
}
render () { render () {
return ( return (
<GroupBox title={'Loans'}> <GroupBox title={'Loans'}>
<MoneySummary title='Cash' value={this.props.player.cash} /> {' '} <MoneySummary title='Cash' value={this.props.player.cash} /> {' '}
<MoneySummary title='Debt' value={this.props.player.debt} />/$50,000 <MoneySummary title='Debt' value={this.props.player.debt} />
/${formatMoney(this.props.game.settings.maxDebt)}
<br />
{this.props.game.settings.loanInterest > 0 ? '(' + (this.props.game.settings.loanInterest * 100) + '% interest added to each loan)' : ''}
<br /><br /> <br /><br />
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<Row collapse='true'> <Row collapse='true'>
@ -558,7 +584,7 @@ class Loans extends React.Component {
$: $:
<input onChange={this.handleInput} name='takeOut' type='number' <input onChange={this.handleInput} name='takeOut' type='number'
value={this.state.takeOut === 0 ? '' : this.state.takeOut} /> value={this.state.takeOut === 0 ? '' : this.state.takeOut} />
{'\u00A0'}K / ${(50000 - this.props.player.debt) / 1000}K {'\u00A0'}K / ${this.state.maxLoan / 1000}K
</div> </div>
</Col> </Col>
<Col width='4'> <Col width='4'>
@ -814,7 +840,6 @@ class Harvest extends React.Component {
} }
} }
// this.props.player.name === this.props.game.currentPlayer TODO
render() { render() {
let view; let view;
const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer; const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer;
@ -841,7 +866,7 @@ class Harvest extends React.Component {
case 'roll': case 'roll':
view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true} view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true}
showScreen={() => this.nextView('income')} showScreen={() => this.nextView('income')}
skip={true} skip={this.props.player.name === this.props.game.currentPlayer}
autoSkip={this.props.autoSkip === 'die'} autoSkip={this.props.autoSkip === 'die'}
showScreenDelay={2000} />); showScreenDelay={2000} />);
break; break;
@ -1088,7 +1113,7 @@ class Action extends React.Component {
<div> <div>
{this.props.player.name === this.props.game.currentPlayer ? 'You' : {this.props.player.name === this.props.game.currentPlayer ? 'You' :
this.props.game.currentPlayer} {this.props.ui.actionValue < 0 ? 'lost ' : 'gained '} this.props.game.currentPlayer} {this.props.ui.actionValue < 0 ? 'lost ' : 'gained '}
${Math.abs(this.props.ui.actionValue)}! ${formatMoney(Math.abs(this.props.ui.actionValue))}!
</div> </div>
</div> </div>
</div>); </div>);
@ -1104,7 +1129,6 @@ class Action extends React.Component {
break; break;
case 'harvest': case 'harvest':
view = (<Harvest rolled={this.props.ui.actionValue.rolled} view = (<Harvest rolled={this.props.ui.actionValue.rolled}
autoSkip={this.props.ui.autoSkip}
player={this.props.player} player={this.props.player}
game={this.props.game} game={this.props.game}
income={this.props.ui.actionValue.income} income={this.props.ui.actionValue.income}
@ -1250,13 +1274,14 @@ class AlertOverlay extends React.Component {
this.props.visible : true }; this.props.visible : true };
} }
hide = () => { hide = e => {
e.preventDefault();
this.setState({ visible: false }); this.setState({ visible: false });
this.props.hideHandler(); this.props.hideHandler();
} }
buttonClick = () => { buttonClick = e => {
this.hide(); this.hide(e);
this.props.handler(); this.props.handler();
} }
@ -1271,13 +1296,27 @@ class AlertOverlay extends React.Component {
<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> <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
<a href='#' onClick={this.hide}>close</a> <a onClick={this.hide}>close</a>
</div> </div>
</div> </div>
); );
} }
} }
class InfoBar extends React.Component {
render() {
if (this.props.players.length > 0) {
return (
<div className='info-bar'>
{(this.props.screen === SCREENS.action) ? '' : this.props.message}
</div>
);
} else {
return (<Fragment />);
}
}
}
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' };
@ -1329,12 +1368,29 @@ class BoardApp extends React.Component {
} }
iconOnClick = icon => { iconOnClick = icon => {
return () => this.showScreen(this.iconToScreen[icon]); return e => {
e.preventDefault();
this.showScreen(this.iconToScreen[icon]);
}
} }
render() { render() {
let alertOverlay; let alertOverlay;
if (!this.props.ui.alertHandled && this.state.screen !== SCREENS.action) { if (!this.props.ui.alertHandled && this.props.ui.alert === ALERTS.endOfGame) {
alertOverlay = (
<AlertOverlay visible={true}
buttonText='Close'
hideHandler={() => this.props.alert(false)}
handler={() => { return false; }}>
<Fragment>
<h1>Game Over!</h1>
{this.props.ui.alertContents.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) { switch (this.props.ui.alert) {
case ALERTS.beginTurn: case ALERTS.beginTurn:
alertOverlay = ( alertOverlay = (
@ -1384,20 +1440,23 @@ class BoardApp extends React.Component {
<div className='game-container'> <div className='game-container'>
{alertOverlay} {alertOverlay}
<Board spaces={this.props.spaces}> <Board spaces={this.props.spaces}>
<InfoBar message={this.props.ui.message}
screen={this.state.screen}
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, faAsterisk]
.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>
<a href='#' onClick={this.iconOnClick(icon.iconName)}> <a onClick={this.iconOnClick(icon.iconName)}>
<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, faAsterisk]
.map((icon, i) => .map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}> (<li key={i} className={this.iconClass(icon.iconName)}>
<a href='#' onClick={this.iconOnClick(icon.iconName)}> <a onClick={this.iconOnClick(icon.iconName)}>
<FontAwesomeIcon icon={icon} /></a></li>))} <FontAwesomeIcon icon={icon} /></a></li>))}
</ul> </ul>
<div className='tab-container'> <div className='tab-container'>
@ -1426,14 +1485,14 @@ class BoardApp extends React.Component {
</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 * 0.2) / 1000} 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))} max={Math.floor(Math.min(this.props.player.cash / 1000, this.props.ui.card.total / 1000))}
cash={(this.props.ui.card.total * 0.2) / 1000} /> cash={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000} />
</div> </div>
</Row> </Row>
</div> </div>
<div className={this.tabClass(SCREENS.loans)}> <div className={this.tabClass(SCREENS.loans)}>
<Loans player={this.props.player} /> <Loans player={this.props.player} game={this.props.game} />
</div> </div>
<div className={this.tabClass(SCREENS.farms)}> <div className={this.tabClass(SCREENS.farms)}>
<FarmsContainer player={this.props.player} <FarmsContainer player={this.props.player}
@ -1505,9 +1564,12 @@ class Card extends React.Component {
<div className='money'> <div className='money'>
$: $:
<input id='cash-input' type='number' <input id='cash-input' type='number'
step='1'
min='0'
max={Math.ceil(this.props.max)}
disabled={this.props.max < this.props.min} disabled={this.props.max < this.props.min}
onChange={this.handleInput} onChange={this.handleInput}
value={this.state.cash} /> value={Math.ceil(this.state.cash)} />
{'\u00A0'},000 {'\u00A0'},000
</div> </div>
</Col> </Col>

@ -35,3 +35,4 @@ export const MARK_ACTION_CHANGE_HANDLED = 'mark-action-change-handled'
export const ALERT = 'alert' 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'

@ -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 } from './actionTypes.js' AUTO_SKIP, MESSAGE } 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 } autoSkip, message }
function updateGame(update) { function updateGame(update) {
return { type: UPDATE_GAME, return { type: UPDATE_GAME,
@ -107,8 +107,8 @@ function markActionChangeHandled() {
return { type: MARK_ACTION_CHANGE_HANDLED }; return { type: MARK_ACTION_CHANGE_HANDLED };
} }
function alert(value) { function alert(value, contents) {
return { type: ALERT, value }; return { type: ALERT, value, contents };
} }
function alertHandled() { function alertHandled() {
@ -118,3 +118,7 @@ function alertHandled() {
function autoSkip(component) { function autoSkip(component) {
return { type: AUTO_SKIP, component }; return { type: AUTO_SKIP, component };
} }
function message(message) {
return { type: MESSAGE, message };
}

@ -24,7 +24,8 @@ 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 } from './actions.js' autoSkip, message, alertHandled } from './actions.js'
import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage, submitTradeDeny, submitTradeCancel, audit, handleMessage,
@ -47,10 +48,11 @@ function handleMessage(evt) {
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));
} else if (data.game.otherPlayers.length > 0 &&
data.game.currentPlayer !== store.getState().farm.game.currentPlayer) {
store.dispatch(alert(ALERTS.otherPlayersTurn));
} }
// 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) {
@ -93,10 +95,15 @@ function handleMessage(evt) {
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));
} else if (data.player.state === GAME_STATES.midTurn) {
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') {
store.dispatch(alert(ALERTS.endOfGame, data.results));
}
}); });
}; };
@ -158,10 +165,19 @@ function skip(component) {
sendCommand({ type: 'skip', component }); sendCommand({ type: 'skip', component });
} }
// TODO share with Board.jsx
// http://stackoverflow.com/questions/149055
function formatMoney(n) {
return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); }
function initialize(st, sc) { function initialize(st, sc) {
store = st; store = st;
sendCommand = sc; sendCommand = sc;
let lastAction = false,
lastAutoSkip = false,
rollMessage = '',
rollTimer = false;
const unsubscribe = store.subscribe( const unsubscribe = store.subscribe(
() => { () => {
const state = store.getState(); const state = store.getState();
@ -170,6 +186,73 @@ function initialize(st, sc) {
store.dispatch(markActionChangeHandled()); store.dispatch(markActionChangeHandled());
nextAction(); nextAction();
} }
if (state.farm.player.name !== state.farm.game.currentPlayer &&
lastAction !== state.farm.ui.action) {
lastAction = state.farm.ui.action;
switch (lastAction) {
case 'roll':
const roll = state.farm.ui.actionValue.to -
(state.farm.ui.actionValue.to < state.farm.ui.actionValue.from ?
state.farm.ui.actionValue.from - 49 : state.farm.ui.actionValue.from);
rollMessage = state.farm.game.currentPlayer + ' rolled a ' + roll;
rollTimer = setInterval(() => {
store.dispatch(message(rollMessage));
clearInterval(rollTimer);
}, 5000);
break;
case 'money':
store.dispatch(message(state.farm.game.currentPlayer +
(state.farm.ui.actionValue < 0 ? ' lost ' : ' gained ') +
'$' + formatMoney(Math.abs(state.farm.ui.actionValue))));
break;
case 'farmers-fate':
store.dispatch(message(state.farm.game.currentPlayer + ' drew a ' +
fateCard));
break;
case 'ff-uncle-bert':
store.dispatch(message(state.farm.game.currentPlayer + ' drew a ' +
fateCard));
break;
case 'otb':
store.dispatch(message(state.farm.game.currentPlayer + ' drew an ' +
itemCard));
break;
case 'harvest':
store.dispatch(message(state.farm.game.currentPlayer +
' is harvesting ' +
state.farm.ui.actionValue.acres + ' ' +
(state.farm.ui.actionValue.crop === 'cows' ? ' head of cow' : ' acres') +
' of ' + state.farm.ui.actionValue.crop));
break;
case false:
store.dispatch(message(''));
break;
}
}
if (state.farm.player.name !== state.farm.game.currentPlayer &&
lastAutoSkip !== state.farm.ui.autoSkip) {
lastAutoSkip = state.farm.ui.autoSkip;
switch (lastAutoSkip) {
case 'die':
if (lastAction !== 'harvest') {
clearInterval(rollTimer);
store.dispatch(message(rollMessage));
}
break;
case 'harvest|income':
store.dispatch(message(state.farm.game.currentPlayer +
' rolled a ' +
state.farm.ui.actionValue.rolled +
' and earned $' +
formatMoney(state.farm.ui.actionValue.income)));
break;
case 'harvest|expense-value':
store.dispatch(message(state.farm.game.currentPlayer +
' had operating expense of $' +
formatMoney(Math.abs(state.farm.ui.actionValue.operatingExpenseValue))));
break;
}
}
}); });
// mpDims.mouseX = e.clientX // mpDims.mouseX = e.clientX

@ -21,7 +21,7 @@ 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 } from './actionTypes.js' AUTO_SKIP, MESSAGE } 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'
@ -92,14 +92,18 @@ const initialState = {
space: 0, space: 0,
trade: {} trade: {}
}, },
game: { auditThreshold: 250000, game: { calledAudit: false,
calledAudit: false,
currentPlayer: '', currentPlayer: '',
messages: [], messages: [],
otherPlayers: [], otherPlayers: [],
state: GAME_STATES.preTurn, state: GAME_STATES.preTurn,
turn: 0, turn: 0,
oldMessages: [] }, oldMessages: [],
settings: { downPayment: 0.2,
loanInterest: 0.2,
maxDebt: 50000,
auditThreshold: 250000 }
},
ui: { card: { type: 'no-card', contents: '', total: 0 }, ui: { card: { type: 'no-card', contents: '', total: 0 },
cards: [], cards: [],
action: false, action: false,
@ -107,7 +111,9 @@ const initialState = {
nextAction: false, nextAction: false,
nextActionValue: null, nextActionValue: null,
actionChangeHandled: true, actionChangeHandled: true,
message: '',
alert: false, alert: false,
alertContents: false,
autoSkip: false, autoSkip: false,
alertHandled: false }, alertHandled: false },
spaces: spaces, spaces: spaces,
@ -175,11 +181,14 @@ export default function(state = initialState, action) {
case ALERT: case ALERT:
return { ...state, ui: { ...state.ui, return { ...state, ui: { ...state.ui,
alert: action.value, alert: action.value,
alertContents: action.contents,
alertHandled: action.value === false ? true : false }}; alertHandled: action.value === false ? true : false }};
case ALERT_HANDLED: case ALERT_HANDLED:
return { ...state, ui: { ...state.ui, alertHandled: true }}; return { ...state, ui: { ...state.ui, alertHandled: true }};
case AUTO_SKIP: case AUTO_SKIP:
return { ...state, ui: { ...state.ui, autoSkip: action.component }}; return { ...state, ui: { ...state.ui, autoSkip: action.component }};
case MESSAGE:
return { ...state, ui: { ...state.ui, message: action.message }};
default: default:
return state; return state;
} }

@ -55,6 +55,7 @@ class JoinGame extends React.Component {
} }
handleJoinAsExisting = e => { handleJoinAsExisting = e => {
e.preventDefault();
this.props.startOrJoinGame({ type: 'join-as-existing', this.props.startOrJoinGame({ type: 'join-as-existing',
playerName: e.target.text, playerName: e.target.text,
gameId: this.state.game.id, gameId: this.state.game.id,
@ -78,7 +79,9 @@ class JoinGame extends React.Component {
{this.props.games {this.props.games
.map((g, i) => .map((g, i) =>
(<li key={i}> (<li key={i}>
<a href='#' onClick={() => this.handleClickGame(g)}>{g.name}</a> <a onClick={e => {
e.preventDefault();
this.handleClickGame(g); }}>{g.name}</a>
</li>))} </li>))}
</ul>) </ul>)
: ( : (
@ -88,7 +91,7 @@ class JoinGame extends React.Component {
<ul> <ul>
{this.state.game.players.map((p, i) => {this.state.game.players.map((p, i) =>
(<li key={i}> (<li key={i}>
<a href='#' onClick={this.handleJoinAsExisting}> <a onClick={this.handleJoinAsExisting}>
{p} {p}
</a> </a>
</li>))} </li>))}

@ -25,16 +25,44 @@ import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js' import { start } from '../app/actions.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons' import { faArrowCircleLeft, faCog } from '@fortawesome/free-solid-svg-icons'
class InputRow extends React.Component {
render() {
return (
<Row>
<Col width='12'>
<label>
{this.props.label}
<input type='number'
min={this.props.min}
max={this.props.max}
step={this.props.step}
name={this.props.name}
value={this.props.value}
onChange={this.props.onChange} />
</label>
</Col>
</Row>
);
}
}
class NewGame extends React.Component { class NewGame extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
showSettings: false,
playerName: '', playerName: '',
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,
loanInterest: 0.2,
maxDebt: 50000,
auditThreshold: 250000,
startingCash: 5000,
startingDebt: 5000
}; };
} }
@ -54,21 +82,50 @@ class NewGame extends React.Component {
} }
handleBack = e => { handleBack = e => {
e.preventDefault();
this.props.start(); this.props.start();
} }
toggleSettings = e => {
e.preventDefault();
this.setState({ showSettings: !this.state.showSettings });
}
render() { render() {
let playerNameInput; let playerNameInput,
return ( titleBar = !this.props.hideBack ? (
<GroupBox title={!this.props.hideBack ? (
<Fragment> <Fragment>
<a href="#" onClick={this.handleBack}> <a onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} /> <FontAwesomeIcon icon={faArrowCircleLeft} />
</a> </a>
{this.props.title} {this.props.title}
</Fragment> </Fragment>
) : this.props.title}> ) : this.props.title,
colors = this.props.colors.map(c => (
<label key={c} className={'player player-selectable player-' + c + (this.state.checkedColor === c ? ' player-selected' : '')}>
<input type='checkbox'
checked={this.state.checkedColor === c}
onChange={this.handleInputChange}
name={c} />
</label>
)
),
gameName = this.props.showGameName && (
<Row>
<Col width='12'>
<label>Game Name
<input type='text' name='gameName' value={this.state.gameName}
onChange={this.handleInputChange} />
</label>
</Col>
</Row>
),
settingsClass = this.state.showSettings ? '' : 'hidden',
mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return (
<GroupBox title={titleBar}>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className={mainScreenClass}>
<Row> <Row>
<Col width='12'> <Col width='12'>
<label>Your Name <label>Your Name
@ -81,31 +138,62 @@ class NewGame extends React.Component {
<Row> <Row>
<Col width='12'> <Col width='12'>
<label>Your Color</label> <label>Your Color</label>
{this.props.colors {colors}
.map(c =>
(<label key={c} className={'player player-selectable player-' + c + (this.state.checkedColor === c ? ' player-selected' : '')}>
<input type='checkbox'
checked={this.state.checkedColor === c}
onChange={this.handleInputChange}
name={c} />
</label>))
}
<br /><br /> <br /><br />
</Col> </Col>
</Row> </Row>
{this.props.showGameName && ( {gameName}
<Row> </div>
<Col width='12'> <div className={settingsClass}>
<label>Game Name <InputRow label='Down Payment'
<input type='text' name='gameName' value={this.state.gameName} name='downPayment'
min={0}
max={1}
step={0.1}
value={this.state.downPayment}
onChange={this.handleInputChange} /> onChange={this.handleInputChange} />
</label> <InputRow label='Loan Interest'
</Col> name='loanInterest'
</Row> min={0}
)} max={1}
step={0.1}
value={this.state.loanInterest}
onChange={this.handleInputChange} />
<InputRow label='Maximum Debt'
name='maxDebt'
min={0}
step={5000}
value={this.state.maxDebt}
onChange={this.handleInputChange} />
<InputRow label='Audit Threshold'
name='auditThreshold'
min={0}
step={25000}
value={this.state.auditThreshold}
onChange={this.handleInputChange} />
<InputRow label='Starting Cash'
name='startingCash'
min={0}
step={1000}
value={this.state.startingCash}
onChange={this.handleInputChange} />
<InputRow label='Starting Debt'
name='startingDebt'
min={0}
step={1000}
value={this.state.startingDebt}
onChange={this.handleInputChange} />
</div>
<Row> <Row>
<Col width='12'> <Col width='12'>
<div className='new-game-submit-container'>
<Button type='submit'>{this.props.button} Game</Button> <Button type='submit'>{this.props.button} Game</Button>
{this.props.showGameName ? (
<a onClick={this.toggleSettings}>
<FontAwesomeIcon icon={faCog} size='lg' />
</a>
) : (<Fragment />)}
</div>
</Col> </Col>
</Row> </Row>
</form> </form>

@ -31,4 +31,5 @@ export const rootId = 'initial-element';
export const messagePanelId = 'message-panel'; 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',
raiseMoney: 'raise-money' } raiseMoney: 'raise-money' }

@ -91,11 +91,21 @@
(colors initform: '() accessor: game-colors) (colors initform: '() accessor: game-colors)
(last-updated initform: 0 accessor: game-last-updated) (last-updated initform: 0 accessor: game-last-updated)
(called-audit initform: #f accessor: game-called-audit) (called-audit initform: #f accessor: game-called-audit)
(audit-threshold initform: 250000 accessor: game-audit-threshold)
(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)
(actions initform: '() accessor: game-actions))) (actions initform: '() accessor: game-actions)
(settings initform:
'((down-payment . 0.2)
(loan-interest . 0.2)
(max-debt . 50000)
(audit-threshold . 250000)
(starting-cash . 5000)
(starting-debt . 5000))
accessor: game-settings)))
(define (game-setting setting game)
(alist-ref setting (game-settings game)))
(define-class <app> () (define-class <app> ()
((games initform: '() accessor: app-games) ((games initform: '() accessor: app-games)
@ -205,7 +215,10 @@
color)) color))
(define (add-player-to-game game color name) (define (add-player-to-game game color name)
(let ((player (make <player> 'cash 5000 'color color (let ((player (make <player>
'cash (game-setting 'starting-cash game)
'debt (game-setting 'starting-debt game)
'color color
'name name 'name name
'state (if (= (length (game-players game)) 0) 'state (if (= (length (game-players game)) 0)
'pre-turn 'turn-ended)))) 'pre-turn 'turn-ended))))
@ -322,9 +335,12 @@
(calledAudit . ,(if (game-called-audit g) (calledAudit . ,(if (game-called-audit g)
(player-name (game-called-audit g)) (player-name (game-called-audit g))
#f)) #f))
(auditThreshold . ,(game-audit-threshold g))
(state . ,(symbol->string (game-state g))) (state . ,(symbol->string (game-state g)))
(turn . ,(game-turn g)))))) (turn . ,(game-turn g))
(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))))))))
(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
@ -361,11 +377,11 @@
((> cash-value (player-cash player)) ((> cash-value (player-cash player))
(push-message player (conc "Could not buy " unnormalized-crop ". Not enough cash.")) (push-message player (conc "Could not buy " unnormalized-crop ". Not enough cash."))
#f) #f)
((< cash-value (* total-cost 0.2)) ((< cash-value (* total-cost (game-setting 'down-payment game)))
(push-message player (push-message player
(conc "Could not buy " unnormalized-crop ". Not enough down payment.")) (conc "Could not buy " unnormalized-crop ". Not enough down payment."))
#f) #f)
((> (- total-cost cash-value) (- 50000 (player-debt player))) ((> (- total-cost cash-value) (- (game-setting 'max-debt game) (player-debt player)))
(push-message player (push-message player
(conc "Could not buy " unnormalized-crop ". Not enough credit.")) (conc "Could not buy " unnormalized-crop ". Not enough credit."))
#f) #f)
@ -611,16 +627,19 @@
(* (player-debt player) -1))) (* (player-debt player) -1)))
(define (do-end-of-game game) (define (do-end-of-game game)
(push-message #f "Game over!") (message-players!
(for-each (lambda (p i) game
(push-message #f #f
(conc i ". " (player-name p) " with $" `((results
(player-net-worth p)))) . ,(list->vector
(map (lambda (p i)
(conc i ". " (player-name p) " with $" (player-net-worth p)))
(sort (game-players game) (sort (game-players game)
(lambda (p1 p2) (lambda (p1 p2)
(> (player-net-worth p1) (> (player-net-worth p1)
(player-net-worth p2)))) (player-net-worth p2))))
(iota (length (game-players game)) 1))) (iota (length (game-players game)) 1)))))
type: "end-of-game"))
(define (create-ws-response player event misc) (define (create-ws-response player event misc)
(append `((event . ,event) ,@misc) (append `((event . ,event) ,@misc)
@ -646,6 +665,26 @@
(define *next-roll* #f) (define *next-roll* #f)
(define (->number x default)
(if (number? x)
x
(if (string? x)
(or (string->number x)
default)
default)))
(define (->pct x default)
(let ((n (->number x default)))
(if (or (> n 1) (< n 0))
default
n)))
(define (->i x default)
(let ((n (inexact->exact (floor (->number x default)))))
(if (< n 0)
default
(- n (modulo n 1000)))))
(define (process-message player game type msg) (define (process-message player game type msg)
(when game (when game
(set! (game-messages game) '()) (set! (game-messages game) '())
@ -842,19 +881,25 @@
(let ((amount (* (alist-ref 'amount msg) 1000))) (let ((amount (* (alist-ref 'amount msg) 1000)))
(if (> amount 0) (if (> amount 0)
;; taking out loan ;; taking out loan
(if (> (+ (player-debt player) amount) 50000) (if (> (+ (player-debt player)
(farming-round (+ amount (* amount (game-setting 'loan-interest game)))))
(game-setting 'max-debt game))
(push-message player "Exceeds max loan.") (push-message player "Exceeds max loan.")
(begin (set! (player-cash player) (+ (player-cash player) amount)) (begin (set! (player-cash player) (+ (player-cash player) amount))
(set! (player-debt player) (+ (player-debt player) amount)) (set! (player-debt player) (+ (player-debt player)
(farming-round
(+ amount (* amount (game-setting 'loan-interest game))))))
(push-message player (conc "Loan of $" amount " taken out.")))) (push-message player (conc "Loan of $" amount " taken out."))))
;; repaying loan ;; repaying loan
(cond ((> amount (player-cash player)) (cond ((> (abs amount) (player-cash player))
(push-message player "Not enough cash to repay loan.")) (push-message player "Not enough cash to repay loan."))
((> amount (player-debt player))
(push-message player "Repayment exceeds total loan amount."))
(else (else
(set! (player-cash player) (+ (player-cash player) amount)) (set! (player-cash player) (+ (player-cash player) amount))
(set! (player-debt player) (+ (player-debt player) amount)) (set! (player-debt player) (+ (player-debt player) amount))
(when (< (player-debt player) 0)
(set! (player-cash player) (+ (player-cash player)
(abs (player-debt player))))
(set! (player-debt player) 0))
(push-message player (conc "Loan of $" (abs amount) " repayed.")))) (push-message player (conc "Loan of $" (abs amount) " repayed."))))
)) ))
(create-ws-response player "loan" '())) (create-ws-response player "loan" '()))
@ -908,7 +953,17 @@
'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)
'settings
`((down-payment . ,(->pct (alist-ref 'downPayment msg) 0.2))
(loan-interest . ,(->pct (alist-ref 'loanInterest msg) 0.2))
(max-debt . ,(->i (alist-ref 'maxDebt msg) 50000))
(audit-threshold . ,(->i (alist-ref 'auditThreshold msg)
250000))
(starting-cash . ,(->i (alist-ref 'startingCash msg)
5000))
(starting-debt . ,(->i (alist-ref 'startingDebt msg)
5000)))))
(player (add-player-to-game game (player (add-player-to-game game
color color
(alist-ref 'playerName msg)))) (alist-ref 'playerName msg))))
@ -930,6 +985,7 @@
(session-set! (sid) 'player player) (session-set! (sid) 'player player)
(session-set! (sid) 'game game) (session-set! (sid) 'game game)
(set-startup-otbs game player 2) (set-startup-otbs game player 2)
(message-players! game player '() type: "update")
(create-start-response "new-game-started"))) (create-start-response "new-game-started")))
((string=? type "join-as-existing") ((string=? type "join-as-existing")
(let* ((name (alist-ref 'gameName msg)) (let* ((name (alist-ref 'gameName msg))
@ -993,8 +1049,8 @@
(set! game (session-ref (sid) 'game))) (set! game (session-ref (sid) 'game)))
(when (not player) (when (not player)
(set! player (session-ref (sid) 'player))) (set! player (session-ref (sid) 'player)))
(when (< (player-last-updated player) ;; when (< (player-last-updated player)
(game-last-updated game)) ;; (game-last-updated game))
(handle-exceptions (handle-exceptions
exn exn
(send-message (send-message
@ -1015,7 +1071,7 @@
(create-ws-response player (create-ws-response player
(alist-ref 'type msg) (alist-ref 'type msg)
(alist-ref 'value msg)) (alist-ref 'value msg))
))))) ))))
(loop (mailbox-receive! (player-mailbox player)))))))) (loop (mailbox-receive! (player-mailbox player))))))))
(define (otb-spec->otb-cards spec id) (define (otb-spec->otb-cards spec id)

@ -762,3 +762,21 @@ $intro-time: 6s;
.fa-arrow-circle-left { .fa-arrow-circle-left {
cursor: pointer; cursor: pointer;
margin-right: 0.5rem; } margin-right: 0.5rem; }
.info-bar {
@include breakpoint(landscape) {
position: absolute; }
text-align: center;
padding: $tab-margin;
height: 2rem; }
.new-game-submit-container {
display: flex;
align-items: center;
justify-content: space-between;
.button {
margin: 0; }
}
.hidden {
display: none; }

Loading…
Cancel
Save