euchre-live

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

CardTable.js (36847B) [raw]


      1 import React from 'react';
      2 import PropTypes from 'prop-types';
      3 import _ from 'lodash';
      4 import {Button, Grid, Row, Column, OverflowMenu, OverflowMenuItem, Tooltip} from 'carbon-components-react';
      5 import {Logout32, Redo32, Package32, View16, ViewOff16, Information16} from '@carbon/icons-react';
      6 import SeatPicker from './SeatPicker';
      7 import MainHand from './MainHand';
      8 import HiddenHand from './HiddenHand';
      9 import TrumpPicker from './TrumpPicker';
     10 import ChatPanel from './ChatPanel';
     11 
     12 const trumpPlacement = ['me', 'left', 'partner', 'right'];
     13 const suit = {
     14     H: 'Hearts',
     15     D: 'Diamonds',
     16     S: 'Spades',
     17     C: 'Clubs'
     18 }
     19 
     20 export default class CardTable extends React.Component {
     21 
     22     constructor(props) {
     23         super(props);
     24         this.state = {
     25             playerNames: [],
     26             mySeat: -1,
     27             myCards: [],
     28             myHandInfo: 'mhi',
     29             myTurnInfo: 'mti',
     30             phase: 'lobby',
     31             leftName: '',
     32             leftSeat: -1,
     33             leftHandInfo: '',
     34             leftTurnInfo: '',
     35             partnerName: '',
     36             partnerSeat: -1,
     37             partnerHandInfo: '',
     38             partnerTurnInfo: '',
     39             rightName: '',
     40             rightSeat: -1,
     41             rightHandInfo: '',
     42             rightTurnInfo: '',
     43             trump: '',
     44             trumpPlace: '',
     45             trumpNom: '',
     46             turnSeat: -1,
     47             dealSeat: -1,
     48             table: [],
     49             handLengths: [],
     50             score: [0, 0],
     51             trickWinner:'',
     52             tricks:[],
     53             spectators: [],
     54             bannerMsg: '',
     55             innerWinMsg: '',
     56             onlyAlone: false,
     57             noPick: false,
     58             noPass: false,
     59             amSpectator: false,
     60             latestPost: '',
     61             showErrors: true,
     62             hard_order: true,
     63             hard_pick: true,
     64             stick_dealer: false
     65         };
     66     };
     67 
     68     componentDidMount () {
     69         const {name, tableName, client, firstMsg} = this.props;
     70         client.onmessage = (event) => this.processResponse(event);
     71         const welcomeMsg = 'Welcome to the ' + tableName + ' table, ' + name + '!';
     72         if (firstMsg) {
     73             this.processPlayerChange(firstMsg);
     74         }
     75         this.setState({
     76             bannerMsg: welcomeMsg
     77         });
     78     };
     79 
     80     componentDidUpdate (prevProps) {
     81         const {firstMsg} = this.props;
     82         if (firstMsg && !prevProps.firstMsg) {
     83             this.processPlayerChange(firstMsg);
     84         }
     85     }
     86 
     87     processResponse = (event) => {
     88         const { playerNames} = this.state;
     89         let msg = JSON.parse(event.data);
     90         if ('pong' != msg.msg_type) {
     91             console.log(msg);
     92         }
     93         if ('game_state' == msg.msg_type) {
     94             this.setState({
     95                 spectators: msg.game.spectators
     96             });
     97             if (msg.game.phase != 'lobby') {
     98                 if (!_.isEqual(playerNames, msg.game.players)){
     99                     this.processPlayerChange(msg);
    100                 } else {
    101                     this.processResponseSwitch(msg);
    102                 }
    103             } else {
    104                 this.processLobby(msg);
    105             }
    106         } else if ('chat' == msg.msg_type) {
    107             this.processChat(msg);
    108         } else if ('error' == msg.msg_type) {
    109             this.processError(msg);
    110         }
    111     };
    112 
    113     processError = msg => {
    114         if (msg.msg) {
    115             const post = '<<Error: ' + msg.msg;
    116             this.setState({
    117                 latestPost: post
    118             });
    119         }
    120     };
    121 
    122     processChat = msg => {
    123         let post = msg.msg;
    124         if (post && post != '') {
    125             this.setState({
    126                 latestPost: post
    127             });
    128         }
    129     }
    130 
    131     processResponseSwitch = msg => {
    132         switch (msg.game.phase) {
    133             case 'lobby':
    134                 this.processLobby(msg);
    135                 break;
    136             case 'vote':
    137                 this.processVote(msg);
    138                 break;
    139             case 'dealer_swap':
    140                 this.processSwap(msg);
    141                 break;
    142             case 'play':
    143                 this.processPlay(msg);
    144                 break;
    145             case 'pause':
    146                 this.processPause(msg);
    147                 break;
    148             case 'end':
    149                 this.processEnd(msg);
    150                 break;
    151             default:
    152                 break;
    153         }
    154     }
    155 
    156     processPlayerChange = msg => {
    157         const {amSpectator} = this.state;
    158         console.log('Player update!!');
    159         const newSpectator = msg.is_spectator > 0;
    160         if (!amSpectator && !newSpectator) {
    161             // I'm already a player, update the name set and continue
    162             this.setState({
    163                 playerNames: msg.game.players
    164             }, () => { 
    165                 this.processResponseSwitch(msg);
    166             });
    167         } else if (!amSpectator && newSpectator) {
    168             // I was playing and just stood up - reset me to spectator mode
    169             this.processFirstMessage(msg);
    170         } else if (amSpectator && !newSpectator) {
    171             // I was spectator and just took a seat
    172             this.processFirstMessage(msg);
    173         } else {
    174             // was and still am spectator - update names and continue
    175             this.setState({
    176                 playerNames: msg.game.players,
    177             }, () => { 
    178                 this.processResponseSwitch(msg);
    179             });
    180         }
    181 
    182     }
    183 
    184     processFirstMessage = msg => {
    185         console.log('processFirstMessage, msg:\n', msg);
    186         // in cases of forceRejoin, the firstMsg could be lobby, vote, play
    187         // for cases of ordinary game join, will be lobby
    188         if (msg && msg.game) {
    189             this.setState({
    190                 spectators: msg.game.spectators
    191             });
    192             switch (msg.game.phase) {
    193                 case 'lobby':
    194                     this.processLobby(msg);
    195                     break;
    196                 case 'vote':
    197                     this.processFirstMsgVote(msg);
    198                     break;
    199                 case 'play':
    200                     this.processFirstMsgPlay(msg);
    201                     break;
    202                 case 'end':
    203                     this.processFirstMsgEnd(msg);
    204                     break;
    205             }
    206         }
    207         //TODO force correct score (for all the rejoin cases)
    208     }
    209 
    210     // NOTE the processFirstMsgXyz functions are needed to handle the case
    211     //  of a user dropping connection and doing a force-rejoin back to the
    212     //  same table.
    213     // In normal game play, firstMsg will be a 'lobby', and none of the other
    214     //  processFirst* variants will be called
    215 
    216     processFirstMsgEnd = msg => {
    217         // timer use in these processFirst* is to allow sequence of setState to complete
    218         this.initMySeat(msg);
    219         setTimeout(this.gameStartSetup, 300, msg);
    220         setTimeout(this.handStartSetup, 600, msg);
    221         setTimeout(this.processEnd, 900, msg);
    222     }
    223 
    224     processFirstMsgPlay = msg => {
    225         this.initMySeat(msg);
    226         setTimeout(this.gameStartSetup, 300, msg);
    227         setTimeout(this.handStartSetup, 600, msg);
    228         setTimeout(this.processPlay, 900, msg);
    229         // this.gameStartSetup(msg);
    230         // this.handStartSetup(msg);
    231         // this.processPlay(msg);
    232     }
    233 
    234     processFirstMsgVote = msg => {
    235         this.initMySeat(msg);
    236         setTimeout(this.gameStartSetup, 300, msg);
    237         setTimeout(this.processVote, 600, msg);
    238         // this.gameStartSetup(msg);
    239         // this.processVote(msg);
    240     }
    241 
    242     // OK to let processLobby run twice on first lobby msg, which
    243     //  may happen if CardTable already mounted on join_table
    244     processLobby = (msg) => {
    245         this.initMySeat(msg);
    246         this.setState({
    247             phase: 'lobby',
    248             score: [0,0]
    249         });
    250     };
    251 
    252     initMySeat = msg => {
    253         const {name} = this.props;
    254         const newSpec = msg.is_spectator >  0;
    255         if (msg.game.players) {
    256             const plAr = msg.game.players;
    257             let mySeat = plAr.findIndex( x => x == name );
    258             console.log('initMySeat.mySeat=', mySeat);
    259             if (newSpec && msg.game.phase != 'lobby'){
    260                 mySeat = 3;
    261             };
    262             this.setState({
    263                 playerNames: plAr,
    264                 mySeat: mySeat,
    265                 amSpectator: newSpec,
    266                 hard_order: msg.settings.hard_order,
    267                 hard_pick: msg.settings.hard_pick,
    268                 stick_dealer: msg.settings.stick_dealer
    269             });
    270         };
    271     }
    272 
    273     haveSuit = (hand, trumpNom) => {
    274         const targetSuit = trumpNom.substring(1);
    275         for (let i=0; i < hand.length; i++) {
    276             if (hand[i].substring(1) == targetSuit) {
    277                 console.log('targetSuit:' + targetSuit + ' matched by ' + hand[i]);
    278                 return true;
    279             }
    280         }
    281         return false;
    282     }
    283 
    284     processVote = (msg) => {
    285         if (this.state.phase == 'lobby') {
    286             this.gameStartSetup(msg);
    287         } else if (this.state.phase == 'pause' || 
    288                   (this.state.phase == 'vote2' && msg.game.pass_count == 0)) {
    289                 // second condition is for all players pass trump twice
    290             this.trumpStartSetup(msg);
    291         }
    292         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat, hard_order, hard_pick, stick_dealer} = this.state;
    293         const vote1phase = msg.game.pass_count < 4;
    294         const phaseString = vote1phase ? 'vote' : 'vote2';
    295         const onlyAlone = hard_order && vote1phase && dealSeat == partnerSeat;
    296         const noPass = stick_dealer && !vote1phase && (dealSeat == mySeat);
    297         const noPick = hard_pick && vote1phase && (dealSeat == mySeat) && !this.haveSuit(msg.hand, msg.game.trump_nominee);
    298         let turnInfo = ['', '', '', ''];
    299         turnInfo[msg.game.turn] = 'trump?';
    300         this.setState({
    301             phase: phaseString,
    302             myCards: msg.hand,
    303             turnSeat: msg.game.turn,
    304             onlyAlone: onlyAlone,
    305             noPick: noPick,
    306             noPass: noPass,
    307             handLengths: msg.game.hand_lengths,
    308             leftTurnInfo: turnInfo[leftSeat],
    309             rightTurnInfo: turnInfo[rightSeat],
    310             partnerTurnInfo: turnInfo[partnerSeat],
    311             myTurnInfo: turnInfo[mySeat]
    312         });
    313     };
    314 
    315     processSwap = msg => {
    316         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    317         let turnInfo = ['', '', '', ''];
    318         turnInfo[dealSeat] = 'Swapping...';
    319         this.setState({
    320             phase: 'swap',
    321             leftTurnInfo: turnInfo[leftSeat],
    322             rightTurnInfo: turnInfo[rightSeat],
    323             partnerTurnInfo: turnInfo[partnerSeat],
    324             myTurnInfo: turnInfo[mySeat]
    325         });
    326     }
    327 
    328     trickMsg = (num, action) => {
    329         let retVal;
    330         if (num == 0) {
    331             retVal = action ? 'action' : '';
    332         } else {
    333             retVal = num == 1 ? '1 trick' : (num + ' tricks');
    334             if (action) {
    335                 retVal = retVal + ', action';
    336             }
    337         }
    338         return retVal;
    339     }
    340 
    341     processPlay = msg => {
    342         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat, phase} = this.state;
    343         let turnInfo = [];
    344         if (phase == 'vote' || phase == 'vote2' || phase == 'swap') {
    345             this.handStartSetup(msg);
    346         }
    347         for (let i = 0; i < 4; i++) {
    348             const tricks = msg.game.tricks[i];
    349             let tInf = this.trickMsg(tricks, (msg.game.turn == i));
    350             turnInfo.push(tInf);
    351         }
    352         this.setState({
    353             phase: 'play',
    354             myCards: msg.hand,
    355             table: msg.game.table,
    356             tricks: msg.game.tricks,
    357             trickWinner: '',
    358             handLengths: msg.game.hand_lengths,
    359             leftTurnInfo: turnInfo[leftSeat],
    360             rightTurnInfo: turnInfo[rightSeat],
    361             partnerTurnInfo: turnInfo[partnerSeat],
    362             myTurnInfo: turnInfo[mySeat]
    363         });
    364 
    365     }
    366 
    367     processPause = msg => {
    368         let trickWinIndex = -1;
    369         let trickWinner = '';
    370         for (let i = 0; i < 4; i++){
    371             if (msg.game.tricks[i] != this.state.tricks[i]){
    372                 trickWinIndex = i;
    373                 break;
    374             }
    375         }
    376         if (trickWinIndex > -1) {
    377             //must be?? sanity check
    378             trickWinner = this.state.playerNames[trickWinIndex] + ' takes the trick!'
    379         }
    380         this.setState ({
    381             table: msg.game.table,
    382             handLengths: msg.game.hand_lengths,
    383             myCards: msg.hand,
    384             tricks: msg.game.tricks,
    385             phase: 'pause',
    386             trickWinner: trickWinner
    387         })
    388     }
    389 
    390     processEnd = msg => {
    391         const {playerNames, leftSeat, partnerSeat, rightSeat, amSpectator} = this.state;
    392         // arrangeScore[0] us, [1] them
    393         const finalScore = this.arrangeScore(msg.game.score);
    394         const myName =  amSpectator ? playerNames[3] : 'You';
    395         const winMsg = finalScore[0] > finalScore[1] ?
    396             myName + ' and ' + playerNames[partnerSeat] + ' win the game!!' :
    397             playerNames[leftSeat] + ' and ' + playerNames[rightSeat] + ' win this one...';
    398         const innerWin = amSpectator ? 'Game Over...' :
    399           finalScore[0] > finalScore[1] ? 'You Win!!' : 'You lost...';
    400         this.setState({
    401             phase: 'end',
    402             score: finalScore,
    403             bannerMsg: winMsg,
    404             innerWinMsg: innerWin
    405         });
    406     }
    407 
    408     handStartSetup = msg => {
    409         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    410         let handInfo = ['', '', '', ''];
    411         const caller = msg.game.out_player > -1 ? 'Alone' : 'Caller';
    412         const callSeat = msg.game.caller;
    413         if (callSeat == dealSeat) {
    414             handInfo[dealSeat] = 'Dealer, ' + caller;
    415         } else {
    416             handInfo[dealSeat] = 'Dealer';
    417             handInfo[callSeat] = caller;
    418         }
    419         // this.state.score is always [{us}, {them}]
    420         let score = this.arrangeScore(msg.game.score);
    421         this.setState({
    422             trump: msg.game.trump,
    423             table: msg.game.table,
    424             score: score,
    425             handLengths: msg.game.hand_lengths,
    426             leftHandInfo: handInfo[leftSeat],
    427             rightHandInfo: handInfo[rightSeat],
    428             partnerHandInfo: handInfo[partnerSeat],
    429             myHandInfo: handInfo[mySeat]
    430         });
    431 
    432     }
    433 
    434     arrangeScore = (msgScore) => {
    435         // this.state.score is always [{us}, {them}]
    436         const { mySeat } = this.state;
    437         let score = [];
    438         if (mySeat % 2 == 0){
    439             // we're evens
    440             score[0] = msgScore[0];
    441             score[1] = msgScore[1];
    442         } else {
    443             score[0] = msgScore[1];
    444             score[1] = msgScore[0];
    445         }
    446         return score;
    447     }
    448 
    449     trumpStartSetup = (msg) => {
    450         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    451         let handInfo = ['', '', '', ''];
    452         const newDeal = msg.game.dealer;
    453         handInfo[newDeal] = 'Dealer';
    454         let tpIndex = msg.game.dealer - mySeat;
    455         tpIndex = (tpIndex < 0) ? tpIndex + 4 : tpIndex;
    456         const trumpPlace = trumpPlacement[tpIndex];
    457         // this.state.score is always [{us}, {them}]
    458         let score = this.arrangeScore(msg.game.score);
    459         this.setState({
    460             trump: '',
    461             trumpPlace: trumpPlace,
    462             dealSeat: newDeal,
    463             score: score,
    464             trickWinner: '',
    465             trumpNom: msg.game.trump_nominee,
    466             leftHandInfo: handInfo[leftSeat],
    467             rightHandInfo: handInfo[rightSeat],
    468             partnerHandInfo: handInfo[partnerSeat],
    469             myHandInfo: handInfo[mySeat]
    470         });
    471 
    472     }
    473 
    474     gameStartSetup = (msg) => {
    475         const { playerNames, mySeat } = this.state;
    476         let handInfo = [' ', ' ', ' ', ' '];
    477         let turnInfo = [' ', ' ', ' ', ' '];
    478         handInfo[msg.game.dealer] = 'Dealer';
    479         turnInfo[msg.game.turn] = 'trump?';
    480         const leftSeat = (mySeat + 1) % 4;
    481         const partnerSeat = (mySeat + 2) % 4;
    482         const rightSeat = (mySeat + 3) % 4;
    483         let tpIndex = msg.game.dealer - mySeat;
    484         tpIndex = (tpIndex < 0) ? tpIndex + 4 : tpIndex;
    485         const trumpPlace = trumpPlacement[tpIndex];
    486         this.setState ({
    487             leftName: playerNames[leftSeat],
    488             leftSeat: leftSeat,
    489             leftHandInfo: handInfo[leftSeat],
    490             leftTurnInfo: turnInfo[leftSeat],
    491             partnerName: playerNames[partnerSeat],
    492             partnerSeat: partnerSeat,
    493             partnerHandInfo: handInfo[partnerSeat],
    494             partnerTurnInfo: turnInfo[partnerSeat],
    495             rightName: playerNames[rightSeat],
    496             rightSeat: rightSeat,
    497             rightHandInfo: handInfo[rightSeat],
    498             rightTurnInfo: turnInfo[rightSeat],
    499             myHandInfo : handInfo[mySeat],
    500             myTurnInfo : turnInfo[mySeat],
    501             trumpPlace : trumpPlace,
    502             trumpNom: msg.game.trump_nominee,
    503             dealSeat: msg.game.dealer
    504         });
    505     };
    506 
    507     sendSit = (index) => {
    508         const { client } = this.props;
    509         client.send(JSON.stringify({
    510             action:'take_seat',
    511             seat: index
    512         }));
    513     };
    514 
    515     sendStand = () => {
    516         const { client } = this.props;
    517         client.send(JSON.stringify({
    518             action:'stand_up'
    519         }));
    520     };
    521 
    522     sendStart = (startDealer) => {
    523         this.props.client.send(JSON.stringify({
    524             action: 'start_game', start_seat: startDealer
    525         }));
    526         // console.log('start game, dealer = ', startDealer);
    527     };
    528 
    529     sendExit = () => {
    530         this.props.exitTable();
    531     };
    532 
    533     sendVote = (voteObject) => {
    534         const voteString = JSON.stringify(voteObject);
    535         // console.log('sendVote:', voteString);
    536         this.props.client.send(voteString);
    537     };
    538 
    539     sendCard = (index) => {
    540         const {phase, myCards} = this.state;
    541         // console.log('card click ', myCards[index]);
    542         if (phase == 'swap') {
    543             this.props.client.send(JSON.stringify({
    544                 action:'dealer_swap', card: myCards[index]
    545             }));
    546         } else if (phase == 'play') {
    547             this.props.client.send(JSON.stringify({
    548                 action:'play_card', card: myCards[index]
    549             }));
    550         }
    551     };
    552 
    553     sendRestart = () => {
    554         this.props.client.send(JSON.stringify({
    555             action:'restart_game'
    556         })); 
    557     }
    558 
    559     sendChat = post => {
    560         console.log(post);
    561         if (post && post != ''){
    562             this.props.client.send(JSON.stringify({
    563                 action: 'chat',
    564                 msg: post
    565             }));
    566         }
    567         // XX TODO remove this debug action when chat stable
    568         if ('chat10' == post){
    569             for (let i=0; i < 10; i++){
    570                 const msg='this is chat10 test-chat-' + i;
    571                 this.props.client.send(JSON.stringify({
    572                     action: 'chat',
    573                     msg: msg
    574                 }));
    575             }
    576         }
    577     }
    578 
    579     toggleErrorDisplay = () => {
    580         // NOTE this trick requires that .err__post is the first selector of all,
    581         //  so requires it first in our .scss, even before imports
    582         const { showErrors } = this.state;
    583         let stylesheet = document.styleSheets[0];
    584         const nextShowErrors = !showErrors;
    585         const rule = nextShowErrors ? 'inherit' : 'none';
    586         stylesheet.cssRules[0].style.display = rule;
    587         this.setState({
    588             showErrors: nextShowErrors
    589         })
    590         console.log('toggleErrorDisplay');
    591     }
    592 
    593     genGameOver = () => {
    594         const {innerWinMsg, amSpectator} = this.state;
    595         let retVal = [];
    596         const instMsg = amSpectator ? 'You can take a seat if one becomes empty, or you can return to the lobby...'
    597         : 'You can play again at this table, or return to the lobby to change your table or player name...';
    598         retVal.push(
    599         <div className="gover__outer" key="gom1">
    600             <div className="gover__inwin">{innerWinMsg}</div>
    601             <div className="gover__inst">
    602                {instMsg} 
    603             </div>
    604             <div className="gover__buttons">
    605                 <Button
    606                     className="exit2__button"
    607                     kind="secondary"
    608                     onClick={this.sendExit}
    609                     renderIcon={Logout32}
    610                     >Exit to Lobby</Button> 
    611                 {!amSpectator && ( 
    612                     <Button
    613                     className="repeat__button"
    614                     kind="primary"
    615                     onClick={()=>this.sendRestart()}
    616                     renderIcon={Redo32}
    617                     >Play Again!!</Button> 
    618 
    619                 )}
    620             </div>
    621         </div>
    622         );
    623         return retVal;
    624     }
    625 
    626     genTrick = () => {
    627         let retVal = [];
    628         const { table, mySeat, leftSeat, rightSeat, partnerSeat } = this.state;
    629         const myCard = table[mySeat];
    630         const leftCard = table[leftSeat];
    631         const partnerCard = table[partnerSeat];
    632         const rightCard = table[rightSeat];
    633         const mySrc = 'cards/' + myCard + '.svg';
    634         const leftSrc = 'cards/' + leftCard + '.svg';
    635         const partnerSrc = 'cards/' + partnerCard + '.svg';
    636         const rightSrc = 'cards/' + rightCard + '.svg';
    637         let midClass = 'mid__trick';
    638         if (myCard && partnerCard) {
    639             midClass += ' both';
    640         } else if (myCard) {
    641             midClass += ' me';
    642         } else if (partnerCard) {
    643             midClass += ' partner';
    644         }
    645         retVal.push (
    646             <div className="trick__outer" key="gt1">
    647                 <Grid className="trick__grid">
    648                     <Row className="trick__row">
    649                         <Column>
    650                             <div className="left__trick">
    651                                 {leftCard && (
    652                                     <div className="trick__div">
    653                                         <img className="trick__card" src={leftSrc} />
    654                                     </div>
    655                                 )}
    656                             </div>
    657                         </Column>
    658                         <Column>
    659                             <div className={midClass}>
    660                                 {partnerCard && (
    661                                     <div className="trick__div">
    662                                         <img className="trick__card" src={partnerSrc} />
    663                                     </div>
    664                                 )}
    665                                 {myCard && (
    666                                     <div className="trick__div">
    667                                         <img className="trick__card" src={mySrc} />
    668                                     </div>
    669                                 )}
    670                             </div>
    671                         </Column>
    672                         <Column>
    673                             <div className="right__trick">
    674                                 {rightCard && (
    675                                     <div className="trick__div">
    676                                         <img className="trick__card" src={rightSrc} />
    677                                     </div>
    678                                 )}
    679                             </div>
    680                         </Column>
    681                     </Row>
    682                 </Grid>
    683             </div>
    684         );
    685         return retVal;
    686     }
    687 
    688     genNameDisplay = seatNum => {
    689         let retVal = '';
    690         const { playerNames, mySeat, myHandInfo, amSpectator } = this.state;
    691         let seatName = playerNames[seatNum];
    692         if (seatNum == mySeat) {
    693             seatName = amSpectator ? seatName : 'You';
    694             if (seatName != 'Empty'){
    695                 seatName += ': ' + myHandInfo;
    696             }
    697         }
    698         if (seatName != 'Empty') {
    699             retVal = (<div>{seatName}</div>);
    700         } else {
    701             retVal = (
    702             <div>Empty
    703                 {amSpectator && (
    704                     <Button
    705                     className="sit__button"
    706                     kind="ghost"
    707                     onClick={()=>{this.sendSit(seatNum)}}
    708                     renderIcon={Package32}>Choose seat</Button>
    709                 )}
    710             </div>);
    711         }
    712         return retVal;
    713     }
    714 
    715     genSpecMsgObj = () => {
    716         const {spectators} = this.state;
    717         let retVal = {}
    718         if (!spectators || spectators.length == 0) {
    719             retVal.title = 'No Spectators';
    720             retVal.list = null;
    721         } else {
    722             retVal.title = (spectators.length == 1) ? '1 Spectator:' : spectators.length + ' Spectators:'; 
    723             retVal.list = spectators[0];
    724             for (let i=1; i < spectators.length; i++) {
    725                 retVal.list += ', ' + spectators[i];
    726             };
    727         }
    728         return retVal;
    729     }
    730 
    731     render () {
    732         const { playerNames, mySeat, phase, myCards, myTurnInfo, amSpectator,
    733             partnerHandInfo, partnerTurnInfo, partnerSeat, leftTurnInfo, leftHandInfo, leftSeat,
    734             rightHandInfo, rightTurnInfo, rightSeat, trumpPlace, trumpNom, turnSeat, spectators,
    735             dealSeat, trump, handLengths, score, trickWinner, bannerMsg, noPick, noPass, onlyAlone,
    736             hard_pick, hard_order, stick_dealer, latestPost, showErrors } = this.state;
    737         const {tableName} = this.props;
    738         const showSeatPicker = phase == 'lobby';
    739         const showGameOver = phase == 'end';
    740         const showTrump = (phase == 'vote') || (phase == 'vote2') || (phase == 'swap');
    741         const showTrumpPicker = showTrump && (turnSeat == mySeat);
    742         const showSwap = (phase == 'swap') && (dealSeat == mySeat);
    743         const showBottomInfo = !showSeatPicker && (amSpectator || (!showTrumpPicker && !showSwap));
    744         const tcp = "trump__holder " + trumpPlace;
    745         const trumpImage = (phase != 'vote2') ? 'cards/' + trumpNom + '.svg' : 'cards/1B.svg';
    746         const trumpMsg = phase == 'play' ? suit[trump] + ' are trump' : '';
    747         const trickDisplay = (phase == 'play' || phase == 'pause') ? this.genTrick() : [];
    748         const gameOverDisplay = (phase == 'end') ? this.genGameOver() : [];
    749         const usLabel = amSpectator ? playerNames[1] + ' & ' + playerNames[3] + ': ' : 'Us: ';
    750         const themLabel = amSpectator ? playerNames[0] + ' & ' + playerNames[2] + ': ' : 'Them: ';
    751         const hasSpec = spectators.length > 0;
    752         const specMsgObj = this.genSpecMsgObj();
    753         const toggleErrorMsg = showErrors ? 'Hide errors in chat' : 'Show errors in chat';
    754         return (
    755             <div id="table" className="table__main">
    756                 <Grid className="og">
    757                     <Row className="og__row">
    758                         <Column className="og__left" md={5}>
    759                         <Grid className="inner__left">
    760                     {(showSeatPicker || showGameOver) && (
    761                      <Row className="table__header">
    762                          <h3 className="banner">{bannerMsg}</h3>
    763                     </Row>
    764                     )}
    765                     <Row className="table__top">
    766                         <Column className="tt__left" md={3}>
    767                             {!showSeatPicker && (
    768                                     <div className="menu__holder">
    769                                         <OverflowMenu>
    770                                             <OverflowMenuItem
    771                                                 itemText="Stand"
    772                                                 disabled={amSpectator}
    773                                                 onClick={this.sendStand}
    774                                             />
    775                                             <OverflowMenuItem
    776                                                 itemText="Exit to Lobby"
    777                                                 onClick={this.sendExit}
    778                                             />
    779                                             <OverflowMenuItem
    780                                                 itemText={toggleErrorMsg}
    781                                                 onClick={this.toggleErrorDisplay}
    782                                             />
    783                                         </OverflowMenu>
    784                                     </div>
    785                                 )}
    786                         </Column>
    787                         <Column className="tt__center" md={5}>
    788                             {!showSeatPicker && (
    789                             <div className="partner__stack">
    790                                 <div className="player__name">{this.genNameDisplay(partnerSeat)}</div>
    791                                 <div className="partner__info">
    792                                     <div className="play__hinfo">{partnerHandInfo}</div>
    793                                     <div className="play__tinfo">{partnerTurnInfo}</div>
    794                                 </div>
    795                                 <HiddenHand
    796                                     numCards={handLengths[partnerSeat]} />
    797                             </div>)}
    798                         </Column>
    799                     </Row>
    800                     <Row className="table__mid">
    801                         <Column className="tm__left" md={3}>
    802                             {!showSeatPicker && (
    803                             <div className="vert__stack">
    804                                 <div className="player__name">{this.genNameDisplay(leftSeat)}</div>
    805                                 <div className="play__hinfo">{leftHandInfo}</div>
    806                                 <div className="play__tinfo">{leftTurnInfo}</div>
    807                                 <HiddenHand
    808                                     numCards={handLengths[leftSeat]} />
    809                             </div>)}
    810                         </Column>
    811                         <Column className="tm__center" md={5}>
    812                             {showSeatPicker && (
    813                             <SeatPicker
    814                                 names={playerNames}
    815                                 handleSit={this.sendSit}
    816                                 handleStand={this.sendStand}
    817                                 mySeat={mySeat}
    818                                 amSpectator={amSpectator}
    819                                 handleStart={this.sendStart}
    820                             />)}
    821                             { showTrump && (
    822                                 <div className="trump__outer">
    823                                     <div className={tcp}>
    824                                         <img className="trump__card" src={trumpImage} />
    825                                     </div>
    826                                 </div>
    827                             )}
    828                             { (phase=='play' || phase=='pause') && (
    829                                 <div className="trick__holder">{trickDisplay}</div>
    830                             )}
    831                             { showGameOver && (
    832                                 <div className="gameOver__holder">
    833                                     {gameOverDisplay}
    834                                 </div>
    835                             )}
    836                         </Column>
    837                     </Row>
    838                     <Row className="table__bot">
    839                         <Column className="tb__left" md={3}>
    840                             {showBottomInfo && (
    841                                 <div className="my__stack">
    842                                     <div className="my__hinfo">{this.genNameDisplay(mySeat)}</div>
    843                                     <div className="my__tinfo">{myTurnInfo}</div>
    844                                     <div className="my__tinfo">{trumpMsg}</div>
    845                                 </div>
    846                             )}
    847                             {showTrumpPicker && !amSpectator && (
    848                                 <TrumpPicker
    849                                     trumpCard={trumpNom}
    850                                     phaseTwo={phase == 'vote2'}
    851                                     myDeal={dealSeat == mySeat}
    852                                     onlyAlone={onlyAlone}
    853                                     noPick={noPick}
    854                                     noPass={noPass}
    855                                     handleVote={this.sendVote} />
    856                             )}
    857                             {showSwap && !amSpectator && (
    858                                 <div className="my__stack">
    859                                     <div className="my_tinfo">Click a card to discard:</div>
    860                                 </div>
    861                             )}
    862                         </Column>
    863                         <Column className="tb__center" md={5}>
    864                             {!showSeatPicker && !amSpectator && (
    865                                 <MainHand
    866                                     cards={myCards}
    867                                     cardClick={this.sendCard}
    868                                 />
    869                             )}
    870                             {!showSeatPicker && amSpectator && (
    871                                 <HiddenHand
    872                                 numCards={handLengths[mySeat]} />
    873                             )}
    874                         </Column>
    875                     </Row>
    876                 </Grid>
    877                 </Column>
    878                 <Column className="og__right" md={3}>
    879                 <Grid className="inner_right">
    880                 <Row className="tt__right" >
    881                     {phase == 'lobby' && (
    882                         <div className="exit__row">
    883                             <Button
    884                                 className="leave__button"
    885                                 kind="ghost"
    886                                 onClick={this.sendExit}
    887                                 renderIcon={Logout32}>Exit</Button>
    888                         </div>
    889                     )}
    890                     {(phase != 'lobby') && (
    891                     <div className="score__holder">
    892                         <div className="us__score">{usLabel}{score[0]}</div>
    893                         <div className="them__score">{themLabel}{score[1]}</div>
    894                         <div className="trick__win">{trickWinner}</div>
    895                         <div className="info__buttons">
    896                             <Tooltip
    897                                 direction="left"
    898                                 renderIcon={Information16}
    899                             >
    900                                 <div className="table__info">
    901                                     <div className="ti__name">{tableName}</div>
    902                                     <div className="ti__opt">HardOrder: {hard_order ? 'on' : 'off'}</div>
    903                                     <div className="ti__opt">HardPick: {hard_pick ? 'on' : 'off'}</div>
    904                                     <div className="ti__opt">StickDealer: {stick_dealer ? 'on' : 'off'}</div>
    905                                 </div>
    906                             </Tooltip>
    907                             <Tooltip
    908                                 direction="left"
    909                                 renderIcon={hasSpec? View16 : ViewOff16}
    910                             >
    911                                 <div className="spec__info">
    912                                     <div className="spec__title">
    913                                         {specMsgObj.title}
    914                                     </div>
    915                                     {(specMsgObj.list != null) && (
    916                                         <div className="spec__list">
    917                                             {specMsgObj.list}
    918                                         </div>
    919                                     )}
    920                                 </div>
    921                             </Tooltip>
    922                         </div>
    923                     </div>
    924                     )}
    925                 </Row>
    926                 <Row className="tm__right">
    927                     {!showSeatPicker && (
    928                     <div className="vert__stack right__stack">
    929                         <div className="player__name">{this.genNameDisplay(rightSeat)}</div>
    930                         <div className="play__hinfo">{rightHandInfo}</div>
    931                         <div className="play__tinfo">{rightTurnInfo}</div>
    932                         <HiddenHand
    933                             numCards={handLengths[rightSeat]} />
    934                     </div>)}
    935                 </Row>
    936                 <Row className="tb__right">
    937                     <ChatPanel
    938                         receivedChat={latestPost}
    939                         sendChat={this.sendChat} />
    940                 </Row>
    941                 </Grid>    
    942                 </Column>
    943             </Row>
    944         </Grid>
    945         </div>
    946         );
    947     };
    948 }
    949 
    950 CardTable.propTypes = {
    951     exitTable: PropTypes.func,
    952     name: PropTypes.string,
    953     tableName: PropTypes.string,
    954     firstMsg: PropTypes.object,
    955     client: PropTypes.object
    956 }