Countdown management complete.

master
Ben Burlingham 5 years ago
parent c20f9d1534
commit 1cf7e0b756
  1. 11
      README.txt
  2. 35
      client/connection.js
  3. 215
      client/controls.js
  4. 36
      client/grid.js
  5. 10
      client/stack.js
  6. 26
      index.html
  7. 264
      server/ricochet.js
  8. 4
      socket/server.js
  9. 37
      style/controls.css
  10. 2
      style/grid.css
  11. 1
      style/index.css

@ -13,16 +13,23 @@ 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
- (n solves) in players list, if > 0
- join mid-countdown
- robot color update
- countdown solution moves value
- countdown solution name value
- better solution
- replay stack
- countdown skip
- slide arrows
- chat box
- no cancel from name prompt
- clear out the trash bins of history (client)
- 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
- limit concurrent players, make sure connections are closed
- move websocket server to /core

@ -2,37 +2,29 @@
const Connection = function() {
// Local event listeners
document.addEventListener('L-guess', (evt) => {
this.ws.send(JSON.stringify({ type: 'guess', rawBody: evt.detail }));
});
document.addEventListener('L-join', (evt) => {
this.connect();
});
document.addEventListener('L-robots', () => {
this.ws.send(JSON.stringify({ type: 'robots' }));
document.addEventListener('L-objective', () => {
this.ws.send(JSON.stringify({ type: 'objective' }));
});
document.addEventListener('L-skip', () => {
this.ws.send(JSON.stringify({ type: 'skip' }));
document.addEventListener('L-robots', () => {
this.ws.send(JSON.stringify({ type: 'robots' }));
});
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' }));
});
document.addEventListener('L-stop', () => {
this.ws.send(JSON.stringify({ type: 'stop' }));
});
document.addEventListener('L-walls', () => {
this.ws.send(JSON.stringify({ type: 'walls' }));
});
document.addEventListener('L-skip', () => {
this.ws.send(JSON.stringify({ type: 'skip' }));
});
};
Connection.prototype.connect = function(){
@ -75,22 +67,17 @@ Connection.prototype.onReceiveMessage = function({ data }) {
let eventName;
switch (msg.type) {
case 'countdown': eventName = 'G-countdown'; break;
case 'connected': eventName = 'G-connected'; break;
case 'guess': eventName = 'G-guess'; break;
case 'countdown': eventName = 'G-countdown'; break;
case 'objective': eventName = 'G-objective'; 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 'state': eventName = 'G-state'; break;
case 'stop': eventName = 'G-stop'; break;
case 'walls': eventName = 'G-walls'; break;
case 'winstate': eventName = 'G-winstate'; break;
}
if (eventName) {
const evt = new CustomEvent(eventName, { detail: msg });
document.dispatchEvent(evt);
}
};
};

215
client/controls.js vendored

@ -1,89 +1,68 @@
//===== Constructor
const Controls = function() {
this.names = {};
this.playerId = null;
this.countdownTimer = null;
// this.starts = [];
// this.timers = {};
// // "Local" and "Global" messages
// "Local" and "Global" messages
document.addEventListener('L-conn-error', this.msgConnectionError.bind(this));
document.addEventListener('L-complete', this.msgComplete.bind(this));
document.addEventListener('L-join', this.msgJoin.bind(this));
document.addEventListener('L-skip', this.msgSkip.bind(this));
document.addEventListener('L-stack', this.msgStack.bind(this));
// document.addEventListener('L-reset', this.msgReset.bind(this));
// document.addEventListener('L-undo', this.msgUndo.bind(this));
// document.addEventListener('G-guess', this.msgGuess.bind(this));
document.addEventListener('G-connected', this.msgCountdown.bind(this));
document.addEventListener('G-countdown', this.msgConnected.bind(this));
document.addEventListener('G-connected', this.msgConnected.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));
document.addEventListener('G-state', this.msgState.bind(this));
// document.addEventListener('G-stop', this.msgStop.bind(this));
// Click handlers
// document.getElementById('controls-reset').addEventListener('click', this.onClickReset.bind(this));
// document.getElementById('controls-robots').addEventListener('click', this.onClickRobots.bind(this));
// 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));
document.getElementById('controls-moves-reset').addEventListener('click', this.onClickMovesReset.bind(this));
document.getElementById('controls-moves-undo').addEventListener('click', this.onClickMovesUndo.bind(this));
document.getElementById('controls-moves-solve').addEventListener('click', this.onClickMovesSolve.bind(this));
document.getElementById('controls-options-objective').addEventListener('click', this.onClickOptionsObjective.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-countdown-skip').addEventListener('click', this.onClickSkip.bind(this));
this.setState('CONNECTING');
}
//===== Countdown
// Controls.prototype.countdownStart = function(seconds) {
// clearTimeout(this.timers.countdown);
// this.timers.countdown = this.countdownTick.bind(this);
Controls.prototype.countdownStart = function(seconds) {
clearTimeout(this.countdownTimer);
this.countdownTimer = this.countdownTick.bind(this);
// const countdown = document.getElementById('controls-countdown');
// countdown.dataset.tick = seconds;
const countdown = document.getElementById('controls-countdown-value');
countdown.dataset.tick = seconds * 1;
// this.countdownTick();
// };
this.countdownTick();
};
// Controls.prototype.countdownTick = function() {
// const countdown = document.getElementById('controls-countdown');
// const tick = countdown.dataset.tick * 1;
// countdown.dataset.tick = tick - 1;
Controls.prototype.countdownTick = function() {
const countdown = document.getElementById('controls-countdown-value');
const tick = countdown.dataset.tick * 1;
countdown.dataset.tick = tick - 1;
// const s = (tick !== 1) ? 's' : '';
// countdown.innerHTML = `${tick} second${s}!`;
countdown.innerHTML = tick;
// if (tick === 0) {
// this.countdownComplete();
// return;
// }
if (tick === 0) {
this.countdownComplete();
return;
}
// this.timers.countdown = setTimeout(this.countdownTick.bind(this), 1000);
// };
this.countdownTimer = setTimeout(this.countdownTick.bind(this), 1000);
};
// Controls.prototype.countdownComplete = function() {
// document.getElementById('controls-countdown').dataset.tick = 0;
// };
Controls.prototype.countdownComplete = function() {
clearTimeout(this.countdownTimer);
document.getElementById('controls-countdown-value').innerHTML = 0;
};
//===== UI
// 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.setState = function(state) {
const blocks = [
'controls-connection',
@ -109,24 +88,49 @@ Controls.prototype.setState = function(state) {
//===== Message handlers
Controls.prototype.msgConnected = function() {
Controls.prototype.msgJoin = function() {
this.setState('CONNECTING');
document.querySelector('#controls-connection .controls-message').innerHTML = 'Connecting...';
};
Controls.prototype.msgComplete = function(evt) {
document.getElementById('controls-moves-solve').style.display = (evt.detail.complete ? 'block' : 'none');
}
Controls.prototype.msgConnected = function(evt) {
this.playerId = evt.detail.body;
console.log("Setting player id to " + this.playerId)
};
Controls.prototype.msgConnectionError = function() {
this.setState('CONNECTING');
document.querySelector('#controls-connection .controls-message').innerHTML = "Can't reach the server.";
};
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];
this.countdownStart(evt.detail.body.duration);
};
Controls.prototype.msgSkip = function() {
this.countdownComplete();
};
Controls.prototype.msgStack = function(evt) {
const robots = evt.detail.reduce((acc, { id }) => acc.has(id) ? acc : acc.add(id), new Set());
const moveCount = evt.detail.length - robots.size;
// document.getElementById('controls-moves').innerHTML = moveCount;
// document.getElementById('controls-undo').style.display = moveCount > 0 ? 'block' : 'none';
document.getElementById('controls-moves-solve').style.display = 'none';
document.getElementById('controls-moves-count').innerHTML = moveCount;
document.getElementById('controls-moves-reset').style.display = moveCount > 0 ? 'block' : 'none';
document.getElementById('controls-moves-undo').style.display = moveCount > 0 ? 'block' : 'none';
};
Controls.prototype.msgPlayers = function(evt) {
@ -141,7 +145,7 @@ Controls.prototype.msgPlayers = function(evt) {
if (keys.length > 1) {
keys.forEach(connectionId => {
const n = document.createElement('div');
n.innerHTML = this.names[connectionId];
n.innerHTML = this.playerId === connectionId ? `${this.names[connectionId]} (you)` : this.names[connectionId];
n.className = 'controls-player';
container.appendChild(n)
});
@ -152,19 +156,10 @@ Controls.prototype.msgPlayers = function(evt) {
msg.style.display = (keys.length > 1 ? 'none' : 'block');
};
Controls.prototype.msgSkip = function() {
// this.coundownComplete();
};
Controls.prototype.msgState = function(evt) {
// this.setState(evt.detail.body);
this.setState('SOLUTION')
this.setState(evt.detail.body);
};
Controls.prototype.msgStop = function() {
//
}
//===== Click handlers
Controls.prototype.dispatch = function(evt, data) {
@ -172,36 +167,38 @@ Controls.prototype.dispatch = function(evt, data) {
document.dispatchEvent(e);
};
Controls.prototype.onClickReset = function() {
this.dispatch('L-reset');
// Options block
Controls.prototype.onClickOptionsObjective = function() {
this.dispatch('L-objective');
};
Controls.prototype.onClickRobots = function() {
Controls.prototype.onClickOptionsRobots = function() {
this.dispatch('L-robots');
};
Controls.prototype.onClickSkip = function() {
this.dispatch('L-skip');
Controls.prototype.onClickOptionsWalls = function() {
this.dispatch('L-walls');
};
Controls.prototype.onClickStart = function() {
this.dispatch('L-start');
};
// Moves block
Controls.prototype.onClickStop = function() {
this.dispatch('L-stop');
Controls.prototype.onClickMovesReset = function() {
this.dispatch('L-reset');
};
Controls.prototype.onClickSubmit = function() {
Controls.prototype.onClickMovesSolve = function() {
this.dispatch('L-submit');
};
Controls.prototype.onClickUndo = function() {
Controls.prototype.onClickMovesUndo = function() {
this.dispatch('L-undo');
};
Controls.prototype.onClickWalls = function() {
this.dispatch('L-walls');
// Countdown block
Controls.prototype.onClickSkip = function() {
this.dispatch('L-skip');
};
//===== THE TRASH BIN OF HISTORY
@ -256,4 +253,40 @@ Controls.prototype.onClickWalls = function() {
// 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));

@ -7,7 +7,7 @@ const Grid = function() {
this.robots = [];
this.shadows = [];
this.walls = [];
this.winstate = {};
this.objective = {};
this.squaresPerSide = 20;
this.squareSideLength = 0;
@ -17,7 +17,7 @@ const Grid = function() {
document.addEventListener('G-robots', this.msgRobots.bind(this));
document.addEventListener('G-walls', this.msgWalls.bind(this));
document.addEventListener('G-winstate', this.msgWinState.bind(this));
document.addEventListener('G-objective', this.msgObjective.bind(this));
window.addEventListener('resize', this.debounce(this.onResize.bind(this), 500));
@ -197,18 +197,19 @@ Grid.prototype.drawArrow = function({ direction, label, left, top, parentId }) {
return arrow;
};
Grid.prototype.drawWinState = function() {
if (this.winstate.i === undefined || this.winstate.j === undefined) {
Grid.prototype.drawObjective = function() {
if (this.objective.i === undefined || this.objective.j === undefined) {
return;
}
const s = this.squareSideLength;
const grid = document.getElementById('content-grid');
grid.querySelectorAll('.content-winstate').forEach(el => el.parentNode.removeChild(el));
document.getElementById('content-objective') && grid.removeChild(document.getElementById('content-objective'));
const star = document.createElementNS("http://www.w3.org/2000/svg", "svg");
star.setAttribute("viewBox", '0 0 100 100');
star.id = 'content-objective';
const path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
path.setAttribute('d', "M 0,38 L 38,38 L 49,0 L 60,38 L 100,38 L 68,66 L 77,100 L 47,79 L 17,100 L 28,64 Z");
@ -216,13 +217,12 @@ Grid.prototype.drawWinState = function() {
path.setAttribute('stroke-linejoin', 'null');
path.setAttribute('stroke-dasharray', 'null');
path.setAttribute('stroke-width', '0');
path.setAttribute('fill', this.colors[this.winstate.id]);
path.setAttribute('fill', this.colors[this.objective.id]);
star.appendChild(path);
star.style.position = 'absolute';
star.style.zIndex = 100;
star.style.left = '50%'
star.style.top = '50%'
star.style.left = `${s * this.objective.i}px`
star.style.top = `${s * this.objective.j}px`
star.style.height = `${s}px`;
star.style.width = `${s}px`;
@ -317,12 +317,10 @@ Grid.prototype.findNextObstacle = function({ direction, i, j }) {
throw Error("Could not find next obstacle, no direction found. ", direction, i, j);
};
Grid.prototype.checkWin = function({ id, i, j }) {
if (i !== this.winstate.i || j !== this.winstate.j || id !== this.winstate.id) {
return;
}
Grid.prototype.checkObjective = function({ id, i, j }) {
const complete = (i === this.objective.i && j === this.objective.j && id === this.objective.id);
const evtSolve = new Event('L-solve');
const evtSolve = new CustomEvent('L-complete', { detail: { complete }});
document.dispatchEvent(evtSolve);
};
@ -341,7 +339,7 @@ Grid.prototype.onArrowClick = function(evt) {
const evtMove = new CustomEvent('L-arrow', { detail: { id, i, j } });
document.dispatchEvent(evtMove);
this.checkWin({ id, i, j });
this.checkObjective({ id, i, j });
};
Grid.prototype.onResize = function() {
@ -372,7 +370,7 @@ Grid.prototype.onResize = function() {
this.drawRobots();
this.drawArrows();
this.drawShadows();
this.drawWinState();
this.drawObjective();
this.updateArrowVisibilities();
};
@ -428,7 +426,7 @@ Grid.prototype.msgWalls = function(evt) {
this.updateArrowVisibilities();
};
Grid.prototype.msgWinState = function(evt) {
this.winstate = evt.detail.body;
this.drawWinState();
Grid.prototype.msgObjective = function(evt) {
this.objective = evt.detail.body;
this.drawObjective();
};

@ -3,12 +3,14 @@ const Stack = function() {
// This is the heart of the robot movement architecture.
// Its elements are of the form { robotId, i, j }
this.moves = [];
this.playerId = null;
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));
document.addEventListener('G-connected', this.msgConnected.bind(this));
document.addEventListener('G-robots', this.msgRobots.bind(this));
};
@ -24,6 +26,10 @@ Stack.prototype.getInitialPositions = function() {
return Object.values(moves);
};
Stack.prototype.msgConnected = function(evt) {
this.playerId = evt.detail.body;
};
Stack.prototype.msgRobots = function(evt) {
this.moves = evt.detail.body.map(({ id, i, j }) => ({ id, i, j }));
@ -49,9 +55,7 @@ Stack.prototype.msgReset = function() {
};
Stack.prototype.msgSubmit = function() {
this.moves = this.getInitialPositions();
const evtSolve = new CustomEvent('L-solve', { detail: this.moves });
const evtSolve = new CustomEvent('L-solve', { detail: { stack: this.moves, id: this.playerId } });
document.dispatchEvent(evtSolve);
};

@ -23,36 +23,38 @@
<div class='controls-block' id="controls-moves">
<div class="controls-title">Moves</div>
<div id='controls-moves-count'>24</div>
<div id='controls-moves-count'></div>
<div class="controls-moves-buttons-layout">
<div class='controls-button' id='controls-undo'>Undo</div>
<div class='controls-button' id='controls-reset'>Reset</div>
<div class='controls-button' id='controls-moves-reset'>Reset</div>
<div class='controls-button' id='controls-moves-undo'>Undo</div>
</div>
<div class='controls-button' id='controls-submit'>Solve the puzzle</div>
<div class='controls-button' id='controls-moves-solve'>Solve the puzzle</div>
</div>
<div class='controls-block' id="controls-options">
<div class="controls-title">Options</div>
<div class='controls-button' id='controls-robots'>Move Robots</div>
<div class='controls-button' id='controls-walls'>Move Walls</div>
<div class='controls-button' id='controls-options-robots'>Move Robots</div>
<div class='controls-button' id='controls-options-walls'>Move Walls</div>
<div class='controls-button' id='controls-options-objective'>Move Objective</div>
<div class="controls-message">Careful - these affect all players!</div>
</div>
<div class='controls-block' id='controls-connection'>
<div class="controls-message" id="controls-connecting">Connecting...</div>
<div class="controls-message" id='controls-error'>Can't reach the server.</div>
<div class="controls-title">Connection</div>
<div class="controls-message"></div>
</div>
<div class='controls-block' id="controls-countdown">
<div class='controls-title'>Aardvark Matthews Dunkirk has a solution!</div>
<div class='controls-title'><span id='controls-countdown-player'></span> has a solution!</div>
<div class="controls-countdown-values-layout">
<div id="controls-solution-value">11</div>
<div class="controls-countdown-layout">
<div id="controls-countdown-moves">11</div>
<div id="controls-countdown-value">19</div>
</div>
<div class='controls-button' id='controls-skip'>Skip</div>
<div class='controls-button' id='controls-countdown-skip'>Skip</div>
</div>
<div class='controls-block' id="controls-solution">

@ -5,7 +5,7 @@ const DEBUG = (process.env.NODE_ENV !== "production");
const STATE = {
COUNTDOWN: 'COUNTDOWN',
PLAY: 'PLAY',
SOLUTION: 'SOLUTION'
REPLAY: 'REPLAY'
};
const Ricochet = function({ messenger }) {
@ -17,13 +17,17 @@ const Ricochet = function({ messenger }) {
this.robots = this.freshRobots();
this.state = STATE.PLAY;
this.walls = this.freshWalls();
this.objective = this.freshObjective();
// this.id = uuid.v4();
// this.countdownTimer = null;
// this.guess = Infinity;
// this.winningStack;
this.countdownTimer = null;
this.countdownTimestamp = null;
this.winningPlayerId = null;
this.winningStack = null;
};
//===== Connection management
Ricochet.prototype.onConnect = function(ws, req) {
const url = UrlParser.parse(req.url, true);
const query = url.query;
@ -33,16 +37,12 @@ Ricochet.prototype.onConnect = function(ws, req) {
this.addPlayer(ws.id, santizedName);
this.messenger.messageOne(ws, { type: 'connected', body: ws.id});
this.messenger.messageAll({ type: 'players', body: this.players });
this.messenger.messageOne(ws, { type: 'robots', body: this.robots});
this.messenger.messageOne(ws, { type: 'connected', body: ws.id});
this.messenger.messageOne(ws, { type: 'objective', body: this.objective});
this.messenger.messageOne(ws, { type: 'state', body: this.state});
this.messenger.messageOne(ws, { type: 'walls', body: this.walls});
// this.messenger.messageOne(ws, { type: 'winstate', body: G.getWinState()});
}
Ricochet.prototype.onMessage = function(ws, json) {
};
Ricochet.prototype.onDisconnect = function(ws) {
@ -64,13 +64,21 @@ Ricochet.prototype.removePlayer = function(id) {
delete this.players[id];
};
//===== State generators
Ricochet.prototype.freshRobots = function() {
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
// spider.svg, ufo.svg
const gen = () => Math.floor(Math.random() * this.squaresPerSide);
return robots.map((color, idx) => ({ i: gen(), j: gen(), color, id: uuid.v4(), icon: icons[idx] }));
return robots.map((color, idx) => ({
i: this.randomSquare(),
j: this.randomSquare(),
color,
id: uuid.v4(),
icon: icons[idx]
}));
};
Ricochet.prototype.freshWalls = function() {
@ -90,13 +98,13 @@ Ricochet.prototype.freshWalls = function() {
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 ri = this.randomSquare();
const rj = this.randomSquare();
const isHorizontal = Math.random() < 0.5;
const isBackward = Math.random() < 0.5;
@ -129,89 +137,139 @@ Ricochet.prototype.freshWalls = function() {
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 = Ricochet;
Ricochet.prototype.freshObjective = function() {
const isValid = ({ i, j }) => {
// Square has a robot on it.
for (let k = 0; k < this.robots.length; k++) {
if (this.robots[k].i === i && this.robots[k].j === j) {
return false;
}
}
return true;
};
const objective = { i: this.robots[0].i, j: this.robots[0].j };
let counter = 0;
while (isValid(objective) === false && counter < 20) {
counter++;
objective.i = this.randomSquare();
objective.j = this.randomSquare();
}
const rand = Math.floor(Math.random() * this.robots.length);
objective.id = this.robots[0].id;
// objective.id = this.robots[rand].id;
return objective;
};
Ricochet.prototype.onMessage = function(ws, rawBody) {
try {
const message = JSON.parse(rawBody);
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 'objective': this.msgObjective(); break;
case 'robots': this.msgRobots(); break;
case 'skip': this.msgSkip(); break;
case 'solve': this.msgSolve(message.rawBody); break;
case 'walls': this.msgWalls(); break;
default: console.warn("Unknown message type: ", message.type)
}
} catch (error) {
console.error(error);
}
};
//===== Message handlers
Ricochet.prototype.msgObjective = function() {
this.objective = this.freshObjective();
this.messenger.messageAll({ type: 'objective', body: this.objective });
};
Ricochet.prototype.msgRobots = function() {
this.robots = this.freshRobots();
this.messenger.messageAll({ type: 'robots', body: this.robots});
};
Ricochet.prototype.msgWalls = function() {
this.walls = this.freshWalls();
this.messenger.messageAll({ type: 'walls', body: this.walls });
};
Ricochet.prototype.msgSkip = function() {
this.onCountdownComplete();
};
Ricochet.prototype.msgSolve = function(message) {
clearTimeout(this.countdownTimer);
const duration = 15;
this.countdownTimer = setTimeout(this.onCountdownComplete.bind(this), duration * 1000);
this.countdownTimestamp = new Date().getTime();
this.state = STATE.COUNTDOWN;
this.winningStack = this.sanitizeStack(message.stack);
this.winningPlayerId = this.sanitizeId(message.id);
console.log("=========", this.winningStack.length, this.robots.length);
this.messenger.messageAll({
type: 'countdown',
body: {
duration,
id: this.winningPlayerId,
moveCount: this.winningStack.length - this.robots.length,
timestamp: this.countdownTimestamp
}
});
};
//===== Helper functions
Ricochet.prototype.randomSquare = function() {
return Math.floor(Math.random() * this.squaresPerSide);
};
Ricochet.prototype.sanitizeStack = function(rawMoveStack) {
const sanitizedStack = rawMoveStack.map(move => {
const sanitizedRobotId = this.sanitizeId(move.id);
const sanitizedI = move.i * 1;
const sanitizedJ = move.j * 1;
return {
i: sanitizedI,
j: sanitizedJ,
id: sanitizedRobotId
};
});
return sanitizedStack;
};
Ricochet.prototype.sanitizeId = function(id) {
return id.replace(/[^0-9a-zA-Z\-]/g, '');
};
Ricochet.prototype.onCountdownComplete = function() {
clearTimeout(this.countdownTimer);
this.countdownTimestamp = null;
this.state = STATE.REPLAY;
this.messenger.messageAll({ type: 'win', body: { id: this.winningPlayerId, stack: this.winningStack } });
};
module.exports = Ricochet;

@ -33,7 +33,9 @@ const onConnect = (ws, req) => {
app.onConnect(ws, req);
ws.on('message', app.onMessage);
ws.on('message', (rawBody) => {
app.onMessage(ws, rawBody);
});
ws.on('close', () => {
app.onDisconnect(ws);

@ -18,6 +18,7 @@
overflow: hidden;
padding-bottom: 24px;
position: relative;
text-align: center;
}
.controls-title {
@ -26,6 +27,7 @@
font-size: 14px;
margin-bottom: 16px;
padding: 8px 24px;
text-align: left;
}
.controls-message {
@ -37,7 +39,6 @@
.controls-button {
cursor: pointer;
padding: 8px 0;
text-align: center;
}
.controls-button:hover {
@ -66,7 +67,6 @@
#controls-moves-count {
cursor: default;
font-size: 84px;
text-align: center;
}
.controls-moves-buttons-layout {
@ -74,14 +74,14 @@
flex-direction: row;
}
#controls-reset,
#controls-undo {
#controls-moves-reset,
#controls-moves-undo {
flex: 1;
font-size: 12px;
padding: 12px;
}
#controls-submit {
#controls-moves-solve {
font-size: 24px;
padding: 8px 0;
}
@ -95,6 +95,12 @@
padding: 8px 24px;
}
#controls-options .controls-message {
color: #ffa283;
font-size: 12px;
padding: 16px 0 0 0;
}
/*===== COUNTDOWN BLOCK =====*/
#controls-countdown {
background: #F96600;
@ -108,7 +114,7 @@
color: #ffec83;
}
.controls-countdown-values-layout {
.controls-countdown-layout {
display: flex;
flex-direction: row;
}
@ -117,7 +123,6 @@
cursor: default;
flex: 1;
font-size: 64px;
text-align: center;
}
#controls-countdown-value::after {
@ -127,21 +132,20 @@
margin-top: -12px;
}
#controls-solution-value {
#controls-countdown-moves {
cursor: default;
flex: 1;
font-size: 64px;
text-align: center;
}
#controls-solution-value::after {
#controls-countdown-moves::after {
content: 'moves';
display: block;
font-size: 12px;
margin-top: -12px;
}
#controls-skip {
#controls-countdown-skip {
margin-top: 16px;
}
@ -150,7 +154,6 @@
background: #483c6c;
border-width: 0;
color: #F96600;
text-align: center;
}
#controls-solution-message {
@ -158,16 +161,6 @@
padding-bottom: 0;
}
/*
0cffe1 CYAN 00dfd4
ff217c PINK
483c6c PURPLE 23074d
fe5e78 SALMON
ffa283 PEACH
F96600 ORANGE
ffec83 YELLOW FFFF00
*/
#controls-solution-count {
color: #ffec83;
cursor: default;

@ -49,7 +49,7 @@
z-index: 1;
}
.content-winstate {
#content-objective {
position: absolute;
z-index: 1;
}

@ -1,6 +1,7 @@
* {
box-sizing: border-box;
font-family: Days One;
user-select: none;
}
body {

Loading…
Cancel
Save