From c20f9d153422a8029036f5b680b7e7bc1655d629 Mon Sep 17 00:00:00 2001 From: Ben Burlingham Date: Sun, 28 Jun 2020 11:05:22 -0700 Subject: [PATCH] UI overhaul. Centralized game state. --- README.txt | 11 ++- assets/gas-giant.png | Bin 0 -> 31197 bytes assets/stars.png | Bin 0 -> 8875 bytes client/connection.js | 6 +- client/controls.js | 121 +++++++++++++---------- client/grid.js | 32 ------ client/join.js | 52 ---------- controls.css | 100 ------------------- index.css | 16 --- index.html | 159 +++++++++++++---------------- join.css | 102 ------------------- server/ricochet.js | 163 +++++++++++++++--------------- socket/messenger.js | 6 +- style/controls.css | 198 +++++++++++++++++++++++++++++++++++++ grid.css => style/grid.css | 2 - style/index.css | 26 +++++ 16 files changed, 463 insertions(+), 531 deletions(-) create mode 100644 assets/gas-giant.png create mode 100644 assets/stars.png delete mode 100644 client/join.js delete mode 100644 controls.css delete mode 100644 index.css delete mode 100644 join.css create mode 100644 style/controls.css rename grid.css => style/grid.css (92%) create mode 100644 style/index.css diff --git a/README.txt b/README.txt index 606bb0d..a2a70e0 100644 --- a/README.txt +++ b/README.txt @@ -9,23 +9,26 @@ Any movement, including initial locations, is represented by pushing or popping A victory state can be stored by taking a snapshot of the current stack. -## Icons +## Credit Icons from [https://game-icons.net](https://game-icons.net) ## TODO - win declare - replay stack -- countdown skip -- Extract loader from join.css and kill join.css, join.js +- countdown skip - slide arrows - chat box - no cancel from name prompt -- restore state on join +- clear out the trash bins of history (client) +- handle conn interrupt +- donate link heart fill in not underline +- Robots not resizing (shadows do though) - limit concurrent players, make sure connections are closed - move websocket server to /core - dynamic socket server resolution - walls and winstate algorithm +- restore name prompt - tutorial - donate link diff --git a/assets/gas-giant.png b/assets/gas-giant.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a4551855a9636c8e2fc6eb9f57e8d09b21a661 GIT binary patch literal 31197 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%0A2%AyXo6ROrnQuQCi?|tHbb`Gm zl2J*wqIc?r+yYZ+P;Xs_y$V(}>>>nW8=C{vo>uwFU zUid7v0rZ=(S9r}eh_*~$qe#0nQb51jQI9t7%0yjU$$~Opx8}~B>tV5mCW}T3kpc!c z3(}3PC$=e}eB1v>UiHO&;RjhgVmE%U1{KSVTOf5FW3;`J+Fy9}dG=PL&Lc~Uu>)*_ zV(WL6*hW`F8?Hq%SO{Fਭ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 M07*qoM6N<$g7CN2SO5S3 literal 0 HcmV?d00001 diff --git a/assets/stars.png b/assets/stars.png new file mode 100644 index 0000000000000000000000000000000000000000..9fdb59c2e67bdabdee47ac380d7543dcff2b73b3 GIT binary patch literal 8875 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 Mrz%@2V-ooP05t8gA^-pY literal 0 HcmV?d00001 diff --git a/client/connection.js b/client/connection.js index a877f8b..7bf511d 100644 --- a/client/connection.js +++ b/client/connection.js @@ -39,7 +39,8 @@ Connection.prototype.connect = function(){ const names = ["Biff", "Morty", "Herb", "Chester", "Lyle", "Cap", "Dale", "Ned", "Mindy", "Frankie", "Gabriel", "Mona", "Dolores", "Sepulveda", "Venus", "Blingbing", "Cyrpt"] const r = Math.floor(Math.random() * names.length); - const rawInput = names[r] //prompt("What is your name?"); + const rawInput = names[r] + // const rawInput = prompt("What is your name?"); this.ws = new WebSocket('ws://localhost:8080/ricochet?name=' + rawInput); @@ -74,14 +75,15 @@ Connection.prototype.onReceiveMessage = function({ data }) { let eventName; switch (msg.type) { - case 'connected': eventName = 'G-connected'; break; case 'countdown': eventName = 'G-countdown'; break; + case 'connected': eventName = 'G-connected'; break; case 'guess': eventName = 'G-guess'; 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; diff --git a/client/controls.js b/client/controls.js index 9f3ba5d..d7ffb14 100644 --- a/client/controls.js +++ b/client/controls.js @@ -4,35 +4,35 @@ const Controls = function() { - const evt = new Event('L-join'); - document.dispatchEvent(evt); - - // this.starts = []; // this.timers = {}; // // "Local" and "Global" messages -// document.addEventListener('L-connected', this.msgConnected.bind(this)); + document.addEventListener('L-conn-error', this.msgConnectionError.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-countdown', this.msgCountdown.bind(this)); + document.addEventListener('G-connected', this.msgCountdown.bind(this)); + document.addEventListener('G-countdown', this.msgConnected.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-stop', this.msgStop.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-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)); + + this.setState('CONNECTING'); } //===== Countdown @@ -84,62 +84,81 @@ const Controls = function() { // document.getElementById('controls-panic').style.display = ''; // }; +Controls.prototype.setState = function(state) { + const blocks = [ + 'controls-connection', + 'controls-countdown', + 'controls-moves', + 'controls-options', + 'controls-players', + 'controls-solution', + ]; + + blocks.forEach(id => document.getElementById(id).style.display = 'none'); + + // IDs to show for each state. + const STATE = { + CONNECTING: ['controls-connection'], + PLAY: ['controls-players', 'controls-moves', 'controls-options'], + COUNTDOWN: ['controls-players', 'controls-moves', 'controls-countdown'], + SOLUTION: ['controls-players', 'controls-solution'] + }; + + (STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block'); +}; + //===== Message handlers +Controls.prototype.msgConnected = function() { + +}; + +Controls.prototype.msgConnectionError = function() { + this.setState('CONNECTING'); +}; + Controls.prototype.msgCountdown = function(evt) { - // this.showWaiting(); - console.error(evt); - alert("COUNTDOWN RECEIVED"); + }; 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').innerHTML = moveCount; + // document.getElementById('controls-undo').style.display = moveCount > 0 ? 'block' : 'none'; }; Controls.prototype.msgPlayers = function(evt) { - const container = document.getElementById('controls-players'); - const names = evt.detail.body; - const keys = Object.keys(names); - - if (keys.length > 0) { - const nobody = document.getElementById('controls-players-nobody'); - nobody && nobody.parentNode.removeChild(nobody); - } + this.names = evt.detail.body; - keys.forEach(connectionId => { - const id = `player-${connectionId}`; - - if (document.getElementById(id)) { - return; - } - - const n = document.createElement('div'); - n.id = id; - n.innerHTML = names[connectionId]; - n.className = 'controls-player'; - container.appendChild(n) + const container = document.getElementById('controls-players'); + container.querySelectorAll('.controls-player').forEach(el => { + container.removeChild(el); }); - this.names = names; + const keys = Object.keys(this.names); + if (keys.length > 1) { + keys.forEach(connectionId => { + const n = document.createElement('div'); + n.innerHTML = this.names[connectionId]; + n.className = 'controls-player'; + container.appendChild(n) + }); + } - container.querySelectorAll('.controls-player').forEach(el => { - const id = el.id.split('player-').pop(); - if (!this.names[id]) { - container.removeChild(el); - } - }); + 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.msgSkip = function() { // this.coundownComplete(); }; -Controls.prototype.msgStart = function() { - // +Controls.prototype.msgState = function(evt) { + // this.setState(evt.detail.body); + this.setState('SOLUTION') }; Controls.prototype.msgStop = function() { diff --git a/client/grid.js b/client/grid.js index c716211..bec1af2 100644 --- a/client/grid.js +++ b/client/grid.js @@ -432,35 +432,3 @@ Grid.prototype.msgWinState = function(evt) { this.winstate = evt.detail.body; this.drawWinState(); }; - -//============ THE TRASH BIN OF HISTORY -// Content.prototype.drawRobot = function({ id, i, j }) { -// const robot = document.getElementById(`robot-${id}`); -// const arrows = document.getElementById(`arrows-${id}`); - -// const { x, y } = this.ijToXy({ i, j }); -// const s = this.squares.sideLength; - -// robot.style.display = 'block'; -// robot.style.left = x + 'px'; -// robot.style.top = y + 'px'; -// robot.dataset.i = i; -// robot.dataset.j = j; - -// this.robots[`${i}-${j}`] = id; - -// arrows.style.display = 'block'; -// arrows.style.left = (x - s) + 'px'; -// arrows.style.top = (y - s) + 'px'; -// }; - -// Content.prototype.ijToXy = function({ i, j }) { -// if ((i !== 0 && !i) || (j !== 0 && !j)) { -// return -// } - -// return { -// x: this.squares.x0 + i * this.squares.sideLength, -// y: this.squares.y0 + j * this.squares.sideLength -// } -// }; \ No newline at end of file diff --git a/client/join.js b/client/join.js deleted file mode 100644 index 6167789..0000000 --- a/client/join.js +++ /dev/null @@ -1,52 +0,0 @@ -const Join = function() { - document.addEventListener('L-conn-error', this.onConnectionError.bind(this)); - document.addEventListener('L-conn-open', this.onConnectionOpen.bind(this)); - - document.getElementById('join-setup-start').addEventListener('click', this.onClickStart.bind(this)); - document.getElementById('join-setup-go').addEventListener('click', this.onClickStart.bind(this)); - document.getElementById('join-setup-back').addEventListener('click', this.onClickBack.bind(this)); - - this.showSetup(); -}; - -Join.prototype.showSetup = function() { - document.getElementById('join-setup').style.display = 'block'; - document.getElementById('join-connect').style.display = 'none'; - document.getElementById('join-error').style.display = 'none'; -}; - -Join.prototype.showLoad = function() { - document.getElementById('join-setup').style.display = 'none'; - document.getElementById('join-connect').style.display = 'block'; - document.getElementById('join-error').style.display = 'none'; -}; - -Join.prototype.showError = function() { - document.getElementById('join-setup').style.display = 'none'; - document.getElementById('join-connect').style.display = 'none'; - document.getElementById('join-error').style.display = 'block'; -}; - -Join.prototype.onClickBack = function() { - this.showSetup(); -}; - -Join.prototype.onClickGo = function() { - this.showLoad(); -}; - -Join.prototype.onClickStart = function() { - this.showLoad(); - - const evt = new Event('L-join'); - document.dispatchEvent(evt); -}; - -Join.prototype.onConnectionError = function() { - this.showError(); -}; - -Join.prototype.onConnectionOpen = function() { - document.getElementById('join').style.display = 'none'; - this.showSetup(); -}; \ No newline at end of file diff --git a/controls.css b/controls.css deleted file mode 100644 index 3d48329..0000000 --- a/controls.css +++ /dev/null @@ -1,100 +0,0 @@ -#controls-container { - background: #e7e7e7; - border: solid #e7e7e7; - border-width: 0 10px; - bottom: 0; - left: 20px; - position: absolute; - top: 0; - width: 300px; - z-index: 1; -} - -.controls-title { - background-color: #639978; - background-image: url('sprite-robots.png'); - /*color: #fff;*/ - font-size: 12px; - line-height: 48px; - margin-bottom: 24px; - text-align: center; -} - -.controls-subtitle { - background-color: #4D3243; - background-image: linear-gradient(90deg, #4D3243, #3C2132); - color: #fff; - padding: 12px; - margin: 24px 0 12px 0; -} - -.controls-alert-info { - background: #21dfae; - color: #222; - padding: 12px; - margin: 24px 0 12px 0; -} - -.controls-alert-urgent { - background: #DE4F21; - color: #fff; - padding: 12px; - margin: 24px 0 12px 0; -} - -.controls-row { - align-items: center; - display: flex; - flex-direction: row; - padding: 8px; -} - -.controls-row :nth-child(2) { - flex: 1; - margin: 0 8px; - width: 100%; -} - -.controls-player { - padding: 8px; -} - -#controls-footer { - bottom: 0; - left: 0; - padding: 24px; - position: absolute; - right: 0; - text-align: center; -} - -#controls-footer a { - color: #639699; - font-size: 24px; - text-decoration: none; -} - -#controls-footer a:hover { - text-decoration: underline; -} - -.controls-button { - background: none; - border: 1px solid #888; - color: #444; - cursor: pointer; - font-size: 12px; - padding: 4px 8px; -} - -.controls-button:hover { - background: #21dfae; - border-color: #21dfae; - color: #222; -} - -#controls-start { - display: block; - margin: 0 auto; - padding: 4px 8px; -} \ No newline at end of file diff --git a/index.css b/index.css deleted file mode 100644 index 07e9fe9..0000000 --- a/index.css +++ /dev/null @@ -1,16 +0,0 @@ -* { - box-sizing: border-box; - font-family: Montserrat; -} - -body { - overflow: hidden; -} - -/* -PALETTE: -21DFAE Aquamarine -4D3243 Plum -DE4F21 Fire -639699 Cadet -*/ diff --git a/index.html b/index.html index fd6faca..df72504 100644 --- a/index.html +++ b/index.html @@ -1,115 +1,92 @@ - - - - Robots - - - - - - - - - - - - - - -
-
-
Global
+
+
Undo
+
Reset
+
-
-
Start Next Round
-
 
