euchre-live

Euchre web-app for the socially distant family
git clone git://git.alexkarle.com/euchre-live.git
Log | Files | Refs | README | LICENSE

commit d64704812a643d70126792c2e04a2197d78a2d76 (patch)
parent 1003ff9a3c3db67c83f20a427572018b5046b21b
Author: Chris Karle <chriskarle@hotmail.com>
Date:   Sun, 26 Apr 2020 18:11:28 -0400

app, CardTable, Lobby: implement rejoin logic

Add extra processing and state necessary to allow any
active client to be replaced by playerName+tableName with
a new connection.  Tested in basic lobby, vote and play
phases.

Diffstat:
Massets/app.js | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Massets/components/CardTable.js | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Massets/components/Lobby.js | 19+++++++++++++++++--
3 files changed, 152 insertions(+), 25 deletions(-)

diff --git a/assets/app.js b/assets/app.js @@ -22,11 +22,14 @@ class App extends React.Component { this.state = { playerName: initialName, tableName: initialTable, - showTable: tableDebug + showTable: false, + uniqueError: false, + firstMsg: null }; const host = window.location.host; const clientAddr = 'ws://' + host + '/play'; client = new W3CWebSocket(clientAddr); + client.onmessage = (event) => this.processResponse(event); if (tableDebug) { // on tableDebug send join plus add 3 fakeClient players that join+sit fc1 = new W3CWebSocket(clientAddr); @@ -46,20 +49,77 @@ class App extends React.Component { ); } + // need to only showTable if no user-not-unique + // only process incoming if we're waiting to join table + // on joinTable success get first 'lobby' phase, put msg on + // CardTable.firstMsg prop for table init + processResponse = event => { + const {showTable} = this.state; + if (!showTable){ + let msg = JSON.parse(event.data); + if (msg) { + if ('pong' != msg.msg_type){ + // console.log('App procResp'); + if (msg.msg_type == 'game_state'){ + //replace w switch if more than one needed + // if (msg.game && msg.game.phase == 'lobby'){ + //set firstMsg, then set showTable + // console.log('App.procResp: set firstMsg & showTable'); + this.setState({ + firstMsg: msg + }, () => { + this.setState({ + showTable: true + }) + }); + // }; + } else if (msg.msg_type == 'error'){ + if (msg.msg && msg.msg.includes('Username not unique')){ + // show confirm-force + // console.log('NOT UNIQUE!!'); + this.setState({ + uniqueError: true + }); + } + } + } + } + } + } + chooseTable = tableName => { - const show = tableName && tableName != ''; + if (!tableName || tableName == ''){ + client.onmessage = (event) => this.processResponse(event); + this.setState({ + firstMsg: null + }); + }; this.setState( { tableName: tableName, - showTable: show + showTable: false }, () => { - client.send(JSON.stringify({ - action:'join_game', - player_name: this.state.playerName, - game_id: tableName - })); + if (tableName && tableName != ''){ + client.send(JSON.stringify({ + action:'join_game', + player_name: this.state.playerName, + game_id: tableName + })); + } }); } + forceJoin = () => { + // console.log('send force rejoin'); + // should only be calling this on unique error and user wants force rejoin + // so all other state should be correct + client.send(JSON.stringify({ + action:'join_game', + player_name: this.state.playerName, + game_id: this.state.tableName, + force: true + })); + } + setFakeGame = (initialName, initialTable) => { client.send(JSON.stringify({ action:'join_game', player_name: initialName, game_id: initialTable })); fc1.send(JSON.stringify({ action:'join_game', player_name: 'Betty', game_id: initialTable })); @@ -71,7 +131,7 @@ class App extends React.Component { } render () { - const {showTable, playerName, tableName} = this.state; + const {showTable, playerName, tableName, firstMsg, uniqueError} = this.state; return ( <div id="top-app"> {!showTable && ( @@ -79,6 +139,8 @@ class App extends React.Component { setName={this.setPlayerName} chooseTable={this.chooseTable} name={playerName} + uniqueError={uniqueError} + forceJoin={this.forceJoin} /> )} {showTable && ( @@ -86,7 +148,7 @@ class App extends React.Component { chooseTable={this.chooseTable} name={playerName} tableName={tableName} - active={showTable} + firstMsg={firstMsg} client={client} /> )} diff --git a/assets/components/CardTable.js b/assets/components/CardTable.js @@ -54,14 +54,24 @@ export default class CardTable extends React.Component { }; componentDidMount () { - const {name, tableName, client} = this.props; + const {name, tableName, client, firstMsg} = this.props; client.onmessage = (event) => this.processResponse(event); const welcomeMsg = 'Welcome to the ' + tableName + ' table, ' + name + '!'; + if (firstMsg) { + this.processFirstMessage(firstMsg); + } this.setState({ bannerMsg: welcomeMsg }); }; + componentDidUpdate (prevProps) { + const {firstMsg} = this.props; + if (firstMsg && !prevProps.firstMsg) { + this.processFirstMessage(firstMsg); + } + } + processResponse = (event) => { let msg = JSON.parse(event.data); if ('pong' != msg.msg_type) { @@ -95,17 +105,62 @@ export default class CardTable extends React.Component { }; }; + processFirstMessage = msg => { + // console.log('processFirstMessage'); + // in cases of forceRejoin, the firstMsg could be lobby, vote, play + // for cases of ordinary game join, will be lobby + if (msg && msg.game) { + switch (msg.game.phase) { + case 'lobby': + this.processLobby(msg); + break; + case 'vote': + this.processFirstMsgVote(msg); + break; + case 'play': + this.processFirstMsgPlay(msg); + break; + } + } + } + + processFirstMsgPlay = msg => { + this.initMySeat(msg); + setTimeout(this.gameStartSetup, 300, msg); + setTimeout(this.handStartSetup, 600, msg); + setTimeout(this.processPlay, 900, msg); + // this.gameStartSetup(msg); + // this.handStartSetup(msg); + // this.processPlay(msg); + } + + processFirstMsgVote = msg => { + this.initMySeat(msg); + setTimeout(this.gameStartSetup, 300, msg); + setTimeout(this.processVote, 600, msg); + // this.gameStartSetup(msg); + // this.processVote(msg); + } + + // OK to let processLobby run twice on first lobby msg, which + // may happen if CardTable already mounted on join_table processLobby = (msg) => { + this.initMySeat(msg); + this.setState({ + phase: 'lobby' + }); + }; + + initMySeat = msg => { if (msg.game.players) { const plAr = msg.game.players; const mySeat = plAr.findIndex( x => x == this.props.name ); this.setState({ playerNames: plAr, mySeat: mySeat, - phase: 'lobby' - }) + }); }; - }; + } processVote = (msg) => { if (this.state.phase == 'lobby') { @@ -183,9 +238,6 @@ export default class CardTable extends React.Component { } processPause = msg => { - // console.log('new t:', msg.game.tricks); - // console.log('old t:', this.state.tricks); - // console.log('start processPause, msg.hand=', msg.hand); let trickWinIndex = -1; let trickWinner = ''; for (let i = 0; i < 4; i++){ @@ -198,7 +250,6 @@ export default class CardTable extends React.Component { //must be?? sanity check trickWinner = this.state.playerNames[trickWinIndex] + ' takes the trick!' } - // console.log('trickWinIndex = ', trickWinIndex); this.setState ({ table: msg.game.table, handLengths: msg.game.hand_lengths, @@ -301,7 +352,6 @@ export default class CardTable extends React.Component { let tpIndex = msg.game.dealer - mySeat; tpIndex = (tpIndex < 0) ? tpIndex + 4 : tpIndex; const trumpPlace = trumpPlacement[tpIndex]; - console.log('trumpPlace:', trumpPlace); this.setState ({ leftName: playerNames[leftSeat], leftSeat: leftSeat, @@ -342,18 +392,18 @@ export default class CardTable extends React.Component { this.props.client.send(JSON.stringify({ action: 'start_game', start_seat: startDealer })); - console.log('start game, dealer = ', startDealer); + // console.log('start game, dealer = ', startDealer); }; sendVote = (voteObject) => { const voteString = JSON.stringify(voteObject); - console.log('sendVote:', voteString); + // console.log('sendVote:', voteString); this.props.client.send(voteString); }; sendCard = (index) => { const {phase, myCards} = this.state; - console.log('card click ', myCards[index]); + // console.log('card click ', myCards[index]); if (phase == 'swap') { this.props.client.send(JSON.stringify({ action:'dealer_swap', card: myCards[index] @@ -569,6 +619,6 @@ CardTable.propTypes = { chooseTable: PropTypes.func, name: PropTypes.string, tableName: PropTypes.string, - active: PropTypes.bool, + firstMsg: PropTypes.object, client: PropTypes.object } \ No newline at end of file diff --git a/assets/components/Lobby.js b/assets/components/Lobby.js @@ -52,7 +52,7 @@ export default class Lobby extends React.Component { } render () { - const {name} = this.props; + const {name, uniqueError} = this.props; const {nameIn, nameError, tableIn, tableError} = this.state; return ( <div id="lobby" className="lobby__outer"> @@ -110,6 +110,19 @@ export default class Lobby extends React.Component { </div> </div> )} + {uniqueError && ( + <div> + <h3>Hmm, a player named {name} is at the table...</h3> + <p>You need a unique player name, which you can change in the top input above.</p> + <p><emphasis>OR </emphasis> if you lost your connection and want to re-join the + game as player &quot;{name}&quot; from this page, press the REJOIN button below</p> + <Button + className="rejoin__button" + kind="primary" + onClick={()=>this.props.forceJoin()} + >REJOIN</Button> + </div> + )} </div> ); }; @@ -118,5 +131,7 @@ export default class Lobby extends React.Component { Lobby.propTypes = { setName: PropTypes.func, chooseTable: PropTypes.func, - name: PropTypes.string + name: PropTypes.string, + uniqueError: PropTypes.bool, + forceJoin: PropTypes.func } \ No newline at end of file