Skip to content

Instantly share code, notes, and snippets.

@e0da
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save e0da/6744fcd244d8719df4f4 to your computer and use it in GitHub Desktop.
Save e0da/6744fcd244d8719df4f4 to your computer and use it in GitHub Desktop.
Fireworks!

Fireworks!

You can click and drag then release to fling them, or press F for a fireworks show. Pressing any key will detonate all currently visible fireworks. There's a commented bit that activates the game when the Konami code is entered on the keyboard.

A Pen by Justin Force on CodePen.

License.

(function() {
'use strict';
var sling;
var game;
var BRIGHT_COLORS = ['#f44336', '#ff9800', '#ffeb3b', '#4caf50', '#2196f3', '#9c27b0'];
var FIREWORKS_PROGRAM = [
20, 0, 0, 0, 40, 0, 0, 0, 60, 0, 0, 0, 80, 0, 0, 0,
0, 0, 0, 20, 0, 0, 0, 80, 0, 0, 0, 40, 60, 0, 0,
10, 20, 30, 40, 50, 60, 70, 80, 90,
10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90,
90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10,
0, 0, 0, 0, 0, 0, 50
];
var Util = {
randomBrightColor: function() {
return BRIGHT_COLORS[this.randomInt(0, BRIGHT_COLORS.length)];
},
randomRGBA: function() {
return [
'rgba(', [
this.randomInt(0, 255),
this.randomInt(0, 255),
this.randomInt(0, 255),
Math.random()
].join(','),
')'
].join('');
},
randomInt: function(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
};
var Sound = {
play: function(url) {
new Audio(url).play();
},
launch: function() {
this.play('http://splatoperator.com/assets/swish.mp3');
},
explode: function() {
this.play('http://splatoperator.com/assets/click.mp3');
},
ding: function () {
this.play('http://splatoperator.com/assets/ding.mp3');
}
};
function Game(width, height) {
this.canvas = document.createElement('canvas');
this.canvas.width = width;
this.canvas.height = height;
this.canvas.style.position = 'absolute';
this.canvas.style.zIndex = 2147483647;
this.canvas.style.pointerEvents = 'none';
this.canvas.style.overflow = 'hidden';
this.canvas.style.top = 0;
this.canvas.style.left = 0;
this.renderingContext = this.canvas.getContext('2d');
this.projectiles = [];
this.particles = [];
this.fireworkTimers = [];
this.fireworkShowIsInProgress = false;
document.body.appendChild(this.canvas);
requestAnimationFrame(this.update.bind(this));
Sound.ding();
}
Game.prototype.toggleFireworkShow = function() {
this.fireworkShowIsInProgress = !this.fireworkShowIsInProgress;
if (this.fireworkShowIsInProgress) {
this.startFireworkShow();
} else {
this.stopFireworkShow();
}
};
Game.prototype.startFireworkShow = function() {
var game = this; /* It's hard to hang onto `this` inside of .forEach */
this.fireworkTimers = [];
FIREWORKS_PROGRAM.forEach(function(xPercentage, index) {
if (xPercentage == 0) {
return; /* Zeroes are for skipping this tick */
}
game.fireworkTimers.push(
setTimeout(function() {
var x = (xPercentage / 100) * game.canvas.width;
game.addProjectile(new Projectile({
x: x,
y: game.height(),
forceX: Util.randomInt(-20, 20),
forceY: Util.randomInt(110, 130),
fuse: Util.randomInt(40, 60)
}));
}, 250 * index)
);
});
game.fireworksTimers.push(setTimeout(
game.stopFireworkShow, 250 * (FIREWORKS_PROGRAM.length)
));
};
Game.prototype.stopFireworkShow = function() {
var game = this;
this.fireworkTimers.forEach(function(timer) {
clearInterval(timer);
});
this.fireworkTimers = [];
};
Game.prototype.update = function() {
//this.updateDimensions();
this.updateProjectiles();
this.updateParticles();
this.drawBackground();
this.drawProjectiles();
this.drawParticles();
requestAnimationFrame(this.update.bind(this));
};
Game.prototype.updateDimensions = function() {
this.canvas.width = document.documentElement.clientWidth - 20;
this.canvas.height = document.documentElement.clientHeight - 20;
};
Game.prototype.updateThings = function(collection) {
var game = this;
/* XXX This looks funny because we're going through each collection twice, but
* we need to prune dead things from the collection on a separate pass because
* splicing the collection while we're iterating over it aborts the forEach.
* */
collection.forEach(function(thing) {
if (thing.dead) {
game.removeThing(collection, thing);
}
});
collection.forEach(function(thing) {
thing.update();
});
};
Game.prototype.updateProjectiles = function() {
this.updateThings(this.projectiles);
};
Game.prototype.updateParticles = function() {
this.updateThings(this.particles);
};
Game.prototype.drawBackground = function() {
this.renderingContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
Game.prototype.drawCircles = function(collection) {
var game = this; /* to access this game inside the forEach callback */
collection.forEach(function(item) {
game.renderingContext.beginPath();
game.renderingContext.arc(
item.position.x,
item.position.y,
Math.max(item.radius, 1),
0, (2 * Math.PI),
false
);
game.renderingContext.fillStyle = item.color;
game.renderingContext.fill();
});
};
Game.prototype.drawParticles = function() {
this.drawCircles(this.particles);
};
Game.prototype.drawProjectiles = function() {
this.drawCircles(this.projectiles);
};
Game.prototype.addProjectile = function(projectile) {
this.addThing(this.projectiles, projectile);
};
Game.prototype.removeThing = function(collection, thing) {
collection.splice(collection.indexOf(thing), 1);
};
Game.prototype.width = function() {
return document.documentElement.clientWidth;
};
Game.prototype.height = function() {
return document.documentElement.clientHeight;
};
Game.prototype.addThing = function(collection, thing) {
collection.push(thing);
};
Game.prototype.addParticle = function(particle) {
this.addThing(this.particles, particle);
};
Game.prototype.detonateAllProjectiles = function() {
this.projectiles.forEach(function(projectile) {
projectile.explode();
});
};
function Sling(x, y) {
this.startX = x;
this.startY = y;
document.addEventListener('mouseup', mouseup, true);
}
Sling.prototype.release = function(x, y) {
var deltaX = (x - this.startX);
var deltaY = (y - this.startY);
game.addProjectile(new Projectile({
x: x,
y: y,
forceX: deltaX,
forceY: deltaY
}));
};
function Projectile(params) {
this.position = {
x: params.x,
y: params.y
};
this.force = {
x: params.forceX,
y: params.forceY
};
this.velocity = {
x: -(this.force.x / 5),
y: -(this.force.y / 3)
};
this.fuse = params.fuse;
this.radius = 5;
this.gravity = 1;
this.dead = false;
this.color = '#00aedb';
Sound.launch();
}
Projectile.prototype.outOfBounds = function() {
/* Don't check whether it leaves the top of the viewport
because that's fine. We want it to eventually fall back
into the screen. */
return (
this.position.x < 0 ||
this.position.x > game.canvas.width ||
this.position.y > game.canvas.height
);
};
Projectile.prototype.update = function() {
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
this.velocity.y += this.gravity;
if (this.outOfBounds()) {
this.explode();
}
if (this.fuse !== undefined && this.fuse-- < 0) {
this.explode();
}
};
Projectile.prototype.explode = function() {
Sound.explode();
for (var i = 0; i <= Util.randomInt(50, 100); i++) {
game.addParticle(new ExplosionParticle(this.position.x, this.position.y));
}
this.die();
};
Projectile.prototype.die = function() {
this.dead = true;
};
function ExplosionParticle(x, y) {
this.position = {
x: x,
y: y
};
this.velocity = {
x: Util.randomInt(-10, 10),
y: Util.randomInt(-10, 10)
};
this.radius = Util.randomInt(2, 5);
this.color = Util.randomBrightColor();
this.gravity = 0.5;
this.dead = false;
}
ExplosionParticle.prototype.die = function() {
this.dead = true;
};
ExplosionParticle.prototype.update = function() {
this.position.x = this.position.x < 0 ? 0 : this.position.x;
this.position.y = this.position.y < 0 ? 0 : this.position.y;
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
this.velocity.y += this.gravity;
var shrink = setInterval(function() {
this.radius--;
}.bind(this), 500);
if (this.radius < 1) {
clearInterval(shrink);
this.die();
}
};
function mousedown(event) {
sling = new Sling(event.clientX, event.clientY);
}
function mouseup(event) {
sling.release(event.clientX, event.clientY);
}
function setup() {
game = new Game();
window.addEventListener('resize', game.updateDimensions.bind(game));
game.updateDimensions();
document.addEventListener('keydown', game.detonateAllProjectiles.bind(game), true);
document.addEventListener('mousedown', mousedown, true);
document.addEventListener('keypress', function(event) {
switch (event.keyCode) {
case 102:
game.toggleFireworkShow();
break;
}
});
}
/*
var previousKeys = [];
var keyTimeout;
document.addEventListener('keydown', function(event) {
clearTimeout(keyTimeout);
keyTimeout = setTimeout(function () { previousKeys = []; }, 2000);
previousKeys.push(event.keyCode);
if (previousKeys.toString() == '38,38,40,40,37,39,37,39,66') {
setup();
}
});
*/
setup();
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment