Connection retry backoff.

master
Ben Burlingham 5 years ago
parent 1cf7e0b756
commit 292ed16beb
  1. 13
      README.txt
  2. 59
      client/connection.js
  3. 128
      client/controls.js
  4. 16
      index.html
  5. 52
      server/ricochet.js
  6. 27
      style/controls.css

@ -14,22 +14,16 @@ Icons from [https://game-icons.net](https://game-icons.net)
## TODO ## TODO
- (n solves) in players list, if > 0 - (n solves) in players list, if > 0
- join mid-countdown
- robot color update - robot color update
- countdown solution moves value - test multi solve
- countdown solution name value
- better solution
- replay stack - replay stack
- countdown skip
- slide arrows - slide arrows
- chat box - chat box
- no cancel from name prompt - 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 not resizing (shadows do though)
- Robots can gen on top of eachother - 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 - limit concurrent players, make sure connections are closed
- move websocket server to /core - move websocket server to /core
@ -38,4 +32,3 @@ Icons from [https://game-icons.net](https://game-icons.net)
- restore name prompt - restore name prompt
- tutorial - tutorial
- donate link

@ -1,30 +1,28 @@
//===== Constructor //===== Constructor
const Connection = function() { 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 // Local event listeners
document.addEventListener('L-join', (evt) => { document.addEventListener('L-join', (evt) => {
this.connect(); this.connect();
}); });
document.addEventListener('L-objective', () => { document.addEventListener('L-objective', () => this.send({ type: 'objective' }) );
this.ws.send(JSON.stringify({ type: 'objective' }));
});
document.addEventListener('L-robots', () => { document.addEventListener('L-robots', () => this.send({ type: 'robots' }) );
this.ws.send(JSON.stringify({ type: 'robots' }));
});
document.addEventListener('L-solve', (evt) => { document.addEventListener('L-solve', (evt) => this.send({ type: 'solve', rawBody: evt.detail }) );
this.ws.send(JSON.stringify({ type: 'solve', rawBody: evt.detail }));
});
document.addEventListener('L-walls', () => { document.addEventListener('L-walls', () => this.send({ type: 'walls' }) );
this.ws.send(JSON.stringify({ type: 'walls' }));
});
document.addEventListener('L-skip', () => { document.addEventListener('L-skip', () => this.send({ type: 'skip' }) );
this.ws.send(JSON.stringify({ type: 'skip' }));
});
}; };
Connection.prototype.connect = function(){ Connection.prototype.connect = function(){
@ -36,14 +34,24 @@ Connection.prototype.connect = function(){
this.ws = new WebSocket('ws://localhost:8080/ricochet?name=' + rawInput); this.ws = new WebSocket('ws://localhost:8080/ricochet?name=' + rawInput);
this.ws.addEventListener('open', this.onOpen.bind(this)); this.ws.addEventListener('open', this.socketListeners.open);
this.ws.addEventListener('error', this.onError.bind(this)); this.ws.addEventListener('error', this.socketListeners.error);
this.ws.addEventListener('message', this.onReceiveMessage.bind(this)); 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 event handlers
Connection.prototype.onOpen = function(aaa) { Connection.prototype.onOpen = function() {
this.retryCounter = 0;
const evt = new Event('L-conn-open'); const evt = new Event('L-conn-open');
document.dispatchEvent(evt); document.dispatchEvent(evt);
}; };
@ -51,8 +59,20 @@ Connection.prototype.onOpen = function(aaa) {
Connection.prototype.onError = function(err) { Connection.prototype.onError = function(err) {
console.error(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 }); const evt = new CustomEvent('L-conn-error', { detail: err });
document.dispatchEvent(evt); 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 }) { Connection.prototype.onReceiveMessage = function({ data }) {
@ -74,6 +94,7 @@ Connection.prototype.onReceiveMessage = function({ data }) {
case 'robots': eventName = 'G-robots'; break; case 'robots': eventName = 'G-robots'; break;
case 'state': eventName = 'G-state'; break; case 'state': eventName = 'G-state'; break;
case 'walls': eventName = 'G-walls'; break; case 'walls': eventName = 'G-walls'; break;
case 'win': eventName = 'G-win'; break;
} }
if (eventName) { if (eventName) {

128
client/controls.js vendored

@ -15,6 +15,7 @@ const Controls = function() {
document.addEventListener('G-countdown', this.msgCountdown.bind(this)); document.addEventListener('G-countdown', this.msgCountdown.bind(this));
document.addEventListener('G-players', this.msgPlayers.bind(this)); document.addEventListener('G-players', this.msgPlayers.bind(this));
document.addEventListener('G-state', this.msgState.bind(this)); document.addEventListener('G-state', this.msgState.bind(this));
document.addEventListener('G-win', this.msgWin.bind(this));
// Click handlers // Click handlers
document.getElementById('controls-moves-reset').addEventListener('click', this.onClickMovesReset.bind(this)); 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-robots').addEventListener('click', this.onClickOptionsRobots.bind(this));
document.getElementById('controls-options-walls').addEventListener('click', this.onClickOptionsWalls.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'); this.setState('CONNECTING');
} }
@ -70,7 +74,7 @@ Controls.prototype.setState = function(state) {
'controls-moves', 'controls-moves',
'controls-options', 'controls-options',
'controls-players', 'controls-players',
'controls-solution', 'controls-win',
]; ];
blocks.forEach(id => document.getElementById(id).style.display = 'none'); blocks.forEach(id => document.getElementById(id).style.display = 'none');
@ -80,7 +84,7 @@ Controls.prototype.setState = function(state) {
CONNECTING: ['controls-connection'], CONNECTING: ['controls-connection'],
PLAY: ['controls-players', 'controls-moves', 'controls-options'], PLAY: ['controls-players', 'controls-moves', 'controls-options'],
COUNTDOWN: ['controls-players', 'controls-moves', 'controls-countdown'], 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'); (STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block');
@ -109,12 +113,16 @@ Controls.prototype.msgConnectionError = function() {
}; };
Controls.prototype.msgCountdown = function(evt) { Controls.prototype.msgCountdown = function(evt) {
this.setState('COUNTDOWN');
document.getElementById('controls-countdown-moves').innerHTML = evt.detail.body.moveCount; 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() { Controls.prototype.msgSkip = function() {
@ -160,6 +168,11 @@ Controls.prototype.msgState = function(evt) {
this.setState(evt.detail.body); 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 //===== Click handlers
Controls.prototype.dispatch = function(evt, data) { Controls.prototype.dispatch = function(evt, data) {
@ -197,96 +210,17 @@ Controls.prototype.onClickMovesUndo = function() {
// Countdown block // Countdown block
Controls.prototype.onClickSkip = function() { Controls.prototype.onClickCountdownSkip = function() {
this.dispatch('L-skip'); this.dispatch('L-skip');
}; };
//===== THE TRASH BIN OF HISTORY // Win block
// Controls.prototype.msgRobots = function(evt) { Controls.prototype.onClickWinReplay = function() {
// this.starts = []; // this.dispatch('L-skip');
// this.moves = []; alert('win replay')
}
// evt.detail.body.forEach(({ id, i, j}) => {
// this.starts.push({ id, i, j }); Controls.prototype.onClickWinNext = function() {
// this.moves.push({ id, i, j }); // this.dispatch('L-skip');
// }); alert('win next')
// }; }
// 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));

@ -47,25 +47,25 @@
</div> </div>
<div class='controls-block' id="controls-countdown"> <div class='controls-block' id="controls-countdown">
<div class='controls-title'><span id='controls-countdown-player'></span> has a solution!</div> <div class='controls-title' id='controls-countdown-player'></div>
<div class="controls-countdown-layout"> <div class="controls-countdown-layout">
<div id="controls-countdown-moves">11</div> <div id="controls-countdown-moves"></div>
<div id="controls-countdown-value">19</div> <div id="controls-countdown-value"></div>
</div> </div>
<div class='controls-button' id='controls-countdown-skip'>Skip</div> <div class='controls-button' id='controls-countdown-skip'>Skip</div>
</div> </div>
<div class='controls-block' id="controls-solution"> <div class='controls-block' id="controls-win">
<div class='controls-title'>Round over</div> <div class='controls-title'>Round over</div>
<div class="controls-message" id='controls-solution-message'>Congrats Aardvark Matthews Dunkirk!</div> <div class="controls-message" id='controls-win-message'></div>
<div id='controls-solution-count'>24</div> <div id='controls-win-count'></div>
<div class='controls-button' id='controls-solution-replay'>Replay</div> <div class='controls-button' id='controls-win-replay'>Replay</div>
<div class='controls-button' id='controls-next'>Start next round</div> <div class='controls-button' id='controls-win-next'>Start next round</div>
</div> </div>
<div id="controls-footer"> <div id="controls-footer">

@ -5,7 +5,7 @@ const DEBUG = (process.env.NODE_ENV !== "production");
const STATE = { const STATE = {
COUNTDOWN: 'COUNTDOWN', COUNTDOWN: 'COUNTDOWN',
PLAY: 'PLAY', PLAY: 'PLAY',
REPLAY: 'REPLAY' WIN: 'WIN'
}; };
const Ricochet = function({ messenger }) { const Ricochet = function({ messenger }) {
@ -19,6 +19,7 @@ const Ricochet = function({ messenger }) {
this.walls = this.freshWalls(); this.walls = this.freshWalls();
this.objective = this.freshObjective(); this.objective = this.freshObjective();
this.countdownDuration = 15;
this.countdownTimer = null; this.countdownTimer = null;
this.countdownTimestamp = null; this.countdownTimestamp = null;
@ -37,12 +38,21 @@ Ricochet.prototype.onConnect = function(ws, req) {
this.addPlayer(ws.id, santizedName); this.addPlayer(ws.id, santizedName);
this.messenger.messageOne(ws, { type: 'connected', body: ws.id});
this.messenger.messageAll({ type: 'players', body: this.players }); this.messenger.messageAll({ type: 'players', body: this.players });
this.messenger.messageOne(ws, { type: 'connected', body: ws.id});
this.messenger.messageOne(ws, { type: 'robots', body: this.robots}); this.messenger.messageOne(ws, { type: 'robots', body: this.robots});
this.messenger.messageOne(ws, { type: 'objective', body: this.objective}); this.messenger.messageOne(ws, { type: 'objective', body: this.objective});
this.messenger.messageOne(ws, { type: 'state', body: this.state}); this.messenger.messageOne(ws, { type: 'state', body: this.state});
this.messenger.messageOne(ws, { type: 'walls', body: this.walls}); this.messenger.messageOne(ws, { type: 'walls', body: this.walls});
if (this.state === STATE.COUNTDOWN) {
this.messenger.messageOne(ws, { type: 'countdown', body: this.getCountdownStateBody() });
}
if (this.state === STATE.WIN) {
this.messenger.messageOne(ws, { type: 'win', body: this.getWinStateBody() });
}
}; };
Ricochet.prototype.onDisconnect = function(ws) { Ricochet.prototype.onDisconnect = function(ws) {
@ -213,9 +223,7 @@ Ricochet.prototype.msgSkip = function() {
Ricochet.prototype.msgSolve = function(message) { Ricochet.prototype.msgSolve = function(message) {
clearTimeout(this.countdownTimer); clearTimeout(this.countdownTimer);
const duration = 15; this.countdownTimer = setTimeout(this.onCountdownComplete.bind(this), this.countdownDuration * 1000);
this.countdownTimer = setTimeout(this.onCountdownComplete.bind(this), duration * 1000);
this.countdownTimestamp = new Date().getTime(); this.countdownTimestamp = new Date().getTime();
this.state = STATE.COUNTDOWN; this.state = STATE.COUNTDOWN;
@ -224,17 +232,9 @@ Ricochet.prototype.msgSolve = function(message) {
this.winningPlayerId = this.sanitizeId(message.id); this.winningPlayerId = this.sanitizeId(message.id);
console.log("=========", this.winningStack.length, this.robots.length); this.messenger.messageAll({ type: 'state', body: this.state });
this.messenger.messageAll({ this.messenger.messageAll({ type: 'countdown', body: this.getCountdownStateBody() });
type: 'countdown',
body: {
duration,
id: this.winningPlayerId,
moveCount: this.winningStack.length - this.robots.length,
timestamp: this.countdownTimestamp
}
});
}; };
//===== Helper functions //===== Helper functions
@ -267,9 +267,27 @@ Ricochet.prototype.onCountdownComplete = function() {
clearTimeout(this.countdownTimer); clearTimeout(this.countdownTimer);
this.countdownTimestamp = null; this.countdownTimestamp = null;
this.state = STATE.REPLAY; this.state = STATE.WIN;
this.messenger.messageAll({ type: 'state', body: this.state });
this.messenger.messageAll({ type: 'win', body: this.getWinStateBody() });
};
Ricochet.prototype.getCountdownStateBody = function() {
return {
duration: this.countdownDuration,
id: this.winningPlayerId,
moveCount: this.winningStack.length - this.robots.length,
timestamp: this.countdownTimestamp
};
};
this.messenger.messageAll({ type: 'win', body: { id: this.winningPlayerId, stack: this.winningStack } }); Ricochet.prototype.getWinStateBody = function() {
return {
moveCount: this.winningStack.length - this.robots.length,
id: this.winningPlayerId,
stack: this.winningStack
};
}; };
module.exports = Ricochet; module.exports = Ricochet;

@ -149,25 +149,32 @@
margin-top: 16px; margin-top: 16px;
} }
/*===== SOLUTION BLOCK =====*/ /*===== WIN BLOCK =====*/
#controls-solution { #controls-win {
background: #483c6c; background: #483c6c;
border-width: 0; border-width: 0;
color: #F96600; color: #F96600;
} }
#controls-solution-message { #controls-win-message {
color: #ffec83; color: #ffec83;
padding-bottom: 0; padding-bottom: 0;
} }
#controls-solution-count { #controls-win-count {
color: #ffec83; color: #ffec83;
cursor: default; cursor: default;
font-size: 84px; font-size: 84px;
} }
#controls-solution-replay { #controls-win-count::after {
content: 'moves';
display: block;
font-size: 12px;
margin-top: -12px;
}
#controls-win-replay {
} }
/*===== FOOTER BLOCK =====*/ /*===== FOOTER BLOCK =====*/
@ -181,11 +188,17 @@
} }
#controls-footer a { #controls-footer a {
border: 1px solid transparent;
color: #639699; color: #639699;
font-size: 24px; font-size: 24px;
font-weight: bold;
padding: 0 24px;
text-decoration: none; text-decoration: none;
} }
#controls-footer a:hover { #controls-footer a:hover {
text-decoration: underline; background: #ffec83;
} border-color: #ff217c;
color: #ff217c;
font-size: 30px;
}

Loading…
Cancel
Save