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;
const player = this.props.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>) : '';
if (player.state === GAME_STATES.preTurn) {
view = (
@ -505,7 +505,22 @@ class Messages extends React.Component {
class Loans extends React.Component {
constructor(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 => {
@ -520,10 +535,10 @@ class Loans extends React.Component {
} else {
takeOut = value * 1000;
}
this.setState({ repay: Math.max(0, Math.floor(
Math.min(repay, this.props.player.debt, this.props.player.cash) / 1000)),
this.setState({ repay: Math.max(0, Math.ceil(
Math.min(repay, this.props.player.debt + 900, this.props.player.cash) / 1000)),
takeOut: Math.max(0, Math.floor(
Math.min(takeOut, 50000 - this.props.player.debt) / 1000)) });
Math.min(takeOut, this.state.maxLoan) / 1000)) });
}
handleSubmit = e => {
@ -532,11 +547,22 @@ class Loans extends React.Component {
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 () {
return (
<GroupBox title={'Loans'}>
<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 />
<form onSubmit={this.handleSubmit}>
<Row collapse='true'>
@ -558,7 +584,7 @@ class Loans extends React.Component {
$:
<input onChange={this.handleInput} name='takeOut' type='number'
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>
</Col>
<Col width='4'>
@ -814,7 +840,6 @@ class Harvest extends React.Component {
}
}
// this.props.player.name === this.props.game.currentPlayer TODO
render() {
let view;
const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer;
@ -841,7 +866,7 @@ class Harvest extends React.Component {
case 'roll':
view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true}
showScreen={() => this.nextView('income')}
skip={true}
skip={this.props.player.name === this.props.game.currentPlayer}
autoSkip={this.props.autoSkip === 'die'}
showScreenDelay={2000} />);
break;
@ -1088,7 +1113,7 @@ class Action extends React.Component {
<div>
{this.props.player.name === this.props.game.currentPlayer ? 'You' :
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>);
@ -1104,7 +1129,6 @@ class Action extends React.Component {
break;
case 'harvest':
view = (<Harvest rolled={this.props.ui.actionValue.rolled}
autoSkip={this.props.ui.autoSkip}
player={this.props.player}
game={this.props.game}
income={this.props.ui.actionValue.income}
@ -1250,13 +1274,14 @@ class AlertOverlay extends React.Component {
this.props.visible : true };
}
hide = () => {
hide = e => {
e.preventDefault();
this.setState({ visible: false });
this.props.hideHandler();
}
buttonClick = () => {
this.hide();
buttonClick = e => {
this.hide(e);
this.props.handler();
}
@ -1271,13 +1296,27 @@ class AlertOverlay extends React.Component {
<br />
<Button onClick={this.buttonClick}>{this.props.buttonText}</Button>
<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>
);
}
}
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',
cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action' };
@ -1329,12 +1368,29 @@ class BoardApp extends React.Component {
}
iconOnClick = icon => {
return () => this.showScreen(this.iconToScreen[icon]);
return e => {
e.preventDefault();
this.showScreen(this.iconToScreen[icon]);
}
}
render() {
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) {
case ALERTS.beginTurn:
alertOverlay = (
@ -1384,20 +1440,23 @@ class BoardApp extends React.Component {
<div className='game-container'>
{alertOverlay}
<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'>
<ul className='horizontal menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk]
.map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}>
<div></div>
<a href='#' onClick={this.iconOnClick(icon.iconName)}>
<a onClick={this.iconOnClick(icon.iconName)}>
<FontAwesomeIcon icon={icon} /></a></li>))}
</ul>
<ul className='vertical menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk]
.map((icon, i) =>
(<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>))}
</ul>
<div className='tab-container'>
@ -1426,14 +1485,14 @@ class BoardApp extends React.Component {
</div>
<div className='cell medium-auto'>
<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))}
cash={(this.props.ui.card.total * 0.2) / 1000} />
cash={(this.props.ui.card.total * this.props.game.settings.downPayment) / 1000} />
</div>
</Row>
</div>
<div className={this.tabClass(SCREENS.loans)}>
<Loans player={this.props.player} />
<Loans player={this.props.player} game={this.props.game} />
</div>
<div className={this.tabClass(SCREENS.farms)}>
<FarmsContainer player={this.props.player}
@ -1505,9 +1564,12 @@ class Card extends React.Component {
<div className='money'>
$:
<input id='cash-input' type='number'
step='1'
min='0'
max={Math.ceil(this.props.max)}
disabled={this.props.max < this.props.min}
onChange={this.handleInput}
value={this.state.cash} />
value={Math.ceil(this.state.cash)} />
{'\u00A0'},000
</div>
</Col>

@ -35,3 +35,4 @@ export const MARK_ACTION_CHANGE_HANDLED = 'mark-action-change-handled'
export const ALERT = 'alert'
export const ALERT_HANDLED = 'alert-handled'
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,
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 } from './actionTypes.js'
AUTO_SKIP, MESSAGE } from './actionTypes.js'
export { updateGame, updatePlayer, gameState, setSelectedCard, setCards,
spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace,
mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction,
markActionChangeHandled, nextUIActionSilent, alert, alertHandled,
autoSkip }
autoSkip, message }
function updateGame(update) {
return { type: UPDATE_GAME,
@ -107,8 +107,8 @@ function markActionChangeHandled() {
return { type: MARK_ACTION_CHANGE_HANDLED };
}
function alert(value) {
return { type: ALERT, value };
function alert(value, contents) {
return { type: ALERT, value, contents };
}
function alertHandled() {
@ -118,3 +118,7 @@ function alertHandled() {
function autoSkip(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,
movePlayer, setOldMessages, markActionChangeHandled,
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,
submitTradeDeny, submitTradeCancel, audit, handleMessage,
@ -47,10 +48,11 @@ function handleMessage(evt) {
data.game.otherPlayers.length > 0 &&
store.getState().farm.player.state !== GAME_STATES.preTurn) {
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
if (data.event === 'update' &&
data.game.currentPlayer !== store.getState().farm.game.currentPlayer) {
@ -93,10 +95,15 @@ function handleMessage(evt) {
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());
}
if (data.event === 'auto-skip') {
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 });
}
// 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) {
store = st;
sendCommand = sc;
let lastAction = false,
lastAutoSkip = false,
rollMessage = '',
rollTimer = false;
const unsubscribe = store.subscribe(
() => {
const state = store.getState();
@ -170,6 +186,73 @@ function initialize(st, sc) {
store.dispatch(markActionChangeHandled());
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

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

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

@ -25,16 +25,44 @@ import { startOrJoinGame } from '../start/actions.js'
import { start } from '../app/actions.js'
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 {
constructor(props) {
super(props);
this.state = {
showSettings: false,
playerName: '',
checkedColor: props.colors[0],
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,46 +82,35 @@ class NewGame extends React.Component {
}
handleBack = e => {
e.preventDefault();
this.props.start();
}
toggleSettings = e => {
e.preventDefault();
this.setState({ showSettings: !this.state.showSettings });
}
render() {
let playerNameInput;
return (
<GroupBox title={!this.props.hideBack ? (
let playerNameInput,
titleBar = !this.props.hideBack ? (
<Fragment>
<a href="#" onClick={this.handleBack}>
<a onClick={this.handleBack}>
<FontAwesomeIcon icon={faArrowCircleLeft} />
</a>
{this.props.title}
</Fragment>
) : this.props.title}>
<form onSubmit={this.handleSubmit}>
<Row>
<Col width='12'>
<label>Your Name
<input type='text' name='playerName'
value={this.state.playerName}
onChange={this.handleInputChange} />
</label>
</Col>
</Row>
<Row>
<Col width='12'>
<label>Your Color</label>
{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>))
}
<br /><br />
</Col>
</Row>
{this.props.showGameName && (
) : 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
@ -102,10 +119,81 @@ class NewGame extends React.Component {
</label>
</Col>
</Row>
)}
),
settingsClass = this.state.showSettings ? '' : 'hidden',
mainScreenClass = !this.state.showSettings ? '' : 'hidden';
return (
<GroupBox title={titleBar}>
<form onSubmit={this.handleSubmit}>
<div className={mainScreenClass}>
<Row>
<Col width='12'>
<label>Your Name
<input type='text' name='playerName'
value={this.state.playerName}
onChange={this.handleInputChange} />
</label>
</Col>
</Row>
<Row>
<Col width='12'>
<label>Your Color</label>
{colors}
<br /><br />
</Col>
</Row>
{gameName}
</div>
<div className={settingsClass}>
<InputRow label='Down Payment'
name='downPayment'
min={0}
max={1}
step={0.1}
value={this.state.downPayment}
onChange={this.handleInputChange} />
<InputRow label='Loan Interest'
name='loanInterest'
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>
<Col width='12'>
<Button type='submit'>{this.props.button} Game</Button>
<div className='new-game-submit-container'>
<Button type='submit'>{this.props.button} Game</Button>
{this.props.showGameName ? (
<a onClick={this.toggleSettings}>
<FontAwesomeIcon icon={faCog} size='lg' />
</a>
) : (<Fragment />)}
</div>
</Col>
</Row>
</form>

@ -31,4 +31,5 @@ export const rootId = 'initial-element';
export const messagePanelId = 'message-panel';
export const ALERTS = { beginTurn: 'begin-turn',
otherPlayersTurn: 'other-players-turn',
endOfGame: 'end-of-game',
raiseMoney: 'raise-money' }

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

@ -762,3 +762,21 @@ $intro-time: 6s;
.fa-arrow-circle-left {
cursor: pointer;
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