Snek/src/js/main.js

249 lines
6.2 KiB
JavaScript
Raw Normal View History

2020-03-25 14:57:20 +00:00
(async () => {
// 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');
const input=require('input');
const levels=require('levels');
const config=require('config');
2020-04-04 20:59:50 +00:00
// get a known state
2020-03-25 14:57:20 +00:00
await new Promise(ok => assets.onReady(ok));
location.hash='menu';
2020-03-25 14:57:20 +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
// load data from server
const levelList=assets.get('levelList');
// get our global variables
2020-03-25 14:57:20 +00:00
let currentGame=null;
// forward-declare functions
2020-04-05 18:58:35 +00:00
let resizeCanvas, getLevel, startGame, handleWin, handleDeath, menu, help, restart;
// 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);
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
// 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) => {
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'));
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
});
});
// start a new game
startGame=async (category, levelId, filename) => {
// stop any running games
if(currentGame) currentGame.playing=false;
2020-03-26 11:54:23 +00:00
// load rules and level from cache or server
2020-03-25 14:57:20 +00:00
const rules=levelList[category].rules || {};
const level=await levels.get(filename);
2020-03-25 14:57:20 +00:00
2020-04-05 16:23:11 +00:00
// create the game and attach the callbacks and config
const snek=currentGame=new SnekGame(level, canvas, rules);
2020-03-25 17:29:28 +00:00
snek.callback=evt => {
if(evt=='tick') {
input.framefn();
snek.handleInputs(input.inputs);
2020-04-04 20:59:50 +00:00
} else if(evt=='win') {
handleWin(snek);
} else if(evt=='die') {
handleDeath(snek);
2020-03-25 17:29:28 +00:00
}
2020-03-25 14:57:20 +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
// push some userdata to the snake
snek.userdata={
category,
levelId,
filename
};
2020-03-26 11:54:23 +00:00
// reset the inputs
input.clear();
2020-03-25 14:57:20 +00:00
// start the actual game
snek.start();
};
2020-03-26 09:47:22 +00:00
// return to the menu
menu=() => {
// stop any running games
if(currentGame) currentGame.playing=false;
2020-03-26 11:04:18 +00:00
// setup the DOM
nav.classList.remove('hidden');
canvas.classList.add('hidden');
2020-04-05 18:58:35 +00:00
hud.classList.add('hidden');
};
2020-03-26 11:54:23 +00:00
// display the win popup
handleWin=async snek => {
2020-04-05 18:58:35 +00:00
// hide the HUD
hud.classList.add('hidden');
// fetch userdata from the game
const {category, levelId, filename}=snek.userdata;
// create and configure popup
let popup=new Popup("Finished!");
popup.addStrong("You won!");
popup.addContent({
"Time": snek.playTime/1000+'s',
"Score": snek.score,
"Final length": snek.snake.length
2020-03-26 11:54:23 +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
// 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);
} else if(result=='menu') {
location.hash='menu';
} else if(result=='next') {
2020-04-05 18:58:35 +00:00
const {category, levelId}=snek.userdata;
let nextId=(+levelId)+1;
let {levelString}=getLevel(category, nextId)
location.hash=levelString;
}
};
2020-03-26 09:47:22 +00:00
// display the death popup
handleDeath=async snek => {
2020-04-05 18:58:35 +00:00
// hide the HUD
hud.classList.add('hidden');
// create and configure popup
let popup=new Popup("Finished!");
popup.addStrong("You died...");
popup.addContent({
"Time": snek.playTime/1000+'s',
"Score": snek.score,
"Final length": snek.snake.length
2020-04-04 22:35:40 +00:00
});
popup.buttons={
retry: "Retry",
menu: "Main menu"
};
2020-04-04 22:35:40 +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);
} 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);
})();
// handle page navigation
window.addEventListener('hashchange', () => {
const hash=location.hash.substr(1);
2020-04-04 22:35:40 +00:00
if(hash=='' || hash=='menu') return menu();
else if(hash=='help') return help();
const [_, category, levelId, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/([a-zA-Z0-9_-]+?)\/(.+)/);
startGame(category, levelId, filename);
});
// enable input methods overlay
input.init({hud});
2020-03-25 14:57:20 +00:00
})();