Breakout={Defaults:{fps:60,stats:false,score:{lives:{initial:3,max:5}},court:{xchunks:30,ychunks:25},ball:{radius:0.3,speed:15,labels:{3:{text:'ready...',fill:'#D82800',stroke:'black',font:'bold 28pt arial'},2:{text:'set..',fill:'#FC9838',stroke:'black',font:'bold 28pt arial'},1:{text:'go!',fill:'#80D010',stroke:'black',font:'bold 28pt arial'}}},paddle:{width:6,height:1,speed:25},color:{background:'rgba(200, 200, 200, 0.5)',foreground:'green',border:'#222',wall:'#333',ball:'black',paddle:'rgb(245,111,37)',score:"#EFD279",highscore:"#AFD775"},state:{initial:'menu',events:[{name:'play',from:'menu',to:'game'},{name:'abandon',from:'game',to:'menu'},{name:'lose',from:'game',to:'menu'}]},keys:[{keys:[Game.KEY.LEFT,Game.KEY.A],mode:'down',action:function(){this.paddle.moveLeft();}},{keys:[Game.KEY.RIGHT,Game.KEY.D],mode:'down',action:function(){this.paddle.moveRight();}},{keys:[Game.KEY.LEFT,Game.KEY.A],action:function(){this.paddle.stopMovingLeft();}},{keys:[Game.KEY.RIGHT,Game.KEY.D],action:function(){this.paddle.stopMovingRight();}},{keys:[Game.KEY.SPACE,Game.KEY.RETURN],state:'menu',action:function(){this.play();}},{keys:[Game.KEY.SPACE,Game.KEY.RETURN],state:'game',action:function(){this.ball.launchNow();}},{key:Game.KEY.ESC,state:'game',action:function(){this.abandon();}},{key:Game.KEY.UP,state:'menu',action:function(){this.nextLevel();}},{key:Game.KEY.DOWN,state:'menu',action:function(){this.prevLevel();}}],sounds:{brick:'/sound/breakout/brick.mp3',paddle:'/sound/breakout/paddle.mp3',go:'/sound/breakout/go.mp3',levelup:'/sound/breakout/levelup.mp3',loselife:'/sound/breakout/loselife.mp3',gameover:'/sound/breakout/gameover.mp3'}},initialize:function(runner,cfg){this.cfg=cfg;this.runner=runner;this.width=runner.width;this.height=runner.height;this.storage=runner.storage();this.color=cfg.color;this.sound=false;this.court=Object.construct(Breakout.Court,this,cfg.court);this.paddle=Object.construct(Breakout.Paddle,this,cfg.paddle);this.ball=Object.construct(Breakout.Ball,this,cfg.ball);this.score=Object.construct(Breakout.Score,this,cfg.score);Game.loadSounds({sounds:cfg.sounds});},onstartup:function(){this.addEvents();this.runner.start();},addEvents:function(){Game.addEvent('prev','click',this.prevLevel.bind(this,false));Game.addEvent('next','click',this.nextLevel.bind(this,false));Game.addEvent('sound','change',this.toggleSound.bind(this,false));Game.addEvent('instructions','touchstart',this.play.bind(this));Game.addEvent(this.runner.canvas,'touchmove',this.ontouchmove.bind(this));Game.addEvent(document.body,'touchmove',function(event){event.preventDefault();});},toggleSound:function(){this.storage.sound=this.sound=!this.sound;},update:function(dt){this.court.update(dt);this.paddle.update(dt);this.ball.update(dt);this.score.update(dt);},draw:function(ctx){ctx.save();ctx.clearRect(0,0,this.width,this.height);ctx.fillStyle=this.color.background;ctx.fillRect(0,0,this.width,this.height);this.court.draw(ctx);this.paddle.draw(ctx);this.ball.draw(ctx);this.score.draw(ctx);ctx.restore();},onresize:function(width,height){this.width=width;this.height=height;this.court.resize();this.paddle.reset();this.ball.reset();},onmenu:function(){this.resetLevel();this.paddle.reset();this.ball.reset();this.refreshDOM();},ongame:function(){this.refreshDOM();this.score.reset();this.ball.reset({launch:true});},onlose:function(){this.playSound('gameover');},onleavegame:function(){this.score.save();this.score.resetLives();},onbeforeabandon:function(){return this.runner.confirm("Abandon game?")},loseBall:function(){this.playSound('loselife');if(this.score.loseLife()) this.lose();else{this.ball.reset({launch:true});}},winLevel:function(){this.playSound('levelup');this.score.gainLife();this.nextLevel(true);this.ball.reset({launch:true});},hitBrick:function(brick){this.playSound('brick');this.court.remove(brick);this.score.increase(brick.score);this.ball.speed+=10*(1-(this.ball.speed/this.ball.maxspeed));if(this.court.empty()) this.winLevel();},resetLevel:function(){this.setLevel();},setLevel:function(level){level=(typeof level=='undefined')?(this.storage.level?parseInt(this.storage.level):0):level;level=level0);},canNextLevel:function(){return this.is('menu')&&(this.level<(Breakout.Levels.length-1));},prevLevel:function(force){if(force||this.canPrevLevel())this.setLevel(this.level-1);},nextLevel:function(force){if(force||this.canNextLevel())this.setLevel(this.level+1);},initCanvas:function(ctx){ctx.fillStyle=this.color.foreground;ctx.strokeStyle=this.color.foreground;ctx.lineWidth=1;this.score.measure(ctx);},refreshDOM:function(){$('instructions').className=Game.ua.hasTouch?'touch':'keyboard';$('instructions').showIf(this.is('menu'));$('prev').toggleClassName('disabled',!this.canPrevLevel());$('next').toggleClassName('disabled',!this.canNextLevel());$('level').update(this.level+1);$('sound').checked=this.sound;},playSound:function(id){if(soundManager&&this.sound){soundManager.play(id);}},ontouchmove:function(ev){if(ev.targetTouches.length==1){this.paddle.place(ev.targetTouches[0].pageX-this.runner.bounds.left-this.paddle.w/2);}},Score:{initialize:function(game,cfg){this.game=game;this.cfg=cfg;this.load();this.reset();},reset:function(){this.set(0);this.resetLives();},set:function(n){this.score=this.vscore=n;this.rerender=true;},increase:function(n){this.score=this.score+n;this.rerender=true;},format:function(n){return("0000000"+n).slice(-7);},load:function(){this.highscore=this.game.storage.highscore?parseInt(this.game.storage.highscore):1000;},save:function(){if(this.score>this.highscore)this.game.storage.highscore=this.highscore=this.score;},resetLives:function(){this.setLives(this.cfg.lives.initial);},setLives:function(n){this.lives=n;this.rerender=true;},gainLife:function(){this.setLives(Math.min(this.cfg.lives.max,this.lives+1));},loseLife:function(){this.setLives(this.lives-1);return(this.lives==0);},update:function(dt){if(this.vscorethis.highscore);ctx.textBaseline="middle";ctx.fillStyle=this.game.color.score;ctx.font=this.scorefont;text=this.format(this.vscore);ctx.fillText(text,0,this.height/2);ctx.fillStyle=ishigh?this.game.color.score:this.game.color.highscore;text="HIGH SCORE: "+this.format(ishigh?this.score:this.highscore);ctx.font=this.highfont;width=ctx.measureText(text).width;ctx.fillText(text,this.width-width,this.height/2);paddle={game:this.game,w:this.game.court.chunk*1.5,h:this.game.court.chunk*2/3} ctx.translate(this.scorewidth+20,(this.height-paddle.h)/2);for(var n=0;n=100)?'#333':'#bbb';ctx.fillStyle=clr;ctx.font="11px Arial";ctx.textAlign='center';ctx.fillText(brick.score,brick.x+brick.w/2,brick.y+brick.h-3);}} ctx.fillStyle=this.game.color.wall;ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(this.wall.top.left,this.wall.top.top);ctx.lineTo(this.wall.top.right,this.wall.top.top);ctx.lineTo(this.wall.top.right,this.wall.right.bottom);ctx.lineTo(this.wall.right.left,this.wall.right.bottom);ctx.lineTo(this.wall.right.left,this.wall.top.bottom);ctx.lineTo(this.wall.left.right,this.wall.top.bottom);ctx.lineTo(this.wall.left.right,this.wall.left.bottom);ctx.lineTo(this.wall.left.left,this.wall.left.bottom);ctx.lineTo(this.wall.top.left,this.wall.top.top);ctx.fill();ctx.stroke();ctx.closePath();},remove:function(brick){brick.hit=true;this.numhits++;this.rerender=true;},empty:function(){return(this.numhits==this.numbricks);}},Ball:{initialize:function(game,cfg){this.game=game;this.cfg=cfg;},reset:function(options){this.radius=this.cfg.radius*this.game.court.chunk;this.speed=this.cfg.speed*this.game.court.chunk;this.maxspeed=this.speed*1.5;this.color=this.game.color.ball;this.moveToPaddle();this.setdir(0,0);this.clearLaunch();this.hitTargets=[this.game.paddle,this.game.court.wall.top,this.game.court.wall.left,this.game.court.wall.right,].concat(this.game.court.bricks);if(options&&options.launch) this.launch();},moveToPaddle:function(){this.setpos(this.game.paddle.left+(this.game.paddle.w/2),this.game.court.bottom-this.game.paddle.h-this.radius);},setpos:function(x,y){this.x=x;this.y=y;Game.Math.bound(this);},setdir:function(dx,dy){var dir=Game.Math.normalize({x:dx,y:dy});this.dx=dir.x;this.dy=dir.y;this.moving=dir.m!=0;},launch:function(){if(!this.moving||this.countdown){this.countdown=(typeof this.countdown=='undefined')||(this.countdown==null)?3:this.countdown-1;if(this.countdown>0){this.label=this.launchLabel(this.countdown);this.delayTimer=setTimeout(this.launch.bind(this),1000);if(this.countdown==1) this.setdir(1,-1);}else{this.clearLaunch();}}},launchNow:function(){if(!this.moving){this.clearLaunch();this.setdir(1,-1);}},launchLabel:function(count){var label=this.cfg.labels[count];var ctx=this.game.runner.front2d;ctx.save();ctx.font=label.font;ctx.fillStyle=label.fill;ctx.strokeStyle=label.stroke;ctx.lineWidth=0.5;var width=ctx.measureText(label.text).width;ctx.restore();label.x=this.game.court.left+(this.game.court.width-width)/2;label.y=this.game.paddle.top-60;return label;},clearLaunch:function(){if(this.delayTimer){clearTimeout(this.delayTimer);this.delayTimer=this.label=this.countdown=null;}},update:function(dt){if(!this.moving) return this.moveToPaddle();var p2=Game.Math.move(this.x,this.y,this.dx*this.speed,this.dy*this.speed,dt);var mCurrent,mClosest=Infinity,point,item,closest=null;for(var n=0;nthis.game.width)||(p2.y>this.game.height)){this.game.loseBall();}else{this.setpos(p2.x,p2.y);this.setdir(p2.dx,p2.dy);}},draw:function(ctx){ctx.fillStyle=this.color;ctx.beginPath();ctx.arc(this.x,this.y,this.radius,0,Game.THREESIXTY,true);ctx.fill();ctx.stroke();ctx.closePath();if(this.label){ctx.font=this.label.font;ctx.fillStyle=this.label.fill;ctx.strokeStyle=this.label.stroke;ctx.lineWidth=0.5;ctx.fillText(this.label.text,this.label.x,this.label.y);ctx.strokeText(this.label.text,this.label.x,this.label.y);}}},Paddle:{initialize:function(game,cfg){this.game=game;this.cfg=cfg;},reset:function(){this.speed=this.cfg.speed*this.game.court.chunk;this.w=this.cfg.width*this.game.court.chunk;this.h=this.cfg.height*this.game.court.chunk;this.minX=this.game.court.left;this.maxX=this.game.court.right-this.w;this.setpos(Game.random(this.minX,this.maxX),this.game.court.bottom-this.h);this.setdir(0);this.rerender=true;},setpos:function(x,y){this.x=x;this.y=y;Game.Math.bound(this);},setdir:function(dx){this.dleft=(dx<0?-dx:0);this.dright=(dx>0?dx:0);},place:function(x){this.setpos(Math.min(this.maxX,Math.max(this.minX,x)),this.y);},update:function(dt){var amount=this.dright-this.dleft;if(amount!=0) this.place(this.x+(amount*dt*this.speed));},draw:function(ctx){if(this.rerender){this.canvas=Game.renderToCanvas(this.w,this.h,this.render.bind(this));this.rerender=false;} ctx.drawImage(this.canvas,this.x,this.y);},render:function(ctx){var gradient=ctx.createLinearGradient(0,this.h,0,0);gradient.addColorStop(0.36,'rgb(245,111,37)');gradient.addColorStop(0.68,'rgb(255,145,63)');gradient.addColorStop(0.84,'rgb(255,174,95)');var r=this.h/2;ctx.fillStyle=gradient;ctx.strokeStyle=this.game.color.border;ctx.beginPath();ctx.moveTo(r,0);ctx.lineTo(this.w-r,0);ctx.arcTo(this.w,0,this.w,r,r);ctx.lineTo(this.w,this.h-r);ctx.arcTo(this.w,this.h,this.w-r,this.h,r);ctx.lineTo(r,this.h);ctx.arcTo(0,this.h,0,this.h-r,r);ctx.lineTo(0,r);ctx.arcTo(0,0,r,0,r);ctx.fill();ctx.stroke();ctx.closePath();},moveLeft:function(){this.dleft=1;},moveRight:function(){this.dright=1;},stopMovingLeft:function(){this.dleft=0;},stopMovingRight:function(){this.dright=0;}}};