diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..4acc96c --- /dev/null +++ b/README.txt @@ -0,0 +1,18 @@ +## Architecture: +- Local events are broadcast as custom events prefixed `L-` +- 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 +- 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 diff --git a/client/connection.js b/client/connection.js index eaa0382..59e733b 100644 --- a/client/connection.js +++ b/client/connection.js @@ -60,7 +60,7 @@ Connection.prototype.onError = function(err) { Connection.prototype.onReceiveMessage = function({ data }) { const msg = JSON.parse(data); - console.warn(msg); + console.warn(JSON.stringify(msg)); if (!msg.type) { console.warn("Unprocessable message: ", msg) diff --git a/client/content.js b/client/content.js index 5803c2f..3c44801 100644 --- a/client/content.js +++ b/client/content.js @@ -9,29 +9,31 @@ const Content = function({ parent, squares }) { this.listeners = {}; this.initialRobotState = []; - // this.drawSquares(); - // document.addEventListener('L-conn-open', this.msgStartGame.bind(this)); -// document.addEventListener('G-walls', this.msgStart.bind(this)); - // document.addEventListener('G-robots', this.msgStart.bind(this)); - // document.addEventListener('board-reset', this.onReset.bind(this)); - // from connection: board.drawRobots(data.body); - // from connection: board.drawWalls(data.body); - // from connection: board.onReceiveGuess(data.body); + this.drawSquares(); + + document.addEventListener('L-reset', this.onReset.bind(this)); + + document.addEventListener('G-walls', this.msgWalls.bind(this)); + document.addEventListener('G-robots', this.msgRobots.bind(this)); }; //===== Event handlers +Content.prototype.msgRobots = function(evt) { + this.initialRobotState = evt.detail.body; + this.drawRobots() +} + //===== Event handlers -Content.prototype.drawRobots = function(robots) { - this.initialRobotState = robots; +Content.prototype.drawRobots = function() { this.robots = {}; this.parent.querySelectorAll('.content-robot').forEach(el => el.parentNode.removeChild(el)); this.parent.querySelectorAll('.content-arrows').forEach(el => el.parentNode.removeChild(el)); - robots.forEach(({ color, i, j }) => { + this.initialRobotState.forEach(({ color, i, j }) => { const { x, y } = this.squares.ijToXy({ i, j }); const s = this.squares.sideLength; const id = color.replace('#', '').toUpperCase(); @@ -61,19 +63,19 @@ Content.prototype.drawRobots = function(robots) { arrows.style.top = (y - s) + 'px'; const up = this.drawArrow({ - direction: 'up', label: '▲', i, j, left: s, top: 0, parentId: id + direction: 'up', label: '▲', i, j, left: (s * 1.25), top: (s * 0.5), parentId: id }); const down = this.drawArrow({ - direction: 'down', label: '▼', i, j, left: s, top: (2 * s), parentId: id + direction: 'down', label: '▼', i, j, left: (s * 1.25), top: (s * 2), parentId: id }); const left = this.drawArrow({ - direction: 'left', label: '◀', i, j, left: 0, top: s, parentId: id + direction: 'left', label: '◀', i, j, left: (s * 0.5), top: (s * 1.25), parentId: id }); const right = this.drawArrow({ - direction: 'right', label: '▶', i, j, left: (2 * s), top: s, parentId: id + direction: 'right', label: '▶', i, j, left: (s * 2), top: (s * 1.25), parentId: id }); arrows.appendChild(up); @@ -89,7 +91,7 @@ Content.prototype.drawRobots = function(robots) { }; Content.prototype.drawArrow = function({ direction, label, i, j, left, top, parentId }) { - const s = this.squares.sideLength; + const s = this.squares.sideLength / 2; const arrow = document.createElement('div'); arrow.className = 'content-arrow'; @@ -125,7 +127,9 @@ Content.prototype.drawSquares = function() { } }; -Content.prototype.drawWalls = function(edges) { +Content.prototype.msgWalls = function(msg) { + const edges = msg.detail.body; + this.walls = {}; this.parent.querySelectorAll('.content-wall-x').forEach(el => el.parentNode.removeChild(el)); @@ -186,7 +190,7 @@ Content.prototype.moveRobot = function({ id, i, j }) { this.updateArrowVisibilities(); - var event = new Event('move-increment'); + var event = new Event('L-move'); document.dispatchEvent(event); } @@ -237,7 +241,7 @@ Content.prototype.onReceiveGuess = function() { } Content.prototype.onReset = function() { - this.drawRobots(this.initialRobotState); + this.drawRobots(); }; Content.prototype.findNextObstacle = function({ direction, i, j }) { diff --git a/client/controls.js b/client/controls.js index 9ac3785..1ed6e60 100644 --- a/client/controls.js +++ b/client/controls.js @@ -3,6 +3,8 @@ const Controls = function() { this.moves = 0; this.names = {}; + this.timers = {}; + this.currentWinningGuess = Infinity; this.drawGuesses(); @@ -12,6 +14,7 @@ const Controls = function() { // Message handlers: "Local message" and "Global message" // document.addEventListener('G-move', this.msgMove.bind(this)); // document.addEventListener('G-win', this.msgWin.bind(this)); + document.addEventListener('G-attempt', this.msgAttempt.bind(this)); document.addEventListener('G-guess', this.msgGuess.bind(this)); document.addEventListener('G-skip', this.msgSkip.bind(this)); document.addEventListener('G-start', this.msgStart.bind(this)); @@ -31,10 +34,13 @@ const Controls = function() { //===== UI Controls.prototype.countdownStart = function(seconds) { + clearTimeout(this.timers.countdown); + this.timers.countdown = this.countdownTick.bind(this); + const countdown = document.getElementById('controls-countdown'); - countdown.dataset.tick = seconds - 1; + countdown.dataset.tick = seconds + 1; - setTimeout(this.countdownTick.bind(this), 1000); + this.countdownTick(); }; Controls.prototype.countdownTick = function() { @@ -49,12 +55,13 @@ Controls.prototype.countdownTick = function() { const s = (tick !== 1) ? 's' : ''; countdown.innerHTML = `${tick} second${s}!`; - setTimeout(this.countdownTick.bind(this), 1000); + + this.timers.countdown = setTimeout(this.countdownTick.bind(this), 1000); }; Controls.prototype.countdownComplete = function() { - alert("boom") -}; + document.getElementById('controls-countdown').dataset.tick = 0; +} ; Controls.prototype.drawGuesses = function() { const container = document.getElementById('controls-guesses'); @@ -99,6 +106,10 @@ Controls.prototype.showPanic = function() { //===== Message handlers +Controls.prototype.msgAttempt = function() { + alert("Ready for winning attempt!"); +}; + Controls.prototype.msgGuess = function(evt) { const blurbs = [ " has a solution: ", " can do it in ", " says, maybe ", " wagers ", " reckons ", " is pretty sure it's ", ", confidently: ", " wants it to be ", @@ -108,9 +119,11 @@ Controls.prototype.msgGuess = function(evt) { const msg = evt.detail; const guess = msg.guess; + this.currentWinningGuess = guess; + document.getElementById('controls-panic').querySelector('.controls-alert-urgent').innerHTML = (`${this.names[msg.id]}${blurb}${guess} moves.`); this.showPanic(); - this.countdownStart(30); + this.countdownStart(5); } Controls.prototype.msgJoin = function() { @@ -176,7 +189,17 @@ Controls.prototype.dispatch = function(evt, data) { } Controls.prototype.onClickGuess = function(evt) { - this.dispatch('L-guess', { moves: evt.currentTarget.dataset.value }); + const guess = evt.currentTarget.dataset.value * 1; + + if (!guess || guess < 1) { + return; + } + + if (guess < this.currentWinningGuess) { + this.dispatch('L-guess', { moves: evt.currentTarget.dataset.value }); + } else { + alert(`That doesn't beat ${this.currentWinningGuess} - try again!`) + } } Controls.prototype.onClickRobots = function() { diff --git a/index.html b/index.html index d195482..6f97323 100644 --- a/index.html +++ b/index.html @@ -75,7 +75,7 @@
Local
Moves:
-
-
+
0
Reset
diff --git a/notes.txt b/notes.txt deleted file mode 100644 index c4d481c..0000000 --- a/notes.txt +++ /dev/null @@ -1,13 +0,0 @@ -// TODO move websocket server to /core -// TODO dynamic socket server resolution -// TODO namespace server to /ricochet -// TODO cookie room link -// TODO no cancel from name prompt -// TODO limit concurrent players, make sure connections are closed -// TODO window resize update board -// TODO donate link -// TODO tutorial -// TODO walls algorigthm -// TODO fix overlapping robot arrows -// TODO win declare/add/remove -// TODO restore state on join \ No newline at end of file diff --git a/server.js b/server.js index c5e5505..5f66f3e 100644 --- a/server.js +++ b/server.js @@ -84,7 +84,10 @@ const Server = { 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: 'attempt', id: ws.id })); break; case 'robots': Server.messageAll({ type: 'robots', body: G.getRobots()}); diff --git a/server/game.js b/server/game.js index fcfa0e1..23819fb 100644 --- a/server/game.js +++ b/server/game.js @@ -4,6 +4,8 @@ const players = {}; const Game = function() { this.id = uuid.v4(); + this.countdownTimer = null; + this.winningGuess = Infinity; } Game.prototype.addPlayer = function(id, name) { @@ -95,4 +97,17 @@ Game.prototype.getWalls = function() { return edges; }; +Game.prototype.onGuess = function(guess) { + return new Promise((resolve, reject) => { + if (guess < 1 || guess >= this.guess) { + reject(); + } + + this.guess = guess; + + clearTimeout(this.countdownTimer); + this.countdownTimer = setTimeout(resolve, 5 * 1000); + }); +}; + module.exports = Game; \ No newline at end of file