Adding multiplayer info bar, game settings, cleaning up loans.
This commit is contained in:
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user