Skip to content

Commit

Permalink
Connection allows swapping websocket
Browse files Browse the repository at this point in the history
If your client loses connection, rejoin as the same name and rejoin the game #4
Swapping in the new ws at the right moment is a little fragile
(it will crash if you send() a non-message while clinet is disconnected)
I was careful to preserve client setup of colors and help
  • Loading branch information
darthwalsh committed Feb 1, 2019
1 parent 7af6607 commit 7bfb5ed
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 28 deletions.
2 changes: 1 addition & 1 deletion cardspec.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ describe("cards", () => {

let interactionIndex = 0;

const game = new Game(console.error);
const game = new Game();

// @ts-ignore create a mock connection
const p = new Player({
Expand Down
46 changes: 40 additions & 6 deletions server/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ class Connection {
constructor(ws) {
this.ws = ws;
this.name = "NoName";
this.sentChoices = null;

this.messageHandlers = {};
this.messageHandlers.choice = data => this.onChoice(data);
this.messageHandlers.name = data => this.name = data;
this.messageHandlers = {
choice: data => this.onChoice(data),
name: data => this.name = data,
};

this.initListeners();
}

initListeners() {
this.ws.addEventListener("message", data => {
const o = JSON.parse(data.data);
const type = Object.keys(o)[0];
Expand All @@ -17,12 +23,36 @@ class Connection {
}
handler(content);
});

this.ws.addEventListener("close", () => {
this.ws = null;
});
}

// TODO(E2E) should test disconnecting both during your turn, and during somebody else's turn
newConnection(ws) {
ws.onmessage = null;
ws.onclose = null;
this.ws = ws;
this.initListeners();
}

resendChoices() {
if (this.onChoice) {
this.ws.send(this.sentChoices);
}
}

/**
* @param {object} data
*/
send(data) {
if (!this.ws) {
if (typeof data.message !== "undefined") {
return;
}
throw new Error("Player is disconnected");
}
if (data.choices) {
throw new Error("Use sendChoices instead!");
}
Expand All @@ -34,17 +64,21 @@ class Connection {
* @param {function} handleChoice
*/
sendChoice(choices, handleChoice) {
if (!this.ws) {
throw new Error("Player is disconnected");
}
if (!choices.length) {
console.error("EMPTY CHOICE!!!");
throw new Error("EMPTY CHOICE!!!");
}
if (this.onChoice) {
console.error("onChoice wasn't empty!!!");
throw new Error("onChoice wasn't empty!!!");
}
this.onChoice = choice => {
this.onChoice = null;
handleChoice(choice);
};
this.ws.send(JSON.stringify({choices: choices}));
this.sentChoices = JSON.stringify({choices: choices});
this.ws.send(this.sentChoices);
}
}

Expand Down
37 changes: 19 additions & 18 deletions server/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,33 @@ const Player = require("./player").Player;
const Connection = require("./connection").Connection; // Useful for VS Code type info

class Game {
constructor(log) {
this.log = log;
constructor() {
this.store = new Store();
/**
* @type {Object.<string, Player>}
*/
this.players = {};
this.trash = [];
this.started = false;
}

canStart() {
// TODO Game of 1 is only fun when debugging
return Object.keys(this.players).length >= 1;
/**
* @param {Player[]} ps
*/
initClients(ps) {
const included = this.store.included.map(c => c.name);
ps.forEach(p => p.send({included}));

const colors = this.store.getAllCards().reduce((o, c) => {
o[c.name] = c.color;
o["Buy: " + c.name] = c.color;
return o;
}, {});
ps.forEach(p => p.send({colors}));
}

start(debugMode) {
this.started = true;
const ps = this.allPlayers();

if (debugMode) {
Expand All @@ -29,15 +40,7 @@ class Game {
this.allLog("!!!!!!\n" + ps[0].name + " IS CHEATING\n!!!!!!");
}

const included = this.store.included.map(c => c.name);
ps.forEach(p => p.send({included}));

const colors = this.store.getAllCards().reduce((o, c) => {
o[c.name] = c.color;
o["Buy: " + c.name] = c.color;
return o;
}, {});
ps.forEach(p => p.send({colors}));
this.initClients(ps);

let turn = Math.floor(Math.random() * ps.length);
const nextTurn = () => {
Expand Down Expand Up @@ -68,7 +71,6 @@ class Game {
*/
addPlayer(connection) {
const name = connection.name;
this.log(name + " joined");

const player = new Player(connection, this);

Expand All @@ -88,9 +90,8 @@ class Game {
this.start(data.debugMode);
};

connection.ws.addEventListener("close", () => { // TODO(NODE) move to Connection.js
this.log(player.name + " disconnected");
delete this.players[player.name];
connection.ws.addEventListener("close", () => {
this.allLog(player.name + " disconnected");
});
}

Expand Down
22 changes: 19 additions & 3 deletions server/lobby.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,37 @@ class Lobby {
* @param {Connection} connection
*/
sendLobby(connection) {
const choices = ["Refresh", "New Game", ...Object.keys(this.games)];
const choices = [
"Refresh",
"New Game",
...Object.keys(this.games).filter(g => {
const game = this.games[g];
const player = game.players[connection.name];
return (player && !player.connection.ws) || !game.started;
})];
connection.sendChoice(choices, choice => {
let game;
switch (choice) {
case "Refresh":
this.sendLobby(connection);
return;
case "New Game":
game = new Game(console.log);
game = new Game();
this.games[`${connection.name}'s game`] = game;
break;
}
game = game || this.games[choice];
delete connection.messageHandlers.name;
game.addPlayer(connection);

const existingPlayer = game.players[connection.name];
if (existingPlayer) {
existingPlayer.connection.newConnection(connection.ws);
game.initClients([existingPlayer]);
existingPlayer.connection.resendChoices();
game.allLog(`${connection.name} rejoined`);
} else {
game.addPlayer(connection);
}
});
}
}
Expand Down

0 comments on commit 7bfb5ed

Please sign in to comment.