Skip to content

Instantly share code, notes, and snippets.

@hizkifw
Last active April 9, 2019 15:09
Show Gist options
  • Save hizkifw/2662f1ed029413411124bdde137547b0 to your computer and use it in GitHub Desktop.
Save hizkifw/2662f1ed029413411124bdde137547b0 to your computer and use it in GitHub Desktop.
Bootleg Touhou game in Javascript
<!DOCTYPE html>
<html>
<head>
<title>Bullet Game</title>
<script>
// quick and dirty bullet hell game written in javascript using html canvas
// made this at my school's computer lab while waiting for the guys to fix some technical difficulties
// not using any libraries because they disabled the internet
// wouldn't have made this if the internet was up in the first place anyways
// anyways have fun with this thing
// i'll probably add some cool things to it later
var c;
function init() {
c = document.getElementById("canvas").getContext("2d");
window.onkeydown = keydown;
window.onkeyup = keyup;
setInterval(update, 1000/60);
draw();
}
keys = {
Up: false,
Down: false,
Left: false,
Right: false,
Slow: false,
Pause: false
};
keymap = {
"Escape": "Pause",
ArrowUp: "Up",
ArrowDown: "Down",
ArrowLeft: "Left",
ArrowRight: "Right",
w: "Up",
s: "Down",
a: "Left",
d: "Right",
W: "Up",
S: "Down",
A: "Left",
D: "Right",
Shift: "Slow"
};
function keydown(e) {
console.log(e.key);
if(typeof(keymap[e.key]) !== "undefined") {
keys[keymap[e.key]] = true;
if(keymap[e.key] == "Pause")
pause = !pause;
}
}
function keyup(e) {
if(typeof(keymap[e.key]) !== "undefined")
keys[keymap[e.key]] = false;
}
var player = {
pos: [10, 300],
size: 10,
red: 1,
health: 100
}
var enemy = {
pos: [400, 300],
size: 50,
health: 1000,
movement: 0
}
var pause = false;
var bullets = [];
var b_rot = 0;
var frame = 0;
var score = 0;
var stage = 0;
var stage_timer = 0;
var lastUpdate = new Date().getTime();
var delta = 0;
var stages = [
{
name: "Game start!",
duration: 500,
clear: true,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
}
]
},
{
name: "Oh look, more bullets!",
duration: 750,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
},
{
interval: 4,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "Getting uncomfortable",
duration: 1500,
clear: true,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
},
{
interval: 4,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
}
],
bursts: [
{
interval: 500,
pos: [400, 300],
size: 10,
number: 75,
speed: .5,
angle: .5
},
{
interval: 2000,
pos: [400, 300],
size: 10,
number: 50,
speed: .25,
angle: .5
}
]
},
{
name: "Ah it stopped",
duration: 500,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
}
]
},
{
name: "Just kidding!",
duration: 2500,
bullets: [
{
interval: 5,
pos: [200, 300],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 4,
pos: [200, 300],
size: 10,
angle: .5,
speed: .25
},
{
interval: 5,
pos: [600, 300],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 4,
pos: [600, 300],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "It stopped again",
duration: 300,
clear: true,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
}
]
},
{
name: "Watch the corners!",
duration: 3000,
bullets: [
{
interval: 5,
pos: [0, 0],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 4,
pos: [800, 0],
size: 10,
angle: .5,
speed: .25
},
{
interval: 5,
pos: [800, 600],
size: 10,
angle: .666666666,
speed: .75
},
{
interval: 4,
pos: [0, 600],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "You're screwed now!",
duration: 3000,
clear: true,
bullets: [
{
interval: 5,
pos: [0, 0],
size: 10,
angle: .5,
speed: .75
},
{
interval: 4,
pos: [800, 0],
size: 10,
angle: .5,
speed: .25
},
{
interval: 5,
pos: [800, 600],
size: 10,
angle: .5,
speed: .75
},
{
interval: 4,
pos: [0, 600],
size: 10,
angle: .5,
speed: .25
},
{
interval: 5,
pos: [400, 0],
size: 10,
angle: .5,
speed: .75
},
{
interval: 4,
pos: [800, 300],
size: 10,
angle: .5,
speed: .25
},
{
interval: 5,
pos: [0, 300],
size: 10,
angle: .5,
speed: .75
},
{
interval: 4,
pos: [400, 600],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "Phew, that was close!",
duration: 500,
bullets: [
{
interval: 5,
pos: [400, 300],
size: 10,
angle: .666666666,
speed: .75
}
]
},
{
name: "Oh no",
duration: 2500,
bursts: [
{
interval: 100,
pos: [185, 115],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [585, 515],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [215, 485],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [615, 85],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
]
},
{
name: "OH NO",
duration: 2500,
clear: true,
bullets: [
{
interval: 4,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
}
],
bursts: [
{
interval: 100,
pos: [185, 115],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [585, 515],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [215, 485],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [615, 85],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
]
},
{
name: "NO NO NO NO",
duration: 300,
bullets: [
{
interval: 1,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "ah",
duration: 300,
bullets: [
{
interval: 4,
pos: [400, 300],
size: 10,
angle: .5,
speed: .25
}
]
},
{
name: "AH!",
duration: 300,
bullets: [
{
interval: 1,
pos: [400, 300],
size: 10,
angle: -.5,
speed: .25
}
]
},
{
name: "haa...",
duration: 300,
bullets: [
{
interval: 4,
pos: [400, 300],
size: 10,
angle: -.5,
speed: .25
}
]
},
{
name: "ha?",
duration: 500
},
{
name: "HAAAAAA!",
duration: 300,
bullets: [
{
interval: 1,
pos: [400, 300],
size: 10,
angle: .5,
speed: .5
}
]
},
{
name: "haaaaaa",
duration: 300,
clear: true,
bullets: [
{
interval: 4,
pos: [400, 300],
size: 10,
angle: -.5,
speed: .5
}
]
},
{
name: "That wasn't hard, was it?",
duration: 250
},
{
name: "What'd you say? You want more?",
duration: 250
},
{
name: "Well then...",
duration: 250
},
{
name: "Here's something new!",
duration: 1000,
bullets: [
{
interval: 5,
pos: [200, 100],
osc: [50, 0],
size: 10,
angle: 1,
speed: 2,
aim: true
},
{
interval: 5,
pos: [600, 100],
osc: [50, 0],
size: 10,
angle: 1,
speed: 2,
aim: true
}
]
},
{
name: "You like that huh?",
duration: 750,
clear: true,
bullets: [
{
interval: 10,
pos: [400, 0],
osc: [400, 0],
size: 10,
angle: .005,
speed: 2,
aim: true
},
{
interval: 10,
pos: [400, 600],
osc: [400, 0],
size: 10,
angle: .005,
speed: 2,
aim: true
},
{
interval: 10,
pos: [400, 0],
osc: [400, 0],
size: 10,
angle: .005,
angleoff: 3.14/4,
speed: 2,
aim: true
},
{
interval: 10,
pos: [400, 600],
osc: [400, 0],
size: 10,
angle: .005,
angleoff: 3.14/4,
speed: 2,
aim: true
},
{
interval: 10,
pos: [400, 0],
osc: [400, 0],
size: 10,
angle: .005,
angleoff: -3.14/4,
speed: 2,
aim: true
},
{
interval: 10,
pos: [400, 600],
osc: [400, 0],
size: 10,
angle: .005,
angleoff: -3.14/4,
speed: 2,
aim: true
}
]
},
{
name: "Ah cool you're alive",
duration: 500
},
{
name: "Let's try something exciting",
duration: 500
},
{
name: "Shall we?",
duration: 1000,
bullets: [
{
interval: 5,
pos: [400, 300],
osc: [50, 50],
size: 10,
angle: 1,
speed: 1,
aim: true
},
],
bursts: [
{
interval: 100,
pos: [185, 115],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [585, 515],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [215, 485],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
{
interval: 100,
pos: [615, 85],
size: 10,
number: 20,
speed: .5,
angle: .1*Math.PI
},
]
},
{
name: "Oh boy this is fun!",
duration: 1000,
clear: true,
bullets: [
{
interval: 5,
pos: [200, 200],
osc: [50, 50],
size: 10,
angle: 1,
speed: 1,
aim: true
},
{
interval: 5,
pos: [600, 200],
osc: [50, 50],
size: 10,
angle: 1,
speed: 1,
aim: true
},
{
interval: 5,
pos: [200, 400],
osc: [50, 50],
size: 10,
angle: 1,
speed: 1,
aim: true
},
{
interval: 5,
pos: [600, 400],
osc: [50, 50],
size: 10,
angle: 1,
speed: 1,
aim: true
},
]
},
]
function update_bullets_new() {
var b;
// Create bullets
if(typeof(stages[stage].bullets) !== "undefined") {
for(var i = 0; i < stages[stage].bullets.length; i++) {
if(frame % stages[stage].bullets[i].interval == 0) {
b = JSON.parse(JSON.stringify(stages[stage].bullets[i]));
if(typeof(b.angleoff) == "undefined")
b.angleoff = 0;
if(typeof(b.osc) !== "undefined") {
b.pos[0] += Math.sin(b.angleoff + b_rot*b.angle) * b.osc[0];
b.pos[1] += Math.cos(b.angleoff + b_rot*b.angle) * b.osc[1];
}
if(b.aim)
b.angle = Math.atan2(player.pos[0] - b.pos[0], player.pos[1] - b.pos[1]);
else
b.angle = b_rot*b.angle;
bullets = bullets.concat(b);
}
}
}
// Create bursts
if(typeof(stages[stage].bursts) !== "undefined") {
for(var i = 0; i < stages[stage].bursts.length; i++) {
if(frame % stages[stage].bursts[i].interval == 0) {
for(var o = 0; o < stages[stage].bursts[i].number; o++) {
b = JSON.parse(JSON.stringify(stages[stage].bursts[i]));
b.angle *= o;
bullets = bullets.concat(b);
}
}
}
}
}
function update_bullets_move() {
// update bullets
for(var i = 0; i < bullets.length; i++) {
if(typeof(bullets[i]) === "undefined")
continue;
// quick check distance for collision
if(Math.abs(bullets[i].pos[0] - player.pos[0]) < 2*player.size &&
Math.abs(bullets[i].pos[1] - player.pos[1]) < 2*player.size) {
if(
Math.sqrt(
Math.pow(player.pos[0] - bullets[i].pos[0], 2) +
Math.pow(player.pos[1] - bullets[i].pos[1], 2)
) < bullets[i].size / 2) {
// Player within bullet
bullets[i].pos = [-10000, -10000];
// Check bullet type
if(typeof(bullets[i].type) === "undefined" || bullets[i].type == 0) {
// damage
player.red = 1;
if(player.health > 0)
player.health -= 10;
} else if(bullets[i].type == 1) {
// add HP
if(player.health < 100 && player.health > 0)
player.health++;
} else if(bullets[i].type == 2) {
// add score
score += 100;
}
}
}
// homing bullets
if(typeof(bullets[i].home) !== "undefined" && bullets[i].home) {
bullets[i].angle = Math.atan2(player.pos[0] - bullets[i].pos[0], player.pos[1] - bullets[i].pos[1]);
}
bullets[i].pos[0] += bullets[i].speed * Math.sin(bullets[i].angle)
bullets[i].pos[1] += bullets[i].speed * Math.cos(bullets[i].angle)
// check if outside of screen
if(bullets[i].pos[0] < 0 || bullets[i].pos[0] > 800 || bullets[i].pos[1] < 0 || bullets[i].pos[1] > 600) {
//bullets.splice(i, 1);
delete bullets[i];
//bullets = bullets.filter(val => val);
}
}
}
function update_bullets_clean() {
// clean bullet array
if(frame % 500 == 0)
bullets = bullets.filter(val => val);
}
function update_player() {
// update player
var speed = keys.Slow ? 0.5 : 1;
if(keys.Up)
player.pos[1] -= speed;
else if(keys.Down)
player.pos[1] += speed;
if(keys.Right)
player.pos[0] += speed;
else if(keys.Left)
player.pos[0] -= speed;
player.red -= .1;
}
function update() {
if(pause)
return 0;
lastUpdate = new Date().getTime();
frame++;
stage_timer++;
b_rot++;
enemy.movement += 0.001;
if(player.health > 0)
score++;
else if(player.health < 0)
player.health = 0;
if(stage_timer > stages[stage].duration) {
if(stage+1 < stages.length) {
if(typeof(stages[stage].clear) !== "undefined" && stages[stage].clear) {
for(var i = 0; i < bullets.length; i++) {
// 0 - normal
// 1 - health
// 2 - score
if(typeof(bullets[i]) === "undefined")
continue;
bullets[i].type = Math.random() > 0.99 ? 1 : 2;
bullets[i].home = true;
bullets[i].speed = .015 * Math.sqrt(Math.pow(bullets[i].pos[0] - player.pos[0], 2) + Math.pow(bullets[i].pos[1] - player.pos[1], 2));
bullets[i].size /= 2;
}
}
stage++;
}
stage_timer = 0;
}
update_bullets_new();
update_bullets_move();
update_bullets_clean();
update_player();
now = new Date().getTime();
delta = now - lastUpdate;
if(delta > 1000/60) {
console.log("LAG! update took " + delta + " milliseconds");
}
}
// shim layer with setTimeout fallback
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
function draw_bullets() {
// bullets
for(var i = 0; i < bullets.length; i++) {
if(typeof(bullets[i]) === "undefined")
continue;
if(typeof(bullets[i].type) === "undefined" || bullets[i].type == 0)
c.fillStyle = "#FFFFFF";
else if(bullets[i].type == 1)
c.fillStyle = "#FF3333";
else if(bullets[i].type == 2)
c.fillStyle = "#5555FF";
else
c.fillStyle = "#555555";
c.beginPath();
c.ellipse(bullets[i].pos[0], bullets[i].pos[1], bullets[i].size/2, bullets[i].size/2, 0, 0, 2 * Math.PI);
c.fill();
}
}
function draw_warnings() {
// bullet spawn warning
var b, s;
if(stages[stage].duration - stage_timer < 250 && typeof(stages[stage+1]) !== "undefined") {
c.fillStyle = "#FF0000";
c.strokeStyle = "#FF0000";
c.lineWidth = 2;
if(typeof(stages[stage+1].bullets) !== "undefined") {
for(var i = 0; i < stages[stage+1].bullets.length; i++) {
b = stages[stage+1].bullets[i];
s = stages[stage].duration - stage_timer;
c.beginPath();
c.ellipse(b.pos[0], b.pos[1], s, s, 0, 0, 2 * Math.PI);
c.stroke();
}
}
if(typeof(stages[stage+1].bursts) !== "undefined") {
for(var i = 0; i < stages[stage+1].bursts.length; i++) {
b = stages[stage+1].bursts[i];
s = stages[stage].duration - stage_timer;
c.beginPath();
c.ellipse(b.pos[0], b.pos[1], s, s, 0, 0, 2 * Math.PI);
c.stroke();
}
}
}
}
function draw_ui() {
// health bar
c.fillStyle = "#FF0000";
c.fillRect(10, 10, 100, 10);
c.fillStyle = "#00FF00";
c.fillRect(10, 10, player.health, 10);
// score
c.font = "15px monospace";
c.textAlign = "left";
c.fillStyle = "#FFFFFF";
c.fillText("Score: " + score, 120, 19);
c.fillText(stages[stage].name, 10, 32);
// game over
if(player.health == 0) {
c.fillStyle = "#000000";
c.fillRect(200, 225, 400, 100);
c.font = "100px monospace";
c.textAlign = "center";
c.fillStyle = "#FF0000";
c.fillText("ur ded", 402, 302);
c.fillStyle = "#FFFFFF";
c.fillText("ur ded", 400, 300);
}
}
function draw() {
requestAnimFrame(draw);
// clear
if(player.red > 0 || player.health == 0)
c.fillStyle = "#555555";
else
c.fillStyle = "#000000";
c.fillRect(0, 0, 800, 600);
c.fillStyle = "#FFFFFF";
// background
c.fillStyle = "#555555";
c.font = "50px monospace";
c.textAlign = "center";
c.fillText(stages[stage].name, 400, 100);
// player
c.fillStyle = "#00FF00";
c.beginPath();
c.ellipse(player.pos[0], player.pos[1], player.size/2, player.size/2, 0, 0, 2 * Math.PI);
c.fill();
if(keys.Slow) {
c.fillStyle = "#FF0000";
c.fillRect(player.pos[0], player.pos[1], 1, 1);
}
draw_bullets();
draw_warnings();
draw_ui();
}
</script>
<style>
body {
color: #fff;
background: #000;
}
#canvas {
width: 800px;
height: 600px;
position: absolute;
left: 50%;
top: 50%;
margin: -300px 0 0 -400px;
border: 1px solid #fff;
}
</style>
</head>
<body onload="init()">
<canvas id="canvas" width="800" height="600"></canvas>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment