diff --git a/README.txt b/README.txt
index 5a2831c..bc3e548 100644
--- a/README.txt
+++ b/README.txt
@@ -13,21 +13,18 @@ A victory state can be stored by taking a snapshot of the current stack.
Icons from [https://game-icons.net](https://game-icons.net)
## TODO
-- win declare/add/remove
-- no more guesses, send stack and replay
+- win declare
+- replay stack
- countdown skip
-- move guess and move logic out of server (clean up server file)
-
+- slide arrows
+- chat box
- no cancel from name prompt
- restore state on join
-- walls algorigthm
-
-- limit concurrent players, make sure connections are closed, clean up empty rooms
-- cookie room link, add to all messages, namespace them
+- limit concurrent players, make sure connections are closed
- move websocket server to /core
- dynamic socket server resolution
-- namespace server to /ricochet
+- walls and winstate algorithm
- tutorial
- donate link
diff --git a/client/connection.js b/client/connection.js
index f7d5a2f..a877f8b 100644
--- a/client/connection.js
+++ b/client/connection.js
@@ -18,6 +18,10 @@ const Connection = function() {
this.ws.send(JSON.stringify({ type: 'skip' }));
});
+ document.addEventListener('L-solve', (evt) => {
+ this.ws.send(JSON.stringify({ type: 'solve', rawBody: evt.detail }));
+ });
+
document.addEventListener('L-start', () => {
this.ws.send(JSON.stringify({ type: 'start' }));
});
@@ -71,6 +75,7 @@ Connection.prototype.onReceiveMessage = function({ data }) {
switch (msg.type) {
case 'connected': eventName = 'G-connected'; break;
+ case 'countdown': eventName = 'G-countdown'; break;
case 'guess': eventName = 'G-guess'; break;
case 'players': eventName = 'G-players'; break;
case 'robots': eventName = 'G-robots'; break;
diff --git a/client/controls.js b/client/controls.js
index 3c32983..82f7d43 100644
--- a/client/controls.js
+++ b/client/controls.js
@@ -11,6 +11,7 @@ const Controls = function() {
// document.addEventListener('L-undo', this.msgUndo.bind(this));
// document.addEventListener('G-guess', this.msgGuess.bind(this));
+ document.addEventListener('G-countdown', this.msgCountdown.bind(this));
document.addEventListener('G-players', this.msgPlayers.bind(this));
document.addEventListener('G-skip', this.msgSkip.bind(this));
document.addEventListener('G-start', this.msgStart.bind(this));
@@ -22,6 +23,7 @@ const Controls = function() {
document.getElementById('controls-skip').addEventListener('click', this.onClickSkip.bind(this));
document.getElementById('controls-start').addEventListener('click', this.onClickStart.bind(this));
document.getElementById('controls-stop').addEventListener('click', this.onClickStop.bind(this));
+ document.getElementById('controls-submit').addEventListener('click', this.onClickSubmit.bind(this));
document.getElementById('controls-undo').addEventListener('click', this.onClickUndo.bind(this));
document.getElementById('controls-walls').addEventListener('click', this.onClickWalls.bind(this));
}
@@ -77,9 +79,11 @@ const Controls = function() {
//===== Message handlers
-// Controls.prototype.msgConnected = function() {
-// this.showWaiting();
-// };
+Controls.prototype.msgCountdown = function(evt) {
+ // this.showWaiting();
+ console.error(evt);
+ alert("COUNTDOWN RECEIVED");
+};
Controls.prototype.msgStack = function(evt) {
const robots = evt.detail.reduce((acc, { id }) => acc.has(id) ? acc : acc.add(id), new Set());
@@ -162,6 +166,10 @@ Controls.prototype.onClickStop = function() {
this.dispatch('L-stop');
};
+Controls.prototype.onClickSubmit = function() {
+ this.dispatch('L-submit');
+};
+
Controls.prototype.onClickUndo = function() {
this.dispatch('L-undo');
};
diff --git a/client/stack.js b/client/stack.js
index 2df84e3..0ac75c6 100644
--- a/client/stack.js
+++ b/client/stack.js
@@ -5,6 +5,7 @@ const Stack = function() {
this.moves = [];
document.addEventListener('L-arrow', this.msgArrow.bind(this));
+ document.addEventListener('L-submit', this.msgSubmit.bind(this));
document.addEventListener('L-undo', this.msgUndo.bind(this));
document.addEventListener('L-reset', this.msgReset.bind(this));
@@ -47,6 +48,13 @@ Stack.prototype.msgReset = function() {
document.dispatchEvent(evtStack);
};
+Stack.prototype.msgSubmit = function() {
+ this.moves = this.getInitialPositions();
+
+ const evtSolve = new CustomEvent('L-solve', { detail: this.moves });
+ document.dispatchEvent(evtSolve);
+};
+
Stack.prototype.msgUndo = function() {
this.moves.pop();
diff --git a/grid.css b/grid.css
index b5297b0..c94dc72 100644
--- a/grid.css
+++ b/grid.css
@@ -59,7 +59,7 @@
.content-arrows {
position: absolute;
transition: left 0.4s cubic-bezier(0,1,.5,1), top 0.4s cubic-bezier(0,1,.5,1);
- z-index: 1;
+ z-index: 5;
}
.content-arrow {
@@ -68,6 +68,7 @@
position: absolute;
text-align: center;
user-select: none;
+ z-index: 5;
}
.content-arrow:hover {
diff --git a/index.html b/index.html
index b50aeb4..1ac1932 100644
--- a/index.html
+++ b/index.html
@@ -81,6 +81,8 @@
diff --git a/package.json b/package.json
index 0ea4626..0678fd2 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
- "start": "node server.js"
+ "start": "node socket/server.js"
},
"author": "",
"license": "ISC",
diff --git a/server.js b/server.js
deleted file mode 100644
index 1e9eec0..0000000
--- a/server.js
+++ /dev/null
@@ -1,130 +0,0 @@
-const WebSocket = require('ws');
-const url = require('url');
-const uuid = require('node-uuid');
-
-const jsDir = `${__dirname}/server`;
-const Game = require(`${jsDir}/game.js`);
-
-const wss = new WebSocket.Server({ port: 8080 });
-
-const DEBUG = true;
-
-const G = new Game();
-
-// Global, for now. Is there a need for an instance? Ben 052220
-const Server = {
- games: {},
-
- messageOne: (ws, message) => {
- DEBUG && console.log(`Sending to only ${ws.id}:`);
- DEBUG && console.log(message);
-
- ws.send(JSON.stringify(message));
- },
-
- messageOthers: (ws, message) => {
- DEBUG && console.log(`Sending to other client(s):`);
- DEBUG && console.log(message);
-
- wss.clients.forEach((client) => {
- if (client !== ws && client.readyState === WebSocket.OPEN) {
- client.send(JSON.stringify(message));
- }
- });
- },
-
- messageAll: (message) => {
- DEBUG && console.log(`Sending to all ${wss.clients.size} client(s):`);
- DEBUG && console.log(message);
-
- wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN) {
- client.send(JSON.stringify(message));
- }
- });
- },
-
- onDisconnect: (ws) => {
- DEBUG && console.log("Disconnected:");
- DEBUG && console.log(ws.id);
-
- G.removePlayer(ws.id);
- Server.messageOthers(ws, { type: 'players', body: G.getPlayers() });
- },
-
- onConnect: (ws, req) => {
- ws.id = uuid.v4();
- ws.on('message', Server.onMessage.bind(null, ws));
- ws.on('close', Server.onDisconnect.bind(null, ws));
-
- const query = url.parse(req.url, true).query;
- const santizedName = (query.name || 'Unknown').replace(/[^\w ]/g, '');
-
- DEBUG && console.log("Connected:");
- DEBUG && console.log (`${santizedName} ${ws.id} via ${req.url}`);
- G.addPlayer(ws.id, santizedName);
-
- Server.messageAll({ type: 'players', body: G.getPlayers() });
- Server.messageOne(ws, { type: 'walls', body: G.getWalls()});
- Server.messageOne(ws, { type: 'robots', body: G.getRobots()});
- Server.messageOne(ws, { type: 'winstate', body: G.getWinState()});
- Server.messageOne(ws, { type: 'connected', body: ws.id});
- },
-
- onMessage: (ws, json) => {
- const message = JSON.parse(json);
-
- DEBUG && console.log('Received message: ');
- DEBUG && console.log(message);
-
- if (!message.type) {
- DEBUG && console.warn("Unprocessable message: ")
- DEBUG && console.warn(message);
- return;
- }
-
- switch (message.type) {
- case 'guess':
- const santizedGuess = message.rawBody.moves * 1;
-
- santizedGuess && Server.messageAll({ type: 'guess', guess: santizedGuess, id: ws.id });
-
- G.onGuess(santizedGuess).then(Server.messageAll.bind(null, { type: 'solve', id: ws.id }));
- break;
- // case 'move':
- // const sanitizedRobotId = message.rawBody.id.replace(/[^0-9a-zA-Z]/g, '');
- // const sanitizedI = message.rawBody.i * 1;
- // const sanitizedJ = message.rawBody.j * 1;
-
- // const body = {
- // i: sanitizedI,
- // j: sanitizedJ,
- // id: sanitizedRobotId
- // };
-
- // Server.messageOthers(ws, { type: 'move', body });
- // break;
- case 'robots':
- Server.messageAll({ type: 'robots', body: G.getRobots()});
- break;
- case 'skip':
- Server.messageAll({ type: 'start' });
- break;
- case 'start':
- Server.messageAll({ type: 'start' });
- break;
- case 'stop':
- Server.messageAll({ type: 'stop' });
- break;
- case 'walls':
- Server.messageAll({ type: 'walls', body: G.getWalls()});
- break;
- default:
- console.warn("Unknown message type: ", message.head.type)
- }
- },
-};
-
-wss.on('connection', Server.onConnect);
-
-console.log("Websocket server listening on :8080");
\ No newline at end of file
diff --git a/server/game.js b/server/game.js
deleted file mode 100644
index f8762f3..0000000
--- a/server/game.js
+++ /dev/null
@@ -1,124 +0,0 @@
-const uuid = require('node-uuid');
-
-const players = {};
-
-const Game = function() {
- this.id = uuid.v4();
- this.countdownTimer = null;
- this.guess = Infinity;
- this.squaresPerSide = 20;
-
- const robots = ['#E00000', '#00C000', '#0000FF', '#00C0C0', '#F000F0'];
- const icons = ['assets/comet.svg', 'assets/moon.svg', 'assets/planet.svg', 'assets/rocket.svg', 'assets/spacesuit.svg'];
- // spider.svg, ufo.svg
-
- const gen = () => Math.floor(Math.random() * this.squaresPerSide);
- this.TEMP_ROBOTS = robots.map((color, idx) => ({ i: gen(), j: gen(), color, id: uuid.v4(), icon: icons[idx] }));
-}
-
-Game.prototype.addPlayer = function(id, name) {
- if (!players[id]) {
- players[id] = name;
- }
-}
-
-Game.prototype.removePlayer = function(id) {
- players[id] = undefined;
- delete players[id];
-}
-
-Game.prototype.getPlayers = function() {
- return players;
-}
-
-Game.prototype.getRobots = function() {
- return this.TEMP_ROBOTS;
-}
-
-Game.prototype.getWalls = function() {
- // Edge IDs are of the form [i1-j1-i2-j2]. Top left is 0, 0.
-
- // Leave here for testing.
- // return [
- // "1-9-1-10",
- // "9-1-10-1",
- // "9-19-10-19",
- // "19-9-19-10"
- // ];
-
- // console.log("Generating walls.");
-
- // Squares per side has quadratic relationship with wall/corner requirements.
- const numberOfCorners = Math.ceil(Math.pow((this.squaresPerSide / 10), 2));
- const numberOfWalls = Math.ceil(Math.pow((this.squaresPerSide / 5), 2));
-
- const gen = () => Math.floor(Math.random() * this.squaresPerSide);
- const edges = [];
-
- // DO NUMBER OF CORNERS FIRST AFTER TESTING
- for (let n = 0; n < numberOfWalls; n++) {
- const ri = gen();
- const rj = gen();
-
- const isHorizontal = Math.random() < 0.5;
- const isBackward = Math.random() < 0.5;
-
- let i1, j1, i2, j2;
-
- if (isHorizontal) {
- i1 = isBackward ? ri - 1 : ri;
- i2 = isBackward ? ri : ri + 1;
-
- j1 = rj;
- j2 = rj;
- } else {
- i1 = ri;
- i2 = ri;
-
- j1 = isBackward ? rj - 1 : rj;
- j2 = isBackward ? rj : rj + 1;
- }
-
- const edge = `${i1}-${j1}-${i2}-${j2}`;
-
- if (edges.includes(edge)) {
- n--;
- } else {
- edges.push(edge);
- }
- }
-
- return edges;
-};
-
-Game.prototype.getWinState = function() {
- // const gen = () => Math.floor(Math.random() * this.squaresPerSide);
-
- return { i: 0, j: 0, id: this.TEMP_ROBOTS[0].id };
-};
-
-Game.prototype.onGuess = function(guess) {
- return new Promise((resolve, reject) => {
- const timeIsUp = () => {
- this.guess = Infinity;
- resolve();
- };
-
- if (guess < 1) {
- reject(`${guess} is less than 1.`);
- return;
- }
-
- if (guess >= this.guess) {
- reject(`${guess} is greater than ${this.guess}.`);
- return;
- }
-
- this.guess = guess;
-
- clearTimeout(this.countdownTimer);
- this.countdownTimer = setTimeout(timeIsUp, 5 * 1000);
- });
-};
-
-module.exports = Game;
\ No newline at end of file
diff --git a/server/ricochet.js b/server/ricochet.js
new file mode 100644
index 0000000..eada038
--- /dev/null
+++ b/server/ricochet.js
@@ -0,0 +1,227 @@
+const uuid = require('node-uuid');
+
+const UrlParser = require('url');
+
+///////////////
+
+
+// REMEMBER THIS WILL BE IN A COMPLETELY SEPARATE DIRECTORY
+
+
+///////////////
+
+
+const Ricochet = function({ messenger }) {
+ this.messenger = messenger;
+
+ console.log("HELLO RICOCHET")
+
+ // const players = {};
+
+ // this.id = uuid.v4();
+ // this.countdownTimer = null;
+ // this.guess = Infinity;
+ // this.squaresPerSide = 20;
+ // this.winningStack;
+
+ // const robots = ['#E00000', '#00C000', '#0000FF', '#00C0C0', '#F000F0'];
+ // const icons = ['assets/comet.svg', 'assets/moon.svg', 'assets/planet.svg', 'assets/rocket.svg', 'assets/spacesuit.svg'];
+ // // spider.svg, ufo.svg
+
+ // const gen = () => Math.floor(Math.random() * this.squaresPerSide);
+ // this.TEMP_ROBOTS = robots.map((color, idx) => ({ i: gen(), j: gen(), color, id: uuid.v4(), icon: icons[idx] }));
+};
+
+Ricochet.prototype.onConnect = function(ws, req) {
+ console.log("CONNECTED TO RICOCHET INSTANCE")
+ // const query = url.query;
+ // const santizedName = (query.name || 'Unknown').replace(/[^\w ]/g, '');
+
+ // DEBUG && console.log("Connected:");
+ // DEBUG && console.log (`${santizedName} ${ws.id} via ${req.url}`);
+ // G.addPlayer(ws.id, santizedName);
+
+ // Server.messageAll({ type: 'players', body: G.getPlayers() });
+ // Server.messageOne(ws, { type: 'walls', body: G.getWalls()});
+ // Server.messageOne(ws, { type: 'robots', body: G.getRobots()});
+ // Server.messageOne(ws, { type: 'winstate', body: G.getWinState()});
+ // Server.messageOne(ws, { type: 'connected', body: ws.id});
+}
+
+Ricochet.prototype.onMessage = function(ws, json) {
+
+};
+
+Ricochet.prototype.onDisconnect = function(ws) {
+// onDisconnect: (ws) => {
+// DEBUG && console.log("Disconnected:");
+// DEBUG && console.log(ws.id);
+
+// G.removePlayer(ws.id);
+// Server.messageOthers(ws, { type: 'players', body: G.getPlayers() });
+// },
+};
+
+// Game.prototype.addPlayer = function(id, name) {
+// if (!players[id]) {
+// players[id] = name;
+// }
+// }
+
+// Game.prototype.removePlayer = function(id) {
+// players[id] = undefined;
+// delete players[id];
+// }
+
+// Game.prototype.getPlayers = function() {
+// return players;
+// }
+
+// Game.prototype.getRobots = function() {
+// return this.TEMP_ROBOTS;
+// }
+
+// Game.prototype.getWalls = function() {
+// // Edge IDs are of the form [i1-j1-i2-j2]. Top left is 0, 0.
+
+// // Leave here for testing.
+// // return [
+// // "1-9-1-10",
+// // "9-1-10-1",
+// // "9-19-10-19",
+// // "19-9-19-10"
+// // ];
+
+// // console.log("Generating walls.");
+
+// // Squares per side has quadratic relationship with wall/corner requirements.
+// const numberOfCorners = Math.ceil(Math.pow((this.squaresPerSide / 10), 2));
+// const numberOfWalls = Math.ceil(Math.pow((this.squaresPerSide / 5), 2));
+
+// const gen = () => Math.floor(Math.random() * this.squaresPerSide);
+// const edges = [];
+
+// // DO NUMBER OF CORNERS FIRST AFTER TESTING
+// for (let n = 0; n < numberOfWalls; n++) {
+// const ri = gen();
+// const rj = gen();
+
+// const isHorizontal = Math.random() < 0.5;
+// const isBackward = Math.random() < 0.5;
+
+// let i1, j1, i2, j2;
+
+// if (isHorizontal) {
+// i1 = isBackward ? ri - 1 : ri;
+// i2 = isBackward ? ri : ri + 1;
+
+// j1 = rj;
+// j2 = rj;
+// } else {
+// i1 = ri;
+// i2 = ri;
+
+// j1 = isBackward ? rj - 1 : rj;
+// j2 = isBackward ? rj : rj + 1;
+// }
+
+// const edge = `${i1}-${j1}-${i2}-${j2}`;
+
+// if (edges.includes(edge)) {
+// n--;
+// } else {
+// edges.push(edge);
+// }
+// }
+
+// return edges;
+// };
+
+// Game.prototype.getWinState = function() {
+// // const gen = () => Math.floor(Math.random() * this.squaresPerSide);
+
+// return { i: 0, j: 0, id: this.TEMP_ROBOTS[0].id };
+// };
+
+// // Game.prototype.onGuess = function(guess) {
+// // return new Promise((resolve, reject) => {
+// // const timeIsUp = () => {
+// // this.guess = Infinity;
+// // resolve();
+// // };
+
+// // if (guess < 1) {
+// // reject(`${guess} is less than 1.`);
+// // return;
+// // }
+
+// // if (guess >= this.guess) {
+// // reject(`${guess} is greater than ${this.guess}.`);
+// // return;
+// // }
+
+// // this.guess = guess;
+
+// // clearTimeout(this.countdownTimer);
+// // this.countdownTimer = setTimeout(timeIsUp, 5 * 1000);
+// // });
+// // };
+
+// Game.prototype.onSolve = function(rawMoveStack) {
+// const sanitizedStack = rawMoveStack.map(move => {
+// const sanitizedRobotId = move.id.replace(/[^0-9a-zA-Z]/g, '');
+// const sanitizedI = move.i * 1;
+// const sanitizedJ = move.j * 1;
+
+// return {
+// i: sanitizedI,
+// j: sanitizedJ,
+// id: sanitizedRobotId
+// };
+// });
+
+// this.winningStack = sanitizedStack;
+// return sanitizedStack;
+// }
+
+// onMessage: (ws, json) => {
+// const message = JSON.parse(json);
+
+// DEBUG && console.log('Received message: ');
+// DEBUG && console.log(message);
+
+// if (!message.type) {
+// DEBUG && console.warn("Unprocessable message: ")
+// DEBUG && console.warn(message);
+// return;
+// }
+
+// switch (message.type) {
+// case 'robots':
+// Server.messageAll({ type: 'robots', body: G.getRobots()});
+// break;
+// case 'skip':
+// Server.messageAll({ type: 'start' });
+// break;
+// case 'solve':
+// const sanitizedStack = G.onSolve(message.rawBody);
+
+// Server.messageAll(ws, { type: 'countdown', body: { id: ws.id, stack: sanitizedStack } });
+// break;
+// case 'start':
+// Server.messageAll({ type: 'start' });
+// break;
+// case 'stop':
+// Server.messageAll({ type: 'stop' });
+// break;
+// case 'walls':
+// Server.messageAll({ type: 'walls', body: G.getWalls()});
+// break;
+// default:
+// console.warn("Unknown message type: ", message.head.type)
+// }
+// },
+
+module.exports = {
+ new: Ricochet
+};
\ No newline at end of file
diff --git a/socket/messenger.js b/socket/messenger.js
new file mode 100644
index 0000000..78dbf1c
--- /dev/null
+++ b/socket/messenger.js
@@ -0,0 +1,35 @@
+const WebSocket = require('ws');
+const DEBUG = (process.env.NODE_ENV !== "production");
+
+const Messenger = {
+ messageOne: (ws, message) => {
+ DEBUG && console.log(`Sending to only ${ws.id}:`);
+ DEBUG && console.log(message);
+
+ ws.send(JSON.stringify(message));
+ },
+
+ messageOthers: (ws, message) => {
+ DEBUG && console.log(`Sending to other client(s):`);
+ DEBUG && console.log(message);
+
+ wss.clients.forEach((client) => {
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
+ client.send(JSON.stringify(message));
+ }
+ });
+ },
+
+ messageAll: (message) => {
+ DEBUG && console.log(`Sending to all ${wss.clients.size} client(s):`);
+ DEBUG && console.log(message);
+
+ wss.clients.forEach((client) => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(JSON.stringify(message));
+ }
+ });
+ },
+};
+
+module.exports = Messenger;
\ No newline at end of file
diff --git a/socket/server.js b/socket/server.js
new file mode 100644
index 0000000..00edcb0
--- /dev/null
+++ b/socket/server.js
@@ -0,0 +1,33 @@
+const WebSocket = require('ws');
+const UrlParser = require('url');
+const uuid = require('node-uuid');
+const messenger = require('./messenger');
+
+const jsDir = `${__dirname}/server`;
+
+const RicochetApp = require(`${jsDir}/ricochet.js`);
+
+const clientApps = {
+ '/ricochet': RicochetApp.new({ messenger })
+};
+
+const onConnect = (ws, req) => {
+ // Store an ID on the socket connection.
+ ws.id = uuid.v4();
+
+ const url = UrlParser.parse(req.url, true);
+
+ if (clientApps[url.pathname]) {
+ app.onConnect(ws, req);
+
+ ws.on('message', app.onMessage);
+ ws.on('close', app.onDisconnect);
+ }
+};
+
+//===== Generic socket server for any application instantiated above.
+const wss = new WebSocket.Server({ port: 8080 });
+wss.on('connection', onConnect);
+
+console.log("Websocket server listening on :8080");
+