function GameStorage(a){this.works=!0;this.tempStorage={};try{if(!localStorage.getItem("version")&&(localStorage.setItem("version",a),!localStorage.getItem("version")))throw"grenade";}catch(b){this.works=!1}this.works?Number(localStorage.getItem("version"))!==a&&(localStorage.clear(),console.log("Storage cleared because of game update to version "+a)):this.setItem("version",a)} GameStorage.prototype.setItem=function(a,b){b=JSON.stringify(b);this.works?localStorage.setItem(a,b):this.tempStorage[a]=b};GameStorage.prototype.removeItem=function(a){this.works?localStorage.removeItem(a):delete this.tempStorage[a]};GameStorage.prototype.getItem=function(a){return this.works?JSON.parse(localStorage.getItem(a)):this.tempStorage[a]?JSON.parse(this.tempStorage[a]):null};Object.deepcopy=function(a){function b(a,c,f,m){var h;if("object"==typeof a){if(null==a)return a;for(h in d)if(d[h]===a)return g.push({resolveTo:h,child:f,i:m}),null;d[c]=a;Object.isArray(a)?f=[]:(f={},f.__proto__=a.__proto__);for(h in a)f[h]=b(a[h],c+"["+h+"]",f,h);e[c]=f}else f=a;return f}var c,d={},e={},g=[];a=b(a,"*");for(c in g){var f=g[c];f&&f.child&&f.i in f.child&&(f.child[f.i]=e[f.resolveTo])}return a};var DEBUG=!0,RIGHT=0,LEFT=1,NOT_FALLING=0,IN_JUMP=1,FALLING=2,GAME_WIDTH=800,GAME_HEIGHT=600,FIRST_LEVEL="level1.js",LEVEL_DIR="levels/",STORAGE_NO_AUDIO="no_audio",STORAGE_LEVEL="last_level",STORAGE_STATE="state";window.addEventListener("load",function(){var a=new GameEngine;DEBUG&&"localhost"===location.host&&window.LevelEditor&&new LevelEditor(a)},!1); function GameEngine(){this.version=.02;this.width=GAME_WIDTH;this.height=GAME_HEIGHT;this.storage=new GameStorage(this.version);this.storage.works||dbg_warn("localStorage not supported. Your game will not be saved.");this.keyboard=new KeyboardManager(this);this.renderer=new GameRenderer(this);this.audio=new AudioManager(!this.storage.getItem(STORAGE_NO_AUDIO));this.audio.works||dbg_warn("Your browser does not support HTML5 audio or ogg/vorbis");this.posY=this.posX=this.viewportY=this.viewportX=null; this.lastTick=Date.now();this.totalDelta=0;this.running=this.drawableObjects=this.blockingObjects=this.triggeringObjects=this.objectMap=this.objects=this.charBitmap=this.vspeed=this.direction=this.canJump=this.fallingState=this.tickCount=null;this.tickFunctionStopped=!0;this.isMoving=null;this.images={};this.drawHooks=this.gameData=null;this.levelFile=this.storage.getItem(STORAGE_LEVEL);var a=this;document.getElementById("mute_button").addEventListener("click",function(){a.toggleMute()},!1);document.getElementById("reset_save").addEventListener("click", function(){a.nextLevel(FIRST_LEVEL)},!1);this.levelFile||(this.levelFile=FIRST_LEVEL);this.loadLevel(this.levelFile)}GameEngine.prototype.nextLevel=function(a){this.levelFile=a;this.storage.setItem(STORAGE_LEVEL,a);this.storage.removeItem(STORAGE_STATE);this.loadLevel(a)};GameEngine.prototype.loadLevel=function(a){var b=this;this.running=!1;http_get(LEVEL_DIR+a+"?"+Math.random(),function(a){a=eval(a);if(!a)throw"level not found";b.level=a;b.loadResources(a)})}; GameEngine.prototype.loadResources=function(a){function b(){g++;return function(){f++;h.renderer.drawLoadingScreen(f,g);g===f&&h.start()}}function c(){throw"loading a resource failed. Will not start";}var d=a.resourceDir,e=Object.keys(a.images),g=0,f=0,h=this;this.audio.path=a.musicDir;this.level=a;this.audio.preload(this.level.jumpMusic1,b(),c);this.audio.preload(this.level.jumpMusic2,b(),c);e.forEach(function(e){var g=a.images[e],f=new Image;h.images[e]=f;f.onload=b();f.onerror=c;f.src=d+g});this.renderer.drawLoadingScreen(0, g)};GameEngine.prototype.start=function(){this.charBitmap=Bitmap.fromImage(this.images.charHitmap);this.dead=!1;this.audio.play(this.level.backgroundMusic,!0,!0);this.restart();this.running=!0;this.tickFunctionStopped&&this.doTick(this)}; GameEngine.prototype.restart=function(){this.viewportX=this.level.startViewport.x;this.viewportY=this.level.startViewport.y;this.posX=this.level.startPosition.x;this.posY=this.level.startPosition.y;this.fallingState=FALLING;this.direction=RIGHT;this.canJump=this.isMoving=this.dead=!1;this.vspeed=0;this.audio.stop(this.level.deathMusic);this.tickCount=0;this.gameData={};this.drawHooks=[];this.loadObjects();var a=this.storage.getItem(STORAGE_STATE);a&&this.level.loadState(this,a);this.level.init(this)}; GameEngine.prototype.saveState=function(a){this.storage.setItem(STORAGE_STATE,a)}; GameEngine.prototype.loadObjects=function(){var a=this,b=Object.deepcopy(this.level.objects);this.objects=[];this.objectMap={};this.triggeringObjects=[];this.blockingObjects=[];this.drawableObjects=[];b=b.concatMap(function(a){return cartesianProductOnObjects(a.position,["x","y"]).map(function(b){var c=Object.deepcopy(a);c.x=b.x;c.y=b.y;return c})});var c={};b.forEach(function(b){b=a.addObject(b,c);b.init&&b.init(a)});b=this.drawableObjects.partition(function(a){return a.dynamic});this.drawableObjects= b[0];this.renderer.loadBackground(b[1]);this.renderer.loadForeground([])}; GameEngine.prototype.addObject=function(a,b){var c=a.dynamic||!!a.id,d=a.shape,e=a.trigger,g=void 0,f=void 0,h=void 0,l=void 0,k="x y dynamic trigger image blocking killing id tickFunction zIndex position shape retrigger init".split(" ");b=b||{};Object.keys(a).deleteList(k).length&&(console.log(Object.keys(a).deleteList(k)),dbg_assert(!1,"Unkown properties"));a.image&&(g=this.images[a.image],dbg_assert(g,"invalid image id"),f=g.width,h=g.height);dbg_assert(!a.blocking||!e,"an object cannot block and have a trigger at the same time"); a.killing&&(dbg_assert(!e,"an object cannot kill and have a trigger at the same time"),dbg_assert(!a.blocking,"an object cannot kill and block at the same time"),e=this.die.bind(this));void 0===d&&g&&(e||a.blocking||c)&&(b[a.image]?d=b[a.image]:b[a.image]=d=new AutoShape(g));d&&(f=d.width,h=d.height,l=d.getBitmap());b={x:a.x,y:a.y,width:f,height:h,dynamic:c,visible:!0,image:g,bitmap:l,trigger:e,zIndex:a.zIndex||0,retrigger:!!a.retrigger,tickFunction:a.tickFunction,init:a.init};e&&(dbg_assert(e instanceof Function,"trigger has to be a function"),dbg_assert(d,"objects that kill or have a trigger require a shape"),this.triggeringObjects.push(b));a.image&&this.drawableObjects.push(b);a.blocking&&(dbg_assert(d,"objects that block require a shape"),this.blockingObjects.push(b));this.objects.push(b);a.id&&(dbg_assert(!this.objectMap[a.id],"id used twice"),this.objectMap[a.id]=b);return b}; GameEngine.prototype.removeObject=function(a){this.objects=this.objects.delete(a);this.blockingObjects=this.blockingObjects.delete(a);this.drawableObjects=this.drawableObjects.delete(a);this.triggeringObjects=this.triggeringObjects.delete(a);a.id&&delete this.objectMap[a.id]};GameEngine.prototype.removeObjectById=function(a){(a=this.objectMap[a])&&this.removeObject(a)};GameEngine.prototype.toggleMute=function(){this.audio.toggleMute();this.audio.muted?this.storage.setItem(STORAGE_NO_AUDIO,1):this.storage.removeItem(STORAGE_NO_AUDIO)}; GameEngine.prototype.die=function(){this.dead||(this.audio.play(this.level.deathMusic,!1,!0,.3),this.dead=!0)};GameEngine.prototype.crush=function(){console.log("death by crushing");this.die()}; GameEngine.prototype.doTick=function doTick(a){a.tickFunctionStopped=!1;if(a.running){var c=a.level,d=Date.now();a.totalDelta+=d-a.lastTick;a.lastTick=d;500<=a.totalDelta&&(a.totalDelta=0);for(;a.totalDelta>=c.physics.timePerTick;)a.tick(a),a.totalDelta-=c.physics.timePerTick;a.renderer.redraw();a.drawHooks.forEach(function(c){c.call(a.level,a)});requestAnimationFrame(function(){doTick(a)})}else a.tickFunctionStopped=!0}; GameEngine.prototype.tick=function(a){var b=a.level.physics,c=a.keyboard.keyWasPressed;a.level.tickFunction(a);a.objects.forEach(function(b){b.tickFunction&&b.tickFunction.call(b,a);if(b.bitmap&&b.trigger){var c=a.characterCollision(b.bitmap,b.x,b.y);!c||!b.retrigger&&b.triggered||b.trigger.call(b,a);b.triggered=c}});a.tickCount++;a.dead||(!c[KEY_LEFT]!==!c[KEY_RIGHT]?(a.isMoving=!0,c[KEY_LEFT]?(a.moveCharacterRight(-b.moveSpeed),a.direction=LEFT):c[KEY_RIGHT]&&(a.moveCharacterRight(b.moveSpeed), a.direction=RIGHT)):a.isMoving=!1,a.fallingState===NOT_FALLING?(c[KEY_JUMP]&&(a.audio.play(a.level.jumpMusic1,!1,!1,.6),a.fallingState=IN_JUMP,a.canJump=!0,a.vspeed=b.jumpInitialSpeed,a.jumpTicks=b.jumpTicks),a.moveCharacterDown(1)||(a.fallingState=FALLING,a.canJump=!0)):(a.fallingState===IN_JUMP?(a.vspeed+=b.jumpGravity,a.jumpTicks--,c[KEY_JUMP]&&0!==a.jumpTicks||(a.fallingState=FALLING)):(a.canJump&&c[KEY_JUMP]&&(a.audio.play(a.level.jumpMusic2,!1,!1,.6),a.fallingState=IN_JUMP,a.canJump=!1,a.vspeed= b.jumpInitialSpeed/1.5,a.jumpTicks=b.jumpTicks),a.vspeed=a.vspeedthis.posX+this.level.characterWidth||c>this.posY+this.level.characterHeight||b+a.widthf.readyState?f.addEventListener("loadedmetadata",function(){f.currentTime=e}): f.currentTime=e,f.loop=b,f.play())}};AudioManager.prototype.preload=function(a,b,c){if(this.works&&!this.muted)if(this.playing.find(Function.byIndex("file",a)))b&&setTimeout(b,0);else{var d=new Audio(this.path+a);d.muted=this.muted;b&&(d.addEventListener("canplaythrough",b),c&&d.addEventListener("error",c));d.load();this.playing.push({audio:d,file:a});return d}else setTimeout(b,0)}; AudioManager.prototype.stop=function(a){function b(a){a.audio.pause()}this.works&&this.playing.filter(Function.byIndex("file",a)).forEach(b)};AudioManager.prototype.toggleMute=function(){function a(a){a.audio.muted=b}if(this.works){var b=this.muted=!this.muted;this.playing.forEach(a)}if(!this.muted){var c=this;this.playQueue.forEach(function(a){a[1][4]=(Date.now()-a[0])/1E3;c.play.apply(c,a[1])});this.playQueue=[]}};function Bitmap(a,b){this.width=a;this.height=b;this.count=a*b;this.data=new Uint8Array(this.count)}Bitmap.fromImage=function(a){var b=a.width,c=a.height,d=new Bitmap(b,c),e=document.createElement("canvas"),g=e.getContext("2d");e.width=b;e.height=c;g.clearRect(0,0,b,c);g.drawImage(a,0,0);a=g.getImageData(0,0,b,c).data;for(b=0;bb+d.width||a.y>c+d.height||a.x+a.bitmap.width=b.viewportX&&c.x< b.viewportX+b.width&&c.y+c.height>=b.viewportY&&c.y>1,100);this.context.fillText(a+" out of "+b,this.game.width>>1,140)};Math.sign=function(a){return(0a)};Math.roundInfinity=function(a){return 0a-b)};Math.triangle=function(a){return function(b){b/=a;return-1+4*Math.abs(b+.25-Math.floor(b+.75))}};Math.rectangle=function(a){var b=Math.triangle(a);return function(a){return Math.sign(b(a))}};Math.compare=function(a,b){return Math.sign(a-b)}; Array.prototype.findIndex=function(a){var b;this.some(function(c,d){if(a(c))return b=d,!0});return b};Array.prototype.find=function(a){a=this.findIndex(a);if(void 0!==a)return this[a]};Array.prototype.delete=function(a){a=this.indexOf(a);return-1!==a?this.slice(0,a).concat(this.slice(a+1)):this};Array.prototype.concatMap=function(a){var b=[];this.forEach(function(c){b=b.concat(a(c))});return b};Array.prototype.deleteList=function(a){return this.filter(function(b){return-1===a.indexOf(b)})}; Array.replicate=function(a,b){for(var c=[],d=0;d