From 16897679e37ee40103d156882ce7bcdc83c3dfe1 Mon Sep 17 00:00:00 2001 From: Thomas Hintz Date: Thu, 2 Apr 2020 10:24:06 -0700 Subject: [PATCH] New trading interface and moving bug fixes. --- src/components/farm/Board.jsx | 954 +++++++++++++++++++---------- src/components/farm/actionTypes.js | 1 + src/components/farm/actions.js | 9 +- src/components/farm/interface.js | 51 +- src/components/farm/reducers.js | 6 +- src/server/farm.scm | 30 +- src/style.scss | 88 +++ webpack.common.js | 3 +- 8 files changed, 796 insertions(+), 346 deletions(-) diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx index c813799..b1a74ae 100644 --- a/src/components/farm/Board.jsx +++ b/src/components/farm/Board.jsx @@ -31,7 +31,8 @@ import { connect } from 'react-redux' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faUser, faUsers, faTractor, faWindowRestore, faDollarSign, faTimes, faAsterisk, faExchangeAlt, - faInfoCircle, faArrowUp, faAward } from '@fortawesome/free-solid-svg-icons' + faInfoCircle, faArrowUp, faArrowDown, faAward, + faTimesCircle } from '@fortawesome/free-solid-svg-icons' import { GroupBox, Row, Col, Button } from '../widgets.jsx' import SpaceNode from './SpaceNode.jsx' @@ -40,24 +41,25 @@ 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 } from './actions.js' + nextUIAction, alert, alertHandled, setCardError, + setMovingSkip } from './actions.js' import { buy, roll, endTurn, loan, trade, submitTradeAccept, submitTradeDeny, submitTradeCancel, audit, buyUncleBert, skip } 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.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); + (player.assets.fruit * 5000) + + (player.assets.cows * 500) + + ((player.assets.harvester + player.assets.tractor) * 10000); } function getElementValue(id) { @@ -174,7 +176,7 @@ class ResourceUnit extends React.Component { render () { const hslString = 'hsl(' + this.props.h + ', ' + this.props.s; return ( -
@@ -448,6 +450,363 @@ class TradeContainer extends React.Component { } } +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 !== findPlayer(this.props.game, this.state.otherPlayer.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 ( + + + +
+

{player.name}

+ {Object.keys(player.ridges).map((ridge, idx) => ( + + {!tradeProposed && (player.ridges[ridge] > 0) && !toTrade[ridge] ? ( + + ) : (<>)} + {' '} + + ))} +
+ {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 ( + + {o.icon ? ( + <> + (K) +
+ + ) : ''} + {amount + Math.min(0, toTrade[o.key])} + {o.icon ? (
) : ''} + {!tradeProposed && ((amount + Math.min(0, toTrade[o.key])) > 0 || toTrade[o.key] > 0) ? ( + <> + + {o.key === 'money' && (amount + Math.min(0, toTrade[o.key])) >= 10 ? ( + + ) : ''} + + ) : (<>)} +
+ ); + })} +
+
+ +
+ + + {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => ( + + {(otherPlayer.ridges[ridge] > 0) && toTrade[ridge] ? ( + + ) : (<>)} + {' '} + + ))} + + + + +
+ {this.resources.map((o, i) => ( + + {Math.abs(toTrade[o.key])} + + ))} +
+ +
+ + + {Object.keys(player.ridges).map((ridge, idx) => ( + + {(player.ridges[ridge] > 0) && toTrade[ridge] ? ( + + ) : (<>)} + {' '} + + ))} + + + + +
+ {otherPlayer ? ( +
+ {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 ( + + {o.icon ? ( + <> + (K) +
+ + ) : ''} + {amount - Math.max(0, toTrade[o.key])} + {o.icon ? (
) : ''} + {!tradeProposed && ((amount - Math.max(0, toTrade[o.key])) > 0 || toTrade[o.key] < 0) ? ( + <> + + {o.key === 'money' && (amount - Math.min(0, toTrade[o.key])) >= 10 ? ( + + ) : ''} + + ) : (<>)} +
+ ); + })} +
+ ) : (<>)} + {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => ( + + {!tradeProposed && ((otherPlayer.ridges[ridge] > 0) && !toTrade[ridge]) ? ( + + ) : (<>)} + {' '} + + ))} + +
+ +
+ + + this.tradeAsset('cards', e.target.value)} /> + + + + + {player.trade.error ? ( +

ERROR: {player.trade.error}

+ ) : (<>)} + {tradeProposed && player.trade.originator === player.name ? ( + + ) : (<>)} + {tradeProposed && player.trade.originator !== player.name ? ( + <> + {' '} + + + ) : (<>)} + {!tradeProposed ? ( + + ) : (<>)} + +
+
+ ); + } +} + class CCBY extends React.Component { render() { return ( @@ -659,7 +1018,7 @@ function random(max, min = 1) { } function decay(totalMs, decayFactorPct, ticksToGo) { - return totalMs * Math.pow(1 - decayFactorPct, ticksToGo); + return totalMs * Math.pow(1 - decayFactorPct, ticksToGo); } class Die extends React.Component { @@ -679,7 +1038,7 @@ class Die extends React.Component { this.state = { num: props.roll ? this.roll() : props.num, finalFace: false, autoSkip: typeof props.autoSkip === 'undefined' ? false : - props.autoSkip }; + props.autoSkip }; this.trigger = 1000 * this.speed[props.speed ? props.speed : 'fast'] if (props.ms) { this.maxTicks = Math.floor(props.ms / this.trigger); @@ -771,62 +1130,62 @@ class Die extends React.Component { render() { let face; switch (this.state.num) { - case 1: - face = (
); - break; - case 2: - face = ( + case 1: + face = (
); + break; + case 2: + face = (
-
); - break; - case 3: - face = ( + ); + break; + case 3: + face = (
-
); - break; - case 4: - face = ( + ); + break; + case 4: + face = (
-
); - break; - case 5: - face = ( + ); + break; + case 5: + face = (
-
); - break; - case 6: - face = ( + ); + break; + case 6: + face = (
-
); - break; +
); + break; } return ( -
-
- {face} +
+
+ {face} +
-
- {this.props.skip && !this.state.finalFace ? - (
- -
) : - ()} + {this.props.skip && !this.state.finalFace ? + (
+ +
) : + ()} ); } @@ -836,7 +1195,7 @@ class PreRolling extends React.Component { render() { return ( - + ); } @@ -846,11 +1205,11 @@ class Rolling extends React.Component { render() { return ( - + ); } @@ -870,7 +1229,7 @@ class Harvest extends React.Component { super(props); this.state = { view: 'ready', autoSkip: typeof props.autoSkip === 'undefined' ? false : - props.autoSkip, + props.autoSkip, autoSkipView: false }; } @@ -880,7 +1239,7 @@ class Harvest extends React.Component { nextView = (view, preventAutoSkip) => { const newView = view ? view : - this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1]; + this.viewOrder[this.viewOrder.indexOf(this.state.view) + 1]; this.setState({ view: newView }); if (!preventAutoSkip) { skip('harvest|' + newView); @@ -905,220 +1264,147 @@ class Harvest extends React.Component { let view; const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer; switch (this.state.view) { - case 'ready': - view = ( -
-
-

{this.props.crop + ' harvest!'}

-
- - Get ready to harvest {this.props.acres} - {this.props.crop === 'cows' ? ' head of cow' : ' acres'}! + case 'ready': + view = ( +
+
+

{this.props.crop + ' harvest!'}

+
+ + Get ready to harvest {this.props.acres} + {this.props.crop === 'cows' ? ' head of cow' : ' acres'}! +
+ {isCurrentPlayer ? ( + + ) : ()} + {this.props.harvestMult !== 1 ? ( + Multiplied by {this.props.harvestMult}! + ) : (<>)} +
+
+ ); + break; + case 'roll': + view = ( this.nextView('income')} + skip={this.props.player.name === this.props.game.currentPlayer} + autoSkip={this.props.autoSkip === 'die'} + showScreenDelay={2000} />); + break; + case 'income': + view = ( +
+
+
+ +
+ {isCurrentPlayer ? 'You' : + this.props.game.currentPlayer} rolled a {this.props.rolled} and earned ${formatMoney(this.props.income)}! +
+
{isCurrentPlayer ? ( - +
+ +
) : ()} - {this.props.harvestMult !== 1 ? ( - Multiplied by {this.props.harvestMult}! - ) : (<>)}
-
- ); - break; - case 'roll': - view = ( this.nextView('income')} - skip={this.props.player.name === this.props.game.currentPlayer} - autoSkip={this.props.autoSkip === 'die'} - showScreenDelay={2000} />); - break; - case 'income': - view = ( -
-
-
- -
- {isCurrentPlayer ? 'You' : - this.props.game.currentPlayer} rolled a {this.props.rolled} and earned ${formatMoney(this.props.income)}! -
-
-
- {isCurrentPlayer ? ( -
- -
- ) : ()} -
- ); - break; - case 'operating-expense': - view = ( - -
- -
- -
- {isCurrentPlayer ? ( -
- -
- ) : ()} - - ); - break; - case 'expense-value': - const currentExpense = this.props.expenseValue[this.props.game.currentPlayer]; - view = ( -
-
-
- -
-

- {isCurrentPlayer ? 'You' : this.props.game.currentPlayer} - {currentExpense <= 0 ? ' lost ' : ' gained '} - ${formatMoney(Math.abs(currentExpense))}! -

- {Object.keys(this.props.expenseValue) - .filter(p => p !== this.props.game.currentPlayer) - .map((p, i) => - ( -

- {this.nameText(p) + ' '} - {this.props.expenseValue[p] <= 0 ? 'lost ' : 'gained '} - ${formatMoney(Math.abs(this.props.expenseValue[p]))}! -

- ) - )} -
-
-
- {isCurrentPlayer ? - ( -
- + ); + break; + case 'operating-expense': + view = ( + +
+ +
+
- ) : ()} -
- ); - break; + {isCurrentPlayer ? ( +
+ +
+ ) : ()} + + ); + break; + case 'expense-value': + const currentExpense = this.props.expenseValue[this.props.game.currentPlayer]; + view = ( +
+
+
+ +
+

+ {isCurrentPlayer ? 'You' : this.props.game.currentPlayer} + {currentExpense <= 0 ? ' lost ' : ' gained '} + ${formatMoney(Math.abs(currentExpense))}! +

+ {Object.keys(this.props.expenseValue) + .filter(p => p !== this.props.game.currentPlayer) + .map((p, i) => + ( +

+ {this.nameText(p) + ' '} + {this.props.expenseValue[p] <= 0 ? 'lost ' : 'gained '} + ${formatMoney(Math.abs(this.props.expenseValue[p]))}! +

+ ) + )} +
+
+
+ {isCurrentPlayer ? + ( +
+ +
+ ) : ()} +
+ ); + break; } return view; } } -// props: currentPos, moveTo, color -// actions: movePlayer class Moving extends React.Component { - endPos = 0 - color = '' - hurtBack = false - - constructor(props) { - super(props); - const to = this.props.ui.actionValue.to, - from = this.props.ui.actionValue.from; - 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; - const currentPos = from; - this.state = { currentPos: currentPos < 0 ? currentPos + 49 : currentPos, - autoSkip: typeof props.autoSkip === 'undefined' ? false : - props.autoSkip }; - this.endPos = to; - this.color = currentPlayer.color; - // only for hurt back do we go backwards - this.hurtBack = to === 2 && from === 11 ? true : false; - } - - componentDidMount() { - // TODO combine with tick - const delta = this.state.currentPos === 48 ? -48 : this.hurtBack ? -1 : 1; - this.props.movePlayer(this.state.currentPos + delta, - this.state.currentPos, - this.color); - this.setState(state => ({ currentPos: state.currentPos + delta})); - if (this.state.currentPos + delta !== this.endPos) { - if (this.props.timerId) { - clearInterval(this.props.timerId); - } - this.props.setTimerId(setInterval(() => this.tick(), 500)); - } - } - - componentWillUnmount() { - if (this.props.timerId) { - clearInterval(this.props.timerId); - this.props.setTimerId(false); - } - // if another player skips movement - if (this.state.currentPos !== this.endPos) { - this.props.movePlayer(this.endPos, - this.state.currentPos, - this.color); - } - } - - tick() { - const delta = this.state.currentPos === 48 ? -48 : this.hurtBack ? -1 : 1; - this.props.movePlayer(this.state.currentPos + delta, - this.state.currentPos, - this.color); - this.setState(state => ({ currentPos: state.currentPos + delta})); - if (this.state.currentPos === this.endPos) { - if (this.props.timerId) { clearInterval(this.props.timerId); } - this.props.setTimerId(false); - } - } - skip(preventAutoSkip) { if (!preventAutoSkip) { skip('moving'); } - clearInterval(this.props.timerId); - this.props.setTimerId(false); - this.props.movePlayer(this.endPos, this.state.currentPos, this.color); - this.setState({ currentPos: this.endPos }); - } - - 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 }); - } + this.props.setMovingSkip(true); } render() { let buttons; if (this.props.player.name !== this.props.game.currentPlayer) { buttons = (); - } else if (this.state.currentPos === this.endPos) { + } else if (this.props.ui.playerSpaces[this.props.player.color] === this.props.ui.actionValue.to) { buttons = (); } else { buttons = (); } + 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; return ( - -
-
- -
-
-
-
- {buttons} -
- + +
+
+ +
+
+
+
+ {buttons} +
+
); } @@ -1128,8 +1414,8 @@ class Action extends React.Component { render() { let view, buttons; 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.props.player : this.props.game.otherPlayers + .find(p => p.player.name === this.props.game.currentPlayer).player; switch (this.props.ui.action) { case 'otb': if (this.props.player.name === this.props.game.currentPlayer) { @@ -1242,6 +1528,7 @@ class Action extends React.Component { autoSkip={this.props.ui.autoSkip} timerId={this.props.timerId} setTimerId={this.props.setTimerId} + setMovingSkip={this.props.setMovingSkip} ui={this.props.ui} />); buttons = (); break; @@ -1251,6 +1538,7 @@ class Action extends React.Component { 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} @@ -1308,59 +1596,59 @@ class Board extends React.Component { render() { const rh = this.props.height || '20px'; // height={h} const renderSpace = (s, h, o) => - (); + (); return ( - - - {renderSpace(this.props.spaces[0], rh, 'corner-tl')} - -
- {this.props.spaces.slice(1, 14).map(s => renderSpace(s, rh, 'top'))} -
- - {renderSpace(this.props.spaces[14], rh, 'corner-tr')} -
- - -
- {this.props.spaces.slice(38, 49).reverse() - .map(s => renderSpace(s, this.props.height || false, 'left'))} -
- - -
-
- - - {this.props.children} - - -

Alpha Centauri Farming

- -
-
- - -
- {this.props.spaces.slice(15, 25) - .map(s => renderSpace(s, this.props.height ? (parseInt(this.props.height) * 1.11) + 'px' : false, 'right'))} -
- -
- - {renderSpace(this.props.spaces[37], rh, 'corner-bl')} - -
- {this.props.spaces.slice(26, 37).reverse() - .map(s => renderSpace(s, rh, 'bottom'))} -
- - {renderSpace(this.props.spaces[25], rh, 'corner-br')} -
-
+ + + {renderSpace(this.props.spaces[0], rh, 'corner-tl')} + +
+ {this.props.spaces.slice(1, 14).map(s => renderSpace(s, rh, 'top'))} +
+ + {renderSpace(this.props.spaces[14], rh, 'corner-tr')} +
+ + +
+ {this.props.spaces.slice(38, 49).reverse() + .map(s => renderSpace(s, this.props.height || false, 'left'))} +
+ + +
+
+ + + {this.props.children} + + +

Alpha Centauri Farming

+ +
+
+ + +
+ {this.props.spaces.slice(15, 25) + .map(s => renderSpace(s, this.props.height ? (parseInt(this.props.height) * 1.11) + 'px' : false, 'right'))} +
+ +
+ + {renderSpace(this.props.spaces[37], rh, 'corner-bl')} + +
+ {this.props.spaces.slice(26, 37).reverse() + .map(s => renderSpace(s, rh, 'bottom'))} +
+ + {renderSpace(this.props.spaces[25], rh, 'corner-br')} +
+
); - } + } } // handler, buttonText, children @@ -1368,7 +1656,7 @@ class AlertOverlay extends React.Component { constructor(props) { super(props); this.state = { visible: typeof this.props.visible !== 'undefined' ? - this.props.visible : true }; + this.props.visible : true }; } hide = e => { @@ -1382,20 +1670,20 @@ class AlertOverlay extends React.Component { this.hide(e); this.props.handler(); } - // + // render() { return (
-
- -
-
- {this.props.children} -
- - close -
+
+ +
+
+ {this.props.children} +
+ + close +
); } @@ -1406,7 +1694,7 @@ class InfoBar extends React.Component { if (this.props.players.length > 0) { return (
- {(this.props.screen === SCREENS.action) ? '' : this.props.message} + {(this.props.screen === SCREENS.action) ? '' : this.props.message}
); } else { @@ -1433,11 +1721,11 @@ class HarvestTable extends React.Component { selectedCrop = this.state.selected; return (
-

Crop

- + {Object.keys(table).map((k, idx) => ( + + ))} @@ -1578,7 +1866,7 @@ class BoardApp extends React.Component { 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 ' : ' ')); + : (icon === 'tractor' ? ' hide-for-large ' : ' ')); } tabClass = screen => { @@ -1603,12 +1891,12 @@ class BoardApp extends React.Component { buttonText='Close' hideHandler={() => 'nothing'} handler={() => { return false; }}> - -

Game Over!

- {alert.contents.map((e, i) => ( -

{e}

- ))} -
+ +

Game Over!

+ {alert.contents.map((e, i) => ( +

{e}

+ ))} +
); } else if (alert && alert.type === ALERTS.auditCalled) { @@ -1619,11 +1907,11 @@ class BoardApp extends React.Component { buttonText='Close' hideHandler={() => 'nothing'} handler={() => { return false; }}> - -

Audit Called!

-

{alert.contents} called an audit!

-

This is your last trip around the board.

-
+ +

Audit Called!

+

{alert.contents} called an audit!

+

This is your last trip around the board.

+
); } else if (alert && alert.type === ALERTS.proposedTrade) { @@ -1705,6 +1993,7 @@ class BoardApp extends React.Component { 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 ( @@ -1768,8 +2057,8 @@ class BoardApp extends React.Component { -
- +
+
@@ -1792,7 +2081,8 @@ class BoardApp extends React.Component { export default connect( state => state.farm, - { setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert, alertHandled, setCardError } + { setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert, alertHandled, + setCardError, setMovingSkip } )(BoardApp) class Card extends React.Component { @@ -1873,19 +2163,19 @@ class Card extends React.Component {
); break; - case 'no-card': action = + case 'no-card': action = (); break; } return (
- -
{card.id}
-
- {this.props.hideBuying ? () : action} - + +
{card.id}
+
+ {this.props.hideBuying ? () : action} +
- ); + ); } } @@ -1907,11 +2197,11 @@ class CardListComp extends React.Component { )); return ( - +
    {cardOps}
-
+
); } } diff --git a/src/components/farm/actionTypes.js b/src/components/farm/actionTypes.js index b31c71c..77d31af 100644 --- a/src/components/farm/actionTypes.js +++ b/src/components/farm/actionTypes.js @@ -38,3 +38,4 @@ export const ALERT_HANDLED = 'alert-handled' export const AUTO_SKIP = 'auto-skip' export const MESSAGE = 'message' export const SET_HARVEST_TABLE = 'set-harvest-table' +export const SET_MOVING_SKIP = 'set-moving-skip' diff --git a/src/components/farm/actions.js b/src/components/farm/actions.js index 2af7f2b..43a241e 100644 --- a/src/components/farm/actions.js +++ b/src/components/farm/actions.js @@ -20,13 +20,14 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS, SPACE_PUSH_PLAYER, SPACE_CLEAR_PLAYERS, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION, MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, - AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR } from './actionTypes.js' + AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR, + SET_MOVING_SKIP } from './actionTypes.js' export { updateGame, updatePlayer, gameState, setSelectedCard, setCards, spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace, mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction, markActionChangeHandled, nextUIActionSilent, alert, alertHandled, - autoSkip, message, setHarvestTable, setCardError } + autoSkip, message, setHarvestTable, setCardError, setMovingSkip } function updateGame(update) { return { type: UPDATE_GAME, @@ -130,3 +131,7 @@ function message(message) { function setHarvestTable(table) { return { type: SET_HARVEST_TABLE, table }; } + +function setMovingSkip(skip) { + return { type: SET_MOVING_SKIP, skip }; +} diff --git a/src/components/farm/interface.js b/src/components/farm/interface.js index c7b29ad..59fd45e 100644 --- a/src/components/farm/interface.js +++ b/src/components/farm/interface.js @@ -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, message, alertHandled, setHarvestTable, setCardError } from './actions.js' + autoSkip, message, alertHandled, setHarvestTable, + setCardError, setMovingSkip } from './actions.js' import { itemCard, fateCard } from 'game.js' export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, @@ -189,7 +190,9 @@ function initialize(st, sc) { let lastAction = false, lastAutoSkip = false, rollMessage = '', - rollTimer = false; + rollTimer = false, + movingAction = { from: 99, to: 99 }, + movingTimer = 0; const unsubscribe = store.subscribe( () => { const state = store.getState(); @@ -265,6 +268,50 @@ function initialize(st, sc) { break; } } + if ((state.farm.ui.action === 'move' || state.farm.ui.action === 'goto') && + movingAction.from !== state.farm.ui.actionValue.from && + movingAction.to !== state.farm.ui.actionValue.to) { + movingAction = state.farm.ui.actionValue; + const movingProc = (player, from, to) => { + let currentPos = from < 0 ? from + 49 : from; + const color = player.color; + const hurtBack = to === 2 && from === 11 ? true : false; + const delta = currentPos === 48 ? -48 : hurtBack ? -1 : 1; + store.dispatch(movePlayer(currentPos + delta, currentPos, color)); + currentPos += delta; + + const tick = () => { + if (store.getState().farm.ui.movingSkip || + store.getState().farm.ui.autoSkip === 'moving') { + store.dispatch(setMovingSkip(false)); + store.dispatch(movePlayer(to, currentPos, color)); + currentPos = to; + } else { + const delta = currentPos === 48 ? -48 : hurtBack ? -1 : 1; + store.dispatch(movePlayer(currentPos + delta, currentPos, color)); + currentPos += delta; + } + if (currentPos === to) { + clearInterval(movingTimer); + movingTimer = 0; + } + } + + if (currentPos !== to) { + movingTimer = setInterval(tick, 500); + } + } + movingProc(state.farm.player.name === state.farm.game.currentPlayer ? + state.farm.player : state.farm.game.otherPlayers + .find(p => p.player.name === state.farm.game.currentPlayer).player, + movingAction.from, movingAction.to); + } else if (state.farm.ui.action !== 'move' && state.farm.ui.action !== 'goto') { + if (movingTimer) { + clearInterval(movingTimer); + movingTimer = 0; + movingAction = { from: 99, to: 99 }; + } + } }); // mpDims.mouseX = e.clientX diff --git a/src/components/farm/reducers.js b/src/components/farm/reducers.js index 83808e8..cfe7eae 100644 --- a/src/components/farm/reducers.js +++ b/src/components/farm/reducers.js @@ -21,7 +21,8 @@ import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, MP_MOUSE, SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION, MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, - AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR } from './actionTypes.js' + AUTO_SKIP, MESSAGE, SET_HARVEST_TABLE, SET_CARD_ERROR, + SET_MOVING_SKIP } from './actionTypes.js' import { GAME_STATES } from '../../constants.js' import { spaceContent, corners } from 'game.js' @@ -121,6 +122,7 @@ const initialState = { unhandledAlert: false, autoSkip: false, playerSpaces: {}, + movingSkip: false, harvestTable: false }, spaces: spaces, space: null, @@ -219,6 +221,8 @@ export default function(state = initialState, action) { return { ...state, ui: { ...state.ui, message: action.message }}; case SET_HARVEST_TABLE: return { ...state, ui: { ...state.ui, harvestTable: action.table }}; + case SET_MOVING_SKIP: + return { ...state, ui: { ...state.ui, movingSkip: action.skip }}; default: return state; } diff --git a/src/server/farm.scm b/src/server/farm.scm index be74496..c52dce4 100644 --- a/src/server/farm.scm +++ b/src/server/farm.scm @@ -584,6 +584,16 @@ (when (alist-ref ridge params) (if (> (player-ridge player ridge) 0) (begin + (safe-set! + (player-assets originator) + (alist-update 'cows (+ (alist-ref 'cows (player-assets originator)) + (alist-ref ridge (player-ridges player))) + (player-assets originator))) + (safe-set! + (player-assets player) + (alist-update 'cows (- (alist-ref 'cows (player-assets player)) + (alist-ref ridge (player-ridges player))) + (player-assets player))) (safe-set! (player-ridges originator) (alist-update ridge (alist-ref ridge (player-ridges player)) @@ -591,6 +601,16 @@ (safe-set! (player-ridges player) (alist-update ridge 0 (player-ridges player)))) (begin + (safe-set! + (player-assets originator) + (alist-update 'cows (- (alist-ref 'cows (player-assets originator)) + (alist-ref ridge (player-ridges originator))) + (player-assets originator))) + (safe-set! + (player-assets player) + (alist-update 'cows (+ (alist-ref 'cows (player-assets player)) + (alist-ref ridge (player-ridges originator))) + (player-assets player))) (safe-set! (player-ridges player) (alist-update ridge (alist-ref ridge (player-ridges originator)) @@ -1903,17 +1923,11 @@ ;; TODO ;; make sure two players can't have the same name ;; info actions should look better -;; you can get $50 from harvest -;; moving bug 5 from oct 2 saw by player 2 -;; from harvest moon rolled 5 -;; finished -;; infinite loop ((?action . end-game) (?value . #)) +;; you can get $50 from operating expense ;; auto-skip loop ;; harvester / tractor don't say total price ;; mark spaces -;; trade notification keeps popping up - -;; show harvest multiplier +;; can't trade other player's ridge diff --git a/src/style.scss b/src/style.scss index 2732007..c602a94 100644 --- a/src/style.scss +++ b/src/style.scss @@ -268,8 +268,77 @@ $tab-margin: 0.3rem; .player-trade-resources .resource-unit { display: inline-block; + margin-bottom: 0.3rem; width: 4rem; } +.resource-unit.double-width { + width: 84px; +} + +.resource-unit.double-width .button:last-child { + margin-left: 0.2rem; +} + +.player-trade-resources img { + margin-top: 0.2rem; + margin-bottom: 0.2rem; } + +.trade-player-container { + border: 0.2rem solid $dark-color; + border-radius: 0.2rem; + background: #ccc; + padding: 0.3rem; + .button { + margin: 0; } +} + +$trade-margin: 3rem; +.resources-to-trade { + margin-left: 0.5rem; + visibility: hidden; + .resource-unit { + position: relative; + margin-top: $trade-margin / 2; + margin-bottom: $trade-margin / 2; + } + + .trade-asset-cancel { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + color: red; + padding: 0.2rem; + display: none; + cursor: pointer; + opacity: 0.7; + svg { + width: 100%; + height: 100%; + } + } + + .trade-to { + margin-top: ($trade-margin / 2) - 1.2; + margin-bottom: ($trade-margin / 2) + 1.2; + visibility: visible; + } + + .trade-to:hover .trade-asset-cancel { + display: block; + } + .trade-from:hover .trade-asset-cancel { + display: block; + } + + .trade-from { + margin-bottom: ($trade-margin / 2) - 1.2; + margin-top: ($trade-margin / 2) + 1.2; + visibility: visible; + } +} + .card-id { position: absolute; top: 10px; @@ -305,6 +374,7 @@ $tab-margin: 0.3rem; } .resource-unit { + position: relative; text-align: center; margin-right: 0.5rem; width: 40px; @@ -313,6 +383,19 @@ $tab-margin: 0.3rem; height: 34px; } } +.resource-doubled { + position: absolute; + top: -0.7rem; + right: -0.2rem; + color: blue; + font-size: 1.4rem; + padding: 0.1rem; + transform: rotate(17deg); + svg { + filter: drop-shadow( 1px 1px 1px rgba(0, 0, 0, .7)); + } +} + .resource-unit-container { display: flex; flex-direction: row; } @@ -556,12 +639,17 @@ $tab-margin: 0.3rem; max-height: 80vh; @include breakpoint(medium down) { flex-grow: 2; + max-width: 82vw; } @include breakpoint(large) { width: 26rem; } } +.trade-tab { + min-width: 26rem; +} + .static-tab-container { overflow-y: auto; max-height: 80vh; diff --git a/webpack.common.js b/webpack.common.js index 5bd0cc7..33fc4b8 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -46,7 +46,8 @@ module.exports = { chunkFilename: '[id].css', }), new CopyPlugin([ - { from: './src/server/', to: './' }, + { from: './src/server/farm.scm', to: './[name].[ext]' }, + { from: './src/server/farm', to: './[name]' }, { from: './assets/game/', to: './assets/game/' } ]), new webpack.LoaderOptionsPlugin({