From 2d8aee8c65bce7ae9272535162979f0a7047ae08 Mon Sep 17 00:00:00 2001 From: Thomas Hintz Date: Mon, 10 Feb 2020 15:00:14 -0800 Subject: [PATCH] Syncing multi-player actions. --- src/components/farm/Board.jsx | 105 ++++++++++++++++++++----- src/components/farm/actionTypes.js | 1 + src/components/farm/actions.js | 11 ++- src/components/farm/interface.js | 13 +++- src/components/farm/reducers.js | 8 +- src/main.jsx | 3 +- src/server/farm.scm | 119 +++++++++++++++-------------- 7 files changed, 171 insertions(+), 89 deletions(-) diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx index c3d79f0..ba7fef3 100644 --- a/src/components/farm/Board.jsx +++ b/src/components/farm/Board.jsx @@ -43,7 +43,7 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer, nextUIAction, alert } from './actions.js' import { buy, roll, endTurn, loan, trade, submitTradeAccept, submitTradeDeny, submitTradeCancel, audit, - buyUncleBert } from './interface.js' + buyUncleBert, skip } from './interface.js' function netWorth(player) { return ((player.assets.hay + player.assets.grain) * 2000) + @@ -594,7 +594,9 @@ class Die extends React.Component { constructor(props) { super(props); this.state = { num: props.roll ? this.roll() : props.num, - finalFace: false } + 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); @@ -657,7 +659,10 @@ class Die extends React.Component { } } - skip() { + skip(preventAutoSkip) { + if (!preventAutoSkip) { + skip('die'); + } clearInterval(this.timerId); this.timerId = false; this.setState({ num: this.props.num, finalFace: true }); @@ -670,6 +675,16 @@ class Die extends React.Component { } } + 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) { @@ -751,6 +766,7 @@ class Rolling extends React.Component { ); @@ -765,21 +781,43 @@ class Harvest extends React.Component { fruit: FruitImg, corn: CornImg } + viewOrder = ['roll', 'income', 'operating-expense', 'expense-value', false] + constructor(props) { super(props); - this.state = { view: 'ready' } + this.state = { view: 'ready', + autoSkip: typeof props.autoSkip === 'undefined' ? false : + props.autoSkip, + autoSkipView: false }; } - nextView = view => { - this.setState({ view }); + 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] }); + } + } + + // this.props.player.name === this.props.game.currentPlayer TODO render() { let view; + const isCurrentPlayer = this.props.player.name === this.props.game.currentPlayer; switch (this.state.view) { case 'ready': view = ( @@ -791,7 +829,7 @@ class Harvest extends React.Component { Get ready to harvest {this.props.acres} {this.props.crop === 'cows' ? ' head of cow' : ' acres'}! - {this.props.player.name === this.props.game.currentPlayer ? ( + {isCurrentPlayer ? ( @@ -804,6 +842,7 @@ class Harvest extends React.Component { view = ( this.nextView('income')} skip={true} + autoSkip={this.props.autoSkip === 'die'} showScreenDelay={2000} />); break; case 'income': @@ -813,14 +852,16 @@ class Harvest extends React.Component {
- {this.props.player.name === this.props.game.currentPlayer ? 'You' : + {isCurrentPlayer ? 'You' : this.props.game.currentPlayer} rolled a {this.props.rolled} and earned ${formatMoney(this.props.income)}!
- -
- -
+ + {isCurrentPlayer ? ( +
+ +
+ ) : ()} ); break; @@ -833,9 +874,11 @@ class Harvest extends React.Component { dangerouslySetInnerHTML={{__html: this.props.contents}} /> -
- -
+ {isCurrentPlayer ? ( +
+ +
+ ) : ()} ); break; @@ -846,16 +889,18 @@ class Harvest extends React.Component {
- {this.props.player.name === this.props.game.currentPlayer ? 'You' : + {isCurrentPlayer ? 'You' : this.props.game.currentPlayer} {this.props.expenseValue < 0 ? 'lost ' : 'gained '} ${Math.abs(this.props.expenseValue)}!
- + + {isCurrentPlayer ? (
- + ) : ()} + ); break; } @@ -879,7 +924,9 @@ class Moving extends React.Component { 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 }; + 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 @@ -923,13 +970,26 @@ class Moving extends React.Component { } } - skip() { + skip(preventAutoSkip) { + if (!preventAutoSkip) { + skip('moving'); + } clearInterval(this.timerId); this.timerId = 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 }); + } + } + render() { let buttons; if (this.props.player.name !== this.props.game.currentPlayer) { @@ -1036,6 +1096,7 @@ class Action extends React.Component { break; case 'harvest': view = ( this.props.showNextAction()} />); buttons = (); break; @@ -1053,6 +1115,7 @@ class Action extends React.Component { game={this.props.game} spaces={this.props.spaces} movePlayer={this.props.movePlayer} + autoSkip={this.props.ui.autoSkip} ui={this.props.ui}/>); buttons = (); break; @@ -1063,6 +1126,7 @@ class Action extends React.Component { game={this.props.game} spaces={this.props.spaces} movePlayer={this.props.movePlayer} + autoSkip={this.props.ui.autoSkip} ui={this.props.ui} />); buttons = (); break; @@ -1079,6 +1143,7 @@ class Action extends React.Component { showScreen={this.props.player.name === this.props.game.currentPlayer ? () => this.props.showNextAction() : false} + autoSkip={this.props.ui.autoSkip} skip={this.props.player.name === this.props.game.currentPlayer} showScreenDelay={2000} />); buttons = (); diff --git a/src/components/farm/actionTypes.js b/src/components/farm/actionTypes.js index 8c4f164..c27ada2 100644 --- a/src/components/farm/actionTypes.js +++ b/src/components/farm/actionTypes.js @@ -34,3 +34,4 @@ export const NEXT_UI_ACTION_SILENT = 'next-ui-action-silent' export const MARK_ACTION_CHANGE_HANDLED = 'mark-action-change-handled' export const ALERT = 'alert' export const ALERT_HANDLED = 'alert-handled' +export const AUTO_SKIP = 'auto-skip' diff --git a/src/components/farm/actions.js b/src/components/farm/actions.js index 1921671..889b293 100644 --- a/src/components/farm/actions.js +++ b/src/components/farm/actions.js @@ -19,13 +19,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 - } from './actionTypes.js' + MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, + AUTO_SKIP } from './actionTypes.js' export { updateGame, updatePlayer, gameState, setSelectedCard, setCards, spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace, mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction, - markActionChangeHandled, nextUIActionSilent, alert, alertHandled } + markActionChangeHandled, nextUIActionSilent, alert, alertHandled, + autoSkip } function updateGame(update) { return { type: UPDATE_GAME, @@ -113,3 +114,7 @@ function alert(value) { function alertHandled() { return { type: ALERT_HANDLED }; } + +function autoSkip(component) { + return { type: AUTO_SKIP, component }; +} diff --git a/src/components/farm/interface.js b/src/components/farm/interface.js index 042b309..7080369 100644 --- a/src/components/farm/interface.js +++ b/src/components/farm/interface.js @@ -23,12 +23,12 @@ import * as websocket from '../../websocket.js' import { updateGame, updatePlayer, gameState, setSelectedCard, setCards, movePlayer, setOldMessages, markActionChangeHandled, - mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert - } from './actions.js' + mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert, + autoSkip } from './actions.js' export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, submitTradeDeny, submitTradeCancel, audit, handleMessage, - nextAction, buyUncleBert, actionsFinished } + nextAction, buyUncleBert, actionsFinished, skip } let store; @@ -94,6 +94,9 @@ function handleMessage(evt) { !store.getState().farm.ui.nextAction) { store.dispatch(alert(ALERTS.raiseMoney)); } + if (data.event === 'auto-skip') { + store.dispatch(autoSkip(data.component)); + } }); }; @@ -151,6 +154,10 @@ function actionsFinished() { sendCommand({ type: 'actions-finished' }); } +function skip(component) { + sendCommand({ type: 'skip', component }); +} + function initialize(st, sc) { store = st; sendCommand = sc; diff --git a/src/components/farm/reducers.js b/src/components/farm/reducers.js index 2baf4e6..ec58f1a 100644 --- a/src/components/farm/reducers.js +++ b/src/components/farm/reducers.js @@ -20,8 +20,8 @@ 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, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION, - MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED - } from './actionTypes.js' + MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED, + AUTO_SKIP } from './actionTypes.js' import { GAME_STATES } from '../../constants.js' import { spaceContent, corners } from 'game.js' @@ -108,6 +108,7 @@ const initialState = { nextActionValue: null, actionChangeHandled: true, alert: false, + autoSkip: false, alertHandled: false }, spaces: spaces, space: null, @@ -164,6 +165,7 @@ export default function(state = initialState, action) { case NEXT_UI_ACTION: return { ...state, ui: { ...state.ui, action: state.ui.nextAction, actionValue: state.ui.nextActionValue, + autoSkip: false, actionChangeHandled: !state.ui.nextAction }}; case NEXT_UI_ACTION_SILENT: // don't set actionChangeHandled return { ...state, ui: { ...state.ui, action: state.ui.nextAction, @@ -176,6 +178,8 @@ export default function(state = initialState, action) { alertHandled: action.value === false ? true : false }}; case ALERT_HANDLED: return { ...state, ui: { ...state.ui, alertHandled: true }}; + case AUTO_SKIP: + return { ...state, ui: { ...state.ui, autoSkip: action.component }}; default: return state; } diff --git a/src/main.jsx b/src/main.jsx index bf4207e..e344880 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -72,6 +72,7 @@ function handleMessage(evt) { } else if (data.event === 'new-game-started') { initialize(store, Ws.sendCommand); Ws.setMainOnMessage(handleMessageFarm); + Ws.openSecondary('push-web-socket'); Ws.setSecondaryOnMessage(handleMessageFarm); Ws.sendCommand({ type: 'init' }) store.dispatch(play()); @@ -95,7 +96,5 @@ function handleMessage(evt) { } Ws.openMain('web-socket'); -Ws.openSecondary('push-web-socket'); Ws.setMainOnMessage(handleMessage); -Ws.setSecondaryOnMessage(handleMessage); Ws.setMainOnOpen(() => Ws.sendCommand({ type: 'main-init' })); diff --git a/src/server/farm.scm b/src/server/farm.scm index aaadda8..6643870 100644 --- a/src/server/farm.scm +++ b/src/server/farm.scm @@ -20,7 +20,8 @@ (import chicken scheme srfi-1 data-structures) (use http-session srfi-69 coops uri-common srfi-18 medea numbers spiffy spiffy-cookies - intarweb pll sxml-transforms websockets miscmacros) + intarweb pll sxml-transforms websockets miscmacros + mailbox) (cond-expand (geiser @@ -76,7 +77,7 @@ (trade initform: '() accessor: player-trade) (last-updated initform: 0 accessor: player-last-updated) (last-cash initform: 5000 accessor: player-last-cash) - (last-ui-action initform: #f accessor: player-last-ui-action))) + (mailbox initform: (make-mailbox) accessor: player-mailbox))) (define-class () ((id initform: 0 accessor: game-id) @@ -94,8 +95,7 @@ (state initform: 'playing accessor: game-state) (name initform: "game" accessor: game-name) (turn initform: 1 accessor: game-turn) - (actions initform: '() accessor: game-actions) - (last-ui-action initform: #f accessor: game-last-ui-action))) + (actions initform: '() accessor: game-actions))) (define-class () ((games initform: '() accessor: app-games) @@ -152,9 +152,6 @@ (set-cookie! (session-cookie-name) sid)))) (session-lifetime (* 60 60 24 7 4)) -(define update-condition-variable (make-condition-variable)) -(define update-mutex (make-mutex)) - (access-log (current-output-port)) (handle-not-found @@ -641,15 +638,11 @@ (players . ,(list->vector (map player-name (game-players game)))))) (app-games *app*)))))))) - -(define (set-ui-action! action game) - (set! (game-last-ui-action game) action)) - -(define (ui-action game) - (game-last-ui-action game)) - -(define (new-ui-action? player action) - (not (eq? (player-last-ui-action player) action))) +(define (message-players! game player message #!key (type "action")) + (for-each (lambda (p) + (when (not (eq? p player)) + (mailbox-send! (player-mailbox p) `((type . ,type) (value . ,message))))) + (game-players game))) (define *next-roll* #f) @@ -681,16 +674,16 @@ (append (game-actions game) `(((?action . move) (?value . ,resp))) (sort-actions (get-actions player (player-space player))))) - (set-ui-action! `((action . "roll") - (value . ,resp)) - game) + (message-players! game player + `((action . "roll") + (value . ,resp))) (create-ws-response player "action" `((action . "roll") (value . ,resp)))))) ((and (string=? type "next-action") (not (eq? (player-state player) 'turn-ended))) (let loop () (if (null? (game-actions game)) (begin - (set-ui-action! `((action . #f) (value . #f)) game) + (message-players! game player `((action . #f) (value . #f))) (create-ws-response player "action" '((action . #f)))) (let* ((action (car (game-actions game))) (name (alist-ref '?action action)) @@ -701,22 +694,23 @@ (set! (game-actions game) (cdr (game-actions game))) (if otb (begin - (set-ui-action! `((action . "otb") - (value . ,(alist-ref 'contents otb))) - game) + (message-players! game player + `((action . "otb") + (value . ,(alist-ref 'contents otb)))) (create-ws-response player "action" `((action . "otb") (value . ,(alist-ref 'contents otb))))) (begin - (set-ui-action! `((action . "info") - (value . ,(conc "Out of " *item-card-short* "'s."))) - game) + (message-players! game player + `((action . "info") + (value . ,(conc "Out of " *item-card-short* "'s.")))) (create-ws-response player "action" `((action . "info") (value . ,(conc "Out of " *item-card-short* "'s.")))))))) ((eq? name 'move) (set! (game-actions game) (cdr (game-actions game))) - (set-ui-action! `((action . "move") (value . ,value)) game) + (message-players! game player + `((action . "move") (value . ,value))) (create-ws-response player "action" `((action . "move") (value . ,value)))) ((eq? name 'harvest) @@ -725,8 +719,9 @@ (if (eq? res 'nothing) (loop) (begin - (set-ui-action! - `((action . "harvest") (value . ,res)) game) + (message-players! + game player + `((action . "harvest") (value . ,res))) (create-ws-response player "action" `((action . "harvest") @@ -740,10 +735,10 @@ (if (= (- (player-cash player) previous-cash) 0) (loop) (begin - (set-ui-action! `((action . "money") - (value . ,(- (player-cash player) - previous-cash))) - game) + (message-players! game player + `((action . "money") + (value . ,(- (player-cash player) + previous-cash)))) (create-ws-response player "action" `((action . "money") (value . ,(- (player-cash player) @@ -758,9 +753,9 @@ (set! (game-actions game) (append (alist-ref 'actions ff) (cdr (game-actions game)))) - (set-ui-action! `((action . "farmers-fate") - (value . ,(alist-ref 'contents ff))) - game) + (message-players! game player + `((action . "farmers-fate") + (value . ,(alist-ref 'contents ff)))) (create-ws-response player "action" `((action . "farmers-fate") (value . ,(alist-ref 'contents ff)))))) @@ -769,21 +764,22 @@ (if (= value 0) (loop) (begin - (set-ui-action! `((action . "money") (value . ,value)) - game) + (message-players! game player + `((action . "money") (value . ,value))) (create-ws-response player "action" `((action . "money") (value . ,value)))))) ((eq? name 'ff-uncle-bert) (set! (game-actions game) (cdr (game-actions game))) - (set-ui-action! `((action . "ff-uncle-bert") (value . #f)) - game) + (message-players! game player + `((action . "ff-uncle-bert") (value . #f))) (create-ws-response player "action" `((action . "ff-uncle-bert") (value . #f)))) ((eq? name 'info) (set! (game-actions game) (cdr (game-actions game))) - (set-ui-action! `((action . "info") (value . ,value)) game) + (message-players! game player + `((action . "info") (value . ,value))) (create-ws-response player "action" `((action . "info") (value . ,value)))) @@ -795,9 +791,8 @@ (cdr (game-actions game)))) (let ((resp `((from . ,(player-previous-space player)) (to . ,(player-space player))))) - (set-ui-action! `((action . "goto") - (value . ,resp)) - game) + (message-players! game player `((action . "goto") + (value . ,resp))) (create-ws-response player "action" `((action . "goto") (value . ,resp))))) @@ -807,6 +802,10 @@ (loop)) (else ;; TODO make error (create-ws-response player "action" `((action . ,name))))))))) + ((string=? type "skip") + (message-players! game player `((component . ,(alist-ref 'component msg))) + type: "auto-skip") + (create-ws-response player "update" '())) ((string=? type "buy") (let* ((id (alist-ref 'id msg)) (otb (find (lambda (x) (= id (alist-ref 'id x))) @@ -828,12 +827,14 @@ (set! (player-otbs player) (filter (lambda (x) (not (= id (alist-ref 'id x)))) (player-otbs player))))) + (message-players! game player '() type: "update") (create-ws-response player "buy" '())) ((string=? type "buy-uncle-bert") (set! (player-cash player) (- (player-cash player) 10000)) (set! (player-assets player) (alist-update 'hay (+ (alist-ref 'hay (player-assets player)) 10) (player-assets player))) + (message-players! game player '() type: "update") (create-ws-response player "buy" '())) ((string=? type "actions-finished") (create-ws-response player "update" '())) @@ -859,9 +860,11 @@ (create-ws-response player "loan" '())) ((string=? type "trade") (propose-trade game player (alist-ref 'parameters msg)) + (message-players! game player '() type: "update") (create-ws-response player "trade" '())) ((string=? type "trade-accept") (accept-trade game player) + (message-players! game player '() type: "update") (create-ws-response player "trade-accepted" '())) ((string=? type "trade-deny") (push-message player (conc (player-name player) " denied trade with " @@ -870,6 +873,7 @@ game (alist-ref 'originator (player-trade player)))) '()) (set! (player-trade player) '()) + (message-players! game player '() type: "update") (create-ws-response player "trade-denied" '())) ((string=? type "trade-cancel") (push-message player (conc (player-name player) " cancelled trade with " @@ -878,16 +882,19 @@ game (alist-ref 'player (player-trade player)))) '()) (set! (player-trade player) '()) + (message-players! game player '() type: "update") (create-ws-response player "trade-cancelled" '())) ((string=? type "audit") (call-audit game player) + (message-players! game player '() type: "update") (create-ws-response player "called-audit" '())) ((string=? type "init") (create-ws-response player "init" '())) ((string=? type "turn-ended") (if (>= (player-cash player) 0) (begin (advance-turn game player) - (create-ws-response player "turn-ended" '())) + (message-players! game player '() type: "update") + (create-ws-response player "update" '())) (begin (push-message player "Cannot end a turn with negative cash!") (create-ws-response player "update" '())))) ;;;;;;;;;;;;;;;;;;;;; start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -969,8 +976,7 @@ msg))) (when game (set! (game-last-updated game) (+ (game-last-updated game) 1)) - (set! (player-last-updated player) (game-last-updated game)) - (condition-variable-broadcast! update-condition-variable)) + (set! (player-last-updated player) (game-last-updated game))) res))))) (loop (read-json (receive-message))))))) @@ -981,7 +987,8 @@ (lambda () (let ((game (session-ref (sid) 'game)) (player (session-ref (sid) 'player))) - (let loop ((updated (mutex-unlock! update-mutex update-condition-variable))) + (let loop ((msg (mailbox-receive! (player-mailbox player)))) + (print msg) (when (not game) (set! game (session-ref (sid) 'game))) (when (not player) @@ -1005,16 +1012,11 @@ (print-call-chain) (print-error-message exn)))) (event . "error")) - (if (and (new-ui-action? player (ui-action game)) - (or (eq? (player-state player) 'turn-ended) - (eq? (player-state player) 'finished-game))) - (begin - (set! (player-last-ui-action player) (ui-action game)) - (create-ws-response player - "action" - (ui-action game))) - (create-ws-response player "update" '()))))))) - (loop (mutex-unlock! update-mutex update-condition-variable))))))) + (create-ws-response player + (alist-ref 'type msg) + (alist-ref 'value msg)) + ))))) + (loop (mailbox-receive! (player-mailbox player)))))))) (define (otb-spec->otb-cards spec id) `((contents . ,(sxml->html* (list-ref spec 5))) @@ -1709,7 +1711,6 @@ ;; TODO ;; make game finished display results. ;; make sure two players can't have the same name -;; bug: harvest action multiplayer doesn't flow right for other players ;; info actions should look better ;; you can get $50 from harvest ;; bug: new websocket messages should not reset IFS card selection