summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorJoffrey BION <joffrey.bion@gmail.com>2017-05-14 18:43:58 +0200
committerJoffrey BION <joffrey.bion@gmail.com>2017-05-14 18:43:58 +0200
commitb6f54ed56121aa5435dd813c952b292b25b56bfb (patch)
treeb4c88476393b031f00e9a1398f4f2a352f530341 /frontend
parentFix typo in error subscription path (diff)
downloadseven-wonders-b6f54ed56121aa5435dd813c952b292b25b56bfb.tar.gz
seven-wonders-b6f54ed56121aa5435dd813c952b292b25b56bfb.tar.bz2
seven-wonders-b6f54ed56121aa5435dd813c952b292b25b56bfb.zip
Add lobby saga
- Fix lobby's player list updates - Fix lobby's player list order - Add 'start game' button (not restricted to owner yet) Resolves: https://github.com/luxons/seven-wonders/issues/7
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/playerList.js4
-rw-r--r--frontend/src/containers/lobby.js13
-rw-r--r--frontend/src/redux/games.js4
-rw-r--r--frontend/src/redux/players.js4
-rw-r--r--frontend/src/routes.js6
-rw-r--r--frontend/src/sagas/gameBrowser.js77
-rw-r--r--frontend/src/sagas/lobby.js58
7 files changed, 112 insertions, 54 deletions
diff --git a/frontend/src/components/playerList.js b/frontend/src/components/playerList.js
index 45aa01a2..70e8b1f6 100644
--- a/frontend/src/components/playerList.js
+++ b/frontend/src/components/playerList.js
@@ -5,8 +5,8 @@ import Immutable from 'seamless-immutable'
const PlayerList = (props) => (
<div>
- {Immutable.asMutable(props.players).map((player, index) => {
- return (<Flex key={index}>
+ {Immutable.asMutable(props.players).map(player => {
+ return (<Flex key={player.index}>
<Text>{player.displayName}</Text>
<Text>({player.username})</Text>
</Flex>)
diff --git a/frontend/src/containers/lobby.js b/frontend/src/containers/lobby.js
index f0df0c44..e326698f 100644
--- a/frontend/src/containers/lobby.js
+++ b/frontend/src/containers/lobby.js
@@ -1,14 +1,14 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Immutable from 'seamless-immutable'
-import { Text } from 'rebass'
+import { Button } from 'rebass'
import PlayerList from '../components/playerList'
class Lobby extends Component {
getTitle() {
if (this.props.currentGame) {
- return this.props.currentGame.name
+ return this.props.currentGame.name + ' — Lobby'
} else {
return 'What are you doing here? You haven\'t joined a game yet!'
}
@@ -17,15 +17,16 @@ class Lobby extends Component {
render() {
return (
<div>
- <Text>{this.getTitle()}</Text>
+ <h2>{this.getTitle()}</h2>
<PlayerList players={this.props.players}/>
+ <Button onClick={this.props.startGame}>Start Game</Button>
</div>
)
}
}
import { getPlayers } from '../redux/players'
-import { getCurrentGame } from '../redux/games'
+import { getCurrentGame, actions } from '../redux/games'
const mapStateToProps = (state) => {
const game = getCurrentGame(state)
@@ -35,6 +36,8 @@ const mapStateToProps = (state) => {
})
}
-const mapDispatchToProps = {}
+const mapDispatchToProps = {
+ startGame: actions.requestStartGame
+}
export default connect(mapStateToProps, mapDispatchToProps)(Lobby)
diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js
index 2b9a92a4..2986de07 100644
--- a/frontend/src/redux/games.js
+++ b/frontend/src/redux/games.js
@@ -4,14 +4,18 @@ export const types = {
UPDATE_GAMES: 'GAME/UPDATE_GAMES',
REQUEST_CREATE_GAME: 'GAME/REQUEST_CREATE_GAME',
REQUEST_JOIN_GAME: 'GAME/REQUEST_JOIN_GAME',
+ REQUEST_START_GAME: 'GAME/REQUEST_JOIN_GAME',
ENTER_LOBBY: 'GAME/ENTER_LOBBY',
+ ENTER_GAME: 'GAME/ENTER_GAME',
}
export const actions = {
updateGames: (games) => ({ type: types.UPDATE_GAMES, games: Immutable(games) }),
requestJoinGame: (gameId) => ({ type: types.REQUEST_JOIN_GAME, gameId }),
requestCreateGame: (gameName) => ({ type: types.REQUEST_CREATE_GAME, gameName }),
+ requestStartGame: () => ({ type: types.REQUEST_START_GAME }),
enterLobby: (lobby) => ({ type: types.ENTER_LOBBY, lobby: Immutable(lobby) }),
+ enterGame: () => ({ type: types.ENTER_GAME }),
}
const initialState = Immutable.from({
diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js
index 84e24796..2b530ca1 100644
--- a/frontend/src/redux/players.js
+++ b/frontend/src/redux/players.js
@@ -40,5 +40,5 @@ export default (state = initialState, action) => {
}
export const getCurrentPlayer = state => state.players.all && state.players.all[state.players.current]
-export const getPlayers = (state, usernames) => Object.values(state.players.all)
- .filter(p => usernames.indexOf(p.username) !== -1)
+export const getPlayer = (state, username) => state.players.all[username]
+export const getPlayers = (state, usernames) => usernames.map(u => getPlayer(state, u))
diff --git a/frontend/src/routes.js b/frontend/src/routes.js
index 1ce46bb3..6fd60c04 100644
--- a/frontend/src/routes.js
+++ b/frontend/src/routes.js
@@ -1,6 +1,7 @@
import { fork } from 'redux-saga/effects'
import homeSaga from './sagas/home'
import gameBrowserSaga from './sagas/gameBrowser'
+import lobbySaga from './sagas/lobby'
export const makeSagaRoutes = wsConnection => ({
*'/'() {
@@ -8,6 +9,9 @@ export const makeSagaRoutes = wsConnection => ({
},
*'/games'() {
yield fork(gameBrowserSaga, wsConnection)
+ },
+ *'/lobby/*'() {
+ yield fork(lobbySaga, wsConnection)
}
})
@@ -28,7 +32,7 @@ export const routes = [
component: LobbyLayout,
childRoutes: [
{ path: '/games', component: GameBrowser },
- { path: '/lobby', component: Lobby }
+ { path: '/lobby/*', component: Lobby }
]
},
{
diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js
index 4f3309c3..10d6ec1d 100644
--- a/frontend/src/sagas/gameBrowser.js
+++ b/frontend/src/sagas/gameBrowser.js
@@ -1,73 +1,62 @@
import { call, put, take, apply } from 'redux-saga/effects'
-import { eventChannel } from 'redux-saga'
+import { createSubscriptionChannel } from '../utils/websocket'
import { push } from 'react-router-redux'
import { normalize } from 'normalizr'
-import { game, gameList } from '../schemas/games'
+import { game as gameSchema, gameList as gameListSchema} from '../schemas/games'
import { actions as gameActions, types } from '../redux/games'
import { actions as playerActions } from '../redux/players'
-function gameBrowserChannel(socket) {
- return eventChannel(emit => {
-
- const makeHandler = type => event => {
- const response = JSON.parse(event.body)
- emit({type, response})
- }
-
- const newGame = socket.subscribe('/topic/games', makeHandler(types.UPDATE_GAMES))
- const joinGame = socket.subscribe('/user/queue/lobby/joined', makeHandler(types.ENTER_LOBBY))
-
- return () => {
- newGame.unsubscribe()
- joinGame.unsubscribe()
+function *watchGames({socket}) {
+ const gamesChannel = yield call(createSubscriptionChannel, socket, '/topic/games')
+ 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 || {}))
}
- })
+ } finally {
+ yield apply(gamesChannel, gamesChannel.close)
+ }
}
-export function *watchGames({socket}) {
- const socketChannel = gameBrowserChannel(socket)
-
+function *watchLobbyJoined({socket}) {
+ const joinedLobbyChannel = yield call(createSubscriptionChannel, socket, '/user/queue/lobby/joined')
try {
- while (true) {
- const {type, response} = yield take(socketChannel)
-
- switch (type) {
- case types.UPDATE_GAMES:
- const normGameList = normalize(response, gameList)
- yield put(playerActions.updatePlayers(normGameList.entities.players || {}))
- yield put(gameActions.updateGames(normGameList.entities.games || {}))
- break
- case types.ENTER_LOBBY:
- const normGame = normalize(response, game)
- yield put(gameActions.enterLobby(normGame.entities.games[normGame.result]))
- socketChannel.close()
- yield put(push('/lobby'))
- break
- default:
- console.error('Unknown type')
- }
- }
+ 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(normalized.entities.games[gameId]))
+ yield put(push(`/lobby/${gameId}`))
} finally {
- console.info('gameBrowserChannel closed')
+ yield apply(joinedLobbyChannel, joinedLobbyChannel.close)
}
}
-export function *createGame({socket}) {
+function *createGame({socket}) {
const {gameName} = yield take(types.REQUEST_CREATE_GAME)
yield apply(socket, socket.send, ['/app/lobby/create', JSON.stringify({gameName}), {}])
}
-export function *joinGame({socket}) {
+function *joinGame({socket}) {
const {gameId} = yield take(types.REQUEST_JOIN_GAME)
yield apply(socket, socket.send, ['/app/lobby/join', JSON.stringify({gameId}), {}])
}
-export function *gameBrowserSaga(socketConnection) {
- yield [call(watchGames, socketConnection), call(createGame, socketConnection), call(joinGame, socketConnection)]
+function *gameBrowserSaga(socketConnection) {
+ yield [
+ call(watchGames, socketConnection),
+ call(watchLobbyJoined, socketConnection),
+ call(createGame, socketConnection),
+ call(joinGame, socketConnection)
+ ]
}
export default gameBrowserSaga
diff --git a/frontend/src/sagas/lobby.js b/frontend/src/sagas/lobby.js
new file mode 100644
index 00000000..f002c897
--- /dev/null
+++ b/frontend/src/sagas/lobby.js
@@ -0,0 +1,58 @@
+import { call, put, take, apply } from 'redux-saga/effects'
+import { createSubscriptionChannel } from '../utils/websocket'
+import { push } from 'react-router-redux'
+
+import { normalize } from 'normalizr'
+import { game as gameSchema } from '../schemas/games'
+
+import { actions as gameActions, types } from '../redux/games'
+import { actions as playerActions } from '../redux/players'
+
+function getCurrentGameId() {
+ const path = window.location.pathname
+ return path.split('lobby/')[1]
+}
+
+function *watchLobbyUpdates({ socket }) {
+ const currentGameId = getCurrentGameId()
+ const lobbyUpdatesChannel = yield call(createSubscriptionChannel, socket, `/topic/lobby/${currentGameId}/updated`)
+ try {
+ while (true) {
+ const lobby = yield take(lobbyUpdatesChannel)
+ const normalized = normalize(lobby, gameSchema)
+ yield put(gameActions.updateGames(normalized.entities.games))
+ yield put(playerActions.updatePlayers(normalized.entities.players))
+ }
+ } finally {
+ yield apply(lobbyUpdatesChannel, lobbyUpdatesChannel.close)
+ }
+}
+
+function *watchGameStart({ socket }) {
+ const currentGameId = getCurrentGameId()
+ const gameStartedChannel = yield call(createSubscriptionChannel, socket, `/topic/lobby/${currentGameId}/started`)
+ try {
+ yield take(gameStartedChannel)
+ yield put(gameActions.enterGame())
+ yield put(push('/game'))
+ } finally {
+ yield apply(gameStartedChannel, gameStartedChannel.close)
+ }
+}
+
+function *startGame({ socket }) {
+ while (true) {
+ yield take(types.REQUEST_START_GAME)
+ yield apply(socket, socket.send, ['/app/lobby/startGame', {}])
+ }
+}
+
+function *lobbySaga(socketConnection) {
+ yield [
+ call(watchLobbyUpdates, socketConnection),
+ call(watchGameStart, socketConnection),
+ call(startGame, socketConnection)
+ ]
+}
+
+export default lobbySaga
bgstack15