fixed engine and added level2
This commit is contained in:
parent
beb9598f69
commit
03e0c97280
8 changed files with 186 additions and 46 deletions
|
@ -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
23
levels/level2.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"world": [
|
||||||
|
" ",
|
||||||
|
" f ",
|
||||||
|
" fw w wf ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" w ",
|
||||||
|
" f "
|
||||||
|
],
|
||||||
|
"snake": [
|
||||||
|
[6,6],
|
||||||
|
[6,7],
|
||||||
|
[6,8],
|
||||||
|
[6,9]
|
||||||
|
],
|
||||||
|
"delay": 100
|
||||||
|
}
|
|
@ -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}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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(evt=='tick') {
|
||||||
if(navigator.getGamepads) handleGamepads();
|
if(navigator.getGamepads) handleGamepads();
|
||||||
snek.handleInputs(currentInputs);
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,20 +6,61 @@ const ifNaN=(v, r) => isNaN(v)?r:v;
|
||||||
|
|
||||||
class SnekGame {
|
class SnekGame {
|
||||||
constructor(settings, canvas, rules) {
|
constructor(settings, canvas, rules) {
|
||||||
// build the world
|
// setup the 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];
|
this.dimensions=[...settings.dimensions];
|
||||||
|
|
||||||
|
// build an empty world
|
||||||
this.world=Array(settings.dimensions[0]);
|
this.world=Array(settings.dimensions[0]);
|
||||||
for(let i=0; i<settings.dimensions[0]; i++) {
|
for(let i=0; i<settings.dimensions[0]; i++) {
|
||||||
this.world[i]=Array(settings.dimensions[1]);
|
this.world[i]=Array(settings.dimensions[1]);
|
||||||
this.world[i].fill(EMPTY);
|
this.world[i].fill(EMPTY);
|
||||||
}
|
}
|
||||||
console.log(this);
|
|
||||||
|
// add the walls
|
||||||
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
|
settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
|
||||||
|
|
||||||
|
// add the food
|
||||||
settings.food.forEach(([x, y]) => this.world[x][y]=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);
|
settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE);
|
||||||
|
|
||||||
// setup the delay
|
|
||||||
this.delay=settings.delay;
|
|
||||||
|
|
||||||
// get the head and initial direction
|
// get the head and initial direction
|
||||||
this.head=[...settings.snake[0]];
|
this.head=[...settings.snake[0]];
|
||||||
|
@ -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.rules.winCondition=='fruit') {
|
||||||
if(!this.fruits.length) return this.win();
|
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;
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
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 {
|
||||||
|
@ -12,5 +21,12 @@ nav {
|
||||||
|
|
||||||
li {
|
li {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
margin: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
29
src/less/popup.less
Normal file
29
src/less/popup.less
Normal 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%);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue