Skip to content

Commit

Permalink
Generic lobby (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
francoijs authored and nicolodavis committed Jan 18, 2019
1 parent fb9b41e commit a32d3d5
Show file tree
Hide file tree
Showing 16 changed files with 1,669 additions and 1 deletion.
14 changes: 14 additions & 0 deletions examples/react-web/src/lobby/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import routes from './routes';

// Any other additional setup for this module
export default {
routes,
};
71 changes: 71 additions & 0 deletions examples/react-web/src/lobby/lobby.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

#lobby-view {
display: flex;
flex-direction: column;
width: 100%;
}

.phase-title {
margin-top: 40px;
}

.hidden {
display: none;
}

.phase {
border: none;
outline: none;
}

#game-creation select {
font-size: 14px;
margin-right: 10px;
}

#game-creation span {
margin-right: 10px;
}

#instances td {
list-style: none;
border: 0px solid #eee;
border-top: none;
height: 30px;
line-height: 30px;
text-align: left;
font-size: 14px;
padding-left: 10px;
padding-right: 10px;
}

#instances table {
height: 100%;
width: 500px;
white-space: nowrap;
}

#instances button {
font-size: 12px;
}

.error-msg {
font-size: 12px;
color: red;
margin-top: 10px;
margin-bottom: 10px;
display: block;
}

.buttons button {
margin-left: 10px;
margin-top: 10px;
width: 70px;
}
38 changes: 38 additions & 0 deletions examples/react-web/src/lobby/lobby.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import React from 'react';
import { Lobby } from 'boardgame.io/react';
import { default as BoardTicTacToe } from '../tic-tac-toe/board';
import { default as BoardChess } from '../chess/board';
import { default as GameTicTacToe } from '../tic-tac-toe/game';
import { default as GameChess } from '../chess/game';
import './lobby.css';

GameTicTacToe.minPlayers = 1;
GameTicTacToe.maxPlayers = 2;
GameChess.minPlayers = GameChess.maxPlayers = 2;

const importedGames = [
{ game: GameTicTacToe, board: BoardTicTacToe },
{ game: GameChess, board: BoardChess },
];

const LobbyView = () => (
<div style={{ padding: 50 }}>
<h1>Lobby</h1>

<Lobby
gameServer="localhost:8000"
lobbyServer="localhost:8001"
gameComponents={importedGames}
/>
</div>
);

export default LobbyView;
19 changes: 19 additions & 0 deletions examples/react-web/src/lobby/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import LobbyView from './lobby';

const routes = [
{
path: '/lobby/main',
text: 'Example',
component: LobbyView,
},
];

export default routes;
5 changes: 5 additions & 0 deletions examples/react-web/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import random from './random';
import turnorder from './turnorder';
import ui from './ui';
import threejs from './threejs';
import lobby from './lobby';
import redacted_move from './redacted-move';

const routes = [
Expand Down Expand Up @@ -53,6 +54,10 @@ const routes = [
name: 'Other Frameworks',
routes: threejs.routes,
},
{
name: 'Lobby',
routes: lobby.routes,
},
];

export default routes;
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"lru-cache": "^4.1.1",
"mousetrap": "^1.6.1",
"prop-types": "^15.5.10",
"react-cookies": "^0.1.0",
"react-dragtastic": "^2.4.3",
"redux": "^4.0.0",
"socket.io": "^2.1.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
*/

import { Client } from '../src/client/react.js';
import Lobby from '../src/lobby/react.js';

export { Client };
export { Client, Lobby };
173 changes: 173 additions & 0 deletions src/lobby/connection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

