From 97cf2dc632c9f753e0d7074e96041c1dd165589a Mon Sep 17 00:00:00 2001 From: Ben Burlingham Date: Sat, 23 May 2020 11:18:17 -0700 Subject: [PATCH] Collision detection experiments. --- client/board.js | 184 +++++++++++++++++++++++ client/controls.js | 53 +++++++ client/cookie.js | 16 ++ client/settings.js | 3 + client/squares.js | 52 +++++++ ricochet.css => index.css | 67 ++++----- index.html | 89 +++++++++++ notes.txt | 12 ++ ricochet.html | 301 -------------------------------------- server.js | 2 +- game.js => server/game.js | 20 +-- 11 files changed, 449 insertions(+), 350 deletions(-) create mode 100644 client/board.js create mode 100644 client/controls.js create mode 100644 client/cookie.js create mode 100644 client/settings.js create mode 100644 client/squares.js rename ricochet.css => index.css (59%) create mode 100644 index.html create mode 100644 notes.txt delete mode 100644 ricochet.html rename game.js => server/game.js (52%) diff --git a/client/board.js b/client/board.js new file mode 100644 index 0000000..b27b33c --- /dev/null +++ b/client/board.js @@ -0,0 +1,184 @@ +const Board = function({ parent, squares }) { + this.parent = parent; + this.squares = squares; + + this.blockers = {}; + this.listeners = {}; + + this.drawSquares(); + this.resetBlockers(); +}; + +Board.prototype.resetBlockers = function() { + this.blockers = { + negativeI: null, + negativeJ: null, + positiveI: null, + positiveJ: null + }; +}; + +Board.prototype.drawRobots = function(robots) { + robots.forEach(({ color, i, j }) => { + const { x, y } = this.squares.ijToXy({ i, j }); + const s = this.squares.sideLength; + const id = color.replace('#', '').toUpperCase(); + + const robot = document.createElement('div'); + robot.className = 'content-robot'; + robot.style.background = color; + robot.style.borderRadius = (s / 2) + 'px'; + robot.style.height = s + 'px'; + robot.style.width = s + 'px'; + robot.style.left = x + 'px'; + robot.style.top = y + 'px'; + robot.id = id + + // Find if a robot is on a square: document.querySelector('[data-robot=i-j]') + robot.dataset.robot = `${i}-${j}`; + + const shadow = document.createElement('div'); + shadow.className = 'content-shadow'; + shadow.style.background = color; + shadow.style.borderRadius = (s / 2) + 'px'; + shadow.style.height = s + 'px'; + shadow.style.width = s + 'px'; + shadow.style.left = x + 'px'; + shadow.style.top = y + 'px'; + shadow.dataset.parentRobot = id; + + this.parent.appendChild(robot); + this.parent.appendChild(shadow); + + shadow.addEventListener('mousedown', this.onRobotDragStart.bind(this)); + }); +}; + +Board.prototype.drawSquares = function() { + for (let i = 0; i < this.squares.perSide; i++) { + for (let j = 0; j < this.squares.perSide; j++) { + // All squares are absolutely positioned relative to the viewport. + const { x, y } = this.squares.ijToXy({ i, j }); + + const square = document.createElement('div'); + square.className = 'content-square'; + square.style.height = this.squares.sideLength + 'px'; + square.style.width = this.squares.sideLength + 'px'; + square.style.left = x + 'px'; + square.style.top = y + 'px'; + + this.parent.appendChild(square); + } + } +}; + +Board.prototype.drawWalls = function(edges) { + edges.forEach(edge => { + const id = `wall-${edge}`; + + if (document.getElementById(id)) { + return; + } + + const [i1, j1, i2, j2] = edge.split('-'); + + console.log(edge) + + const wall = document.createElement('div'); + wall.id = id; + wall.title = edge; + + const { x, y } = this.squares.ijToXy({ i: i1, j: j1 }); + wall.style.left = x + 'px'; + wall.style.top = y + 'px'; + + // Find if edge has a wall: document.querySelector('[data-wall=i1-j1-i2-j2]') + wall.dataset.wall = edge; + + if (i1 === i2) { + wall.className = 'content-wall-y'; + wall.style.height = this.squares.sideLength + 'px'; + } else { + wall.className = 'content-wall-x'; + wall.style.width = this.squares.sideLength + 'px'; + } + + this.parent.appendChild(wall) + }) +}; + +Board.prototype.getBlockers = function({ i1, j1, i2, j2 }) { + // const upperEdge = `${i1}-${j1}-${i2}-${j2}`; + // const lowerEdge = `${i1}-${j1}-${i2}-${j2}`; + + if (i1 === i2 && j1 < j2) { + + + } else if (i1 === i2 && j1 > j2) { + + + } else if (j1 === j2 && i1 <= i2) { + this.blockers.positiveI && console.log(this.blockers.positiveI.i, i1) + + if (this.blockers.positiveI && this.blockers.positiveI.i <= i1) { + return this.blockers.positiveI.blockage; + } + + const rightEdge = `${i1 + 1}-${j1}-${i1 + 1}-${j1 + 1}`; + + const wall = document.querySelector(`[data-wall='${rightEdge}']`); + + if (wall) { + this.blockers.positiveI = { i: i1 + 1, blockage: wall }; + return wall; + } + } else { + // const leftEdge = `${i1}-${j1}-${i2}-${j2}`; + // console.log("Left edge: ", leftEdge) + } + + return null; +}; + +Board.prototype.onRobotDragStart = function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + + this.listeners.onRobotDragStop = this.onRobotDragStop.bind(this); + this.listeners.onRobotDrag = this.onRobotDrag.bind(this, evt.currentTarget); + + document.body.addEventListener('mouseup', this.listeners.onRobotDragStop); + document.body.addEventListener('mousemove', this.listeners.onRobotDrag); + + const { x, y } = this.squares.ijToXy(this.squares.xyToIj(evt)); + + evt.currentTarget.style.left = x + 'px'; + evt.currentTarget.style.top = y + 'px'; +}; + +Board.prototype.onRobotDrag = function(dragTarget, evt) { + const { i: i1, j: j1 } = this.squares.xyToIj({ x: evt.x - evt.movementX, y: evt.y - evt.movementY }); + const { i: i2, j: j2 } = this.squares.xyToIj({ x: evt.x, y: evt.y }); + + const blockage = this.getBlockers({ i1, j1, i2, j2 }); + // console.warn(blockers) + + if (blockage) { + console.log(blockage) + console.log("NOPE") + // this.onRobotDragStop(); works but i don't like it + return; + } + + const { x, y } = this.squares.ijToXy({ i: i2, j: j2 }); + + dragTarget.style.left = x + 'px'; + dragTarget.style.top = y + 'px'; +}; + +Board.prototype.onRobotDragStop = function(_) { + this.resetBlockers(); + + document.body.removeEventListener('mouseup', this.listeners.onRobotDragStop); + document.body.removeEventListener('mousemove', this.listeners.onRobotDrag); +}; \ No newline at end of file diff --git a/client/controls.js b/client/controls.js new file mode 100644 index 0000000..fb6971b --- /dev/null +++ b/client/controls.js @@ -0,0 +1,53 @@ +const Controls = { + guessBuild: () => { + const container = document.getElementById('controls-guesses'); + container.querySelectorAll('.controls-guess').forEach(el => el.parentNode.removeChild(el)); + + for (let i = 1; i <= 30; i++) { + const guess = document.createElement('div'); + guess.className = 'controls-guess'; + guess.innerHTML = i; + guess.setAttribute('data-value', i); + guess.addEventListener('click', Controls.guessClick) + container.appendChild(guess); + } + }, + + guessClick: (evt) => { + alert(evt.currentTarget.dataset.value) + }, + + playerAdd: () => { + const rawInput = prompt("What is your name?"); + connection.send(JSON.stringify({ head: { type: 'playerAdd' }, body: rawInput })) + }, + + playerRemove: (rawInput) => { + connection.send(JSON.stringify({ head: { type: 'playerRemove' }, body: rawInput })) + }, + + playersUpdate: (names) => { + const container = document.getElementById('controls-players'); + console.log(names) + + Object.keys(names).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) + }); + + // container.querySelectorAll('.controls-player').forEach(el => { + // if (!names[el.id]) { + // container.removeChild(el); + // } + // }) + }, +}; \ No newline at end of file diff --git a/client/cookie.js b/client/cookie.js new file mode 100644 index 0000000..9b87af3 --- /dev/null +++ b/client/cookie.js @@ -0,0 +1,16 @@ +const Cookie = { + getCookie: function(name) { + var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); + return v ? decodeURI(v[2]) : null; + }, + + setCookie: function(name, value, days) { + var d = new Date; + d.setTime(d.getTime() + 24*60*60*1000*days); + document.cookie = name + "=" + encodeURI(value) + ";path=/;expires=" + d.toGMTString(); + }, + + deleteCookie: function(name) { + Util.setCookie(name, '', -1); + } +}; \ No newline at end of file diff --git a/client/settings.js b/client/settings.js new file mode 100644 index 0000000..fed6df2 --- /dev/null +++ b/client/settings.js @@ -0,0 +1,3 @@ +const Settings = function() { + this.squaresPerSide = 20; +} \ No newline at end of file diff --git a/client/squares.js b/client/squares.js new file mode 100644 index 0000000..11b9377 --- /dev/null +++ b/client/squares.js @@ -0,0 +1,52 @@ +// This layer acts to convert squares and their edges to pixel coords and back. +// i and j are notations for the grid of squares. +// x and y are notations for absolute pixels. +// +// Grid: XY pair. 0-i on x and 0-j on y. +// Absolute: XY pair. 0-width and 0-height, relative to viewport +// Edge: A string. i1-j1-i1-j2. + +const Squares = function() { + const controlBounds = document.getElementById('controls-container').getBoundingClientRect(); + const contentBounds = document.getElementById('content-container').getBoundingClientRect(); + + const h = contentBounds.height - 40; + const w = contentBounds.width - controlBounds.right - 40; + const min = Math.min(h, w); + + // Centering + const offsetX = (min === h) ? ((w - min) / 2) : 0; + const offsetY = (min === h) ? 0 : ((h - min) / 2); + + this.perSide = 20; + this.sideLength = Math.floor(min / this.perSide); + + // Origin (top left) + this.x0 = controlBounds.left + controlBounds.width + 40 + offsetX; + this.y0 = contentBounds.top + 40 + offsetY; +} + +Squares.prototype.ijToXy = function({ i, j }) { + if ((i !== 0 && !i) || (j !== 0 && !j)) { + return + } + + return { + x: this.x0 + i * this.sideLength, + y: this.y0 + j * this.sideLength + } +} + +Squares.prototype.xyToIj = function({ x, y }) { + const i = Math.floor((x - this.x0) / this.sideLength); + const j = Math.floor((y - this.y0) / this.sideLength); + + const min = 0; + const max = this.perSide - 1; + + // Coalesce outlying results to grid edges. + return { + i: Math.min(Math.max(min, i), max), + j: Math.min(Math.max(min, j), max), + } +} \ No newline at end of file diff --git a/ricochet.css b/index.css similarity index 59% rename from ricochet.css rename to index.css index 423caaa..70f64da 100644 --- a/ricochet.css +++ b/index.css @@ -3,32 +3,44 @@ font-family: Montserrat; } -.controls-container { +body { + overflow: hidden; +} + +/* +4D3243 +9A748C +639699 +364B4D +*/ + +#controls-container { background: #e7e7e7; bottom: 0; - left: 0; + left: 20px; position: absolute; top: 0; width: 300px; + z-index: 1; } .controls-subtitle { - background-color: #555; + background-color: #4D3243; color: #fff; + padding: 12px; } .controls-title { - background-color: #222; + background-color: #639978; background-image: url('sprite-robots.png'); - color: #fff; + /*color: #fff;*/ font-size: 12px; line-height: 48px; + margin-bottom: 24px; text-align: center; } .controls-room { - line-height: 48px; - text-align: center; } .controls-room span { @@ -61,61 +73,44 @@ background: orange; } -.board-container { - background: conic-gradient(lime 0 25%, yellow 25% 50%, red 50% 75%, blue 75% 100%); +#content-container { + background-color: #28313b; + background-image: linear-gradient(315deg, #28313b 0%, #1A2026 74%); bottom: 0; - left: 300px; + left: 0; position: absolute; right: 0; top: 0; + z-index: 0; } -#board-squares { - border-color: #aaa; - border-style: solid; - border-width: 1px 0 0 1px; - position: relative; -} - -.board-square{ +.content-square { background: #ddd; border-style: solid; border-color: #aaa; border-width: 0 1px 1px 0; - float: left; - height: 40px; - width: 40px; + position: absolute; } -.board-wall-x { +.content-wall-x { background: #222; height: 8px; margin-top: -4px; position: absolute; - width: 40px; } -.board-wall-y { +.content-wall-y { background: #222; - height: 40px; margin-left: -4px; position: absolute; width: 8px; } -.board-robot { - /*border-radius: 20px;*/ - border: 2px solid transparent; - height: 36px; - margin: 2px; +.content-robot { + opacity: 0.25; position: absolute; - width: 36px; } -.board-robot-shadow { - background: red; - height: 36px; - opacity: 0.75; +.content-shadow { position: absolute; - width: 36px; } \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..f211b90 --- /dev/null +++ b/index.html @@ -0,0 +1,89 @@ + + + + + + Document + + + + + + + + +
+
Puzzle Robots
+ +
+
Room
+ + +
+ + + +
+
Players
+
+ + +
+
Guess
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..2266666 --- /dev/null +++ b/notes.txt @@ -0,0 +1,12 @@ +// TODO dynamic sizing of squares based on available height +// TODO dynamic move population +// TODO move websocket server to /core +// TODO dynamic socket server resolution +// TODO namespace server to /ricochet +// TODO [soap, xmpp] +// TODO a message must have a head and a body +// TODO your favorite games +// TODO no cancel from name prompt +// TODO limit concurrent players +// TODO window resize update board +// TODO donate link \ No newline at end of file diff --git a/ricochet.html b/ricochet.html deleted file mode 100644 index fcbbacf..0000000 --- a/ricochet.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - Document - - - - -
-
Puzzle Robots
- -
- Room ID - - -
- - - -
-
Players
-
- - -
-
Guess
-
-
- -
-
-
- - - - \ No newline at end of file diff --git a/server.js b/server.js index 78f5ac7..040c520 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ const WebSocket = require('ws'); const uuid = require('node-uuid'); -const jsDir = `${__dirname}/`; +const jsDir = `${__dirname}/server`; const Game = require(`${jsDir}/game.js`); const wss = new WebSocket.Server({ port: 8080 }); diff --git a/game.js b/server/game.js similarity index 52% rename from game.js rename to server/game.js index 814983c..198eb3e 100644 --- a/game.js +++ b/server/game.js @@ -22,23 +22,19 @@ Game.prototype.getPlayers = function() { } Game.prototype.getRobots = function() { - const robots = [ - {x: 3, y: 1, color: 'red' }, - {x: 19, y: 0, color: 'silver' }, - {x: 1, y: 19, color: 'green' } + return [ + {i: 4, j: 3, color: '#E00000' }, + {i: 1, j: 3, color: '#00C000' }, + {i: 1, j: 19, color: '#0000C0' } ]; - - return robots.map(r => [r.color, r.x, r.y]); } Game.prototype.getWalls = function() { - const walls = [ - {x: 8, y: 9, n: 1, e: 0, s: 0, w: 1 }, - {x: 18, y: 9, n: 1, e: 0, s: 0, w: 1 }, - {x: 4, y: 19, n: 1, e: 0, s: 0, w: 1 } + // Edge IDs are of the form [i1-j1-i2-j2]. Top left is 0, 0. + return [ + "4-8-5-8", + "8-3-8-4" ]; - - return walls.map(w => [w.x, w.y, w.n, w.s, w.e, w.w]); }; module.exports = Game; \ No newline at end of file