summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt16
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt6
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt6
-rw-r--r--frontend/src/api/model.js20
-rw-r--r--frontend/src/api/sevenWondersApi.js2
-rw-r--r--frontend/src/components/Application.jsx2
-rw-r--r--frontend/src/components/game-browser/GameBrowser.jsx16
-rw-r--r--frontend/src/components/game-browser/GameList.jsx3
-rw-r--r--frontend/src/components/game/GameScene.jsx64
-rw-r--r--frontend/src/components/home/ChooseNameForm.jsx2
-rw-r--r--frontend/src/components/lobby/Lobby.jsx3
-rw-r--r--frontend/src/models/currentGame.js6
-rw-r--r--frontend/src/models/games.js6
-rw-r--r--frontend/src/reducers.js2
-rw-r--r--frontend/src/redux/actions/all.js5
-rw-r--r--frontend/src/redux/actions/game.js25
-rw-r--r--frontend/src/redux/actions/lobby.js35
-rw-r--r--frontend/src/redux/actions/players.js29
-rw-r--r--frontend/src/redux/currentGame.js33
-rw-r--r--frontend/src/redux/games.js42
-rw-r--r--frontend/src/redux/players.js35
-rw-r--r--frontend/src/sagas.js2
-rw-r--r--frontend/src/sagas/game.js74
-rw-r--r--frontend/src/sagas/gameBrowser.js7
-rw-r--r--frontend/src/sagas/home.js3
-rw-r--r--frontend/src/sagas/lobby.js33
26 files changed, 357 insertions, 120 deletions
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt
index e05bf319..0cee6531 100644
--- a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt
@@ -2,10 +2,12 @@ package org.luxons.sevenwonders.controllers
import org.hildan.livedoc.core.annotations.Api
import org.luxons.sevenwonders.actions.PrepareMoveAction
+import org.luxons.sevenwonders.api.PlayerDTO
+import org.luxons.sevenwonders.api.toDTO
import org.luxons.sevenwonders.game.Game
import org.luxons.sevenwonders.game.api.Table
+import org.luxons.sevenwonders.game.cards.CardBack
import org.luxons.sevenwonders.lobby.Player
-import org.luxons.sevenwonders.output.PreparedCard
import org.luxons.sevenwonders.repositories.PlayerRepository
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
@@ -42,13 +44,13 @@ class GameController @Autowired constructor(
val lobby = player.lobby
val players = lobby.getPlayers()
- val allReady = players.stream().allMatch { it.isReady }
+ sendPlayerReady(game.id, player)
+
+ val allReady = players.all { it.isReady }
if (allReady) {
logger.info("Game {}: all players ready, sending turn info", game.id)
players.forEach { it.isReady = false }
sendTurnInfo(players, game)
- } else {
- sendPlayerReady(game.id, player)
}
}
@@ -60,7 +62,7 @@ class GameController @Autowired constructor(
}
private fun sendPlayerReady(gameId: Long, player: Player) =
- template.convertAndSend("/topic/game/$gameId/playerReady", player.username)
+ template.convertAndSend("/topic/game/$gameId/playerReady", "\"${player.username}\"")
/**
* Prepares the player's next move. When all players have prepared their moves, all moves are executed.
@@ -75,7 +77,7 @@ class GameController @Autowired constructor(
val player = principal.player
val game = player.game
val preparedCardBack = game.prepareMove(player.index, action.move)
- val preparedCard = PreparedCard(player, preparedCardBack)
+ val preparedCard = PreparedCard(player.toDTO(principal.name), preparedCardBack)
logger.info("Game {}: player {} prepared move {}", game.id, principal.name, action.move)
if (game.allPlayersPreparedTheirMove()) {
@@ -97,3 +99,5 @@ class GameController @Autowired constructor(
private val logger = LoggerFactory.getLogger(GameController::class.java)
}
}
+
+class PreparedCard(val player: PlayerDTO, val cardBack: CardBack)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt
index a3ccd148..bd672000 100644
--- a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt
@@ -25,10 +25,8 @@ class HomeController @Autowired constructor(
/**
* Creates/updates the player's name (for the user's session).
*
- * @param action
- * the action to choose the name of the player
- * @param principal
- * the connected user's information
+ * @param action the action to choose the name of the player
+ * @param principal the connected user's information
*
* @return the created [PlayerDTO] object
*/
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt
deleted file mode 100644
index 956b1a2c..00000000
--- a/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.luxons.sevenwonders.output
-
-import org.luxons.sevenwonders.game.cards.CardBack
-import org.luxons.sevenwonders.lobby.Player
-
-class PreparedCard(val player: Player, val cardBack: CardBack)
diff --git a/frontend/src/api/model.js b/frontend/src/api/model.js
index 5d1f92c6..3408d5a2 100644
--- a/frontend/src/api/model.js
+++ b/frontend/src/api/model.js
@@ -37,18 +37,28 @@ export type ApiPlayer = {
username: string,
displayName: string,
index: number,
- ready: boolean
+ gameOwner: Boolean,
+ user: Boolean,
};
-export type ApiTable = {};
+export type ApiTable = {
+
+};
export type ApiAction = {};
export type ApiHandCard = {};
-export type ApiCard = {};
+export type ApiTableCard = {};
-export type ApiPreparedCard = {};
+export type ApiCardBack = {
+ image: string
+};
+
+export type ApiPreparedCard = {
+ player: ApiPlayer,
+ cardBack: ApiCardBack
+};
export type ApiPlayerTurnInfo = {
playerIndex: number,
@@ -56,7 +66,7 @@ export type ApiPlayerTurnInfo = {
currentAge: number,
action: ApiAction,
hand: ApiHandCard[],
- neighbourGuildCards: ApiCard[],
+ neighbourGuildCards: ApiTableCard[],
message: string
};
diff --git a/frontend/src/api/sevenWondersApi.js b/frontend/src/api/sevenWondersApi.js
index b5a7df7e..8c4aedaf 100644
--- a/frontend/src/api/sevenWondersApi.js
+++ b/frontend/src/api/sevenWondersApi.js
@@ -81,7 +81,7 @@ export class SevenWondersSession {
return this.client.subscriber(`/topic/game/${currentGameId}/tableUpdates`);
}
- watchPreparedMove(currentGameId: number): SubscribeFn<ApiPreparedCard> {
+ watchPreparedCards(currentGameId: number): SubscribeFn<ApiPreparedCard> {
return this.client.subscriber(`/topic/game/${currentGameId}/prepared`);
}
diff --git a/frontend/src/components/Application.jsx b/frontend/src/components/Application.jsx
index d7e1738c..e0ec604d 100644
--- a/frontend/src/components/Application.jsx
+++ b/frontend/src/components/Application.jsx
@@ -1,11 +1,13 @@
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { GameBrowser } from './game-browser/GameBrowser';
+import { GameScene } from './game/GameScene';
import { Lobby } from './lobby/Lobby';
import { Home } from './home/Home';
export const Application = () => (
<Switch>
+ <Route path="/game" component={GameScene} />
<Route path="/games" component={GameBrowser} />
<Route path="/lobby" component={Lobby} />
<Route path="/" component={Home} />
diff --git a/frontend/src/components/game-browser/GameBrowser.jsx b/frontend/src/components/game-browser/GameBrowser.jsx
index 10d823b4..cfa0e45e 100644
--- a/frontend/src/components/game-browser/GameBrowser.jsx
+++ b/frontend/src/components/game-browser/GameBrowser.jsx
@@ -3,9 +3,9 @@ import { Button, Classes, InputGroup, Intent } from '@blueprintjs/core';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Flex } from 'reflexbox';
+import { actions } from '../../redux/actions/lobby';
import { GameList } from './GameList';
import { PlayerInfo } from './PlayerInfo';
-import { actions } from '../../redux/games';
type GameBrowserProps = {
createGame: (gameName: string) => void,
@@ -26,12 +26,14 @@ class GameBrowserPresenter extends Component<GameBrowserProps> {
return (
<div>
<Flex align="center" justify='space-between' p={1}>
- <InputGroup
- placeholder="Game name"
- name="game_name"
- onChange={(e: SyntheticInputEvent<*>) => (this._gameName = e.target.value)}
- rightElement={<CreateGameButton onClick={this.createGame}/>}
- />
+ <form onSubmit={this.createGame}>
+ <InputGroup
+ placeholder="Game name"
+ name="game_name"
+ onChange={(e: SyntheticInputEvent<*>) => (this._gameName = e.target.value)}
+ rightElement={<CreateGameButton onClick={this.createGame}/>}
+ />
+ </form>
<PlayerInfo />
</Flex>
<GameList />
diff --git a/frontend/src/components/game-browser/GameList.jsx b/frontend/src/components/game-browser/GameList.jsx
index 6363cff2..64ced9b0 100644
--- a/frontend/src/components/game-browser/GameList.jsx
+++ b/frontend/src/components/game-browser/GameList.jsx
@@ -3,7 +3,8 @@ import type { List } from 'immutable';
import React from 'react';
import { connect } from 'react-redux';
import type { Game } from '../../models/games';
-import { actions, getAllGames } from '../../redux/games';
+import { actions } from '../../redux/actions/lobby';
+import { getAllGames } from '../../redux/games';
import { IconButton } from '../shared/IconButton';
import './GameList.css';
import { GameStatus } from './GameStatus';
diff --git a/frontend/src/components/game/GameScene.jsx b/frontend/src/components/game/GameScene.jsx
new file mode 100644
index 00000000..fb5763db
--- /dev/null
+++ b/frontend/src/components/game/GameScene.jsx
@@ -0,0 +1,64 @@
+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 { Game } from '../../models/games';
+import { Player } from '../../models/players';
+import { actions } from '../../redux/actions/game';
+import { getCurrentTurnInfo } from '../../redux/currentGame';
+import { getCurrentGame } from '../../redux/games';
+
+import { getCurrentPlayer, getPlayers } from '../../redux/players';
+import { PlayerList } from '../lobby/PlayerList';
+
+type GameSceneProps = {
+ game: Game,
+ currentPlayer: Player,
+ players: List<Player>,
+ turnInfo: ApiPlayerTurnInfo,
+ 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!';
+ }
+ }
+
+ 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} />
+
+ <h3>Turn Info</h3>
+ <div>
+ <pre>{JSON.stringify(this.props.turnInfo, null, 2) }</pre>
+ </div>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps: (state) => GameSceneProps = state => {
+ const game = getCurrentGame(state.get('games'));
+ console.info(game);
+
+ return {
+ game: game,
+ currentPlayer: getCurrentPlayer(state),
+ players: game ? getPlayers(state.get('players'), game.players) : new List(),
+ turnInfo: getCurrentTurnInfo(state.get('currentGame'))
+ };
+};
+
+const mapDispatchToProps = {
+ sayReady: actions.sayReady,
+};
+
+export const GameScene = connect(mapStateToProps, mapDispatchToProps)(GameScenePresenter);
diff --git a/frontend/src/components/home/ChooseNameForm.jsx b/frontend/src/components/home/ChooseNameForm.jsx
index 614ad172..619d967c 100644
--- a/frontend/src/components/home/ChooseNameForm.jsx
+++ b/frontend/src/components/home/ChooseNameForm.jsx
@@ -2,7 +2,7 @@
import { Classes, InputGroup, Intent } from '@blueprintjs/core';
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import { actions } from '../../redux/players';
+import { actions } from '../../redux/actions/players';
import { IconButton } from '../shared/IconButton';
type ChooseNameFormPresenterProps = {
diff --git a/frontend/src/components/lobby/Lobby.jsx b/frontend/src/components/lobby/Lobby.jsx
index 82c00b20..df6557af 100644
--- a/frontend/src/components/lobby/Lobby.jsx
+++ b/frontend/src/components/lobby/Lobby.jsx
@@ -5,7 +5,8 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Game } from '../../models/games';
import type { Player } from '../../models/players';
-import { actions, getCurrentGame } from '../../redux/games';
+import { actions } from '../../redux/actions/lobby';
+import { getCurrentGame } from '../../redux/games';
import { getCurrentPlayer, getPlayers } from '../../redux/players';
import { RadialPlayerList } from './RadialPlayerList';
diff --git a/frontend/src/models/currentGame.js b/frontend/src/models/currentGame.js
new file mode 100644
index 00000000..fcabe875
--- /dev/null
+++ b/frontend/src/models/currentGame.js
@@ -0,0 +1,6 @@
+import type { ApiPlayerTurnInfo } from '../api/model';
+
+export class CurrentGameState {
+ playersReadiness: Map<string, boolean> = new Map();
+ turnInfo: ApiPlayerTurnInfo | null = null
+}
diff --git a/frontend/src/models/games.js b/frontend/src/models/games.js
index 7a8dfbc7..85aab2df 100644
--- a/frontend/src/models/games.js
+++ b/frontend/src/models/games.js
@@ -15,6 +15,7 @@ export type SettingsShape = {
wonderSidePickMethod: "EACH_RANDOM" | "TODO",
pointsPer3Gold: number
};
+
export type SettingsType = Record<SettingsShape>;
const SettingsRecord: SettingsType = Record({
@@ -32,6 +33,7 @@ const SettingsRecord: SettingsType = Record({
wonderSidePickMethod: 'EACH_RANDOM',
pointsPer3Gold: 1,
});
+
export class Settings extends SettingsRecord {}
export type GameState = 'LOBBY' | 'PLAYING';
@@ -43,6 +45,7 @@ export type GameShape = {
settings: SettingsType,
state: GameState,
};
+
export type GameType = Record<GameShape>;
export type GameMapType = Map<string, GameShape>;
export type GameNormalMapType = { [string]: GameShape };
@@ -55,18 +58,21 @@ const GameRecord: GameType = Record({
settings: new Settings(),
state: 'LOBBY',
});
+
export class Game extends GameRecord {}
export type GamesShape = {
all: Map<Game>,
current: string
};
+
export type GamesType = Record<GamesShape>;
const GamesRecord: GamesType = Record({
all: new Map(),
current: null,
});
+
export class GamesState extends GamesRecord {
addGame(g: GameShape) {
const game: Game = new Game(g);
diff --git a/frontend/src/reducers.js b/frontend/src/reducers.js
index 85f7e3c1..f5e7b18d 100644
--- a/frontend/src/reducers.js
+++ b/frontend/src/reducers.js
@@ -1,11 +1,13 @@
// @flow
import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux-immutable';
+import { currentGameReducer } from './redux/currentGame';
import { gamesReducer } from './redux/games';
import { playersReducer } from './redux/players';
export function createReducer() {
return combineReducers({
+ currentGame: currentGameReducer,
games: gamesReducer,
players: playersReducer,
routing: routerReducer,
diff --git a/frontend/src/redux/actions/all.js b/frontend/src/redux/actions/all.js
new file mode 100644
index 00000000..45d3ab7a
--- /dev/null
+++ b/frontend/src/redux/actions/all.js
@@ -0,0 +1,5 @@
+import type { GameAction } from './game';
+import type { LobbyAction } from './lobby';
+import type { PlayerAction } from './players';
+
+export type Action = PlayerAction | LobbyAction | GameAction
diff --git a/frontend/src/redux/actions/game.js b/frontend/src/redux/actions/game.js
new file mode 100644
index 00000000..1fe49dfb
--- /dev/null
+++ b/frontend/src/redux/actions/game.js
@@ -0,0 +1,25 @@
+import type { ApiPlayerTurnInfo, ApiPreparedCard, ApiTable } from '../../api/model';
+
+export const types = {
+ REQUEST_SAY_READY: 'GAME/REQUEST_SAY_READY',
+ PLAYER_READY_RECEIVED: 'GAME/PLAYER_READY_RECEIVED',
+ TABLE_UPDATE_RECEIVED: 'GAME/TABLE_UPDATE_RECEIVED',
+ PREPARED_CARD_RECEIVED: 'GAME/PREPARED_CARD_RECEIVED',
+ TURN_INFO_RECEIVED: 'GAME/TURN_INFO_RECEIVED',
+};
+
+export type SayReadyAction = { type: 'GAME/REQUEST_SAY_READY' };
+export type PlayerReadyEvent = { type: 'GAME/PLAYER_READY_RECEIVED', username: string };
+export type TableUpdateEvent = { type: 'GAME/TABLE_UPDATE_RECEIVED', table: ApiTable };
+export type PreparedCardEvent = { type: 'GAME/PREPARED_CARD_RECEIVED', card: ApiPreparedCard };
+export type TurnInfoEvent = { type: 'GAME/TURN_INFO_RECEIVED', turnInfo: ApiPlayerTurnInfo };
+
+export type GameAction = SayReadyAction | PlayerReadyEvent | TableUpdateEvent | PreparedCardEvent | TurnInfoEvent;
+
+export const actions = {
+ sayReady: () => ({ type: types.REQUEST_SAY_READY }),
+ receivePlayerReady: (username: string) => ({ type: types.PLAYER_READY_RECEIVED, username }),
+ receiveTableUpdate: (table: ApiTable) => ({ type: types.TABLE_UPDATE_RECEIVED, table }),
+ receivePreparedCard: (card: ApiPreparedCard) => ({ type: types.PREPARED_CARD_RECEIVED, card }),
+ receiveTurnInfo: (turnInfo: ApiPlayerTurnInfo) => ({ type: types.TURN_INFO_RECEIVED, turnInfo }),
+};
diff --git a/frontend/src/redux/actions/lobby.js b/frontend/src/redux/actions/lobby.js
new file mode 100644
index 00000000..b3151a23
--- /dev/null
+++ b/frontend/src/redux/actions/lobby.js
@@ -0,0 +1,35 @@
+import { fromJS } from 'immutable';
+import type { GameMapType, GameNormalMapType } from '../../models/games';
+
+export const types = {
+ UPDATE_GAMES: 'GAMES/UPDATE_GAMES',
+ REQUEST_CREATE_GAME: 'GAMES/REQUEST_CREATE_GAME',
+ REQUEST_JOIN_GAME: 'GAMES/REQUEST_JOIN_GAME',
+ REQUEST_START_GAME: 'GAMES/REQUEST_START_GAME',
+ ENTER_LOBBY: 'GAMES/ENTER_LOBBY',
+ ENTER_GAME: 'GAMES/ENTER_GAME',
+};
+
+export type UpdateGamesAction = { type: 'GAMES/UPDATE_GAMES', games: GameMapType };
+export type RequestCreateGameAction = { type: 'GAMES/REQUEST_CREATE_GAME', gameName: string };
+export type RequestJoinGameAction = { type: 'GAMES/REQUEST_JOIN_GAME', gameId: number };
+export type RequestStartGameAction = { type: 'GAMES/REQUEST_START_GAME' };
+export type EnterLobbyAction = { type: 'GAMES/ENTER_LOBBY', gameId: number };
+export type EnterGameAction = { type: 'GAMES/ENTER_GAME', gameId: number };
+
+export type LobbyAction =
+ | UpdateGamesAction
+ | RequestCreateGameAction
+ | RequestJoinGameAction
+ | RequestStartGameAction
+ | EnterLobbyAction
+ | EnterGameAction;
+
+export const actions = {
+ updateGames: (games: GameNormalMapType): UpdateGamesAction => ({ type: types.UPDATE_GAMES, games: fromJS(games) }),
+ requestJoinGame: (gameId: number): RequestJoinGameAction => ({ type: types.REQUEST_JOIN_GAME, gameId }),
+ requestCreateGame: (gameName: string): RequestCreateGameAction => ({ type: types.REQUEST_CREATE_GAME, gameName }),
+ requestStartGame: (): RequestStartGameAction => ({ type: types.REQUEST_START_GAME }),
+ enterLobby: (gameId: number): EnterLobbyAction => ({ type: types.ENTER_LOBBY, gameId }),
+ enterGame: (gameId: number): EnterGameAction => ({ type: types.ENTER_GAME, gameId }),
+};
diff --git a/frontend/src/redux/actions/players.js b/frontend/src/redux/actions/players.js
new file mode 100644
index 00000000..7df174c4
--- /dev/null
+++ b/frontend/src/redux/actions/players.js
@@ -0,0 +1,29 @@
+import { Map } from 'immutable';
+import { PlayerShape } from '../../models/players';
+
+export const types = {
+ REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME',
+ SET_CURRENT_PLAYER: 'USER/SET_CURRENT_PLAYER',
+ UPDATE_PLAYERS: 'USER/UPDATE_PLAYERS',
+};
+
+export type RequestChooseUsernameAction = { type: types.REQUEST_CHOOSE_USERNAME, username: string };
+export type SetCurrentPlayerAction = { type: types.SET_CURRENT_PLAYER, player: PlayerShape };
+export type UpdatePlayersAction = { type: types.UPDATE_PLAYERS, players: Map<string, PlayerShape> };
+
+export type PlayerAction = RequestChooseUsernameAction | SetCurrentPlayerAction | UpdatePlayersAction;
+
+export const actions = {
+ chooseUsername: (username: string): RequestChooseUsernameAction => ({
+ type: types.REQUEST_CHOOSE_USERNAME,
+ username,
+ }),
+ setCurrentPlayer: (player: PlayerShape): SetCurrentPlayerAction => ({
+ type: types.SET_CURRENT_PLAYER,
+ player,
+ }),
+ updatePlayers: (players: Map<string, PlayerShape>): UpdatePlayersAction => ({
+ type: types.UPDATE_PLAYERS,
+ players,
+ }),
+};
diff --git a/frontend/src/redux/currentGame.js b/frontend/src/redux/currentGame.js
new file mode 100644
index 00000000..cefabb6f
--- /dev/null
+++ b/frontend/src/redux/currentGame.js
@@ -0,0 +1,33 @@
+// @flow
+import type { ApiPlayerTurnInfo } 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 const getCurrentTurnInfo = (state: CurrentGameState): ApiPlayerTurnInfo => state.turnInfo;
diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js
index ee8b13df..68571981 100644
--- a/frontend/src/redux/games.js
+++ b/frontend/src/redux/games.js
@@ -1,48 +1,16 @@
// @flow
import type { List, Map } from 'immutable';
-import { fromJS } from 'immutable';
-import type { Game, GameMapType, GameNormalMapType, GameShape } from '../models/games';
+import type { Game } from '../models/games';
import { GamesState } from '../models/games';
+import type { Action } from './actions/all';
+import { types } from './actions/lobby';
-export const types = {
- UPDATE_GAMES: 'GAMES/UPDATE_GAMES',
- REQUEST_CREATE_GAME: 'GAMES/REQUEST_CREATE_GAME',
- REQUEST_JOIN_GAME: 'GAMES/REQUEST_JOIN_GAME',
- REQUEST_START_GAME: 'GAMES/REQUEST_START_GAME',
- ENTER_LOBBY: 'GAMES/ENTER_LOBBY',
- ENTER_GAME: 'GAMES/ENTER_GAME',
-};
-
-export type UpdateGamesAction = { type: 'GAMES/UPDATE_GAMES', games: GameMapType };
-export type RequestCreateGameAction = { type: 'GAMES/REQUEST_CREATE_GAME', gameName: string };
-export type RequestJoinGameAction = { type: 'GAMES/REQUEST_JOIN_GAME', gameId: string };
-export type RequestStartGameAction = { type: 'GAMES/REQUEST_START_GAME' };
-export type EnterLobbyAction = { type: 'GAMES/ENTER_LOBBY', lobby: GameShape };
-export type EnterGameAction = { type: 'GAMES/ENTER_GAME' };
-
-export type GamesAction =
- | UpdateGamesAction
- | RequestCreateGameAction
- | RequestJoinGameAction
- | RequestStartGameAction
- | EnterLobbyAction
- | EnterGameAction;
-
-export const actions = {
- updateGames: (games: GameNormalMapType): UpdateGamesAction => ({ type: types.UPDATE_GAMES, games: fromJS(games) }),
- requestJoinGame: (gameId: string): RequestJoinGameAction => ({ type: types.REQUEST_JOIN_GAME, gameId }),
- requestCreateGame: (gameName: string): RequestCreateGameAction => ({ type: types.REQUEST_CREATE_GAME, gameName }),
- requestStartGame: (): RequestStartGameAction => ({ type: types.REQUEST_START_GAME }),
- enterLobby: (lobby: GameShape): EnterLobbyAction => ({ type: types.ENTER_LOBBY, lobby: fromJS(lobby) }),
- enterGame: (): EnterGameAction => ({ type: types.ENTER_GAME }),
-};
-
-export const gamesReducer = (state: GamesState = new GamesState(), action: GamesAction) => {
+export const gamesReducer = (state: GamesState = new GamesState(), action: Action) => {
switch (action.type) {
case types.UPDATE_GAMES:
return state.addGames(action.games);
case types.ENTER_LOBBY:
- return state.set('current', action.lobby.get('id'));
+ return state.set('current', action.gameId);
default:
return state;
}
diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js
index b9f37c8c..ce3c305f 100644
--- a/frontend/src/redux/players.js
+++ b/frontend/src/redux/players.js
@@ -1,34 +1,9 @@
-import { Map } from 'immutable';
-import { Player, PlayerShape, PlayerState } from '../models/players';
+import { List } from 'immutable';
+import { Player, PlayerState } from '../models/players';
+import type { Action } from './actions/all';
+import { types } from './actions/players';
-export const types = {
- REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME',
- SET_CURRENT_PLAYER: 'USER/SET_CURRENT_PLAYER',
- UPDATE_PLAYERS: 'USER/UPDATE_PLAYERS',
-};
-
-export type RequestChooseUsernameAction = { type: types.REQUEST_CHOOSE_USERNAME, username: string };
-export type SetCurrentPlayerAction = { type: types.SET_CURRENT_PLAYER, player: PlayerShape };
-export type UpdatePlayersAction = { type: types.UPDATE_PLAYERS, players: Map<string, PlayerShape> };
-
-export type PlayerAction = RequestChooseUsernameAction | SetCurrentPlayerAction | UpdatePlayersAction;
-
-export const actions = {
- chooseUsername: (username: string): RequestChooseUsernameAction => ({
- type: types.REQUEST_CHOOSE_USERNAME,
- username,
- }),
- setCurrentPlayer: (player: PlayerShape): SetCurrentPlayerAction => ({
- type: types.SET_CURRENT_PLAYER,
- player,
- }),
- updatePlayers: (players: Map<string, PlayerShape>): UpdatePlayersAction => ({
- type: types.UPDATE_PLAYERS,
- players,
- }),
-};
-
-export const playersReducer = (state = new PlayerState(), action: PlayerAction) => {
+export const playersReducer = (state = new PlayerState(), action: Action) => {
switch (action.type) {
case types.SET_CURRENT_PLAYER:
return state.addPlayer(action.player);
diff --git a/frontend/src/sagas.js b/frontend/src/sagas.js
index bbc9a6b6..1aa72215 100644
--- a/frontend/src/sagas.js
+++ b/frontend/src/sagas.js
@@ -3,6 +3,7 @@ import type { SagaIterator } from 'redux-saga';
import { call, fork } from 'redux-saga/effects';
import { connectToGame, SevenWondersSession } from './api/sevenWondersApi';
import { errorHandlingSaga } from './sagas/errors';
+import { gameSaga } from './sagas/game';
import { gameBrowserSaga } from './sagas/gameBrowser';
import { homeSaga } from './sagas/home';
import { lobbySaga } from './sagas/lobby';
@@ -19,4 +20,5 @@ export function* rootSaga(): SagaIterator {
yield fork(homeSaga, sevenWondersSession);
yield fork(gameBrowserSaga, sevenWondersSession);
yield fork(lobbySaga, sevenWondersSession);
+ yield fork(gameSaga, sevenWondersSession);
}
diff --git a/frontend/src/sagas/game.js b/frontend/src/sagas/game.js
new file mode 100644
index 00000000..04209e91
--- /dev/null
+++ b/frontend/src/sagas/game.js
@@ -0,0 +1,74 @@
+import { eventChannel } from 'redux-saga';
+import { apply, call, put, take } from 'redux-saga/effects';
+import type { ApiPlayerTurnInfo, ApiPreparedCard, ApiTable } from '../api/model';
+import { SevenWondersSession } from '../api/sevenWondersApi';
+import { actions } from '../redux/actions/game';
+import { types } from '../redux/actions/game';
+import { types as gameTypes } from '../redux/actions/lobby';
+
+function* watchPlayerReady(session: SevenWondersSession, gameId: number) {
+ const channel = yield eventChannel(session.watchPlayerReady(gameId));
+ try {
+ while (true) {
+ const username = yield take(channel);
+ yield put(actions.receivePlayerReady(username));
+ }
+ } finally {
+ yield apply(channel, channel.close);
+ }
+}
+
+function* watchTableUpdates(session: SevenWondersSession, gameId: number) {
+ const channel = yield eventChannel(session.watchTableUpdates(gameId));
+ try {
+ while (true) {
+ const table: ApiTable = yield take(channel);
+ yield put(actions.receiveTableUpdate(table));
+ }
+ } finally {
+ yield apply(channel, channel.close);
+ }
+}
+
+function* watchPreparedCards(session: SevenWondersSession, gameId: number) {
+ const channel = yield eventChannel(session.watchPreparedCards(gameId));
+ try {
+ while (true) {
+ const preparedCard: ApiPreparedCard = yield take(channel);
+ yield put(actions.receivePreparedCard(preparedCard));
+ }
+ } finally {
+ yield apply(channel, channel.close);
+ }
+}
+
+function* sayReady(session: SevenWondersSession) {
+ while (true) {
+ yield take(types.REQUEST_SAY_READY);
+ yield apply(session, session.sayReady);
+ }
+}
+
+function* watchTurnInfo(session: SevenWondersSession) {
+ const channel = yield eventChannel(session.watchTurnInfo());
+ try {
+ while (true) {
+ const turnInfo: ApiPlayerTurnInfo = yield take(channel);
+ yield put(actions.receiveTurnInfo(turnInfo));
+ }
+ } finally {
+ yield apply(channel, channel.close);
+ }
+}
+
+export function* gameSaga(session: SevenWondersSession) {
+ const { gameId } = yield take(gameTypes.ENTER_GAME);
+ console.log('Entered game!', gameId);
+ yield [
+ call(watchPlayerReady, session, gameId),
+ call(watchTableUpdates, session, gameId),
+ call(watchPreparedCards, session, gameId),
+ call(sayReady, session),
+ call(watchTurnInfo, session)
+ ];
+}
diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js
index 7cd45667..062603a3 100644
--- a/frontend/src/sagas/gameBrowser.js
+++ b/frontend/src/sagas/gameBrowser.js
@@ -5,8 +5,9 @@ import type { SagaIterator } from 'redux-saga';
import { eventChannel } from 'redux-saga';
import { all, apply, call, put, take } from 'redux-saga/effects';
import type { SevenWondersSession } from '../api/sevenWondersApi';
-import { actions as gameActions, types } from '../redux/games';
-import { actions as playerActions } from '../redux/players';
+import { actions as gameActions } from '../redux/actions/lobby';
+import { types } from '../redux/actions/lobby';
+import { actions as playerActions } from '../redux/actions/players';
import { game as gameSchema, gameList as gameListSchema } from '../schemas/games';
function* watchGames(session: SevenWondersSession): SagaIterator {
@@ -32,7 +33,7 @@ function* watchLobbyJoined(session: SevenWondersSession): SagaIterator {
const gameId = normalized.result;
yield put(playerActions.updatePlayers(normalized.entities.players));
yield put(gameActions.updateGames(normalized.entities.games));
- yield put(gameActions.enterLobby(normalized.entities.games[gameId]));
+ yield put(gameActions.enterLobby(gameId));
yield put(push(`/lobby/${gameId}`));
} finally {
yield apply(joinedLobbyChannel, joinedLobbyChannel.close);
diff --git a/frontend/src/sagas/home.js b/frontend/src/sagas/home.js
index 328102fb..705a7a40 100644
--- a/frontend/src/sagas/home.js
+++ b/frontend/src/sagas/home.js
@@ -5,7 +5,8 @@ import { eventChannel } from 'redux-saga';
import { all, apply, call, put, take } from 'redux-saga/effects';
import type { ApiPlayer } from '../api/model';
import type { SevenWondersSession } from '../api/sevenWondersApi';
-import { actions, types } from '../redux/players';
+import { actions } from '../redux/actions/players';
+import { types } from '../redux/actions/players';
function* sendUsername(session: SevenWondersSession): SagaIterator {
while (true) {
diff --git a/frontend/src/sagas/lobby.js b/frontend/src/sagas/lobby.js
index c87f6ad5..b0f52d5c 100644
--- a/frontend/src/sagas/lobby.js
+++ b/frontend/src/sagas/lobby.js
@@ -5,37 +5,31 @@ import type { Channel, SagaIterator } from 'redux-saga';
import { eventChannel } from 'redux-saga';
import { all, apply, call, put, take } from 'redux-saga/effects';
import { SevenWondersSession } from '../api/sevenWondersApi';
-import { actions as gameActions, types } from '../redux/games';
-import { actions as playerActions } from '../redux/players';
+import { actions as gameActions, types } from '../redux/actions/lobby';
+import { actions as playerActions } from '../redux/actions/players';
import { game as gameSchema } from '../schemas/games';
-function getCurrentGameId(): number {
- const path = window.location.pathname;
- return path.split('lobby/')[1];
-}
-
-function* watchLobbyUpdates(session: SevenWondersSession): SagaIterator {
- const currentGameId: number = getCurrentGameId();
- const lobbyUpdatesChannel: Channel = yield eventChannel(session.watchLobbyUpdated(currentGameId));
+function* watchLobbyUpdates(session: SevenWondersSession, lobbyId: number): SagaIterator {
+ const lobbyUpdatesChannel: Channel = yield eventChannel(session.watchLobbyUpdated(lobbyId));
try {
while (true) {
const lobby = yield take(lobbyUpdatesChannel);
const normalized = normalize(lobby, gameSchema);
- yield put(gameActions.updateGames(normalized.entities.games));
+ // players update needs to be first, otherwise the UI cannot find the player in the list
yield put(playerActions.updatePlayers(normalized.entities.players));
+ yield put(gameActions.updateGames(normalized.entities.games));
}
} finally {
yield apply(lobbyUpdatesChannel, lobbyUpdatesChannel.close);
}
}
-function* watchGameStart(session: SevenWondersSession): SagaIterator {
- const currentGameId = getCurrentGameId();
- const gameStartedChannel = yield eventChannel(session.watchGameStarted(currentGameId));
+function* watchGameStart(session: SevenWondersSession, lobbyId: number): SagaIterator {
+ const gameStartedChannel = yield eventChannel(session.watchGameStarted(lobbyId));
try {
yield take(gameStartedChannel);
- yield put(gameActions.enterGame());
- yield put(push('/game'));
+ yield put(gameActions.enterGame(lobbyId));
+ yield put(push(`/game/${lobbyId}`));
} finally {
yield apply(gameStartedChannel, gameStartedChannel.close);
}
@@ -49,5 +43,10 @@ function* startGame(session: SevenWondersSession): SagaIterator {
}
export function* lobbySaga(session: SevenWondersSession): SagaIterator {
- yield all([call(watchLobbyUpdates, session), call(watchGameStart, session), call(startGame, session)]);
+ const { gameId } = yield take(types.ENTER_LOBBY);
+ yield all([
+ call(watchLobbyUpdates, session, gameId),
+ call(watchGameStart, session, gameId),
+ call(startGame, session)
+ ]);
}
bgstack15