├── .gitignore
├── fernetjs-invaders.png
├── package.json
├── scripts
├── classes
│ ├── static.js
│ ├── brick.js
│ ├── shieldBrick.js
│ ├── shoot.js
│ ├── drawableElement.js
│ ├── shield.js
│ ├── class.js
│ ├── alien.js
│ ├── imageCreator.js
│ ├── ship.js
│ ├── imageMapper.js
│ ├── invaders.js
│ ├── invasion.js
│ └── input.js
├── gameTime.js
├── camera.js
├── index.js
├── rAFPolyfill.js
├── particles.js
└── input.fakedevice.js
├── Gruntfile.js
├── index.html
├── README.md
├── invaders404.min.js
├── game.min.js
└── game.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/fernetjs-invaders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pjnovas/invaders404/HEAD/fernetjs-invaders.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "invaders",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 |
7 | },
8 | "devDependencies": {
9 | "grunt": "0.4.1",
10 | "grunt-contrib-concat": "~0.1.2",
11 | "grunt-contrib-uglify": "~0.1.1"
12 | }
13 | }
--------------------------------------------------------------------------------
/scripts/classes/static.js:
--------------------------------------------------------------------------------
1 |
2 | function Controls() { throw 'Controls class is Static.'; };
3 | Controls.Left = "Left";
4 | Controls.Right = "Right";
5 | Controls.Shoot = "Shoot";
6 |
7 | function Keyboard() { throw 'KeyboardCode class is Static.'; };
8 | Keyboard.Left = 37;
9 | Keyboard.Right = 39;
10 | Keyboard.Up = 38;
11 | Keyboard.Down = 40;
12 | Keyboard.Space = 32;
13 |
--------------------------------------------------------------------------------
/scripts/classes/brick.js:
--------------------------------------------------------------------------------
1 |
2 | var Brick = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.destroyed = false;
7 | this.value = options.value || 1;
8 | },
9 | build: function(){
10 |
11 | },
12 | update: function(dt){
13 |
14 | },
15 | draw: function(){
16 | if (!this.destroyed){
17 | this.ctx.beginPath();
18 | this.ctx.rect(this.position.x, this.position.y, this.size.width, this.size.height);
19 |
20 | this.ctx.fillStyle = this.color;
21 | this.ctx.fill();
22 | }
23 | },
24 | destroy: function(){
25 | this.destroyed = true;
26 | }
27 | });
28 |
--------------------------------------------------------------------------------
/scripts/gameTime.js:
--------------------------------------------------------------------------------
1 | // Manages the ticks for a Game Loop
2 |
3 | window.gameTime = {
4 | lastTime : Date.now(),
5 | frameTime : 0 ,
6 | typicalFrameTime : 20,
7 | minFrameTime : 12 ,
8 | time : 0
9 | };
10 |
11 | // move the clock one tick.
12 | // return true if new frame, false otherwise.
13 | window.gameTime.tick = function() {
14 | var now = Date.now();
15 | var delta = now - this.lastTime;
16 |
17 | if (delta < this.minFrameTime ) {
18 | return false;
19 | }
20 |
21 | if (delta > 2 * this.typicalFrameTime) { // +1 frame if too much time elapsed
22 | this.frameTime = this.typicalFrameTime;
23 | } else {
24 | this.frameTime = delta;
25 | }
26 |
27 | this.time += this.frameTime;
28 | this.lastTime = now;
29 |
30 | return true;
31 | };
--------------------------------------------------------------------------------
/scripts/camera.js:
--------------------------------------------------------------------------------
1 |
2 | window.camera = (function(){
3 |
4 | var pars = [],
5 | t = 0.1,
6 | currT = 0,
7 | pos = [0, 0];
8 |
9 | function rnd(from, to){
10 | return Math.floor((Math.random()*to)+from);
11 | }
12 |
13 | function rndM(){
14 | return (Math.round(Math.random()) ? 1 : -1);
15 | }
16 |
17 | return {
18 | pos: function(){
19 | return [pos[0], pos[1]];
20 | },
21 | shake: function(powa){
22 | powa = powa || 3;
23 | currT = t;
24 | pos = [ rnd(-powa, powa), rnd(-powa, powa) ];
25 | },
26 | update: function(dt){
27 | dt = dt/1000;
28 | currT -= dt;
29 |
30 | if (currT < 0){
31 | pos = [0, 0];
32 | }
33 | else {
34 | pos[0] *= rndM();
35 | pos[1] *= rndM();
36 | }
37 | }
38 | }
39 |
40 | })();
--------------------------------------------------------------------------------
/scripts/classes/shieldBrick.js:
--------------------------------------------------------------------------------
1 |
2 | var ShieldBrick = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.state = 0;
7 | this.imgsState = options.imgsState;
8 | this.destroyed = false;
9 | },
10 | build: function(){
11 |
12 | },
13 | update: function(){
14 |
15 | },
16 | draw: function(){
17 | if (!this.destroyed){
18 | this._super(this.imgsState[this.state]);
19 | }
20 | },
21 | collided: function(full){
22 | window.camera.shake(1);
23 | window.particles.create([
24 | this.position.x + this.size.width/2,
25 | this.position.y + this.size.height/2
26 | ], 4, this.color);
27 |
28 | if (full) this.state = Math.floor((Math.random()*3)+2);
29 | else this.state++;
30 |
31 | if (this.state > 1){
32 | this.destroyed = true;
33 | }
34 | },
35 | destroy: function(){
36 | this._super();
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/scripts/index.js:
--------------------------------------------------------------------------------
1 | var invaders,
2 | gamepad;
3 |
4 | window.addEventListener("MozGamepadConnected", function(e) {
5 | gamepad = new Input.Device(e.gamepad);
6 | });
7 |
8 | window.addEventListener('load', function(){
9 | initInvaders404();
10 | });
11 |
12 | function play (){
13 | var splash = document.getElementById('splash');
14 | splash.style.display = "none";
15 | splash.style.opacity = 0;
16 |
17 | invaders.start();
18 | }
19 |
20 | function showSplash(){
21 | invaders.drawSplash(function (){
22 | var splash = document.getElementById('splash');
23 | splash.style.display = "block";
24 |
25 | setInterval(function(){
26 | var opa = parseFloat(splash.style.opacity) || 0;
27 | if (opa < 1){
28 | splash.style.opacity = opa + 0.2;
29 | }
30 | }, 200);
31 | });
32 | }
33 |
34 | function initInvaders404(){
35 | invaders = new Invaders404({
36 | canvasId: "game-canvas",
37 | onLoose: function(){
38 | showSplash();
39 | },
40 | onWin: function(){
41 | showSplash();
42 | }
43 | });
44 |
45 | invaders.start();
46 | }
--------------------------------------------------------------------------------
/scripts/rAFPolyfill.js:
--------------------------------------------------------------------------------
1 | //taken from Gist: https://gist.github.com/paulirish/1579671
2 |
3 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
4 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
5 |
6 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
7 |
8 | // MIT license
9 |
10 | ;(function() {
11 | var lastTime = 0;
12 | var vendors = ['ms', 'moz', 'webkit', 'o'];
13 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
14 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
15 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
16 | || window[vendors[x]+'CancelRequestAnimationFrame'];
17 | }
18 |
19 | if (!window.requestAnimationFrame)
20 | window.requestAnimationFrame = function(callback, element) {
21 | var currTime = new Date().getTime();
22 | var timeToCall = Math.max(0, 17 - (currTime - lastTime));
23 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
24 | timeToCall);
25 | lastTime = currTime + timeToCall;
26 | return id;
27 | };
28 |
29 | if (!window.cancelAnimationFrame)
30 | window.cancelAnimationFrame = function(id) {
31 |
32 | window.clearTimeout(id);
33 | };
34 | }());
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function(grunt) {
3 |
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 |
7 | concat: {
8 | game: {
9 | src: [
10 | "scripts/rAFPolyfill.js"
11 | , "scripts/gameTime.js"
12 |
13 | , "scripts/camera.js"
14 | , "scripts/particles.js"
15 |
16 | , "scripts/classes/class.js"
17 | , "scripts/classes/static.js"
18 | , "scripts/classes/imageMapper.js"
19 | , "scripts/classes/imageCreator.js"
20 | , "scripts/classes/drawableElement.js"
21 | , "scripts/classes/shoot.js"
22 | , "scripts/classes/ship.js"
23 | , "scripts/classes/invasion.js"
24 | , "scripts/classes/alien.js"
25 | , "scripts/classes/brick.js"
26 | , "scripts/classes/shieldBrick.js"
27 | , "scripts/classes/shield.js"
28 | , "scripts/classes/invaders.js"
29 |
30 | , "scripts/classes/input.js"
31 | ],
32 | dest: 'game.js'
33 | }
34 | },
35 |
36 | uglify: {
37 | game: {
38 | options: {
39 | stripBanners: {
40 | line: true
41 | },
42 | },
43 | files: {
44 | 'game.min.js': [ 'game.js' ]
45 | }
46 | }
47 | },
48 |
49 | });
50 |
51 | grunt.loadNpmTasks('grunt-contrib-concat');
52 | grunt.loadNpmTasks('grunt-contrib-uglify');
53 |
54 | grunt.registerTask("default", [ "concat", "uglify" ]);
55 |
56 | };
57 |
--------------------------------------------------------------------------------
/scripts/classes/shoot.js:
--------------------------------------------------------------------------------
1 |
2 | var Shoot = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.MOVE_FACTOR = 5;
7 | this.dir = options.dir;
8 |
9 | this.shootImage = options.shootImage;
10 |
11 | this.collateBricks = options.collateBricks;
12 | this.collateAliens = options.collateAliens;
13 |
14 | this.timer = null;
15 | },
16 | build: function(){
17 |
18 | },
19 | update: function(dt){
20 | var dir = this.dir;
21 | var vel = this.MOVE_FACTOR;
22 |
23 | this.position.y += (vel * dir);
24 |
25 | if(this.hasCollision()){
26 | this.collided();
27 | return;
28 | }
29 | },
30 | draw: function(){
31 | this._super(this.shootImage);
32 | },
33 | collided: function(){
34 | this.destroy();
35 | },
36 | destroy: function(){
37 | clearInterval(this.timer);
38 |
39 | this.collateBricks = null;
40 | this.collateAliens = null;
41 |
42 | this.onDestroy(this);
43 |
44 | this._super();
45 | },
46 | hasCollision: function(){
47 | var sX = this.position.x;
48 | var sY = this.position.y;
49 |
50 | if (sY < 0 || sY > 400)
51 | return true;
52 |
53 | function checkCollision(arr){
54 | if (!arr){
55 | return false;
56 | }
57 |
58 | var cb = arr;
59 | var cbLen = cb.length;
60 |
61 | for(var i=0; i< cbLen; i++){
62 | var cbO = cb[i];
63 |
64 | var cbL = cbO.position.x;
65 | var cbT = cbO.position.y;
66 | var cbR = cbL + cbO.size.width;
67 | var cbD = cbT + cbO.size.height;
68 |
69 | if (sX >= cbL && sX <= cbR && sY >= cbT && sY <= cbD && !cbO.destroyed){
70 | arr[i].collided();
71 | return true;
72 | }
73 | }
74 |
75 | return false;
76 | }
77 |
78 | if (checkCollision(this.collateBricks)) return true;
79 | if (this.collateAliens && checkCollision(this.collateAliens)) return true;
80 | }
81 | });
--------------------------------------------------------------------------------
/scripts/classes/drawableElement.js:
--------------------------------------------------------------------------------
1 |
2 | var DrawableElement = Class.extend({
3 | init: function(options){
4 | this.ctx = (options.ctx) ? options.ctx : null; // throw "must provide a Canvas Context";
5 |
6 | this.size = {
7 | width: options.width || 0,
8 | height: options.height || 0
9 | };
10 |
11 | this.position = {
12 | x: options.x || 0,
13 | y: options.y || 0
14 | };
15 |
16 | this.brickSize = options.brickSize || 1;
17 | this.color = options.color || '#000';
18 |
19 | this.bricks = [];
20 |
21 | this.onDestroy = options.onDestroy || function(){};
22 | },
23 | build: function(){
24 |
25 | },
26 | update: function(){
27 |
28 | },
29 | draw: function(img){
30 | if (this.ctx != null)
31 | this.ctx.drawImage(img,
32 | this.position.x + window.camera.pos()[0],
33 | this.position.y + window.camera.pos()[1]);
34 | },
35 | destroy: function(){
36 | this.ctx = null;
37 |
38 | if (this.size != null) {
39 | this.size.width = null;
40 | this.size.height = null;
41 | this.size = null;
42 | }
43 |
44 | if (this.position != null) {
45 | this.position.x = null;
46 | this.position.y = null;
47 | this.position = null;
48 | }
49 |
50 | this.brickSize = null;
51 | this.color = null;
52 |
53 | var bricks = this.bricks;
54 | if (bricks != null) {
55 | var bricksL = bricks.length;
56 | for(var i=0; i< bricksL; i++)
57 | bricks[i] = null;
58 |
59 | this.bricks = null;
60 | }
61 |
62 | //if (this.onDestroy) this.onDestroy(this);
63 | }
64 | });
65 |
66 | /* TEMPLATE for Inheritance
67 |
68 | var DrawableElement = Class.extend({
69 | init: function(options){
70 | this._super(options);
71 |
72 | },
73 | build: function(){
74 |
75 | },
76 | update: function(){
77 |
78 | },
79 | draw: function(){
80 |
81 | },
82 | destroy: function(){
83 |
84 | }
85 | });
86 |
87 | */
88 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | FernetJS Invaders
7 |
8 |
9 |
10 |
11 |
49 |
50 |
51 | FernetJS Invaders 404
52 |
53 |
56 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/scripts/classes/shield.js:
--------------------------------------------------------------------------------
1 |
2 | var Shield = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.imgs = [];
7 | this.build();
8 | },
9 | build: function(){
10 | this.createImagesStateBricks();
11 |
12 | var bSize = this.brickSize;
13 | var x = this.position.x;
14 | var y = this.position.y;
15 | var ctx = this.ctx;
16 | var color = this.color;
17 |
18 | var fernetArr = ImageMapper.Shield();
19 | var fArrLen = fernetArr.length;
20 |
21 | for(var i=0; i< fArrLen; i++){
22 | var fColLen = fernetArr[i].length;
23 |
24 | for(var j=0; j< fColLen; j++){
25 |
26 | if (fernetArr[i][j]){
27 | var b = new ShieldBrick({
28 | ctx: ctx,
29 | x: (j * bSize) + x,
30 | y: (i * bSize) + y,
31 | width: bSize,
32 | height: bSize,
33 | color: color,
34 | imgsState: this.imgs
35 | });
36 |
37 | this.bricks.push(b);
38 | }
39 | }
40 | }
41 | },
42 | update: function(dt){
43 | var b = this.bricks;
44 | var bLen = b.length;
45 |
46 | for(var i=0; i< bLen; i++){
47 | if (b[i]){
48 | b[i].update(dt);
49 | }
50 | }
51 | },
52 | draw: function(){
53 | var b = this.bricks;
54 | if (!b) return;
55 |
56 | var bLen = b.length;
57 |
58 | for(var i=0; i< bLen; i++){
59 | if (b[i]){
60 | b[i].draw();
61 | }
62 | }
63 | },
64 | destroy: function(){
65 | var b = this.bricks;
66 | var bLen = b.length;
67 | for(var i=0; i< bLen; i++){
68 | b[i].destroy();
69 | }
70 | this.bricks = [];
71 |
72 | this._super();
73 | },
74 | createImagesStateBricks: function(){
75 | var opts = {
76 | width: this.brickSize,
77 | height: this.brickSize,
78 | states: [1],
79 | brickSize: 2,
80 | color: this.color
81 | };
82 |
83 | var states = ImageMapper.ShieldBrick();
84 |
85 | for (var i=0; i< states.length; i++){
86 | opts.mapper = states[i];
87 | this.imgs.push(ImageCreator.getImages(opts)[0]);
88 | }
89 | }
90 | });
--------------------------------------------------------------------------------
/scripts/classes/class.js:
--------------------------------------------------------------------------------
1 | /* Simple JavaScript Inheritance
2 | * By John Resig http://ejohn.org/
3 | * MIT Licensed.
4 | */
5 | // Inspired by base2 and Prototype
6 | (function(){
7 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
8 |
9 | // The base Class implementation (does nothing)
10 | this.Class = function(){};
11 |
12 | // Create a new Class that inherits from this class
13 | Class.extend = function(prop) {
14 | var _super = this.prototype;
15 |
16 | // Instantiate a base class (but only create the instance,
17 | // don't run the init constructor)
18 | initializing = true;
19 | var prototype = new this();
20 | initializing = false;
21 |
22 | // Copy the properties over onto the new prototype
23 | for (var name in prop) {
24 | // Check if we're overwriting an existing function
25 | prototype[name] = typeof prop[name] == "function" &&
26 | typeof _super[name] == "function" && fnTest.test(prop[name]) ?
27 | (function(name, fn){
28 | return function() {
29 | var tmp = this._super;
30 |
31 | // Add a new ._super() method that is the same method
32 | // but on the super-class
33 | this._super = _super[name];
34 |
35 | // The method only need to be bound temporarily, so we
36 | // remove it when we're done executing
37 | var ret = fn.apply(this, arguments);
38 | this._super = tmp;
39 |
40 | return ret;
41 | };
42 | })(name, prop[name]) :
43 | prop[name];
44 | }
45 |
46 | // The dummy class constructor
47 | function Class() {
48 | // All construction is actually done in the init method
49 | if ( !initializing && this.init )
50 | this.init.apply(this, arguments);
51 | }
52 |
53 | // Populate our constructed prototype object
54 | Class.prototype = prototype;
55 |
56 | // Enforce the constructor to be what we expect
57 | Class.prototype.constructor = Class;
58 |
59 | // And make this class extendable
60 | Class.extend = arguments.callee;
61 |
62 | return Class;
63 | };
64 | })();
65 |
--------------------------------------------------------------------------------
/scripts/particles.js:
--------------------------------------------------------------------------------
1 |
2 | window.particles = (function(){
3 |
4 | var pars = [],
5 | //gravity = [5, 40],
6 | gravity = [2, 10],
7 | ctx,
8 | size;
9 |
10 | function rnd(from, to){
11 | return Math.floor((Math.random()*to)+from);
12 | }
13 |
14 | function rndM(){
15 | return (Math.round(Math.random()) ? 1 : -1);
16 | }
17 |
18 | function hexToRGB(c){
19 | if (c.indexOf('#') === -1) return c;
20 |
21 | function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}
22 | function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
23 | function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
24 | function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
25 |
26 | return [ hexToR(c), hexToG(c), hexToB(c), 1 ];
27 | }
28 |
29 | return {
30 | init: function(_ctx, _size){
31 | ctx = _ctx;
32 | size = _size;
33 | pars = [];
34 | },
35 | create: function(pos, qty, color){
36 | var c = hexToRGB(color);
37 |
38 | for (var i=0; i < qty; i++){
39 |
40 | var vel = [rnd(10, 30)*rndM(), rnd(10, 30)*-1];
41 |
42 | pars.push({
43 | pos: [
44 | pos[0] + (rnd(1, 3)*rndM()),
45 | pos[1] + (rnd(1, 3)*rndM())
46 | ],
47 | vel: vel,
48 | c: c,
49 | t: 2,
50 | });
51 | }
52 | },
53 | update: function(dt){
54 | dt = dt/500;
55 |
56 | for(var i=0; i < pars.length; i++){
57 | var p = pars[i];
58 |
59 | p.t -= dt;
60 |
61 | p.vel[0] += gravity[0] * dt;
62 | p.vel[1] += gravity[1] * dt;
63 |
64 | p.pos[0] += p.vel[0] * dt;
65 | p.pos[1] += p.vel[1] * dt;
66 |
67 | if (p.pos[1] > size.h || p.t < 0){
68 | pars.splice(i, 1);
69 | }
70 | else {
71 | p.c[3] = p.t.toFixed(2);
72 | }
73 | }
74 | },
75 | draw: function(dt){
76 | for(var i=0; i < pars.length; i++){
77 | var p = pars[i];
78 | ctx.save();
79 | ctx.fillStyle = 'rgba(' + p.c[0] + ',' + p.c[1] + ',' + p.c[2] + ',' + p.c[3] + ')';
80 | ctx.fillRect(p.pos[0], p.pos[1], 3, 3);
81 | ctx.restore();
82 | }
83 | }
84 | }
85 |
86 | })();
--------------------------------------------------------------------------------
/scripts/classes/alien.js:
--------------------------------------------------------------------------------
1 |
2 | var Alien = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.images = options.stateImgs || [];
7 | this.destroyedImg = options.destroyedImg || [];
8 |
9 | this.onWallCollision = options.onWallCollision || [];
10 |
11 | this.shield = options.shield || null;
12 | this.ship = options.ship || null;
13 |
14 | this.destroyed = false;
15 | this.shoots = [];
16 | },
17 | build: function(){
18 |
19 | },
20 | update: function(){
21 | this.hasCollision();
22 |
23 | var sX = this.position.x;
24 | if (sX < 20 || sX > (590 - this.size.width))
25 | this.onWallCollision();
26 |
27 | var sY = this.position.y + this.size.height;
28 | if (sY < 0) this.ship.collided();
29 | },
30 | draw: function(state){
31 | if (!this.destroyed){
32 | var idx = (state) ? 0: 1;
33 | this._super(this.images[idx]);
34 | }
35 | else {
36 | this._super(this.destroyedImg[0]);
37 | this.destroy();
38 | this.onDestroy(this);
39 | }
40 | },
41 | hasCollision: function(){
42 | var sX = this.position.x + this.size.width/2;
43 | var sY = this.position.y + this.size.height*0.8;
44 |
45 | function checkCollision(arr){
46 | if (!arr){
47 | return false;
48 | }
49 |
50 | var cb = arr;
51 | var cbLen = cb.length;
52 |
53 | for(var i=0; i< cbLen; i++){
54 | var cbO = cb[i];
55 |
56 | var cbL = cbO.position.x;
57 | var cbT = cbO.position.y;
58 | var cbR = cbL + cbO.size.width;
59 | var cbD = cbT + cbO.size.height;
60 |
61 | if (sX >= cbL && sX <= cbR && sY >= cbT && sY <= cbD && !cbO.destroyed){
62 | arr[i].collided(true);
63 | return true;
64 | }
65 | }
66 |
67 | return false;
68 | }
69 |
70 | if (checkCollision(this.shield.bricks)) return true;
71 | if (checkCollision([this.ship])) return true;
72 | },
73 | collided: function(){
74 | this.destroyed = true;
75 |
76 | window.camera.shake(3);
77 |
78 | window.particles.create([
79 | this.position.x + this.size.width/2,
80 | this.position.y + this.size.height/2
81 | ], 10, this.color);
82 |
83 | },
84 | destroy: function(){
85 | this._super();
86 | }
87 | });
88 |
--------------------------------------------------------------------------------
/scripts/classes/imageCreator.js:
--------------------------------------------------------------------------------
1 |
2 | function ImageCreator(){ throw 'ImageCreator class is Static.'; };
3 |
4 | ImageCreator.getImages = function(options){
5 |
6 | var images = [];
7 | var bricks = [];
8 |
9 | // B - Get parameters ---------------------------------
10 |
11 | var mapper = options.mapper || [];
12 | var w = options.width || 100;
13 | var h = options.height || 100;
14 |
15 | var states = options.states || [];
16 | var bSize = options.brickSize || 5;
17 |
18 | var color = options.color || '#000';
19 |
20 | // E - Get parameters ---------------------------------
21 |
22 |
23 | // B - Create CANVAS to render ------------------------
24 |
25 | var canvas = document.createElement('canvas');
26 | canvas.width = w;
27 | canvas.height = h;
28 | var ctx = canvas.getContext('2d');
29 | //TODO: delete element
30 |
31 | // E - Create CANVAS to render ------------------------
32 |
33 |
34 | // B - Create image from mapper -----------------------
35 |
36 | function buildBricks(){
37 | var arrLen = mapper.length;
38 |
39 | for(var i=0; i< arrLen; i++){
40 | var colLen = mapper[i].length;
41 |
42 | for(var j=0; j< colLen; j++){
43 | var val = mapper[i][j];
44 |
45 | if (val){
46 | var b = new Brick({
47 | ctx: ctx,
48 | x: (j * bSize),
49 | y: (i * bSize),
50 | width: bSize,
51 | height: bSize,
52 | color: color,
53 | value: val
54 | });
55 |
56 | bricks.push(b);
57 | }
58 | }
59 | }
60 | }
61 |
62 | // E - Create image from mapper -----------------------
63 |
64 |
65 | // B - Draw on canvas context and get image -----------
66 |
67 | function createImage(state){
68 | ctx.clearRect(0, 0, w, h);
69 |
70 | var bLen = bricks.length;
71 | for(var i=0; i< bLen; i++){
72 | if (bricks[i].value === 1 || bricks[i].value === state)
73 | bricks[i].draw();
74 | }
75 |
76 | var imgData = canvas.toDataURL("image/png");
77 |
78 | var image = new Image();
79 | image.src = imgData;
80 |
81 | images.push(image);
82 | }
83 |
84 | // E - Draw on canvas context and get image -----------
85 |
86 |
87 | //Run the build
88 | buildBricks();
89 |
90 | //Create all images for each state
91 | for(var i=0; i< states.length; i++){
92 | createImage(states[i]);
93 | }
94 |
95 | // destroy all bricks created
96 | var i = bricks.length - 1;
97 | do{ bricks[i] = null; } while(i--);
98 |
99 | return images;
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Invaders 404
2 | A code for fun, yet another custom HTML5 CANVAS 404 error page with the classic game Space Invaders made in JavaScript.
3 |
4 | ##How to Use
5 | Its easy, just instanciate the class and suscribe to the events:
6 |
7 | ```javascript
8 | var invaders = new Invaders404({
9 | canvasId: 'game-canvas',
10 | onLoose: function(){
11 | alert('You Loose!');
12 | },
13 | onWin: function(){
14 | alert('You Win!');
15 | }
16 | });
17 |
18 | invaders.start();
19 | ```
20 |
21 | - Browser compatibility: any browser which supports HTML5
22 |
23 | ##About the code
24 | Prototypal OOP-like code in JavaScript - thanks to John Resig (http://ejohn.org/) for the utility to make inheritance simplier: classes/class.js.
25 |
26 | It has a prove of concept to use no images at all. They are all generated on the fly from a JSON array map of numbers; drawing them first to a canvas and then using canvas.toDataURL("image/png") for better performance (classes/ImageCreator.js).
27 |
28 | ```js
29 | // The Alien Crab JSON array
30 | // 0 = transparent | 1 = static | 2 & 3 = animation states
31 | ImageMapper.AlienCrab = function(){
32 | return [
33 | [0,0,1,0,0,0,0,0,1,0,0],
34 | [3,0,0,1,0,0,0,1,0,0,3],
35 | [3,0,0,1,0,0,0,1,0,0,3],
36 | [3,0,1,1,1,1,1,1,1,0,3],
37 | [3,0,1,0,1,1,1,0,1,0,3],
38 | [3,1,1,1,1,1,1,1,1,1,3],
39 | [2,1,1,1,1,1,1,1,1,1,2],
40 | [2,0,1,1,1,1,1,1,1,0,2],
41 | [2,0,1,1,1,1,1,1,1,0,2],
42 | [2,0,1,0,0,0,0,0,1,0,2],
43 | [2,0,1,0,0,0,0,0,1,0,2],
44 | [0,3,0,2,2,0,2,2,0,3,0]
45 | ];
46 | };
47 | ```
48 |
49 | It has also some JSON arrays to configure the Aliens Invasion and the Shield disposition...
50 |
51 | ```js
52 | // The Aliens Invasion JSON array making the "404"
53 | // 1 = Alien Squid | 2 = Alien Crab
54 | ImageMapper.Invasion = function(){
55 | return [
56 | [2,2,2,2,2,2,2,2,2,2,2,2,2],
57 | [2,2,2,1,2,1,1,1,2,2,2,1,2],
58 | [2,2,1,1,2,1,2,1,2,2,1,1,2],
59 | [2,1,2,1,2,1,2,1,2,1,2,1,2],
60 | [2,1,1,1,2,1,2,1,2,1,1,1,2],
61 | [2,2,2,1,2,1,1,1,2,2,2,1,2],
62 | [2,2,2,2,2,2,2,2,2,2,2,2,2]
63 | ];
64 | };
65 | ```
66 |
67 | ```js
68 | // The Shield JSON array making the "NOT FOUND"
69 | // 1 = Shield brick
70 | ImageMapper.Shield = function(){
71 | return [
72 | [1,0,0,1,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,0],
73 | [1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1,0,1],
74 | [1,1,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,1,0,1,0,1],
75 | [1,0,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1],
76 | [1,0,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,1,1,0]
77 | ];
78 | };
79 | ```
80 |
81 | ... so try it out, Fork me and have fun!
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/scripts/input.fakedevice.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var keyCodeBlob = {
3 | 'Firefox' : {
4 | 'Mac' : [
5 | 72,
6 | 74,
7 | 75,
8 | 76,
9 | 87,
10 | 83,
11 | 65,
12 | 68
13 | ]
14 | }
15 | };
16 |
17 | var osList = ['Win', 'Mac', 'Linux'];
18 | function detectOS() {
19 | for (var i in osList) {
20 | if (navigator.platform.indexOf(osList[i]) !== -1) {
21 | return osList[i];
22 | }
23 | }
24 | return 'Unknown';
25 | }
26 |
27 | var browserList = ['Firefox', 'Chrome', 'Safari', 'Internet Explorer', 'Opera'];
28 | function detectBrowser() {
29 | for (var i in browserList) {
30 | if (navigator.userAgent.indexOf(browserList[i]) !== -1) {
31 | return browserList[i];
32 | }
33 | }
34 | return 'Unknown';
35 | }
36 |
37 | function fakeButton(buttons, index, keyPressed) {
38 | Object.defineProperty(buttons, index, {
39 | enumerable: true,
40 | get: function() { return keyPressed[index]; }
41 | });
42 | }
43 |
44 | var FakeDevice = Input.FakeDevice = function() {
45 | var browser = detectBrowser(),
46 | os = detectOS(),
47 | keyboard = keyCodeBlob,
48 | keyPressed = [0, 0, 0, 0, 0, 0, 0, 0];
49 | axes = this.axes = {},
50 | buttons = this.buttons = {};
51 |
52 | if (keyboard && keyboard[browser] && keyboard[browser][os]) {
53 | keyboard = keyboard[browser][os];
54 | } else {
55 | throw "Oops, I didn't add fake gamepad support for " + browser + " on " + os;
56 | }
57 |
58 | window.addEventListener("keydown", function(e) {
59 | for (var i in keyboard) {
60 | if (e.keyCode === keyboard[i]) {
61 | keyPressed[i] = 1;
62 | return;
63 | }
64 | }
65 | }, false);
66 |
67 | window.addEventListener("keyup", function(e) {
68 | for (var i in keyboard) {
69 | if (e.keyCode === keyboard[i]) {
70 | keyPressed[i] = 0;
71 | return;
72 | }
73 | }
74 | }, false);
75 |
76 | window.addEventListener("blur", function(e) {
77 | for (var i in keyPressed) {
78 | keyPressed[i] = 0;
79 | }
80 | }, false);
81 |
82 | for (var b in keyboard) {
83 | fakeButton(buttons, b, keyPressed);
84 | }
85 |
86 | Object.defineProperty(this, "connected", {
87 | enumerable: true,
88 | get: function() { return true; }
89 | });
90 |
91 | Object.defineProperty(this, "id", {
92 | enumerable: true,
93 | get: function() { return "Firefox-Fake Gamepad-Bored on a plane industries"; }
94 | });
95 |
96 | Object.defineProperty(this, "index", {
97 | enumerable: true,
98 | get: function() { return -1; }
99 | });
100 | }
101 | }());
102 |
--------------------------------------------------------------------------------
/scripts/classes/ship.js:
--------------------------------------------------------------------------------
1 |
2 | var Ship = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.maxMove = {
7 | left: options.maxMoveLeft,
8 | right: options.maxMoveRight,
9 | };
10 |
11 | this.onShipHit = options.onShipHit || function(){};
12 |
13 | this.MOVE_FACTOR = 0.2;
14 | this.SHOOT_TIME = 200;
15 |
16 | this.brickSize = 2;
17 | this.shootImage = null;
18 | this.shoots = [];
19 | this.lastShoot = 0;
20 |
21 | this.imgs = [];
22 |
23 | var map = ImageMapper.Ship();
24 |
25 | this.size = {
26 | width: this.brickSize * map[0].length,
27 | height: this.brickSize * map.length
28 | };
29 |
30 | this.build();
31 |
32 | this.shield = options.shield;
33 | this.invasion = {};
34 | },
35 | build: function(){
36 | this.buildShootImage();
37 |
38 | var opts = {
39 | width: this.size.width,
40 | height: this.size.height,
41 | states: [1],
42 | brickSize: this.brickSize,
43 | mapper: ImageMapper.Ship(),
44 | color: this.color
45 | };
46 |
47 | this.imgs = ImageCreator.getImages(opts);
48 | },
49 | update: function(actions, dt){
50 | var vel = this.MOVE_FACTOR;
51 |
52 | if (actions.indexOf(Controls.Left)>-1){
53 | if (this.position.x > this.maxMove.left){
54 | this.position.x -= vel * dt;
55 | }
56 | }
57 | else if (actions.indexOf(Controls.Right)>-1) {
58 | if (this.position.x < (this.maxMove.right - this.size.width)){
59 | this.position.x += vel * dt;
60 | }
61 | }
62 |
63 | this.lastShoot -= dt;
64 | var shootIdx = actions.indexOf(Controls.Shoot);
65 | if (shootIdx>-1 && this.lastShoot <= 0){
66 | this.lastShoot = this.SHOOT_TIME;
67 | actions.splice(shootIdx, 1);
68 | this.makeShoot();
69 | }
70 |
71 | var s = this.shoots;
72 | var sLen = s.length;
73 | for(var i=0; i< sLen; i++){
74 | if (s[i]){
75 | s[i].update(dt);
76 | }
77 | }
78 | },
79 | draw: function(){
80 | this._super(this.imgs[0]);
81 |
82 | var s = this.shoots;
83 | var sLen = s.length;
84 | for(var i=0; i< sLen; i++){
85 | if (s[i]){
86 | s[i].draw();
87 | }
88 | }
89 | },
90 | collided: function(){
91 | this.onShipHit();
92 | },
93 | destroy: function(){
94 | this.onShipHit = null;
95 |
96 | this.shootImage = null;
97 |
98 | for(var i=0; i< this.shoots.length; i++){
99 | this.shoots[i].destroy();
100 | }
101 | this.shoots = [];
102 |
103 | this.imgs = [];
104 |
105 | this.shield = null;
106 | this.invasion = null;
107 |
108 | this._super();
109 | },
110 | makeShoot: function(){
111 | var self = this;
112 |
113 | var s = new Shoot({
114 | ctx: this.ctx,
115 | x: this.position.x + (this.size.width /2),
116 | y: this.position.y,
117 | dir: -1,
118 | shootImage: this.shootImage,
119 | onDestroy: function(s){
120 | for(var i=0; i -1) {
144 | var dir = getAction(key);
145 |
146 | if(self.currentDir.indexOf(dir) === -1)
147 | self.currentDir.push(dir);
148 |
149 | event.stopPropagation();
150 | event.preventDefault();
151 | return false;
152 | }
153 | }
154 | });
155 |
156 | document.addEventListener('keyup', function(event) {
157 | if(self.isOnGame) {
158 | var key = event.keyCode;
159 |
160 | var dir = getAction(key);
161 | var pos = self.currentDir.indexOf(dir);
162 | if(pos > -1)
163 | self.currentDir.splice(pos, 1);
164 | }
165 | });
166 | },
167 | unbindControls : function(params) {
168 | document.removeEventListener('keydown', function() {});
169 | document.removeEventListener('keyup', function() {});
170 | },
171 | destroy : function() {
172 | this.shield.destroy();
173 | this.invasion.destroy();
174 | this.ship.destroy();
175 | },
176 | stop : function() {
177 | //this.unbindControls();
178 | this.isOnGame = false;
179 |
180 | for(var i = 0; i < this.currentDir.length; i++)
181 | this.currentDir[i] = null;
182 |
183 | this.currentDir = [];
184 |
185 | this.destroy();
186 | },
187 | drawSplash : function(callback) {
188 | var ctx = this.ctx,
189 | cols = this.canvas.height,
190 | colsL = this.canvas.width,
191 | colIdx = 0;
192 |
193 | function drawColumn(idx, color){
194 | var size = 20;
195 | var x = (idx*size)-size;
196 |
197 | ctx.save();
198 | ctx.fillStyle = color;
199 | ctx.fillRect(x, 0, size, cols);
200 | ctx.restore();
201 | }
202 |
203 | var loopInterval = this.loopInterval*2;
204 |
205 | function doForward(){
206 | for(var i=0; i<5; i++){
207 | drawColumn(colIdx+i, "rgba(240,219,79," + (i ? i/10 : 1) + ")");
208 | }
209 |
210 | colIdx++;
211 |
212 | if(colIdx < colsL/10)
213 | setTimeout(doForward, loopInterval);
214 | else {
215 | callback();
216 | //colIdx = colsL/10;
217 | //doBack();
218 | }
219 | }
220 |
221 | function doBack(){
222 | for(var i=5; i>=0; i--){
223 | drawColumn(colIdx-i, "rgba(0,0,0," + (i ? i/10 : 1) + ")");
224 | }
225 |
226 | colIdx--;
227 |
228 | if(colIdx > 0)
229 | setTimeout(doBack, loopInterval);
230 | else {
231 | callback();
232 | }
233 | }
234 |
235 | doForward();
236 | }
237 | });
--------------------------------------------------------------------------------
/scripts/classes/invasion.js:
--------------------------------------------------------------------------------
1 |
2 | var Invasion = DrawableElement.extend({
3 | init: function(options){
4 | this._super(options);
5 |
6 | this.colors = {
7 | crab: '#FF2727',
8 | squid: '#F8FF41'
9 | };
10 |
11 | this.size = {
12 | width: 390,
13 | height: 210
14 | };
15 |
16 | this.shield = options.shield;
17 | this.ship = options.ship;
18 |
19 | this.MOVE_FACTOR = 10;
20 | this.DOWN_FACTOR = 12;
21 | this.CURR_VEL = 600;
22 | this.VEL_FACTOR = 50;
23 |
24 | this.MOVE_TIME = 500;
25 | this.lastMove = 0;
26 |
27 | this.dir = 1;
28 | this.lastDir = 1;
29 | this.lastPer = 100;
30 |
31 | this.state = 0;
32 |
33 | this.alienSize = 30;
34 | this.aliens = [];
35 |
36 | this.crabImages = [];
37 | this.squidImages = [];
38 | this.deadAlienImgs = [];
39 |
40 | this.shootImage = null;
41 | this.shoots = [];
42 |
43 | this.build();
44 |
45 | this.aliensAmm = this.aliens.length;
46 | this.hadAlienCollision = false;
47 |
48 | this.onAliensClean = options.onAliensClean || function(){};
49 |
50 | this.timer = null;
51 | //this.update();
52 | },
53 | build: function(){
54 | var self = this;
55 | this.buildShootImage();
56 | this.buildAliensImages();
57 |
58 | var aSize = this.alienSize;
59 | var x = this.position.x;
60 | var y = this.position.y;
61 | var ctx = this.ctx;
62 |
63 | var aliensArr = ImageMapper.Invasion();
64 | var aArrLen = aliensArr.length;
65 |
66 | for(var i=0; i< aArrLen; i++){
67 | var aColLen = aliensArr[i].length;
68 |
69 | for(var j=0; j< aColLen; j++){
70 |
71 | if (aliensArr[i][j]){
72 | var alien;
73 | var opts = {
74 | ctx: ctx,
75 | x: (j * aSize) + x,
76 | y: (i * aSize) + y,
77 | width: aSize,
78 | height: aSize,
79 | destroyedImg: this.deadAlienImgs,
80 | shield: this.shield,
81 | ship: this.ship,
82 | onDestroy: function(alien){
83 | for(var i=0; i -1)
154 | this.makeShoot(arr[i]);
155 | }
156 |
157 | if (this.vMove > 0) this.vMove = 0;
158 |
159 | var cPer = (arrLen * 100) / this.aliensAmm;
160 | if((this.lastPer - cPer) > 9){
161 | this.CURR_VEL -= this.VEL_FACTOR;
162 | this.MOVE_TIME -= this.VEL_FACTOR;
163 | if (this.MOVE_TIME < 200){
164 | this.MOVE_TIME = 200;
165 | }
166 | this.lastPer = cPer;
167 | return;
168 | }
169 | },
170 | update: function(dt){
171 | this.lastMove -= dt;
172 |
173 | if (this.lastMove <= 0){
174 | this.loop();
175 | this.lastMove = this.MOVE_TIME;
176 |
177 | var state = this.state;
178 |
179 | var arr = this.aliens;
180 | var arrLen = arr.length;
181 | for(var i=0; i< arrLen; i++){
182 | if (arr[i] !== undefined)
183 | arr[i].update(dt);
184 | }
185 | }
186 |
187 | var shoots = this.shoots;
188 | var shootsLen = shoots.length;
189 | for(var i=0; i< shootsLen; i++){
190 | if (shoots[i]){
191 | shoots[i].update(dt);
192 | }
193 | }
194 | },
195 | draw: function(){
196 | var state = this.state;
197 |
198 | var arr = this.aliens;
199 | var arrLen = arr.length;
200 | for(var i=0; i< arrLen; i++){
201 | if (arr[i] !== undefined)
202 | arr[i].draw(state);
203 | }
204 |
205 | var shoots = this.shoots;
206 | var shootsLen = shoots.length;
207 | for(var i=0; i< shootsLen; i++){
208 | shoots[i].draw();
209 | }
210 | },
211 | destroy: function(){
212 | clearInterval(this.timer);
213 |
214 | this.shield = null;
215 | this.ship = null;
216 |
217 | for(var i=0; i< this.shoots.length; i++){
218 | this.shoots[i].destroy();
219 | }
220 | this.shoots = [];
221 |
222 | this._super();
223 | },
224 | makeShoot: function(alien){
225 | var shield = this.shield;
226 | var ship = this.ship;
227 |
228 | var self = this;
229 |
230 | var s = new Shoot({
231 | ctx: this.ctx,
232 | x: alien.position.x + (alien.size.width /2),
233 | y: alien.position.y,
234 | dir: 1,
235 | shootImage: this.shootImage,
236 | onDestroy: function(s){
237 | for(var i=0; i400)
16 | return true;function checkCollision(arr){var cb=arr;var cbLen=cb.length;for(var i=0;i=cbL&&sX<=cbR&&sY>=cbT&&sY<=cbD&&!cbO.destroyed){arr[i].collided();return true;}}
17 | return false;}
18 | if(checkCollision(this.collateBricks))return true;if(this.collateAliens&&checkCollision(this.collateAliens))return true;}});var Ship=DrawableElement.extend({init:function(options){this._super(options);this.maxMove={left:options.maxMoveLeft,right:options.maxMoveRight,};this.onShipHit=options.onShipHit||function(){};this.MOVE_FACTOR=5;this.brickSize=2;this.shootImage=null;this.shoots=[];this.imgs=[];var map=ImageMapper.Ship();this.size={width:this.brickSize*map[0].length,height:this.brickSize*map.length};this.build();this.shield=options.shield;this.invasion={};},build:function(){this.buildShootImage();var opts={width:this.size.width,height:this.size.height,states:[1],brickSize:this.brickSize,mapper:ImageMapper.Ship(),color:this.color};this.imgs=ImageCreator.getImages(opts);},update:function(actions){var vel=this.MOVE_FACTOR;if(actions.indexOf(Controls.Left)>-1){if(this.position.x>this.maxMove.left){this.position.x-=vel;}}
19 | else if(actions.indexOf(Controls.Right)>-1){if(this.position.x<(this.maxMove.right-this.size.width)){this.position.x+=vel;}}
20 | var shootIdx=actions.indexOf(Controls.Shoot);if(shootIdx>-1&&this.shoots.length===0){actions.splice(shootIdx,1);this.makeShoot();}},draw:function(){this._super(this.imgs[0]);var s=this.shoots;var sLen=s.length;for(var i=0;i0)this.vMove=0;var cPer=(arrLen*100)/this.aliensAmm;if((this.lastPer-cPer)>9){this.CURR_VEL-=this.VEL_FACTOR;this.lastPer=cPer;this.update();return;}},update:function(){clearInterval(this.timer);var self=this;this.timer=setInterval(function(){self.loop();},this.CURR_VEL);},draw:function(){var state=this.state;var arr=this.aliens;var arrLen=arr.length;for(var i=0;i(590-this.size.width))
31 | this.onWallCollision();var sY=this.position.y+this.size.height;if(sY<0)this.ship.collided();},draw:function(state){if(!this.destroyed){var idx=(state)?0:1;this._super(this.images[idx]);}
32 | else{this._super(this.destroyedImg[0]);this.destroy();this.onDestroy(this);}},hasCollision:function(){var sX=this.position.x+this.size.width/2;var sY=this.position.y+this.size.height*0.8;function checkCollision(arr){var cb=arr;var cbLen=cb.length;for(var i=0;i=cbL&&sX<=cbR&&sY>=cbT&&sY<=cbD&&!cbO.destroyed){arr[i].collided(true);return true;}}
33 | return false;}
34 | if(checkCollision(this.shield.bricks))return true;if(checkCollision([this.ship]))return true;},collided:function(){this.destroyed=true;},destroy:function(){this._super();}});var Brick=DrawableElement.extend({init:function(options){this._super(options);this.destroyed=false;this.value=options.value||1;},build:function(){},update:function(){},draw:function(){if(!this.destroyed){this.ctx.beginPath();this.ctx.rect(this.position.x,this.position.y,this.size.width,this.size.height);this.ctx.fillStyle=this.color;this.ctx.fill();}},destroy:function(){this.destroyed=true;}});var ShieldBrick=DrawableElement.extend({init:function(options){this._super(options);this.state=0;this.imgsState=options.imgsState;this.destroyed=false;},build:function(){},update:function(){},draw:function(){if(!this.destroyed){this._super(this.imgsState[this.state]);}},collided:function(full){if(full)this.state=3;else this.state++;if(this.state>2){this.destroyed=true;}},destroy:function(){this._super();}});var Shield=DrawableElement.extend({init:function(options){this._super(options);this.imgs=[];this.build();},build:function(){this.createImagesStateBricks();var bSize=this.brickSize;var x=this.position.x;var y=this.position.y;var ctx=this.ctx;var color=this.color;var fernetArr=ImageMapper.Shield();var fArrLen=fernetArr.length;for(var i=0;i-1){var dir=getAction(key);if(self.currentDir.indexOf(dir)===-1)
39 | self.currentDir.push(dir);event.stopPropagation();event.preventDefault();return false;}}});document.addEventListener('keyup',function(event){if(self.isOnGame){var key=event.keyCode;var dir=getAction(key);var pos=self.currentDir.indexOf(dir);if(pos>-1)
40 | self.currentDir.splice(pos,1);}});},unbindControls:function(params){document.removeEventListener('keydown',function(){});document.removeEventListener('keyup',function(){});},destroy:function(){this.shield.destroy();this.invasion.destroy();this.ship.destroy();},stop:function(){this.isOnGame=false;for(var i=0;ie&&!window.requestAnimationFrame;++e)window.requestAnimationFrame=window[i[e]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[i[e]+"CancelAnimationFrame"]||window[i[e]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(i){var e=(new Date).getTime(),s=Math.max(0,17-(e-t)),n=window.setTimeout(function(){i(e+s)},s);return t=e+s,n}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(t){window.clearTimeout(t)})})(),window.gameTime={lastTime:Date.now(),frameTime:0,typicalFrameTime:20,minFrameTime:12,time:0},window.gameTime.tick=function(){var t=Date.now(),i=t-this.lastTime;return this.minFrameTime>i?!1:(this.frameTime=i>2*this.typicalFrameTime?this.typicalFrameTime:i,this.time+=this.frameTime,this.lastTime=t,!0)},window.camera=function(){function t(t,i){return Math.floor(Math.random()*i+t)}function i(){return Math.round(Math.random())?1:-1}var e=.1,s=0,n=[0,0];return{pos:function(){return[n[0],n[1]]},shake:function(i){i=i||3,s=e,n=[t(-i,i),t(-i,i)]},update:function(t){t/=1e3,s-=t,0>s?n=[0,0]:(n[0]*=i(),n[1]*=i())}}}(),window.particles=function(){function t(t,i){return Math.floor(Math.random()*i+t)}function i(){return Math.round(Math.random())?1:-1}function e(t){function i(t){return"#"==t.charAt(0)?t.substring(1,7):t}function e(t){return parseInt(i(t).substring(0,2),16)}function s(t){return parseInt(i(t).substring(2,4),16)}function n(t){return parseInt(i(t).substring(4,6),16)}return-1===t.indexOf("#")?t:[e(t),s(t),n(t),1]}var s,n,o=[],h=[2,10];return{init:function(t,i){s=t,n=i,o=[]},create:function(s,n,h){for(var r=e(h),a=0;n>a;a++){var l=[t(10,30)*i(),-1*t(10,30)];o.push({pos:[s[0]+t(1,3)*i(),s[1]+t(1,3)*i()],vel:l,c:r,t:2})}},update:function(t){t/=500;for(var i=0;o.length>i;i++){var e=o[i];e.t-=t,e.vel[0]+=h[0]*t,e.vel[1]+=h[1]*t,e.pos[0]+=e.vel[0]*t,e.pos[1]+=e.vel[1]*t,e.pos[1]>n.h||0>e.t?o.splice(i,1):e.c[3]=e.t.toFixed(2)}},draw:function(){for(var t=0;o.length>t;t++){var i=o[t];s.save(),s.fillStyle="rgba("+i.c[0]+","+i.c[1]+","+i.c[2]+","+i.c[3]+")",s.fillRect(i.pos[0],i.pos[1],3,3),s.restore()}}}}(),function(){var t=!1,i=/xyz/.test(function(){})?/\b_super\b/:/.*/;this.Class=function(){},Class.extend=function(e){function s(){!t&&this.init&&this.init.apply(this,arguments)}var n=this.prototype;t=!0;var o=new this;t=!1;for(var h in e)o[h]="function"==typeof e[h]&&"function"==typeof n[h]&&i.test(e[h])?function(t,i){return function(){var e=this._super;this._super=n[t];var s=i.apply(this,arguments);return this._super=e,s}}(h,e[h]):e[h];return s.prototype=o,s.prototype.constructor=s,s.extend=arguments.callee,s}}(),Controls.Left="Left",Controls.Right="Right",Controls.Shoot="Shoot",Keyboard.Left=37,Keyboard.Right=39,Keyboard.Up=38,Keyboard.Down=40,Keyboard.Space=32,ImageMapper.Ship=function(){return[[0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0],[0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0],[0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0],[0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0],[0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],[0,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,0],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0],[0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0],[0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0]]},ImageMapper.ShipShoot=function(){return[[1],[1],[1],[1],[1],[1],[1]]},ImageMapper.Invasion=function(){return[[2,2,2,2,2,2,2,2,2,2,2,2,2],[2,2,2,1,2,1,1,1,2,2,2,1,2],[2,2,1,1,2,1,2,1,2,2,1,1,2],[2,1,2,1,2,1,2,1,2,1,2,1,2],[2,1,1,1,2,1,2,1,2,1,1,1,2],[2,2,2,1,2,1,1,1,2,2,2,1,2],[2,2,2,2,2,2,2,2,2,2,2,2,2]]},ImageMapper.AlienCrab=function(){return[[0,0,1,0,0,0,0,0,1,0,0],[3,0,0,1,0,0,0,1,0,0,3],[3,0,0,1,0,0,0,1,0,0,3],[3,0,1,1,1,1,1,1,1,0,3],[3,0,1,0,1,1,1,0,1,0,3],[3,1,1,1,1,1,1,1,1,1,3],[2,1,1,1,1,1,1,1,1,1,2],[2,0,1,1,1,1,1,1,1,0,2],[2,0,1,1,1,1,1,1,1,0,2],[2,0,1,0,0,0,0,0,1,0,2],[2,0,1,0,0,0,0,0,1,0,2],[0,3,0,2,2,0,2,2,0,3,0]]},ImageMapper.AlienSquid=function(){return[[0,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,1,1,1,0,0,0,0],[0,0,0,1,1,1,1,1,0,0,0],[0,0,1,1,1,1,1,1,1,0,0],[0,1,1,0,1,1,1,0,1,1,0],[1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1],[0,0,1,0,0,0,0,0,1,0,0],[0,0,1,0,0,0,0,0,1,0,0],[0,1,0,3,0,0,0,3,0,1,0],[3,0,1,0,3,0,3,0,1,0,3]]},ImageMapper.DeadAlien=function(){return[[1,0,0,0,0,0,0,0,0,0,1],[0,1,0,0,0,1,0,0,0,1,0],[0,0,1,0,0,1,0,0,1,0,0],[0,0,0,1,0,1,0,1,0,0,0],[0,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,0,0,0,1,1,1,1],[0,0,0,0,0,0,0,0,0,0,0],[0,0,0,1,0,1,0,1,0,0,0],[0,0,1,0,0,1,0,0,1,0,0],[0,1,0,0,0,1,0,0,0,1,0],[1,0,0,0,0,1,0,0,0,0,1]]},ImageMapper.AlienShoot=function(){return[[0,1,0],[1,0,0],[0,1,0],[0,0,1],[0,1,0],[1,0,0],[0,1,0]]},ImageMapper.Shield=function(){return[[1,0,0,1,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,0],[1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1,0,1],[1,1,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,1,0,1,0,1],[1,0,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1],[1,0,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,1,1,0]]},ImageMapper.ShieldBrick=function(){return[[[1,1,1,1,1,1],[1,1,1,1,1,1],[1,1,1,1,1,1],[1,1,1,1,1,1],[1,1,1,1,1,1],[1,1,1,1,1,1]],[[0,1,1,1,0,1],[1,1,1,0,0,0],[1,1,0,1,1,0],[0,0,1,0,1,1],[1,0,0,1,0,1],[1,1,0,0,1,1]],[[0,0,0,1,0,1],[0,0,0,0,0,0],[1,0,0,1,0,0],[0,0,1,0,1,1],[1,0,0,1,0,1],[1,1,0,0,0,0]]]},ImageCreator.getImages=function(t){function i(){for(var t=o.length,i=0;t>i;i++)for(var e=o[i].length,s=0;e>s;s++){var h=o[i][s];if(h){var r=new Brick({ctx:d,x:s*l,y:i*l,width:l,height:l,color:u,value:h});n.push(r)}}}function e(t){d.clearRect(0,0,h,r);for(var i=n.length,e=0;i>e;e++)(1===n[e].value||n[e].value===t)&&n[e].draw();var o=c.toDataURL("image/png"),a=new Image;a.src=o,s.push(a)}var s=[],n=[],o=t.mapper||[],h=t.width||100,r=t.height||100,a=t.states||[],l=t.brickSize||5,u=t.color||"#000",c=document.createElement("canvas");c.width=h,c.height=r;var d=c.getContext("2d");i();for(var f=0;a.length>f;f++)e(a[f]);var f=n.length-1;do n[f]=null;while(f--);return s};var DrawableElement=Class.extend({init:function(t){this.ctx=t.ctx?t.ctx:null,this.size={width:t.width||0,height:t.height||0},this.position={x:t.x||0,y:t.y||0},this.brickSize=t.brickSize||1,this.color=t.color||"#000",this.bricks=[],this.onDestroy=t.onDestroy||function(){}},build:function(){},update:function(){},draw:function(t){null!=this.ctx&&this.ctx.drawImage(t,this.position.x+window.camera.pos()[0],this.position.y+window.camera.pos()[1])},destroy:function(){this.ctx=null,null!=this.size&&(this.size.width=null,this.size.height=null,this.size=null),null!=this.position&&(this.position.x=null,this.position.y=null,this.position=null),this.brickSize=null,this.color=null;var t=this.bricks;if(null!=t){for(var i=t.length,e=0;i>e;e++)t[e]=null;this.bricks=null}}}),Shoot=DrawableElement.extend({init:function(t){this._super(t),this.MOVE_FACTOR=5,this.dir=t.dir,this.shootImage=t.shootImage,this.collateBricks=t.collateBricks,this.collateAliens=t.collateAliens,this.timer=null},build:function(){},update:function(){var t=this.dir,i=this.MOVE_FACTOR;return this.position.y+=i*t,this.hasCollision()?(this.collided(),void 0):void 0},draw:function(){this._super(this.shootImage)},collided:function(){this.destroy()},destroy:function(){clearInterval(this.timer),this.collateBricks=null,this.collateAliens=null,this.onDestroy(this),this._super()},hasCollision:function(){function t(t){if(!t)return!1;for(var s=t,n=s.length,o=0;n>o;o++){var h=s[o],r=h.position.x,a=h.position.y,l=r+h.size.width,u=a+h.size.height;if(i>=r&&l>=i&&e>=a&&u>=e&&!h.destroyed)return t[o].collided(),!0}return!1}var i=this.position.x,e=this.position.y;return 0>e||e>400?!0:t(this.collateBricks)?!0:this.collateAliens&&t(this.collateAliens)?!0:void 0}}),Ship=DrawableElement.extend({init:function(t){this._super(t),this.maxMove={left:t.maxMoveLeft,right:t.maxMoveRight},this.onShipHit=t.onShipHit||function(){},this.MOVE_FACTOR=.2,this.SHOOT_TIME=200,this.brickSize=2,this.shootImage=null,this.shoots=[],this.lastShoot=0,this.imgs=[];var i=ImageMapper.Ship();this.size={width:this.brickSize*i[0].length,height:this.brickSize*i.length},this.build(),this.shield=t.shield,this.invasion={}},build:function(){this.buildShootImage();var t={width:this.size.width,height:this.size.height,states:[1],brickSize:this.brickSize,mapper:ImageMapper.Ship(),color:this.color};this.imgs=ImageCreator.getImages(t)},update:function(t,i){var e=this.MOVE_FACTOR;t.indexOf(Controls.Left)>-1?this.position.x>this.maxMove.left&&(this.position.x-=e*i):t.indexOf(Controls.Right)>-1&&this.position.x-1&&0>=this.lastShoot&&(this.lastShoot=this.SHOOT_TIME,t.splice(s,1),this.makeShoot());for(var n=this.shoots,o=n.length,h=0;o>h;h++)n[h]&&n[h].update(i)},draw:function(){this._super(this.imgs[0]);for(var t=this.shoots,i=t.length,e=0;i>e;e++)t[e]&&t[e].draw()},collided:function(){this.onShipHit()},destroy:function(){this.onShipHit=null,this.shootImage=null;for(var t=0;this.shoots.length>t;t++)this.shoots[t].destroy();this.shoots=[],this.imgs=[],this.shield=null,this.invasion=null,this._super()},makeShoot:function(){var t=this,i=new Shoot({ctx:this.ctx,x:this.position.x+this.size.width/2,y:this.position.y,dir:-1,shootImage:this.shootImage,onDestroy:function(i){for(var e=0;t.shoots.length>e;e++)if(t.shoots[e]===i){t.shoots.splice(e,1);break}},collateBricks:this.shield.bricks,collateAliens:this.invasion.aliens});this.shoots.push(i)},buildShootImage:function(){var t=ImageMapper.ShipShoot(),i=2,e=i*t[0].length,s=i*t.length,n={width:e,height:s,states:[1],brickSize:i,mapper:t,color:this.color};this.shootImage=ImageCreator.getImages(n)[0]}}),Invasion=DrawableElement.extend({init:function(t){this._super(t),this.colors={crab:"#FF2727",squid:"#F8FF41"},this.size={width:390,height:210},this.shield=t.shield,this.ship=t.ship,this.MOVE_FACTOR=10,this.DOWN_FACTOR=12,this.CURR_VEL=600,this.VEL_FACTOR=50,this.MOVE_TIME=500,this.lastMove=0,this.dir=1,this.lastDir=1,this.lastPer=100,this.state=0,this.alienSize=30,this.aliens=[],this.crabImages=[],this.squidImages=[],this.deadAlienImgs=[],this.shootImage=null,this.shoots=[],this.build(),this.aliensAmm=this.aliens.length,this.hadAlienCollision=!1,this.onAliensClean=t.onAliensClean||function(){},this.timer=null},build:function(){var t=this;this.buildShootImage(),this.buildAliensImages();for(var i=this.alienSize,e=this.position.x,s=this.position.y,n=this.ctx,o=ImageMapper.Invasion(),h=o.length,r=0;h>r;r++)for(var a=o[r].length,l=0;a>l;l++)if(o[r][l]){var u,c={ctx:n,x:l*i+e,y:r*i+s,width:i,height:i,destroyedImg:this.deadAlienImgs,shield:this.shield,ship:this.ship,onDestroy:function(i){for(var e=0;t.aliens.length>e;e++)if(t.aliens[e]===i){t.aliens.splice(e,1);break}},onWallCollision:function(){t.hadAlienCollision=!0}};switch(o[r][l]){case 1:c.stateImgs=this.crabImages,c.color=this.colors.crab;break;case 2:c.stateImgs=this.squidImages,c.color=this.colors.squid}u=new Alien(c),this.aliens.push(u)}},loop:function(){this.state=!this.state;var t=this.MOVE_FACTOR,i=0,e=0,s=this.aliens,n=s.length;0===n&&this.onAliensClean(),this.hadAlienCollision&&(this.dir*=-1,this.hadAlienCollision=!1,e=this.DOWN_FACTOR,this.lastDir=this.dir),i=t*this.dir,this.position.x+=i,this.position.y+=e;var o=!1;if(this.state&&Math.floor(2*Math.random())){o=!0,shooterIdx=[];for(var h=0;2>h;h++)shooterIdx.push(Math.floor(Math.random()*n))}for(var h=0;n>h;h++)s[h].position.x+=i,s[h].position.y+=e,o&&shooterIdx.indexOf(h)>-1&&this.makeShoot(s[h]);this.vMove>0&&(this.vMove=0);var r=100*n/this.aliensAmm;return this.lastPer-r>9?(this.CURR_VEL-=this.VEL_FACTOR,this.MOVE_TIME-=this.VEL_FACTOR,200>this.MOVE_TIME&&(this.MOVE_TIME=200),this.lastPer=r,void 0):void 0},update:function(t){if(this.lastMove-=t,0>=this.lastMove){this.loop(),this.lastMove=this.MOVE_TIME,this.state;for(var i=this.aliens,e=i.length,s=0;e>s;s++)void 0!==i[s]&&i[s].update(t)}for(var n=this.shoots,o=n.length,s=0;o>s;s++)n[s]&&n[s].update(t)},draw:function(){for(var t=this.state,i=this.aliens,e=i.length,s=0;e>s;s++)void 0!==i[s]&&i[s].draw(t);for(var n=this.shoots,o=n.length,s=0;o>s;s++)n[s].draw()},destroy:function(){clearInterval(this.timer),this.shield=null,this.ship=null;for(var t=0;this.shoots.length>t;t++)this.shoots[t].destroy();this.shoots=[],this._super()},makeShoot:function(t){var i=this.shield,e=this.ship,s=this,n=new Shoot({ctx:this.ctx,x:t.position.x+t.size.width/2,y:t.position.y,dir:1,shootImage:this.shootImage,onDestroy:function(t){for(var i=0;s.shoots.length>i;i++)if(s.shoots[i]===t){s.shoots.splice(i,1);break}},collateBricks:i.bricks,collateAliens:[e]});this.shoots.push(n)},buildShootImage:function(){var t=ImageMapper.AlienShoot(),i=2,e=i*t[0].length,s=i*t.length,n={width:e,height:s,states:[1],brickSize:i,mapper:t,color:"yellow"};this.shootImage=ImageCreator.getImages(n)[0]},buildAliensImages:function(){var t={width:30,height:30,states:[1],brickSize:2};t.mapper=ImageMapper.DeadAlien(),t.color="white",this.deadAlienImgs=ImageCreator.getImages(t),t.states=[2,3],t.mapper=ImageMapper.AlienCrab(),t.color=this.colors.crab,this.crabImages=ImageCreator.getImages(t),t.mapper=ImageMapper.AlienSquid(),t.color=this.colors.squid,this.squidImages=ImageCreator.getImages(t)}}),Alien=DrawableElement.extend({init:function(t){this._super(t),this.images=t.stateImgs||[],this.destroyedImg=t.destroyedImg||[],this.onWallCollision=t.onWallCollision||[],this.shield=t.shield||null,this.ship=t.ship||null,this.destroyed=!1,this.shoots=[]},build:function(){},update:function(){this.hasCollision();var t=this.position.x;(20>t||t>590-this.size.width)&&this.onWallCollision();var i=this.position.y+this.size.height;0>i&&this.ship.collided()},draw:function(t){if(this.destroyed)this._super(this.destroyedImg[0]),this.destroy(),this.onDestroy(this);else{var i=t?0:1;this._super(this.images[i])}},hasCollision:function(){function t(t){if(!t)return!1;for(var s=t,n=s.length,o=0;n>o;o++){var h=s[o],r=h.position.x,a=h.position.y,l=r+h.size.width,u=a+h.size.height;if(i>=r&&l>=i&&e>=a&&u>=e&&!h.destroyed)return t[o].collided(!0),!0}return!1}var i=this.position.x+this.size.width/2,e=this.position.y+.8*this.size.height;return t(this.shield.bricks)?!0:t([this.ship])?!0:void 0},collided:function(){this.destroyed=!0,window.camera.shake(3),window.particles.create([this.position.x+this.size.width/2,this.position.y+this.size.height/2],10,this.color)},destroy:function(){this._super()}}),Brick=DrawableElement.extend({init:function(t){this._super(t),this.destroyed=!1,this.value=t.value||1},build:function(){},update:function(){},draw:function(){this.destroyed||(this.ctx.beginPath(),this.ctx.rect(this.position.x,this.position.y,this.size.width,this.size.height),this.ctx.fillStyle=this.color,this.ctx.fill())},destroy:function(){this.destroyed=!0}}),ShieldBrick=DrawableElement.extend({init:function(t){this._super(t),this.state=0,this.imgsState=t.imgsState,this.destroyed=!1},build:function(){},update:function(){},draw:function(){this.destroyed||this._super(this.imgsState[this.state])},collided:function(t){window.camera.shake(1),window.particles.create([this.position.x+this.size.width/2,this.position.y+this.size.height/2],4,this.color),t?this.state=Math.floor(3*Math.random()+2):this.state++,this.state>1&&(this.destroyed=!0)},destroy:function(){this._super()}}),Shield=DrawableElement.extend({init:function(t){this._super(t),this.imgs=[],this.build()},build:function(){this.createImagesStateBricks();for(var t=this.brickSize,i=this.position.x,e=this.position.y,s=this.ctx,n=this.color,o=ImageMapper.Shield(),h=o.length,r=0;h>r;r++)for(var a=o[r].length,l=0;a>l;l++)if(o[r][l]){var u=new ShieldBrick({ctx:s,x:l*t+i,y:r*t+e,width:t,height:t,color:n,imgsState:this.imgs});this.bricks.push(u)}},update:function(t){for(var i=this.bricks,e=i.length,s=0;e>s;s++)i[s]&&i[s].update(t)},draw:function(){var t=this.bricks;if(t)for(var i=t.length,e=0;i>e;e++)t[e]&&t[e].draw()},destroy:function(){for(var t=this.bricks,i=t.length,e=0;i>e;e++)t[e].destroy();this.bricks=[],this._super()},createImagesStateBricks:function(){for(var t={width:this.brickSize,height:this.brickSize,states:[1],brickSize:2,color:this.color},i=ImageMapper.ShieldBrick(),e=0;i.length>e;e++)t.mapper=i[e],this.imgs.push(ImageCreator.getImages(t)[0])}}),Invaders404=Class.extend({init:function(t){this.canvas=null,this.ctx=null,this.loopInterval=10,this.currentDir=[],this.shield={},this.ship={},this.invasion={},this.initCanvas(t.canvasId),this.onLoose=t.onLoose||function(){},this.onWin=t.onWin||function(){},this.isOnGame=!1,this.boundGameRun=this.gameRun.bind(this),this.fps=0,this.now=null,this.lastUpdate=1*new Date-1,this.fpsFilter=this.loopInterval;var i=this,e=document.getElementById("fps");setInterval(function(){e.innerHTML=i.fps.toFixed(1)+"fps"},1e3)},initCanvas:function(t){this.canvas=document.getElementById(t||"canvas"),this.ctx=this.canvas.getContext("2d"),window.particles.init(this.ctx,{w:this.canvas.width,h:this.canvas.height})},start:function(){this.build(),this.gameRun()},gameRun:function(){window.gameTime.tick()&&this.loop(),this.tLoop=window.requestAnimationFrame(this.boundGameRun)},build:function(){var t=this;this.shield=new Shield({ctx:this.ctx,x:70,y:290,brickSize:12,color:"#ffffff"});var i=this.canvas.width;this.ship=new Ship({ctx:this.ctx,shield:this.shield,maxMoveLeft:5,maxMoveRight:i-10,x:(i-10)/2,y:370,color:"#1be400",onShipHit:function(){t.stop(),t.onLoose()}}),this.invasion=new Invasion({ctx:this.ctx,x:60,y:10,shield:this.shield,ship:this.ship,onAliensClean:function(){t.stop(),t.onWin()}}),this.ship.invasion=this.invasion,this.currentDir=[],this.isOnGame=!0,this.bindControls()},loop:function(){this.isOnGame&&(this.update(window.gameTime.frameTime),this.draw())},update:function(t){window.camera.update(t),this.shield.update(t),this.ship.update(this.currentDir,t),this.invasion.update(t),window.particles.update(t)},draw:function(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.shield.draw(),this.ship.draw(),this.invasion.draw(),window.particles.draw();var t=1e3/((this.now=new Date)-this.lastUpdate);this.fps+=(t-this.fps)/this.fpsFilter,this.lastUpdate=this.now},bindControls:function(){function t(t){switch(t){case Keyboard.Space:return Controls.Shoot;case Keyboard.Left:return Controls.Left;case Keyboard.Right:return Controls.Right}return null}var i=this,e=[Keyboard.Space,Keyboard.Left,Keyboard.Right];document.addEventListener("keydown",function(s){if(i.isOnGame){var n=s.keyCode;if(e.indexOf(n)>-1){var o=t(n);return-1===i.currentDir.indexOf(o)&&i.currentDir.push(o),s.stopPropagation(),s.preventDefault(),!1}}}),document.addEventListener("keyup",function(e){if(i.isOnGame){var s=e.keyCode,n=t(s),o=i.currentDir.indexOf(n);o>-1&&i.currentDir.splice(o,1)}})},unbindControls:function(){document.removeEventListener("keydown",function(){}),document.removeEventListener("keyup",function(){})},destroy:function(){this.shield.destroy(),this.invasion.destroy(),this.ship.destroy()},stop:function(){this.isOnGame=!1;for(var t=0;this.currentDir.length>t;t++)this.currentDir[t]=null;this.currentDir=[],this.destroy()},drawSplash:function(t){function i(t,i){var e=20,o=t*e-e;s.save(),s.fillStyle=i,s.fillRect(o,0,e,n),s.restore()}function e(){for(var s=0;5>s;s++)i(h+s,"rgba(240,219,79,"+(s?s/10:1)+")");h++,o/10>h?setTimeout(e,r):t()}var s=this.ctx,n=this.canvas.height,o=this.canvas.width,h=0,r=2*this.loopInterval;e()}});(function(){function t(){for(var t in l)if(-1!==navigator.platform.indexOf(l[t]))return l[t];return"Unknown"}function i(t,i,e,s,n){return s+(n-s)*((t-i)/(e-i))}function e(t,i,e,s){Object.defineProperty(e,s,{enumerable:!0,get:function(){return t.axes[i.axes[s]]}})}function s(t,i,e,s){Object.defineProperty(e,s,{enumerable:!0,get:function(){return 0}})}function n(t,i,e,s){Object.defineProperty(e,s,{enumerable:!0,get:function(){return t.buttons[i.buttons[s]]}})}function o(t,e,s,n){var o=e.axes[n]instanceof Array;Object.defineProperty(s,n,{enumerable:!0,get:function(){return o?i(t.axes[e.axes[n][0]],e.axes[n][1],e.axes[n][2],0,1):t.axes[e.axes[n]]}})}function h(t,i){Object.defineProperty(t,i,{enumerable:!0,get:function(){return 0}})}var r={"45e":{"28e":{Mac:{axes:{Left_Stick_X:0,Left_Stick_Y:1,Right_Stick_X:2,Right_Stick_Y:3,Left_Trigger_2:[4,-1,1],Right_Trigger_2:[5,-1,1]},buttons:{A_Button:0,B_Button:1,X_Button:2,Y_Button:3,Left_Trigger_1:4,Right_Trigger_1:5,Left_Stick_Button:6,Right_Stick_Button:7,Start_Button:8,Back_Button:9,Home_Button:10,Pad_Up:11,Pad_Down:12,Pad_Left:13,Pad_Right:14}},Win:{axes:{Left_Stick_X:0,Left_Stick_Y:1,Right_Stick_X:3,Right_Stick_Y:4,Pad_Left:[5,0,-1],Pad_Right:[5,0,1],Pad_Up:[6,0,-1],Pad_Down:[6,0,1],Left_Trigger_2:[2,0,1],Right_Trigger_2:[2,0,-1]},buttons:{A_Button:0,B_Button:1,X_Button:2,Y_Button:3,Left_Trigger_1:4,Right_Trigger_1:5,Back_Button:6,Start_Button:7,Left_Stick_Button:8,Right_Stick_Button:9}}}},"54c":{268:{Mac:{axes:{Left_Stick_X:0,Left_Stick_Y:1,Right_Stick_X:2,Right_Stick_Y:3},buttons:{Back_Button:0,Left_Stick_Button:1,Right_Stick_Button:2,Start_Button:3,Pad_Up:4,Pad_Down:6,Pad_Right:5,Pad_Left:7,Left_Trigger_2:8,Right_Trigger_2:9,Left_Trigger_1:10,Right_Trigger_1:11,Y_Button:12,B_Button:13,A_Button:14,X_Button:15,Home_Button:16}}}},"46d":{c242:{Win:{axes:{Left_Stick_X:0,Left_Stick_Y:1,Right_Stick_Y:4,Right_Stick_X:3,Left_Trigger_2:[2,0,1],Right_Trigger_2:[2,-1,0],Pad_Left:[5,-1,0],Pad_Right:[5,0,1],Pad_Up:[6,-1,0],Pad_Down:[6,0,1]},buttons:{A_Button:0,X_Button:2,B_Button:1,Y_Button:3,Left_Trigger_1:4,Right_Trigger_1:5,Back_Button:6,Start_Button:7,Left_Stick_Button:8,Right_Stick_Button:9}}},c216:{Mac:{axes:{Left_Stick_X:1,Left_Stick_Y:2,Right_Stick_X:3,Right_Stick_Y:4,Pad_Left:[1,0,-1],Pad_Right:[1,0,1],Pad_Up:[2,0,-1],Pad_Down:[2,0,1]},buttons:{X_Button:0,A_Button:1,B_Button:2,Y_Button:3,Left_Trigger_1:4,Right_Trigger_1:5,Left_Trigger_2:6,Right_Trigger_2:7,Back_Button:8,Start_Button:9,Left_Stick_Button:10,Right_Stick_Button:11}}}},"40b":{6533:{Mac:{axes:{Pad_Left:[0,0,-1],Pad_Right:[0,0,1],Pad_Up:[1,0,-1],Pad_Down:[1,0,1]},buttons:{A_Button:0,B_Button:1,X_Button:2,Y_Button:3}}}},Firefox:{"Fake Gamepad":{Mac:{axes:{},buttons:{A_Button:0,B_Button:1,X_Button:2,Y_Button:3,Pad_Up:4,Pad_Down:5,Pad_Left:6,Pad_Right:7}}}}},a={axes:["Left_Stick_X","Left_Stick_Y","Right_Stick_X","Right_Stick_Y"],buttons:["A_Button","B_Button","X_Button","Y_Button","Left_Stick_Button","Right_Stick_Button","Start_Button","Back_Button","Home_Button","Pad_Up","Pad_Down","Pad_Left","Pad_Right","Left_Trigger_1","Right_Trigger_1","Left_Trigger_2","Right_Trigger_2"]},l=["Win","Mac","Linux"],u=window.Input={};u.Device=function(i){if(!i)throw"You didn't pass a valid gamepad to the constructor";var l=i,u=i.id.split("-")[0],c=i.id.split("-")[1],d=t(),f=r,g=this.axes={},p=this.buttons={};if(!(f&&f[u]&&f[u][c]&&f[u][c][d]))throw"A physical device layout for "+u+"-"+c+"-"+d+" isn't available";f=f[u][c][d];for(var m in a.axes)void 0!==f.axes[a.axes[m]]?e(l,f,g,a.axes[m]):void 0!==f.buttons[a.axes[m]]?s(l,f,g,a.axes[m]):h(g,a.axes[m]);for(var _ in a.buttons)void 0!==f.buttons[a.buttons[_]]?n(l,f,p,a.buttons[_]):void 0!==f.axes[a.buttons[_]]?o(l,f,p,a.buttons[_]):h(p,a.buttons[_]);Object.defineProperty(this,"connected",{enumerable:!0,get:function(){return l.connected}}),Object.defineProperty(this,"id",{enumerable:!0,get:function(){return l.id}}),Object.defineProperty(this,"index",{enumerable:!0,get:function(){return l.index}})}})();
--------------------------------------------------------------------------------
/game.js:
--------------------------------------------------------------------------------
1 | //taken from Gist: https://gist.github.com/paulirish/1579671
2 |
3 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
4 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
5 |
6 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
7 |
8 | // MIT license
9 |
10 | ;(function() {
11 | var lastTime = 0;
12 | var vendors = ['ms', 'moz', 'webkit', 'o'];
13 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
14 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
15 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
16 | || window[vendors[x]+'CancelRequestAnimationFrame'];
17 | }
18 |
19 | if (!window.requestAnimationFrame)
20 | window.requestAnimationFrame = function(callback, element) {
21 | var currTime = new Date().getTime();
22 | var timeToCall = Math.max(0, 17 - (currTime - lastTime));
23 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
24 | timeToCall);
25 | lastTime = currTime + timeToCall;
26 | return id;
27 | };
28 |
29 | if (!window.cancelAnimationFrame)
30 | window.cancelAnimationFrame = function(id) {
31 |
32 | window.clearTimeout(id);
33 | };
34 | }());
35 | // Manages the ticks for a Game Loop
36 |
37 | window.gameTime = {
38 | lastTime : Date.now(),
39 | frameTime : 0 ,
40 | typicalFrameTime : 20,
41 | minFrameTime : 12 ,
42 | time : 0
43 | };
44 |
45 | // move the clock one tick.
46 | // return true if new frame, false otherwise.
47 | window.gameTime.tick = function() {
48 | var now = Date.now();
49 | var delta = now - this.lastTime;
50 |
51 | if (delta < this.minFrameTime ) {
52 | return false;
53 | }
54 |
55 | if (delta > 2 * this.typicalFrameTime) { // +1 frame if too much time elapsed
56 | this.frameTime = this.typicalFrameTime;
57 | } else {
58 | this.frameTime = delta;
59 | }
60 |
61 | this.time += this.frameTime;
62 | this.lastTime = now;
63 |
64 | return true;
65 | };
66 |
67 | window.camera = (function(){
68 |
69 | var pars = [],
70 | t = 0.1,
71 | currT = 0,
72 | pos = [0, 0];
73 |
74 | function rnd(from, to){
75 | return Math.floor((Math.random()*to)+from);
76 | }
77 |
78 | function rndM(){
79 | return (Math.round(Math.random()) ? 1 : -1);
80 | }
81 |
82 | return {
83 | pos: function(){
84 | return [pos[0], pos[1]];
85 | },
86 | shake: function(powa){
87 | powa = powa || 3;
88 | currT = t;
89 | pos = [ rnd(-powa, powa), rnd(-powa, powa) ];
90 | },
91 | update: function(dt){
92 | dt = dt/1000;
93 | currT -= dt;
94 |
95 | if (currT < 0){
96 | pos = [0, 0];
97 | }
98 | else {
99 | pos[0] *= rndM();
100 | pos[1] *= rndM();
101 | }
102 | }
103 | }
104 |
105 | })();
106 |
107 | window.particles = (function(){
108 |
109 | var pars = [],
110 | //gravity = [5, 40],
111 | gravity = [2, 10],
112 | ctx,
113 | size;
114 |
115 | function rnd(from, to){
116 | return Math.floor((Math.random()*to)+from);
117 | }
118 |
119 | function rndM(){
120 | return (Math.round(Math.random()) ? 1 : -1);
121 | }
122 |
123 | function hexToRGB(c){
124 | if (c.indexOf('#') === -1) return c;
125 |
126 | function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}
127 | function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
128 | function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
129 | function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
130 |
131 | return [ hexToR(c), hexToG(c), hexToB(c), 1 ];
132 | }
133 |
134 | return {
135 | init: function(_ctx, _size){
136 | ctx = _ctx;
137 | size = _size;
138 | pars = [];
139 | },
140 | create: function(pos, qty, color){
141 | var c = hexToRGB(color);
142 |
143 | for (var i=0; i < qty; i++){
144 |
145 | var vel = [rnd(10, 30)*rndM(), rnd(10, 30)*-1];
146 |
147 | pars.push({
148 | pos: [
149 | pos[0] + (rnd(1, 3)*rndM()),
150 | pos[1] + (rnd(1, 3)*rndM())
151 | ],
152 | vel: vel,
153 | c: c,
154 | t: 2,
155 | });
156 | }
157 | },
158 | update: function(dt){
159 | dt = dt/500;
160 |
161 | for(var i=0; i < pars.length; i++){
162 | var p = pars[i];
163 |
164 | p.t -= dt;
165 |
166 | p.vel[0] += gravity[0] * dt;
167 | p.vel[1] += gravity[1] * dt;
168 |
169 | p.pos[0] += p.vel[0] * dt;
170 | p.pos[1] += p.vel[1] * dt;
171 |
172 | if (p.pos[1] > size.h || p.t < 0){
173 | pars.splice(i, 1);
174 | }
175 | else {
176 | p.c[3] = p.t.toFixed(2);
177 | }
178 | }
179 | },
180 | draw: function(dt){
181 | for(var i=0; i < pars.length; i++){
182 | var p = pars[i];
183 | ctx.save();
184 | ctx.fillStyle = 'rgba(' + p.c[0] + ',' + p.c[1] + ',' + p.c[2] + ',' + p.c[3] + ')';
185 | ctx.fillRect(p.pos[0], p.pos[1], 3, 3);
186 | ctx.restore();
187 | }
188 | }
189 | }
190 |
191 | })();
192 | /* Simple JavaScript Inheritance
193 | * By John Resig http://ejohn.org/
194 | * MIT Licensed.
195 | */
196 | // Inspired by base2 and Prototype
197 | (function(){
198 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
199 |
200 | // The base Class implementation (does nothing)
201 | this.Class = function(){};
202 |
203 | // Create a new Class that inherits from this class
204 | Class.extend = function(prop) {
205 | var _super = this.prototype;
206 |
207 | // Instantiate a base class (but only create the instance,
208 | // don't run the init constructor)
209 | initializing = true;
210 | var prototype = new this();
211 | initializing = false;
212 |
213 | // Copy the properties over onto the new prototype
214 | for (var name in prop) {
215 | // Check if we're overwriting an existing function
216 | prototype[name] = typeof prop[name] == "function" &&
217 | typeof _super[name] == "function" && fnTest.test(prop[name]) ?
218 | (function(name, fn){
219 | return function() {
220 | var tmp = this._super;
221 |
222 | // Add a new ._super() method that is the same method
223 | // but on the super-class
224 | this._super = _super[name];
225 |
226 | // The method only need to be bound temporarily, so we
227 | // remove it when we're done executing
228 | var ret = fn.apply(this, arguments);
229 | this._super = tmp;
230 |
231 | return ret;
232 | };
233 | })(name, prop[name]) :
234 | prop[name];
235 | }
236 |
237 | // The dummy class constructor
238 | function Class() {
239 | // All construction is actually done in the init method
240 | if ( !initializing && this.init )
241 | this.init.apply(this, arguments);
242 | }
243 |
244 | // Populate our constructed prototype object
245 | Class.prototype = prototype;
246 |
247 | // Enforce the constructor to be what we expect
248 | Class.prototype.constructor = Class;
249 |
250 | // And make this class extendable
251 | Class.extend = arguments.callee;
252 |
253 | return Class;
254 | };
255 | })();
256 |
257 |
258 | function Controls() { throw 'Controls class is Static.'; };
259 | Controls.Left = "Left";
260 | Controls.Right = "Right";
261 | Controls.Shoot = "Shoot";
262 |
263 | function Keyboard() { throw 'KeyboardCode class is Static.'; };
264 | Keyboard.Left = 37;
265 | Keyboard.Right = 39;
266 | Keyboard.Up = 38;
267 | Keyboard.Down = 40;
268 | Keyboard.Space = 32;
269 |
270 | /**
271 | * @author pjnovas
272 | */
273 |
274 | function ImageMapper() { throw 'ImageMapper class is Static.'; };
275 | ImageMapper.Ship = function(){
276 | return [
277 | [0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0],
278 | [0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0],
279 | [0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
280 | [0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0],
281 | [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
282 | [0,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,0],
283 | [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
284 | [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
285 | [0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0],
286 | [0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0],
287 | [0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0]
288 | ];
289 | };
290 | ImageMapper.ShipShoot = function(){
291 | return [
292 | [1],
293 | [1],
294 | [1],
295 | [1],
296 | [1],
297 | [1],
298 | [1]
299 | ];
300 | };
301 | ImageMapper.Invasion = function(){
302 | return [
303 | [2,2,2,2,2,2,2,2,2,2,2,2,2],
304 | [2,2,2,1,2,1,1,1,2,2,2,1,2],
305 | [2,2,1,1,2,1,2,1,2,2,1,1,2],
306 | [2,1,2,1,2,1,2,1,2,1,2,1,2],
307 | [2,1,1,1,2,1,2,1,2,1,1,1,2],
308 | [2,2,2,1,2,1,1,1,2,2,2,1,2],
309 | [2,2,2,2,2,2,2,2,2,2,2,2,2]
310 | ];
311 | };
312 | ImageMapper.AlienCrab = function(){
313 | return [
314 | [0,0,1,0,0,0,0,0,1,0,0],
315 | [3,0,0,1,0,0,0,1,0,0,3],
316 | [3,0,0,1,0,0,0,1,0,0,3],
317 | [3,0,1,1,1,1,1,1,1,0,3],
318 | [3,0,1,0,1,1,1,0,1,0,3],
319 | [3,1,1,1,1,1,1,1,1,1,3],
320 | [2,1,1,1,1,1,1,1,1,1,2],
321 | [2,0,1,1,1,1,1,1,1,0,2],
322 | [2,0,1,1,1,1,1,1,1,0,2],
323 | [2,0,1,0,0,0,0,0,1,0,2],
324 | [2,0,1,0,0,0,0,0,1,0,2],
325 | [0,3,0,2,2,0,2,2,0,3,0]
326 | ];
327 | };
328 | ImageMapper.AlienSquid = function(){
329 | return [
330 | [0,0,0,0,0,1,0,0,0,0,0],
331 | [0,0,0,0,1,1,1,0,0,0,0],
332 | [0,0,0,1,1,1,1,1,0,0,0],
333 | [0,0,1,1,1,1,1,1,1,0,0],
334 | [0,1,1,0,1,1,1,0,1,1,0],
335 | [1,1,1,1,1,1,1,1,1,1,1],
336 | [1,1,1,1,1,1,1,1,1,1,1],
337 | [1,1,1,1,1,1,1,1,1,1,1],
338 | [0,0,1,0,0,0,0,0,1,0,0],
339 | [0,0,1,0,0,0,0,0,1,0,0],
340 | [0,1,0,3,0,0,0,3,0,1,0],
341 | [3,0,1,0,3,0,3,0,1,0,3]
342 | ];
343 | };
344 | ImageMapper.DeadAlien = function(){
345 | return [
346 | [1,0,0,0,0,0,0,0,0,0,1],
347 | [0,1,0,0,0,1,0,0,0,1,0],
348 | [0,0,1,0,0,1,0,0,1,0,0],
349 | [0,0,0,1,0,1,0,1,0,0,0],
350 | [0,0,0,0,0,0,0,0,0,0,0],
351 | [1,1,1,1,0,0,0,1,1,1,1],
352 | [0,0,0,0,0,0,0,0,0,0,0],
353 | [0,0,0,1,0,1,0,1,0,0,0],
354 | [0,0,1,0,0,1,0,0,1,0,0],
355 | [0,1,0,0,0,1,0,0,0,1,0],
356 | [1,0,0,0,0,1,0,0,0,0,1]
357 | ];
358 | };
359 | ImageMapper.AlienShoot = function(){
360 | return [
361 | [0,1,0],
362 | [1,0,0],
363 | [0,1,0],
364 | [0,0,1],
365 | [0,1,0],
366 | [1,0,0],
367 | [0,1,0]
368 | ];
369 | };
370 | /*ImageMapper.Shield = function(){
371 | return [ //FERNET JS
372 | [1,1,1,0,1,1,1,0,1,1,1,0,1,0,0,1,0,1,1,1,0,1,1,1,0,0,1,1,1,0,1,1,1],
373 | [1,0,0,0,1,0,0,0,1,0,1,0,1,1,0,1,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,0,0],
374 | [1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0,1,0,0,0,0,1,0,0,1,1,1],
375 | [1,0,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0,0,0,0,1],
376 | [1,0,0,0,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,1,0,0,1,0,0,0,1,1,0,0,1,1,1]
377 | ];
378 | };*/
379 | ImageMapper.Shield = function(){
380 | return [ //NOT FOUND
381 | [1,0,0,1,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,0],
382 | [1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1,0,1],
383 | [1,1,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,1,0,1,0,1],
384 | [1,0,1,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1],
385 | [1,0,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,1,1,0]
386 | ];
387 | };
388 | ImageMapper.ShieldBrick = function(){
389 | return [
390 | [
391 | [1,1,1,1,1,1],
392 | [1,1,1,1,1,1],
393 | [1,1,1,1,1,1],
394 | [1,1,1,1,1,1],
395 | [1,1,1,1,1,1],
396 | [1,1,1,1,1,1]
397 | ],
398 | [
399 | [0,1,1,1,0,1],
400 | [1,1,1,0,0,0],
401 | [1,1,0,1,1,0],
402 | [0,0,1,0,1,1],
403 | [1,0,0,1,0,1],
404 | [1,1,0,0,1,1]
405 | ],
406 | [
407 | [0,0,0,1,0,1],
408 | [0,0,0,0,0,0],
409 | [1,0,0,1,0,0],
410 | [0,0,1,0,1,1],
411 | [1,0,0,1,0,1],
412 | [1,1,0,0,0,0]
413 | ]
414 | ];
415 | };
416 |
417 |
418 |
419 |
420 |
421 | function ImageCreator(){ throw 'ImageCreator class is Static.'; };
422 |
423 | ImageCreator.getImages = function(options){
424 |
425 | var images = [];
426 | var bricks = [];
427 |
428 | // B - Get parameters ---------------------------------
429 |
430 | var mapper = options.mapper || [];
431 | var w = options.width || 100;
432 | var h = options.height || 100;
433 |
434 | var states = options.states || [];
435 | var bSize = options.brickSize || 5;
436 |
437 | var color = options.color || '#000';
438 |
439 | // E - Get parameters ---------------------------------
440 |
441 |
442 | // B - Create CANVAS to render ------------------------
443 |
444 | var canvas = document.createElement('canvas');
445 | canvas.width = w;
446 | canvas.height = h;
447 | var ctx = canvas.getContext('2d');
448 | //TODO: delete element
449 |
450 | // E - Create CANVAS to render ------------------------
451 |
452 |
453 | // B - Create image from mapper -----------------------
454 |
455 | function buildBricks(){
456 | var arrLen = mapper.length;
457 |
458 | for(var i=0; i< arrLen; i++){
459 | var colLen = mapper[i].length;
460 |
461 | for(var j=0; j< colLen; j++){
462 | var val = mapper[i][j];
463 |
464 | if (val){
465 | var b = new Brick({
466 | ctx: ctx,
467 | x: (j * bSize),
468 | y: (i * bSize),
469 | width: bSize,
470 | height: bSize,
471 | color: color,
472 | value: val
473 | });
474 |
475 | bricks.push(b);
476 | }
477 | }
478 | }
479 | }
480 |
481 | // E - Create image from mapper -----------------------
482 |
483 |
484 | // B - Draw on canvas context and get image -----------
485 |
486 | function createImage(state){
487 | ctx.clearRect(0, 0, w, h);
488 |
489 | var bLen = bricks.length;
490 | for(var i=0; i< bLen; i++){
491 | if (bricks[i].value === 1 || bricks[i].value === state)
492 | bricks[i].draw();
493 | }
494 |
495 | var imgData = canvas.toDataURL("image/png");
496 |
497 | var image = new Image();
498 | image.src = imgData;
499 |
500 | images.push(image);
501 | }
502 |
503 | // E - Draw on canvas context and get image -----------
504 |
505 |
506 | //Run the build
507 | buildBricks();
508 |
509 | //Create all images for each state
510 | for(var i=0; i< states.length; i++){
511 | createImage(states[i]);
512 | }
513 |
514 | // destroy all bricks created
515 | var i = bricks.length - 1;
516 | do{ bricks[i] = null; } while(i--);
517 |
518 | return images;
519 | }
520 |
521 |
522 |
523 | var DrawableElement = Class.extend({
524 | init: function(options){
525 | this.ctx = (options.ctx) ? options.ctx : null; // throw "must provide a Canvas Context";
526 |
527 | this.size = {
528 | width: options.width || 0,
529 | height: options.height || 0
530 | };
531 |
532 | this.position = {
533 | x: options.x || 0,
534 | y: options.y || 0
535 | };
536 |
537 | this.brickSize = options.brickSize || 1;
538 | this.color = options.color || '#000';
539 |
540 | this.bricks = [];
541 |
542 | this.onDestroy = options.onDestroy || function(){};
543 | },
544 | build: function(){
545 |
546 | },
547 | update: function(){
548 |
549 | },
550 | draw: function(img){
551 | if (this.ctx != null)
552 | this.ctx.drawImage(img,
553 | this.position.x + window.camera.pos()[0],
554 | this.position.y + window.camera.pos()[1]);
555 | },
556 | destroy: function(){
557 | this.ctx = null;
558 |
559 | if (this.size != null) {
560 | this.size.width = null;
561 | this.size.height = null;
562 | this.size = null;
563 | }
564 |
565 | if (this.position != null) {
566 | this.position.x = null;
567 | this.position.y = null;
568 | this.position = null;
569 | }
570 |
571 | this.brickSize = null;
572 | this.color = null;
573 |
574 | var bricks = this.bricks;
575 | if (bricks != null) {
576 | var bricksL = bricks.length;
577 | for(var i=0; i< bricksL; i++)
578 | bricks[i] = null;
579 |
580 | this.bricks = null;
581 | }
582 |
583 | //if (this.onDestroy) this.onDestroy(this);
584 | }
585 | });
586 |
587 | /* TEMPLATE for Inheritance
588 |
589 | var DrawableElement = Class.extend({
590 | init: function(options){
591 | this._super(options);
592 |
593 | },
594 | build: function(){
595 |
596 | },
597 | update: function(){
598 |
599 | },
600 | draw: function(){
601 |
602 | },
603 | destroy: function(){
604 |
605 | }
606 | });
607 |
608 | */
609 |
610 |
611 | var Shoot = DrawableElement.extend({
612 | init: function(options){
613 | this._super(options);
614 |
615 | this.MOVE_FACTOR = 5;
616 | this.dir = options.dir;
617 |
618 | this.shootImage = options.shootImage;
619 |
620 | this.collateBricks = options.collateBricks;
621 | this.collateAliens = options.collateAliens;
622 |
623 | this.timer = null;
624 | },
625 | build: function(){
626 |
627 | },
628 | update: function(dt){
629 | var dir = this.dir;
630 | var vel = this.MOVE_FACTOR;
631 |
632 | this.position.y += (vel * dir);
633 |
634 | if(this.hasCollision()){
635 | this.collided();
636 | return;
637 | }
638 | },
639 | draw: function(){
640 | this._super(this.shootImage);
641 | },
642 | collided: function(){
643 | this.destroy();
644 | },
645 | destroy: function(){
646 | clearInterval(this.timer);
647 |
648 | this.collateBricks = null;
649 | this.collateAliens = null;
650 |
651 | this.onDestroy(this);
652 |
653 | this._super();
654 | },
655 | hasCollision: function(){
656 | var sX = this.position.x;
657 | var sY = this.position.y;
658 |
659 | if (sY < 0 || sY > 400)
660 | return true;
661 |
662 | function checkCollision(arr){
663 | if (!arr){
664 | return false;
665 | }
666 |
667 | var cb = arr;
668 | var cbLen = cb.length;
669 |
670 | for(var i=0; i< cbLen; i++){
671 | var cbO = cb[i];
672 |
673 | var cbL = cbO.position.x;
674 | var cbT = cbO.position.y;
675 | var cbR = cbL + cbO.size.width;
676 | var cbD = cbT + cbO.size.height;
677 |
678 | if (sX >= cbL && sX <= cbR && sY >= cbT && sY <= cbD && !cbO.destroyed){
679 | arr[i].collided();
680 | return true;
681 | }
682 | }
683 |
684 | return false;
685 | }
686 |
687 | if (checkCollision(this.collateBricks)) return true;
688 | if (this.collateAliens && checkCollision(this.collateAliens)) return true;
689 | }
690 | });
691 |
692 | var Ship = DrawableElement.extend({
693 | init: function(options){
694 | this._super(options);
695 |
696 | this.maxMove = {
697 | left: options.maxMoveLeft,
698 | right: options.maxMoveRight,
699 | };
700 |
701 | this.onShipHit = options.onShipHit || function(){};
702 |
703 | this.MOVE_FACTOR = 0.2;
704 | this.SHOOT_TIME = 200;
705 |
706 | this.brickSize = 2;
707 | this.shootImage = null;
708 | this.shoots = [];
709 | this.lastShoot = 0;
710 |
711 | this.imgs = [];
712 |
713 | var map = ImageMapper.Ship();
714 |
715 | this.size = {
716 | width: this.brickSize * map[0].length,
717 | height: this.brickSize * map.length
718 | };
719 |
720 | this.build();
721 |
722 | this.shield = options.shield;
723 | this.invasion = {};
724 | },
725 | build: function(){
726 | this.buildShootImage();
727 |
728 | var opts = {
729 | width: this.size.width,
730 | height: this.size.height,
731 | states: [1],
732 | brickSize: this.brickSize,
733 | mapper: ImageMapper.Ship(),
734 | color: this.color
735 | };
736 |
737 | this.imgs = ImageCreator.getImages(opts);
738 | },
739 | update: function(actions, dt){
740 | var vel = this.MOVE_FACTOR;
741 |
742 | if (actions.indexOf(Controls.Left)>-1){
743 | if (this.position.x > this.maxMove.left){
744 | this.position.x -= vel * dt;
745 | }
746 | }
747 | else if (actions.indexOf(Controls.Right)>-1) {
748 | if (this.position.x < (this.maxMove.right - this.size.width)){
749 | this.position.x += vel * dt;
750 | }
751 | }
752 |
753 | this.lastShoot -= dt;
754 | var shootIdx = actions.indexOf(Controls.Shoot);
755 | if (shootIdx>-1 && this.lastShoot <= 0){
756 | this.lastShoot = this.SHOOT_TIME;
757 | actions.splice(shootIdx, 1);
758 | this.makeShoot();
759 | }
760 |
761 | var s = this.shoots;
762 | var sLen = s.length;
763 | for(var i=0; i< sLen; i++){
764 | if (s[i]){
765 | s[i].update(dt);
766 | }
767 | }
768 | },
769 | draw: function(){
770 | this._super(this.imgs[0]);
771 |
772 | var s = this.shoots;
773 | var sLen = s.length;
774 | for(var i=0; i< sLen; i++){
775 | if (s[i]){
776 | s[i].draw();
777 | }
778 | }
779 | },
780 | collided: function(){
781 | this.onShipHit();
782 | },
783 | destroy: function(){
784 | this.onShipHit = null;
785 |
786 | this.shootImage = null;
787 |
788 | for(var i=0; i< this.shoots.length; i++){
789 | this.shoots[i].destroy();
790 | }
791 | this.shoots = [];
792 |
793 | this.imgs = [];
794 |
795 | this.shield = null;
796 | this.invasion = null;
797 |
798 | this._super();
799 | },
800 | makeShoot: function(){
801 | var self = this;
802 |
803 | var s = new Shoot({
804 | ctx: this.ctx,
805 | x: this.position.x + (this.size.width /2),
806 | y: this.position.y,
807 | dir: -1,
808 | shootImage: this.shootImage,
809 | onDestroy: function(s){
810 | for(var i=0; i -1)
994 | this.makeShoot(arr[i]);
995 | }
996 |
997 | if (this.vMove > 0) this.vMove = 0;
998 |
999 | var cPer = (arrLen * 100) / this.aliensAmm;
1000 | if((this.lastPer - cPer) > 9){
1001 | this.CURR_VEL -= this.VEL_FACTOR;
1002 | this.MOVE_TIME -= this.VEL_FACTOR;
1003 | if (this.MOVE_TIME < 200){
1004 | this.MOVE_TIME = 200;
1005 | }
1006 | this.lastPer = cPer;
1007 | return;
1008 | }
1009 | },
1010 | update: function(dt){
1011 | this.lastMove -= dt;
1012 |
1013 | if (this.lastMove <= 0){
1014 | this.loop();
1015 | this.lastMove = this.MOVE_TIME;
1016 |
1017 | var state = this.state;
1018 |
1019 | var arr = this.aliens;
1020 | var arrLen = arr.length;
1021 | for(var i=0; i< arrLen; i++){
1022 | if (arr[i] !== undefined)
1023 | arr[i].update(dt);
1024 | }
1025 | }
1026 |
1027 | var shoots = this.shoots;
1028 | var shootsLen = shoots.length;
1029 | for(var i=0; i< shootsLen; i++){
1030 | if (shoots[i]){
1031 | shoots[i].update(dt);
1032 | }
1033 | }
1034 | },
1035 | draw: function(){
1036 | var state = this.state;
1037 |
1038 | var arr = this.aliens;
1039 | var arrLen = arr.length;
1040 | for(var i=0; i< arrLen; i++){
1041 | if (arr[i] !== undefined)
1042 | arr[i].draw(state);
1043 | }
1044 |
1045 | var shoots = this.shoots;
1046 | var shootsLen = shoots.length;
1047 | for(var i=0; i< shootsLen; i++){
1048 | shoots[i].draw();
1049 | }
1050 | },
1051 | destroy: function(){
1052 | clearInterval(this.timer);
1053 |
1054 | this.shield = null;
1055 | this.ship = null;
1056 |
1057 | for(var i=0; i< this.shoots.length; i++){
1058 | this.shoots[i].destroy();
1059 | }
1060 | this.shoots = [];
1061 |
1062 | this._super();
1063 | },
1064 | makeShoot: function(alien){
1065 | var shield = this.shield;
1066 | var ship = this.ship;
1067 |
1068 | var self = this;
1069 |
1070 | var s = new Shoot({
1071 | ctx: this.ctx,
1072 | x: alien.position.x + (alien.size.width /2),
1073 | y: alien.position.y,
1074 | dir: 1,
1075 | shootImage: this.shootImage,
1076 | onDestroy: function(s){
1077 | for(var i=0; i (590 - this.size.width))
1155 | this.onWallCollision();
1156 |
1157 | var sY = this.position.y + this.size.height;
1158 | if (sY < 0) this.ship.collided();
1159 | },
1160 | draw: function(state){
1161 | if (!this.destroyed){
1162 | var idx = (state) ? 0: 1;
1163 | this._super(this.images[idx]);
1164 | }
1165 | else {
1166 | this._super(this.destroyedImg[0]);
1167 | this.destroy();
1168 | this.onDestroy(this);
1169 | }
1170 | },
1171 | hasCollision: function(){
1172 | var sX = this.position.x + this.size.width/2;
1173 | var sY = this.position.y + this.size.height*0.8;
1174 |
1175 | function checkCollision(arr){
1176 | if (!arr){
1177 | return false;
1178 | }
1179 |
1180 | var cb = arr;
1181 | var cbLen = cb.length;
1182 |
1183 | for(var i=0; i< cbLen; i++){
1184 | var cbO = cb[i];
1185 |
1186 | var cbL = cbO.position.x;
1187 | var cbT = cbO.position.y;
1188 | var cbR = cbL + cbO.size.width;
1189 | var cbD = cbT + cbO.size.height;
1190 |
1191 | if (sX >= cbL && sX <= cbR && sY >= cbT && sY <= cbD && !cbO.destroyed){
1192 | arr[i].collided(true);
1193 | return true;
1194 | }
1195 | }
1196 |
1197 | return false;
1198 | }
1199 |
1200 | if (checkCollision(this.shield.bricks)) return true;
1201 | if (checkCollision([this.ship])) return true;
1202 | },
1203 | collided: function(){
1204 | this.destroyed = true;
1205 |
1206 | window.camera.shake(3);
1207 |
1208 | window.particles.create([
1209 | this.position.x + this.size.width/2,
1210 | this.position.y + this.size.height/2
1211 | ], 10, this.color);
1212 |
1213 | },
1214 | destroy: function(){
1215 | this._super();
1216 | }
1217 | });
1218 |
1219 |
1220 | var Brick = DrawableElement.extend({
1221 | init: function(options){
1222 | this._super(options);
1223 |
1224 | this.destroyed = false;
1225 | this.value = options.value || 1;
1226 | },
1227 | build: function(){
1228 |
1229 | },
1230 | update: function(dt){
1231 |
1232 | },
1233 | draw: function(){
1234 | if (!this.destroyed){
1235 | this.ctx.beginPath();
1236 | this.ctx.rect(this.position.x, this.position.y, this.size.width, this.size.height);
1237 |
1238 | this.ctx.fillStyle = this.color;
1239 | this.ctx.fill();
1240 | }
1241 | },
1242 | destroy: function(){
1243 | this.destroyed = true;
1244 | }
1245 | });
1246 |
1247 |
1248 | var ShieldBrick = DrawableElement.extend({
1249 | init: function(options){
1250 | this._super(options);
1251 |
1252 | this.state = 0;
1253 | this.imgsState = options.imgsState;
1254 | this.destroyed = false;
1255 | },
1256 | build: function(){
1257 |
1258 | },
1259 | update: function(){
1260 |
1261 | },
1262 | draw: function(){
1263 | if (!this.destroyed){
1264 | this._super(this.imgsState[this.state]);
1265 | }
1266 | },
1267 | collided: function(full){
1268 | window.camera.shake(1);
1269 | window.particles.create([
1270 | this.position.x + this.size.width/2,
1271 | this.position.y + this.size.height/2
1272 | ], 4, this.color);
1273 |
1274 | if (full) this.state = Math.floor((Math.random()*3)+2);
1275 | else this.state++;
1276 |
1277 | if (this.state > 1){
1278 | this.destroyed = true;
1279 | }
1280 | },
1281 | destroy: function(){
1282 | this._super();
1283 | }
1284 | });
1285 |
1286 |
1287 | var Shield = DrawableElement.extend({
1288 | init: function(options){
1289 | this._super(options);
1290 |
1291 | this.imgs = [];
1292 | this.build();
1293 | },
1294 | build: function(){
1295 | this.createImagesStateBricks();
1296 |
1297 | var bSize = this.brickSize;
1298 | var x = this.position.x;
1299 | var y = this.position.y;
1300 | var ctx = this.ctx;
1301 | var color = this.color;
1302 |
1303 | var fernetArr = ImageMapper.Shield();
1304 | var fArrLen = fernetArr.length;
1305 |
1306 | for(var i=0; i< fArrLen; i++){
1307 | var fColLen = fernetArr[i].length;
1308 |
1309 | for(var j=0; j< fColLen; j++){
1310 |
1311 | if (fernetArr[i][j]){
1312 | var b = new ShieldBrick({
1313 | ctx: ctx,
1314 | x: (j * bSize) + x,
1315 | y: (i * bSize) + y,
1316 | width: bSize,
1317 | height: bSize,
1318 | color: color,
1319 | imgsState: this.imgs
1320 | });
1321 |
1322 | this.bricks.push(b);
1323 | }
1324 | }
1325 | }
1326 | },
1327 | update: function(dt){
1328 | var b = this.bricks;
1329 | var bLen = b.length;
1330 |
1331 | for(var i=0; i< bLen; i++){
1332 | if (b[i]){
1333 | b[i].update(dt);
1334 | }
1335 | }
1336 | },
1337 | draw: function(){
1338 | var b = this.bricks;
1339 | if (!b) return;
1340 |
1341 | var bLen = b.length;
1342 |
1343 | for(var i=0; i< bLen; i++){
1344 | if (b[i]){
1345 | b[i].draw();
1346 | }
1347 | }
1348 | },
1349 | destroy: function(){
1350 | var b = this.bricks;
1351 | var bLen = b.length;
1352 | for(var i=0; i< bLen; i++){
1353 | b[i].destroy();
1354 | }
1355 | this.bricks = [];
1356 |
1357 | this._super();
1358 | },
1359 | createImagesStateBricks: function(){
1360 | var opts = {
1361 | width: this.brickSize,
1362 | height: this.brickSize,
1363 | states: [1],
1364 | brickSize: 2,
1365 | color: this.color
1366 | };
1367 |
1368 | var states = ImageMapper.ShieldBrick();
1369 |
1370 | for (var i=0; i< states.length; i++){
1371 | opts.mapper = states[i];
1372 | this.imgs.push(ImageCreator.getImages(opts)[0]);
1373 | }
1374 | }
1375 | });
1376 |
1377 | var Invaders404 = Class.extend({
1378 | init : function(options) {
1379 | this.canvas = null;
1380 | this.ctx = null;
1381 |
1382 | this.loopInterval = 10;
1383 | this.currentDir = [];
1384 |
1385 | this.shield = {};
1386 | this.ship = {};
1387 | this.invasion = {};
1388 |
1389 | this.initCanvas(options.canvasId);
1390 |
1391 | this.onLoose = options.onLoose || function(){};
1392 | this.onWin = options.onWin || function(){};
1393 |
1394 | this.isOnGame = false;
1395 |
1396 | this.boundGameRun = this.gameRun.bind(this);
1397 |
1398 | /* FPS Info */
1399 | this.fps = 0
1400 | this.now = null;
1401 | this.lastUpdate = (new Date) * 1 - 1;
1402 | this.fpsFilter = this.loopInterval;
1403 |
1404 | var self = this;
1405 | var fpsOut = document.getElementById('fps');
1406 | setInterval(function() {
1407 | fpsOut.innerHTML = self.fps.toFixed(1) + "fps";
1408 | }, 1000);
1409 | /* End FPS Info */
1410 | },
1411 | initCanvas : function(canvasId) {
1412 | this.canvas = document.getElementById(canvasId || 'canvas');
1413 | this.ctx = this.canvas.getContext('2d');
1414 | window.particles.init(this.ctx, { w: this.canvas.width, h: this.canvas.height });
1415 | },
1416 | start : function() {
1417 | this.build();
1418 | this.gameRun();
1419 | },
1420 | gameRun: function(){
1421 | if (window.gameTime.tick()) { this.loop(); }
1422 | this.tLoop = window.requestAnimationFrame(this.boundGameRun);
1423 | },
1424 | build : function() {
1425 | var self = this;
1426 |
1427 | this.shield = new Shield({
1428 | ctx : this.ctx,
1429 | x : 70,
1430 | y : 290,
1431 | brickSize : 12,
1432 | color : '#ffffff'
1433 | });
1434 |
1435 | var cnvW = this.canvas.width;
1436 |
1437 | this.ship = new Ship({
1438 | ctx : this.ctx,
1439 | shield : this.shield,
1440 | maxMoveLeft : 5,
1441 | maxMoveRight : cnvW - 10,
1442 | x : ((cnvW - 10) / 2),
1443 | y : 370,
1444 | color : '#1be400',
1445 | onShipHit : function() {
1446 | self.stop();
1447 | self.onLoose();
1448 | }
1449 | });
1450 |
1451 | this.invasion = new Invasion({
1452 | ctx : this.ctx,
1453 | x : 60,
1454 | y : 10,
1455 | shield : this.shield,
1456 | ship : this.ship,
1457 | onAliensClean : function() {
1458 | self.stop();
1459 | self.onWin();
1460 | }
1461 | });
1462 |
1463 | this.ship.invasion = this.invasion;
1464 |
1465 | this.currentDir = [];
1466 |
1467 | this.isOnGame = true;
1468 | this.bindControls();
1469 | },
1470 | loop : function() {
1471 | if (this.isOnGame){
1472 | this.update(window.gameTime.frameTime);
1473 | this.draw();
1474 | }
1475 | },
1476 | update : function(dt) {
1477 | window.camera.update(dt);
1478 | this.shield.update(dt);
1479 | this.ship.update(this.currentDir, dt);
1480 | this.invasion.update(dt);
1481 | window.particles.update(dt);
1482 | },
1483 | draw : function() {
1484 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1485 |
1486 | this.shield.draw();
1487 | this.ship.draw();
1488 | this.invasion.draw();
1489 | window.particles.draw();
1490 |
1491 | /* FPS Info */
1492 | var thisFrameFPS = 1000 / ((this.now = new Date) - this.lastUpdate);
1493 | this.fps += (thisFrameFPS - this.fps) / this.fpsFilter;
1494 | this.lastUpdate = this.now;
1495 | /* End FPS Info */
1496 | },
1497 | bindControls : function(params) {
1498 | var self = this;
1499 | var gameKeys = [Keyboard.Space, Keyboard.Left, Keyboard.Right];
1500 |
1501 | function getAction(code) {
1502 | switch (code) {
1503 | case Keyboard.Space:
1504 | return Controls.Shoot;
1505 | case Keyboard.Left:
1506 | return Controls.Left;
1507 | case Keyboard.Right:
1508 | return Controls.Right;
1509 | }
1510 |
1511 | return null;
1512 | }
1513 |
1514 | document.addEventListener('keydown', function(event) {
1515 | if(self.isOnGame) {
1516 | var key = event.keyCode;
1517 |
1518 | if(gameKeys.indexOf(key) > -1) {
1519 | var dir = getAction(key);
1520 |
1521 | if(self.currentDir.indexOf(dir) === -1)
1522 | self.currentDir.push(dir);
1523 |
1524 | event.stopPropagation();
1525 | event.preventDefault();
1526 | return false;
1527 | }
1528 | }
1529 | });
1530 |
1531 | document.addEventListener('keyup', function(event) {
1532 | if(self.isOnGame) {
1533 | var key = event.keyCode;
1534 |
1535 | var dir = getAction(key);
1536 | var pos = self.currentDir.indexOf(dir);
1537 | if(pos > -1)
1538 | self.currentDir.splice(pos, 1);
1539 | }
1540 | });
1541 | },
1542 | unbindControls : function(params) {
1543 | document.removeEventListener('keydown', function() {});
1544 | document.removeEventListener('keyup', function() {});
1545 | },
1546 | destroy : function() {
1547 | this.shield.destroy();
1548 | this.invasion.destroy();
1549 | this.ship.destroy();
1550 | },
1551 | stop : function() {
1552 | //this.unbindControls();
1553 | this.isOnGame = false;
1554 |
1555 | for(var i = 0; i < this.currentDir.length; i++)
1556 | this.currentDir[i] = null;
1557 |
1558 | this.currentDir = [];
1559 |
1560 | this.destroy();
1561 | },
1562 | drawSplash : function(callback) {
1563 | var ctx = this.ctx,
1564 | cols = this.canvas.height,
1565 | colsL = this.canvas.width,
1566 | colIdx = 0;
1567 |
1568 | function drawColumn(idx, color){
1569 | var size = 20;
1570 | var x = (idx*size)-size;
1571 |
1572 | ctx.save();
1573 | ctx.fillStyle = color;
1574 | ctx.fillRect(x, 0, size, cols);
1575 | ctx.restore();
1576 | }
1577 |
1578 | var loopInterval = this.loopInterval*2;
1579 |
1580 | function doForward(){
1581 | for(var i=0; i<5; i++){
1582 | drawColumn(colIdx+i, "rgba(240,219,79," + (i ? i/10 : 1) + ")");
1583 | }
1584 |
1585 | colIdx++;
1586 |
1587 | if(colIdx < colsL/10)
1588 | setTimeout(doForward, loopInterval);
1589 | else {
1590 | callback();
1591 | //colIdx = colsL/10;
1592 | //doBack();
1593 | }
1594 | }
1595 |
1596 | function doBack(){
1597 | for(var i=5; i>=0; i--){
1598 | drawColumn(colIdx-i, "rgba(0,0,0," + (i ? i/10 : 1) + ")");
1599 | }
1600 |
1601 | colIdx--;
1602 |
1603 | if(colIdx > 0)
1604 | setTimeout(doBack, loopInterval);
1605 | else {
1606 | callback();
1607 | }
1608 | }
1609 |
1610 | doForward();
1611 | }
1612 | });
1613 | /*
1614 | Input.js is MIT-licensed software
1615 | Copyright (c) 2011 Jon Buckley
1616 | */
1617 |
1618 | (function() {
1619 | // Holds all of the physical device to USB enumeration mappings
1620 | var keymapBlob = {
1621 | '45e' : { /* Microsoft */
1622 | '28e' : { /* Xbox 360 controller */
1623 | 'Mac' : {
1624 | 'axes' : {
1625 | 'Left_Stick_X': 0,
1626 | 'Left_Stick_Y': 1,
1627 | 'Right_Stick_X': 2,
1628 | 'Right_Stick_Y': 3,
1629 | 'Left_Trigger_2': [4, -1, 1],
1630 | 'Right_Trigger_2': [5, -1, 1]
1631 | },
1632 | 'buttons' : {
1633 | 'A_Button': 0,
1634 | 'B_Button': 1,
1635 | 'X_Button': 2,
1636 | 'Y_Button': 3,
1637 | 'Left_Trigger_1': 4,
1638 | 'Right_Trigger_1': 5,
1639 | 'Left_Stick_Button': 6,
1640 | 'Right_Stick_Button': 7,
1641 | 'Start_Button': 8,
1642 | 'Back_Button': 9,
1643 | 'Home_Button': 10,
1644 | 'Pad_Up': 11,
1645 | 'Pad_Down': 12,
1646 | 'Pad_Left': 13,
1647 | 'Pad_Right': 14
1648 | }
1649 | },
1650 | "Win": {
1651 | "axes": {
1652 | "Left_Stick_X": 0,
1653 | "Left_Stick_Y": 1,
1654 | "Right_Stick_X": 3,
1655 | "Right_Stick_Y": 4,
1656 | "Pad_Left": [5, 0, -1],
1657 | "Pad_Right": [5, 0, 1],
1658 | "Pad_Up": [6, 0, -1],
1659 | "Pad_Down": [6, 0, 1],
1660 | "Left_Trigger_2": [2, 0, 1],
1661 | "Right_Trigger_2": [2, 0, -1]
1662 | },
1663 | "buttons": {
1664 | "A_Button": 0,
1665 | "B_Button": 1,
1666 | "X_Button": 2,
1667 | "Y_Button": 3,
1668 | "Left_Trigger_1": 4,
1669 | "Right_Trigger_1": 5,
1670 | "Back_Button": 6,
1671 | "Start_Button": 7,
1672 | "Left_Stick_Button": 8,
1673 | "Right_Stick_Button": 9
1674 | }
1675 | }
1676 | }
1677 | },
1678 | "54c": { /* Sony */
1679 | "268": { /* PS3 Controller */
1680 | "Mac": {
1681 | "axes": {
1682 | "Left_Stick_X": 0,
1683 | "Left_Stick_Y": 1,
1684 | "Right_Stick_X": 2,
1685 | "Right_Stick_Y": 3
1686 | },
1687 | "buttons": {
1688 | "Back_Button": 0,
1689 | "Left_Stick_Button": 1,
1690 | "Right_Stick_Button": 2,
1691 | "Start_Button": 3,
1692 | "Pad_Up": 4,
1693 | "Pad_Down": 6,
1694 | "Pad_Right": 5,
1695 | "Pad_Left": 7,
1696 | "Left_Trigger_2": 8,
1697 | "Right_Trigger_2": 9,
1698 | "Left_Trigger_1": 10,
1699 | "Right_Trigger_1": 11,
1700 | "Y_Button": 12,
1701 | "B_Button": 13,
1702 | "A_Button": 14,
1703 | "X_Button": 15,
1704 | "Home_Button": 16
1705 | }
1706 | }
1707 | }
1708 | },
1709 | "46d": { /* Logitech */
1710 | "c242": { /* Chillstream */
1711 | "Win": {
1712 | "axes": {
1713 | "Left_Stick_X": 0,
1714 | "Left_Stick_Y": 1,
1715 | "Right_Stick_Y": 4,
1716 | "Right_Stick_X": 3,
1717 | "Left_Trigger_2": [2, 0, 1],
1718 | "Right_Trigger_2": [2, -1, 0],
1719 | "Pad_Left": [5, -1, 0],
1720 | "Pad_Right": [5, 0, 1],
1721 | "Pad_Up": [6, -1, 0],
1722 | "Pad_Down": [6, 0, 1]
1723 | },
1724 | "buttons": {
1725 | "A_Button": 0,
1726 | "X_Button": 2,
1727 | "B_Button": 1,
1728 | "Y_Button": 3,
1729 | "Left_Trigger_1": 4,
1730 | "Right_Trigger_1": 5,
1731 | "Back_Button": 6,
1732 | "Start_Button": 7,
1733 | "Left_Stick_Button": 8,
1734 | "Right_Stick_Button": 9
1735 | }
1736 | }
1737 | },
1738 | "c216": { /* Dual Action */
1739 | "Mac": {
1740 | "axes": {
1741 | "Left_Stick_X": 1,
1742 | "Left_Stick_Y": 2,
1743 | "Right_Stick_X": 3,
1744 | "Right_Stick_Y": 4,
1745 | "Pad_Left": [1, 0, -1],
1746 | "Pad_Right": [1, 0, 1],
1747 | "Pad_Up": [2, 0, -1],
1748 | "Pad_Down": [2, 0, 1]
1749 | },
1750 | "buttons": {
1751 | "X_Button": 0,
1752 | "A_Button": 1,
1753 | "B_Button": 2,
1754 | "Y_Button": 3,
1755 | "Left_Trigger_1": 4,
1756 | "Right_Trigger_1": 5,
1757 | "Left_Trigger_2": 6,
1758 | "Right_Trigger_2": 7,
1759 | "Back_Button": 8,
1760 | "Start_Button": 9,
1761 | "Left_Stick_Button": 10,
1762 | "Right_Stick_Button": 11
1763 | }
1764 | }
1765 | }
1766 | },
1767 | "40b": {
1768 | "6533": { /* USB 2A4K GamePad */
1769 | "Mac": {
1770 | "axes": {
1771 | "Pad_Left": [0, 0, -1],
1772 | "Pad_Right": [0, 0, 1],
1773 | "Pad_Up": [1, 0, -1],
1774 | "Pad_Down": [1, 0, 1]
1775 | },
1776 | "buttons": {
1777 | "A_Button": 0,
1778 | "B_Button": 1,
1779 | "X_Button": 2,
1780 | "Y_Button": 3
1781 | }
1782 | }
1783 | }
1784 | },
1785 | "Firefox": {
1786 | "Fake Gamepad": {
1787 | "Mac": {
1788 | "axes": {
1789 |
1790 | },
1791 | "buttons": {
1792 | 'A_Button' : 0,
1793 | 'B_Button' : 1,
1794 | 'X_Button' : 2,
1795 | 'Y_Button' : 3,
1796 | 'Pad_Up' : 4,
1797 | 'Pad_Down': 5,
1798 | 'Pad_Left': 6,
1799 | 'Pad_Right': 7
1800 | }
1801 | }
1802 | }
1803 | }
1804 | };
1805 |
1806 | // Our ideal gamepad that we present to the developer
1807 | var ImaginaryGamepad = {
1808 | 'axes' : [
1809 | 'Left_Stick_X',
1810 | 'Left_Stick_Y',
1811 | 'Right_Stick_X',
1812 | 'Right_Stick_Y'
1813 | ],
1814 | 'buttons' : [
1815 | 'A_Button',
1816 | 'B_Button',
1817 | 'X_Button',
1818 | 'Y_Button',
1819 | 'Left_Stick_Button',
1820 | 'Right_Stick_Button',
1821 | 'Start_Button',
1822 | 'Back_Button',
1823 | 'Home_Button',
1824 | 'Pad_Up',
1825 | 'Pad_Down',
1826 | 'Pad_Left',
1827 | 'Pad_Right',
1828 | 'Left_Trigger_1',
1829 | 'Right_Trigger_1',
1830 | 'Left_Trigger_2',
1831 | 'Right_Trigger_2'
1832 | ]
1833 | };
1834 |
1835 | var osList = ['Win', 'Mac', 'Linux'];
1836 | function detectOS() {
1837 | for (var i in osList) {
1838 | if (navigator.platform.indexOf(osList[i]) !== -1) {
1839 | return osList[i];
1840 | }
1841 | }
1842 | return 'Unknown';
1843 | }
1844 |
1845 | function map(value, istart, istop, ostart, ostop) {
1846 | return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
1847 | };
1848 |
1849 | // Map imaginary device action to physical device action
1850 | function mapAxisToAxis(device, keymap, axes, prop) {
1851 | Object.defineProperty(axes, prop, {
1852 | enumerable: true,
1853 | get: function() { return device.axes[keymap.axes[prop]]; }
1854 | });
1855 | }
1856 |
1857 | function mapAxisToButton(device, keymap, axes, prop) {
1858 | Object.defineProperty(axes, prop, {
1859 | enumerable: true,
1860 | get: function() { return 0; }
1861 | });
1862 | }
1863 |
1864 | function mapButtonToButton(device, keymap, buttons, prop) {
1865 | Object.defineProperty(buttons, prop, {
1866 | enumerable: true,
1867 | get: function() { return device.buttons[keymap.buttons[prop]]; }
1868 | });
1869 | }
1870 |
1871 | function mapButtonToAxis(device, keymap, buttons, prop) {
1872 | var transform = keymap.axes[prop] instanceof Array;
1873 |
1874 | Object.defineProperty(buttons, prop, {
1875 | enumerable: true,
1876 | get: function() {
1877 | if (transform) {
1878 | return map(device.axes[keymap.axes[prop][0]], keymap.axes[prop][1], keymap.axes[prop][2], 0, 1);
1879 | } else {
1880 | return device.axes[keymap.axes[prop]];
1881 | }
1882 | }
1883 | });
1884 | }
1885 |
1886 | function mapZero(type, prop) {
1887 | Object.defineProperty(type, prop, {
1888 | enumerable: true,
1889 | get: function() { return 0; }
1890 | });
1891 | }
1892 |
1893 | var Input = window.Input = {};
1894 | var Device = Input.Device = function(domGamepad) {
1895 | if (!domGamepad) {
1896 | throw "You didn't pass a valid gamepad to the constructor";
1897 | }
1898 |
1899 | var device = domGamepad,
1900 | usbVendor = domGamepad.id.split('-')[0],
1901 | usbDevice = domGamepad.id.split('-')[1],
1902 | os = detectOS(),
1903 | keymap = keymapBlob,
1904 | axes = this.axes = {},
1905 | buttons = this.buttons = {};
1906 |
1907 | if (keymap && keymap[usbVendor] && keymap[usbVendor][usbDevice] && keymap[usbVendor][usbDevice][os]) {
1908 | keymap = keymap[usbVendor][usbDevice][os];
1909 | } else {
1910 | throw "A physical device layout for " + usbVendor + "-" + usbDevice + "-" + os + " isn't available";
1911 | }
1912 |
1913 | // Wire the axes and buttons up
1914 | for (var a in ImaginaryGamepad.axes) {
1915 | if (keymap.axes[ImaginaryGamepad.axes[a]] !== undefined) {
1916 | mapAxisToAxis(device, keymap, axes, ImaginaryGamepad.axes[a]);
1917 | } else if (keymap.buttons[ImaginaryGamepad.axes[a]] !== undefined) {
1918 | mapAxisToButton(device, keymap, axes, ImaginaryGamepad.axes[a]);
1919 | } else {
1920 | mapZero(axes, ImaginaryGamepad.axes[a]);
1921 | }
1922 | }
1923 |
1924 | for (var b in ImaginaryGamepad.buttons) {
1925 | if (keymap.buttons[ImaginaryGamepad.buttons[b]] !== undefined) {
1926 | mapButtonToButton(device, keymap, buttons, ImaginaryGamepad.buttons[b]);
1927 | } else if (keymap.axes[ImaginaryGamepad.buttons[b]] !== undefined) {
1928 | mapButtonToAxis(device, keymap, buttons, ImaginaryGamepad.buttons[b]);
1929 | } else {
1930 | mapZero(buttons, ImaginaryGamepad.buttons[b]);
1931 | }
1932 | }
1933 |
1934 | // Add some useful properties from the DOMGamepad object
1935 | Object.defineProperty(this, "connected", {
1936 | enumerable: true,
1937 | get: function() { return device.connected; }
1938 | });
1939 |
1940 | Object.defineProperty(this, "id", {
1941 | enumerable: true,
1942 | get: function() { return device.id; }
1943 | });
1944 |
1945 | Object.defineProperty(this, "index", {
1946 | enumerable: true,
1947 | get: function() { return device.index; }
1948 | });
1949 | };
1950 | }());
1951 |
--------------------------------------------------------------------------------