Skip to content

Instantly share code, notes, and snippets.

@bpierre
Created October 15, 2012 11:37
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 bpierre/3892044 to your computer and use it in GitHub Desktop.
Save bpierre/3892044 to your computer and use it in GitHub Desktop.
SUPER #7
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SUPER #7</title>
<style>
html { display: table; width: 100%; height: 100%; background: #000; }
body { display: table-cell; vertical-align:middle; text-align:center; margin: 0; }
canvas { margin: 0; }
h1 { color: #fff; margin: 0 0 10px; font:24px sans-serif; }
</style>
</head>
<body>
<h1>SUPER #7<span id="lvl"></span></h1>
<canvas width="500" height="500" id="c"></canvas>
<script src="http://pierrebertet.net/tmp/7/raf.js"></script>
<script src="main.js"></script>
</body>
</html>
(function(window, document){
'use strict';
var canv = document.getElementById('c'),
ctx = canv.getContext('2d'),
levelElt = document.getElementById('lvl'),
baseUrl = 'http://pierrebertet.net/tmp/7/',
MUTE_SOUND = false,
MAX_FPS = 60,
GAME_SPEED = 20,
INSTRUCTIONS = [
'YOU ARE THE GREEN GUY.',
'THERE IS A GREY BOSS.',
'YOU HAVE TO KILL HIM.',
'BUT HE HAS TO KILL YOU TOO.',
'AVOID ITS BULLETS. KILL HIM FIRST.',
'GOOD LUCK.',
'PRESS [SPACE]'
],
PLAYER_SPEED = 4,
BULLET_MIN_SPEED = 3,
BULLET_MAX_SPEED = 3,
BULLET_RATE = 200,
MONSTER_MIN_SPEED = 1,
MONSTER_MAX_SPEED = 3,
MONSTER_RATE = 1000,
BOSS_SPEED = 1,
LEVELS = [
[BULLET_MIN_SPEED, BULLET_MAX_SPEED, BULLET_RATE, MONSTER_MIN_SPEED, MONSTER_MAX_SPEED, MONSTER_RATE, BOSS_SPEED],
[3, 3, 200, 2, 3, 800, 1],
[3, 3, 300, 2, 3, 600, 1],
[3, 3, 300, 2, 4, 500, 1],
[3, 3, 400, 2, 4, 400, 2],
[3, 3, 500, 3, 5, 300, 2],
[3, 3, 600, 3, 5, 200, 2],
[3, 3, 700, 3, 5, 100, 2],
[3, 3, 800, 3, 5, 100, 2],
[3, 3, 900, 3, 5, 80, 2]
],
KEYS = {
LEFT: 37,
RIGHT: 39,
UP: 38,
DOWN: 40,
SPACE: 32
},
PLAYER_SIZE = 11,
BULLET_SIZE = 5,
BOSS_SIZE = 11,
MONSTER_SIZE = 5,
IMAGES = ['boss', 'player', 'bullet', 'monster'],
SOUNDS = ['bullet', 'monster', 'death', 'level', 'win'],
level = {},
pressedKeys = {
left: false,
right: false,
up: false,
down: false
},
canvWidth = canv.width,
canvHeight = canv.height,
getRandomInt = function(min, max, excludes) {
var result = Math.floor(Math.random() * (max - min + 1)) + min;
if (excludes && excludes.indexOf(result) !== -1) {
return getRandomInt(min, max, excludes);
}
return result;
},
lastRenderTime = null,
game = null;
canv.style.background = '#1D1F23';
document.addEventListener('keydown', function(e){
switch (e.keyCode) {
case KEYS.LEFT:
pressedKeys.left = true;
break;
case KEYS.RIGHT:
pressedKeys.right = true;
break;
case KEYS.UP:
pressedKeys.up = true;
break;
case KEYS.DOWN:
pressedKeys.down = true;
break;
}
}, false);
document.addEventListener('keyup', function(e){
switch (e.keyCode) {
case KEYS.LEFT:
pressedKeys.left = false;
break;
case KEYS.RIGHT:
pressedKeys.right = false;
break;
case KEYS.UP:
pressedKeys.up = false;
break;
case KEYS.DOWN:
pressedKeys.down = false;
break;
}
}, false);
var setLevel = function(levelNum) {
if (LEVELS.length < levelNum + 1) {
return false;
}
levelElt.innerHTML = ' - LEVEL ' + (levelNum + 1);
level.num = levelNum;
level.BULLET_MIN_SPEED = LEVELS[levelNum][0];
level.BULLET_MAX_SPEED = LEVELS[levelNum][1];
level.BULLET_RATE = LEVELS[levelNum][2];
level.MONSTER_MIN_SPEED = LEVELS[levelNum][3];
level.MONSTER_MAX_SPEED = LEVELS[levelNum][4];
level.MONSTER_RATE = LEVELS[levelNum][5];
level.BOSS_SPEED = LEVELS[levelNum][6];
return true;
};
var getImage = (function(){
var loadedImages = {};
for (var i=0; i < IMAGES.length; i++) {
loadedImages[IMAGES[i]] = document.createElement('img');
loadedImages[IMAGES[i]].src = baseUrl + 'img/' + IMAGES[i] + '.png';
}
return function(name){
return loadedImages[name];
};
})();
var playSound = (function(){
var loadedSounds = {};
if (!MUTE_SOUND) {
for (var i=0; i < SOUNDS.length; i++) {
loadedSounds[SOUNDS[i]] = document.createElement('audio');
loadedSounds[SOUNDS[i]].preload = 'auto';
loadedSounds[SOUNDS[i]].controls = false;
loadedSounds[SOUNDS[i]].src = baseUrl + 'sound/' + SOUNDS[i] + '.ogg';
}
}
return function(name){
if (MUTE_SOUND) {
return;
}
if (loadedSounds[name].readyState > 0) {
loadedSounds[name].pause();
loadedSounds[name].currentTime = 0;
}
loadedSounds[name].play();
};
})();
var drawOverlay = function(text, base) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvWidth, canvHeight);
ctx.fillStyle = '#FFF';
ctx.font = "24px sans-serif";
ctx.textAlign = 'center';
for (var i = text.length - 1; i >= 0; i--){
ctx.fillText(text[i], canvWidth/2, canvHeight/2 - 150 + (base || 0) + (50 * i));
}
};
var Entity = function(x, y){
this.x = 0;
this.y = 0;
this.width = 10;
this.height = 10;
this.limits = {
top: 0,
bottom: canvHeight - this.height,
left: 0,
right: canvWidth - this.width
};
if (x) {
this.setX(x);
}
if (y) {
this.setY(y);
}
};
Entity.prototype.setX = function(x) {
if (x < this.limits.left) {
this.x = this.limits.left;
return false;
} else if (x > this.limits.right) {
this.x = this.limits.right;
return false;
} else {
this.x = x;
return true;
}
};
Entity.prototype.setY = function(y) {
if (y < this.limits.top) {
this.y = this.limits.top;
return false;
} else if (y > this.limits.bottom) {
this.y = this.limits.bottom;
return false;
} else {
this.y = y;
return true;
}
};
Entity.prototype.addX = function(x) {
return this.setX(this.x + x);
};
Entity.prototype.addY = function(y) {
return this.setY(this.y + y);
};
Entity.prototype.collide = function(entity) {
if ((this.x + this.width) < entity.x) {
return false;
}
if (this.x > (entity.x + entity.width)) {
return false;
}
if ((this.y + this.height) < entity.y) {
return false;
}
if (this.y > (entity.y + entity.height)) {
return false;
}
return true;
};
var nextLevel = 0;
var startGame = function(gameover, win) {
setLevel(nextLevel);
// Player
var player = (function(){
var entity = new Entity(PLAYER_SIZE, PLAYER_SIZE);
entity.bullets = [];
entity.width = entity.height = PLAYER_SIZE;
entity.popRate = level.BULLET_RATE;
entity.lastPop = null;
entity.popBullet = function(xSpeed, ySpeed){
var bulletPos = [entity.x + Math.round(entity.width/2 - BULLET_SIZE/2), entity.y + Math.round(entity.height/2 - BULLET_SIZE/2)],
bullet = null;
if (xSpeed > 0) {
bulletPos[0] = entity.x + entity.width;
} else if (xSpeed < 0) {
bulletPos[0] = entity.x - BULLET_SIZE;
}
if (ySpeed > 0) {
bulletPos[1] = entity.y + entity.height;
} else if (ySpeed < 0) {
bulletPos[1] = entity.y - BULLET_SIZE;
}
bullet = new Entity(bulletPos[0], bulletPos[1]);
bullet.width = bullet.height = BULLET_SIZE;
bullet.limits.left = -bullet.width;
bullet.limits.right = canvWidth;
bullet.limits.top = -bullet.height;
bullet.limits.bottom = canvHeight;
bullet.xSpeed = xSpeed;
bullet.ySpeed = ySpeed;
bullet.draw = function(){
ctx.drawImage(getImage('bullet'), bullet.x, bullet.y, bullet.width, bullet.height);
};
entity.bullets.push(bullet);
playSound('bullet');
};
entity.draw = function(){
ctx.drawImage(getImage('player'), entity.x, entity.y, entity.width, entity.height);
};
return entity;
})();
// Boss
var boss = (function(){
var initialPosition = (function(){
if (getRandomInt(0, 1)) {
return [getRandomInt(100, canvWidth - BOSS_SIZE), getRandomInt(0, canvHeight - BOSS_SIZE)];
} else {
return [getRandomInt(0, canvWidth - BOSS_SIZE), getRandomInt(100, canvHeight - BOSS_SIZE)];
}
})();
var entity = new Entity(initialPosition[0], initialPosition[1]);
entity.monsters = [];
entity.width = entity.height = BOSS_SIZE;
entity.popRate = level.MONSTER_RATE;
entity.lastPop = null;
entity.target = {
x: entity.x,
y: entity.y
};
entity.popMonster = function(){
var xSpeed = getRandomInt(-level.MONSTER_MAX_SPEED, level.MONSTER_MAX_SPEED, [level.BOSS_SPEED]),
ySpeed = getRandomInt(-level.MONSTER_MAX_SPEED, level.MONSTER_MAX_SPEED, [level.BOSS_SPEED]),
pos = [entity.x + Math.round(entity.width/2 - MONSTER_SIZE/2), entity.y + Math.round(entity.height/2 - MONSTER_SIZE/2)],
monster = null;
// Force movement
if (!xSpeed && !ySpeed) {
if (getRandomInt(0, 1)) {
xSpeed = getRandomInt(-level.MONSTER_MAX_SPEED, level.MONSTER_MAX_SPEED, [0, level.BOSS_SPEED]);
} else {
ySpeed = getRandomInt(-level.MONSTER_MAX_SPEED, level.MONSTER_MAX_SPEED, [0, level.BOSS_SPEED]);
}
}
if (xSpeed > 0) {
pos[0] = entity.x + entity.width + MONSTER_SIZE;
} else if (xSpeed < 0) {
pos[0] = entity.x - BULLET_SIZE;
}
if (ySpeed > 0) {
pos[1] = entity.y + entity.height + MONSTER_SIZE;
} else if (ySpeed < 0) {
pos[1] = entity.y - MONSTER_SIZE;
}
monster = new Entity(pos[0], pos[1]);
monster.width = monster.height = MONSTER_SIZE;
monster.limits.left = -MONSTER_SIZE;
monster.limits.right = canvWidth;
monster.limits.top = -MONSTER_SIZE;
monster.limits.bottom = canvHeight;
monster.xSpeed = xSpeed;
monster.ySpeed = ySpeed;
monster.draw = function() {
ctx.drawImage(getImage('monster'), monster.x, monster.y, monster.width, monster.height);
};
entity.monsters.push(monster);
playSound('monster');
};
entity.draw = function(){
ctx.drawImage(getImage('boss'), entity.x, entity.y, entity.width, entity.height);
};
return entity;
})();
var loopTimer = 0, reqAnimId = 0;
var game = {
loop: function(){
var now = Date.now();
var plSpeed = PLAYER_SPEED;
if ((pressedKeys.left || pressedKeys.right) && (pressedKeys.up || pressedKeys.down)) {
plSpeed = plSpeed / 2;
}
if (pressedKeys.left) {
player.addX(-plSpeed);
} else if (pressedKeys.right) {
player.addX(plSpeed);
}
if (pressedKeys.up) {
player.addY(-plSpeed);
} else if (pressedKeys.down) {
player.addY(plSpeed);
}
// Add bullet
var bulletSpeedX = 0, bulletSpeedY = 0;
if (!player.lastPop || now - player.lastPop > player.popRate) {
if (pressedKeys.left) {
bulletSpeedX = getRandomInt(-level.BULLET_MAX_SPEED, -level.BULLET_MIN_SPEED);
} else if (pressedKeys.right) {
bulletSpeedX = getRandomInt(level.BULLET_MIN_SPEED, level.BULLET_MAX_SPEED);
}
if (pressedKeys.up) {
bulletSpeedY = getRandomInt(-level.BULLET_MAX_SPEED, -level.BULLET_MIN_SPEED);
} else if (pressedKeys.down) {
bulletSpeedY = getRandomInt(level.BULLET_MIN_SPEED, level.BULLET_MAX_SPEED);
}
if (bulletSpeedX || bulletSpeedY) {
player.popBullet(bulletSpeedX, bulletSpeedY);
}
player.lastPop = now;
}
// Move bullets
var bulletsMoveX, bulletsMoveY, bullets = player.bullets;
for (var bi = bullets.length - 1; bi >= 0; bi--) {
if (bullets[bi].xSpeed !== 0 && bullets[bi].ySpeed !== 0) {
bulletsMoveX = bullets[bi].addX(bullets[bi].xSpeed/2);
bulletsMoveY = bullets[bi].addY(bullets[bi].ySpeed/2);
} else {
bulletsMoveX = bullets[bi].addX(bullets[bi].xSpeed);
bulletsMoveY = bullets[bi].addY(bullets[bi].ySpeed);
}
// Bullet / boss collision
if (bullets[bi].collide(boss)) {
return win();
}
if (!bulletsMoveX || !bulletsMoveY) {
// Remove the bullet from the bullets list
bullets.splice(bullets.indexOf(bullets[bi]), 1);
}
}
// Move monsters
var xMove, yMove, monsters = boss.monsters;
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i].xSpeed !== 0 && monsters[i].xSpeed !== 0) {
xMove = monsters[i].addX(monsters[i].xSpeed/2);
yMove = monsters[i].addY(monsters[i].ySpeed/2);
} else {
xMove = monsters[i].addX(monsters[i].xSpeed);
yMove = monsters[i].addY(monsters[i].ySpeed);
}
// Player / monster collision
if (player.collide(monsters[i])) {
return gameover();
}
if (!xMove || !yMove) {
// Remove the monster from the monsters list
monsters.splice(monsters.indexOf(monsters[i]), 1);
}
}
// Add monster
if (!boss.lastPop || now - boss.lastPop > boss.popRate) {
boss.popMonster();
boss.lastPop = now;
}
// Move boss
if (boss.target.x === boss.x || boss.target.y === boss.y) {
boss.target.x = getRandomInt(boss.limits.left, boss.limits.right);
boss.target.y = getRandomInt(boss.limits.top, boss.limits.bottom);
} else {
if (boss.target.x < boss.x) {
boss.addX(-level.BOSS_SPEED);
// Prevent boss hover
if (boss.target.x > boss.x) {
boss.x = boss.target.x;
}
} else if (boss.target.x > boss.x) {
boss.addX(level.BOSS_SPEED);
// Prevent boss hover
if (boss.target.x < boss.x) {
boss.x = boss.target.x;
}
}
if (boss.target.y < boss.y) {
boss.addY(-level.BOSS_SPEED);
// Prevent boss hover
if (boss.target.y > boss.y) {
boss.y = boss.target.y;
}
} else if (boss.target.y > boss.y) {
boss.addY(level.BOSS_SPEED);
// Prevent boss hover
if (boss.target.y < boss.y) {
boss.y = boss.target.y;
}
}
}
// Boss / player collision
if (player.collide(boss)) {
return gameover();
}
loopTimer = window.setTimeout(game.loop, GAME_SPEED);
},
draw: function(last){
var now = Date.now();
// Max fps
if (last !== true && lastRenderTime && now - lastRenderTime < 1000 / MAX_FPS) {
reqAnimId = window.requestAnimationFrame(game.draw);
return;
}
// Save the last rendering time (to lock the max. framerate)
lastRenderTime = now;
// Clear
canv.width = canv.width;
// Bullets
for (var i = player.bullets.length - 1; i >= 0; i--) {
player.bullets[i].draw();
}
// Player
player.draw();
// Monsters
for (var j = boss.monsters.length - 1; j >= 0; j--) {
boss.monsters[j].draw();
}
// Boss
boss.draw();
if (last !== true) {
reqAnimId = window.requestAnimationFrame(game.draw);
}
},
stop: function(){
game.draw(true);
window.clearTimeout(loopTimer);
window.cancelAnimationFrame(reqAnimId);
lastRenderTime = 0;
}
};
return game;
};
var initOverlay = function(text, base) {
drawOverlay(text, base);
document.addEventListener('keypress', initGame, false);
};
var initGame = function(e){
if (e.charCode === KEYS.SPACE) {
document.removeEventListener('keypress', initGame, false);
game = startGame(
// Game over
function(){
playSound('death');
game.stop();
game = null;
nextLevel = 0;
initOverlay(['YOU LOSE AT LEVEL', 'HE WINS.', 'HA HA HA.'], 80);
},
// Win
function(){
game.stop();
game = null;
if (level.num < LEVELS.length-1) {
playSound('level');
nextLevel = level.num + 1;
initOverlay(['YOU WIN!', 'READY FOR THE NEXT LEVEL?', 'PRESS [SPACE]'], 60);
} else {
playSound('win');
nextLevel = 0;
initOverlay(['★★★ THE END ★★★', 'START AGAIN?', 'PRESS [SPACE]'], -40);
}
});
game.loop();
game.draw();
}
};
initOverlay(INSTRUCTIONS);
})(window, document);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment