Fixes to enforce synchronicity. Fixes to avoid position overlaps.

master
Ben Burlingham 5 years ago
parent 292ed16beb
commit 96b0d73f83
  1. 16
      README.txt
  2. 2
      client/connection.js
  3. 50
      client/controls.js
  4. 52
      client/grid.js
  5. 168
      server/ricochet.js
  6. 2
      socket/server.js
  7. 7
      style/controls.css
  8. 1
      style/index.css

@ -13,22 +13,18 @@ A victory state can be stored by taking a snapshot of the current stack.
Icons from [https://game-icons.net](https://game-icons.net) Icons from [https://game-icons.net](https://game-icons.net)
## TODO ## TODO
- (n solves) in players list, if > 0 - multi solve doesn't check if one solution is better
- robot color update
- test multi solve
- replay stack - replay stack
- slide arrows
- chat box - chat box
- no cancel from name prompt - no cancel from name prompt
- Robots not resizing (shadows do though)
- Robots can gen on top of eachother
- 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
- walls and winstate algorithm
# Final install
- move websocket server to /core - move websocket server to /core
- dynamic socket server resolution - dynamic socket server resolution
- walls and winstate algorithm
- restore name prompt - restore name prompt
# Nice to haves
- shuffle colors
- tutorial - tutorial

@ -14,6 +14,8 @@ const Connection = function() {
this.connect(); this.connect();
}); });
document.addEventListener('L-newround', () => this.send({ type: 'newround' }) );
document.addEventListener('L-objective', () => this.send({ type: 'objective' }) ); document.addEventListener('L-objective', () => this.send({ type: 'objective' }) );
document.addEventListener('L-robots', () => this.send({ type: 'robots' }) ); document.addEventListener('L-robots', () => this.send({ type: 'robots' }) );

50
client/controls.js vendored

