Last active
April 9, 2019 15:09
-
-
Save hizkifw/2662f1ed029413411124bdde137547b0 to your computer and use it in GitHub Desktop.
Bootleg Touhou game in Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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