class _LobbyConnectionImpl {
constructor({ server, gameComponents, playerName, playerCredentials }) {
this.gameComponents = gameComponents;
this.playerName = playerName || 'Visitor';
this.playerCredentials = playerCredentials;
this.server = server;
this.gameInstances = [];
}

_baseUrl() {
if (this.server) {
return `http://${this.server}/games`;
}
return '/games';
}

async refresh() {
try {
this.gameInstances.length = 0;
const resp = await fetch(this._baseUrl());
if (resp.status !== 200) {
throw 'HTTP status ' + resp.status;
}
const json = await resp.json();
for (let gameName of json) {
if (!this._getGameComponents(gameName)) continue;
const gameResp = await fetch(this._baseUrl() + '/' + gameName);
const gameJson = await gameResp.json();
for (let inst of gameJson.gameInstances) {
inst.gameName = gameName;
}
this.gameInstances = this.gameInstances.concat(gameJson.gameInstances);
}
} catch (error) {
throw new Error('failed to retrieve list of games (' + error + ')');
}
}

_getGameInstance(gameID) {
for (let inst of this.gameInstances) {
if (inst['gameID'] === gameID) return inst;
}
}

_getGameComponents(gameName) {
for (let comp of this.gameComponents) {
if (comp.game.name == gameName) return comp;
}
}

_findPlayer(playerName) {
for (let inst of this.gameInstances) {
if (inst.players.some(player => player.name === playerName)) return inst;
}
}

async join(gameName, gameID, playerID) {
try {
let inst = this._findPlayer(this.playerName);
if (inst) {
throw 'player has already joined ' + inst.gameID;
}
inst = this._getGameInstance(gameID);
if (!inst) {
throw 'game instance ' + gameID + ' not found';
}
const resp = await fetch(
this._baseUrl() + '/' + gameName + '/' + gameID + '/join',
{
method: 'POST',
body: JSON.stringify({
playerID: playerID,
playerName: this.playerName,
}),
headers: { 'Content-Type': 'application/json' },
}
);
if (resp.status !== 200) throw 'HTTP status ' + resp.status;
const json = await resp.json();
inst.players[Number.parseInt(playerID)].name = this.playerName;
this.playerCredentials = json.playerCredentials;
} catch (error) {
throw new Error('failed to join room ' + gameID + ' (' + error + ')');
}
}

async leave(gameName, gameID) {
try {
let inst = this._getGameInstance(gameID);
if (!inst) throw 'game instance not found';
for (let player of inst.players) {
if (player.name === this.playerName) {
const resp = await fetch(
this._baseUrl() + '/' + gameName + '/' + gameID + '/leave',
{
method: 'POST',
body: JSON.stringify({
playerID: player.id,
playerCredentials: this.playerCredentials,
}),
headers: { 'Content-Type': 'application/json' },
}
);
if (resp.status !== 200) throw 'HTTP status ' + resp.status;
delete player.name;
delete this.playerCredentials;
return;
}
}
throw 'player not found in room';
} catch (error) {
throw new Error('failed to leave room ' + gameID + ' (' + error + ')');
}
}

async disconnect() {
let inst = this._findPlayer(this.playerName);
if (inst) {
await this.leave(inst.gameName, inst.gameID);
}
this.gameInstances = [];
this.playerName = 'Visitor';
}

async create(gameName, numPlayers) {
try {
const comp = this._getGameComponents(gameName);
if (!comp) throw 'game not found';
if (
numPlayers < comp.game.minPlayers ||
numPlayers > comp.game.maxPlayers
)
throw 'invalid number of players ' + numPlayers;
const resp = await fetch(this._baseUrl() + '/' + gameName + '/create', {
method: 'POST',
body: JSON.stringify({
numPlayers: numPlayers,
}),
headers: { 'Content-Type': 'application/json' },
});
if (resp.status !== 200) throw 'HTTP status ' + resp.status;
} catch (error) {
throw new Error(
'failed to create room for ' + gameName + ' (' + error + ')'
);
}
}
}

/**
* LobbyConnection
*
* Lobby model.
*
* @param {string} server - '<host>:<port>' of the server.
* @param {Array} gameComponents - A map of Board and Game objects for the supported games.
* @param {string} playerName - The name of the player.
* @param {string} playerCredentials - The credentials currently used by the player, if any.
*
* Returns:
* A JS object that synchronizes the list of running game instances with the server and provides an API to create/join/start instances.
*/
export function LobbyConnection(opts) {
return new _LobbyConnectionImpl(opts);
}
Loading

0 comments on commit a32d3d5

Please sign in to comment.