-
Do It
+
Solve the puzzle
-
-
Stop This Round
-
 
-
Yeah
+
+
Options
+
Move Robots
+
Move Walls
-
-
Move Robots
-
 
-
Reposition
+
+
Connecting...
+
Can't reach the server.
-
-
Move Walls
-
 
-
Regenerate
-
-
+
+
Aardvark Matthews Dunkirk has a solution!
+ +
+
11
+
19
+
-
-
Players
+
Skip
+
-
Nobody is in the game yet.
-
- +
+
Round over
+
Congrats Aardvark Matthews Dunkirk!
-
-
Local
-
-
Moves:
-
-
Undo
-
Reset
-
+
24
- +
Replay
-
-
+
Start next round
+
-
-
Countdown:
-
30 seconds!
-
Skip
-
+
- -
-
-
+ - + // Entry point. + const evt = new Event('L-join'); + document.dispatchEvent(evt); + }) + + \ No newline at end of file diff --git a/join.css b/join.css deleted file mode 100644 index 85f03dd..0000000 --- a/join.css +++ /dev/null @@ -1,102 +0,0 @@ -#join { - background-color: #28313b; - background-image: linear-gradient(315deg, #28313b 0%, #1A2026 74%); - bottom: 0; - color: #e7e7e7; - display: flex; - flex-direction: column; - justify-content: center; - left: 0; - position: absolute; - right: 0; - text-align: center; - top: 0; - z-index: 10; -} - -.join-button { - background: none; - border: 1px solid #888; - color: #ccc; - cursor: pointer; - font-size: 32px; - height: 72px; - line-height: 72px; - vertical-align: middle; -} - -.join-button:hover { - background: #21dfae; - border-color: #21dfae; - color: #222; -} - -.join-message { - font-size: 24px; - line-height: 72px; -} - -#join-setup-start, -#join-setup-back { - width: 500px; -} - -#join-setup-go { - width: 96px; -} - -#join-setup .join-message { - margin: 24px 0; -} - -#join-setup input { - border: 0; - font-size: 32px; - height: 72px; - line-height: 72px; - margin-right: 40px; - padding: 0 24px; - vertical-align: middle; - width: 360px; -} - -@keyframes LoadingBackground { - 0%{background-position:0% 50%} - 50%{background-position:100% 50%} - 100%{background-position:0% 50%} -} - -#join-connect { - animation: LoadingBackground 8s infinite; - background-size: 230% 230%; - background-image: linear-gradient(90deg, #21DFAE 600px, #FFF 600px, #FFF 605px, #21DFAE 605px); - color: #222; - display: none; - font-size: 32px; - height: 72px; - margin: 0 auto; - text-align: center; - width: 500px; -} - -#join-error { - display: none; - font-size: 32px; - height: 264px; - line-height: 264px; - margin: 0 auto; - position: relative; - text-align: center; - width: 500px; -} - -#join-error .join-message { - background-color: #DE4F21; - color: #fff; -} - -#join-error button { - position: absolute; - bottom: 0; - left: 0; -} diff --git a/server/ricochet.js b/server/ricochet.js index 1dec405..df60f41 100644 --- a/server/ricochet.js +++ b/server/ricochet.js @@ -1,39 +1,44 @@ const uuid = require('node-uuid'); +const UrlParser = require('url'); const DEBUG = (process.env.NODE_ENV !== "production"); +const STATE = { + COUNTDOWN: 'COUNTDOWN', + PLAY: 'PLAY', + SOLUTION: 'SOLUTION' +}; + const Ricochet = function({ messenger }) { this.messenger = messenger; + this.squaresPerSide = 20; + + // Properties that will be emitted this.players = {}; + this.robots = this.freshRobots(); + this.state = STATE.PLAY; + this.walls = this.freshWalls(); // this.id = uuid.v4(); // this.countdownTimer = null; // this.guess = Infinity; - // this.squaresPerSide = 20; // this.winningStack; - - // 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 - - // const gen = () => Math.floor(Math.random() * this.squaresPerSide); - // this.TEMP_ROBOTS = robots.map((color, idx) => ({ i: gen(), j: gen(), color, id: uuid.v4(), icon: icons[idx] })); }; Ricochet.prototype.onConnect = function(ws, req) { - // const query = url.query; - // const santizedName = (query.name || 'Unknown').replace(/[^\w ]/g, ''); - - // DEBUG && console.log("Connected:"); - // DEBUG && console.log (`${santizedName} ${ws.id} via ${req.url}`); + const url = UrlParser.parse(req.url, true); + const query = url.query; + const santizedName = (query.name || 'Unknown').replace(/[^\w ]/g, ''); - // this.addPlayer(ws.id, santizedName); + DEBUG && console.log(`Connected: ${santizedName} (${ws.id.substr(0, 8)}) via ${req.url}`); - // this.messenger.messageAll({ type: 'players', body: this.players }); + this.addPlayer(ws.id, santizedName); - // this.messenger.messageOne(ws, { type: 'walls', body: G.getWalls()}); - // this.messenger.messageOne(ws, { type: 'robots', body: G.getRobots()}); + 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: 'state', body: this.state}); + this.messenger.messageOne(ws, { type: 'walls', body: this.walls}); // this.messenger.messageOne(ws, { type: 'winstate', body: G.getWinState()}); - // this.messenger.messageOne(ws, { type: 'connected', body: ws.id}); } Ricochet.prototype.onMessage = function(ws, json) { @@ -41,12 +46,11 @@ Ricochet.prototype.onMessage = function(ws, json) { }; Ricochet.prototype.onDisconnect = function(ws) { - DEBUG && console.log("Disconnected:"); - DEBUG && console.log(ws.id); + DEBUG && console.log(`Disconnected: ${this.players[ws.id]} (${ws.id.substr(0, 8)})`); this.removePlayer(ws.id); - // this.messenger.messageAll({ type: 'players', body: this.players }); + this.messenger.messageAll({ type: 'players', body: this.players }); }; Ricochet.prototype.addPlayer = function(id, name) { @@ -60,65 +64,70 @@ Ricochet.prototype.removePlayer = function(id) { delete this.players[id]; }; -// Game.prototype.getRobots = function() { -// return this.TEMP_ROBOTS; -// } - -// Game.prototype.getWalls = function() { -// // Edge IDs are of the form [i1-j1-i2-j2]. Top left is 0, 0. +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 -// // Leave here for testing. -// // return [ -// // "1-9-1-10", -// // "9-1-10-1", -// // "9-19-10-19", -// // "19-9-19-10" -// // ]; - -// // console.log("Generating walls."); - -// // Squares per side has quadratic relationship with wall/corner requirements. -// 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 isHorizontal = Math.random() < 0.5; -// const isBackward = Math.random() < 0.5; - -// let i1, j1, i2, j2; - -// if (isHorizontal) { -// i1 = isBackward ? ri - 1 : ri; -// i2 = isBackward ? ri : ri + 1; - -// j1 = rj; -// j2 = rj; -// } else { -// i1 = ri; -// i2 = ri; - -// j1 = isBackward ? rj - 1 : rj; -// j2 = isBackward ? rj : rj + 1; -// } - -// const edge = `${i1}-${j1}-${i2}-${j2}`; + const gen = () => Math.floor(Math.random() * this.squaresPerSide); + return robots.map((color, idx) => ({ i: gen(), j: gen(), color, id: uuid.v4(), icon: icons[idx] })); +}; -// if (edges.includes(edge)) { -// n--; -// } else { -// edges.push(edge); -// } -// } +Ricochet.prototype.freshWalls = function() { + // Edge IDs are of the form [i1-j1-i2-j2]. Top left is 0, 0. + + // Leave here for testing. + // return [ + // "1-9-1-10", + // "9-1-10-1", + // "9-19-10-19", + // "19-9-19-10" + // ]; + + // console.log("Generating walls."); + + // Squares per side has quadratic relationship with wall/corner requirements. + 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 isHorizontal = Math.random() < 0.5; + const isBackward = Math.random() < 0.5; + + let i1, j1, i2, j2; + + if (isHorizontal) { + i1 = isBackward ? ri - 1 : ri; + i2 = isBackward ? ri : ri + 1; + + j1 = rj; + j2 = rj; + } else { + i1 = ri; + i2 = ri; + + j1 = isBackward ? rj - 1 : rj; + j2 = isBackward ? rj : rj + 1; + } + + const edge = `${i1}-${j1}-${i2}-${j2}`; + + if (edges.includes(edge)) { + n--; + } else { + edges.push(edge); + } + } -// return edges; -// }; + return edges; +}; // Game.prototype.getWinState = function() { // // const gen = () => Math.floor(Math.random() * this.squaresPerSide); diff --git a/socket/messenger.js b/socket/messenger.js index 79a0ce4..a1fa392 100644 --- a/socket/messenger.js +++ b/socket/messenger.js @@ -24,10 +24,12 @@ Messenger.prototype.messageOthers = function(ws, message) { }; Messenger.prototype.messageAll = function(message) { - DEBUG && console.log(`Sending to all ${wss.clients.size} client(s):`); + const clients = Object.values(this.clients); + + DEBUG && console.log(`Sending to all ${clients.length} client(s):`); DEBUG && console.log(message); - Object.values(this.clients).forEach((client) => { + clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify(message)); } diff --git a/style/controls.css b/style/controls.css new file mode 100644 index 0000000..68f8690 --- /dev/null +++ b/style/controls.css @@ -0,0 +1,198 @@ +#controls-container { + bottom: 0; + left: 20px; + padding: 40px 0; + position: absolute; + top: 0; + width: 300px; + z-index: 1; +} + +.controls-block { + background: #23074d; + border: 6px solid #483c6c; + border-radius: 0 18px 0 18px; + color: #0cffe1; + font-size: 24px; + margin-bottom: 24px; + overflow: hidden; + padding-bottom: 24px; + position: relative; +} + +.controls-title { + background: #0cffe1; + color: #ff217c; + font-size: 14px; + margin-bottom: 16px; + padding: 8px 24px; +} + +.controls-message { + cursor: default; + font-size: 16px; + padding: 24px; +} + +.controls-button { + cursor: pointer; + padding: 8px 0; + text-align: center; +} + +.controls-button:hover { + background: yellow; + color: #23074d; +} + +/*===== PLAYERS BLOCK =====*/ +#controls-players { +} + +.controls-player { + cursor: default; + padding: 8px 24px; +} + +/*===== MOVES BLOCK =====*/ +#controls-moves { + background: #00dfd4; + border: 6px solid #483c6c; + border-radius: 0 18px 0 18px; + color: #ff217c; + position: relative; +} + +#controls-moves-count { + cursor: default; + font-size: 84px; + text-align: center; +} + +.controls-moves-buttons-layout { + display: flex; + flex-direction: row; +} + +#controls-reset, +#controls-undo { + flex: 1; + font-size: 12px; + padding: 12px; +} + +#controls-submit { + font-size: 24px; + padding: 8px 0; +} + +/*===== OPTIONS BLOCK =====*/ +#controls-options { +} + +#controls-options .controls-button { + font-size: 16px; + padding: 8px 24px; +} + +/*===== COUNTDOWN BLOCK =====*/ +#controls-countdown { + background: #F96600; + border-color: #ffec83; + color: #ffff00; + cursor: default; +} + +#controls-countdown .controls-title { + background: #CA1800; + color: #ffec83; +} + +.controls-countdown-values-layout { + display: flex; + flex-direction: row; +} + +#controls-countdown-value { + cursor: default; + flex: 1; + font-size: 64px; + text-align: center; +} + +#controls-countdown-value::after { + content: 'seconds'; + display: block; + font-size: 12px; + margin-top: -12px; +} + +#controls-solution-value { + cursor: default; + flex: 1; + font-size: 64px; + text-align: center; +} + +#controls-solution-value::after { + content: 'moves'; + display: block; + font-size: 12px; + margin-top: -12px; +} + +#controls-skip { + margin-top: 16px; +} + +/*===== SOLUTION BLOCK =====*/ +#controls-solution { + background: #483c6c; + border-width: 0; + color: #F96600; + text-align: center; +} + +#controls-solution-message { + color: #ffec83; + 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; + font-size: 84px; +} + +#controls-solution-replay { +} + +/*===== FOOTER BLOCK =====*/ +#controls-footer { + bottom: 0; + left: 0; + padding: 24px; + position: absolute; + right: 0; + text-align: center; +} + +#controls-footer a { + color: #639699; + font-size: 24px; + text-decoration: none; +} + +#controls-footer a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/grid.css b/style/grid.css similarity index 92% rename from grid.css rename to style/grid.css index c94dc72..1e4c9fe 100644 --- a/grid.css +++ b/style/grid.css @@ -1,6 +1,4 @@ #content-container { - background-color: #28313b; - background-image: linear-gradient(315deg, #28313b 0%, #1A2026 74%); bottom: 0; left: 0; position: absolute; diff --git a/style/index.css b/style/index.css new file mode 100644 index 0000000..90484a1 --- /dev/null +++ b/style/index.css @@ -0,0 +1,26 @@ +* { + box-sizing: border-box; + font-family: Days One; +} + +body { + animation: MovingBackground 600s infinite; + background-image: url('../assets/stars.png'); + overflow: hidden; +} + +@keyframes MovingBackground { + 0%{background-position:0% 50%} + 50%{background-position:100% 50%} + 100%{background-position:0% 50%} +} + +/* +0cffe1 CYAN 00dfd4 +ff217c PINK +483c6c PURPLE 23074d +fe5e78 SALMON +ffa283 PEACH +F96600 ORANGE +ffec83 YELLOW FFFF00 +*/ \ No newline at end of file