From bea133bee40582414a401764232266f91112d1ff Mon Sep 17 00:00:00 2001 From: Thomas Hintz Date: Sat, 25 Apr 2020 13:00:36 -0700 Subject: [PATCH] Visual card trading. --- src/components/farm/Board.jsx | 475 +++++++++++++------------- src/components/farm/interface.js | 6 +- src/components/farm/reducers.js | 2 + src/components/join-game/JoinGame.jsx | 2 +- src/server/farm.scm | 43 ++- 5 files changed, 266 insertions(+), 262 deletions(-) diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx index 43731d8..701c497 100644 --- a/src/components/farm/Board.jsx +++ b/src/components/farm/Board.jsx @@ -32,9 +32,9 @@ 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, + faDollarSign, faTimes, faExchangeAlt, faInfoCircle, faArrowUp, faArrowDown, faAward, - faTimesCircle, faBan } from '@fortawesome/free-solid-svg-icons' + faBan, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons' import { GroupBox, Row, Col, Button } from '../widgets.jsx' import SpaceNode from './SpaceNode.jsx' @@ -48,7 +48,7 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer, import { buy, roll, endTurn, loan, trade, submitTradeAccept, submitTradeDeny, submitTradeCancel, audit, buyUncleBert, skip, endAiTurn, startGame, readyToStart, - leaveGame, kickPlayer } from './interface.js' + leaveGame, kickPlayer, toggleRevealForTrade } from './interface.js' function netWorth(player) { return ((player.assets.hay + player.assets.grain) * 2000) + @@ -65,116 +65,6 @@ function assetsValue(player) { ((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 (

