summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorjbion <joffrey.bion@amadeus.com>2019-02-27 03:12:55 +0100
committerjbion <joffrey.bion@amadeus.com>2019-02-27 03:12:55 +0100
commit8d73d21108738754efd07b63ecc7368fd49502fa (patch)
tree8755dd1aa9c61e9f473fd6787ba8f3595006f0fa /frontend/src
parentRemove unnecessary Jackson annotation on non-DTOs (diff)
downloadseven-wonders-8d73d21108738754efd07b63ecc7368fd49502fa.tar.gz
seven-wonders-8d73d21108738754efd07b63ecc7368fd49502fa.tar.bz2
seven-wonders-8d73d21108738754efd07b63ecc7368fd49502fa.zip
Simplify state and reducers
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/api/model.js18
-rw-r--r--frontend/src/components/game-browser/GameList.jsx11
-rw-r--r--frontend/src/components/game-browser/GameStatus.jsx4
-rw-r--r--frontend/src/components/game-browser/PlayerInfo.jsx15
-rw-r--r--frontend/src/components/game/GameScene.jsx22
-rw-r--r--frontend/src/components/home/ChooseNameForm.jsx2
-rw-r--r--frontend/src/components/lobby/Lobby.jsx20
-rw-r--r--frontend/src/components/lobby/PlayerList.jsx6
-rw-r--r--frontend/src/components/lobby/RadialPlayerList.jsx23
-rw-r--r--frontend/src/models/currentGame.js8
-rw-r--r--frontend/src/models/games.js84
-rw-r--r--frontend/src/models/players.js47
-rw-r--r--frontend/src/reducers.js20
-rw-r--r--frontend/src/redux/actions/all.js2
-rw-r--r--frontend/src/redux/actions/lobby.js7
-rw-r--r--frontend/src/redux/actions/user.js (renamed from frontend/src/redux/actions/players.js)14
-rw-r--r--frontend/src/redux/currentGame.js19
-rw-r--r--frontend/src/redux/games.js40
-rw-r--r--frontend/src/redux/players.js25
-rw-r--r--frontend/src/redux/user.js37
-rw-r--r--frontend/src/sagas/gameBrowser.js23
-rw-r--r--frontend/src/sagas/home.js4
-rw-r--r--frontend/src/sagas/lobby.js8
-rw-r--r--frontend/src/schemas/games.js9
-rw-r--r--frontend/src/store.js6
25 files changed, 167 insertions, 307 deletions
diff --git a/frontend/src/api/model.js b/frontend/src/api/model.js
index 2d1d2dde..cd718ff1 100644
--- a/frontend/src/api/model.js
+++ b/frontend/src/api/model.js
@@ -10,10 +10,18 @@ export type ApiErrorDetail = {
export type ApiGameState = "LOBBY" | "PLAYING";
+export type ApiPlayer = {
+ username: string,
+ displayName: string,
+ index: number,
+ gameOwner: Boolean,
+ user: Boolean,
+};
+
export type ApiLobby = {
id: number,
name: string,
- owner: ApiPlayer,
+ owner: string,
players: ApiPlayer[],
settings: ApiSettings,
state: ApiGameState
@@ -33,14 +41,6 @@ export type ApiSettings = {
wonPointsPerVictoryPerAge: Map<number, number>
};
-export type ApiPlayer = {
- username: string,
- displayName: string,
- index: number,
- gameOwner: Boolean,
- user: Boolean,
-};
-
export type ApiTable = {
boards: ApiBoard[],
currentAge: number,
diff --git a/frontend/src/components/game-browser/GameList.jsx b/frontend/src/components/game-browser/GameList.jsx
index 64ced9b0..b46cd14c 100644
--- a/frontend/src/components/game-browser/GameList.jsx
+++ b/frontend/src/components/game-browser/GameList.jsx
@@ -2,7 +2,8 @@
import type { List } from 'immutable';
import React from 'react';
import { connect } from 'react-redux';
-import type { Game } from '../../models/games';
+import type { ApiLobby } from '../../api/model';
+import type { GlobalState } from '../../reducers';
import { actions } from '../../redux/actions/lobby';
import { getAllGames } from '../../redux/games';
import { IconButton } from '../shared/IconButton';
@@ -11,7 +12,7 @@ import { GameStatus } from './GameStatus';
import { PlayerCount } from './PlayerCount';
type GameListProps = {
- games: List<Game>,
+ games: List<ApiLobby>,
joinGame: (gameId: string) => void,
};
@@ -21,7 +22,7 @@ const GameListPresenter = ({ games, joinGame }: GameListProps) => (
<GameListHeaderRow />
</thead>
<tbody>
- {games.map((game: Game) => <GameListItemRow key={game.id} game={game} joinGame={joinGame}/>)}
+ {games.map((game: ApiLobby) => <GameListItemRow key={game.id} game={game} joinGame={joinGame}/>)}
</tbody>
</table>
);
@@ -56,8 +57,8 @@ const JoinButton = ({game, joinGame}) => {
return <IconButton minimal disabled={disabled} icon='arrow-right' title='Join Game' onClick={onClick}/>;
};
-const mapStateToProps = state => ({
- games: getAllGames(state.get('games')),
+const mapStateToProps = (state: GlobalState) => ({
+ games: getAllGames(state),
});
const mapDispatchToProps = {
diff --git a/frontend/src/components/game-browser/GameStatus.jsx b/frontend/src/components/game-browser/GameStatus.jsx
index 749d3cfa..fc14bbf6 100644
--- a/frontend/src/components/game-browser/GameStatus.jsx
+++ b/frontend/src/components/game-browser/GameStatus.jsx
@@ -1,10 +1,10 @@
//@flow
import { Tag } from '@blueprintjs/core';
import * as React from 'react';
-import type { GameState } from '../../models/games';
+import type { ApiGameState } from '../../api/model';
type GameStatusProps = {
- state: GameState,
+ state: ApiGameState,
}
export const GameStatus = ({state}: GameStatusProps) => (
diff --git a/frontend/src/components/game-browser/PlayerInfo.jsx b/frontend/src/components/game-browser/PlayerInfo.jsx
index 2f29ea60..baee67c1 100644
--- a/frontend/src/components/game-browser/PlayerInfo.jsx
+++ b/frontend/src/components/game-browser/PlayerInfo.jsx
@@ -2,23 +2,24 @@
import { Text } from '@blueprintjs/core';
import React from 'react';
import { connect } from 'react-redux';
-import type { Player } from '../../models/players';
-import { getCurrentPlayer } from '../../redux/players';
+import type { GlobalState } from '../../reducers';
+import type { User } from '../../redux/user';
+import { getCurrentUser } from '../../redux/user';
type PlayerInfoProps = {
- player: ?Player,
+ user: ?User,
}
-const PlayerInfoPresenter = ({player}: PlayerInfoProps) => (
+const PlayerInfoPresenter = ({user}: PlayerInfoProps) => (
<Text>
<b>Username:</b>
{' '}
- {player && player.displayName}
+ {user && user.displayName}
</Text>
);
-const mapStateToProps = state => ({
- player: getCurrentPlayer(state),
+const mapStateToProps = (state: GlobalState): PlayerInfoProps => ({
+ user: getCurrentUser(state),
});
const mapDispatchToProps = {
diff --git a/frontend/src/components/game/GameScene.jsx b/frontend/src/components/game/GameScene.jsx
index 5221ab8e..70e857f0 100644
--- a/frontend/src/components/game/GameScene.jsx
+++ b/frontend/src/components/game/GameScene.jsx
@@ -2,22 +2,18 @@ import { Button, Classes, Intent, NonIdealState } from '@blueprintjs/core';
import { List } from 'immutable';
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import type { ApiPlayerMove, ApiPlayerTurnInfo } from '../../api/model';
-import { Game } from '../../models/games';
-import { Player } from '../../models/players';
+import type { ApiPlayer, ApiPlayerMove, ApiPlayerTurnInfo } from '../../api/model';
+import type { GlobalState } from '../../reducers';
import { actions } from '../../redux/actions/game';
import { getCurrentTurnInfo } from '../../redux/currentGame';
import { getCurrentGame } from '../../redux/games';
-import { getCurrentPlayer, getPlayers } from '../../redux/players';
import { Board } from './Board';
-import { Hand } from './Hand';
import './GameScene.css'
+import { Hand } from './Hand';
import { ProductionBar } from './ProductionBar';
type GameSceneProps = {
- game: Game,
- currentPlayer: Player,
- players: List<Player>,
+ players: List<ApiPlayer>,
turnInfo: ApiPlayerTurnInfo,
sayReady: () => void,
prepareMove: (move: ApiPlayerMove) => void,
@@ -54,15 +50,13 @@ const GamePreStart = ({onReadyClicked}) => <NonIdealState
onClick={onReadyClicked}/>}
/>;
-const mapStateToProps: (state) => GameSceneProps = state => {
- const game = getCurrentGame(state.get('games'));
+const mapStateToProps: (state: GlobalState) => GameSceneProps = state => {
+ const game = getCurrentGame(state);
console.info(game);
return {
- game: game,
- currentPlayer: getCurrentPlayer(state),
- players: game ? getPlayers(state.get('players'), game.players) : new List(),
- turnInfo: getCurrentTurnInfo(state.get('currentGame')),
+ players: game ? new List(game.players) : new List(),
+ turnInfo: getCurrentTurnInfo(state),
};
};
diff --git a/frontend/src/components/home/ChooseNameForm.jsx b/frontend/src/components/home/ChooseNameForm.jsx
index 619d967c..13f6034b 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/actions/players';
+import { actions } from '../../redux/actions/user';
import { IconButton } from '../shared/IconButton';
type ChooseNameFormPresenterProps = {
diff --git a/frontend/src/components/lobby/Lobby.jsx b/frontend/src/components/lobby/Lobby.jsx
index f352ab83..cc979190 100644
--- a/frontend/src/components/lobby/Lobby.jsx
+++ b/frontend/src/components/lobby/Lobby.jsx
@@ -3,17 +3,17 @@ import { Button, Classes, Intent } from '@blueprintjs/core';
import { List } from 'immutable';
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import type { Game } from '../../models/games';
-import type { Player } from '../../models/players';
+import type { ApiLobby, ApiPlayer } from '../../api/model';
+import type { GlobalState } from '../../reducers';
import { actions } from '../../redux/actions/lobby';
import { getCurrentGame } from '../../redux/games';
-import { getCurrentPlayer, getPlayers } from '../../redux/players';
+import { getCurrentPlayer } from '../../redux/user';
import { RadialPlayerList } from './RadialPlayerList';
export type LobbyProps = {
- currentGame: Game,
- currentPlayer: Player,
- players: List<Player>,
+ currentGame: ApiLobby,
+ currentPlayer: ApiPlayer,
+ players: List<ApiPlayer>,
startGame: () => void,
}
@@ -24,7 +24,7 @@ class LobbyPresenter extends Component<LobbyProps> {
return (
<div>
<h2>{currentGame.name + ' — Lobby'}</h2>
- <RadialPlayerList players={players} owner={currentGame.owner} currentPlayer={currentPlayer}/>
+ <RadialPlayerList players={players}/>
{currentPlayer.gameOwner && <Button text="START" className={Classes.LARGE} intent={Intent.PRIMARY} icon='play'
onClick={startGame} disabled={players.size < 3}/>}
</div>
@@ -32,13 +32,13 @@ class LobbyPresenter extends Component<LobbyProps> {
}
}
-const mapStateToProps = state => {
- const game = getCurrentGame(state.get('games'));
+const mapStateToProps = (state: GlobalState) => {
+ const game = getCurrentGame(state);
console.info(game);
return {
currentGame: game,
currentPlayer: getCurrentPlayer(state),
- players: game ? getPlayers(state.get('players'), game.players) : new List(),
+ players: game ? new List(game.players) : new List(),
};
};
diff --git a/frontend/src/components/lobby/PlayerList.jsx b/frontend/src/components/lobby/PlayerList.jsx
index 87887dd0..bd37f40d 100644
--- a/frontend/src/components/lobby/PlayerList.jsx
+++ b/frontend/src/components/lobby/PlayerList.jsx
@@ -3,12 +3,12 @@ import { Icon } from '@blueprintjs/core'
import { List } from 'immutable';
import * as React from 'react';
import { Flex } from 'reflexbox';
-import { Player } from '../../models/players';
+import { ApiPlayer } from '../../api/model';
type PlayerListProps = {
- players: List<Player>,
+ players: List<ApiPlayer>,
owner: string,
- currentPlayer: Player,
+ currentPlayer: ApiPlayer,
};
const PlayerListItem = ({player, isOwner, isUser}) => (
diff --git a/frontend/src/components/lobby/RadialPlayerList.jsx b/frontend/src/components/lobby/RadialPlayerList.jsx
index 8345b48c..0f122910 100644
--- a/frontend/src/components/lobby/RadialPlayerList.jsx
+++ b/frontend/src/components/lobby/RadialPlayerList.jsx
@@ -3,19 +3,17 @@ import { Icon } from '@blueprintjs/core'
import { List } from 'immutable';
import * as React from 'react';
import { Flex } from 'reflexbox';
-import { Player } from '../../models/players';
+import { ApiPlayer } from '../../api/model';
import { RadialList } from './radial-list/RadialList';
import roundTable from './round-table.png';
type RadialPlayerListProps = {
- players: List<Player>,
- owner: string,
- currentPlayer: Player,
+ players: List<ApiPlayer>
};
-const PlayerItem = ({player, isOwner, isUser}) => (
+const PlayerItem = ({player}) => (
<Flex column align='center'>
- <UserIcon isOwner={isOwner} isUser={isUser} title={isOwner ? 'Game owner' : false}/>
+ <UserIcon isOwner={player.gameOwner} isUser={player.user} title={player.gameOwner ? 'Game owner' : false}/>
<h5 style={{margin: 0}}>{player.displayName}</h5>
</Flex>
);
@@ -33,12 +31,9 @@ const UserIcon = ({isUser, isOwner, title}) => {
return <Icon icon={icon} iconSize={50} intent={intent} title={title}/>;
};
-export const RadialPlayerList = ({players, owner, currentPlayer}: RadialPlayerListProps) => {
- const orderedPlayers = placeFirst(players.toArray(), currentPlayer.username);
- const playerItems = orderedPlayers.map(player => <PlayerItem key={player.username}
- player={player}
- isOwner={player.username === owner}
- isUser={player.username === currentPlayer.username}/>);
+export const RadialPlayerList = ({players}: RadialPlayerListProps) => {
+ const orderedPlayers = placeUserFirst(players.toArray());
+ const playerItems = orderedPlayers.map(player => <PlayerItem key={player.username} player={player}/>);
const tableImg = <img src={roundTable} alt='Round table' style={{width: 200, height: 200}}/>;
return <RadialList items={completeWithPlaceholders(playerItems)}
centerElement={tableImg}
@@ -48,8 +43,8 @@ export const RadialPlayerList = ({players, owner, currentPlayer}: RadialPlayerLi
itemHeight={100}/>;
};
-function placeFirst(players: Array<Player>, targetFirstUsername: string): Array<Player> {
- while (players[0].username !== targetFirstUsername) {
+function placeUserFirst(players: Array<ApiPlayer>): Array<ApiPlayer> {
+ while (!players[0].user) {
players.push(players.shift());
}
return players;
diff --git a/frontend/src/models/currentGame.js b/frontend/src/models/currentGame.js
deleted file mode 100644
index 398c65e8..00000000
--- a/frontend/src/models/currentGame.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { List } from 'immutable';
-import type { ApiPlayerTurnInfo, ApiTable } from '../api/model';
-
-export class CurrentGameState {
- readyUsernames: List<string> = new List();
- turnInfo: ApiPlayerTurnInfo | null = null;
- table: ApiTable | null = null;
-}
diff --git a/frontend/src/models/games.js b/frontend/src/models/games.js
deleted file mode 100644
index 85aab2df..00000000
--- a/frontend/src/models/games.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import { List, Map, Record } from 'immutable';
-
-export type SettingsShape = {
- initialGold: number,
- lostPointsPerDefeat: number,
- timeLimitInSeconds: number,
- randomSeedForTests: number,
- discardedCardGold: number,
- defaultTradingCost: number,
- wonPointsPerVictoryPerAge: {
- "1": number,
- "2": number,
- "3": number
- },
- wonderSidePickMethod: "EACH_RANDOM" | "TODO",
- pointsPer3Gold: number
-};
-
-export type SettingsType = Record<SettingsShape>;
-
-const SettingsRecord: SettingsType = Record({
- initialGold: 3,
- lostPointsPerDefeat: 1,
- timeLimitInSeconds: 45,
- randomSeedForTests: -1,
- discardedCardGold: 3,
- defaultTradingCost: 2,
- wonPointsPerVictoryPerAge: {
- '1': 1,
- '2': 3,
- '3': 5,
- },
- wonderSidePickMethod: 'EACH_RANDOM',
- pointsPer3Gold: 1,
-});
-
-export class Settings extends SettingsRecord {}
-
-export type GameState = 'LOBBY' | 'PLAYING';
-export type GameShape = {
- id: number,
- name: string | void,
- owner: string,
- players: List<string>,
- settings: SettingsType,
- state: GameState,
-};
-
-export type GameType = Record<GameShape>;
-export type GameMapType = Map<string, GameShape>;
-export type GameNormalMapType = { [string]: GameShape };
-
-const GameRecord: GameType = Record({
- id: -1,
- name: null,
- owner: 'anonymous',
- players: new List(),
- 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);
- return this.mergeDeepIn(['all', game.id], game);
- }
- addGames(games: GameNormalMapType) {
- return this.mergeIn(['all'], games.map((game: GameShape): Game => new Game(game)));
- }
-}
diff --git a/frontend/src/models/players.js b/frontend/src/models/players.js
deleted file mode 100644
index 587300f5..00000000
--- a/frontend/src/models/players.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// @flow
-import { Map, Record } from 'immutable';
-
-export type PlayerShape = {
- username: string,
- displayName: string,
- index: number,
- ready: boolean,
- gameOwner: boolean,
- user: boolean,
-};
-export type PlayerType = Record<PlayerShape>;
-
-const PlayerRecord: PlayerType = Record({
- username: null,
- displayName: null,
- index: 0,
- ready: false,
- gameOwner: false,
- user: false,
-});
-// $FlowFixMe
-export class Player extends PlayerRecord {}
-
-export type PlayersShape = {
- all: Map<string, PlayerType>,
- current: string
-};
-export type PlayersType = Record<PlayersShape>;
-
-const PlayersRecord: PlayersType = Record({
- all: Map(),
- current: '',
-});
-// $FlowFixMe
-export class PlayerState extends PlayersRecord {
- addPlayer(p: PlayerShape) {
- const player: Player = new Player(p);
- const playerMap = Map({ [player.username]: player });
- return this.addPlayers(playerMap).set('current', player.username);
- }
-
- addPlayers(p: Map<string, PlayerShape>) {
- const players: Map<string, PlayerShape> = Map(p);
- return this.mergeIn(['all'], players.map((player: PlayerShape): Player => new Player(player)));
- }
-}
diff --git a/frontend/src/reducers.js b/frontend/src/reducers.js
index 5e066d28..612bd0c5 100644
--- a/frontend/src/reducers.js
+++ b/frontend/src/reducers.js
@@ -1,15 +1,25 @@
// @flow
import { routerReducer } from 'react-router-redux';
-import { combineReducers } from 'redux-immutable';
+import { combineReducers } from 'redux';
+import type { ApiPlayer } from './api/model';
+import type { CurrentGameState } from './redux/currentGame';
import { createCurrentGameReducer } from './redux/currentGame';
-import { gamesReducer } from './redux/games';
-import { playersReducer } from './redux/players';
+import type { GamesState } from './redux/games';
+import { createGamesReducer } from './redux/games';
+import { currentUserReducer } from './redux/user';
+
+export type GlobalState = {
+ currentGame: CurrentGameState;
+ currentUser: ApiPlayer;
+ games: GamesState;
+ routing: any;
+}
export function createReducer() {
return combineReducers({
currentGame: createCurrentGameReducer(),
- games: gamesReducer,
- players: playersReducer,
+ currentUser: currentUserReducer,
+ games: createGamesReducer(),
routing: routerReducer,
});
}
diff --git a/frontend/src/redux/actions/all.js b/frontend/src/redux/actions/all.js
index 45d3ab7a..12522819 100644
--- a/frontend/src/redux/actions/all.js
+++ b/frontend/src/redux/actions/all.js
@@ -1,5 +1,5 @@
import type { GameAction } from './game';
import type { LobbyAction } from './lobby';
-import type { PlayerAction } from './players';
+import type { PlayerAction } from './user';
export type Action = PlayerAction | LobbyAction | GameAction
diff --git a/frontend/src/redux/actions/lobby.js b/frontend/src/redux/actions/lobby.js
index b3151a23..8768ec80 100644
--- a/frontend/src/redux/actions/lobby.js
+++ b/frontend/src/redux/actions/lobby.js
@@ -1,5 +1,4 @@
-import { fromJS } from 'immutable';
-import type { GameMapType, GameNormalMapType } from '../../models/games';
+import type { ApiLobby } from '../../api/model';
export const types = {
UPDATE_GAMES: 'GAMES/UPDATE_GAMES',
@@ -10,7 +9,7 @@ export const types = {
ENTER_GAME: 'GAMES/ENTER_GAME',
};
-export type UpdateGamesAction = { type: 'GAMES/UPDATE_GAMES', games: GameMapType };
+export type UpdateGamesAction = { type: 'GAMES/UPDATE_GAMES', games: ApiLobby[]};
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' };
@@ -26,7 +25,7 @@ export type LobbyAction =
| EnterGameAction;
export const actions = {
- updateGames: (games: GameNormalMapType): UpdateGamesAction => ({ type: types.UPDATE_GAMES, games: fromJS(games) }),
+ updateGames: (games: ApiLobby[]): UpdateGamesAction => ({ type: types.UPDATE_GAMES, 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 }),
diff --git a/frontend/src/redux/actions/players.js b/frontend/src/redux/actions/user.js
index 7df174c4..4406230b 100644
--- a/frontend/src/redux/actions/players.js
+++ b/frontend/src/redux/actions/user.js
@@ -1,5 +1,5 @@
import { Map } from 'immutable';
-import { PlayerShape } from '../../models/players';
+import type { ApiPlayer } from '../../api/model';
export const types = {
REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME',
@@ -8,8 +8,8 @@ export const types = {
};
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 SetCurrentPlayerAction = { type: types.SET_CURRENT_PLAYER, player: ApiPlayer };
+export type UpdatePlayersAction = { type: types.UPDATE_PLAYERS, players: Map<string, ApiPlayer> };
export type PlayerAction = RequestChooseUsernameAction | SetCurrentPlayerAction | UpdatePlayersAction;
@@ -18,12 +18,8 @@ export const actions = {
type: types.REQUEST_CHOOSE_USERNAME,
username,
}),
- setCurrentPlayer: (player: PlayerShape): SetCurrentPlayerAction => ({
+ setCurrentPlayer: (player: ApiPlayer): SetCurrentPlayerAction => ({
type: types.SET_CURRENT_PLAYER,
- player,
- }),
- updatePlayers: (players: Map<string, PlayerShape>): UpdatePlayersAction => ({
- type: types.UPDATE_PLAYERS,
- players,
+ player: player,
}),
};
diff --git a/frontend/src/redux/currentGame.js b/frontend/src/redux/currentGame.js
index e5659195..e315a1e8 100644
--- a/frontend/src/redux/currentGame.js
+++ b/frontend/src/redux/currentGame.js
@@ -1,27 +1,22 @@
// @flow
-import { List } from 'immutable';
import { combineReducers } from 'redux';
import type { ApiPlayerTurnInfo, ApiTable } from '../api/model';
-import { CurrentGameState } from '../models/currentGame';
+import type { GlobalState } from '../reducers';
import type { Action } from './actions/all';
import { types } from './actions/game';
+export type CurrentGameState = {
+ turnInfo: ApiPlayerTurnInfo | null;
+ table: ApiTable | null;
+}
+
export function createCurrentGameReducer() {
return combineReducers({
- readyUsernames: readyUsernamesReducer,
turnInfo: turnInfoReducer,
table: tableUpdatesReducer,
});
}
-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) => {
switch (action.type) {
case types.TURN_INFO_RECEIVED:
@@ -44,4 +39,4 @@ const tableUpdatesReducer = (state: ApiTable | null = null, action: Action) => {
}
};
-export const getCurrentTurnInfo = (state: CurrentGameState): ApiPlayerTurnInfo => state.turnInfo;
+export const getCurrentTurnInfo = (state: GlobalState): ApiPlayerTurnInfo => state.currentGame.turnInfo;
diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js
index 68571981..f5543a76 100644
--- a/frontend/src/redux/games.js
+++ b/frontend/src/redux/games.js
@@ -1,22 +1,42 @@
// @flow
-import type { List, Map } from 'immutable';
-import type { Game } from '../models/games';
-import { GamesState } from '../models/games';
+import { List, Map } from 'immutable';
+import { combineReducers } from 'redux';
+import type { ApiLobby } from '../api/model';
+import type { GlobalState } from '../reducers';
import type { Action } from './actions/all';
import { types } from './actions/lobby';
-export const gamesReducer = (state: GamesState = new GamesState(), action: Action) => {
+export type GamesState = {
+ all: Map<string, ApiLobby>,
+ current: string | void
+};
+
+export const createGamesReducer = () => {
+ return combineReducers({
+ all: allGamesReducer,
+ current: currentGameIdReducer
+ })
+};
+
+export const allGamesReducer = (state: Map<string, ApiLobby> = Map(), action: Action) => {
switch (action.type) {
case types.UPDATE_GAMES:
- return state.addGames(action.games);
+ let newGames = {};
+ action.games.forEach(g => newGames[g.id] = g);
+ return state.merge(Map(newGames));
+ default:
+ return state;
+ }
+};
+
+export const currentGameIdReducer = (state: string | void = null, action: Action) => {
+ switch (action.type) {
case types.ENTER_LOBBY:
- return state.set('current', action.gameId);
+ return `${action.gameId}`;
default:
return state;
}
};
-export const getAllGamesById = (games: GamesState): Map<string, Game> => games.all;
-export const getAllGames = (games: GamesState): List<Game> => getAllGamesById(games).toList();
-export const getGame = (games: GamesState, id: string | number): Game => getAllGamesById(games).get(`${id}`);
-export const getCurrentGame = (games: GamesState): Game => getGame(games, games.current);
+export const getAllGames = (state: GlobalState): List<ApiLobby> => state.games.all.toList();
+export const getCurrentGame = (state: GlobalState): ApiLobby | null => state.games.all.get(state.games.current);
diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js
deleted file mode 100644
index ce3c305f..00000000
--- a/frontend/src/redux/players.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { List } from 'immutable';
-import { Player, PlayerState } from '../models/players';
-import type { Action } from './actions/all';
-import { types } from './actions/players';
-
-export const playersReducer = (state = new PlayerState(), action: Action) => {
- switch (action.type) {
- case types.SET_CURRENT_PLAYER:
- return state.addPlayer(action.player);
- case types.UPDATE_PLAYERS:
- return state.addPlayers(action.players);
- default:
- return state;
- }
-};
-
-const ANONYMOUS = new Player({displayName: '[NOT LOGGED]'});
-
-export function getCurrentPlayer(state): Player {
- const players = state.get('players');
- return getPlayer(players, players.current, ANONYMOUS);
-}
-
-export const getPlayer = (players, username, defaultPlayer): ?Player => players.all.get(username, defaultPlayer);
-export const getPlayers = (players, usernames): List<Player> => usernames.map(u => getPlayer(players, u, undefined));
diff --git a/frontend/src/redux/user.js b/frontend/src/redux/user.js
new file mode 100644
index 00000000..f2247b38
--- /dev/null
+++ b/frontend/src/redux/user.js
@@ -0,0 +1,37 @@
+import { ApiPlayer } from '../api/model';
+import type { GlobalState } from '../reducers';
+import type { Action } from './actions/all';
+import { types } from './actions/user';
+import { getCurrentGame } from './games';
+
+export type User = {
+ username: string,
+ displayName: string,
+}
+
+export const currentUserReducer = (state: ?User = null, action: Action) => {
+ switch (action.type) {
+ case types.SET_CURRENT_PLAYER:
+ return {
+ username: action.player.username,
+ displayName: action.player.displayName
+ };
+ default:
+ return state;
+ }
+};
+
+export function getCurrentUser(state: GlobalState): ?User {
+ return state.currentUser
+}
+
+export function getCurrentPlayer(state: GlobalState): ApiPlayer {
+ let game = getCurrentGame(state);
+ for (let i = 0; i < game.players.length; i++) {
+ let player = game.players[i];
+ if (player.username === state.currentUser.username) {
+ return player;
+ }
+ }
+ return null;
+}
diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js
index 062603a3..fec83451 100644
--- a/frontend/src/sagas/gameBrowser.js
+++ b/frontend/src/sagas/gameBrowser.js
@@ -1,24 +1,18 @@
// @flow
-import { normalize } from 'normalizr';
import { push } from 'react-router-redux';
import type { SagaIterator } from 'redux-saga';
import { eventChannel } from 'redux-saga';
import { all, apply, call, put, take } from 'redux-saga/effects';
+import type { ApiLobby } from '../api/model';
import type { SevenWondersSession } from '../api/sevenWondersApi';
-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';
+import { actions as gameActions, types } from '../redux/actions/lobby';
function* watchGames(session: SevenWondersSession): SagaIterator {
const gamesChannel = yield eventChannel(session.watchGames());
try {
while (true) {
const gameList = yield take(gamesChannel);
- const normGameList = normalize(gameList, gameListSchema);
- // for an empty game array, there is no players/games entity maps
- yield put(playerActions.updatePlayers(normGameList.entities.players || {}));
- yield put(gameActions.updateGames(normGameList.entities.games || {}));
+ yield put(gameActions.updateGames(gameList));
}
} finally {
yield apply(gamesChannel, gamesChannel.close);
@@ -28,13 +22,10 @@ function* watchGames(session: SevenWondersSession): SagaIterator {
function* watchLobbyJoined(session: SevenWondersSession): SagaIterator {
const joinedLobbyChannel = yield eventChannel(session.watchLobbyJoined());
try {
- const joinedLobby = yield take(joinedLobbyChannel);
- const normalized = normalize(joinedLobby, gameSchema);
- const gameId = normalized.result;
- yield put(playerActions.updatePlayers(normalized.entities.players));
- yield put(gameActions.updateGames(normalized.entities.games));
- yield put(gameActions.enterLobby(gameId));
- yield put(push(`/lobby/${gameId}`));
+ const joinedLobby: ApiLobby = yield take(joinedLobbyChannel);
+ yield put(gameActions.updateGames([joinedLobby]));
+ yield put(gameActions.enterLobby(joinedLobby.id));
+ yield put(push(`/lobby/${joinedLobby.id}`));
} finally {
yield apply(joinedLobbyChannel, joinedLobbyChannel.close);
}
diff --git a/frontend/src/sagas/home.js b/frontend/src/sagas/home.js
index 705a7a40..43ab97dc 100644
--- a/frontend/src/sagas/home.js
+++ b/frontend/src/sagas/home.js
@@ -5,8 +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 } from '../redux/actions/players';
-import { types } from '../redux/actions/players';
+import { actions } from '../redux/actions/user';
+import { types } from '../redux/actions/user';
function* sendUsername(session: SevenWondersSession): SagaIterator {
while (true) {
diff --git a/frontend/src/sagas/lobby.js b/frontend/src/sagas/lobby.js
index b0f52d5c..7ff94f34 100644
--- a/frontend/src/sagas/lobby.js
+++ b/frontend/src/sagas/lobby.js
@@ -1,23 +1,17 @@
// @flow
-import { normalize } from 'normalizr';
import { push } from 'react-router-redux';
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/actions/lobby';
-import { actions as playerActions } from '../redux/actions/players';
-import { game as gameSchema } from '../schemas/games';
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);
- // 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));
+ yield put(gameActions.updateGames([lobby]));
}
} finally {
yield apply(lobbyUpdatesChannel, lobbyUpdatesChannel.close);
diff --git a/frontend/src/schemas/games.js b/frontend/src/schemas/games.js
deleted file mode 100644
index bcae0235..00000000
--- a/frontend/src/schemas/games.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { schema } from 'normalizr';
-
-const player = new schema.Entity('players', {}, { idAttribute: 'username' });
-
-export const game = new schema.Entity('games', {
- players: [player],
-});
-
-export const gameList = [game];
diff --git a/frontend/src/store.js b/frontend/src/store.js
index 5c958afc..9c5a063b 100644
--- a/frontend/src/store.js
+++ b/frontend/src/store.js
@@ -1,13 +1,13 @@
// @flow
import createHistory from 'history/createBrowserHistory';
-import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
+import type { GlobalState } from './reducers';
import { createReducer } from './reducers';
import { rootSaga } from './sagas';
-export function configureStore(initialState: Object = {}) {
+export function configureStore(initialState: GlobalState = {}) {
const sagaMiddleware = createSagaMiddleware();
const history = createHistory();
@@ -22,7 +22,7 @@ export function configureStore(initialState: Object = {}) {
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
- const store = createStore(createReducer(), fromJS(initialState), composeEnhancers(...enhancers));
+ const store = createStore(createReducer(), initialState, composeEnhancers(...enhancers));
sagaMiddleware.run(rootSaga);
bgstack15