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 9784950f377f5a1349471e5f378ea573a350fba5 (patch)
parent 894fa1b729e9014b0cb359847b6df91baa0a8813
Author: Chris Karle <chriskarle@hotmail.com>
Date:   Tue, 14 Apr 2020 00:57:33 -0400

CardTable, MainHand

When game starts, apply names so local user is bottom.
Created MainHand row of clickable cards - overlapping
cards TBD.
Comment grid colors (just a layout guide).

Diffstat:
Massets/app.js | 29+++++++++++++++++++++--------
Massets/app.scss | 83++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Massets/components/CardTable.js | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Aassets/components/MainHand.js | 42++++++++++++++++++++++++++++++++++++++++++
Massets/components/SeatPicker.js | 54+++++++++++++++++++++++++++++++++++++++++++++++-------
5 files changed, 247 insertions(+), 63 deletions(-)

diff --git a/assets/app.js b/assets/app.js @@ -6,9 +6,11 @@ import CardTable from './components/CardTable'; import { w3cwebsocket as W3CWebSocket } from 'websocket'; const client = new W3CWebSocket('ws://localhost:3000/play'); +// additional fakeClient sockets, only used on debug +let fc1, fc2, fc3 = null; -const tableDebug = false; +const tableDebug = true; class App extends React.Component { @@ -21,16 +23,17 @@ class App extends React.Component { tableName: initialTable, showTable: tableDebug }; - if (tableDebug) { // on tableDebug send join w initialTable to server - // wait 1 second so socket establishes connection + if (tableDebug) { + // on tableDebug send join plus add 3 fakeClient players that join+sit + fc1 = new W3CWebSocket('ws://localhost:3000/play'); + fc2 = new W3CWebSocket('ws://localhost:3000/play'); + fc3 = new W3CWebSocket('ws://localhost:3000/play'); + // wait 1 second so sockets establish connection setTimeout(()=>{ - client.send(JSON.stringify({ - action:'join_game', - player_name: initialName, - game_id: initialTable - })); + this.setFakeGame(initialName, initialTable); }, 1000); } + this.pingTimer = setInterval(() => { client.send(JSON.stringify({action:'ping'})) }, 5000); } setPlayerName = name => { @@ -53,6 +56,16 @@ class App extends React.Component { }); } + 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 })); + fc2.send(JSON.stringify({ action:'join_game', player_name: 'CJ', game_id: initialTable })); + fc3.send(JSON.stringify({ action:'join_game', player_name: 'Dana', game_id: initialTable })); + fc1.send(JSON.stringify({ action:'take_seat', seat: 0 })); + fc2.send(JSON.stringify({ action:'take_seat', seat: 1 })); + fc3.send(JSON.stringify({ action:'take_seat', seat: 3 })); + } + render () { const {showTable, playerName, tableName} = this.state; return ( diff --git a/assets/app.scss b/assets/app.scss @@ -50,6 +50,32 @@ align-items: center; height: 20vh; } +.start__game { + display: flex; + .bx--dropdown__wrapper { + width: -webkit-fill-available; + } +} +.player__name { + font-size: 1.3rem; +} +.tm__left { + // background-color: rgb(217, 249, 255); + .player__name { + float: right; + } +} +.tt__center { + // background-color: rgb(217, 249, 255); + text-align: center; +} + +.mh__card { + width: 100px; +} +.mh__card__row { + display: flex; +} .table__header { height: 10vh; @@ -64,36 +90,33 @@ height: 20vh; } -.hd__left { - background-color: rosybrown; -} -.hd__right { - background-color: skyblue; -} -.tt__left { - background-color: slategray; -} -.tt__center { - background-color: lightgreen; -} -.tt__right { - background-color: lightsalmon; -} -.tm__left { - background-color: lightskyblue; -} + + +// .tm__right { +// background-color: rgb(217, 249, 255); +// } +// .tb__center { +// background-color: rgb(217, 249, 255); +// } + +// .tb__left { +// background-color: slategray; +// } +// .tb__right { +// background-color: lightsalmon; +// } +// .hd__left { +// background-color: rosybrown; +// } +// .hd__right { +// background-color: skyblue; +// } +// .tt__left { +// background-color: slategray; +// } +// .tt__right { +// background-color: lightsalmon; +// } // .tm__center { // background-color: lightpink; // } -.tm__right { - background-color: lightgreen; -} -.tb__left { - background-color: slategray; -} -.tb__center { - background-color: lightgreen; -} -.tb__right { - background-color: lightsalmon; -} diff --git a/assets/components/CardTable.js b/assets/components/CardTable.js @@ -4,6 +4,7 @@ import _ from 'lodash'; import {Button, Grid, Row, Column} from 'carbon-components-react'; import {Logout32} from '@carbon/icons-react'; import SeatPicker from './SeatPicker'; +import MainHand from './MainHand'; export default class CardTable extends React.Component { @@ -11,7 +12,12 @@ export default class CardTable extends React.Component { super(props); this.state = { playerNames: [], - mySeat: -1 + mySeat: -1, + phase: 'lobby', + left: { name: '', seat: -1 }, + partner: { name: '', seat: -1 }, + right: { name: '', seat: -1 }, + myCards: [] }; }; @@ -22,25 +28,68 @@ export default class CardTable extends React.Component { processResponse = (event) => { let msg = JSON.parse(event.data); - console.log(msg); + if ('pong' != msg.msg_type) { + console.log(msg); + } if ('game_state' == msg.msg_type) { - if ('lobby' == msg.game.phase){ - this.processLobby(msg); + if (msg.game) { + switch (msg.game.phase) { + case 'lobby': + this.processLobby(msg); + break; + case 'vote': + this.processVote(msg); + break; + default: + break; + } } }; }; processLobby = (msg) => { - if (msg && msg.game && msg.game.players) { + if (msg.game.players) { const plAr = msg.game.players; const mySeat = plAr.findIndex( x => x == this.props.name ); this.setState({ playerNames: plAr, - mySeat: mySeat + mySeat: mySeat, + phase: 'lobby' }) }; }; + processVote = (msg) => { + if (this.state.phase == 'lobby') { + this.gameStartSetup(); + } + this.setState({ + phase: 'vote', + myCards: msg.hand + }); + }; + + gameStartSetup = () => { + const { playerNames, mySeat } = this.state; + const leftSeat = (mySeat + 1) % 4; + const partnerSeat = (mySeat + 2) % 4; + const rightSeat = (mySeat + 3) % 4; + this.setState ({ + left : { + name: playerNames[leftSeat], + seat: leftSeat + }, + partner : { + name: playerNames[partnerSeat], + seat: partnerSeat + }, + right : { + name: playerNames[rightSeat], + seat: rightSeat + } + }); + }; + sendSit = (index) => { const { client } = this.props; client.send(JSON.stringify({ @@ -56,14 +105,28 @@ export default class CardTable extends React.Component { })); }; + sendStart = (startDealer) => { + this.props.client.send(JSON.stringify({ + action: 'start_game' + })) + console.log('start game, dealer = ', startDealer); + }; + + sendCard = (index) => { + console.log('card click ', index); + } + render () { - const { playerNames, mySeat } = this.state; + const { playerNames, mySeat, phase, left, partner, right, myCards } = this.state; const {name, tableName} = this.props; + const showSeatPicker = phase == 'lobby'; + const showTrump = phase == 'vote'; const welcomeMsg = 'Welcome to the ' + tableName + ' table, ' + name + '!'; return ( <div id="table"> <Grid> - <Row className="table__header"> + {showSeatPicker && ( + <Row className="table__header"> <Column className="hd__left" sm={3}> <h3>{welcomeMsg}</h3> </Column> @@ -77,43 +140,46 @@ export default class CardTable extends React.Component { </div> </Column> </Row> + )} <Row className="table__top"> <Column className="tt__left" sm={1}> - <div>yo!</div> </Column> <Column className="tt__center" sm={2}> - <div>yo!</div> + <div className="player__name">{partner.name}</div> </Column> <Column className="tt__right" sm={1}> - <div>yo!</div> </Column> </Row> <Row className="table__mid"> <Column className="tm__left" sm={1}> - <div>yo!</div> + <div className="player__name">{left.name}</div> </Column> <Column className="tm__center" sm={2}> - {/* <div>yo!</div> */} + {showSeatPicker && ( <SeatPicker names={playerNames} handleSit={this.sendSit} handleStand={this.sendStand} mySeat={mySeat} - /> + handleStart={this.sendStart} + />)} </Column> <Column className="tm__right" sm={1}> - <div>yo!</div> + <div className="player__name">{right.name}</div> </Column> </Row> <Row className="table__bot"> <Column className="tb__left" sm={1}> - <div>yo!</div> </Column> <Column className="tb__center" sm={2}> - <div>yo!</div> + {!showSeatPicker && ( + <MainHand + cards={myCards} + cardClick={this.sendCard} + /> + )} </Column> <Column className="tb__right" sm={1}> - <div>yo!</div> </Column> </Row> </Grid> diff --git a/assets/components/MainHand.js b/assets/components/MainHand.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Button, Link} from 'carbon-components-react'; + +class MainHand extends React.Component { + + cardButtons = (cards) => { + let buttons = []; + cards.map((card, index) => { + const cardSrc = 'cards/' + card + '.svg'; + buttons.push( + <div + key={index} + className="mh__card__div"> + <Link + className="mh__card__link" + href="javascript:void(0)" + onClick={()=> this.props.cardClick(index)}> + <img className="mh__card" src={cardSrc} /> + </Link> + </div> + ) + }); + return buttons; + } + + render () { + const { cards } = this.props; + const cardButtons = this.cardButtons(cards); + return ( + <div className="mh__card__row"> + {cardButtons} + </div> + ); + }; +} +MainHand.propTypes = { + cards: PropTypes.arrayOf(PropTypes.string), + cardClick: PropTypes.func +} +export default MainHand; +\ No newline at end of file diff --git a/assets/components/SeatPicker.js b/assets/components/SeatPicker.js @@ -1,15 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Button, Grid, Row, Column} from 'carbon-components-react'; -import {Package32, Export32} from '@carbon/icons-react'; +import {Button, Grid, Row, Column, Dropdown} from 'carbon-components-react'; +import {Package32, Export32, Play32} from '@carbon/icons-react'; class SeatPicker extends React.Component { constructor(props) { super(props); - //this.state - unneeded? - } + this.state = { + startDealer: -2 + } + }; + + allSeated = names => { + const matchIndex = names.findIndex(name => name == 'Empty'); + return matchIndex == -1; + }; + dealerChange = (e) => { + const { names } = this.props; + const selectIndex = names.findIndex(name => e.selectedItem == name); + this.setState({ + startDealer: selectIndex + }); + }; tableSeat = (name, index) => { const taken = name != 'Empty'; @@ -37,7 +51,11 @@ class SeatPicker extends React.Component { } render () { - const {names} = this.props; + const { names } = this.props; + const { startDealer } = this.state; + const allSeated = this.allSeated(names); + const pickerNames = names.slice(0); + pickerNames.unshift('Random'); return ( <div id="seatPicker" className="seat__picker"> <Grid> @@ -56,14 +74,36 @@ class SeatPicker extends React.Component { {this.tableSeat(names[0], 0)} </Row> </Grid> + { allSeated && ( + <div id="startGame" className="start__game"> + <Dropdown + id="dealer__drop" + className="dealer__drop" + label="Choose Dealer..." + size="xl" + itemToElement={null} + items={pickerNames} + onChange={(event)=>this.dealerChange(event)} + /> + <Button + className="start__game__button" + hasIconOnly={true} + iconDescription="set name" + tooltipPosition="bottom" + disabled={startDealer < -1} + onClick={()=>{this.props.handleStart(startDealer)}} + renderIcon={Play32}>Play! </Button> + </div> + )} </div> - ) + ); } } SeatPicker.propTypes = { mySeat: PropTypes.number, names: PropTypes.arrayOf(PropTypes.string), handleSit: PropTypes.func, - handleStand: PropTypes.func + handleStand: PropTypes.func, + handleStart: PropTypes.func } export default SeatPicker;