You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
farm/src/components/farm/Board.jsx

2475 lines
107 KiB
JavaScript

// Copyright 2020 Thomas Hintz
//
// This file is part of the Alpha Centauri Farming project.
//
// The Alpha Centauri Farming project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// The Alpha Centauri Farming project is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Alpha Centauri Farming project. If not, see
// <https://www.gnu.org/licenses/>.
import CornImg from './../../../assets/img/corn.svg'
import FruitImg from './../../../assets/img/fruit.svg'
import CowImg from './../../../assets/img/cow.svg'
import HayImg from './../../../assets/img/hay.svg'
import WheatImg from './../../../assets/img/wheat.svg'
import TractorImg from './../../../assets/img/tractor-icon.svg'
import CakeImg from './../../../assets/img/cake.svg'
import TractorFullImg from './../../../assets/img/tractor-with-spikes.svg'
import HarvesterImg from './../../../assets/img/harvester.svg'
import VolcanoImg from './../../../assets/img/volcano2.gif'
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser, faUsers, faTractor, faWindowRestore,
faDollarSign, faTimes, faAsterisk, faExchangeAlt,
faInfoCircle, faArrowUp, faArrowDown, faAward,
faTimesCircle, faBan } from '@fortawesome/free-solid-svg-icons'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import SpaceNode from './SpaceNode.jsx'
import Tractor from '../tractor/Tractor.jsx'
import { GAME_STATES, ALERTS } from '../../constants.js'
import { itemCard, itemCardShort, fateCard, ridgeNames } from 'game.js'
import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
nextUIAction, alert, alertHandled, setCardError,
setMovingSkip } from './actions.js'
import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip, endAiTurn, startGame, readyToStart,
leaveGame, kickPlayer } from './interface.js'
function netWorth(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
(player.assets.fruit * 5000) +
(player.assets.cows * 500) +
((player.assets.harvester + player.assets.tractor) * 10000) +
player.displayCash - player.debt;
}
function assetsValue(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
(player.assets.fruit * 5000) +
(player.assets.cows * 500) +
((player.assets.harvester + player.assets.tractor) * 10000);
}
function getElementValue(id) {
return document.getElementById(id).value;
}
function getString(id) {
return getElementValue(id);
}
function getInt(id) {
const i = parseInt(getElementValue(id));
return isNaN(i) ? 0 : i;
}
function getChecked(id) {
return document.getElementById(id).checked;
}
function getOption(id) {
let options = document.getElementById(id).selectedOptions;
if (options.length > 0) {
return options[0].value;
} else {
return false;
}
}
function submitTrade() {
trade({ hay: getInt('trade-hay'), grain: getInt('trade-grain'),
fruit: getInt('trade-fruit'), cows: getInt('trade-cows'),
harvester: getInt('trade-harvesters'),
tractor: getInt('trade-tractors'),
ridge1: getChecked('trade-ridge1'),
ridge2: getChecked('trade-ridge2'),
ridge3: getChecked('trade-ridge3'),
ridge4: getChecked('trade-ridge4'),
player: getOption('trade-player'),
money: getInt('trade-money'), cards: getString('trade-cards') });
}
function tradeString(player, invert) {
var r = '';
let mult = invert ? -1 : 1;
for (var k in player.trade) {
if (k !== 'player' && k !== 'originator' && k !== 'cards' && k !== 'trade-number') {
r += (player.trade[k] === true ? '' : player.trade[k] * mult) + ' ' + k + ', ';
} else if (k === 'cards') {
r += 'cards: ' + player.trade[k] + ', ';
}
}
return r.slice(0, -2); // remove last ", "
}
class PlayerTradeProposed extends React.Component {
render () {
if (this.props.player.trade.error) {
return (<p><b>ERROR:</b> {this.props.player.trade.error}</p>);
} else if (this.props.player.trade.player === undefined) {
return (<p>You GAIN something if it is POSITIVE and you GIVE something if it is NEGATIVE.</p>);
} else if (this.props.player.trade.originator === this.props.player.name) {
return (<p>You proposed a trade with <b>{this.props.player.trade.player}</b> for
{'\u00A0'}<b>{tradeString(this.props.player, false)}</b>.</p>);
} else {
return (<p><b>{this.props.player.trade.originator}</b> proposed a trade for
{'\u00A0'}<b>{tradeString(this.props.player, true)}</b>.</p>);
}
}
}
function tradeFormSubmit(e, player) {
e.preventDefault();
if (player.trade.player === undefined) {
submitTrade();
} else if (player.trade.originator === player.name) {
submitTradeCancel();
} else {
submitTradeAccept();
}
return false;
}
class PlayerTradeButton extends React.Component {
tradeDeny = e => {
e.preventDefault();
submitTradeDeny();
}
render() {
var text = '';
var receiver = false;
if (this.props.player.trade.player === undefined) {
text = 'Propose';
} else if (this.props.player.trade.originator === this.props.player.name) {
text = 'Cancel';
} else {
text = 'Accept';
receiver = true;
}
if (!receiver) {
return (<Button className='tiny' type='submit'>{text} Trade</Button>);
} else {
return (<Fragment>
<Button className='tiny' type='submit'>{text} Trade</Button>
<Button className='tiny' onClick={this.tradeDeny}>
Deny Trade
</Button>
</Fragment>);
}
}
}
class ResourceUnit extends React.Component {
render () {
const hslString = 'hsl(' + this.props.h + ', ' + this.props.s;
return (
<div className={'resource-unit ' + (this.props.className ? this.props.className : '')}
title={this.props.amount + ' ' + this.props.label}
style={{backgroundColor: hslString + '%, 85%)',
borderTop: '3px solid ' + hslString + '%, 55%)'}}>
{this.props.img ? (<img src={this.props.img} />) : (<Fragment />)}
{this.props.children}
{this.props.doubled ? (
<div className="resource-doubled"
title={this.props.doubled + ' harvests are doubled this year!'}
>
<FontAwesomeIcon icon={faAward} />
</div>
) : (<></>)}
</div>
);
}
}
class PlayerTradeResources extends React.Component {
render () {
let player = this.props.player;
return (
<form onSubmit={e => tradeFormSubmit(e, player)}>
<div className='player-trade-resources'>
<ResourceUnit img={HayImg} h='120' s='100' label='acres of Hay' amount={''}>
<input type='number' id='trade-hay' defaultValue='0' />
</ResourceUnit> {' '}
<ResourceUnit img={WheatImg} h='41' s='100' label='acres of Grain' amount={''}>
<input type='number' id='trade-grain' defaultValue='0' />
</ResourceUnit> {' '}
<ResourceUnit img={FruitImg} h='0' s='100' label='acres of Fruit' amount={''}>
<input type='number' id='trade-fruit' defaultValue='0' />
</ResourceUnit> {' '}
<ResourceUnit img={CowImg} h='0' s='59' label='head of Cows' amount={''}>
<input type='number' id='trade-cows' defaultValue='0' />
</ResourceUnit> {' '}
<ResourceUnit img={HarvesterImg} h='240' s='100' label='Harvesters' amount={''}>
<input type='number' id='trade-harvesters' defaultValue='0' />
</ResourceUnit> {' '}
<ResourceUnit img={TractorImg} h='240' s='100' label='Tractors' amount={''}>
<input type='number' id='trade-tractors' defaultValue='0' />
</ResourceUnit>
<br />
<b>Ridges</b>: <b>{ridgeNames[0][0]}</b>: <input type='checkbox' id='trade-ridge1' />{'\u00A0'}
<b>R</b>: <input type='checkbox' id='trade-ridge2' />{'\u00A0'}
<b>C</b>: <input type='checkbox' id='trade-ridge3' />{'\u00A0'}
<b>T</b>: <input type='checkbox' id='trade-ridge4' />{'\u00A0'}
<br /><br />
<Row>
<Col width='4 column-no-padding'>
<label htmlFor='trade-player'><b>Player</b>:</label>
<select id='trade-player'>
{this.props.otherPlayers.map(p => (
<option key={p.player.name} value={p.player.name}>{p.player.name}</option>
))}
</select>
</Col>
<Col width='4 column-no-padding'>
<label htmlFor='trade-money'><b>Money</b>: </label>
<input type='number' id='trade-money' defaultValue='0' />
</Col>
<Col width='4 column-no-padding'>
<label htmlFor='trade-cards'><b>Cards</b>: </label>
<input type='text' id='trade-cards' placeholder="12 46" />
</Col>
</Row>
<PlayerTradeProposed player={this.props.player} />
<PlayerTradeButton player={this.props.player} />
</div>
</form>);
}
}
class PlayerResources extends React.Component {
render () {
const player = this.props.player;
return (
<div className='resource-unit-container'>
<ResourceUnit img={HayImg} h='120' s='100' label='acres of Hay'
amount={player.assets.hay}
doubled={player.hayDoubled && 'Hay'}
>
{player.assets.hay}
</ResourceUnit> {' '}
<ResourceUnit img={WheatImg} h='41' s='100' label='acres of Grain'
amount={player.assets.grain}
doubled={player.cornDoubled && 'Corn'}
>
{player.assets.grain}
</ResourceUnit> {' '}
<ResourceUnit img={FruitImg} h='0' s='100' label='acres of Fruit'
amount={player.assets.fruit}>
{player.assets.fruit}
</ResourceUnit> {' '}
<ResourceUnit img={CowImg} h='0' s='59' label='head of Cows'
amount={player.assets.cows}>
{player.assets.cows}
</ResourceUnit> {' '}
<ResourceUnit img={HarvesterImg} h='240' s='100' label='Harvesters'
amount={player.assets.harvester}>
{player.assets.harvester}
</ResourceUnit> {' '}
<ResourceUnit img={TractorImg} h='240' s='100' label='Tractors'
amount={player.assets.tractor}>
{player.assets.tractor}
</ResourceUnit>
</div>
);
}
}
/* {' '}
* <ResourceUnit img={CakeImg} h='240' s='100' label='Birthday'
* amount={player.assets.birthday ? player.assets.birthday : 0}>
* {player.assets.birthday ? player.assets.birthday : 0}
* </ResourceUnit> */
// http://stackoverflow.com/questions/149055
function formatMoney(n) {
return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); }
class MoneySummary extends React.Component {
render () {
const { max } = this.props;
return (
<span className="small-text">
<Row>
<Col width={max ? '3' : "6"} className="align-right pad-right">
<b>{this.props.title}</b>:
</Col>
<Col width={max ? '9' : "6"}>
${formatMoney(this.props.value)}
{max ? (
<>
{' '}/ ${formatMoney(max)}
</>
) : (<></>)}
</Col>
</Row>
</span>
);
}
}
class PlayerSummary extends React.Component {
render () {
const player = this.props.player;
return (
<GroupBox title={(<Fragment><div className={'player player-' + player.color}></div> {player.name}
</Fragment>)}>
<Row>
<Col width="5">
<MoneySummary title='Cash' value={player.displayCash} />
</Col>
<Col width="7">
<MoneySummary title='Assets' value={assetsValue(player)} />
</Col>
</Row>
<Row>
<Col width="5">
<MoneySummary title='Debt' value={player.debt} />
</Col>
<Col width="7">
<MoneySummary title='Net worth' value={netWorth(player)} />
</Col>
</Row>
<br />
<PlayerResources player={player} />
<br />
<PlayerTurnContainer player={player}
game={this.props.game}
ui={this.props.ui}
hideForLarge={this.props.hideForLarge}
screen={this.props.screen}
showScreen={this.props.showScreen}/>
</GroupBox>
);
}
}
class PlayerTurnContainer extends React.Component {
clickRoll = () => {
roll();
this.props.showScreen(SCREENS.action);
}
render() {
let view;
const { player, ui } = this.props,
worth = netWorth(player),
auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.settings.auditThreshold) ? (
<Button onClick={audit}>Call Audit</Button>) : '';
if (this.props.game.state === 'finished') {
view = (<b>Game finished!</b>);
} else if (player.state === GAME_STATES.preTurn) {
view = (
<Fragment>
<Button onClick={this.clickRoll}>Roll</Button>{' '}{auditButton}
</Fragment>
);
} else if (player.state === GAME_STATES.midTurn) {
if (this.props.ui.action) {
if (this.props.screen === SCREENS.action) {
view = `It's your turn!`;
} else {
view = (
<Button onClick={() => this.props.showScreen(SCREENS.action)}>
Complete your turn
</Button>);
}
} else {
if (player.cash < 0) {
view = (
<Button onClick={() => this.props.showScreen(SCREENS.loans)}>
Raise Cash
</Button>
);
} else {
view = (
<Fragment>
<Button onClick={endTurn}>
End Turn
</Button>{' '}{auditButton}
</Fragment>);
}
}
} else {
view = (
<Button onClick={() => this.props.showScreen(SCREENS.action)}>
Watch {this.props.game.currentPlayer}&apos;s turn
</Button>);
}
const space = ui.playerSpaces[player.color];
return (
<Row>
<Col width='12'>
<div className='turn-container'>
{ space >= 9 && space <= 14 ? (
<>
<div>
Recommendation:
</div>
<div>
<Button className="tiny"
onClick={() => this.props.showScreen(SCREENS.cards)}>
Complete Purchasing
</Button> before next roll
</div>
<br />
</>
) : (<></>)}
<div className={this.props.hideForLarge ? 'hide-for-large' : ''}>
{view}
</div>
</div>
</Col>
</Row>
);
}
}
// just the circle, for players circles in a space see PlayerIcon
class PlayerColorIcon extends React.Component {
render() {
return (<div className={'player player-' + this.props.color}></div>);
}
}
function playerHasRidge(player, ridge) {
return player.ridges[ridge] !== 0 ? true : false;
}
function makeRidgeLookup(player, otherPlayers) {
var ridges = { ridge1: playerHasRidge(player, 'ridge1') ? player.color : false,
ridge2: playerHasRidge(player, 'ridge2') ? player.color : false,
ridge3: playerHasRidge(player, 'ridge3') ? player.color : false,
ridge4: playerHasRidge(player, 'ridge4') ? player.color : false };
for (let i = 0; i < otherPlayers.length; i++) {
let p = otherPlayers[i].player;
if (!ridges.ridge1) {
ridges.ridge1 = playerHasRidge(p, 'ridge1') ? p.color : false; }
if (!ridges.ridge2) {
ridges.ridge2 = playerHasRidge(p, 'ridge2') ? p.color : false; }
if (!ridges.ridge3) {
ridges.ridge3 = playerHasRidge(p, 'ridge3') ? p.color : false; }
if (!ridges.ridge4) {
ridges.ridge4 = playerHasRidge(p, 'ridge4') ? p.color : false; }
}
return ridges;
}
class FarmsContainer extends React.Component {
render() {
let ridges = makeRidgeLookup(this.props.player, this.props.otherPlayers);
return (
<GroupBox title='Farms'>
<b>Ridges</b>:
{ridgeNames.map((ridge, idx) => (
<div key={idx} className={"farms-ridge player-" + ridges['ridge' + (idx + 1)]}>
<span>{ridge}</span>
<span className='num-cows'>{'(' + ((idx + 2) * 10) + ' cows)'}</span>
</div>
))}
<br />
{this.props.otherPlayers
.map(p => (
<div key={p.player.name}>
<PlayerColorIcon color={p.player.color} />{'\u00A0'}
<b>{p.player.name}</b> <PlayerResources player={p.player} />
<br /> <br />
</div>))}
</GroupBox>
);
}
}
class TradeContainer extends React.Component {
render() {
return (
<GroupBox title='Trade'>
<PlayerTradeResources otherPlayers={this.props.game.otherPlayers}
player={this.props.player} />
</GroupBox>
);
}
}
const findPlayer = (game, name) => {
const ret = game.otherPlayers.find(p => p.player.name === name) || false;
return ret ? ret.player : ret;
}
const playerRidgeCows = player => {
return Object.values(player.ridges).reduce((a, b) => a + b, 0);
}
const makeDefaultToTrade = () => {
return { hay: 0,
grain: 0,
fruit: 0,
cows: 0,
harvester: 0,
tractor: 0,
money: 0,
ridge1: false,
ridge2: false,
ridge3: false,
ridge4: false,
cards: ''
};
}
class TradeContainer2 extends React.Component {
resources = [{ img: HayImg,
h: '120',
s: '100',
label: 'acres of Hay',
key: 'hay',
amount: 10
}, {
img: WheatImg,
h: '41',
s: '100',
label: 'acres of Grain',
key: 'grain',
amount: 10
}, {
img: FruitImg,
h: '0',
s: '100',
label: 'acres of Fruit',
key: 'fruit',
amount: 5
}, {
img: CowImg,
h: '0',
s: '59',
label: 'head of Cows',
key: 'cows',
amount: 10
}, {
img: HarvesterImg,
h: '240',
s: '100',
label: 'Harvesters',
key: 'harvester',
amount: 1
}, {
img: TractorImg,
h: '240',
s: '100',
label: 'Tractors',
key: 'tractor',
amount: 1
}, {
icon: faDollarSign,
h: '100',
s: '83',
label: 'Cash',
key: 'money',
amount: 1
}];
state = {
otherPlayer: findPlayer(this.props.game, this.props.game.otherPlayers.length > 0 ? this.props.game.otherPlayers[0].player.name : ''),
toTrade: makeDefaultToTrade()
}
selectPlayer = e => {
this.setState({ otherPlayer: findPlayer(this.props.game, e.target.value) });
}
componentDidUpdate(prevProps) {
if (!this.state.otherPlayer &&
prevProps.game.otherPlayers.length !== this.props.game.otherPlayers.length) {
this.setState({ otherPlayer: findPlayer(this.props.game,
this.props.game.otherPlayers.length > 0 ? this.props.game.otherPlayers[0].player.name : '') });
} else if (this.state.otherPlayer && this.state.otherPlayer.name !== findPlayer(this.props.game, this.state.otherPlayer.name).name) {
this.setState({ otherPlayer: findPlayer(this.props.game, this.state.otherPlayer.name) });
}
if (prevProps.player.trade.originator && !this.props.player.trade.originator) {
this.setState({ otherPlayer: findPlayer(this.props.game,
this.props.game.otherPlayers.length > 0 ? this.props.game.otherPlayers[0].player.name : ''),
toTrade: makeDefaultToTrade()
});
} else if (prevProps.player.trade.originator !== this.props.player.trade.originator) {
const isOriginator = this.props.player.trade.originator === this.props.player.name;
let tradeObj = {};
if (!isOriginator) {
Object.keys(this.props.player.trade).forEach(key => {
const value = this.props.player.trade[key];
switch (typeof value) {
case 'number':
tradeObj[key] = value * -1;
break;
case 'boolean':
tradeObj[key] = value;
break;
default:
tradeObj[key] = value;
}
});
} else {
tradeObj = this.props.player.trade;
}
this.setState({
toTrade: {
...makeDefaultToTrade(),
...tradeObj,
money: tradeObj.money ? tradeObj.money / 1000 : 0
},
otherPlayer: findPlayer(this.props.game, isOriginator ?
this.props.player.trade.player : this.props.player.trade.originator)
});
}
}
tradeClass(amount) {
if (amount > 0) {
return 'trade-to';
} else if (amount < 0) {
return 'trade-from';
} else {
return '';
}
}
tradeAsset = (asset, amount, verbatim) => {
this.setState(state => {
return {
toTrade: {
...state.toTrade,
[asset]: (typeof amount === 'number') && !verbatim ? (state.toTrade[asset] + amount) : amount
}
};
});
}
propose = () => {
trade({ ...this.state.toTrade,
money: this.state.toTrade.money * 1000,
player: this.state.otherPlayer.name
})
}
render() {
const player = this.props.player,
otherPlayer = this.state.otherPlayer,
toTrade = this.state.toTrade,
tradeProposed = !!player.trade.originator;
return (
<GroupBox title='Trade'>
<Row>
<Col width="12">
<div className="trade-player-container">
<h4>{player.name}</h4>
{Object.keys(player.ridges).map((ridge, idx) => (
<Fragment key={'player-trade' + ridge}>
{!tradeProposed && (player.ridges[ridge] > 0) && !toTrade[ridge] ? (
<Button className="tiny"
onClick={() => this.tradeAsset(ridge, player.ridges[ridge], true)}>
<FontAwesomeIcon icon={faArrowDown} /> {ridgeNames[idx]} {'('}{player.ridges[ridge]} cows{')'}
</Button>
) : (<></>)}
{' '}
</Fragment>
))}
<div className='resource-unit-container'>
{this.resources.map((o, i) => {
let amount = o.key === 'money' ? Math.floor(player.cash / 1000) : player.assets[o.key];
if (o.key === 'cows') { amount = player.assets.cows - playerRidgeCows(player); }
return (
<ResourceUnit img={o.img ? o.img : false} h={o.h} s={o.s} label={o.label}
key={i}
className={o.key === 'money' ? 'double-width' : false}
amount={amount + Math.min(0, toTrade[o.key])}
>
{o.icon ? (
<>
<FontAwesomeIcon icon={o.icon} />(K)
<br />
</>
) : ''}
{amount + Math.min(0, toTrade[o.key])}
{o.icon ? (<br />) : ''}
{!tradeProposed && ((amount + Math.min(0, toTrade[o.key])) > 0 || toTrade[o.key] > 0) ? (
<>
<Button className="tiny"
onClick={() => this.tradeAsset(o.key, o.amount * -1)}>
<FontAwesomeIcon icon={faArrowDown} />
</Button>
{o.key === 'money' && (amount + Math.min(0, toTrade[o.key])) >= 10 ? (
<Button className="tiny"
onClick={() => this.tradeAsset(o.key, o.amount * -10)}>
<FontAwesomeIcon icon={faArrowDown} />
<FontAwesomeIcon icon={faArrowDown} />
</Button>
) : ''}
</>
) : (<></>)}
</ResourceUnit>
);
})}
</div>
</div>
</Col>
</Row>
<Row>
<Col width="12">
{Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
<Fragment key={'to-trade' + ridge}>
{(otherPlayer.ridges[ridge] > 0) && toTrade[ridge] ? (
<Button className="tiny"
onClick={() => this.tradeAsset(ridge, false)}>
<FontAwesomeIcon icon={faArrowDown} /> {ridgeNames[idx]} {'('}{otherPlayer.ridges[ridge]} cows{')'}
</Button>
) : (<></>)}
{' '}
</Fragment>
))}
</Col>
</Row>
<Row>
<Col width="12">
<div className='resource-unit-container resources-to-trade'>
{this.resources.map((o, i) => (
<ResourceUnit h={o.h} s={o.s} label={o.label}
key={i}
className={(this.tradeClass(toTrade[o.key])) + (o.key === 'money' ? ' double-width' : '')}
amount={toTrade[o.key]}
>
{Math.abs(toTrade[o.key])}
</ResourceUnit>
))}
</div>
</Col>
</Row>
<Row>
<Col width="12">
{Object.keys(player.ridges).map((ridge, idx) => (
<Fragment key={'to-trade' + ridge}>
{(player.ridges[ridge] > 0) && toTrade[ridge] ? (
<Button className="tiny"
onClick={() => this.tradeAsset(ridge, false)}>
<FontAwesomeIcon icon={faArrowUp} /> {ridgeNames[idx]} {'('}{player.ridges[ridge]} cows{')'}
</Button>
) : (<></>)}
{' '}
</Fragment>
))}
</Col>
</Row>
<Row>
<Col width="12">
<div className="trade-player-container">
{otherPlayer ? (
<div className='resource-unit-container'>
{this.resources.map((o, i) => {
let amount = o.key === 'money' ? 99 : otherPlayer.assets[o.key];
if (o.key === 'cows') { amount = otherPlayer.assets.cows - playerRidgeCows(otherPlayer); }
return (
<ResourceUnit img={o.img ? o.img : false} h={o.h} s={o.s} label={o.label}
key={i}
className={o.key === 'money' ? 'double-width' : false}
amount={amount - Math.max(0, toTrade[o.key])}
>
{o.icon ? (
<>
<FontAwesomeIcon icon={o.icon} />(K)
<br />
</>
) : ''}
{amount - Math.max(0, toTrade[o.key])}
{o.icon ? (<br />) : ''}
{!tradeProposed && ((amount - Math.max(0, toTrade[o.key])) > 0 || toTrade[o.key] < 0) ? (
<>
<Button className="tiny"
onClick={() => this.tradeAsset(o.key, o.amount)}>
<FontAwesomeIcon icon={faArrowUp} />
</Button>
{o.key === 'money' && (amount - Math.min(0, toTrade[o.key])) >= 10 ? (
<Button className="tiny"
onClick={() => this.tradeAsset(o.key, o.amount * 10)}>
<FontAwesomeIcon icon={faArrowUp} />
<FontAwesomeIcon icon={faArrowUp} />
</Button>
) : ''}
</>
) : (<></>)}
</ResourceUnit>
);
})}
</div>
) : (<></>)}
{Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
<Fragment key={'other-player-trade' + ridge}>
{!tradeProposed && ((otherPlayer.ridges[ridge] > 0) && !toTrade[ridge]) ? (
<Button className="tiny"
onClick={() => this.tradeAsset(ridge, otherPlayer.ridges[ridge], true)}>
<FontAwesomeIcon icon={faArrowUp} /> {ridgeNames[idx]} {'('}{otherPlayer.ridges[ridge]} cows{')'}
</Button>
) : (<></>)}
{' '}
</Fragment>
))}
<select onChange={this.selectPlayer}
value={otherPlayer.name}
defaultValue={otherPlayer.name}>
{this.props.game.otherPlayers.map(p => (
<option key={p.player.name} value={p.player.name}>
{p.player.name}
</option>
))}
</select>
</div>
</Col>
</Row>
<Row>
<Col width="12">
<input type="text" placeholder="Cards"
defaultValue={toTrade.cards}
onChange={e => this.tradeAsset('cards', e.target.value)} />
</Col>
</Row>
<Row>
<Col width="12">
{player.trade.error ? (
<p><b>ERROR:</b> {player.trade.error}</p>
) : (<></>)}
{tradeProposed && player.trade.originator === player.name ? (
<Button onClick={submitTradeCancel}>Cancel trade</Button>
) : (<></>)}
{tradeProposed && player.trade.originator !== player.name ? (
<>
<Button onClick={submitTradeAccept}>Accept trade</Button>{' '}
<Button onClick={submitTradeCancel}>Deny trade</Button>
</>
) : (<></>)}
{!tradeProposed ? (
<Button onClick={this.propose}>Propose Trade</Button>
) : (<></>)}
</Col>
</Row>
</GroupBox>
);
}
}
class CCBY extends React.Component {
render() {
return (
<Fragment>
License Creative Commons <a href={`https://creativecommons.org/licenses/by/${this.props.version ? this.props.version : 3}.0/us/legalcode`}>CCBY</a>
</Fragment>
);
}
}
class Misc extends React.Component {
render() {
return (
<div className='credits'>
<h1>Credits</h1>
<ul>
<li>
Game created by <a href='https://thomashintz.org'>Thomas Hintz</a>.
</li>
<li>
<a href='https://code.thintz.com/farm'>Game source</a> available under the <a href='https://www.gnu.org/licenses/gpl-3.0-standalone.html'>GPLv3</a>+ license.
</li>
<li>
IndieFlower font available under the <a href="http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL_web&_sc=1">SIL OFLv1.1</a>. Copyright <a href="http://www.kimberlygeswein.com/">Kimberly Geswein</a>.
</li>
<li>
<img src={TractorFullImg} /> <img src={TractorImg} /> Copyright Nick Roach with modifications by Thomas Hintz - License <a href='GPL http://www.gnu.org/copyleft/gpl.html'>GPL</a>
</li>
<li>
<img src={VolcanoImg} /> Copyright <a href="https://thenounproject.com/Maludk/">Laymik</a> with modifications by Thomas Hintz - <CCBY />
</li>
<li>
<img src={CornImg} /> <img src={FruitImg} /> <img src={CakeImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY />
</li>
<li>
<img src={CowImg} /> Copyright <a href='https://thenounproject.com/rivercon/'>rivercon</a> - <CCBY />
</li>
<li>
<img src={HayImg} /> Copyright <a href='https://thenounproject.com/maxicons/'>Maxicons</a> - <CCBY />
</li>
<li>
<img src={WheatImg} /> Copyright <a href='https://thenounproject.com/amoghdesign/'>P Thanga Vignesh</a> - <CCBY />
</li>
<li>
<img src={HarvesterImg} /> Copyright <a href='https://andrejskirma.com/'>Andrejs Kirma</a> - <CCBY />
</li>
</ul>
</div>
);
}
}
class Messages extends React.Component {
render() {
let lastTurn = -1,
newTurn = false;
return (
<div className='messages'>
{this.props.game.messages.map((m, i) => (
<span key={i}>{m[0] === this.props.player.name ? (<b>{m[0]}</b>) : m[0]}{': ' + m[2]}<br /></span>
))}
{this.props.game.messages.length > 0 ? (<Fragment><br /><br /></Fragment>) : (<Fragment />)}
{this.props.game.oldMessages.map((m, i) => {
newTurn = false;
if (m[1] !== lastTurn) {
lastTurn = m[1];
newTurn = true;
}
return (
<Fragment key={i}>
{newTurn ? (<Fragment><b>Turn {m[1]}</b><br /></Fragment>) : <Fragment />}
<span>
{m[0] + ': ' + m[2]}
<br /></span>
</Fragment>
)})}
</div>
);
}
}
class Loans extends React.Component {
constructor(props) {
super(props);
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 => {
const target = e.target,
name = e.target.name;
let value = Math.max(0, parseInt(e.target.value));
if (isNaN(value)) { value = 0; }
let repay = 0,
takeOut = 0;
if (name === 'repay') {
repay = value * 1000;
} else {
takeOut = value * 1000;
}
this.setState({ repay: Math.max(0, Math.floor(
Math.min(repay, this.props.player.debt + 900, this.props.player.displayCash) / 1000)),
takeOut: Math.max(0, Math.floor(
Math.min(takeOut, this.state.maxLoan) / 1000)) });
}
handleSubmit = e => {
e.preventDefault();
loan((this.state.repay + this.state.takeOut) * (this.state.repay > 0 ? -1 : 1));
this.setState({ repay: 0, takeOut: 0 });
}
handleEmergencyLoan = e => {
e.preventDefault();
loan(1);
}
maxRepay = e => {
e.preventDefault();
this.setState({ repay: Math.max(0, Math.floor(
Math.min(this.props.player.debt + 900, this.props.player.displayCash) / 1000)) });
}
maxTakeout = e => {
e.preventDefault();
this.setState({ takeOut: Math.max(0, Math.floor(this.state.maxLoan / 1000)) });
}
quickLoan = i => {
loan(i);
}
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'}>
<Row>
<Col width="4">
<MoneySummary title='Cash' value={this.props.player.displayCash} />
</Col>
<Col width="8">
<MoneySummary title='Debt' value={this.props.player.debt} max={this.props.game.settings.maxDebt} />
</Col>
</Row>
{this.props.game.settings.loanInterest > 0 ? (
<>
{'('}{this.props.game.settings.loanInterest * 100} % interest added to each loan{')'}
<br />
</>
) : ''}
{this.props.player.debt >= this.props.game.settings.maxDebt ? (
<span className="small-text">
<Button onClick={this.handleEmergencyLoan} className="tiny">Emergency Loan</Button> $1,000 at 100% interest
</span>
) : (<></>)}
<br />
<form onSubmit={this.handleSubmit}>
<Row collapse='true'>
<Col width="2">
{this.props.player.displayCash >= 1000 ? (
<Button className="tiny" onClick={() => this.quickLoan(-1)}>-1</Button>
) : (<span>{' '}</span>)}
</Col>
<Col width='6'>
<div className='money'>
$:
<input onChange={this.handleInput} name='repay' type='number'
value={this.state.repay === 0 ? '' : this.state.repay}/>
{'\u00A0'}K / ${Math.floor(this.props.player.debt / 1000)}K
</div>
</Col>
<Col width='4'>
<Button className='tiny' onClick={this.maxRepay}><FontAwesomeIcon icon={faArrowUp} /></Button>{' '}
<Button className='tiny' type='submit'>Repay</Button>
</Col>
</Row>
<Row collapse='true'>
<Col width="2">
{this.props.player.debt < this.props.game.settings.maxDebt ? (
<Button className="tiny" onClick={() => this.quickLoan(1)}>+1</Button>
) : (<span>{' ' }</span>)}
</Col>
<Col width='6'>
<div className='money'>
$:
<input onChange={this.handleInput} name='takeOut' type='number'
value={this.state.takeOut === 0 ? '' : this.state.takeOut} />
{'\u00A0'}K / ${Math.floor(this.state.maxLoan / 1000)}K
</div>
</Col>
<Col width='4'>
<Button className='tiny' onClick={this.maxTakeout}><FontAwesomeIcon icon={faArrowUp} /></Button>{' '}
<Button className='tiny' type='submit'>Take Out</Button>
</Col>
</Row>
</form>
</GroupBox>
);
}
}
function random(max, min = 1) {
return Math.floor(Math.random() * max) + min;
}
function decay(totalMs, decayFactorPct, ticksToGo) {
return totalMs * Math.pow(1 - decayFactorPct, ticksToGo);
}
class Die extends React.Component {
timerId = false;
lastRoll = 0;
ticks = 0;
maxTicks = Number.MAX_SAFE_INTEGER;
speed = { fast: 0.1, slow: 0.7 };
interval = 100;
acc = 0;
trigger = 0;
decayFactorPct = 0.4;
showScreenTimerId = false;
rollIndex = 0;
constructor(props) {
super(props);
this.state = { num: props.roll ? this.roll() : props.num,
finalFace: false,
autoSkip: typeof props.autoSkip === 'undefined' ? false :
props.autoSkip };
this.trigger = 1000 * this.speed[props.speed ? props.speed : 'fast']
if (props.ms) {
this.maxTicks = Math.floor(props.ms / this.trigger);
if (props.decay) {
this.trigger = decay(props.ms, this.decayFactorPct, this.maxTicks);
}
}
}
roll() {
if (!this.props.rolls) {
let roll = random(6);
while (roll === this.lastRoll) {
roll = random(6);
}
this.lastRoll = roll;
return roll;
} else {
return this.props.rolls[++this.rollIndex];
}
}
tick = () => {
let finished = false;
return function() {
if (!finished) {
this.acc += this.interval;
// update die face
if (this.ticks < this.maxTicks && this.acc >= this.trigger) {
this.ticks++;
this.acc = 0;
// update trigger for decay
if (this.props.decay) {
this.trigger = decay(this.props.ms, this.decayFactorPct,
this.maxTicks - this.ticks);
}
// we're finished, clear things, set final roll value
if (this.ticks >= this.maxTicks) {
finished = true;
clearInterval(this.timerId);
this.timerId = false;
this.setState({ num: this.props.num, finalFace: true });
if (this.props.showScreen) {
this.showScreenTimerId = setInterval(() => {
this.props.showScreen();
clearInterval(this.showScreenTimerId);
this.showScreenTimerId = false;
}, this.props.showScreenDelay);
}
} else {
this.setState({ num: this.roll() });
}
}
} else {
console.log('harvest die finished but still ticking');
}
}
}
componentDidMount() {
if (this.props.roll) {
this.timerId = setInterval(this.tick().bind(this), this.interval);
}
}
componentWillUnmount() {
if (this.timerId) {
clearInterval(this.timerId);
}
if (this.showScreenTimerId) {
clearInterval(this.showScreenTimerId);
}
}
skip(preventAutoSkip) {
if (!preventAutoSkip) {
skip('die');
}
clearInterval(this.timerId);
this.timerId = false;
this.setState({ num: this.props.num, finalFace: true });
if (this.props.showScreen) {
this.showScreenTimerId = setInterval(() => {
this.props.showScreen();
clearInterval(this.showScreenTimerId);
this.showScreenTimerId = false;
}, this.props.showScreenDelay);
}
}
componentDidUpdate() {
if (this.state.autoSkip !== this.props.autoSkip) {
if (this.state.autoSkip === false &&
this.props.autoSkip) {
this.skip(true);
}
this.setState({ autoSkip: this.props.autoSkip });
}
}
render() {
let face;
switch (this.state.num) {
case 1:
face = (<div className='die-face face-1'></div>);
break;
case 2:
face = (<Fragment>
<div className='die-face face-21'></div>
<div className='die-face face-22'></div>
</Fragment>);
break;
case 3:
face = (<Fragment>
<div className='die-face face-21'></div>
<div className='die-face face-1'></div>
<div className='die-face face-22'></div>
</Fragment>);
break;
case 4:
face = (<Fragment>
<div className='die-face face-21'></div>
<div className='die-face face-22'></div>
<div className='die-face face-52'></div>
<div className='die-face face-54'></div>
</Fragment>);
break;
case 5:
face = (<Fragment>
<div className='die-face face-21'></div>
<div className='die-face face-1'></div>
<div className='die-face face-22'></div>
<div className='die-face face-52'></div>
<div className='die-face face-54'></div>
</Fragment>);
break;
case 6:
face = (<Fragment>
<div className='die-face face-21'></div>
<div className='die-face face-63'></div>
<div className='die-face face-64'></div>
<div className='die-face face-22'></div>
<div className='die-face face-52'></div>
<div className='die-face face-54'></div>
</Fragment>);
break;
}
return (
<Fragment>
<div className='die-container'>
<div className={'die' + (this.state.finalFace ? ' die-selected' : '')}>
{face}
</div>
</div>
{this.props.skip && !this.state.finalFace ?
(<div className='center'>
<Button onClick={() => this.skip()}>Skip</Button>
</div>) :
(<Fragment />)}
</Fragment>
);
}
}
class PreRolling extends React.Component {
render() {
return (
<GroupBox title={this.props.name + ' is rolling!'}>
<Die roll={true} />
</GroupBox>
);
}
}
class Rolling extends React.Component {
render() {
return (
<GroupBox title={this.props.name + ' is rolling!'}>
<Die decay={true} num={this.props.num} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={this.props.showScreen}
skip={this.props.skip}
autoSkip={this.props.autoSkip}
showScreenDelay={this.props.showScreenDelay} />
</GroupBox>
);
}
}
class Harvest extends React.Component {
cropsToImg = { hay: HayImg,
cherries: FruitImg,
wheat: WheatImg,
cows: CowImg,
fruit: FruitImg,
corn: CornImg }
viewOrder = ['roll', 'income', 'operating-expense', 'expense-value', false]
constructor(props) {
super(props);
this.state = { view: 'ready',
autoSkip: typeof props.autoSkip === 'undefined' ? false :
props.autoSkip,
autoSkipView: false };
}
nameText = player => {
return player === this.props.game.currentPlayer ? 'You' : player;
}
nextView = (view, preventAutoSkip) => {
const newView = view ? view :
this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1];
this.setState({ view: newView });
if (!preventAutoSkip) {
skip('harvest|' + newView);
}
}
cropToImg = crop => {
return this.cropsToImg[crop];
}
componentDidUpdate() {
if (this.props.autoSkip &&
typeof this.props.autoSkip === 'string' &&
this.props.autoSkip.indexOf('harvest|') === 0 &&
this.state.autoSkipView !== this.props.autoSkip.split('|')[1]) {
this.nextView(this.props.autoSkip.split('|')[1], true);
this.setState({ autoSkipView: this.props.autoSkip.split('|')[1] });
}
}
render() {
let view;
const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer;
switch (this.state.view) {
case 'ready':
view = (
<div className='clear-background'>
<div className={'harvest-card space-type-' + this.props.crop}>
<h1>{this.props.crop + ' harvest!'}</h1>
<div className='harvest-card-contents'>
<img src={this.cropToImg(this.props.crop)} />
Get ready to harvest <b>{this.props.acres}
{this.props.crop === 'cows' ? ' head of cow' : ' acres'}</b>!
</div>
{isCurrentPlayer ? (
<Button onClick={() => this.nextView('roll')}>
Roll for harvest!
</Button>
) : (<Fragment />)}
{this.props.harvestMult !== 1 ? (
<span>Multiplied by {this.props.harvestMult}!</span>
) : (<></>)}
</div>
</div>
);
break;
case 'roll':
view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true}
rolls={this.props.rolls}
showScreen={() => this.nextView('income')}
skip={this.props.player.name === this.props.game.currentPlayer}
autoSkip={this.props.autoSkip === 'die'}
showScreenDelay={2000} />);
break;
case 'income':
view = (
<div>
<div className='game-card'>
<div className={'flex green'}>
<FontAwesomeIcon icon={faDollarSign} size='6x' />
<div>
{isCurrentPlayer ? 'You' :
this.props.game.currentPlayer} rolled a <b>{this.props.rolled}</b> and earned <b>${formatMoney(this.props.income)}</b>!
</div>
</div>
</div>
{isCurrentPlayer ? (
<div className='center spacer'>
<Button onClick={() => this.nextView('operating-expense')}>Draw Operating Expense</Button>
</div>
) : (<Fragment />)}
</div>
);
break;
case 'operating-expense':
view = (
<Fragment>
<div className='game-card'>
<GroupBox title='Operating Expense'>
<div className='card'
dangerouslySetInnerHTML={{__html: this.props.contents}} />
</GroupBox>
</div>
{isCurrentPlayer ? (
<div className='center spacer'>
<Button onClick={() => this.nextView('expense-value')}>Continue</Button>
</div>
) : (<Fragment />)}
</Fragment>
);
break;
case 'expense-value':
const currentExpense = this.props.expenseValue[this.props.game.currentPlayer];
view = (
<div>
<div className='game-card'>
<div className={'flex ' + (currentExpense <= 0 ? 'red' : 'green')}>
<FontAwesomeIcon icon={faDollarSign} size='6x' />
<div>
<p>
{isCurrentPlayer ? 'You' : this.props.game.currentPlayer}
{currentExpense <= 0 ? ' lost ' : ' gained '}
${formatMoney(Math.abs(currentExpense))}!
</p>
{Object.keys(this.props.expenseValue)
.filter(p => p !== this.props.game.currentPlayer)
.map((p, i) =>
(
<p key={i}>
{this.nameText(p) + ' '}
{this.props.expenseValue[p] <= 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(this.props.expenseValue[p]))}!
</p>
)
)}
</div>
</div>
</div>
{isCurrentPlayer ?
(
<div className='center spacer'>
<Button onClick={this.props.nextAction}>Continue</Button>
</div>
) : (<Fragment />)}
</div>
);
break;
}
return view;
}
}
class Moving extends React.Component {
skip(preventAutoSkip) {
if (!preventAutoSkip) {
skip('moving');
}
this.props.setMovingSkip(true);
}
render() {
let buttons;
if ((this.props.player.name !== this.props.game.currentPlayer &&
!this.props.currentPlayer.ai)) {
buttons = (<Fragment />);
} else if (this.props.ui.playerSpaces[(this.props.currentPlayer.ai && this.props.currentPlayer.color) || this.props.player.color]
=== this.props.ui.actionValue.to) {
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
} else {
buttons = (<Button onClick={() => this.skip()}>Skip</Button>);
}
const { currentPlayer } = this.props;
return (
<Row>
<Col width={'12'}>
<div className='moving'>
<div className={'action clear-background'}>
<SpaceNode space={this.props.spaces[this.props.ui.playerSpaces[currentPlayer.color]]}
height={'170px'} showtitle={true} />
</div>
</div>
<br />
<div className={'center'}>
{buttons}
</div>
</Col>
</Row>
);
}
}
class Action extends React.Component {
state = {
bertChoice: 'nothing'
}
setBertChoice = (e) => {
this.setState({ bertChoice: e.target.value });
}
bertSubmit = (e) => {
e.preventDefault();
if (this.state.bertChoice === 'accept') {
buyUncleBert();
this.props.showNextAction();
} else if (this.state.bertChoice === 'deny') {
this.props.showNextAction();
}
}
render() {
let view, buttons;
const { currentPlayer } = this.props;
switch (this.props.ui.action) {
case 'otb':
if (this.props.player.name === this.props.game.currentPlayer) {
view = (
<div className='game-card'>
<GroupBox title={itemCard}>
<div className='card'
dangerouslySetInnerHTML={{ __html: this.props.ui.actionValue }} />
</GroupBox>
</div>
);
} else {
view = (
<div>
<p>{currentPlayer.name} drew an {itemCard}</p>
</div>
);
}
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'farmers-fate':
view = (
<div className='game-card'>
<GroupBox title={fateCard}>
<div className='card'
dangerouslySetInnerHTML={{ __html: this.props.ui.actionValue }} />
</GroupBox>
</div>
);
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'ff-uncle-bert':
const { cash } = this.props.player;
const ffButtons = (
<>
{cash < 10000 ? (
<Button onClick={() => this.props.showScreen(SCREENS.loans)}>Raise ${formatMoney(10000 - cash)} Now</Button>
) : (<></>)}
<form onSubmit={this.bertSubmit}>
<label>
<input type="radio" name="bert" value="accept" onClick={this.setBertChoice} />
Yes, take over for $10,000!
</label>
<label>
<input type="radio" name="bert" value="deny" onClick={this.setBertChoice} />
No, continue on
</label>
<Button type="submit" disabled={this.state.bertChoice === 'nothing' || this.state.bertChoice === 'accept' && cash < 10000}>Submit</Button>
</form>
</>
);
view = (
<GroupBox title={`Uncle Bert's inheritance`}>
<div className='center'>
<p>
{currentPlayer.cash >= 10000 ?
`You have enough cash to take over Uncle Bert's farm!` :
(
<>
You must raise another $
{formatMoney(10000 - currentPlayer.cash) + ' '}
to be able to take over Uncle Berts farm!
</>
)}
</p>
<div>
{(this.props.player.name === this.props.game.currentPlayer) ?
ffButtons : (<Fragment />)}
</div>
</div>
</GroupBox>
);
buttons = (<Fragment />);
break;
case 'money':
const { amount, player } = this.props.ui.actionValue;
view = (
<div className='game-card'>
<div className={'flex ' + (amount < 0 ? 'red' : 'green')}>
<FontAwesomeIcon icon={faDollarSign} size='6x' />
<div>
{this.props.player.name === player ? 'You' :
player} {amount <= 0 ? 'lost ' : 'gained '}
${formatMoney(Math.abs(amount))}!
</div>
</div>
</div>);
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'info':
view = (
<div>
<p>{this.props.ui.actionValue}</p>
</div>
);
buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>);
break;
case 'harvest':
view = (<Harvest rolled={this.props.ui.actionValue.rolled}
rolls={this.props.ui.actionValue.rolls}
player={this.props.player}
currentPlayer={currentPlayer}
harvestMult={this.props.ui.actionValue.harvestMult}
game={this.props.game}
income={this.props.ui.actionValue.income}
contents={this.props.ui.actionValue.operatingExpense}
expenseValue={this.props.ui.actionValue.operatingExpenseValue}
crop={this.props.ui.actionValue.crop}
acres={this.props.ui.actionValue.acres}
autoSkip={this.props.ui.autoSkip}
nextAction={() => this.props.showNextAction()} />);
buttons = (<Fragment />);
break;
case 'move':
view = (<Moving showNextAction={() => this.props.showNextAction()}
key={this.props.game.currentPlayer + '|' + this.props.ui.actionValue.from + '|' + this.props.ui.actionValue.to}
currentPlayer={currentPlayer}
player={this.props.player}
game={this.props.game}
spaces={this.props.spaces}
movePlayer={this.props.movePlayer}
autoSkip={this.props.ui.autoSkip}
timerId={this.props.timerId}
setTimerId={this.props.setTimerId}
setMovingSkip={this.props.setMovingSkip}
ui={this.props.ui} />);
buttons = (<Fragment />);
break;
case 'resolve-move':
view = (<Moving showNextAction={() => this.props.showNextAction()}
key={this.props.game.currentPlayer + '|' + this.props.ui.actionValue.from + '|' + this.props.ui.actionValue.to}
currentPlayer={currentPlayer}
player={this.props.player}
game={this.props.game}
spaces={this.props.spaces}
movePlayer={this.props.movePlayer}
autoSkip={this.props.ui.autoSkip}
timerId={this.props.timerId}
setTimerId={this.props.setTimerId}
setMovingSkip={this.props.setMovingSkip}
ui={this.props.ui} />);
buttons = (<Fragment />);
break;
case 'goto':
view = (<Moving showNextAction={() => this.props.showNextAction()}
timerId={this.props.timerId}
currentPlayer={currentPlayer}
setTimerId={this.props.setTimerId}
player={this.props.player}
game={this.props.game}
setMovingSkip={this.props.setMovingSkip}
spaces={this.props.spaces}
movePlayer={this.props.movePlayer}
autoSkip={this.props.ui.autoSkip}
ui={this.props.ui} />);
buttons = (<Fragment />);
break;
case 'pre-rolling':
view = (<PreRolling name={this.props.game.currentPlayer} />);
buttons = (<Fragment />);
break;
case 'roll':
const roll = this.props.ui.actionValue.to -
(this.props.ui.actionValue.to < this.props.ui.actionValue.from ?
this.props.ui.actionValue.from - 49 : this.props.ui.actionValue.from);
view = (<Rolling num={roll}
rolls={this.props.ui.actionValue.rolls}
name={this.props.game.currentPlayer}
showScreen={(this.props.player.name === this.props.game.currentPlayer ||
currentPlayer.ai)
? () => this.props.showNextAction()
: false}
autoSkip={this.props.ui.autoSkip}
skip={(this.props.player.name === this.props.game.currentPlayer ||
currentPlayer.ai)}
showScreenDelay={2000} />);
buttons = (<Fragment />);
break;
default:
if (this.props.player.name === this.props.game.currentPlayer) {
view = (<PlayerSummary player={this.props.player}
ui={this.props.ui}
screen={this.props.screen}
showScreen={this.props.showScreen}
game={this.props.game} />);
} else if (currentPlayer.ai) {
view = (
<Button onClick={endAiTurn}>
Next Player's Turn
</Button>
);
} else {
view = (<Fragment>Waiting for {this.props.game.currentPlayer}</Fragment>);
}
}
return (
<div ref={x => this.props.forwardRef(x)}>
<Row>
<Col width={'12'}>
<div className={'action'}>
{view}
</div>
<br />
<div className={'center'}>
{(this.props.otherPlayersTurn && !currentPlayer.ai ) ? (<Fragment />) : buttons}
</div>
</Col>
</Row>
</div>
);
}
}
class Board extends React.Component {
render() {
const rh = this.props.height || '20px'; // height={h}
const renderSpace = (s, h, o) =>
(<SpaceNode current={this.props.currentPlayer.color}
showIcons={true} space={s} key={s.key} orientation={o} />);
return (
<Fragment>
<Row collapse='true' className='row-spaces'>
<Col width='1'>{renderSpace(this.props.spaces[0], rh, 'corner-tl')}</Col>
<Col width='10'>
<div className='flex-row'>
{this.props.spaces.slice(1, 14).map(s => renderSpace(s, rh, 'top'))}
</div>
</Col>
<Col width='1'>{renderSpace(this.props.spaces[14], rh, 'corner-tr')}</Col>
</Row>
<Row collapse='true' className='mid-row-container'>
<Col width='1'>
<div className='mid-col'>
{this.props.spaces.slice(38, 49).reverse()
.map(s => renderSpace(s, this.props.height || false, 'left'))}
</div>
</Col>
<Col width='10'>
<div className='mid-row'>
<div className='center-board'>
<Row>
<Col width='12 column-no-padding'>
{this.props.children}
</Col>
</Row>
<div className='board-heading'><h1>Alpha Centauri Farming</h1></div>
<Tractor />
</div>
</div>
</Col>
<Col width='1'>
<div className='mid-col'>
{this.props.spaces.slice(15, 25)
.map(s => renderSpace(s, this.props.height ? (parseInt(this.props.height) * 1.11) + 'px' : false, 'right'))}
</div>
</Col>
</Row>
<Row collapse='true' className='row-spaces'>
<Col width='1'>{renderSpace(this.props.spaces[37], rh, 'corner-bl')}</Col>
<Col width='10'>
<div className='flex-row'>
{this.props.spaces.slice(26, 37).reverse()
.map(s => renderSpace(s, rh, 'bottom'))}
</div>
</Col>
<Col width='1'>{renderSpace(this.props.spaces[25], rh, 'corner-br')}</Col>
</Row>
</Fragment>
);
}
}
// handler, buttonText, children
class AlertOverlay extends React.Component {
constructor(props) {
super(props);
this.state = { visible: typeof this.props.visible !== 'undefined' ?
this.props.visible : true };
}
hide = e => {
e.preventDefault();
this.setState({ visible: false });
this.props.alertHandled(this.props.id);
this.props.hideHandler();
}
buttonClick = e => {
this.hide(e);
this.props.handler();
}
cancelButtonClick = e => {
e.preventDefault();
this.props.cancelHandler();
}
// <label><input type='checkbox' onClick={this.hidePermanent} /> {`Don't show again`}</label>
render() {
return (
<div className={'alert-overlay' + (this.state.visible ? '' : ' hidden') }>
{!this.props.preventHiding ? (
<div onClick={this.hide} className='alert-overlay-hide'>
<FontAwesomeIcon icon={faTimes} />
</div>
) : (<></>)}
<div className='alert-overlay-contents'>
{this.props.children}
<br />
<div>
<Button onClick={this.buttonClick} disabled={!!this.props.disabled}>{this.props.buttonText}</Button>
{this.props.cancelButtonText ? (
<>
{' '}
<Button onClick={this.cancelButtonClick} disabled={!!this.props.cancelDisabled}>
{this.props.cancelButtonText}
</Button>
</>
): (<></>)}
</div>
{!this.props.preventHiding ? (<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 />);
}
}
}
function ceilHundred(n) {
return Math.ceil(n + (n % 100));
}
class HarvestTable extends React.Component {
state = {
selected: 'hay'
}
selectCrop = e => {
this.setState({ selected: e.target.value });
}
render() {
const table = this.props.table,
selectedCrop = this.state.selected;
return (
<div className="harvest-table">
<h4>Crop</h4>
<select onChange={this.selectCrop} defaultValue={selectedCrop}>
{Object.keys(table).map((k, idx) => (
<option key={idx} value={k}>{k}</option>
))}
</select>
<table>
<thead>
<tr>
<th>Dice</th>{table[selectedCrop].slice(1).map((x, idx) => (
<th key={idx}>{(idx + 1) * table[selectedCrop][0]} {selectedCrop === 'cows' ? 'COWS' : 'ACRES'}</th>
))}
</tr>
</thead>
<tbody>
{table[selectedCrop].slice(1).map((mult, row) => (
<tr key={row}>
{new Array(7).fill(0).map((x, idx) => (
<td key={idx}>{idx === 0 ? row + 1 : formatMoney(ceilHundred(mult * idx))}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
}
class Info extends React.Component {
render() {
// TODO change $250,000 to actual value in settings
return (
<>
<h1>Game Instructions</h1>
<h2>Winner</h2>
<p>The winner is the player with the highest net worth.</p>
<h2>Game Ending</h2>
<p>When a player reaches a net worth of $250,000 or more they may 'Call Audit'. Once they call an audit all players continue playing until they finish their current year. Once all players have finished their last year the game is over and the player with the highest net worth is the winner.</p>
<h2>Harvests</h2>
<p>Harvests occur once per block on the matching asset type. For example: hay shows as green and is harvested the first time landing on a block of green spaces each time around the board.</p>
<p>When harvesting a resource the die is first rolled then the harvest amount is calculated based on the following table and how many units of the asset you own:</p>
{this.props.harvestTable ? (<HarvestTable table={this.props.harvestTable} />) : (<></>)}
<p>After a harvest an Operating Expense is drawn.</p>
<h2>Purchashing</h2>
<p>To increase harvest amounts new assets must be bought. This is done via {itemCard} cards. Click on <FontAwesomeIcon icon={faWindowRestore} />, select the card for the asset you wish to purchase, and enter the amount of cash you desire to spend to purchase the selected asset. The remainder will be taken out in debit, if available.</p>
<p>Purchashing may only take place on Purchashing spaces (Christmas - Spring Celebration).</p>
<Misc />
</>
);
}
}
class StartGame extends React.Component {
render() {
const { auditThreshold, downPayment, loanInterest, maxDebt, startingOtbs, startingCash, startingDebt } = this.props.game.settings;
const playerName = this.props.player.name;
const { color } = this.props.player;
const { name, host } = this.props.game;
return (
<>
<h1>Lobby</h1>
<p>
<b>Game</b>: {name}
</p>
<h3>Players</h3>
<ul>
<li><PlayerColorIcon color={color} /> {playerName}</li>
{this.props.game.otherPlayers.map((p, i) => (
<li key={i}>
<PlayerColorIcon color={p.player.color} /> {p.player.name}
{playerName === host ? (
<span title="Kick Player" className="kick-player" onClick={() => kickPlayer(p.player.name)}>
<FontAwesomeIcon icon={faBan} />
</span>
): (<></>)}
</li>
))}
</ul>
<h4>Game Settings</h4>
<ul>
<li><b>Audit Threshold</b>: ${formatMoney(auditThreshold)}</li>
<li><b>Max Debt</b>: ${formatMoney(maxDebt)}</li>
<li><b>Loan Interest</b>: {loanInterest * 100}%</li>
<li><b>Required Down Payment</b>: {downPayment * 100}%</li>
<li><b>Starting {itemCardShort}</b>: {startingOtbs}</li>
<li><b>Starting Cash</b>: ${formatMoney(startingCash)}</li>
<li><b>Starting Debt</b>: ${formatMoney(startingDebt)}</li>
</ul>
<label>
<input type="checkbox" onChange={this.props.toggleReady} />
Ready to start
</label>
</>
);
}
}
const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms',
cards: 'cards', trade: 'trade', loans: 'loans',
action: 'action', info: 'info', error: 'error' };
class BoardApp extends React.Component {
iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards,
'dollar-sign': SCREENS.loans, users: SCREENS.farms,
'exchange-alt': SCREENS.trade, asterisk: SCREENS.misc,
tractor: SCREENS.action, 'info-circle': SCREENS.info }
unsubscribeAlert = () => null
constructor(props) {
super(props);
this.state = {
screen: SCREENS.summary,
card: props.ui.card,
timerId: false,
currentPlayer: this.props.player,
readyToStart: false
};
this.myRef = React.createRef();
this.actionRef = React.createRef();
this.actionRefExtra = React.createRef();
}
actionShowing = () => {
return (this.actionRef && this.actionRef.offsetParent) ||
(this.actionRefExtra && this.actionRefExtra.offsetParent)
}
showScreen = screen => {
if (!(window.innerWidth >= 1024 && screen === SCREENS.action)) {
this.setState({ screen: screen });
}
}
setCard = card => {
this.setState({ card });
}
componentDidUpdate(prevProps) {
if (this.state.card.type === 'no-card' &&
this.props.ui.cards.length > 0) {
this.setState({ card: this.props.ui.cards[0] });
} else if (this.state.card.type !== 'no-card' &&
this.props.ui.cards.length === 0) {
this.setState({ card: { type: 'no-card', contents: '', total: 0 } });
} else if (this.state.card.type !== 'no-card' &&
this.props.ui.cards.map(x => x.id).indexOf(this.state.card.id) < 0) {
this.setState({ card: this.props.ui.cards[0] });
}
// we don't show this alert when on the actions screen so mark
// it as handled.
if (this.props.ui.unhandledAlert &&
this.props.ui.unhandledAlert.type === ALERTS.beginTurn &&
this.actionShowing()) {
this.props.alertHandled(this.props.ui.unhandledAlert.id);
}
if (prevProps.game.currentPlayer !== this.props.game.currentPlayer) {
const currentPlayer = this.props.player.name === this.props.game.currentPlayer ?
this.props.player : this.props.game.otherPlayers
.find(p => p.player.name === this.props.game.currentPlayer).player;
this.setState({ currentPlayer });
}
if (!prevProps.ui.exn && this.props.ui.exn) {
this.setState({ screen: SCREENS.error });
}
}
componentDidMount() {
const currentPlayer = this.props.player.name === this.props.game.currentPlayer ?
this.props.player : this.props.game.otherPlayers
.find(p => p.player.name === this.props.game.currentPlayer).player;
this.setState({ currentPlayer });
// const midColHeight = (window.innerHeight -
// (document.getElementsByClassName('flex-row')[0].offsetHeight * 2)) + 'px';
// const midCols = document.getElementsByClassName('mid-col');
// midCols[0].style.height = midColHeight;
// midCols[1].style.height = midColHeight;
// const mpDims = this.props.mpDims;
// const minWidth = midCols[0].clientWidth;
// const minHeight = 78 + mpDims.padding;
// const maxHeight =
// midCols[0].clientHeight + minHeight - 225 - mpDims.padding;
// const maxWidth = window.innerWidth - minWidth - 160 - mpDims.padding;
// this.props.setMPDims(minWidth, minHeight, maxHeight, maxWidth);
// const midRow = document.getElementsByClassName('mid-row')[0];
// midRow.style.height = midColHeight;
// midRow.onmouseover = () => this.props.setMessagePanelSpace(null);
}
setTimerId = id => {
this.setState(state => {
if (state.timerId) {
clearInterval(state.timerId);
}
return { timerId: id }
});
}
iconClass = icon => {
return this.iconToScreen[icon] === this.state.screen ? 'is-active' :
(this.iconToScreen[icon] === SCREENS.trade ?
(this.props.game.settings.trade ? ' ' : ' hidden ')
: (icon === 'tractor' ? ' hide-for-large ' : ' '));
}
tabClass = screen => {
return 'tab' + (screen === this.state.screen ? ' show' : '');
}
iconOnClick = icon => {
return e => {
e.preventDefault();
this.showScreen(this.iconToScreen[icon]);
}
}
startGameToggleReady = () => {
this.setState(state => { return { readyToStart: !state.readyToStart }; });
readyToStart();
}
render() {
let alertOverlay;
const alert = this.props.ui.unhandledAlert;
if (alert && alert.type === ALERTS.endOfGame) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Close'
hideHandler={() => 'nothing'}
handler={() => { return false; }}>
<Fragment>
<h1>Game Over!</h1>
{alert.contents.map((e, i) => (
<p key={i}>{e}</p>
))}
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.auditCalled) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Close'
hideHandler={() => 'nothing'}
handler={() => { return false; }}>
<Fragment>
<h1>Audit Called!</h1>
<p>{alert.contents} called an audit!</p>
<p>This is your last trip around the board.</p>
</Fragment>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.preGame) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Start Game'
hideHandler={() => 'nothing'}
cancelButtonText='Leave Game'
cancelHandler={leaveGame}
cancelDisabled={this.state.readyToStart}
preventHiding={true}
disabled={!this.props.game.readyToStart}
handler={startGame}>
<StartGame player={this.props.player}
game={this.props.game}
toggleReady={this.startGameToggleReady}
/>
</AlertOverlay>
);
} else if (alert && alert.type === ALERTS.proposedTrade) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='View Trade'
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.trade)
}}>
<Fragment>
<h1>{alert.contents} proposed a trade!</h1>
</Fragment>
</AlertOverlay>
);
} else if (alert && !this.actionShowing()) {
switch (alert.type) {
case ALERTS.beginTurn:
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Roll'
hideHandler={() => 'nothing'}
handler={() => {
roll();
this.showScreen(SCREENS.action); }}>
<h1>{`It's your turn!`}</h1>
</AlertOverlay>
);
break;
case ALERTS.otherPlayersTurn:
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText={'Watch ' + this.props.game.currentPlayer + `'s turn`}
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.action); }}>
<h1>It&apos;s {this.props.game.currentPlayer}&apos;s turn</h1>
</AlertOverlay>
);
break;
default:
alertOverlay = (<Fragment />);
}
} else {
alertOverlay = (<Fragment />);
}
if (alert && alert.type === ALERTS.raiseMoney && !this.props.ui.action) {
alertOverlay = (
<AlertOverlay visible={true}
id={alert.id}
alertHandled={this.props.alertHandled}
buttonText='Raise Money'
hideHandler={() => 'nothing'}
handler={() => {
this.showScreen(SCREENS.loans); }}>
<Fragment>
<h1>Out of cash!</h1>
<p>You have less than $0 cash and must raise money to continue.</p>
</Fragment>
</AlertOverlay>
);
}
const actionComponent = (
<Action
currentPlayer={this.state.currentPlayer}
timerId={this.state.timerId}
setTimerId={this.setTimerId}
forwardRef={x => this.actionRef = x}
spaces={this.props.spaces}
player={this.props.player}
game={this.props.game}
movePlayer={this.props.movePlayer}
showNextAction={this.props.nextUIAction}
otherPlayersTurn={this.props.player.name !== this.props.game.currentPlayer}
screen={this.state.screen}
showScreen={screen => this.showScreen(screen)}
setMovingSkip={this.props.setMovingSkip}
ui={this.props.ui} />);
// faExchangeAlt -> trade icon, hidden for now
return (
<div className='game-container' ref={this.myRef}>
{alertOverlay}
<Board spaces={this.props.spaces}
currentPlayer={this.state.currentPlayer}
>
<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, faExchangeAlt, faInfoCircle]
.map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}>
<div></div>
<a onClick={this.iconOnClick(icon.iconName)}>
<FontAwesomeIcon icon={icon} /></a></li>))}
</ul>
<ul className='vertical menu icons icons-top'>
{[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faExchangeAlt, faInfoCircle]
.map((icon, i) =>
(<li key={i} className={this.iconClass(icon.iconName)}>
<a onClick={this.iconOnClick(icon.iconName)}>
<FontAwesomeIcon icon={icon} /></a></li>))}
</ul>
<div className='tab-container'>
<div className={this.tabClass(SCREENS.summary)}>
<PlayerSummary player={this.props.player}
ui={this.props.ui}
screen={this.state.screen}
hideForLarge={true}
game={this.props.game} showScreen={this.showScreen} />
</div>
<div className={this.tabClass(SCREENS.action)}>
{actionComponent}
</div>
<div className={this.tabClass(SCREENS.cards)}>
<Row>
<Col width="12">
<div>
<CardList ui={this.props.ui} cardId={this.state.card.id}
setCard={this.setCard} />
</div>
<div>
<Card ui={this.props.ui}
setCardError={this.props.setCardError}
playerCash={this.props.player.displayCash}
playerDebt={this.props.player.debt}
card={this.state.card}
cost={this.state.card.total / 1000}
min={(this.state.card.total * this.props.game.settings.downPayment) / 1000}
max={Math.floor(Math.min(this.props.player.displayCash / 1000, this.state.card.total / 1000))}
cash={(this.state.card.total * this.props.game.settings.downPayment) / 1000} />
</div>
</Col>
</Row>
</div>
<div className={this.tabClass(SCREENS.loans)}>
<Loans player={this.props.player} game={this.props.game} />
</div>
<div className={this.tabClass(SCREENS.farms)}>
<FarmsContainer player={this.props.player}
otherPlayers={this.props.game.otherPlayers} />
</div>
<div className={this.tabClass(SCREENS.trade) + ' trade-tab'}>
<TradeContainer2 player={this.props.player} game={this.props.game} />
</div>
<div className={this.tabClass(SCREENS.misc)}>
<Misc />
</div>
<div className={this.tabClass(SCREENS.info)}>
<Info harvestTable={this.props.ui.harvestTable} game={this.props.game} />
</div>
<div className={this.tabClass(SCREENS.error)}>
<h3>Error</h3>
<p>A server error occured.</p>
<p><a href="/?reload-game=true">Reload game</a></p>
</div>
</div>
<div className='static-tab-container show-for-large'>
<div className='tab show'>
{actionComponent}
</div>
</div>
</div>
</Board>
</div>
);
}
}
export default connect(
state => state.farm,
{ setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert, alertHandled,
setCardError, setMovingSkip }
)(BoardApp)
class Card extends React.Component {
constructor(props) {
super(props);
this.state = { cash: this.props.cash, initialCash: this.props.cash };
}
static getDerivedStateFromProps(props, state) {
if (state.initialCash !== props.cash) {
return {
cash: props.cash,
initialCash: props.cash
}
}
return null;
}
handleInput = e => {
const target = e.target;
let value = Math.min(Math.max(this.props.min,
parseInt(target.value)), this.props.max);
if (isNaN(value)) { value = this.props.min; }
this.setState({ cash: value });
this.props.setCardError(false);
}
handleSubmit = e => {
e.preventDefault();
buy(this.props.card.id, this.state.cash);
this.setState({ cash: 0 });
}
render () {
const card = this.props.card;
let action = (null);
switch (card.type) {
case 'otb': action = (
<div className='card-action'>
<form onSubmit={this.handleSubmit}>
<Row collapse='true'>
<Col width='12'>
You: <b>Cash</b>: ${formatMoney(this.props.playerCash)} <b>Debt</b>: ${formatMoney(this.props.playerDebt)}
</Col>
</Row>
<Row collapse='true'>
<Col width='12'>
Buy using: ${this.state.cash}K cash ${this.props.cost - this.state.cash}K debt
</Col>
</Row>
<Row collapse='true'>
<Col width='2'>
<Button className='tiny'
disabled={this.props.max < this.props.min}
type='submit'>Buy</Button>
</Col>
<Col width='10'>
<div className='money'>
with cash $
<input id='cash-input' type='number'
step='1'
min='0'
max={Math.floor(this.props.max)}
disabled={this.props.max < this.props.min}
onChange={this.handleInput}
value={Math.floor(this.state.cash)} />
{'\u00A0'}K
</div>
</Col>
</Row>
{this.props.ui.cardError ? (
<Row>
<Col width="12">
<span className="error">{this.props.ui.cardError}</span>
</Col>
</Row>
) : (<></>)}
<Row collapse='true'>
<Col width="12">
<label className="small-text"><input type="checkbox" /> Reveal for trading</label>
</Col>
</Row>
</form>
</div>); break;
case 'no-card': action =
(<span></span>); break;
}
return (
<div className='game-card'>
<GroupBox title={this.props.card.title}>
<div className='card-id'>{card.id}</div>
<div className='card'
dangerouslySetInnerHTML={{__html: card.contents}} />
{this.props.hideBuying ? (<Fragment />) : action}
</GroupBox>
</div>
);
}
}
// (<option key={i}>{typeToText(c.type)}: {cropToText(c.crop)}</option>))
class CardListComp extends React.Component {
render () {
const typeToText = type => { switch (type) {
case 'otb': return itemCardShort }},
cropToText = crop => { switch (crop) {
case 'fruit': return '5 acres Fruit';
case 'grain': return '10 acres Grain'}};
const ui = this.props.ui,
cards = ui.cards,
cardOps = cards.map((c, i) =>
(<li key={i} className={c.id == this.props.cardId ? 'card-select-selected' : ''}
onClick={() => { this.props.setCard(c); this.props.setCardError(false); }}>
{c.summary}
</li>));
return (
<GroupBox title='Cards'>
<ul className='card-select'>
{cardOps}
</ul>
</GroupBox>
);
}
}
const CardList = connect(
null,
{ setSelectedCard, setCardError }
)(CardListComp)