diff --git a/README.txt b/README.txt index dbc0e69..5a2831c 100644 --- a/README.txt +++ b/README.txt @@ -9,22 +9,19 @@ 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. -## TODO -- robot shadow starting spot -- robot icons with personality add credit to readmee -- countdown skip -- restore state on join +## Icons +Icons from [https://game-icons.net](https://game-icons.net) +## TODO - win declare/add/remove -- goal - no more guesses, send stack and replay +- countdown skip - move guess and move logic out of server (clean up server file) - no cancel from name prompt -- window resize update board +- restore state on join - walls algorigthm -- restore state on join - limit concurrent players, make sure connections are closed, clean up empty rooms - cookie room link, add to all messages, namespace them diff --git a/assets/comet.svg b/assets/comet.svg new file mode 100644 index 0000000..38c600c --- /dev/null +++ b/assets/comet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/moon.svg b/assets/moon.svg new file mode 100644 index 0000000..a2df3ea --- /dev/null +++ b/assets/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/planet.svg b/assets/planet.svg new file mode 100644 index 0000000..43197e8 --- /dev/null +++ b/assets/planet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/rocket.svg b/assets/rocket.svg new file mode 100644 index 0000000..1749d57 --- /dev/null +++ b/assets/rocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/spacesuit.svg b/assets/spacesuit.svg new file mode 100644 index 0000000..d3b35b5 --- /dev/null +++ b/assets/spacesuit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/spider.svg b/assets/spider.svg new file mode 100644 index 0000000..df7880f --- /dev/null +++ b/assets/spider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ufo.svg b/assets/ufo.svg new file mode 100644 index 0000000..432b212 --- /dev/null +++ b/assets/ufo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/connection.js b/client/connection.js index 22ad911..f7d5a2f 100644 --- a/client/connection.js +++ b/client/connection.js @@ -79,6 +79,7 @@ Connection.prototype.onReceiveMessage = function({ data }) { case 'start': eventName = 'G-start'; break; case 'stop': eventName = 'G-stop'; break; case 'walls': eventName = 'G-walls'; break; + case 'winstate': eventName = 'G-winstate'; break; } if (eventName) { diff --git a/client/controls.js b/client/controls.js index 745674a..3c32983 100644 --- a/client/controls.js +++ b/client/controls.js @@ -1,5 +1,4 @@ -// //===== Constructor - +//===== Constructor const Controls = function() { this.names = {}; // this.starts = []; @@ -11,7 +10,6 @@ const Controls = function() { // document.addEventListener('L-reset', this.msgReset.bind(this)); // document.addEventListener('L-undo', this.msgUndo.bind(this)); -// document.addEventListener('G-attempt', this.msgAttempt.bind(this)); // document.addEventListener('G-guess', this.msgGuess.bind(this)); document.addEventListener('G-players', this.msgPlayers.bind(this)); document.addEventListener('G-skip', this.msgSkip.bind(this)); @@ -77,32 +75,15 @@ const Controls = function() { // document.getElementById('controls-panic').style.display = ''; // }; -// //===== Message handlers - -// Controls.prototype.msgAttempt = function() { -// alert("Ready for winning attempt!"); -// }; +//===== Message handlers // Controls.prototype.msgConnected = function() { // this.showWaiting(); // }; -// Controls.prototype.msgUndo = function(evt) { -// if (this.moves.length <= this.starts.length) { -// return; -// } - -// const { id } = this.moves.pop(); - -// const indexOfPreviousMove = this.moves.reduce((acc, v, i) => (v.id === id ? i : acc), -1); -// const previousMove = this.moves.splice(indexOfPreviousMove, 1); - -// const evtMove = new CustomEvent('L-move', { detail: previousMove[0] }); -// document.dispatchEvent(evtMove); -// }; - Controls.prototype.msgStack = function(evt) { - const moveCount = evt.detail.length; + 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'; @@ -142,15 +123,6 @@ Controls.prototype.msgPlayers = function(evt) { }); }; -// Controls.prototype.msgReset = function() { -// // Broadcast starting locations. -// this.moves = []; -// this.starts.forEach(move => { -// const evtMove = new CustomEvent('L-move', { detail: move }); -// document.dispatchEvent(evtMove); -// }); -// }; - Controls.prototype.msgSkip = function() { // this.coundownComplete(); }; @@ -223,4 +195,31 @@ Controls.prototype.onClickWalls = function() { // document.getElementById('controls-panic').querySelector('.controls-alert-urgent').innerHTML = (`${this.names[msg.id]}${blurb}${guess} moves.`); // this.showPanic(); // this.countdownStart(5); -// } \ No newline at end of file +// } + +// Controls.prototype.msgReset = function() { +// // Broadcast starting locations. +// this.moves = []; +// this.starts.forEach(move => { +// const evtMove = new CustomEvent('L-move', { detail: move }); +// document.dispatchEvent(evtMove); +// }); +// }; + +// Controls.prototype.msgUndo = function(evt) { +// if (this.moves.length <= this.starts.length) { +// return; +// } + +// const { id } = this.moves.pop(); + +// const indexOfPreviousMove = this.moves.reduce((acc, v, i) => (v.id === id ? i : acc), -1); +// const previousMove = this.moves.splice(indexOfPreviousMove, 1); + +// const evtMove = new CustomEvent('L-move', { detail: previousMove[0] }); +// document.dispatchEvent(evtMove); +// }; + +// Controls.prototype.msgAttempt = function() { +// alert("Ready for winning attempt!"); +// }; \ No newline at end of file diff --git a/client/grid.js b/client/grid.js index 0233ccb..c716211 100644 --- a/client/grid.js +++ b/client/grid.js @@ -2,17 +2,22 @@ const Grid = function() { this.colors = {}; + this.icons = {}; this.obstacles = {}; this.robots = []; + this.shadows = []; this.walls = []; + this.winstate = {}; this.squaresPerSide = 20; this.squareSideLength = 0; document.addEventListener('L-stack', this.msgStack.bind(this)); + document.addEventListener('L-shadows', this.msgShadows.bind(this)); document.addEventListener('G-robots', this.msgRobots.bind(this)); document.addEventListener('G-walls', this.msgWalls.bind(this)); + document.addEventListener('G-winstate', this.msgWinState.bind(this)); window.addEventListener('resize', this.debounce(this.onResize.bind(this), 500)); @@ -73,27 +78,69 @@ Grid.prototype.drawWalls = function() { }; Grid.prototype.drawRobots = function() { + const s = this.squareSideLength; + const ids = new Set(); + + this.robots.forEach(({ id, i, j }) => { + const robot = document.getElementById(`robot-${id}`) || this.drawRobot({ id, i, j }); + + robot.style.left = `${i * s}px`; + robot.style.top = `${j * s}px`; + + ids.add(robot.id); + }); + + const grid = document.getElementById('content-grid'); + grid.querySelectorAll('.content-robot').forEach(el => { + if (ids.has(el.id) === false) { + el.parentNode.removeChild(el); + } + }); +}; + +Grid.prototype.drawRobot = function({ id, i, j }) { const grid = document.getElementById('content-grid'); - grid.querySelectorAll('.content-robot').forEach(el => el.parentNode.removeChild(el)); + const color = this.colors[id]; + const s = this.squareSideLength; + + // const robot = document.createElement('div'); + const robot = document.createElement('img'); + robot.src = this.icons[id]; + robot.className = 'content-robot'; + robot.id = `robot-${id}`; + + robot.style.background = color; + + robot.style.height = `${s}px`; + robot.style.width = `${s}px`; + + grid.appendChild(robot); + + return robot; +}; + +Grid.prototype.drawShadows = function() { + const grid = document.getElementById('content-grid'); + grid.querySelectorAll('.content-shadow').forEach(el => el.parentNode.removeChild(el)); const s = this.squareSideLength; - this.robots.forEach(({ id, i, j }) => { + this.shadows.forEach(({ id, i, j }) => { const color = this.colors[id]; - const robot = document.createElement('div'); - robot.className = 'content-robot'; + const shadow = document.createElement('img'); + shadow.src = this.icons[id]; + shadow.className = 'content-shadow'; - robot.style.background = `radial-gradient(circle at ${s/3}px ${s/3}px, ${color} 10%, #000)`; - robot.style.borderRadius = (s / 2) + 'px'; + shadow.style.background = color; - robot.style.height = s + 'px'; - robot.style.width = s + 'px'; + shadow.style.height = `${s}px`; + shadow.style.width = `${s}px`; - robot.style.left = `${i * s}px`; - robot.style.top = `${j * s}px`; + shadow.style.left = `${i * s}px`; + shadow.style.top = `${j * s}px`; - grid.appendChild(robot); + grid.appendChild(shadow); }); }; @@ -138,9 +185,9 @@ Grid.prototype.drawArrow = function({ direction, label, left, top, parentId }) { arrow.style.left = left + 'px'; arrow.style.top = top + 'px'; - arrow.style.lineHeight = s + 'px'; - arrow.style.height = s + 'px'; - arrow.style.width = s + 'px'; + arrow.style.lineHeight = `${s}px`; + arrow.style.height = `${s}px`; + arrow.style.width = `${s}px`; arrow.dataset.direction = direction; arrow.dataset.parent = parentId; @@ -150,6 +197,38 @@ Grid.prototype.drawArrow = function({ direction, label, left, top, parentId }) { return arrow; }; +Grid.prototype.drawWinState = function() { + if (this.winstate.i === undefined || this.winstate.j === undefined) { + return; + } + + const s = this.squareSideLength; + + const grid = document.getElementById('content-grid'); + grid.querySelectorAll('.content-winstate').forEach(el => el.parentNode.removeChild(el)); + + const star = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + star.setAttribute("viewBox", '0 0 100 100'); + + 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"); + path.setAttribute('stroke-linecap', 'null'); + path.setAttribute('stroke-linejoin', 'null'); + path.setAttribute('stroke-dasharray', 'null'); + path.setAttribute('stroke-width', '0'); + path.setAttribute('fill', this.colors[this.winstate.id]); + + star.appendChild(path); + star.style.position = 'absolute'; + star.style.zIndex = 100; + star.style.left = '50%' + star.style.top = '50%' + star.style.height = `${s}px`; + star.style.width = `${s}px`; + + grid.appendChild(star); +}; + //===== Obstacle logic // i and j are notations for the grid of squares. // x and y are notations for absolute pixels. @@ -238,6 +317,15 @@ 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; + } + + const evtSolve = new Event('L-solve'); + document.dispatchEvent(evtSolve); +}; + //===== DOM event handlers Grid.prototype.onArrowClick = function(evt) { @@ -252,6 +340,8 @@ Grid.prototype.onArrowClick = function(evt) { const evtMove = new CustomEvent('L-arrow', { detail: { id, i, j } }); document.dispatchEvent(evtMove); + + this.checkWin({ id, i, j }); }; Grid.prototype.onResize = function() { @@ -281,6 +371,8 @@ Grid.prototype.onResize = function() { this.drawWalls(); this.drawRobots(); this.drawArrows(); + this.drawShadows(); + this.drawWinState(); this.updateArrowVisibilities(); }; @@ -296,6 +388,17 @@ Grid.prototype.debounce = function(fn, ms) { //===== Message handlers +Grid.prototype.msgRobots = function(evt) { + // Do not assign position or redraw here: movements are fully managed using the stack. + this.colors = {}; + this.icons = {}; + + evt.detail.body.forEach(({ id, color, icon }) => { + this.colors[id] = color; + this.icons[id] = icon; + }, {}); +}; + Grid.prototype.msgStack = function(evt) { const latestPositions = evt.detail.reduce((acc, { id, i, j }) => { acc[id] = { id, i, j }; @@ -311,6 +414,11 @@ Grid.prototype.msgStack = function(evt) { this.updateArrowVisibilities(); }; +Grid.prototype.msgShadows = function(evt) { + this.shadows = evt.detail; + this.drawShadows(); +}; + Grid.prototype.msgWalls = function(evt) { this.walls = evt.detail.body; @@ -320,15 +428,11 @@ Grid.prototype.msgWalls = function(evt) { this.updateArrowVisibilities(); }; -Grid.prototype.msgRobots = function(evt) { - // Do not assign position or redraw here: movements are fully managed using the stack. - this.colors = evt.detail.body.reduce((acc, { color, id }) => { - acc[id] = color; - return acc; - }, {}); +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}`); diff --git a/client/icons.js b/client/icons.js new file mode 100644 index 0000000..5794a7a --- /dev/null +++ b/client/icons.js @@ -0,0 +1,39 @@ +const Icons = { + comet: (color) => { + // const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + // star.setAttribute("viewBox", '0 0 100 100'); + + // const path = document.createElementNS("http://www.w3.org/2000/svg", 'path'); + + const obj = document.getElementById('TEST_OBJECT_SVG'); + + console.log(obj); + console.log(obj.contentDocument) + + return obj; + }, + + moon: (color) => { + + }, + + planet: (color) => { + + }, + + rocket: (color) => { + + }, + + spacesuit: (color) => { + + }, + + spider: (color) => { + + }, + + ufo: (color) => { + + }, +}; \ No newline at end of file diff --git a/client/stack.js b/client/stack.js index a295de2..2df84e3 100644 --- a/client/stack.js +++ b/client/stack.js @@ -7,6 +7,7 @@ const Stack = function() { document.addEventListener('L-arrow', this.msgArrow.bind(this)); document.addEventListener('L-undo', this.msgUndo.bind(this)); document.addEventListener('L-reset', this.msgReset.bind(this)); + document.addEventListener('G-robots', this.msgRobots.bind(this)); }; @@ -27,6 +28,9 @@ Stack.prototype.msgRobots = function(evt) { const evtStack = new CustomEvent('L-stack', { detail: this.moves }); document.dispatchEvent(evtStack); + + const evtShadows = new CustomEvent('L-shadows', { detail: this.moves }); + document.dispatchEvent(evtShadows); }; Stack.prototype.msgArrow = function(evt) { @@ -39,8 +43,6 @@ Stack.prototype.msgArrow = function(evt) { Stack.prototype.msgReset = function() { this.moves = this.getInitialPositions(); - console.log(this.moves) - const evtStack = new CustomEvent('L-stack', { detail: this.moves }); document.dispatchEvent(evtStack); }; diff --git a/content.css b/grid.css similarity index 80% rename from content.css rename to grid.css index 8adfe95..b5297b0 100644 --- a/content.css +++ b/grid.css @@ -37,8 +37,22 @@ } .content-robot { + padding: 4px; position: absolute; transition: left 0.4s cubic-bezier(0,1,.5,1), top 0.4s cubic-bezier(0,1,.5,1); + z-index: 2; +} + +.content-shadow { + opacity: 0.2; + padding: 4px; + position: absolute; + transition: left 0.4s cubic-bezier(0,1,.5,1), top 0.4s cubic-bezier(0,1,.5,1); + z-index: 1; +} + +.content-winstate { + position: absolute; z-index: 1; } diff --git a/index.html b/index.html index af3c2d9..b50aeb4 100644 --- a/index.html +++ b/index.html @@ -6,13 +6,14 @@