summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/gameList.js20
-rw-r--r--frontend/src/components/gamesList.js17
-rw-r--r--frontend/src/components/playerList.js16
-rw-r--r--frontend/src/containers/gameBrowser.js13
-rw-r--r--frontend/src/containers/lobby.js32
-rw-r--r--frontend/src/redux/games.js38
-rw-r--r--frontend/src/redux/players.js57
-rw-r--r--frontend/src/routes.js4
-rw-r--r--frontend/src/sagas/gameBrowser.js40
-rw-r--r--frontend/src/sagas/usernameChoice.js4
-rw-r--r--frontend/src/schemas/games.js4
11 files changed, 164 insertions, 81 deletions
diff --git a/frontend/src/components/gameList.js b/frontend/src/components/gameList.js
new file mode 100644
index 00000000..c8720b26
--- /dev/null
+++ b/frontend/src/components/gameList.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import { Flex } from 'reflexbox'
+import { Text, Space, Button } from 'rebass'
+
+const GameList = (props) => (
+ <div>
+ {props.games.map((game, index) => {
+
+ const joinGame = () => props.joinGame(game.get('id'))
+
+ return (<Flex key={index}>
+ <Text>{game.get('name')}</Text>
+ <Space auto />
+ <Button onClick={joinGame}>Join</Button>
+ </Flex>)
+ })}
+ </div>
+)
+
+export default GameList
diff --git a/frontend/src/components/gamesList.js b/frontend/src/components/gamesList.js
deleted file mode 100644
index 0519e7d6..00000000
--- a/frontend/src/components/gamesList.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react'
-import { Flex } from 'reflexbox'
-import { Text, Space } from 'rebass'
-
-const GameBrowser = (props) => (
- <div>
- {props.games.valueSeq().map((game, index) => {
- return (<Flex key={index}>
- <Text>{game.get('name')}</Text>
- <Space auto />
- <a href="#">Join</a>
- </Flex>)
- })}
- </div>
-)
-
-export default GameBrowser
diff --git a/frontend/src/components/playerList.js b/frontend/src/components/playerList.js
new file mode 100644
index 00000000..30384d53
--- /dev/null
+++ b/frontend/src/components/playerList.js
@@ -0,0 +1,16 @@
+import React from 'react'
+import { Flex } from 'reflexbox'
+import { Text } from 'rebass'
+
+const PlayerList = (props) => (
+ <div>
+ {props.players.map((player, index) => {
+ return (<Flex key={index}>
+ <Text>{player.get('displayName')}</Text>
+ <Text>({player.get('username')})</Text>
+ </Flex>)
+ })}
+ </div>
+)
+
+export default PlayerList
diff --git a/frontend/src/containers/gameBrowser.js b/frontend/src/containers/gameBrowser.js
index 8c48f071..6a6b3ce2 100644
--- a/frontend/src/containers/gameBrowser.js
+++ b/frontend/src/containers/gameBrowser.js
@@ -6,9 +6,9 @@ import {
Text
} from 'rebass'
import { Flex } from 'reflexbox'
-import GamesList from '../components/gamesList'
+import GameList from '../components/gameList'
-class App extends Component {
+class GameBrowser extends Component {
createGame = (e) => {
e.preventDefault()
@@ -33,7 +33,7 @@ class App extends Component {
<Text><b>Username:</b> {this.props.currentPlayer.get('displayName')}</Text>
<Space x={1} />
</Flex>
- <GamesList games={this.props.games} />
+ <GameList games={this.props.games} joinGame={this.props.joinGame}/>
</div>
)
}
@@ -41,14 +41,15 @@ class App extends Component {
import { getCurrentPlayer } from '../redux/players'
import { getAllGames, actions } from '../redux/games'
+
const mapStateToProps = (state) => ({
currentPlayer: getCurrentPlayer(state),
games: getAllGames(state)
})
-
const mapDispatchToProps = {
- createGame: actions.createGame
+ createGame: actions.requestCreateGame,
+ joinGame: actions.requestJoinGame
}
-export default connect(mapStateToProps, mapDispatchToProps)(App)
+export default connect(mapStateToProps, mapDispatchToProps)(GameBrowser)
diff --git a/frontend/src/containers/lobby.js b/frontend/src/containers/lobby.js
new file mode 100644
index 00000000..c36c3263
--- /dev/null
+++ b/frontend/src/containers/lobby.js
@@ -0,0 +1,32 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import { List } from 'immutable'
+import { Text } from 'rebass'
+import PlayerList from '../components/playerList'
+
+class Lobby extends Component {
+
+ render() {
+ return (
+ <div>
+ {this.props.currentGame && <Text>{this.props.currentGame.name}</Text>}
+ <PlayerList players={this.props.players}/>
+ </div>
+ )
+ }
+}
+
+import { getPlayers } from '../redux/players'
+import { getCurrentGame } from '../redux/games'
+
+const mapStateToProps = (state) => {
+ const game = getCurrentGame(state)
+ return ({
+ currentGame: game,
+ players: game ? getPlayers(state, game.get('players')) : List()
+ })
+}
+
+const mapDispatchToProps = {}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Lobby)
diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js
index 4115323c..5ad6f8d2 100644
--- a/frontend/src/redux/games.js
+++ b/frontend/src/redux/games.js
@@ -1,29 +1,39 @@
-import { Map } from 'immutable'
+import {fromJS} from 'immutable'
export const types = {
- CREATE_OR_UPDATE_GAMES: 'GAME/CREATE_OR_UPDATE_GAMES',
- ENTER_GAME: 'GAME/ENTER_GAME',
- JOIN_GAME: 'GAME/JOIN_GAME',
- CREATE_GAME: 'GAME/CREATE_GAME',
+ UPDATE_GAMES: 'GAME/UPDATE_GAMES',
+ REQUEST_CREATE_GAME: 'GAME/REQUEST_CREATE_GAME',
+ REQUEST_JOIN_GAME: 'GAME/REQUEST_JOIN_GAME',
+ ENTER_LOBBY: 'GAME/ENTER_LOBBY',
}
export const actions = {
- createOrUpdateGame: (games) => ({ type: types.CREATE_OR_UPDATE_GAMES, games }),
- enterGame: (username) => ({ type: types.ENTER_GAME, username }),
- joinGame: (id) => ({ type: types.JOIN_GAME, id }),
- createGame: (name) => ({ type: types.CREATE_GAME, name }),
+ updateGames: (games) => ({ type: types.UPDATE_GAMES, games }),
+ requestJoinGame: (id) => ({ type: types.REQUEST_JOIN_GAME, id }),
+ requestCreateGame: (name) => ({ type: types.REQUEST_CREATE_GAME, name }),
+ enterLobby: (lobby) => ({ type: types.ENTER_LOBBY, lobby }),
}
-
-const initialState = Map({})
+const initialState = fromJS({
+ all: {},
+ current: ''
+})
export default (state = initialState, action) => {
switch (action.type) {
- case types.CREATE_OR_UPDATE_GAMES:
- return state.mergeDeep(action.games)
+ case types.UPDATE_GAMES:
+ return state.setIn(['all'], state.get('all').mergeDeep(action.games))
+ case types.ENTER_LOBBY:
+ return state.set('current', action.lobby.get('id'))
default:
return state
}
}
-export const getAllGames = state => state.get('games')
+const getState = globalState => globalState.get('games')
+
+export const getAllGamesById = globalState => getState(globalState).get('all')
+export const getAllGames = globalState => getAllGamesById(globalState).toList()
+export const getGame = (globalState, id) => getAllGamesById(globalState).get(id)
+export const getCurrentGameId = globalState => getState(globalState).get('current')
+export const getCurrentGame = globalState => getGame(globalState, getCurrentGameId(globalState))
diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js
index 967d93b6..85b5d042 100644
--- a/frontend/src/redux/players.js
+++ b/frontend/src/redux/players.js
@@ -1,23 +1,26 @@
-import { fromJS, Map } from 'immutable'
+import {fromJS, Map, Set} from 'immutable'
export const types = {
- SET_USERNAME: 'USER/SET_USERNAME',
- SET_USERNAMES: 'USER/SET_USERNAMES',
- CHOOSE_USERNAME: 'USER/CHOOSE_USERNAME'
+ REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME',
+ SET_CURRENT_PLAYER: 'USER/SET_CURRENT_PLAYER',
+ UPDATE_PLAYERS: 'USER/UPDATE_PLAYERS'
}
export const actions = {
- setUsername: (username, displayName, index) => ({
- type: types.SET_USERNAME,
- username,
- index,
- displayName
+ chooseUsername: (username) => ({
+ type: types.REQUEST_CHOOSE_USERNAME,
+ username
+ }),
+ setCurrentPlayer: (player) => ({
+ type: types.SET_CURRENT_PLAYER,
+ player
+ }),
+ updatePlayers: (players) => ({
+ type: types.UPDATE_PLAYERS,
+ players
}),
- setPlayers: (players) => ({ type: types.SET_USERNAMES, players }),
- chooseUsername: (username) => ({ type: types.CHOOSE_USERNAME, username }),
}
-
const initialState = fromJS({
all: {},
current: ''
@@ -25,20 +28,28 @@ const initialState = fromJS({
export default (state = initialState, action) => {
switch (action.type) {
- case types.SET_USERNAME:
- const user = fromJS({
- username: action.username,
- displayName: action.displayName,
- index: action.index,
- })
- return state.setIn(['all', user.get('username')], user).set('current', user.get('username'))
- case types.SET_USERNAMES:
+ case types.SET_CURRENT_PLAYER:
+ const player = action.player
+ const username = player.get('username')
+ return state.setIn(['all', username], player).set('current', username)
+ case types.UPDATE_PLAYERS:
return state.setIn(['all'], state.get('all').mergeDeep(action.players))
default:
return state
}
}
-export const getCurrentPlayerUserName = state => state.get('players').get('current')
-export const getAllPlayers = state => state.get('players').get('all')
-export const getCurrentPlayer = (state) => getAllPlayers(state).get(getCurrentPlayerUserName(state), Map())
+const getState = globalState => globalState.get('players')
+
+function keyIn(...keys) {
+ return (v, k) => Set(keys).has(k)
+}
+
+export const getAllPlayersByUsername = globalState => getState(globalState).get('all')
+export const getAllPlayers = globalState => getAllPlayersByUsername(globalState).toList()
+export const getPlayers = (globalState, usernames) => getAllPlayersByUsername(globalState)
+ .filter(keyIn(usernames))
+ .toList()
+export const getCurrentPlayerUsername = globalState => getState(globalState).get('current')
+export const getCurrentPlayer = globalState => getAllPlayersByUsername(globalState)
+ .get(getCurrentPlayerUsername(globalState), Map())
diff --git a/frontend/src/routes.js b/frontend/src/routes.js
index 2e95abda..16800736 100644
--- a/frontend/src/routes.js
+++ b/frontend/src/routes.js
@@ -14,6 +14,7 @@ export const makeSagaRoutes = wsConnection => ({
import { HomeLayout, LobbyLayout } from './layouts'
import HomePage from './containers/home'
import GameBrowser from './containers/gameBrowser'
+import Lobby from './containers/lobby'
import Error404 from './components/errors/Error404'
export const routes = [
@@ -26,7 +27,8 @@ export const routes = [
path: '/',
component: LobbyLayout,
childRoutes: [
- { path: '/games', component: GameBrowser }
+ { path: '/games', component: GameBrowser },
+ { path: '/lobby', component: Lobby }
]
},
{
diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js
index 2dbd4040..596da428 100644
--- a/frontend/src/sagas/gameBrowser.js
+++ b/frontend/src/sagas/gameBrowser.js
@@ -1,10 +1,10 @@
-import { call, put, take } from 'redux-saga/effects'
-import { eventChannel } from 'redux-saga'
+import { call, put, take, apply } from 'redux-saga/effects'
+import { eventChannel} from 'redux-saga'
import { fromJS } from 'immutable'
import { push } from 'react-router-redux'
import { normalize } from 'normalizr'
-import gameSchema from '../schemas/games'
+import { game, gameList } from '../schemas/games'
import { actions as gameActions, types } from '../redux/games'
import { actions as playerActions } from '../redux/players'
@@ -13,12 +13,12 @@ function gameBrowserChannel(socket) {
return eventChannel(emit => {
const makeHandler = type => event => {
- const response = fromJS(JSON.parse(event.body))
+ const response = JSON.parse(event.body)
emit({ type, response })
}
- const newGame = socket.subscribe('/topic/games', makeHandler(types.CREATE_OR_UPDATE_GAMES))
- const joinGame = socket.subscribe('/user/queue/lobby/joined', makeHandler(types.JOIN_GAME))
+ 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()
@@ -35,15 +35,16 @@ export function *watchGames({ socket }) {
const { type, response } = yield take(socketChannel)
switch (type) {
- case types.CREATE_OR_UPDATE_GAMES:
- const normalizedResponse = normalize(response.toJS(), gameSchema)
- yield put(playerActions.setPlayers(fromJS(normalizedResponse.entities.players)))
- yield put(gameActions.createOrUpdateGame(fromJS(normalizedResponse.entities.games)))
+ case types.UPDATE_GAMES:
+ const normGameList = normalize(response, gameList)
+ yield put(playerActions.updatePlayers(fromJS(normGameList.entities.players)))
+ yield put(gameActions.updateGames(fromJS(normGameList.entities.games)))
break
- case types.JOIN_GAME:
- yield put(gameActions.joinGame(response))
+ case types.ENTER_LOBBY:
+ const normGame = normalize(response, game)
+ yield put(gameActions.enterLobby(fromJS(normGame.entities.games[normGame.result])))
socketChannel.close()
- yield put(push(`/lobby/${response.id}`))
+ yield put(push('/lobby'))
break
default:
console.error('Unknown type')
@@ -55,15 +56,22 @@ export function *watchGames({ socket }) {
}
export function *createGame({ socket }) {
- const { name } = yield take(types.CREATE_GAME)
+ const { name } = yield take(types.REQUEST_CREATE_GAME)
- socket.send("/app/lobby/create", JSON.stringify({ gameName: name }), {})
+ yield apply(socket, socket.send, ["/app/lobby/create", JSON.stringify({ gameName: name }), {}])
+}
+
+export function *joinGame({ socket }) {
+ const { id } = yield take(types.REQUEST_JOIN_GAME)
+
+ yield apply(socket, socket.send, ["/app/lobby/join", JSON.stringify({ gameId: id }), {}])
}
export function *gameBrowserSaga(socketConnection) {
yield [
call(watchGames, socketConnection),
- call(createGame, socketConnection)
+ call(createGame, socketConnection),
+ call(joinGame, socketConnection)
]
}
diff --git a/frontend/src/sagas/usernameChoice.js b/frontend/src/sagas/usernameChoice.js
index da720e9f..ad5b5341 100644
--- a/frontend/src/sagas/usernameChoice.js
+++ b/frontend/src/sagas/usernameChoice.js
@@ -17,13 +17,13 @@ function usernameValidationChannel(socket) {
function *usernameValidation({ socket }) {
const usernameChannel = yield call(usernameValidationChannel, socket)
const user = yield take(usernameChannel)
- yield put(actions.setUsername(user.get('username'), user.get('displayName'), user.get('index')))
+ yield put(actions.setCurrentPlayer(user))
usernameChannel.close()
yield put(push('/games'))
}
function *sendUsername({ socket }) {
- const { username } = yield take(types.CHOOSE_USERNAME)
+ const { username } = yield take(types.REQUEST_CHOOSE_USERNAME)
yield socket.send('/app/chooseName', JSON.stringify({
playerName: username
diff --git a/frontend/src/schemas/games.js b/frontend/src/schemas/games.js
index 180c9e69..f7a9ffb8 100644
--- a/frontend/src/schemas/games.js
+++ b/frontend/src/schemas/games.js
@@ -4,8 +4,8 @@ const player = new schema.Entity('players', {}, {
idAttribute: 'username'
})
-const game = new schema.Entity('games', {
+export const game = new schema.Entity('games', {
players: [ player ]
})
-export default [ game ]
+export const gameList = [ game ]
bgstack15