From 9cf3c987f3b5c9a6ff08d8cc130b090c32625e46 Mon Sep 17 00:00:00 2001 From: Neel Somani Date: Fri, 24 Apr 2020 03:40:23 -0700 Subject: [PATCH] Add make move action to UI (#6) * Add make move action to UI * Add make move call * Show correct cards for make move and add instructions * Fix tests --- app.py | 2 +- src/App.css | 23 +++++++- src/App.js | 55 +++++++++++++++++- src/App.test.js | 5 ++ src/components/Card.js | 6 +- src/components/MakeMoveModal.js | 51 ++++++++++++++++ src/components/MoveDisplay.js | 10 ++-- src/components/Player.js | 13 ++++- src/components/Players.js | 3 +- src/components/Timer.js | 19 +++++- src/components/VerticalCards.js | 51 ++++++++++++---- .../{player.png => player-even.png} | Bin src/components/player-odd.png | Bin 0 -> 16227 bytes 13 files changed, 209 insertions(+), 29 deletions(-) create mode 100644 src/components/MakeMoveModal.js rename src/components/{player.png => player-even.png} (100%) create mode 100644 src/components/player-odd.png diff --git a/app.py b/app.py index 448e712..8a7bc4d 100644 --- a/app.py +++ b/app.py @@ -16,7 +16,7 @@ api = LiteratureAPI(u_id=uuid.uuid4().hex, logger=app.logger, n_players=4, - time_limit=10) + time_limit=30) @app.route('/') diff --git a/src/App.css b/src/App.css index 6d4c675..de70df7 100644 --- a/src/App.css +++ b/src/App.css @@ -20,11 +20,11 @@ } .MoveDisplay { - width: 50%; + width: 80%; top: 50%; left: 50%; position: absolute; - margin-left: -25%; + margin-left: -40%; margin-top: -1em; font-size: 2em; text-align: center; @@ -42,3 +42,22 @@ padding-top: 2%; padding-bottom: 2%; } + +.MakeMoveCover { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + align-items: center; + justify-content: center; + display: flex; +} + +.CardSelector { + text-align: center; + background-color: white; + border-radius: 10px; + padding: 1%; +} diff --git a/src/App.js b/src/App.js index 9960bb7..276ba51 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,7 @@ import Players from './components/Players'; import MoveDisplay from './components/MoveDisplay'; import Timer from './components/Timer'; import VerticalCards from './components/VerticalCards'; +import MakeMoveModal from './components/MakeMoveModal'; import './App.css'; class App extends Component { @@ -12,9 +13,47 @@ class App extends Component { uuid: '', hand: [], nPlayers: 0, + showMakeMoveModal: false }; } + playCard(card) { + // Make sure the user is looking at the make move modal. + if (this.state.showMakeMoveModal && card) { + this.makeMove(card, this.state.toBeRespondent); + } + } + + hideMakeMoveModal() { + this.setState({ + showMakeMoveModal: false, + toBeRespondent: undefined + }); + } + + showMakeMoveModal(toBeRespondent) { + if (this.state.turn != this.state.playerN) return; + if (this.state.playerN % 2 == toBeRespondent % 2) return; + this.setState({ + showMakeMoveModal: true, + toBeRespondent + }); + } + + makeMove(card, toBeRespondent) { + this.sendMessage({ + 'action': 'move', + 'payload': { + 'key': this.state.uuid, + 'respondent': toBeRespondent, + 'card': card + } + }) + this.setState({ + showMakeMoveModal: false + }); + } + register(payload) { const { uuid, player_n, n_players, time_limit } = payload; this.setState({ @@ -47,6 +86,7 @@ class App extends Component { respondent, interrogator }) + if (turn != this.state.playerN) this.hideMakeMoveModal(); } handleMessage(message) { @@ -70,6 +110,7 @@ class App extends Component { } sendMessage(payload) { + console.log('Sending: ' + JSON.stringify(payload)) this.state.sender.send(JSON.stringify(payload)); } @@ -89,7 +130,8 @@ class App extends Component { ); this.setState({ 'sender': sender - }) + }); + window.cards.playCard = (c) => { }; } render() { @@ -99,7 +141,8 @@ class App extends Component { nPlayers={this.state.nPlayers} playerN={this.state.playerN} nCards={this.state.nCards} - turn={this.state.turn} /> + turn={this.state.turn} + showModal={this.showMakeMoveModal.bind(this)} /> this.sendMessage({ 'action': 'switch_team' })} /> + switchTeam={() => this.sendMessage({ 'action': 'switch_team' })} + turn={this.state.turn} + playerN={this.state.playerN} /> + {this.state.showMakeMoveModal && } ); } diff --git a/src/App.test.js b/src/App.test.js index b84af98..62dd46c 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -2,7 +2,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +class MockReconnectingWebSocket { +} + it('renders without crashing', () => { const div = document.createElement('div'); + window.ReconnectingWebSocket = MockReconnectingWebSocket; + window.cards = {} ReactDOM.render(, div); }); diff --git a/src/components/Card.js b/src/components/Card.js index 7a09558..9bc0ece 100644 --- a/src/components/Card.js +++ b/src/components/Card.js @@ -110,6 +110,10 @@ export default class Card extends Component { } render() { - return + return { this.props.playCard(this.props.card) }} + aria-card-name={this.props.card} + src={this.cardMap[this.props.card]} /> } } diff --git a/src/components/MakeMoveModal.js b/src/components/MakeMoveModal.js new file mode 100644 index 0000000..cee919f --- /dev/null +++ b/src/components/MakeMoveModal.js @@ -0,0 +1,51 @@ +import React, { Component } from 'react'; +import VerticalCards from './VerticalCards'; + +export default class MakeMoveModal extends Component { + sets = [ + ['A', '2', '3', '4', '5', '6'], + ['8', '9', '10', 'J', 'Q', 'K'] + ] + suits = ['C', 'D', 'H', 'S'] + + constructor(props) { + super(props); + const hand = new Set(props.hand); + this.state = { + cards: [] + } + this.sets.forEach((set) => { + this.suits.forEach((s) => { + let canAskHalf = false; + set.forEach((r) => { + if (hand.has(r + s)) canAskHalf = true; + }); + if (!canAskHalf) return; + set.forEach((r) => { + if (!hand.has(r + s)) { + this.state.cards.push((r + s)) + } + }); + }); + }); + } + + render() { + return
+
+
+ +
+
+ } +} diff --git a/src/components/MoveDisplay.js b/src/components/MoveDisplay.js index d70295e..4de0d0c 100644 --- a/src/components/MoveDisplay.js +++ b/src/components/MoveDisplay.js @@ -2,15 +2,15 @@ import React, { Component } from 'react'; export default class MoveDisplay extends Component { render() { - if (!this.props.interrogator) { - return
+ if (this.props.interrogator === undefined) { + return
No move has been executed yet.
} const success = this.props.success && 'Success' || 'Failure'; - return
- {success}: Player {this.props.interrogator} requested - {this.props.card} from Player {this.props.respondent} + return
+ {success}: Player {this.props.interrogator} + {' ' + this.props.card} from Player {this.props.respondent}
} } diff --git a/src/components/Player.js b/src/components/Player.js index 8258bdb..d4272d8 100644 --- a/src/components/Player.js +++ b/src/components/Player.js @@ -1,13 +1,20 @@ import React, { Component } from 'react'; -import PlayerIcon from './player.png'; +import PlayerEvenIcon from './player-even.png'; +import PlayerOddIcon from './player-odd.png'; export default class Players extends Component { render() { + const icons = [PlayerEvenIcon, PlayerOddIcon]; let border = (this.props.turn == this.props.playerN) && { border: 'solid 3px white' } || {}; - return
- + return
this.props.showModal(this.props.playerN)}> +

Player {this.props.playerN} { this.props.nCards &&

{this.props.nCards} cards

diff --git a/src/components/Players.js b/src/components/Players.js index 9e7cdf4..9d1e68e 100644 --- a/src/components/Players.js +++ b/src/components/Players.js @@ -3,12 +3,13 @@ import Player from './Player'; export default class Players extends Component { render() { - return
+ return
{[ ...Array(this.props.nPlayers).keys() ].map((p) => { if (p != this.props.playerN) { return diff --git a/src/components/Timer.js b/src/components/Timer.js index 1da3b22..277f20a 100644 --- a/src/components/Timer.js +++ b/src/components/Timer.js @@ -1,5 +1,8 @@ import React, { Component } from 'react'; +import PlayerEvenIcon from './player-even.png'; +import PlayerOddIcon from './player-odd.png'; + export default class Timer extends Component { constructor(props) { super(props); @@ -32,8 +35,20 @@ export default class Timer extends Component { render() { const timeRemaining = this.timeRemaining() - return
- {timeRemaining} second{(timeRemaining != 1) && 's'} + const img = [PlayerEvenIcon, PlayerOddIcon][this.props.playerN % 2] + const icon = ( + + Player {this.props.playerN} + + ) + return
+ {timeRemaining} second{(timeRemaining != 1) && 's'} // + {(this.props.playerN != -1) + && this.props.playerN !== undefined + && icon + || ' Visitor'} + {(this.props.turn !== undefined && this.props.playerN == this.props.turn) + && '// Click a player from the opposite team to request a card'}
} } diff --git a/src/components/VerticalCards.js b/src/components/VerticalCards.js index 79cc366..1de04f9 100644 --- a/src/components/VerticalCards.js +++ b/src/components/VerticalCards.js @@ -2,6 +2,27 @@ import React, { Component } from 'react'; import Card from './Card'; export default class VerticalCards extends Component { + sortCard(a, b) { + const mapping = { + 'A': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + '10': 10, + 'J': 11, + 'Q': 12, + 'K': 13 + } + if (mapping[a] > mapping[b]) return 1; + else if (mapping[a] == mapping[b]) return 0; + return -1; + } + render() { const suited = { 'C': [], @@ -14,25 +35,33 @@ export default class VerticalCards extends Component { suited[suit].push(c); }); for (let s in suited) { - suited[s].sort(); + suited[s].sort(this.sortCard); } let suitClass = "hand vhand-compact" if (this.props.suitClass) { - suitClass += this.props.suitClass; + suitClass += ' ' + this.props.suitClass; } return ( -
-
- {suited['C'].map((c) => )} +
+
+ {suited['C'].map((c) => )}
-
- {suited['D'].map((c) => )} +
+ {suited['D'].map((c) => )}
-
- {suited['H'].map((c) => )} +
+ {suited['H'].map((c) => )}
-
- {suited['S'].map((c) => )} +
+ {suited['S'].map((c) => )}
) diff --git a/src/components/player.png b/src/components/player-even.png similarity index 100% rename from src/components/player.png rename to src/components/player-even.png diff --git a/src/components/player-odd.png b/src/components/player-odd.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e80c6c5e5798cef4735465f1a42d8c43f55ce9 GIT binary patch literal 16227 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 literal 0 HcmV?d00001