@ -90,6 +90,27 @@ Controls.prototype.setState = function(state) {
(STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block'); (STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block');
}; };
Controls.prototype.drawPlayers = function() {
const container = document.getElementById('controls-players');
container.querySelectorAll('.controls-player').forEach(el => {
container.removeChild(el);
});
const keys = Object.keys(this.names);
if (keys.length > 1) {
keys.forEach(connectionId => {
const n = document.createElement('div');
n.innerHTML = this.playerId === connectionId ? `${this.names[connectionId]} (you)` : this.names[connectionId];
n.className = 'controls-player';
container.appendChild(n)
});
}
const msg = container.querySelector('.controls-message');
msg.innerHTML = (keys.length > 1 ? '' : 'Nobody is in the game yet.');
msg.style.display = (keys.length > 1 ? 'none' : 'block');
};
//===== Message handlers //===== Message handlers
Controls.prototype.msgJoin = function() { Controls.prototype.msgJoin = function() {
@ -100,11 +121,11 @@ Controls.prototype.msgJoin = function() {
Controls.prototype.msgComplete = function(evt) { Controls.prototype.msgComplete = function(evt) {
document.getElementById('controls-moves-solve').style.display = (evt.detail.complete ? 'block' : 'none'); document.getElementById('controls-moves-solve').style.display = (evt.detail.complete ? 'block' : 'none');
} };
Controls.prototype.msgConnected = function(evt) { Controls.prototype.msgConnected = function(evt) {
this.playerId = evt.detail.body; this.playerId = evt.detail.body;
console.log("Setting player id to " + this.playerId) this.drawPlayers();
}; };
Controls.prototype.msgConnectionError = function() { Controls.prototype.msgConnectionError = function() {
@ -143,25 +164,7 @@ Controls.prototype.msgStack = function(evt) {
Controls.prototype.msgPlayers = function(evt) { Controls.prototype.msgPlayers = function(evt) {
this.names = evt.detail.body; this.names = evt.detail.body;
this.drawPlayers();
const container = document.getElementById('controls-players');
container.querySelectorAll('.controls-player').forEach(el => {
container.removeChild(el);
});
const keys = Object.keys(this.names);
if (keys.length > 1) {
keys.forEach(connectionId => {
const n = document.createElement('div');
n.innerHTML = this.playerId === connectionId ? `${this.names[connectionId]} (you)` : this.names[connectionId];
n.className = 'controls-player';
container.appendChild(n)
});
}
const msg = container.querySelector('.controls-message');
msg.innerHTML = (keys.length > 1 ? '' : 'Nobody is in the game yet.');
msg.style.display = (keys.length > 1 ? 'none' : 'block');
}; };
Controls.prototype.msgState = function(evt) { Controls.prototype.msgState = function(evt) {
@ -221,6 +224,5 @@ Controls.prototype.onClickWinReplay = function() {
} }
Controls.prototype.onClickWinNext = function() { Controls.prototype.onClickWinNext = function() {
// this.dispatch('L-skip'); this.dispatch('L-newround');
alert('win next') };
}

@ -87,6 +87,9 @@ Grid.prototype.drawRobots = function() {
robot.style.left = `${i * s}px`; robot.style.left = `${i * s}px`;
robot.style.top = `${j * s}px`; robot.style.top = `${j * s}px`;
robot.style.height = `${s}px`;
robot.style.width = `${s}px`;
ids.add(robot.id); ids.add(robot.id);
}); });
@ -111,9 +114,6 @@ Grid.prototype.drawRobot = function({ id, i, j }) {
robot.style.background = color; robot.style.background = color;
robot.style.height = `${s}px`;
robot.style.width = `${s}px`;
grid.appendChild(robot); grid.appendChild(robot);
return robot; return robot;
@ -146,14 +146,30 @@ Grid.prototype.drawShadows = function() {
Grid.prototype.drawArrows = function() { Grid.prototype.drawArrows = function() {
const grid = document.getElementById('content-grid'); const grid = document.getElementById('content-grid');
grid.querySelectorAll('.content-arrows').forEach(el => el.parentNode.removeChild(el));
const s = this.squareSideLength; const s = this.squareSideLength;
const ids = new Set();
this.robots.forEach(({ id, i, j }) => { this.robots.forEach(({ id, i, j }) => {
const arrows = document.createElement('div'); let arrowId = `arrows-${id}`;
arrows.id = `arrows-${id}`; let arrows = document.getElementById(arrowId);
arrows.className = 'content-arrows';
if (!arrows) {
arrows = document.createElement('div');
arrows.id = arrowId;
arrows.className = 'content-arrows';
const up = this.drawArrow({ direction: 'up', label: '▲', left: (s * 1.25), top: (s * 0.5) });
const down = this.drawArrow({ direction: 'down', label: '▼', left: (s * 1.25), top: (s * 2) });
const left = this.drawArrow({ direction: 'left', label: '◀', left: (s * 0.5), top: (s * 1.25) });
const right = this.drawArrow({ direction: 'right', label: '▶', left: (s * 2), top: (s * 1.25) });
arrows.appendChild(up);
arrows.appendChild(down);
arrows.appendChild(left);
arrows.appendChild(right);
grid.appendChild(arrows);
}
arrows.dataset.i = i; arrows.dataset.i = i;
arrows.dataset.j = j; arrows.dataset.j = j;
@ -161,18 +177,14 @@ Grid.prototype.drawArrows = function() {
arrows.style.left = `${i * s - s}px`; arrows.style.left = `${i * s - s}px`;
arrows.style.top = `${j * s - s}px`; arrows.style.top = `${j * s - s}px`;
const up = this.drawArrow({ direction: 'up', label: '▲', left: (s * 1.25), top: (s * 0.5) });
const down = this.drawArrow({ direction: 'down', label: '▼', left: (s * 1.25), top: (s * 2) });
const left = this.drawArrow({ direction: 'left', label: '◀', left: (s * 0.5), top: (s * 1.25) });
const right = this.drawArrow({ direction: 'right', label: '▶', left: (s * 2), top: (s * 1.25) });
arrows.appendChild(up); ids.add(arrowId);
arrows.appendChild(down); });
arrows.appendChild(left);
arrows.appendChild(right); grid.querySelectorAll('.content-arrows').forEach(el => {
if (ids.has(el.id) === false) {
grid.appendChild(arrows); el.parentNode.removeChild(el);
}
}); });
}; };
@ -217,7 +229,7 @@ Grid.prototype.drawObjective = function() {
path.setAttribute('stroke-linejoin', 'null'); path.setAttribute('stroke-linejoin', 'null');
path.setAttribute('stroke-dasharray', 'null'); path.setAttribute('stroke-dasharray', 'null');
path.setAttribute('stroke-width', '0'); path.setAttribute('stroke-width', '0');
path.setAttribute('fill', this.colors[this.objective.id]); path.setAttribute('fill', this.objective.color);
star.appendChild(path); star.appendChild(path);
star.style.position = 'absolute'; star.style.position = 'absolute';

@ -8,10 +8,41 @@ const STATE = {
WIN: 'WIN' WIN: 'WIN'
}; };
const COLORS = [
'#FF6200', // Orange
'#06E746', // Green
'#E370FF', // Purple
'#06D6C4', // Turq
'#ff217c' // Pink
];
const ICONS = [
'assets/comet.svg',
'assets/moon.svg',
'assets/planet.svg',
'assets/rocket.svg',
'assets/spacesuit.svg',
'assets/spider.svg',
'assets/ufo.svg'
];
const Ricochet = function({ messenger }) { const Ricochet = function({ messenger }) {
this.messenger = messenger; this.messenger = messenger;
this.squaresPerSide = 20; this.squaresPerSide = 20;
// Reference lookups
this.robotIds = Array.from(Array(5).keys()).map(_ => uuid.v4());
this.colors = this.robotIds.reduce((acc, id, i) => {
acc[id] = COLORS[i];
return acc;
}, {});
this.icons = this.robotIds.reduce((acc, id, i) => {
acc[id] = ICONS[i];
return acc;
}, {});
// Properties that will be emitted // Properties that will be emitted
this.players = {}; this.players = {};
this.robots = this.freshRobots(); this.robots = this.freshRobots();
@ -78,17 +109,22 @@ Ricochet.prototype.removePlayer = function(id) {
//===== State generators //===== State generators
Ricochet.prototype.freshRobots = function() { Ricochet.prototype.freshRobots = function() {
const robots = ['#E00000', '#00C000', '#0000FF', '#00C0C0', '#F000F0']; const result = [];
const icons = ['assets/comet.svg', 'assets/moon.svg', 'assets/planet.svg', 'assets/rocket.svg', 'assets/spacesuit.svg'];
// spider.svg, ufo.svg for (let k = 0; k < this.robotIds.length; k++) {
const id = this.robotIds[k];
return robots.map((color, idx) => ({ const { i, j } = this.randomUnoccupiedSquare();
i: this.randomSquare(),
j: this.randomSquare(), result.push({
color, i,
id: uuid.v4(), j,
icon: icons[idx] color: this.colors[id],
})); id,
icon: this.icons[id]
});
}
return result;
}; };
Ricochet.prototype.freshWalls = function() { Ricochet.prototype.freshWalls = function() {
@ -112,8 +148,8 @@ Ricochet.prototype.freshWalls = function() {
// DO NUMBER OF CORNERS FIRST AFTER TESTING // DO NUMBER OF CORNERS FIRST AFTER TESTING
for (let n = 0; n < numberOfWalls; n++) { for (let n = 0; n < numberOfWalls; n++) {
const ri = this.randomSquare(); break;
const rj = this.randomSquare(); // const { i: ri, j: rj } = this.randomSquare();
const isHorizontal = Math.random() < 0.5; const isHorizontal = Math.random() < 0.5;
@ -148,30 +184,16 @@ Ricochet.prototype.freshWalls = function() {
}; };
Ricochet.prototype.freshObjective = function() { Ricochet.prototype.freshObjective = function() {
const isValid = ({ i, j }) => { const rand = Math.floor(Math.random() * this.robotIds.length);
// Square has a robot on it. const id = this.robotIds[rand];
for (let k = 0; k < this.robots.length; k++) { const { i, j } = this.randomUnoccupiedSquare();
if (this.robots[k].i === i && this.robots[k].j === j) {
return false; return {
} i,
} j,
id: this.robotIds[0], //id;
return true; color: this.colors[id],
}; };
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) { Ricochet.prototype.onMessage = function(ws, rawBody) {
@ -188,6 +210,7 @@ Ricochet.prototype.onMessage = function(ws, rawBody) {
} }
switch (message.type) { switch (message.type) {
case 'newround': this.msgNewRound(); break;
case 'objective': this.msgObjective(); break; case 'objective': this.msgObjective(); break;
case 'robots': this.msgRobots(); break; case 'robots': this.msgRobots(); break;
case 'skip': this.msgSkip(); break; case 'skip': this.msgSkip(); break;
@ -201,6 +224,34 @@ Ricochet.prototype.onMessage = function(ws, rawBody) {
}; };
//===== Message handlers //===== Message handlers
Ricochet.prototype.msgNewRound = function() {
this.objective = this.freshObjective();
this.state = STATE.PLAY;
const lastPositions = this.winningStack.reduce((acc, v) => {
acc[v.id] = v;
return acc;
}, {})
this.robots = Object.values(lastPositions).map(({ i, j, id }) => ({
i,
j,
id,
color: this.colors[id],
icon: this.icons[id]
}));
this.countdownTimer = null;
this.countdownTimestamp = null;
this.winningPlayerId = null;
this.winningStack = null;
this.messenger.messageAll({ type: 'objective', body: this.objective});
this.messenger.messageAll({ type: 'state', body: this.state});
this.messenger.messageAll({ type: 'robots', body: this.robots});
};
Ricochet.prototype.msgObjective = function() { Ricochet.prototype.msgObjective = function() {
this.objective = this.freshObjective(); this.objective = this.freshObjective();
this.messenger.messageAll({ type: 'objective', body: this.objective }); this.messenger.messageAll({ type: 'objective', body: this.objective });
@ -240,7 +291,50 @@ Ricochet.prototype.msgSolve = function(message) {
//===== Helper functions //===== Helper functions
Ricochet.prototype.randomSquare = function() { Ricochet.prototype.randomSquare = function() {
return Math.floor(Math.random() * this.squaresPerSide); return {
i: Math.floor(Math.random() * this.squaresPerSide),
j: Math.floor(Math.random() * this.squaresPerSide)
};
};
Ricochet.prototype.randomUnoccupiedSquare = function() {
let result = this.randomSquare();
let CONTROL_COUNTER = 0;
while (this.isSquareOccupied(result) && CONTROL_COUNTER < 20) {
CONTROL_COUNTER++;
result = this.randomSquare();
}
if (CONTROL_COUNTER > 19) {
console.log(`==================\n\nCRITICAL ERROR!\n\nrandomUnoccupiedSquare() while() short-circuited!\n\n==================`);
}
return result;
};
Ricochet.prototype.isSquareOccupied = function({ i, j }) {
if (i < 0 || i > this.squaresPerSide) {
return true;
}
if (j < 0 || j > this.squaresPerSide) {
return true;
}
if (this.robots) {
for (let k = 0; k < this.robots.length; k++) {
if (this.robots[k].i === i && this.robots[k].j === j) {
return true;
}
}
}
if (this.objective && i === this.objective.i && j === this.objective.j) {
return true;
}
return false;
}; };
Ricochet.prototype.sanitizeStack = function(rawMoveStack) { Ricochet.prototype.sanitizeStack = function(rawMoveStack) {

@ -17,7 +17,7 @@ Object.keys(apps).forEach((key) => {
if (!valid) { if (!valid) {
delete apps[key]; delete apps[key];
console.log(`Application at ${key} is missing one or more: messenger, onConnect, onMessage, onDisconnect.`); console.log(`==================== \n\nCRITICAL ERROR! \n\nApplication at ${key} is missing one or more: messenger, onConnect, onMessage, onDisconnect.\n\n====================\n`);
} }
}); });

@ -197,8 +197,7 @@
} }
#controls-footer a:hover { #controls-footer a:hover {
background: #ffec83; background: #FFFF00;
border-color: #ff217c; border-color: #23074d;
color: #ff217c; color: #23074d;
font-size: 30px;
} }

@ -24,4 +24,5 @@ fe5e78 SALMON
ffa283 PEACH ffa283 PEACH
F96600 ORANGE F96600 ORANGE
ffec83 YELLOW FFFF00 ffec83 YELLOW FFFF00
06CC83 GREEN
*/ */
Loading…
Cancel
Save