diff --git a/README.txt b/README.txt index 4acc96c..fbb9e34 100644 --- a/README.txt +++ b/README.txt @@ -3,16 +3,23 @@ - Incoming global messages are broadcast as custom events prefixed with `G` ## TODO -- move websocket server to /core - robot icons with personality -- dynamic socket server resolution -- namespace server to /ricochet -- cookie room link +- robot GUIDs +- move guess and move logic out of server (clean up server file) +- countdown skip - no cancel from name prompt -- limit concurrent players, make sure connections are closed - window resize update board -- donate link -- tutorial - walls algorigthm - win declare/add/remove -- restore state on join \ No newline at end of file +- be able to "back up" in a move sequence + +- restore state on join +- limit concurrent players, make sure connections are closed, clean up empty rooms +- cookie room link, add to all messages, namespace them + +- move websocket server to /core +- dynamic socket server resolution +- namespace server to /ricochet + +- tutorial +- donate link diff --git a/client/connection.js b/client/connection.js index 59e733b..ae47695 100644 --- a/client/connection.js +++ b/client/connection.js @@ -18,6 +18,12 @@ const Connection = function() { this.ws.send(JSON.stringify({ type: 'guess', rawBody: evt.detail })); }); + document.addEventListener('L-move', (evt) => { + if (evt.detail.emitIf) { + this.ws.send(JSON.stringify({ type: 'move', rawBody: evt.detail })); + } + }); + document.addEventListener('L-robots', () => { this.ws.send(JSON.stringify({ type: 'robots' })); }); @@ -46,7 +52,7 @@ Connection.prototype.connect = function(){ //===== Connection event handlers -Connection.prototype.onOpen = function() { +Connection.prototype.onOpen = function(aaa) { const evt = new Event('L-conn-open'); document.dispatchEvent(evt); }; @@ -70,10 +76,13 @@ Connection.prototype.onReceiveMessage = function({ data }) { let eventName; switch (msg.type) { - case 'guess': eventName = 'G-guess'; break; + case 'connected': eventName = 'G-connected'; break; + case 'guess': eventName = 'G-guess'; break; + case 'move': eventName = 'G-move'; break; case 'players': eventName = 'G-players'; break; case 'robots': eventName = 'G-robots'; break; case 'skip': eventName = 'G-skip'; break; + case 'solve': eventName = 'G-solve'; break; case 'start': eventName = 'G-start'; break; case 'stop': eventName = 'G-stop'; break; case 'walls': eventName = 'G-walls'; break; diff --git a/client/content.js b/client/content.js index 3c44801..7d34719 100644 --- a/client/content.js +++ b/client/content.js @@ -9,23 +9,22 @@ const Content = function({ parent, squares }) { this.listeners = {}; this.initialRobotState = []; + this.playerId = null; + this.isSolving = false; + this.isSolver = false; + this.drawSquares(); - document.addEventListener('L-reset', this.onReset.bind(this)); + document.addEventListener('L-reset', this.msgReset.bind(this)); + document.addEventListener('G-connected', this.msgConnected.bind(this)); document.addEventListener('G-walls', this.msgWalls.bind(this)); document.addEventListener('G-robots', this.msgRobots.bind(this)); + document.addEventListener('G-solve', this.msgSolve.bind(this)); + document.addEventListener('G-move', this.msgMove.bind(this)); }; -//===== Event handlers - -Content.prototype.msgRobots = function(evt) { - this.initialRobotState = evt.detail.body; - this.drawRobots() -} - -//===== Event handlers - +//===== UI Content.prototype.drawRobots = function() { this.robots = {}; @@ -127,9 +126,7 @@ Content.prototype.drawSquares = function() { } }; -Content.prototype.msgWalls = function(msg) { - const edges = msg.detail.body; - +Content.prototype.drawWalls = function(edges) { this.walls = {}; this.parent.querySelectorAll('.content-wall-x').forEach(el => el.parentNode.removeChild(el)); @@ -189,10 +186,7 @@ Content.prototype.moveRobot = function({ id, i, j }) { arrows.style.top = (y - s) + 'px'; this.updateArrowVisibilities(); - - var event = new Event('L-move'); - document.dispatchEvent(event); -} +}; Content.prototype.updateArrowVisibilities = function() { const keys = Object.keys(this.robots); @@ -219,29 +213,6 @@ Content.prototype.updateArrowVisibilities = function() { arrows.querySelector("[data-direction='up']").style.display = (this.robots[ijU] || this.walls[edgeU] || j === 0) ? 'none' : 'block'; arrows.querySelector("[data-direction='down']").style.display = (this.robots[ijD] || this.walls[edgeD] || j === (this.squares.perSide - 1)) ? 'none' : 'block'; }); -} - -Content.prototype.onArrowClick = function(evt) { - const direction = evt.currentTarget.dataset.direction; - const id = evt.currentTarget.dataset.parent; - - const robot = document.getElementById(`robot-${id}`); - const i = robot.dataset.i * 1; - const j = robot.dataset.j * 1; - - delete this.robots[`${i}-${j}`]; - - const { i: i2, j: j2 } = this.findNextObstacle({ direction, i, j }); - - this.moveRobot({ id, i: i2, j: j2 }) -}; - -Content.prototype.onReceiveGuess = function() { - console.log("Board onReceiveGuess()") -} - -Content.prototype.onReset = function() { - this.drawRobots(); }; Content.prototype.findNextObstacle = function({ direction, i, j }) { @@ -291,4 +262,66 @@ Content.prototype.findNextObstacle = function({ direction, i, j }) { } throw Error("Could not find next obstacle, no direction found. ", direction, i, j); +}; + +//===== Click handlers + +Content.prototype.onArrowClick = function(evt) { + const direction = evt.currentTarget.dataset.direction; + const id = evt.currentTarget.dataset.parent; + + const robot = document.getElementById(`robot-${id}`); + const i = robot.dataset.i * 1; + const j = robot.dataset.j * 1; + + delete this.robots[`${i}-${j}`]; + + const { i: i2, j: j2 } = this.findNextObstacle({ direction, i, j }); + + this.moveRobot({ id, i: i2, j: j2 }); + + const detail = { id, i: i2, j: j2, emitIf: this.isSolving }; + const evtMove = new CustomEvent('L-move', { detail }); + + document.dispatchEvent(evtMove); +}; + +//===== Message handlers + +Content.prototype.msgRobots = function(evt) { + this.initialRobotState = evt.detail.body; + this.drawRobots() +}; + +Content.prototype.msgWalls = function(evt) { + this.drawWalls(evt.detail.body); +}; + +Content.prototype.msgMove = function(evt) { + const { id, i, j } = evt.detail.body; + this.moveRobot({ id, i, j }); +}; + +Content.prototype.msgSolve = function(evt) { + this.isSolving = true; + this.isSolver = (this.playerId === evt.detail.id); + + if (this.isSolver) { + console.error("The current player is attempting a solve."); + } else { + console.error("A different player is attempting a solve."); + //...modal. + } +}; + +Content.prototype.msgJoin = function(evt) { + +}; + +Content.prototype.msgConnected = function(evt) { + this.playerId = evt.detail.body; } + +Content.prototype.msgReset = function() { + this.drawRobots(); +}; diff --git a/client/controls.js b/client/controls.js index 1ed6e60..29d6b7d 100644 --- a/client/controls.js +++ b/client/controls.js @@ -8,7 +8,7 @@ const Controls = function() { this.drawGuesses(); - document.addEventListener('L-conn-open', this.msgJoin.bind(this)); + document.addEventListener('L-connected', this.msgConnected.bind(this)); document.addEventListener('L-move', this.msgMove.bind(this)); // Message handlers: "Local message" and "Global message" @@ -38,7 +38,7 @@ Controls.prototype.countdownStart = function(seconds) { this.timers.countdown = this.countdownTick.bind(this); const countdown = document.getElementById('controls-countdown'); - countdown.dataset.tick = seconds + 1; + countdown.dataset.tick = seconds; this.countdownTick(); }; @@ -46,15 +46,15 @@ Controls.prototype.countdownStart = function(seconds) { Controls.prototype.countdownTick = function() { const countdown = document.getElementById('controls-countdown'); const tick = countdown.dataset.tick * 1; - countdown.dataset.tick = tick - 1; + countdown.dataset.tick = tick - 1; + + const s = (tick !== 1) ? 's' : ''; + countdown.innerHTML = `${tick} second${s}!`; if (tick === 0) { this.countdownComplete(); return; - } - - const s = (tick !== 1) ? 's' : ''; - countdown.innerHTML = `${tick} second${s}!`; + } this.timers.countdown = setTimeout(this.countdownTick.bind(this), 1000); }; @@ -126,7 +126,7 @@ Controls.prototype.msgGuess = function(evt) { this.countdownStart(5); } -Controls.prototype.msgJoin = function() { +Controls.prototype.msgConnected = function() { this.showWaiting(); }; diff --git a/server.js b/server.js index 5f66f3e..2d0a734 100644 --- a/server.js +++ b/server.js @@ -67,6 +67,7 @@ const Server = { 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: 'connected', body: ws.id}); }, onMessage: (ws, json) => { @@ -87,7 +88,20 @@ const Server = { santizedGuess && Server.messageAll({ type: 'guess', guess: santizedGuess, id: ws.id }); - G.onGuess(santizedGuess).then(Server.messageAll.bind(null, { type: 'attempt', 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()}); diff --git a/server/game.js b/server/game.js index 23819fb..43820f7 100644 --- a/server/game.js +++ b/server/game.js @@ -5,7 +5,7 @@ const players = {}; const Game = function() { this.id = uuid.v4(); this.countdownTimer = null; - this.winningGuess = Infinity; + this.guess = Infinity; } Game.prototype.addPlayer = function(id, name) { @@ -99,14 +99,25 @@ Game.prototype.getWalls = function() { Game.prototype.onGuess = function(guess) { return new Promise((resolve, reject) => { - if (guess < 1 || guess >= this.guess) { - 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(resolve, 5 * 1000); + this.countdownTimer = setTimeout(timeIsUp, 5 * 1000); }); };