A "simple" Snake, done as my final JS class project back in DUT
https://snek.s.codinget.me
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
5.2 KiB
180 lines
5.2 KiB
(async () => {
|
|
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');
|
|
const nav=main.querySelector('nav');
|
|
const canvas=main.querySelector('canvas');
|
|
|
|
const config=assets.get('config');
|
|
|
|
let currentGame=null;
|
|
let currentInputs={};
|
|
|
|
const resizeCanvas=() => {
|
|
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);
|
|
|
|
const levelList=assets.get('levelList');
|
|
Object.keys(levelList).forEach(category => {
|
|
const cat=levelList[category];
|
|
|
|
const section=nav.appendChild(document.createElement('section'));
|
|
const h1=section.appendChild(document.createElement('h1'));
|
|
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'));
|
|
cat.levels.forEach((level, i) => {
|
|
level=''+level;
|
|
const displayName=cat.levelDisplay
|
|
.replace(/<n>/g, level)
|
|
.replace(/<l>/g, level.toLowerCase());
|
|
const fileName=cat.levelFilename
|
|
.replace(/<n>/g, level)
|
|
.replace(/<l>/g, level.toLowerCase());
|
|
const li=ul.appendChild(document.createElement('li'));
|
|
const a=li.appendChild(document.createElement('a'));
|
|
a.href='#'+category+'/'+fileName;
|
|
a.innerText=displayName;
|
|
if(cat.levelDesc) {
|
|
const span=li.appendChild(document.createElement('span'));
|
|
span.innerText=cat.levelDesc[i];
|
|
}
|
|
});
|
|
});
|
|
|
|
const handleGamepads=() => {
|
|
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) {
|
|
if(angle>.25 && angle <.75) inputs.right=true;
|
|
else if(angle>.75 && angle<1.25) inputs.up=true;
|
|
else if(angle>1.25 && angle<1.75) inputs.left=true;
|
|
else inputs.down=true;
|
|
}
|
|
|
|
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');
|
|
|
|
const [_, category, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/(.+)/);
|
|
const rules=levelList[category].rules || {};
|
|
const level=await (async () => {
|
|
const resp=await fetch('levels/'+filename);
|
|
return await resp.json();
|
|
})();
|
|
|
|
const snek=new SnekGame(level, canvas, rules);
|
|
canvas.classList.remove('hidden');
|
|
snek.start();
|
|
snek.callback=evt => {
|
|
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;
|
|
});
|
|
|
|
window.addEventListener('keydown', async e => {
|
|
e.preventDefault();
|
|
if(e.key=='f') {
|
|
if(document.fullscreenElement) await document.exitFullscreen();
|
|
else await main.requestFullscreen();
|
|
resizeCanvas();
|
|
}
|
|
|
|
let inputs=currentInputs;
|
|
if(e.key=='ArrowUp') inputs.up=true;
|
|
else if(e.key=='ArrowDown') inputs.down=true;
|
|
else if(e.key=='ArrowLeft') inputs.left=true;
|
|
else if(e.key=='ArrowRight') inputs.right=true;
|
|
|
|
if(!config.keyboard.buffer) inputs.clearBuffer=true;
|
|
});
|
|
|
|
if(config.touchscreen.mode=='crosspad') {
|
|
const handleTouch=e => {
|
|
let x=e.touches[0].clientX-window.innerWidth/2;
|
|
let y=e.touches[0].clientY-window.innerHeight/2;
|
|
const angle=((Math.atan2(x, y)+2*Math.PI)%(2*Math.PI))/Math.PI;
|
|
|
|
let inputs=currentInputs;
|
|
if(angle>.25 && angle <.75) inputs.right=true;
|
|
else if(angle>.75 && angle<1.25) inputs.up=true;
|
|
else if(angle>1.25 && angle<1.75) inputs.left=true;
|
|
else inputs.down=true;
|
|
|
|
if(!config.touchscreen.buffer) inputs.clearBuffer=true;
|
|
};
|
|
window.addEventListener('touchstart', handleTouch);
|
|
window.addEventListener('touchmove', handleTouch);
|
|
}
|
|
|
|
if(config.touchscreen.mode=='joystick') {
|
|
let center={x: 0, y: 0};
|
|
window.center=center;
|
|
window.addEventListener('touchstart', e => {
|
|
center.x=e.touches[0].clientX;
|
|
center.y=e.touches[0].clientY;
|
|
});
|
|
window.addEventListener('touchmove', e => {
|
|
let x=e.touches[0].clientX-center.x;
|
|
let y=e.touches[0].clientY-center.y;
|
|
const angle=((Math.atan2(x, y)+2*Math.PI)%(2*Math.PI))/Math.PI;
|
|
const magnitude=Math.hypot(x, y);
|
|
|
|
let inputs=currentInputs;
|
|
if(magnitude>config.touchscreen.deadzone) {
|
|
if(angle>.25 && angle <.75) inputs.right=true;
|
|
else if(angle>.75 && angle<1.25) inputs.up=true;
|
|
else if(angle>1.25 && angle<1.75) inputs.left=true;
|
|
else inputs.down=true;
|
|
}
|
|
|
|
if(!config.touchscreen.buffer) inputs.clearBuffer=true;
|
|
});
|
|
}
|
|
|
|
})();
|
|
|