├── .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 | 54 | FernetJS Invaders 404 - Not Found 55 | 56 |
57 | Jugar! 58 |
59 | [código fuente] 60 |
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 | --------------------------------------------------------------------------------