fixed engine and added level2

This commit is contained in:
Nathan DECHER 2020-03-25 18:29:28 +01:00
parent beb9598f69
commit 03e0c97280
8 changed files with 186 additions and 46 deletions

View file

@ -1,11 +1,12 @@
{ {
"speedrun": { "speedrun": {
"desc": "Get all the fruits as fast as possible without touching the walls",
"rules": { "rules": {
"fruitRegrow": false, "fruitRegrow": false,
"speedIncrease": false, "speedIncrease": false,
"worldWrap": false, "worldWrap": false,
"winCondition": "fruit", "winCondition": "fruit",
"scoreSystem": "time" "scoreSystem": "speedrun"
}, },
"levelFilename": "level<n>.json", "levelFilename": "level<n>.json",
"levelDisplay": "Level <n>", "levelDisplay": "Level <n>",
@ -14,9 +15,11 @@
] ]
}, },
"arcade": { "arcade": {
"desc": "Have fun just like in the good ol' days, walls wrap around, fruits respawn and speed increases",
"rules": { "rules": {
"fruitRegrow": true, "fruitRegrow": true,
"speedIncrease": true, "speedIncrease": true,
"speedMultiplier": 0.9,
"worldWrap": true "worldWrap": true
}, },
"levelFilename": "arcade-<l>.json", "levelFilename": "arcade-<l>.json",
@ -24,7 +27,14 @@
"levels": [ "levels": [
"Arcade", "Arcade",
"Timed", "Timed",
"Survival" "Survival",
"Versus"
],
"levelDesc": [
"The old classic, try to get as high as a score as you can",
"Get a score as high as you can in 30 seconds",
"Survive for as long as you can in an increasingly difficult game",
"Fight against an opponent online"
] ]
} }
} }

23
levels/level2.json Normal file
View file

@ -0,0 +1,23 @@
{
"world": [
" ",
" f ",
" fw w wf ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" w ",
" f "
],
"snake": [
[6,6],
[6,7],
[6,8],
[6,9]
],
"delay": 100
}

View file

