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 @@ Robots - + + diff --git a/package.json b/package.json index 8bdb420..0ea4626 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,6 @@ "dependencies": { "node-uuid": "^1.4.8", "ws": "^7.3.0" - } + }, + "devDependencies": {} } diff --git a/server.js b/server.js index d29398f..1e9eec0 100644 --- a/server.js +++ b/server.js @@ -67,6 +67,7 @@ const Server = { Server.messageAll({ type: 'players', body: G.getPlayers() }); Server.messageOne(ws, { type: 'walls', body: G.getWalls()}); Server.messageOne(ws, { type: 'robots', body: G.getRobots()}); + Server.messageOne(ws, { type: 'winstate', body: G.getWinState()}); Server.messageOne(ws, { type: 'connected', body: ws.id}); }, @@ -126,4 +127,4 @@ const Server = { wss.on('connection', Server.onConnect); -console.log("Websocket server listening on :8080") \ No newline at end of file +console.log("Websocket server listening on :8080"); \ No newline at end of file diff --git a/server/game.js b/server/game.js index 52c346a..f8762f3 100644 --- a/server/game.js +++ b/server/game.js @@ -6,6 +6,14 @@ const Game = function() { this.id = uuid.v4(); this.countdownTimer = null; this.guess = Infinity; + this.squaresPerSide = 20; + + 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] })); } Game.prototype.addPlayer = function(id, name) { @@ -24,20 +32,7 @@ Game.prototype.getPlayers = function() { } Game.prototype.getRobots = function() { - const robots = ['#E00000', '#00C000', '#0000FF', '#00C0C0', '#F000F0']; - const squaresPerSide = 20; - const gen = () => Math.floor(Math.random() * squaresPerSide); - - // Leave here for testing. - // return [ - // {i: 9, j: 9, color: '#E00000' }, - // {i: 18, j: 9, color: '#00C000' }, - // {i: 1, j: 9, color: '#0000FF' }, - // {i: 9, j: 18, color: '#00C0C0' }, - // {i: 9, j: 1, color: '#F000F0' }, - // ]; - - return robots.map(color => ({ i: gen(), j: gen(), color, id: uuid.v4() })); + return this.TEMP_ROBOTS; } Game.prototype.getWalls = function() { @@ -54,11 +49,10 @@ Game.prototype.getWalls = function() { // console.log("Generating walls."); // Squares per side has quadratic relationship with wall/corner requirements. - const squaresPerSide = 20; - const numberOfCorners = Math.ceil(Math.pow((squaresPerSide / 10), 2)); - const numberOfWalls = Math.ceil(Math.pow((squaresPerSide / 5), 2)); + 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() * squaresPerSide); + const gen = () => Math.floor(Math.random() * this.squaresPerSide); const edges = []; // DO NUMBER OF CORNERS FIRST AFTER TESTING @@ -97,6 +91,12 @@ Game.prototype.getWalls = 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 = () => { diff --git a/sprite-robots.png b/sprite-robots.png deleted file mode 100644 index 15d90e6..0000000 Binary files a/sprite-robots.png and /dev/null differ