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 (36908B) [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             let anyEmpty = plAr.includes('Empty');
    259             if (newSpec && !anyEmpty){
    260                 // console.log('all seats taken, initMySeat.newSpec, mySeat=3');
    261                 mySeat = 3;
    262             };
    263             this.setState({
    264                 playerNames: plAr,
    265                 mySeat: mySeat,
    266                 amSpectator: newSpec,
    267                 hard_order: msg.settings.hard_order,
    268                 hard_pick: msg.settings.hard_pick,
    269                 stick_dealer: msg.settings.stick_dealer
    270             });
    271         };
    272     }
    273 
    274     haveSuit = (hand, trumpNom) => {
    275         const targetSuit = trumpNom.substring(1);
    276         for (let i=0; i < hand.length; i++) {
    277             if (hand[i].substring(1) == targetSuit) {
    278                 console.log('targetSuit:' + targetSuit + ' matched by ' + hand[i]);
    279                 return true;
    280             }
    281         }
    282         return false;
    283     }
    284 
    285     processVote = (msg) => {
    286         if (this.state.phase == 'lobby') {
    287             this.gameStartSetup(msg);
    288         } else if (this.state.phase == 'pause' || 
    289                   (this.state.phase == 'vote2' && msg.game.pass_count == 0)) {
    290                 // second condition is for all players pass trump twice
    291             this.trumpStartSetup(msg);
    292         }
    293         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat, hard_order, hard_pick, stick_dealer} = this.state;
    294         const vote1phase = msg.game.pass_count < 4;
    295         const phaseString = vote1phase ? 'vote' : 'vote2';
    296         const onlyAlone = hard_order && vote1phase && dealSeat == partnerSeat;
    297         const noPass = stick_dealer && !vote1phase && (dealSeat == mySeat);
    298         const noPick = hard_pick && vote1phase && (dealSeat == mySeat) && !this.haveSuit(msg.hand, msg.game.trump_nominee);
    299         let turnInfo = ['', '', '', ''];
    300         turnInfo[msg.game.turn] = 'trump?';
    301         this.setState({
    302             phase: phaseString,
    303             myCards: msg.hand,
    304             turnSeat: msg.game.turn,
    305             onlyAlone: onlyAlone,
    306             noPick: noPick,
    307             noPass: noPass,
    308             handLengths: msg.game.hand_lengths,
    309             leftTurnInfo: turnInfo[leftSeat],
    310             rightTurnInfo: turnInfo[rightSeat],
    311             partnerTurnInfo: turnInfo[partnerSeat],
    312             myTurnInfo: turnInfo[mySeat]
    313         });
    314     };
    315 
    316     processSwap = msg => {
    317         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    318         let turnInfo = ['', '', '', ''];
    319         turnInfo[dealSeat] = 'Swapping...';
    320         this.setState({
    321             phase: 'swap',
    322             leftTurnInfo: turnInfo[leftSeat],
    323             rightTurnInfo: turnInfo[rightSeat],
    324             partnerTurnInfo: turnInfo[partnerSeat],
    325             myTurnInfo: turnInfo[mySeat]
    326         });
    327     }
    328 
    329     trickMsg = (num, action) => {
    330         let retVal;
    331         if (num == 0) {
    332             retVal = action ? 'action' : '';
    333         } else {
    334             retVal = num == 1 ? '1 trick' : (num + ' tricks');
    335             if (action) {
    336                 retVal = retVal + ', action';
    337             }
    338         }
    339         return retVal;
    340     }
    341 
    342     processPlay = msg => {
    343         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat, phase} = this.state;
    344         let turnInfo = [];
    345         if (phase == 'vote' || phase == 'vote2' || phase == 'swap') {
    346             this.handStartSetup(msg);
    347         }
    348         for (let i = 0; i < 4; i++) {
    349             const tricks = msg.game.tricks[i];
    350             let tInf = this.trickMsg(tricks, (msg.game.turn == i));
    351             turnInfo.push(tInf);
    352         }
    353         this.setState({
    354             phase: 'play',
    355             myCards: msg.hand,
    356             table: msg.game.table,
    357             tricks: msg.game.tricks,
    358             trickWinner: '',
    359             handLengths: msg.game.hand_lengths,
    360             leftTurnInfo: turnInfo[leftSeat],
    361             rightTurnInfo: turnInfo[rightSeat],
    362             partnerTurnInfo: turnInfo[partnerSeat],
    363             myTurnInfo: turnInfo[mySeat]
    364         });
    365 
    366     }
    367 
    368     processPause = msg => {
    369         let trickWinIndex = -1;
    370         let trickWinner = '';
    371         for (let i = 0; i < 4; i++){
    372             if (msg.game.tricks[i] != this.state.tricks[i]){
    373                 trickWinIndex = i;
    374                 break;
    375             }
    376         }
    377         if (trickWinIndex > -1) {
    378             //must be?? sanity check
    379             trickWinner = this.state.playerNames[trickWinIndex] + ' takes the trick!'
    380         }
    381         this.setState ({
    382             table: msg.game.table,
    383             handLengths: msg.game.hand_lengths,
    384             myCards: msg.hand,
    385             tricks: msg.game.tricks,
    386             phase: 'pause',
    387             trickWinner: trickWinner
    388         })
    389     }
    390 
    391     processEnd = msg => {
    392         const {playerNames, leftSeat, partnerSeat, rightSeat, amSpectator} = this.state;
    393         // arrangeScore[0] us, [1] them
    394         const finalScore = this.arrangeScore(msg.game.score);
    395         const myName =  amSpectator ? playerNames[3] : 'You';
    396         const winMsg = finalScore[0] > finalScore[1] ?
    397             myName + ' and ' + playerNames[partnerSeat] + ' win the game!!' :
    398             playerNames[leftSeat] + ' and ' + playerNames[rightSeat] + ' win this one...';
    399         const innerWin = amSpectator ? 'Game Over...' :
    400           finalScore[0] > finalScore[1] ? 'You Win!!' : 'You lost...';
    401         this.setState({
    402             phase: 'end',
    403             score: finalScore,
    404             bannerMsg: winMsg,
    405             innerWinMsg: innerWin
    406         });
    407     }
    408 
    409     handStartSetup = msg => {
    410         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    411         let handInfo = ['', '', '', ''];
    412         const caller = msg.game.out_player > -1 ? 'Alone' : 'Caller';
    413         const callSeat = msg.game.caller;
    414         if (callSeat == dealSeat) {
    415             handInfo[dealSeat] = 'Dealer, ' + caller;
    416         } else {
    417             handInfo[dealSeat] = 'Dealer';
    418             handInfo[callSeat] = caller;
    419         }
    420         // this.state.score is always [{us}, {them}]
    421         let score = this.arrangeScore(msg.game.score);
    422         this.setState({
    423             trump: msg.game.trump,
    424             table: msg.game.table,
    425             score: score,
    426             handLengths: msg.game.hand_lengths,
    427             leftHandInfo: handInfo[leftSeat],
    428             rightHandInfo: handInfo[rightSeat],
    429             partnerHandInfo: handInfo[partnerSeat],
    430             myHandInfo: handInfo[mySeat]
    431         });
    432 
    433     }
    434 
    435     arrangeScore = (msgScore) => {
    436         // this.state.score is always [{us}, {them}]
    437         const { mySeat } = this.state;
    438         let score = [];
    439         if (mySeat % 2 == 0){
    440             // we're evens
    441             score[0] = msgScore[0];
    442             score[1] = msgScore[1];
    443         } else {
    444             score[0] = msgScore[1];
    445             score[1] = msgScore[0];
    446         }
    447         return score;
    448     }
    449 
    450     trumpStartSetup = (msg) => {
    451         const {leftSeat, rightSeat, partnerSeat, mySeat, dealSeat} = this.state;
    452         let handInfo = ['', '', '', ''];
    453         const newDeal = msg.game.dealer;
    454         handInfo[newDeal] = 'Dealer';
    455         let tpIndex = msg.game.dealer - mySeat;
    456         tpIndex = (tpIndex < 0) ? tpIndex + 4 : tpIndex;
    457         const trumpPlace = trumpPlacement[tpIndex];
    458         // this.state.score is always [{us}, {them}]
    459         let score = this.arrangeScore(msg.game.score);
    460         this.setState({
    461             trump: '',
    462             trumpPlace: trumpPlace,
    463             dealSeat: newDeal,
    464             score: score,
    465             trickWinner: '',
    466             trumpNom: msg.game.trump_nominee,
    467             leftHandInfo: handInfo[leftSeat],
    468             rightHandInfo: handInfo[rightSeat],
    469             partnerHandInfo: handInfo[partnerSeat],
    470             myHandInfo: handInfo[mySeat]
    471         });
    472 
    473     }
    474 
    475     gameStartSetup = (msg) => {
    476         const { playerNames, mySeat } = this.state;
    477         let handInfo = [' ', ' ', ' ', ' '];
    478         let turnInfo = [' ', ' ', ' ', ' '];
    479         handInfo[msg.game.dealer] = 'Dealer';
    480         turnInfo[msg.game.turn] = 'trump?';
    481         const leftSeat = (mySeat + 1) % 4;
    482         const partnerSeat = (mySeat + 2) % 4;
    483         const rightSeat = (mySeat + 3) % 4;
    484         let tpIndex = msg.game.dealer - mySeat;
    485         tpIndex = (tpIndex < 0) ? tpIndex + 4 : tpIndex;
    486         const trumpPlace = trumpPlacement[tpIndex];
    487         this.setState ({
    488             leftName: playerNames[leftSeat],
    489             leftSeat: leftSeat,
    490             leftHandInfo: handInfo[leftSeat],
    491             leftTurnInfo: turnInfo[leftSeat],
    492             partnerName: playerNames[partnerSeat],
    493             partnerSeat: partnerSeat,
    494             partnerHandInfo: handInfo[partnerSeat],
    495             partnerTurnInfo: turnInfo[partnerSeat],
    496             rightName: playerNames[rightSeat],
    497             rightSeat: rightSeat,
    498             rightHandInfo: handInfo[rightSeat],
    499             rightTurnInfo: turnInfo[rightSeat],
    500             myHandInfo : handInfo[mySeat],
    501             myTurnInfo : turnInfo[mySeat],
    502             trumpPlace : trumpPlace,
    503             trumpNom: msg.game.trump_nominee,
    504             dealSeat: msg.game.dealer
    505         });
    506     };
    507 
    508     sendSit = (index) => {
    509         const { client } = this.props;
    510         client.send(JSON.stringify({
    511             action:'take_seat',
    512             seat: index
    513         }));
    514     };
    515 
    516     sendStand = () => {
    517         const { client } = this.props;
    518         client.send(JSON.stringify({
    519             action:'stand_up'
    520         }));
    521     };
    522 
    523     sendStart = (startDealer) => {
    524         this.props.client.send(JSON.stringify({
    525             action: 'start_game', start_seat: startDealer
    526         }));
    527         // console.log('start game, dealer = ', startDealer);
    528     };
    529 
    530     sendExit = () => {
    531         this.props.exitTable();
    532     };
    533 
    534     sendVote = (voteObject) => {
    535         const voteString = JSON.stringify(voteObject);
    536         // console.log('sendVote:', voteString);
    537         this.props.client.send(voteString);
    538     };
    539 
    540     sendCard = (index) => {
    541         const {phase, myCards} = this.state;
    542         // console.log('card click ', myCards[index]);
    543         if (phase == 'swap') {
    544             this.props.client.send(JSON.stringify({
    545                 action:'dealer_swap', card: myCards[index]
    546             }));
    547         } else if (phase == 'play') {
    548             this.props.client.send(JSON.stringify({
    549                 action:'play_card', card: myCards[index]
    550             }));
    551         }
    552     };
    553 
    554     sendRestart = () => {
    555         this.props.client.send(JSON.stringify({
    556             action:'restart_game'
    557         })); 
    558     }
    559 
    560     sendChat = post => {
    561         console.log(post);
    562         if (post && post != ''){
    563             this.props.client.send(JSON.stringify({
    564                 action: 'chat',
    565                 msg: post
    566             }));
    567         }
    568         // XX TODO remove this debug action when chat stable
    569         if ('chat10' == post){
    570             for (let i=0; i < 10; i++){
    571                 const msg='this is chat10 test-chat-' + i;
    572                 this.props.client.send(JSON.stringify({
    573                     action: 'chat',
    574                     msg: msg
    575                 }));
    576             }
    577         }
    578     }
    579 
    580     toggleErrorDisplay = () => {
    581         // NOTE this trick requires that .err__post is the first selector of all,
    582         //  so requires it first in our .scss, even before imports
    583         const { showErrors } = this.state;
    584         let stylesheet = document.styleSheets[0];
    585         const nextShowErrors = !showErrors;
    586         const rule = nextShowErrors ? 'inherit' : 'none';
    587         stylesheet.cssRules[0].style.display = rule;
    588         this.setState({
    589             showErrors: nextShowErrors
    590         })
    591         console.log('toggleErrorDisplay');
    592     }
    593 
    594     genGameOver = () => {
    595         const {innerWinMsg, amSpectator} = this.state;
    596         let retVal = [];
    597         const instMsg = amSpectator ? 'You can take a seat if one becomes empty, or you can return to the lobby...'
    598         : 'You can play again at this table, or return to the lobby to change your table or player name...';
    599         retVal.push(
    600         <div className="gover__outer" key="gom1">
    601             <div className="gover__inwin">{innerWinMsg}</div>
    602             <div className="gover__inst">
    603                {instMsg} 
    604             </div>
    605             <div className="gover__buttons">
    606                 <Button
    607                     className="exit2__button"
    608                     kind="secondary"
    609                     onClick={this.sendExit}
    610                     renderIcon={Logout32}
    611                     >Exit to Lobby</Button> 
    612                 {!amSpectator && ( 
    613                     <Button
    614                     className="repeat__button"
    615                     kind="primary"
    616                     onClick={()=>this.sendRestart()}
    617                     renderIcon={Redo32}
    618                     >Play Again!!</Button> 
    619 
    620                 )}
    621             </div>
    622         </div>
    623         );
    624         return retVal;
    625     }
    626 
    627     genTrick = () => {
    628         let retVal = [];
    629         const { table, mySeat, leftSeat, rightSeat, partnerSeat } = this.state;
    630         const myCard = table[mySeat];
    631         const leftCard = table[leftSeat];
    632         const partnerCard = table[partnerSeat];
    633         const rightCard = table[rightSeat];
    634         const mySrc = 'cards/' + myCard + '.svg';
    635         const leftSrc = 'cards/' + leftCard + '.svg';
    636         const partnerSrc = 'cards/' + partnerCard + '.svg';
    637         const rightSrc = 'cards/' + rightCard + '.svg';
    638         let midClass = 'mid__trick';
    639         if (myCard && partnerCard) {
    640             midClass += ' both';
    641         } else if (myCard) {
    642             midClass += ' me';
    643         } else if (partnerCard) {
    644             midClass += ' partner';
    645         }
    646         retVal.push (
    647             <div className="trick__outer" key="gt1">
    648                 <Grid className="trick__grid">
    649                     <Row className="trick__row">
    650                         <Column>
    651                             <div className="left__trick">
    652                                 {leftCard && (
    653                                     <div className="trick__div">
    654                                         <img className="trick__card" src={leftSrc} />
    655                                     </div>
    656                                 )}
    657                             </div>
    658                         </Column>
    659                         <Column>
    660                             <div className={midClass}>
    661                                 {partnerCard && (
    662                                     <div className="trick__div">
    663                                         <img className="trick__card" src={partnerSrc} />
    664                                     </div>
    665                                 )}
    666                                 {myCard && (
    667                                     <div className="trick__div">
    668                                         <img className="trick__card" src={mySrc} />
    669                                     </div>
    670                                 )}
    671                             </div>
    672                         </Column>
    673                         <Column>
    674                             <div className="right__trick">
    675                                 {rightCard && (
    676                                     <div className="trick__div">
    677                                         <img className="trick__card" src={rightSrc} />
    678                                     </div>
    679                                 )}
    680                             </div>
    681                         </Column>
    682                     </Row>
    683                 </Grid>
    684             </div>
    685         );
    686         return retVal;
    687     }
    688 
    689     genNameDisplay = seatNum => {
    690         let retVal = '';
    691         const { playerNames, mySeat, myHandInfo, amSpectator } = this.state;
    692         let seatName = playerNames[seatNum];
    693         if (seatNum == mySeat) {
    694             seatName = amSpectator ? seatName : 'You';
    695             if (seatName != 'Empty'){
    696                 seatName += ': ' + myHandInfo;
    697             }
    698         }
    699         if (seatName != 'Empty') {
    700             retVal = (<div>{seatName}</div>);
    701         } else {
    702             retVal = (
    703             <div>Empty
    704                 {amSpectator && (
    705                     <Button
    706                     className="sit__button"
    707                     kind="ghost"
    708                     onClick={()=>{this.sendSit(seatNum)}}
    709                     renderIcon={Package32}>Choose seat</Button>
    710                 )}
    711             </div>);
    712         }
    713         return retVal;
    714     }
    715 
    716     genSpecMsgObj = () => {
    717         const {spectators} = this.state;
    718         let retVal = {}
    719         if (!spectators || spectators.length == 0) {
    720             retVal.title = 'No Spectators';
    721             retVal.list = null;
    722         } else {
    723             retVal.title = (spectators.length == 1) ? '1 Spectator:' : spectators.length + ' Spectators:'; 
    724             retVal.list = spectators[0];
    725             for (let i=1; i < spectators.length; i++) {
    726                 retVal.list += ', ' + spectators[i];
    727             };
    728         }
    729         return retVal;
    730     }
    731 
    732     render () {
    733         const { playerNames, mySeat, phase, myCards, myTurnInfo, amSpectator,
    734             partnerHandInfo, partnerTurnInfo, partnerSeat, leftTurnInfo, leftHandInfo, leftSeat,
    735             rightHandInfo, rightTurnInfo, rightSeat, trumpPlace, trumpNom, turnSeat, spectators,
    736             dealSeat, trump, handLengths, score, trickWinner, bannerMsg, noPick, noPass, onlyAlone,
    737             hard_pick, hard_order, stick_dealer, latestPost, showErrors } = this.state;
    738         const {tableName} = this.props;
    739         const showSeatPicker = phase == 'lobby';
    740         const showGameOver = phase == 'end';
    741         const showTrump = (phase == 'vote') || (phase == 'vote2') || (phase == 'swap');
    742         const showTrumpPicker = showTrump && (turnSeat == mySeat);
    743         const showSwap = (phase == 'swap') && (dealSeat == mySeat);
    744         const showBottomInfo = !showSeatPicker && (amSpectator || (!showTrumpPicker && !showSwap));
    745         const tcp = "trump__holder " + trumpPlace;
    746         const trumpImage = (phase != 'vote2') ? 'cards/' + trumpNom + '.svg' : 'cards/1B.svg';
    747         const trumpMsg = phase == 'play' ? suit[trump] + ' are trump' : '';
    748         const trickDisplay = (phase == 'play' || phase == 'pause') ? this.genTrick() : [];
    749         const gameOverDisplay = (phase == 'end') ? this.genGameOver() : [];
    750         const usLabel = amSpectator ? playerNames[1] + ' & ' + playerNames[3] + ': ' : 'Us: ';
    751         const themLabel = amSpectator ? playerNames[0] + ' & ' + playerNames[2] + ': ' : 'Them: ';
    752         const hasSpec = spectators.length > 0;
    753         const specMsgObj = this.genSpecMsgObj();
    754         const toggleErrorMsg = showErrors ? 'Hide errors in chat' : 'Show errors in chat';
    755         return (
    756             <div id="table" className="table__main">
    757                 <Grid className="og">
    758                     <Row className="og__row">
    759                         <Column className="og__left" md={5}>
    760                         <Grid className="inner__left">
    761                     {(showSeatPicker || showGameOver) && (
    762                      <Row className="table__header">
    763                          <h3 className="banner">{bannerMsg}</h3>
    764                     </Row>
    765                     )}
    766                     <Row className="table__top">
    767                         <Column className="tt__left" md={3}>
    768                             {!showSeatPicker && (
    769                                     <div className="menu__holder">
    770                                         <OverflowMenu>
    771                                             <OverflowMenuItem
    772                                                 itemText="Stand"
    773                                                 disabled={amSpectator}
    774                                                 onClick={this.sendStand}
    775                                             />
    776                                             <OverflowMenuItem
    777                                                 itemText="Exit to Lobby"
    778                                                 onClick={this.sendExit}
    779                                             />
    780                                             <OverflowMenuItem
    781                                                 itemText={toggleErrorMsg}
    782                                                 onClick={this.toggleErrorDisplay}
    783                                             />
    784                                         </OverflowMenu>
    785                                     </div>
    786                                 )}
    787                         </Column>
    788                         <Column className="tt__center" md={5}>
    789                             {!showSeatPicker && (
    790                             <div className="partner__stack">
    791                                 <div className="player__name">{this.genNameDisplay(partnerSeat)}</div>
    792                                 <div className="partner__info">
    793                                     <div className="play__hinfo">{partnerHandInfo}</div>
    794                                     <div className="play__tinfo">{partnerTurnInfo}</div>
    795                                 </div>
    796                                 <HiddenHand
    797                                     numCards={handLengths[partnerSeat]} />
    798                             </div>)}
    799                         </Column>
    800                     </Row>
    801                     <Row className="table__mid">
    802                         <Column className="tm__left" md={3}>
    803                             {!showSeatPicker && (
    804                             <div className="vert__stack">
    805                                 <div className="player__name">{this.genNameDisplay(leftSeat)}</div>
    806                                 <div className="play__hinfo">{leftHandInfo}</div>
    807                                 <div className="play__tinfo">{leftTurnInfo}</div>
    808                                 <HiddenHand
    809                                     numCards={handLengths[leftSeat]} />
    810                             </div>)}
    811                         </Column>
    812                         <Column className="tm__center" md={5}>
    813                             {showSeatPicker && (
    814                             <SeatPicker
    815                                 names={playerNames}
    816                                 handleSit={this.sendSit}
    817                                 handleStand={this.sendStand}
    818                                 mySeat={mySeat}
    819                                 amSpectator={amSpectator}
    820                                 handleStart={this.sendStart}
    821                             />)}
    822                             { showTrump && (
    823                                 <div className="trump__outer">
    824                                     <div className={tcp}>
    825                                         <img className="trump__card" src={trumpImage} />
    826                                     </div>
    827                                 </div>
    828                             )}
    829                             { (phase=='play' || phase=='pause') && (
    830                                 <div className="trick__holder">{trickDisplay}</div>
    831                             )}
    832                             { showGameOver && (
    833                                 <div className="gameOver__holder">
    834                                     {gameOverDisplay}
    835                                 </div>
    836                             )}
    837                         </Column>
    838                     </Row>
    839                     <Row className="table__bot">
    840                         <Column className="tb__left" md={3}>
    841                             {showBottomInfo && (
    842                                 <div className="my__stack">
    843                                     <div className="my__hinfo">{this.genNameDisplay(mySeat)}</div>
    844                                     <div className="my__tinfo">{myTurnInfo}</div>
    845                                     <div className="my__tinfo">{trumpMsg}</div>
    846                                 </div>
    847                             )}
    848                             {showTrumpPicker && !amSpectator && (
    849                                 <TrumpPicker
    850                                     trumpCard={trumpNom}
    851                                     phaseTwo={phase == 'vote2'}
    852                                     myDeal={dealSeat == mySeat}
    853                                     onlyAlone={onlyAlone}
    854                                     noPick={noPick}
    855                                     noPass={noPass}
    856                                     handleVote={this.sendVote} />
    857                             )}
    858                             {showSwap && !amSpectator && (
    859                                 <div className="my__stack">
    860                                     <div className="my_tinfo">Click a card to discard:</div>
    861                                 </div>
    862                             )}
    863                         </Column>
    864                         <Column className="tb__center" md={5}>
    865                             {!showSeatPicker && !amSpectator && (
    866                                 <MainHand
    867                                     cards={myCards}
    868                                     cardClick={this.sendCard}
    869                                 />
    870                             )}
    871                             {!showSeatPicker && amSpectator && (
    872                                 <HiddenHand
    873                                 numCards={handLengths[mySeat]} />
    874                             )}
    875                         </Column>
    876                     </Row>
    877                 </Grid>
    878                 </Column>
    879                 <Column className="og__right" md={3}>
    880                 <Grid className="inner_right">
    881                 <Row className="tt__right" >
    882                     {phase == 'lobby' && (
    883                         <div className="exit__row">
    884                             <Button
    885                                 className="leave__button"
    886                                 kind="ghost"
    887                                 onClick={this.sendExit}
    888                                 renderIcon={Logout32}>Exit</Button>
    889                         </div>
    890                     )}
    891                     {(phase != 'lobby') && (
    892                     <div className="score__holder">
    893                         <div className="us__score">{usLabel}{score[0]}</div>
    894                         <div className="them__score">{themLabel}{score[1]}</div>
    895                         <div className="trick__win">{trickWinner}</div>
    896                         <div className="info__buttons">
    897                             <Tooltip
    898                                 direction="left"
    899                                 renderIcon={Information16}
    900                             >
    901                                 <div className="table__info">
    902                                     <div className="ti__name">{tableName}</div>
    903                                     <div className="ti__opt">HardOrder: {hard_order ? 'on' : 'off'}</div>
    904                                     <div className="ti__opt">HardPick: {hard_pick ? 'on' : 'off'}</div>
    905                                     <div className="ti__opt">StickDealer: {stick_dealer ? 'on' : 'off'}</div>
    906                                 </div>
    907                             </Tooltip>
    908                             <Tooltip
    909                                 direction="left"
    910                                 renderIcon={hasSpec? View16 : ViewOff16}
    911                             >
    912                                 <div className="spec__info">
    913                                     <div className="spec__title">
    914                                         {specMsgObj.title}
    915                                     </div>
    916                                     {(specMsgObj.list != null) && (
    917                                         <div className="spec__list">
    918                                             {specMsgObj.list}
    919                                         </div>
    920                                     )}
    921                                 </div>
    922                             </Tooltip>
    923                         </div>
    924                     </div>
    925                     )}
    926                 </Row>
    927                 <Row className="tm__right">
    928                     {!showSeatPicker && (
    929                     <div className="vert__stack right__stack">
    930                         <div className="player__name">{this.genNameDisplay(rightSeat)}</div>
    931                         <div className="play__hinfo">{rightHandInfo}</div>
    932                         <div className="play__tinfo">{rightTurnInfo}</div>
    933                         <HiddenHand
    934                             numCards={handLengths[rightSeat]} />
    935                     </div>)}
    936                 </Row>
    937                 <Row className="tb__right">
    938                     <ChatPanel
    939                         receivedChat={latestPost}
    940                         sendChat={this.sendChat} />
    941                 </Row>
    942                 </Grid>    
    943                 </Column>
    944             </Row>
    945         </Grid>
    946         </div>
    947         );
    948     };
    949 }
    950 
    951 CardTable.propTypes = {
    952     exitTable: PropTypes.func,
    953     name: PropTypes.string,
    954     tableName: PropTypes.string,
    955     firstMsg: PropTypes.object,
    956     client: PropTypes.object
    957 }