@ -4,7 +4,7 @@ const requireFn=`
const require=function require(name) { const require=function require(name) {
if(require.cache[name]) return require.cache[name]; if(require.cache[name]) return require.cache[name];
if(!require.source[name]) throw new Error("Cannot require "+name+": not found"); if(!require.source[name]) throw new Error("Cannot require "+name+": not found");
require.cache[name]=require.source[name]() || true; require.cache[name]=require.source[name]({}) || true;
return require.cache[name]; return require.cache[name];
}; };
require.cache=Object.create(null); require.cache=Object.create(null);
@ -19,7 +19,7 @@ process.argv
.forEach(([modFile, modName]) => { .forEach(([modFile, modName]) => {
const modSource=fs.readFileSync(modFile, 'utf8'); const modSource=fs.readFileSync(modFile, 'utf8');
outputCode.push(` outputCode.push(`
require.source['${modName}']=(a => a.bind(a)) (function ${modName}() { require.source['${modName}']=(a => a.bind(a)) (function ${modName}(module) {
'use strict'; 'use strict';
${modSource} ${modSource}
}); });

View file

@ -25,14 +25,18 @@
const levelList=assets.get('levelList'); const levelList=assets.get('levelList');
Object.keys(levelList).forEach(category => { Object.keys(levelList).forEach(category => {
const nav=document.querySelector('nav'); const cat=levelList[category];
const section=nav.appendChild(document.createElement('section')); const section=nav.appendChild(document.createElement('section'));
const h1=section.appendChild(document.createElement('h1')); const h1=section.appendChild(document.createElement('h1'));
h1.innerText=category[0].toUpperCase()+category.slice(1)+" Mode"; h1.innerText=category[0].toUpperCase()+category.slice(1)+" Mode";
const p=section.appendChild(document.createElement('p'));
p.innerText=cat.desc;
const ul=section.appendChild(document.createElement('ul')); const ul=section.appendChild(document.createElement('ul'));
levelList[category].levels.forEach(level => { cat.levels.forEach((level, i) => {
level=''+level; level=''+level;
const cat=levelList[category];
const displayName=cat.levelDisplay const displayName=cat.levelDisplay
.replace(/<n>/g, level) .replace(/<n>/g, level)
.replace(/<l>/g, level.toLowerCase()); .replace(/<l>/g, level.toLowerCase());
@ -43,13 +47,16 @@
const a=li.appendChild(document.createElement('a')); const a=li.appendChild(document.createElement('a'));
a.href='#'+category+'/'+fileName; a.href='#'+category+'/'+fileName;
a.innerText=displayName; a.innerText=displayName;
if(cat.levelDesc) {
const span=li.appendChild(document.createElement('span'));
span.innerText=cat.levelDesc[i];
}
}); });
}); });
const handleGamepads=() => { const handleGamepads=() => {
const gp=navigator.getGamepads()[0]; const gp=navigator.getGamepads()[0];
let inputs=currentInputs; let inputs=currentInputs;
console.log(gp);
if(!gp || !gp.axes) return; if(!gp || !gp.axes) return;
const magnitude=Math.hypot(gp.axes[0], gp.axes[1]); const magnitude=Math.hypot(gp.axes[0], gp.axes[1]);
@ -71,23 +78,26 @@
const resp=await fetch('levels/'+filename); const resp=await fetch('levels/'+filename);
return await resp.json(); return await resp.json();
})(); })();
console.log(rules, level);
const SnekGame=require('snek'); const SnekGame=require('snek');
const snek=new SnekGame(level, canvas, rules); const snek=new SnekGame(level, canvas, rules);
canvas.classList.remove('hidden'); canvas.classList.remove('hidden');
snek.start(); snek.start();
snek.callback=() => { snek.callback=evt => {
if(navigator.getGamepads) handleGamepads(); if(evt=='tick') {
snek.handleInputs(currentInputs); if(navigator.getGamepads) handleGamepads();
snek.handleInputs(currentInputs);
}
}; };
currentGame=snek; currentGame=snek;
//XXX
window.snek=snek;
}); });
window.addEventListener('keydown', async e => { window.addEventListener('keydown', async e => {
if(e.key=='f' && !canvas.classList.contains('hidden')) { if(e.key=='f') {
if(document.fullscreenElement) await document.exitFullscreen(); if(document.fullscreenElement) await document.exitFullscreen();
else await canvas.requestFullscreen(); else await main.requestFullscreen();
resizeCanvas(); resizeCanvas();
} }

View file

@ -6,21 +6,62 @@ const ifNaN=(v, r) => isNaN(v)?r:v;
class SnekGame { class SnekGame {
constructor(settings, canvas, rules) { constructor(settings, canvas, rules) {
// build the world
this.dimensions=[...settings.dimensions];
this.world=Array(settings.dimensions[0]);
for(let i=0; i<settings.dimensions[0]; i++) {
this.world[i]=Array(settings.dimensions[1]);
this.world[i].fill(EMPTY);
}
console.log(this);
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD);
settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE);
// setup the delay // setup the delay
this.delay=settings.delay; this.delay=settings.delay;
// world is given in the level
if(settings.world) { // explicitly
// convert the world
this.world=Array(settings.world[0].length);
for(let x=0; x<this.world.length; x++) {
this.world[x]=Array(settings.world.length);
for(let y=0; y<this.world[x].length; y++) {
this.world[x][y]=(() => {
switch(settings.world[y][x]) {
case ' ': return EMPTY;
case 'f': return FOOD;
case 'w': return WALL;
}
})();
}
}
//
// extract the dimensions
this.dimensions=[this.world.length, this.world[0].length];
// extract the fruits
this.fruits=[];
this.world
.forEach((l, x) => l.forEach(
(c, y) => {
if(c==FOOD) this.fruits.push([x, y]);
}
));
} else { // dimension and objects
// get the dimensions
this.dimensions=[...settings.dimensions];
// build an empty world
this.world=Array(settings.dimensions[0]);
for(let i=0; i<settings.dimensions[0]; i++) {
this.world[i]=Array(settings.dimensions[1]);
this.world[i].fill(EMPTY);
}
// add the walls
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
// add the food
settings.food.forEach(([x, y]) => this.world[x][y]=FOOD);
this.fruits=[...settings.food];
}
// add the snake to the world
settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE);
// get the head and initial direction // get the head and initial direction
this.head=[...settings.snake[0]]; this.head=[...settings.snake[0]];
this.direction=[ this.direction=[
@ -28,9 +69,8 @@ class SnekGame {
ifNaN(settings.snake[0][1]-settings.snake[1][1], 0) ifNaN(settings.snake[0][1]-settings.snake[1][1], 0)
]; ];
// get the snake and the fruits themselves // store the snake
this.snake=[...settings.snake]; this.snake=[...settings.snake];
this.fruits=[...settings.food];
// get our canvas, like, if we want to actually draw // get our canvas, like, if we want to actually draw
this.canvas=canvas; this.canvas=canvas;
@ -43,8 +83,13 @@ class SnekGame {
speedIncrease: true, speedIncrease: true,
worldWrap: true, worldWrap: true,
winCondition: 'none', winCondition: 'none',
scoreSystem: 'fruit' scoreSystem: 'fruit',
}, rules, settings); netPlay: false
}, rules, settings.rules || {});
}
get playTime() {
return Date.now()-this.firstStep;
} }
draw() { draw() {
@ -121,7 +166,7 @@ class SnekGame {
this.ctx.drawImage( this.ctx.drawImage(
fruit, fruit,
offsetX+cellSize*x+(1-fruitScale)*cellSize/2, offsetX+cellSize*x+(1-fruitScale)*cellSize/2,
offsetY+cellSize*x+(1-fruitScale)*cellSize/2, offsetY+cellSize*y+(1-fruitScale)*cellSize/2,
cellSize*fruitScale, cellSize*fruitScale,
cellSize*fruitScale cellSize*fruitScale
); );
@ -163,11 +208,8 @@ class SnekGame {
// remove the fruit from existence // remove the fruit from existence
this.world[head[0]][head[1]]=SNAKE; this.world[head[0]][head[1]]=SNAKE;
this.fruits.splice( this.fruits=this.fruits.filter(
this.fruits.find( ([x, y]) => !(x==head[0] && y==head[1])
([x, y]) => x==head[0] && y==head[1]
),
1
); );
// custom rules // custom rules
@ -192,14 +234,22 @@ class SnekGame {
this.snake.unshift(head); this.snake.unshift(head);
// victory condition // victory condition
if(!this.fruits.length) return this.win(); if(this.rules.winCondition=='fruit') {
if(!this.fruits.length) return this.win();
}
if(this.rules.winCondition=='time') {
if(this.playTime>=this.rules.gameDuration) return this.win();
}
if(this.rules.winCondition=='score') {
if(this.score>=this.rules.scoreObjective) return this.win();
}
} }
tick() { tick() {
if(!this.playing) return; if(!this.playing) return;
if(!this.lastStep) this.lastStep=this.firstStep; if(!this.lastStep) this.lastStep=this.firstStep;
this.draw(); this.draw();
if(this.callback) this.callback(); if(this.callback) this.callback('tick');
if(this.lastStep+this.delay<Date.now()) { if(this.lastStep+this.delay<Date.now()) {
this.lastStep+=this.delay; this.lastStep+=this.delay;
this.step(); this.step();
@ -209,15 +259,14 @@ class SnekGame {
win() { win() {
this.playing=false; this.playing=false;
// you gud lol this.endPlayTime=this.playTime;
console.log("You gud lol"); if(this.callback) this.callback('win');
console.log(`Won in ${(Date.now()-this.firstStep)/1000} seconds`);
} }
die() { die() {
this.playing=false; this.playing=false;
// you bad lol this.endPlayTime=this.playTime;
console.log("You bad lol"); if(this.callback) this.callback('die');
} }
handleInputs(inputs) { handleInputs(inputs) {
@ -244,4 +293,5 @@ class SnekGame {
} }
} }
module.exports=SnekGame;
return SnekGame; return SnekGame;

View file

@ -1,16 +1,32 @@
nav { nav {
flex: 1;
font-size: 1.6rem; font-size: 1.6rem;
display: flex; display: flex;
justify-content: space-evenly;
align-self: center;
section { section {
flex: 1; flex: 1;
max-width: 50vh;
margin: 2rem;
} }
p {
margin-bottom: 1rem;
}
ul { ul {
padding-left: 2rem; padding-left: 2rem;
} }
li { li {
list-style-type: disc; list-style-type: disc;
* {
display: block;
}
margin: .5rem;
} }
} }

29
src/less/popup.less Normal file
View file

@ -0,0 +1,29 @@
@keyframes popupAppear {
0% {
background: transparent;
}
100% {
background: black;
}
}
.popup {
animation: popupAppear 1s linear no-repeat;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
.content {
border-radius: 2rem;
background: @accentbg;
text-align: center;
postion: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}

View file

@ -20,7 +20,6 @@ html {
body { body {
color: @fg; color: @fg;
background: @bg;
} }
h1, h2, h3, h4, h5, h6, strong, a { h1, h2, h3, h4, h5, h6, strong, a {
@ -72,14 +71,14 @@ header, footer, main {
} }
main { main {
flex: 1; flex: 1;
display: flex;
position: relative; position: relative;
background: @bg;
canvas { canvas {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: @bg;
} }
} }
@ -103,3 +102,6 @@ p {
// setup the main menu // setup the main menu
@import 'mainMenu'; @import 'mainMenu';
// setup the popups
@import 'popup';