ERROR: {this.props.player.trade.error}

); - } else if (this.props.player.trade.player === undefined) { - return (

You GAIN something if it is POSITIVE and you GIVE something if it is NEGATIVE.

); - } else if (this.props.player.trade.originator === this.props.player.name) { - return (

You proposed a trade with {this.props.player.trade.player} for - {'\u00A0'}{tradeString(this.props.player, false)}.

); - } else { - return (

{this.props.player.trade.originator} proposed a trade for - {'\u00A0'}{tradeString(this.props.player, true)}.

); - } - } -} - -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 (); - } else { - return ( - - - ); - } - } -} - class ResourceUnit extends React.Component { render () { const hslString = 'hsl(' + this.props.h + ', ' + this.props.s; @@ -197,61 +87,6 @@ class ResourceUnit extends React.Component { } } -class PlayerTradeResources extends React.Component { - render () { - let player = this.props.player; - return ( -
tradeFormSubmit(e, player)}> -
- - - {' '} - - - {' '} - - - {' '} - - - {' '} - - - {' '} - - - -
- Ridges: {ridgeNames[0][0]}: {'\u00A0'} - R: {'\u00A0'} - C: {'\u00A0'} - T: {'\u00A0'} -

- - - - - - - - - - - - - - - - -
-
); - } -} - class PlayerResources extends React.Component { render () { const player = this.props.player; @@ -495,13 +330,75 @@ class FarmsContainer extends React.Component { } } -class TradeContainer extends React.Component { +class TradeCard extends React.Component { + render () { + const card = this.props.card; + let action = (null); + if (card.type === 'no-card') { + action = (); + } else { + action = ( +
+ + + + + +
+ ); + } + return ( +
+ +
+ {action} + +
+ ); + } +} + +const makeEmptyCard = () => { return { type: 'no-card', contents: '', total: 0, id: -1 }; } + +class TradeCards extends React.Component { + state = { + selectedCard: makeEmptyCard() + } + + componentDidMount() { + if (this.props.player.cards.length > 0) { + this.setState({ selectedCard: this.props.player.cards[0] }); + } + } + render() { + const { player, cardsBeingTraded } = this.props; + const isMe = this.props.me.name === player.name; return ( - - - + <> + + + + + {isMe ? ( + <>Your Cards + ) : ( + <>{player.name}'s Cards + )} + + )} + visibleCards={isMe ? player.cards.filter(c => !cardsBeingTraded.includes(c.id)).map(c => c.id) : + player.revealedCards.filter(c => !cardsBeingTraded.includes(c))} + cardId={this.state.selectedCard.id} + setCard={(c) => this.setState({ selectedCard: c })} + setCardError={() => 'nothing'} + /> + + ); } } @@ -531,7 +428,16 @@ const makeDefaultToTrade = () => { }; } -class TradeContainer2 extends React.Component { + +/* + * + * this.tradeAsset('cards', e.target.value)} /> + * + * */ + +class TradeContainer extends React.Component { resources = [{ img: HayImg, h: '120', s: '100', @@ -584,11 +490,16 @@ class TradeContainer2 extends React.Component { state = { otherPlayer: findPlayer(this.props.game, this.props.game.otherPlayers.length > 0 ? this.props.game.otherPlayers[0].player.name : ''), - toTrade: makeDefaultToTrade() + toTrade: makeDefaultToTrade(), + showCards: false, + showCardsPlayer: false, + sendCards: [], + receiveCards: [] } selectPlayer = e => { - this.setState({ otherPlayer: findPlayer(this.props.game, e.target.value) }); + this.setState({ otherPlayer: findPlayer(this.props.game, e.target.value), + receiveCards: [] }); } componentDidUpdate(prevProps) { @@ -596,8 +507,10 @@ class TradeContainer2 extends React.Component { 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) }); + } 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), + receiveCards: [], + sendCards: [] }); } if (prevProps.player.trade.originator && !this.props.player.trade.originator) { this.setState({ otherPlayer: findPlayer(this.props.game, @@ -624,14 +537,22 @@ class TradeContainer2 extends React.Component { } else { tradeObj = this.props.player.trade; } + const cards = tradeObj.cards.split(' ').map(i => parseInt(i)), + myCards = this.props.player.cards.filter(c => cards.includes(c.id)), + myCardsIds = myCards.map(c => c.id); + console.log(cards); + console.log(myCards) + const otherPlayer = findPlayer(this.props.game, isOriginator ? + this.props.player.trade.player : this.props.player.trade.originator); 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) + receiveCards: otherPlayer.cards.filter(c => cards.includes(c.id) && !myCardsIds.includes(c.id)), + sendCards: myCards, + otherPlayer }); } } @@ -659,11 +580,35 @@ class TradeContainer2 extends React.Component { propose = () => { trade({ ...this.state.toTrade, + cards: this.state.sendCards.map(c => c.id).concat(this.state.receiveCards.map(c => c.id)).join(' '), money: this.state.toTrade.money * 1000, player: this.state.otherPlayer.name }) } + viewPlayerCards = (e, player) => { + e.preventDefault(); + this.setState(state => { + return { showCards: !state.showCards, + showCardsPlayer: player }; + }); + } + + tradeCard = (card) => { + if (this.props.player === this.state.showCardsPlayer) { + this.setState({ sendCards: [...this.state.sendCards, card], + showCards: false }); + } else { + this.setState({ receiveCards: [...this.state.receiveCards, card], + showCards: false }); + } + } + + unTradeCard = (id) => { + this.setState({ sendCards: this.state.sendCards.filter(c => c.id !== id), + receiveCards: this.state.receiveCards.filter(c => c.id !== id) }); + } + render() { const player = this.props.player, otherPlayer = this.state.otherPlayer, @@ -671,59 +616,78 @@ class TradeContainer2 extends React.Component { 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 ? ( - - ) : ''} - - ) : (<>)} -
- ); - })} -
-
+ {this.state.showCards ? ( + c.id).concat(this.state.receiveCards.map(c => c.id))} + /> + ) : ( + <> + + +
+

+ {player.name} + {player.cards.length > 0 ? ( + <> + {' - '} + this.viewPlayerCards(e, player)}> + Trade Cards + + + ) : (<>)} +

+ {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 ? ( + + ) : ''} + + ) : (<>)} +
+ ); + })} +
+
@@ -739,6 +703,13 @@ class TradeContainer2 extends React.Component { {' '} ))} + {this.state.receiveCards.map((card, idx) => ( + + ))} @@ -769,6 +740,13 @@ class TradeContainer2 extends React.Component { {' '} ))} + {this.state.sendCards.map((card, idx) => ( + + ))} @@ -825,24 +803,21 @@ class TradeContainer2 extends React.Component { ))} + {otherPlayer && otherPlayer.revealedCards.length > 0 ? ( + this.viewPlayerCards(e, otherPlayer)}> + Trade Cards + + ) : (<>)}
- - - this.tradeAsset('cards', e.target.value)} /> - - {player.trade.error ? ( @@ -862,6 +837,7 @@ class TradeContainer2 extends React.Component { ) : (<>)} + )} ); } @@ -1988,7 +1964,7 @@ const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms', 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, + 'exchange-alt': SCREENS.trade, tractor: SCREENS.action, 'info-circle': SCREENS.info } unsubscribeAlert = () => null @@ -2293,6 +2269,7 @@ class BoardApp extends React.Component { setCardError={this.props.setCardError} playerCash={this.props.player.displayCash} playerDebt={this.props.player.debt} + revealedCards={this.props.player.revealedCards} card={this.state.card} cost={this.state.card.total / 1000} min={(this.state.card.total * this.props.game.settings.downPayment) / 1000} @@ -2310,7 +2287,7 @@ class BoardApp extends React.Component { otherPlayers={this.props.game.otherPlayers} />
- +
@@ -2419,7 +2396,11 @@ class Card extends React.Component { ) : (<>)} - + @@ -2451,7 +2432,7 @@ class CardListComp extends React.Component { case 'fruit': return '5 acres Fruit'; case 'grain': return '10 acres Grain'}}; const ui = this.props.ui, - cards = ui.cards, + cards = ui.cards.filter(this.props.visibleCards ? (c) => this.props.visibleCards.includes(c.id) : (c) => c), cardOps = cards.map((c, i) => (
  • { this.props.setCard(c); this.props.setCardError(false); }}> @@ -2459,7 +2440,7 @@ class CardListComp extends React.Component {
  • )); return ( - +
      {cardOps}
    diff --git a/src/components/farm/interface.js b/src/components/farm/interface.js index a21b62c..a2d270d 100644 --- a/src/components/farm/interface.js +++ b/src/components/farm/interface.js @@ -31,7 +31,7 @@ import { itemCard, fateCard } from 'game.js' export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, submitTradeDeny, submitTradeCancel, audit, handleMessage, nextAction, buyUncleBert, actionsFinished, skip, endAiTurn, - startGame, readyToStart, leaveGame, kickPlayer } + startGame, readyToStart, leaveGame, kickPlayer, toggleRevealForTrade } let store; let movingTimer = 0; @@ -217,6 +217,10 @@ function kickPlayer(name) { sendCommand({ type: 'kick-player', name }); } +function toggleRevealForTrade(id) { + sendCommand({ type: 'toggle-reveal-for-trading', id }); +} + // TODO share with Board.jsx // http://stackoverflow.com/questions/149055 function formatMoney(n) { diff --git a/src/components/farm/reducers.js b/src/components/farm/reducers.js index 2ae0baa..8e79603 100644 --- a/src/components/farm/reducers.js +++ b/src/components/farm/reducers.js @@ -91,6 +91,8 @@ const initialState = { assets: { hay: 10, grain: 10, fruit: 0, cows: 0, harvester: 0, tractor: 0, birthday: 0 }, color: '', name: '', + cards: [], + revealedCards: [], ridges: { ridge1: 0, ridge2: 0, ridge3: 0, ridge4: 0 }, space: 0, hayDoubled: false, diff --git a/src/components/join-game/JoinGame.jsx b/src/components/join-game/JoinGame.jsx index 653247d..9d71f83 100644 --- a/src/components/join-game/JoinGame.jsx +++ b/src/components/join-game/JoinGame.jsx @@ -86,7 +86,7 @@ class JoinGame extends React.Component { return ( - + Join Game diff --git a/src/server/farm.scm b/src/server/farm.scm index 6627b7f..2073fb1 100644 --- a/src/server/farm.scm +++ b/src/server/farm.scm @@ -115,6 +115,7 @@ (harvest-mult initform: 1 accessor: player-harvest-mult) (otbs initform: '() accessor: player-otbs) (farmers-fates initform: '() accessor: player-farmers-fates) + (revealed-cards initform: '() accessor: player-revealed-cards) (year-rules initform: '() accessor: player-year-rules) (next-year-rules initform: '() accessor: player-next-year-rules) (color initform: #f accessor: player-color) @@ -182,7 +183,8 @@ (harvest-mult . ,(player-harvest-mult player)) (otbs . ,(player-otbs player)) (farmers-fates . ,(map (cut alist-ref 'id <>) (player-farmers-fates player))) - (year-rules . ,(player-year-rules player)) ;; TODO check if all are serializable + (revealed-cards . ,(player-revealed-cards player)) + (year-rules . ,(player-year-rules player)) (next-year-rules . ,(player-next-year-rules player)) (color . ,(player-color player)) (name . ,(player-name player)) @@ -306,18 +308,21 @@ (set! *app* (sexp->app (read)))))) (define (sexp->player x) - (apply make - 'farmers-fates (let ((ffs (alist-ref 'farmers-fates x))) - (list-copy - (filter (lambda (card) - (member (alist-ref 'id card) ffs)) - *farmers-fates-cards*))) - (fold (lambda (k r) (cons k (cons (alist-ref k x) r))) - '() - '(cash debt space previous-space state assets ridges - harvest-mult otbs user-id - year-rules next-year-rules hay-doubled corn-doubled - color name trade last-updated last-cash)))) + (let ((p (apply make + 'farmers-fates (let ((ffs (alist-ref 'farmers-fates x))) + (list-copy + (filter (lambda (card) + (member (alist-ref 'id card) ffs)) + *farmers-fates-cards*))) + (fold (lambda (k r) (cons k (cons (alist-ref k x) r))) + '() + '(cash debt space previous-space state assets ridges + harvest-mult otbs user-id revealed-cards + year-rules next-year-rules hay-doubled corn-doubled + color name trade last-updated last-cash))))) + (when (not (player-revealed-cards p)) + (safe-set! (player-revealed-cards p) '())) + p)) (define (shuffle l) (map cdr @@ -521,6 +526,7 @@ (state . ,(symbol->string (player-state p))) (cards . ,(list->vector (append (player-farmers-fates p) (player-otbs p)))) + (revealedCards . ,(list->vector (player-revealed-cards p))) (color . ,(symbol->string (player-color p))) (name . ,(player-name p)) (user-id . ,(player-user-id p)) @@ -1334,6 +1340,16 @@ (message-players! game player '() type: "update")) (create-ws-response player "update" '())) (begin (create-ws-response player "update" '())))) + ((string=? type "toggle-reveal-for-trading") + (let ((id (alist-ref 'id msg))) + (if (member id (player-revealed-cards player)) + (safe-set! (player-revealed-cards player) + (filter (lambda (cid) (not (eqv? cid id))) + (player-revealed-cards player))) + (safe-set! (player-revealed-cards player) + (cons id (player-revealed-cards player))))) + (message-players! game player '() type: "update") + (create-ws-response player "update" '())) ;;;;;;;;;;;;;;;;;;;;; start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ((string=? type "main-init") (create-start-response "start-init")) @@ -2384,3 +2400,4 @@ ;; TODO ;; make sure two players can't have the same name ;; "your turn to roll" showing up on mobile when on action screen +;; trade cards better