From 3997e94a68e61fb46d71225da30c5e65bd073b6b Mon Sep 17 00:00:00 2001 From: Nathan DECHER Date: Sat, 4 Apr 2020 22:59:50 +0200 Subject: [PATCH] added win popup --- src/js/main.js | 25 +++++++++++-- src/js/popup.js | 87 +++++++++++++++++++++++++++++++++++++++++++++ src/js/snek.js | 13 ++++--- src/less/popup.less | 79 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 src/js/popup.js diff --git a/src/js/main.js b/src/js/main.js index eb1323d..dd9c9da 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -2,6 +2,9 @@ location.hash=''; const assets=require('assets'); + const Popup=require('popup'); + const SnekGame=require('snek'); + await new Promise(ok => assets.onReady(ok)); const main=document.querySelector('main'); @@ -60,7 +63,7 @@ const gp=navigator.getGamepads()[0]; let inputs=currentInputs; if(!gp || !gp.axes) return; - + const magnitude=Math.hypot(gp.axes[0], gp.axes[1]); const angle=((Math.atan2(gp.axes[0], gp.axes[1])+2*Math.PI)%(2*Math.PI))/Math.PI; if(magnitude>config.gamepad.deadzone) { @@ -73,6 +76,10 @@ if(!config.gamepad.buffer) inputs.clearBuffer=true; }; + const startGame=async (category, filename) => { + //TODO migrate relevant code here + }; + window.addEventListener('hashchange', async () => { nav.classList.add('hidden'); @@ -83,7 +90,6 @@ return await resp.json(); })(); - const SnekGame=require('snek'); const snek=new SnekGame(level, canvas, rules); canvas.classList.remove('hidden'); snek.start(); @@ -91,6 +97,21 @@ if(evt=='tick') { if(navigator.getGamepads) handleGamepads(); snek.handleInputs(currentInputs); + } else if(evt=='win') { + let popup=new Popup("Finished!"); + popup.addStrong("You won!"); + popup.addContent({ + "Time": snek.playTime/1000+'s', + "Score": snek.score, + "Final length": snek.snake.length + }); + popup.buttons={ + retry: "Retry", + next: "Next level", + menu: "Main menu" + }; + popup.display(); + //TODO do something with the result } }; currentGame=snek; diff --git a/src/js/popup.js b/src/js/popup.js new file mode 100644 index 0000000..c08677c --- /dev/null +++ b/src/js/popup.js @@ -0,0 +1,87 @@ +const objToDom=obj => { + if(obj[Popup.EM]) { + let em=document.createElement('em'); + em.appendChild(document.createTextNode(obj[Popup.EM])); + return em; + } else if(obj[Popup.STRONG]) { + let em=document.createElement('strong'); + em.appendChild(document.createTextNode(obj[Popup.STRONG])); + return em; + } else if(typeof obj=='string' || typeof obj=='number') { + return document.createTextNode(obj+''); + } else if(Array.isArray(obj)) { + let ul=document.createElement('ul'); + obj.forEach(elem => ul.appendChild(objToDom(elem))); + return ul; + } else { + let table=document.createElement('table'); + Object + .keys(obj) + .forEach(key => { + let tr=table.appendChild(document.createElement('tr')); + tr.appendChild(document.createElement('th')).appendChild(document.createTextNode(key)); + tr.appendChild(document.createElement('td')).appendChild(objToDom(obj[key])); + }); + return table; + } +} + +class Popup { + constructor(title, content=[], buttons={}) { + this.title=title; + this.content=content.map(objToDom); + this.buttons={...buttons}; + } + + addContent(cnt) { + this.content.push(objToDom(cnt)); + } + addText(cnt) { + this.content.push(document.createTextNode(cnt)); + } + addEm(cnt) { + this.content.push(objToDom({[Popup.EM]: cnt})); + } + addStrong(cnt) { + this.content.push(objToDom({[Popup.STRONG]: cnt})); + } + + async display() { + let outer=document.createElement('div'); + outer.classList.add('popup'); + let popup=outer.appendChild(document.createElement('div')); + popup.classList.add('content'); + + let title=popup.appendChild(document.createElement('h1')); + title.innerText=this.title; + + let contentSection=popup.appendChild(document.createElement('section')); + this.content.forEach(elem => contentSection.appendChild(elem)); + + let buttonSection=popup.appendChild(document.createElement('section')); + let buttons=Object.keys(this.buttons).map(btn => { + let button=document.createElement('button'); + button.innerText=this.buttons[btn]; + button.dataset.code=btn; + return button; + }); + buttons.forEach(btn => buttonSection.appendChild(btn)); + + document.body.appendChild(outer); + + const code=await Promise.race(buttons.map(btn => new Promise(ok => { + btn.addEventListener('click', e => { + e.preventDefault(); + return ok(btn.dataset.code); + }); + }))); + + document.body.removeChild(outer); + return code; + } +} + +Popup.EM=Symbol('EM'); +Popup.STRONG=Symbol('STRONG'); + +return module.exports=Popup; diff --git a/src/js/snek.js b/src/js/snek.js index a33c160..60c0117 100644 --- a/src/js/snek.js +++ b/src/js/snek.js @@ -84,7 +84,7 @@ class SnekGame { // get our canvas, like, if we want to actually draw this.canvas=canvas; this.ctx=canvas.getContext('2d'); - + // load the custom rules this.rules=Object.assign({ fruitRegrow: true, @@ -160,7 +160,7 @@ class SnekGame { putTileAnim(x, y, fire); break; - case HOLE: + case HOLE: case HOLE_S: { putTile(x, y, hole.base); let adj=checkAdj(x, y); @@ -293,6 +293,9 @@ class SnekGame { ([x, y]) => !(x==head[0] && y==head[1]) ); + // increase score + this.score++; + // custom rules if(this.rules.fruitRegrow) { const emptyCells=this.world @@ -301,7 +304,7 @@ class SnekGame { .map( (r, y) => r==EMPTY?[x,y]:null ).filter( - a => a + a => a ) ).flat(); const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; @@ -409,9 +412,9 @@ class SnekGame { this.firstStep=Date.now(); this.tickId=0; this.playing=true; + this.score=0; requestAnimationFrame(() => this.tick()); } } -module.exports=SnekGame; -return SnekGame; +return module.exports=SnekGame; diff --git a/src/less/popup.less b/src/less/popup.less index 03d1af6..a90452b 100644 --- a/src/less/popup.less +++ b/src/less/popup.less @@ -1,14 +1,15 @@ @keyframes popupAppear { 0% { - background: transparent; + background: rgba(0, 0, 0, 0%); } 100% { - background: black; + background: rgba(0, 0, 0, 90%); } } .popup { - animation: popupAppear 1s linear no-repeat; + animation: popupAppear 1s linear; + background: rgba(0, 0, 0, 90%); position: absolute; top: 0; @@ -18,12 +19,80 @@ .content { border-radius: 2rem; - background: @accentbg; + background: @bg; text-align: center; + box-shadow: black 0 0 1rem; - postion: absolute; + position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); + box-sizing: border-box; + + max-width: 50vw; + max-height: 50vh; + + padding: 2rem; + display: flex; + flex-direction: column; + + font-size: 1.4rem; + + & > section { + margin: 1rem; + + &:first-of-type { + display: flex; + flex-direction: column; + align-items: center; + + & > * { + margin: .5rem; + } + } + + &:last-of-type { + display: flex; + + & > * { + flex: 1; + } + } + } + + table { + display: flex; + flex-direction: column; + width: 100%; + + tr { + display: flex; + } + th { + text-align: right; + &::after { + content: ':'; + } + } + td { + text-align: left; + } + th, td { + padding: 0 .5rem; + flex: 1; + } + } + + button { + display: inline; + color: @accentfg; + background: @accentbg; + font-weight: bold; + + border-radius: 1rem; + border: 0; + padding: 2rem; + margin: 1rem; + } } }