soundManager.waitForWindowLoad = true;
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
Array.max = function (array) {
return Math.max.apply(Math, array);
};
Array.min = function (array) {
return Math.min.apply(Math, array);
};
DIFFICULTY = 1;
LEVEL = 0;
CANVAS_HEIGHT = 600;
CANVAS_WIDTH = 800;
ENEMY_FIREPOWER = 5000;
spawn = object;
soundfx = function () {
var bites = {},
adv = "1";
return {
play: function (snd, continuous) {
if (bites[snd]) {
if (continuous) {
bites[snd].play({
onfinish: function () {
this.play();
}
});
} else {
bites[snd].play();
}
}
if (snd === "advance") {
if (adv === "1") {
bites.advance = bites.advance2;
adv = "2";
} else if (adv === "2") {
bites.advance = bites.advance3;
adv = "3";
} else if (adv === "3") {
bites.advance = bites.advance4;
adv = "4";
} else {
bites.advance = bites.advance1;
adv = "1";
}
}
},
stop: function (snd) {
if (bites[snd]) {
bites[snd].stop();
}
},
init: function () {
bites.fire = soundManager.createSound({
id: 'fire',
url: 'sounds/shoot.mp3'
});
bites.advance1 = soundManager.createSound({
id: 'advance1',
url: 'sounds/fastinvader4.mp3'
});
bites.advance2 = soundManager.createSound({
id: 'advance2',
url: 'sounds/fastinvader1.mp3'
});
bites.advance3 = soundManager.createSound({
id: 'advance3',
url: 'sounds/fastinvader2.mp3'
});
bites.advance4 = soundManager.createSound({
id: 'advance4',
url: 'sounds/fastinvader3.mp3'
});
bites.death = soundManager.createSound({
id: 'death',
url: 'sounds/explosion.mp3'
});
bites.hit = soundManager.createSound({
id: 'hit',
url: 'sounds/invaderkilled.mp3'
});
bites.mothership = soundManager.createSound({
id: 'mothership',
url: 'sounds/ufo_highpitch.mp3'
})
bites.advance = bites.advance1;
}
};
}();
si = (function () {
var viewport,
sprites = {
invader1s: [],
invader2s: [],
invader3s: [],
invader4s: [],
motherships: []
},
velocity = 2000/DIFFICULTY,
columns = 11,
lasers = [],
invaders = [],
motherships = [],
silos = [],
bombs = [],
melee = function () {
var fns = [], tt, round, lowest = Infinity, incrs = [], now = new Date().getTime(), lastRound, killed = false, cycle = 0, prevDown = false;
return {
down: false,
track: function (f) {
fns.push(f);
},
listen: function () {
round = function () {
var i, reaction, delta, delay = velocity;
if (killed) {
return;
}
now = new Date().getTime();
delta = (now - lastRound) || 0;
if (delta > velocity) {
delay = Math.max(1, delay - (delta - delay))
}
soundfx.play("advance");
if (!prevDown && (melee.xmin(cycle) <= 0 || melee.xmax(cycle) >= 750)) {
melee.down = true;
prevDown = true;
} else {
melee.down = false;
prevDown = false;
}
for (i = 0; i != fns.length; i++) {
reaction = fns[i](melee.down);
if (reaction.stop) {
window.clearTimeout(tt);
}
if (reaction.clear) {
fns.remove(i);
}
if (reaction.low < lowest) {
lowest = reaction.low;
}
}
if (melee.down) {
melee.increaseSpeed("lower" + lowest);
}
melee.down = false;
if (/(4|2|1)/.test(Math.round(invaders.length/11) + "")) {
melee.increaseSpeed("dwindling" + Math.round(invaders.length/11));
}
if (invaders.length === 1) {
melee.increaseSpeed("oneleft" + lowest);
}
lastRound = now;
tt = window.setTimeout(round, delay);
++cycle;
};
tt = window.setTimeout(round, velocity);
},
stop: function () {
window.clearTimeout(tt);
},
increaseSpeed: function (because) {
if (incrs.indexOf(because) >= 0) {
return;
}
incrs.push(because);
velocity = velocity/1.5;
},
xmax: function (cyc) {
return Array.max(invaders.map(function (inv) { if (inv.cycle() === cyc) { return inv.x*1; } else { return -Infinity; } }));
},
xmin: function (cyc) {
return Array.min(invaders.map(function (inv) { if (inv.cycle() === cyc) { return inv.x*1; } else { return Infinity; } }));
},
ylow: function () {
return CANVAS_HEIGHT - Array.max(invaders.map(function (inv) { return inv.y*1; }));
},
reset: function () {
fns = [];
incrs = [];
cycle = 0;
prevDown = false;
}
};
}(),
win = function () {
LEVEL++;
DIFFICULTY = DIFFICULTY*2;
GM.init(LEVEL);
},
lost = function () {
viewport.image("game-over.png", 0, 0, 800, 600);
si.defender.fire = function () {};
si.defender.left = function () {};
si.defender.right = function () {};
melee.stop();
// alow user to enter high score
scoreboard.record();
},
scoreboard = function () {
var livesLeft = 3,
totalScore = 0,
livesElem,
scoreElem,
hasScored = false,
key = "";
return {
start: function () {
livesElem = document.createElement("div");
livesElem.id = "lives";
scoreElem = document.createElement("div");
scoreElem.id = "score";
T("#bn").ls(0).appendChild(livesElem);
T("#bn").ls(0).appendChild(scoreElem);
livesElem.innerHTML = "lives: " + livesLeft;
scoreElem.innerHTML = "score: " + totalScore;
},
lives: function (a) {
livesLeft += a;
livesElem.innerHTML = "lives: " + livesLeft;
},
points: function (a) {
totalScore += a;
scoreElem.innerHTML = "score: " + totalScore;
},
record: function () {
var sdiv = document.createElement("div"),
sform = document.createElement("form"),
ref = this;
sdiv.id = "enter-score";
// yuk!
T(window).on("keypress", function() {
if (hasScored) {
return;
}
sform.innerHTML = "" +
"
";
hasScored = true;
T(sform).on("submit", function (ev) {
var uname = T("#user-name").ls(0).value;
tanto.ajax.callbackJSON("http://si.boundvariable.com/add?access=" + key + "&name=" + uname + "&score=" + totalScore + "&callback=tanto.ajax.callbackHandler", function (data) {
ref.display(data);
}, true, false, true);
ev.stopPropagation();
ev.preventDefault();
});
sdiv.appendChild(sform);
document.body.appendChild(sdiv);
});
},
display: function (data) {
var decorate = function (d) {
var s = "Top Scores
";
d.ten.forEach(function (item) {
s += "- " + item.name + " -- " + item.score + "
";
});
return s + "
Start again";
};
T("#enter-score").ls(0).style.height = "440px";
T("#enter-score").ls(0).style.padding = "10px";
T("#enter-score").el().html(decorate(data));
},
setKey: function (k) {
key = k;
}
};
}(),
mothership = function () {
var isAlive = false,
boost = 20;
return {
live: function () {
motherships.push(this);
this.x = 0;
this.y = 0;
this.isAlive = true;
this.points = 250;
this.sprite = viewport.image("sprites/mothership.png", this.x, this.y, 45, 30);
this.live = function () {
return this;
};
this.die = function () {
var rt, that = this;
this.sprite.remove();
this.sprite = viewport.image("sprites/invader-explode.png", this.x, this.y, 45, 30);
soundfx.play("hit");
scoreboard.points(this.points);
rt = window.setTimeout( function () {
soundfx.stop("mothership");
that.sprite.remove();
}, 200);
};
return this;
},
invade: function () {
var ref = this,
t = window.setInterval( function () {
if (!ref.isAlive) {
window.clearInterval(t);
return false;
}
ref.x += 3;
ref.sprite.translate(3, 0);
if (ref.x > CANVAS_WIDTH) {
window.clearInterval(t);
soundfx.stop("mothership");
return false;
}
}, velocity/boost);
soundfx.play("mothership", true);
}
};
}(),
invader = function () {
var isAlive = false,
spath = "";
return {
cycle: function () {
return this.cyc || 0;
},
pos: function (xy) {
this.x = xy.x;
this.y = xy.y;
return this;
},
live: function () {
invaders.push(this);
this.isAlive = true;
this.points = this.points || 0;
this.live = function () {
return this;
};
this.cyc = 0;
this.die = function () {
this.isAlive = false;
scoreboard.points(this.points);
soundfx.play("hit");
return viewport.image("sprites/invader-explode.png", this.x, this.y, 45, 30);
};
this.stype = "";
return this;
},
fire: function () {
var gcof = 20,
drop = this.y,
ref = this,
bomb = viewport.image("sprites/bomb.png", this.x, this.y + 20, 7, 15).toFront(),
t = window.setInterval(function () {
drop += 10;
bomb.translate(0, 10);
theBomb.y = theBomb.y + 10;
if (!!bombs.length && theBomb.y > CANVAS_HEIGHT) {
bomb.remove();
bombs.shift();
window.clearInterval(t);
}
}, velocity/gcof),
theBomb = {"sprite": bomb, "x": this.x, "y": this.y};
bombs.push(theBomb);
},
invade: function (i) {
var ii = i,
ref = this,
dir = 1,
down = 0,
rval = "",
alt = "-alt",
invadeFn = function (goDown) {
var firing = Math.floor(Math.random()*1000000);
if (!ref.isAlive) {
return {"dead": true};
}
if (invaders.indexOf(ref) < 0) {
return {"clear": true};
}
if (firing > (1000000 - ENEMY_FIREPOWER)) {
ref.fire();
}
if (dir === 1 && goDown) {
down = 40;
dir = -1;
} else if (dir === -1 && goDown) {
down = 40;
dir = 1;
} else {
down = 0;
ref.x += dir*10;
}
ref.y += down;
rval = {"low": CANVAS_HEIGHT - ref.y};
if (ref.y > CANVAS_HEIGHT - 40) {
rval = {"stop": true};
}
if (!!down) {
sprites[ref.stype][ii].remove();
sprites[ref.stype][ii] = viewport.image("sprites/" + (ref.spath || spath) + alt + ".png", ref.x, ref.y, 45, 30);
} else {
sprites[ref.stype][ii].remove();
sprites[ref.stype][ii] = viewport.image("sprites/" + (ref.spath || spath) + alt + ".png", ref.x, ref.y, 45, 30);
}
if (alt === "-alt") {
alt = "";
} else {
alt ="-alt";
}
ref.sprite = sprites[ref.stype][ii];
++ref.cyc;
return rval;
};
melee.track(invadeFn);
},
setSpriteFileName: function (path) {
this.spath = path;
},
setSpriteType: function (s) {
this.stype = s;
},
setPoints: function (points) {
this.points = points;
}
};
}(),
silo = function () {
var lookup = function (xy) {
var tbl = [[0,0], [0,16], [4,8],
[5,5], [5,6], [5,7], [5,8], [5,9], [5,10], [5,11],
[6,5], [6,6], [6,7], [6,8], [6,9], [6,10], [6,11],
[7,5], [7,6], [7,7], [7,8], [7,9], [7,10], [7,11]];
return tbl.indexOf(xy);
},
findY = function (dir, x) {
var gx = Math.round(x/(67/17)), i;
if (dir === "above") {
for (i = 0; i != 7; i++) {
if (this.shield[gx][i]) {
this.shield[gx][i] = 0;
return i;
}
}
return -1;
} else if (dir === "below") {
for (i = 7; i !== 0; i--) {
if (this.shield[gx][i]) {
this.shield[gx][i] = 0;
return i;
}
}
return -1;
}
return -1;
};
return {
hit: function (dir, loc) {
var fy = findY.apply(this, [dir, loc]),
r;
if (fy >= 0) {
r = viewport.rect(this.x + loc - 2, this.y + fy*9 - 9, 8, 13);
r.attr("fill", "black");
return true;
}
return false;
},
activate: function (offset) {
var i, j;
silos.push(this);
this.shield = [];
this.x = Math.round(CANVAS_WIDTH/4*offset + CANVAS_WIDTH/12);
this.y = CANVAS_HEIGHT - 108;
this.w = 67;
this.h = 58;
this.sprite = viewport.image("sprites/silo.png", this.x, this.y, this.w, this.h);
for (i = 0; i != 17; i++) {
for (j = 0; j != 7; j++) {
if (lookup([i, j]) < 0) {
this.shield[i] = this.shield[i] || [];
this.shield[i][j] = 1;
} else {
this.shield[i] = this.shield[i] || [];
this.shield[i][j] = 0;
}
}
}
return this;
},
box: function () {
return {"x": this.x, "y": this.y, "w": this.w, "h": this.h};
}
};
}();
return {
viewport: function () {
viewport = Raphael(T("#si").ls(0), 800, 600);
scoreboard.start();
},
remote: function (k) {
scoreboard.setKey(k);
},
defender: function () {
var pos = (CANVAS_WIDTH - 80)/9,
lpos = pos,
lives = 3,
laserlock = 0;
sprites.defender = viewport.image("sprites/defender.png", pos, CANVAS_HEIGHT - 28, 45, 30);
si.defender = {
left: function (multiplier) {
lpos = pos;
pos += -1*multiplier;
if (pos <= 0) {
pos = 0;
sprites.defender.translate(-1*lpos, 0);
lpos = 0;
return;
}
sprites.defender.translate(-1*multiplier, 0);
},
right: function (multiplier) {
lpos = pos;
pos += 1*multiplier;
if (pos >= (CANVAS_WIDTH - 44)) {
sprites.defender.translate(CANVAS_WIDTH - lpos - 44, 0);
pos = CANVAS_WIDTH - 44;
return;
}
sprites.defender.translate(1*multiplier, 0);
},
fire: function () {
var laser = viewport.rect(pos + 20, CANVAS_HEIGHT - 40, 4, 9);
soundfx.play("fire");
laser.attr("fill", "#10e60f");
laser.animate({y: 0}, 1000);
lasers.push({"start": new Date(), x: pos + 20, sprite: laser});
},
x: function () {
return pos;
},
kill: function () {
var tt, tpos = pos;
sprites.defender.remove();
sprites.defender = viewport.image("sprites/defender-dead.png", pos, CANVAS_HEIGHT - 28, 45, 30);
soundfx.play("death");
lives = lives - 1;
scoreboard.lives(-1);
if (lives === 0) {
pos = NaN;
lost();
} else {
tpos = pos;
pos = NaN;
tt = window.setTimeout(function () {
pos = (CANVAS_WIDTH - 80)/9;
sprites.defender.remove();
sprites.defender = viewport.image("sprites/defender.png", pos, CANVAS_HEIGHT - 28, 45, 30);
}, 500);
}
}
};
},
invaders: function () {
var thisInvader, i = 0,
sploop = ["invader1s", "invader2s", "invader3s", "invader4s", "invader5s"],
sploopi = ["invader-1", "invader-2", "invader-3", "invader-4", "invader-5"],
spoints = [40, 20, 20, 10, 10],
j;
sprites.invader1s = [];
sprites.invader2s = [];
sprites.invader3s = [];
sprites.invader4s = [];
sprites.invader5s = [];
sprites.motherships = [];
for (j = 0; j != sploop.length; j++) {
for (i = 0; i != columns; i++) {
thisInvader = spawn(invader).live();
thisInvader.pos({x: 0 + (50*i) + 120, y: 0 + 50*j});
thisInvader.setSpriteFileName(sploopi[j]);
thisInvader.setPoints(spoints[j]);
thisInvader.setSpriteType(sploop[j]);
sprites[sploop[j]][i] = viewport.image("sprites/" + sploopi[j] + ".png", 0 + (50*i) + 120, 0 + 50*j, 45, 30);
thisInvader.sprite = sprites[sploop[j]][i];
thisInvader.invade(i);
}
}
melee.listen();
},
collisions: function () {
var i, j, xDiff, yDiff, explosion;
window.setInterval(function () {
var scrub = false;
for (i = 0; i < lasers.length; i++) {
if (!lasers[i]) {
continue;
}
// estimate laser y
lasers[i].y = ((CANVAS_HEIGHT - 40) - (((new Date()) - lasers[i].start)/1000)*(CANVAS_HEIGHT - 40));
if (lasers[i].y < 0) {
lasers[i].sprite.remove();
lasers.remove(i);
break;
}
for(j = 0; j < invaders.length; j++) {
if (typeof invaders[j] === "undefined") {
return;
}
// collision detection
xDiff = lasers[i].x - invaders[j].x;
yDiff = lasers[i].y - invaders[j].y;
if (xDiff > 0 && xDiff < 40) {
if (yDiff > 5 && yDiff < 40) {
lasers[i].sprite.remove();
lasers.remove(i);
invaders[j].sprite.remove();
explosion = invaders[j].die();
window.setTimeout(function () {
explosion.remove();
}, 300);
invaders.remove(j);
if (invaders.length === 0) {
win();
}
break;
}
}
}
for (j = 0; j < silos.length; j++) {
if (lasers.length === 0) {
break;
}
if (lasers[i].y > (silos[j].box().y - silos[j].box().h)) {
if (lasers[i].x > silos[j].box().x && lasers[i].x < silos[j].box().x + silos[j].box().w) {
if (silos[j].hit("below", lasers[i].x - silos[j].box().x)) {
lasers[i].sprite.remove();
lasers.remove(i);
break;
}
}
}
}
for (j = 0; j < motherships.length; j++) {
if (lasers[i].y < 80 && lasers[i].x > motherships[j].x && lasers[i].x < motherships[j].x + 50) {
lasers[i].sprite.remove();
lasers.remove(i);
motherships[j].die();
motherships.remove(j);
break;
}
}
}
for(i = 0; i < bombs.length; i++) {
if (!bombs[i]) {
continue;
}
if (bombs[i] && (bombs[i].y > (CANVAS_HEIGHT - 30)) && (bombs[i].y < CANVAS_HEIGHT)) {
if (bombs[i].x < (si.defender.x() + 60) && bombs[i].x > (si.defender.x())) {
bombs[i].sprite.remove();
si.defender.kill();
break;
}
}
for (j = 0; j != silos.length; j++) {
if (!silos[j]) {
continue;
}
if (bombs[i].y > silos[j].box().y - silos[j].box().h) {
if (bombs[i].x > silos[j].box().x && bombs[i].x < silos[j].box().x + silos[j].box().w) {
if (silos[j].hit("above", bombs[i].x - silos[j].box().x)) {
if (bombs[i]) {
bombs[i].sprite.remove();
bombs.remove(i);
}
break;
}
}
}
}
}
if (melee.ylow() < 40) {
lost();
}
}, 50);
},
motherships: function () {
var i;
window.setTimeout(function () {
window.setInterval(function () {
var theMothership;
if (Math.floor(Math.random()*1000000) > 900000) {
if (motherships.length === 0) {
theMothership = spawn(mothership).live();
theMothership.invade();
}
}
}, 2000);
}, 15000*DIFFICULTY);
},
silos: function () {
var n = 4, i = 0, theSilo;
for (; i != n; i++) {
theSilo = spawn(silo).activate(i);
}
},
nlasers: function () {
return lasers.length;
},
reset: function () {
melee.stop();
melee.reset();
}
};
}());
GM = (function () {
var build = function (level) {
if (level === 0) {
si.viewport();
si.silos();
si.defender();
}
si.reset();
si.invaders();
si.collisions();
si.motherships();
},
gameKey;
tanto.ajax.callbackJSON("http://si.boundvariable.com/key?callback=tanto.ajax.callbackHandler", function (data) {
gameKey = data.access;
si.remote(gameKey);
});
return {
init: function (level) {
var m = null,
mult = 1,
last = null,
since = 0,
prev = 0,
iv = 5,
keylasers = 0,
keylistening = true,
SPACE = 32,
LEFT = 37,
RIGHT = 39,
userMove = function (ev) {
var intent = (ev || {keyCode: last}).keyCode,
now = new Date().getTime(),
delta = now - prev,
delay = iv;
//if (prev && (delta > iv)) {
// console.log(delta);
// delay = delta - iv; //Math.max(iv, delay - (delta - delay));
//}
if (intent === LEFT) {
si.defender.left(4);
last = LEFT;
} else if (intent === RIGHT) {
si.defender.right(4);
last = RIGHT;
} else {
return;
}
m = window.setTimeout(function () {
userMove();
}, delay);
prev = now;
if (keylistening) {
tanto.dom.events.remove(document, "keydown", userMove);
keylistening = false;
}
}, userFire = function (ev) {
var intent = (ev || {keyCode: last}).keyCode;
if (intent === SPACE) {
if (si.nlasers() === 0) {
si.defender.fire();
}
}
};
T(window).on("keyup", function (ev) {
if (ev.keyCode === 37 || ev.keyCode === 39) {
window.clearTimeout(m);
last = null;
m = null;
}
tanto.dom.events.add(document, "keydown", userMove);
keylistening = true;
});
tanto.dom.events.add(document, "keydown", userMove);
tanto.dom.events.add(document, "keydown", userFire);
keylistening = true;
build(level);
}
};
}());
T(window).on("load", function () {
soundManager.onload = function () {
soundfx.init();
};
window.onkeydown = function (ev) {
if (ev.keyCode === 32) {
window.onkeydown = null;
document.getElementById("si").style.background = "#000";
GM.init(0);
}
};
});