2020-03-25 14:57:20 +00:00
|
|
|
(async () => {
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// load modules
|
2020-03-25 14:57:20 +00:00
|
|
|
const assets=require('assets');
|
2020-04-04 20:59:50 +00:00
|
|
|
const Popup=require('popup');
|
|
|
|
const SnekGame=require('snek');
|
2020-04-06 18:16:28 +00:00
|
|
|
const configEditor=require('configEditor');
|
2020-04-05 14:14:39 +00:00
|
|
|
const input=require('input');
|
|
|
|
const levels=require('levels');
|
2020-04-06 08:58:44 +00:00
|
|
|
const config=require('config');
|
2020-04-04 20:59:50 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// get a known state
|
2020-03-25 14:57:20 +00:00
|
|
|
await new Promise(ok => assets.onReady(ok));
|
2020-04-05 14:14:39 +00:00
|
|
|
location.hash='menu';
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// get our DOM in check
|
2020-03-25 14:57:20 +00:00
|
|
|
const main=document.querySelector('main');
|
|
|
|
const nav=main.querySelector('nav');
|
|
|
|
const canvas=main.querySelector('canvas');
|
2020-04-05 18:58:35 +00:00
|
|
|
const hud=main.querySelector('#hud');
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// load data from server
|
|
|
|
const levelList=assets.get('levelList');
|
|
|
|
|
|
|
|
// get our global variables
|
2020-03-25 14:57:20 +00:00
|
|
|
let currentGame=null;
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// forward-declare functions
|
2020-04-06 10:02:22 +00:00
|
|
|
let resizeCanvas, getLevel, startGame, stopGame, handleWin, handleDeath, menu, help, settings, restart;
|
2020-04-05 14:14:39 +00:00
|
|
|
|
|
|
|
// handle window resize and fullscreen
|
|
|
|
resizeCanvas=() => {
|
2020-03-25 14:57:20 +00:00
|
|
|
if(document.fullscreenElement) {
|
|
|
|
canvas.width=screen.width;
|
|
|
|
canvas.height=screen.height;
|
|
|
|
} else {
|
|
|
|
canvas.width=main.clientWidth;
|
|
|
|
canvas.height=main.clientHeight;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
resizeCanvas();
|
|
|
|
window.addEventListener('resize', resizeCanvas);
|
2020-04-05 14:14:39 +00:00
|
|
|
window.addEventListener('keydown', async e => {
|
|
|
|
if(e.key=='f') {
|
|
|
|
if(document.fullscreenElement) await document.exitFullscreen();
|
|
|
|
else await main.requestFullscreen();
|
|
|
|
resizeCanvas();
|
|
|
|
}
|
|
|
|
});
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// get a level for a category and an id
|
|
|
|
getLevel=(category, id) => {
|
|
|
|
const cat=levelList[category];
|
|
|
|
id=''+id;
|
|
|
|
|
|
|
|
const displayName=cat.levelDisplay
|
|
|
|
.replace(/<n>/g, id)
|
|
|
|
.replace(/<l>/g, id.toLowerCase());
|
|
|
|
const fileName=cat.levelFilename
|
|
|
|
.replace(/<n>/g, id)
|
|
|
|
.replace(/<l>/g, id.toLowerCase());
|
|
|
|
const levelString=category+'/'+id+'/'+fileName;
|
|
|
|
|
|
|
|
return {
|
|
|
|
displayName,
|
|
|
|
fileName,
|
|
|
|
levelString
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
// buid menu from level list
|
2020-03-25 14:57:20 +00:00
|
|
|
Object.keys(levelList).forEach(category => {
|
2020-03-25 17:29:28 +00:00
|
|
|
const cat=levelList[category];
|
|
|
|
|
2020-03-25 14:57:20 +00:00
|
|
|
const section=nav.appendChild(document.createElement('section'));
|
|
|
|
const h1=section.appendChild(document.createElement('h1'));
|
|
|
|
h1.innerText=category[0].toUpperCase()+category.slice(1)+" Mode";
|
2020-03-25 17:29:28 +00:00
|
|
|
|
|
|
|
const p=section.appendChild(document.createElement('p'));
|
|
|
|
p.innerText=cat.desc;
|
|
|
|
|
2020-03-25 14:57:20 +00:00
|
|
|
const ul=section.appendChild(document.createElement('ul'));
|
2020-03-25 17:29:28 +00:00
|
|
|
cat.levels.forEach((level, i) => {
|
2020-04-05 14:14:39 +00:00
|
|
|
const {displayName, fileName, levelString}=getLevel(category, level);
|
2020-03-25 14:57:20 +00:00
|
|
|
const li=ul.appendChild(document.createElement('li'));
|
|
|
|
const a=li.appendChild(document.createElement('a'));
|
2020-04-05 14:14:39 +00:00
|
|
|
a.href='#'+levelString;
|
2020-03-25 14:57:20 +00:00
|
|
|
a.innerText=displayName;
|
2020-03-25 17:29:28 +00:00
|
|
|
if(cat.levelDesc) {
|
|
|
|
const span=li.appendChild(document.createElement('span'));
|
|
|
|
span.innerText=cat.levelDesc[i];
|
|
|
|
}
|
2020-03-25 14:57:20 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-04-06 10:02:22 +00:00
|
|
|
// stop a running game
|
|
|
|
stopGame=() => {
|
|
|
|
if(currentGame) {
|
|
|
|
// stop the actual game
|
|
|
|
currentGame.playing=false;
|
|
|
|
|
|
|
|
// setup the DOM
|
|
|
|
nav.classList.remove('hidden');
|
|
|
|
canvas.classList.add('hidden');
|
|
|
|
hud.classList.add('hidden');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// start a new game
|
|
|
|
startGame=async (category, levelId, filename) => {
|
|
|
|
// stop any running games
|
2020-04-06 10:02:22 +00:00
|
|
|
stopGame();
|
2020-03-26 11:54:23 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// load rules and level from cache or server
|
2020-03-25 14:57:20 +00:00
|
|
|
const rules=levelList[category].rules || {};
|
2020-04-05 14:14:39 +00:00
|
|
|
const level=await levels.get(filename);
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-06 10:02:22 +00:00
|
|
|
// stop any running games again
|
|
|
|
stopGame();
|
|
|
|
|
2020-04-05 16:23:11 +00:00
|
|
|
// create the game and attach the callbacks and config
|
2020-04-05 14:14:39 +00:00
|
|
|
const snek=currentGame=new SnekGame(level, canvas, rules);
|
2020-03-25 17:29:28 +00:00
|
|
|
snek.callback=evt => {
|
|
|
|
if(evt=='tick') {
|
2020-04-05 14:14:39 +00:00
|
|
|
input.framefn();
|
|
|
|
snek.handleInputs(input.inputs);
|
2020-04-04 20:59:50 +00:00
|
|
|
} else if(evt=='win') {
|
2020-04-05 14:14:39 +00:00
|
|
|
handleWin(snek);
|
|
|
|
} else if(evt=='die') {
|
|
|
|
handleDeath(snek);
|
2020-03-25 17:29:28 +00:00
|
|
|
}
|
2020-03-25 14:57:20 +00:00
|
|
|
};
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// setup the DOM
|
|
|
|
nav.classList.add('hidden');
|
|
|
|
canvas.classList.remove('hidden');
|
2020-04-05 18:58:35 +00:00
|
|
|
hud.classList.remove('hidden');
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// push some userdata to the snake
|
|
|
|
snek.userdata={
|
|
|
|
category,
|
|
|
|
levelId,
|
|
|
|
filename
|
|
|
|
};
|
2020-03-26 11:54:23 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// reset the inputs
|
|
|
|
input.clear();
|
2020-03-25 14:57:20 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// start the actual game
|
|
|
|
snek.start();
|
|
|
|
};
|
2020-03-26 09:47:22 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// return to the menu
|
|
|
|
menu=() => {
|
2020-04-06 10:02:22 +00:00
|
|
|
stopGame();
|
2020-04-05 14:14:39 +00:00
|
|
|
};
|
2020-03-26 11:54:23 +00:00
|
|
|
|
2020-04-06 18:16:28 +00:00
|
|
|
// show config editor
|
|
|
|
settings=async () => {
|
|
|
|
stopGame();
|
|
|
|
await configEditor.show();
|
|
|
|
location.hash='menu';
|
|
|
|
};
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// display the win popup
|
|
|
|
handleWin=async snek => {
|
2020-04-05 18:58:35 +00:00
|
|
|
// hide the HUD
|
|
|
|
hud.classList.add('hidden');
|
2020-04-05 14:14:39 +00:00
|
|
|
|
2020-04-06 08:58:44 +00:00
|
|
|
// fetch userdata from the game
|
|
|
|
const {category, levelId, filename}=snek.userdata;
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// create and configure popup
|
|
|
|
let popup=new Popup("Finished!");
|
|
|
|
popup.addStrong("You won!");
|
|
|
|
popup.addContent({
|
|
|
|
"Time": snek.playTime/1000+'s',
|
|
|
|
"Score": snek.score,
|
2020-04-06 16:39:34 +00:00
|
|
|
"Final length": snek.length
|
2020-03-26 11:54:23 +00:00
|
|
|
});
|
2020-04-05 14:14:39 +00:00
|
|
|
popup.buttons={
|
|
|
|
retry: "Retry",
|
|
|
|
menu: "Main menu"
|
|
|
|
};
|
|
|
|
if(levelList[category].nextLevel) {
|
|
|
|
let nextId=(+levelId)+1;
|
|
|
|
if(levelList[category].levels.includes(nextId)) popup.buttons.next="Next level";
|
|
|
|
}
|
2020-03-26 11:54:23 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// show the actual popup
|
|
|
|
let result=await popup.display(main);
|
|
|
|
|
|
|
|
// act on it
|
|
|
|
if(result=='retry') {
|
2020-04-05 20:22:11 +00:00
|
|
|
startGame(category, levelId, filename);
|
2020-04-05 14:14:39 +00:00
|
|
|
} else if(result=='menu') {
|
|
|
|
location.hash='menu';
|
|
|
|
} else if(result=='next') {
|
2020-04-05 18:58:35 +00:00
|
|
|
const {category, levelId}=snek.userdata;
|
2020-04-05 14:14:39 +00:00
|
|
|
let nextId=(+levelId)+1;
|
|
|
|
let {levelString}=getLevel(category, nextId)
|
|
|
|
location.hash=levelString;
|
|
|
|
}
|
|
|
|
};
|
2020-03-26 09:47:22 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// display the death popup
|
|
|
|
handleDeath=async snek => {
|
2020-04-05 18:58:35 +00:00
|
|
|
// hide the HUD
|
|
|
|
hud.classList.add('hidden');
|
2020-04-05 14:14:39 +00:00
|
|
|
|
|
|
|
// create and configure popup
|
|
|
|
let popup=new Popup("Finished!");
|
|
|
|
popup.addStrong("You died...");
|
|
|
|
popup.addContent({
|
|
|
|
"Time": snek.playTime/1000+'s',
|
|
|
|
"Score": snek.score,
|
2020-04-06 16:39:34 +00:00
|
|
|
"Final length": snek.length
|
2020-04-04 22:35:40 +00:00
|
|
|
});
|
2020-04-05 14:14:39 +00:00
|
|
|
popup.buttons={
|
|
|
|
retry: "Retry",
|
|
|
|
menu: "Main menu"
|
|
|
|
};
|
2020-04-04 22:35:40 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// show the actual popup
|
|
|
|
let result=await popup.display(main);
|
|
|
|
|
|
|
|
// act on it
|
|
|
|
if(result=='retry') {
|
2020-04-05 20:22:11 +00:00
|
|
|
const {category, levelId, filename}=snek.userdata;
|
|
|
|
startGame(category, levelId, filename);
|
2020-04-05 14:14:39 +00:00
|
|
|
} else if(result=='menu') {
|
|
|
|
location.hash='menu';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-05 18:58:35 +00:00
|
|
|
// quick restart
|
|
|
|
restart=() => {
|
|
|
|
if(currentGame && currentGame.playing) {
|
|
|
|
const {category, levelId, filename}=currentGame.userdata;
|
|
|
|
startGame(category, levelId, filename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
window.addEventListener('keydown', e => {
|
|
|
|
if(e.key=='r') restart();
|
|
|
|
});
|
|
|
|
(() => {
|
|
|
|
let restartbtn=hud.appendChild(document.createElement('span'));
|
|
|
|
restartbtn.classList.add('restart');
|
|
|
|
restartbtn.addEventListener('click', restart);
|
|
|
|
restartbtn.addEventListener('touchend', restart);
|
|
|
|
})();
|
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
// handle page navigation
|
|
|
|
window.addEventListener('hashchange', () => {
|
|
|
|
const hash=location.hash.substr(1);
|
2020-04-04 22:35:40 +00:00
|
|
|
|
2020-04-05 14:14:39 +00:00
|
|
|
if(hash=='' || hash=='menu') return menu();
|
|
|
|
else if(hash=='help') return help();
|
2020-04-06 10:02:22 +00:00
|
|
|
else if(hash=='settings') return settings();
|
2020-04-05 14:14:39 +00:00
|
|
|
|
|
|
|
const [_, category, levelId, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/([a-zA-Z0-9_-]+?)\/(.+)/);
|
|
|
|
startGame(category, levelId, filename);
|
|
|
|
});
|
|
|
|
|
2020-04-06 08:58:44 +00:00
|
|
|
// enable input methods overlay
|
|
|
|
input.init({hud});
|
2020-03-25 14:57:20 +00:00
|
|
|
})();
|