diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/src/api/model.js | 16 | ||||
-rw-r--r-- | frontend/src/components/game/GameScene.css | 13 | ||||
-rw-r--r-- | frontend/src/components/game/GameScene.jsx | 38 | ||||
-rw-r--r-- | frontend/src/components/game/Hand.css | 26 | ||||
-rw-r--r-- | frontend/src/components/game/Hand.jsx | 34 | ||||
-rw-r--r-- | frontend/src/models/currentGame.js | 3 | ||||
-rw-r--r-- | frontend/src/reducers.js | 4 | ||||
-rw-r--r-- | frontend/src/redux/currentGame.js | 55 |
8 files changed, 147 insertions, 42 deletions
diff --git a/frontend/src/api/model.js b/frontend/src/api/model.js index 3408d5a2..f5c3481b 100644 --- a/frontend/src/api/model.js +++ b/frontend/src/api/model.js @@ -47,9 +47,21 @@ export type ApiTable = { export type ApiAction = {}; -export type ApiHandCard = {}; +export type ApiCard = { + name: string, + image: string +}; + +export type ApiHandCard = ApiCard & { + playability: ApiPlayability +}; + +export type ApiPlayability = { + playable: boolean +}; -export type ApiTableCard = {}; +export type ApiTableCard = ApiCard & { +}; export type ApiCardBack = { image: string diff --git a/frontend/src/components/game/GameScene.css b/frontend/src/components/game/GameScene.css new file mode 100644 index 00000000..525e68fa --- /dev/null +++ b/frontend/src/components/game/GameScene.css @@ -0,0 +1,13 @@ +.gameSceneRoot { + background: url('background-papyrus.jpg') center no-repeat; + background-size: cover; +} + +.fullscreen { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow: auto; +} diff --git a/frontend/src/components/game/GameScene.jsx b/frontend/src/components/game/GameScene.jsx index fb5763db..cf3c91f8 100644 --- a/frontend/src/components/game/GameScene.jsx +++ b/frontend/src/components/game/GameScene.jsx @@ -2,7 +2,7 @@ import { Button, Classes, Intent } from '@blueprintjs/core'; import { List } from 'immutable'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -import type { ApiPlayerTurnInfo } from '../../api/model'; +import type { ApiHandCard, ApiPlayerTurnInfo } from '../../api/model'; import { Game } from '../../models/games'; import { Player } from '../../models/players'; import { actions } from '../../redux/actions/game'; @@ -10,7 +10,9 @@ import { getCurrentTurnInfo } from '../../redux/currentGame'; import { getCurrentGame } from '../../redux/games'; import { getCurrentPlayer, getPlayers } from '../../redux/players'; -import { PlayerList } from '../lobby/PlayerList'; +import { Hand } from './Hand'; + +import './GameScene.css' type GameSceneProps = { game: Game, @@ -20,21 +22,31 @@ type GameSceneProps = { sayReady: () => void } -class GameScenePresenter extends Component<GameSceneProps> { - getTitle() { - if (this.props.game) { - return this.props.game.name + ' — Game'; - } else { - return 'What are you doing here? You haven\'t joined a game yet!'; - } +type GameSceneState = { + selectedCard: ApiHandCard | void +} + +class GameScenePresenter extends Component<GameSceneProps, GameSceneState> { + + state = { + selectedCard: null + }; + + selectCard(c: ApiHandCard) { + this.setState({selectedCard: c}) } render() { return ( - <div> - <h2>{this.getTitle()}</h2> - <PlayerList players={this.props.players} currentPlayer={this.props.currentPlayer} owner={this.props.game.owner}/> - <Button text="READY" className={Classes.LARGE} intent={Intent.PRIMARY} icon='play' onClick={this.props.sayReady} /> + <div className='gameSceneRoot fullscreen'> + <h2>Now playing!</h2> + <p>{this.props.turnInfo ? this.props.turnInfo.message : 'Click "ready" when you are'}</p> + + {this.props.turnInfo && <Hand cards={this.props.turnInfo.hand} + selectedCard={this.state.selectedCard} + onClick={(c) => this.selectCard(c)}/>} + + {!this.props.turnInfo && <Button text="READY" className={Classes.LARGE} intent={Intent.PRIMARY} icon='play' onClick={this.props.sayReady} />} <h3>Turn Info</h3> <div> diff --git a/frontend/src/components/game/Hand.css b/frontend/src/components/game/Hand.css new file mode 100644 index 00000000..de7b556d --- /dev/null +++ b/frontend/src/components/game/Hand.css @@ -0,0 +1,26 @@ +.hand { + display: flex; +} + +.hand-card { + margin: 0.2rem; +} + +.hand-card-unplayable { + opacity: 0.7; +} + +.hand-card-img-selected { + border: 2px solid #0054ff; +} + +.hand-card-img { + border-radius: 0.5rem; + box-shadow: 0 0 10px black; + width: 11rem; +} + +.hand-card-img:hover { + box-shadow: 0 10px 40px black; + width: 14rem; +} diff --git a/frontend/src/components/game/Hand.jsx b/frontend/src/components/game/Hand.jsx new file mode 100644 index 00000000..7dea23de --- /dev/null +++ b/frontend/src/components/game/Hand.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import type { ApiHandCard } from '../../api/model'; +import './Hand.css' + +type HandCardProps = { + card: ApiHandCard, + isSelected: boolean, + onClick: () => void +} + +const HandCard = ({card, isSelected, onClick}: HandCardProps) => { + let playableClass = card.playability.playable ? '' : 'hand-card-unplayable'; + let selectedClass = isSelected ? 'hand-card-img-selected' : ''; + return <div className={`hand-card ${playableClass}`} + onClick={() => card.playability.playable && onClick()} + aria-disabled={!card.playability.playable}> + <img src={`/images/cards/${card.image}`} + title={card.name} + alt={'Card ' + card.name} + className={`hand-card-img ${selectedClass}`}/> + </div> +}; + +type HandProps = { + cards: ApiHandCard[], + selectedCard: ApiHandCard, + onClick: (card: ApiHandCard) => void +} + +export const Hand = ({cards, selectedCard, onClick}: HandProps) => { + return <div className='hand'>{cards.map((c, i) => <HandCard key={i} card={c} + isSelected={selectedCard === c} + onClick={() => onClick(c)}/>)}</div>; +} diff --git a/frontend/src/models/currentGame.js b/frontend/src/models/currentGame.js index fcabe875..6ff00858 100644 --- a/frontend/src/models/currentGame.js +++ b/frontend/src/models/currentGame.js @@ -1,6 +1,7 @@ +import { List } from 'immutable'; import type { ApiPlayerTurnInfo } from '../api/model'; export class CurrentGameState { - playersReadiness: Map<string, boolean> = new Map(); + readyUsernames: List<string> = new List(); turnInfo: ApiPlayerTurnInfo | null = null } diff --git a/frontend/src/reducers.js b/frontend/src/reducers.js index f5e7b18d..5e066d28 100644 --- a/frontend/src/reducers.js +++ b/frontend/src/reducers.js @@ -1,13 +1,13 @@ // @flow import { routerReducer } from 'react-router-redux'; import { combineReducers } from 'redux-immutable'; -import { currentGameReducer } from './redux/currentGame'; +import { createCurrentGameReducer } from './redux/currentGame'; import { gamesReducer } from './redux/games'; import { playersReducer } from './redux/players'; export function createReducer() { return combineReducers({ - currentGame: currentGameReducer, + currentGame: createCurrentGameReducer(), games: gamesReducer, players: playersReducer, routing: routerReducer, diff --git a/frontend/src/redux/currentGame.js b/frontend/src/redux/currentGame.js index cefabb6f..f4174eca 100644 --- a/frontend/src/redux/currentGame.js +++ b/frontend/src/redux/currentGame.js @@ -1,32 +1,39 @@ // @flow -import type { ApiPlayerTurnInfo } from '../api/model'; +import { List } from 'immutable'; +import { combineReducers } from 'redux'; +import type { ApiPlayerTurnInfo, ApiTable } from '../api/model'; import { CurrentGameState } from '../models/currentGame'; import type { Action } from './actions/all'; import { types } from './actions/game'; -export const currentGameReducer = (state: CurrentGameState = new CurrentGameState(), action: Action) => { - switch (action.type) { - case types.REQUEST_SAY_READY: - // TODO handle end of feedback between say ready and ready event received - return state; - case types.PLAYER_READY_RECEIVED: - // const newReadiness = state.playersReadiness.set(action.username, true); - // return { playersReadiness: newReadiness, ...state }; - return state; - case types.TABLE_UPDATE_RECEIVED: - // TODO - return state; - case types.PREPARED_CARD_RECEIVED: - // TODO - return state; - case types.TURN_INFO_RECEIVED: - // TODO find a better way to just update what's needed - const newState = new CurrentGameState(); - newState.turnInfo = action.turnInfo; - newState.playersReadiness = state.playersReadiness; - return newState; - default: - return state; +export function createCurrentGameReducer() { + return combineReducers({ + readyUsernames: readyUsernamesReducer, + turnInfo: turnInfoReducer, + }); +} + +const readyUsernamesReducer = (state: List<string> = new List(), action: Action) => { + if (action.type === types.PLAYER_READY_RECEIVED) { + return state.push(action.username); + } else { + return state; + } +}; + +const turnInfoReducer = (state: ApiPlayerTurnInfo | null = null, action: Action) => { + if (action.type === types.TURN_INFO_RECEIVED) { + return action.turnInfo; + } else { + return state; + } +}; + +const tableUpdatesReducer = (state: ApiTable, action: Action) => { + if (action.type === types.TABLE_UPDATE_RECEIVED) { + return action.table; + } else { + return state; } }; |