diff --git a/README.txt b/README.txt index 90973fa..5163c8e 100644 --- a/README.txt +++ b/README.txt @@ -14,22 +14,16 @@ Icons from [https://game-icons.net](https://game-icons.net) ## TODO - (n solves) in players list, if > 0 -- join mid-countdown - robot color update -- countdown solution moves value -- countdown solution name value -- better solution +- test multi solve - replay stack -- countdown skip - slide arrows - chat box - no cancel from name prompt -- clear out the trash bins of history -- handle conn interrupt -- donate link heart fill in not underline - Robots not resizing (shadows do though) - Robots can gen on top of eachother -- star is black sometimes +- star is black sometimes because of async +- "you" not picked up sometimes because of async - limit concurrent players, make sure connections are closed - move websocket server to /core @@ -38,4 +32,3 @@ Icons from [https://game-icons.net](https://game-icons.net) - restore name prompt - tutorial -- donate link diff --git a/client/connection.js b/client/connection.js index 19d21b9..578b7c3 100644 --- a/client/connection.js +++ b/client/connection.js @@ -1,30 +1,28 @@ //===== Constructor const Connection = function() { + this.retryCounter = 0; + + this.socketListeners = { + open: this.onOpen.bind(this), + error: this.onError.bind(this), + message: this.onReceiveMessage.bind(this) + }; + // Local event listeners document.addEventListener('L-join', (evt) => { this.connect(); }); - document.addEventListener('L-objective', () => { - this.ws.send(JSON.stringify({ type: 'objective' })); - }); + document.addEventListener('L-objective', () => this.send({ type: 'objective' }) ); - document.addEventListener('L-robots', () => { - this.ws.send(JSON.stringify({ type: 'robots' })); - }); + document.addEventListener('L-robots', () => this.send({ type: 'robots' }) ); - document.addEventListener('L-solve', (evt) => { - this.ws.send(JSON.stringify({ type: 'solve', rawBody: evt.detail })); - }); + document.addEventListener('L-solve', (evt) => this.send({ type: 'solve', rawBody: evt.detail }) ); - document.addEventListener('L-walls', () => { - this.ws.send(JSON.stringify({ type: 'walls' })); - }); + document.addEventListener('L-walls', () => this.send({ type: 'walls' }) ); - document.addEventListener('L-skip', () => { - this.ws.send(JSON.stringify({ type: 'skip' })); - }); + document.addEventListener('L-skip', () => this.send({ type: 'skip' }) ); }; Connection.prototype.connect = function(){ @@ -36,14 +34,24 @@ Connection.prototype.connect = function(){ this.ws = new WebSocket('ws://localhost:8080/ricochet?name=' + rawInput); - this.ws.addEventListener('open', this.onOpen.bind(this)); - this.ws.addEventListener('error', this.onError.bind(this)); - this.ws.addEventListener('message', this.onReceiveMessage.bind(this)); + this.ws.addEventListener('open', this.socketListeners.open); + this.ws.addEventListener('error', this.socketListeners.error); + this.ws.addEventListener('message', this.socketListeners.message); +}; + +Connection.prototype.send = function(json) { + if (this.ws.readyState === 1) { + this.ws.send(JSON.stringify(json)); + } else { + this.onError('WebSocket connection interrupted.'); + } }; //===== Connection event handlers -Connection.prototype.onOpen = function(aaa) { +Connection.prototype.onOpen = function() { + this.retryCounter = 0; + const evt = new Event('L-conn-open'); document.dispatchEvent(evt); }; @@ -51,8 +59,20 @@ Connection.prototype.onOpen = function(aaa) { Connection.prototype.onError = function(err) { console.error(err); + this.ws.removeEventListener('open', this.socketListeners.open); + this.ws.removeEventListener('error', this.socketListeners.error); + this.ws.removeEventListener('message', this.socketListeners.message); + const evt = new CustomEvent('L-conn-error', { detail: err }); document.dispatchEvent(evt); + + if (this.retryCounter < 5) { + this.retryCounter++; + console.error("Retrying connection..."); + setTimeout(this.connect.bind(this), Math.pow(this.retryCounter, 2) * 1000); + } else { + console.error("Retries exhausted. Server can't be contacted."); + } }; Connection.prototype.onReceiveMessage = function({ data }) { @@ -74,6 +94,7 @@ Connection.prototype.onReceiveMessage = function({ data }) { case 'robots': eventName = 'G-robots'; break; case 'state': eventName = 'G-state'; break; case 'walls': eventName = 'G-walls'; break; + case 'win': eventName = 'G-win'; break; } if (eventName) { diff --git a/client/controls.js b/client/controls.js index fed47ad..b04c99f 100644 --- a/client/controls.js +++ b/client/controls.js @@ -15,6 +15,7 @@ const Controls = function() { document.addEventListener('G-countdown', this.msgCountdown.bind(this)); document.addEventListener('G-players', this.msgPlayers.bind(this)); document.addEventListener('G-state', this.msgState.bind(this)); + document.addEventListener('G-win', this.msgWin.bind(this)); // Click handlers document.getElementById('controls-moves-reset').addEventListener('click', this.onClickMovesReset.bind(this)); @@ -25,7 +26,10 @@ const Controls = function() { document.getElementById('controls-options-robots').addEventListener('click', this.onClickOptionsRobots.bind(this)); document.getElementById('controls-options-walls').addEventListener('click', this.onClickOptionsWalls.bind(this)); - document.getElementById('controls-countdown-skip').addEventListener('click', this.onClickSkip.bind(this)); + document.getElementById('controls-countdown-skip').addEventListener('click', this.onClickCountdownSkip.bind(this)); + + document.getElementById('controls-win-replay').addEventListener('click', this.onClickWinReplay.bind(this)); + document.getElementById('controls-win-next').addEventListener('click', this.onClickWinNext.bind(this)); this.setState('CONNECTING'); } @@ -70,7 +74,7 @@ Controls.prototype.setState = function(state) { 'controls-moves', 'controls-options', 'controls-players', - 'controls-solution', + 'controls-win', ]; blocks.forEach(id => document.getElementById(id).style.display = 'none'); @@ -80,7 +84,7 @@ Controls.prototype.setState = function(state) { CONNECTING: ['controls-connection'], PLAY: ['controls-players', 'controls-moves', 'controls-options'], COUNTDOWN: ['controls-players', 'controls-moves', 'controls-countdown'], - SOLUTION: ['controls-players', 'controls-solution'] + WIN: ['controls-players', 'controls-win'] }; (STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block'); @@ -109,12 +113,16 @@ Controls.prototype.msgConnectionError = function() { }; Controls.prototype.msgCountdown = function(evt) { - this.setState('COUNTDOWN'); - document.getElementById('controls-countdown-moves').innerHTML = evt.detail.body.moveCount; - document.getElementById('controls-countdown-player').innerHTML = this.names[evt.detail.body.id]; + document.getElementById('controls-countdown-player').innerHTML = `${this.names[evt.detail.body.id]} has a solution!`; + + const { duration, timestamp } = evt.detail.body; + + const now = new Date().getTime(); + const diff = Math.ceil((now - timestamp) / 1000); + const remaining = duration - diff; - this.countdownStart(evt.detail.body.duration); + this.countdownStart(remaining); }; Controls.prototype.msgSkip = function() { @@ -160,6 +168,11 @@ Controls.prototype.msgState = function(evt) { this.setState(evt.detail.body); }; +Controls.prototype.msgWin = function(evt) { + document.getElementById('controls-win-message').innerHTML = `Congratulations ${this.names[evt.detail.body.id]} !`; + document.getElementById('controls-win-count').innerHTML = evt.detail.body.moveCount; +}; + //===== Click handlers Controls.prototype.dispatch = function(evt, data) { @@ -197,96 +210,17 @@ Controls.prototype.onClickMovesUndo = function() { // Countdown block -Controls.prototype.onClickSkip = function() { +Controls.prototype.onClickCountdownSkip = function() { this.dispatch('L-skip'); }; -//===== THE TRASH BIN OF HISTORY -// Controls.prototype.msgRobots = function(evt) { -// this.starts = []; -// this.moves = []; - -// evt.detail.body.forEach(({ id, i, j}) => { -// this.starts.push({ id, i, j }); -// this.moves.push({ id, i, j }); -// }); -// }; - -// 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 ", -// " says ", " hazards ", " guesses ", " thinks it might be "]; -// const blurb = blurbs[Math.floor(Math.random() * blurbs.length)]; - -// 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(5); -// } - -// Controls.prototype.msgReset = function() { -// // Broadcast starting locations. -// this.moves = []; -// this.starts.forEach(move => { -// const evtMove = new CustomEvent('L-move', { detail: move }); -// document.dispatchEvent(evtMove); -// }); -// }; - -// Controls.prototype.msgUndo = function(evt) { -// if (this.moves.length <= this.starts.length) { -// return; -// } - -// const { id } = this.moves.pop(); - -// const indexOfPreviousMove = this.moves.reduce((acc, v, i) => (v.id === id ? i : acc), -1); -// const previousMove = this.moves.splice(indexOfPreviousMove, 1); - -// const evtMove = new CustomEvent('L-move', { detail: previousMove[0] }); -// document.dispatchEvent(evtMove); -// }; - -// Controls.prototype.msgAttempt = function() { -// alert("Ready for winning attempt!"); -// }; - -// Controls.prototype.showWaiting = function() { -// document.getElementById('controls-start').parentNode.style.display = ''; -// document.getElementById('controls-walls').parentNode.style.display = ''; -// document.getElementById('controls-robots').parentNode.style.display = ''; - -// document.getElementById('controls-stop').parentNode.style.display = 'none'; -// // document.getElementById('controls-moves-reset').parentNode.style.display = 'none'; -// document.getElementById('controls-guesses').style.display = 'none'; -// document.getElementById('controls-panic').style.display = 'none'; -// }; - -// Controls.prototype.showPanic = function() { -// this.showGuessing(); -// document.getElementById('controls-panic').style.display = ''; -// }; - -// Controls.prototype.onClickStart = function() { -// this.dispatch('L-start'); -// }; - -// Controls.prototype.onClickStop = function() { -// this.dispatch('L-stop'); -// }; - -// Controls.prototype.msgStop = function() { -// -// } - -// document.addEventListener('L-reset', this.msgReset.bind(this)); -// document.addEventListener('L-undo', this.msgUndo.bind(this)); -// document.addEventListener('G-stop', this.msgStop.bind(this)); -// document.addEventListener('G-start', this.msgStart.bind(this)); -// document.addEventListener('G-guess', this.msgGuess.bind(this)); -// document.getElementById('controls-start').addEventListener('click', this.onClickStart.bind(this)); -// document.getElementById('controls-stop').addEventListener('click', this.onClickStop.bind(this)); \ No newline at end of file +// Win block +Controls.prototype.onClickWinReplay = function() { + // this.dispatch('L-skip'); + alert('win replay') +} + +Controls.prototype.onClickWinNext = function() { + // this.dispatch('L-skip'); + alert('win next') +} \ No newline at end of file diff --git a/index.html b/index.html index 316e7f3..31e0351 100644 --- a/index.html +++ b/index.html @@ -47,25 +47,25 @@