-
Notifications
You must be signed in to change notification settings - Fork 0
/
jstetris.min.js
1 lines (1 loc) · 35.1 KB
/
jstetris.min.js
1
class Sprite{constructor(scene,dx,dy,w,h,sx,sy){this.scene=scene,this.context=scene.context,this.x=dx,this.y=dy,this.width=w,this.height=h,this.textureX=sx,this.textureY=sy}hide(){this.hidden=!0}show(){this.hidden=!1}draw(){this.hidden||this.context.drawImage(RESOURCE.sprites,this.textureX,this.textureY,this.width,this.height,this.x,this.y,this.width,this.height)}}class Text extends Sprite{static MAP=["ABCDEFGHIJKLMNOPQRSTUVWXYZ.-x ","0123456789_suha!"];static nextLetter(letter){var letterIndex=Text.MAP[0].indexOf(letter);if(letterIndex>=0)return letterIndex=(letterIndex+1)%Text.MAP[0].length,this.MAP[0][letterIndex]}static previousLetter(letter){var letterIndex=Text.MAP[0].indexOf(letter);if(letterIndex>=0)return(letterIndex-=1)<0&&(letterIndex=Text.MAP[0].length-1),this.MAP[0][letterIndex]}constructor(scene,text,x,y,style){super(scene,x,y),this.text=text,this.style=style||"left",this.flashCtr=0}getLetterCoordinates(letter){for(var i=0;i<Text.MAP.length;i++){var letterIndex=Text.MAP[i].indexOf(letter);if(letterIndex>-1)return{x:8*letterIndex,y:8*i}}}draw(){if(!this.hidden&&this.text){if(this.flashCtr<16)for(var context=this.context,i=0;i<this.text.length;i++){var letterCoords=this.getLetterCoordinates(this.text[i]),alignX=0;"right"==this.style&&(alignX=8*(this.text.length-1)),context.drawImage(RESOURCE.sprites,160+letterCoords.x,letterCoords.y,8,8,this.x+8*i-alignX,this.y,8,8),"dotted"==this.style?context.drawImage(RESOURCE.sprites,240,8,8,8,this.x+8*i,this.y+8,8,8):"underline"==this.style&&context.drawImage(RESOURCE.sprites,256,8,8,8,this.x+8*i,this.y+8,8,8)}this.flash?this.flashCtr=(this.flashCtr+1)%32:this.flashCtr=0}}}class Board extends Sprite{static HEIGHT=18;static WIDTH=10;constructor(context,gameType,level,high){super(context,0,0,160,144),this.gameType=gameType,this.level=level,this.high=high,this.grid=[];for(var y=0;y<18;y++)this.addBlankRow();"B"==this.gameType&&this.randomFill(),this.clearFlashTicks=0}addBlankRow(){this.grid.unshift([0,0,0,0,0,0,0,0,0,0])}randomFill(){for(var tiles=[0,20,26,27,28,29,30,31,32,33],y=0;y<2*this.high;y++){for(var hasEmpty=!1,x=0;x<Board.WIDTH;x++){var tile=tiles[Math.max(Math.floor(9*Math.random())-2,0)];this.grid[Board.HEIGHT-1-y][x]=tile,tile||(hasEmpty=!0)}hasEmpty||y--}}curtainCover(row){for(var x=0;x<this.grid[row].length;x++)this.grid[row][x]=32}collide(piece){var collision=!1,pieceOrigin=piece.tileOrigin;return piece.tiles.forEach(t=>{var cell=Vector.add(pieceOrigin,t);if(!(cell.y<0))try{this.grid[cell.y][cell.x]?collision=!0:(cell.x<0||cell.x>9)&&(collision=!0)}catch(ex){collision=!0}}),collision}lock(piece){for(var pieceOrigin=piece.tileOrigin,i=0;i<piece.tiles.length;i++){var t=piece.tiles[i],cell=Vector.add(pieceOrigin,t);if(cell.y<0||this.grid[cell.y][cell.x])return-1;this.grid[cell.y][cell.x]=t.t}for(var clearRows=[],y=0;y<Board.HEIGHT;y++){for(var clear=!0,x=0;x<Board.WIDTH;x++)if(!this.grid[y][x]){clear=!1;break}clear&&clearRows.push(y)}return clearRows.length?(this.clearFlashTicks=0,this.clearing=clearRows):delete this.clearing,clearRows}clearRows(rows){for(var r=0;r<rows.length;r++){var y=rows[r];this.grid.splice(y,1),this.addBlankRow()}}draw(currentPiece){var boardSpriteY="A"==this.gameType?432:288;this.context.drawImage(RESOURCE.sprites,160,boardSpriteY,this.width,this.height,0,0,this.width,this.height),currentPiece.draw();for(var y=0;y<this.grid.length;y++)for(var x=0;x<this.grid[y].length;x++){var tile=this.grid[y][x];tile&&this.context.drawImage(RESOURCE.sprites,8*tile,16,8,8,8*x+16,8*y,8,8)}if(this.clearing&&(this.clearFlashTicks++,this.clearFlashTicks<77&&!(Math.floor(this.clearFlashTicks/10)%2)))for(var r=0;r<this.clearing.length;r++){var y=this.clearing[r];this.clearFlashTicks>=60?this.context.fillStyle="#80832c":this.context.fillStyle="#5f7541",this.context.fillRect(16,8*y,80,8)}}}class Tetromino extends Sprite{static ROT_0=[[1,0],[0,1]];static ROT_90=[[0,-1],[1,0]];static ROT_180=[[-1,0],[0,-1]];static ROT_270=[[0,1],[-1,0]];constructor(scene,x,y){super(scene.context,x,y),this.scene=scene,this.context=scene.context,this.x=x,this.y=y,this.rotationIndex=0,this.tiles=Array.from(this.constructor.SPAWN_TILES)}get origin(){return{x:this.x,y:this.y}}set tileOrigin(coord){this.x=8*coord.x,this.y=8*coord.y}get tileOrigin(){return{x:Math.round(this.x/8),y:Math.round(this.y/8)}}rotate(clockwise,skipCollision){var rotations=this.constructor.ROTATIONS,spawnTiles=this.constructor.SPAWN_TILES;clockwise?this.rotationIndex=(this.rotationIndex+1)%rotations.length:!this.rotationIndex--&&(this.rotationIndex=rotations.length-1);for(var rotation=rotations[this.rotationIndex],i=0;i<spawnTiles.length;i++)this.tiles[i]={x:rotation[0][0]*spawnTiles[i].x+rotation[0][1]*spawnTiles[i].y,y:rotation[1][0]*spawnTiles[i].x+rotation[1][1]*spawnTiles[i].y,t:spawnTiles[i].t};var board=this.scene.board;!skipCollision&&board.collide(this)?this.rotate(!clockwise,!0):skipCollision||Sound.forcePlay("PieceRotate")}move(direction,skipCollision){this.x+=8*direction.x;var board=this.scene.board;!skipCollision&&board.collide(this)?this.move(Vector.inverse(direction),!0):skipCollision||Sound.forcePlay("PieceMove")}fall(){var board=this.scene.board;return this.y+=8,!!board.collide(this)&&(this.y-=8,board.lock(this))}draw(){this.tiles.forEach(t=>{var tileCoords={x:this.origin.x+8*t.x+16,y:this.origin.y+8*t.y};this.context.drawImage(RESOURCE.sprites,8*t.t,16,8,8,tileCoords.x,tileCoords.y,8,8)})}}class RhodeIslandZ extends Tetromino{static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_90];static SPAWN_TILES=[{x:-1,y:0,t:29},{x:0,y:0,t:29},{x:0,y:1,t:29},{x:1,y:1,t:29}]}class ClevelandZ extends Tetromino{static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_90];static SPAWN_TILES=[{x:1,y:0,t:31},{x:0,y:0,t:31},{x:0,y:1,t:31},{x:-1,y:1,t:31}]}class Teewee extends Tetromino{static SPAWN_TILES=[{x:-1,y:0,t:30},{x:0,y:0,t:30},{x:1,y:0,t:30},{x:0,y:1,t:30}];static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_270,Tetromino.ROT_180,Tetromino.ROT_90]}class Smashboy extends Tetromino{static SPAWN_TILES=[{x:0,y:0,t:28},{x:0,y:1,t:28},{x:1,y:1,t:28},{x:1,y:0,t:28}];static ROTATIONS=[Tetromino.ROT_0]}class BlueRicky extends Tetromino{static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_270,Tetromino.ROT_180,Tetromino.ROT_90];static SPAWN_TILES=[{x:-1,y:0,t:26},{x:0,y:0,t:26},{x:1,y:0,t:26},{x:1,y:1,t:26}]}class OrangeRicky extends Tetromino{static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_270,Tetromino.ROT_180,Tetromino.ROT_90];static SPAWN_TILES=[{x:-1,y:1,t:27},{x:0,y:0,t:27},{x:1,y:0,t:27},{x:-1,y:0,t:27}]}class Hero extends Tetromino{static ROTATIONS=[Tetromino.ROT_0,Tetromino.ROT_270];static SPAWN_TILES=[{x:-1,y:0,t:20},{x:0,y:0,t:21},{x:1,y:0,t:21},{x:2,y:0,t:22}];get isHorizontal(){return!this.rotationIndex}rotate(clockwise,skip){Tetromino.prototype.rotate.call(this,clockwise,skip);for(var i=0;i<this.tiles.length;i++)this.tiles[i].t=Hero.SPAWN_TILES[i].t+(this.isHorizontal?0:3)}}class Input{static lastKey=null;static buffer=[];static keyState={};static reset(){Input.buffer=[]}static onKeyDown(e){return Input.keyDown||(Input.keyPress=e.keyCode),Input.keyState[""+e.keyCode]=e.keyCode,Input.lastKey=e.keyCode,e.keyCode>=37&&e.keyCode<=40?(e.preventDefault(),!1):32==e.keyCode?(pauseGame=!pauseGame,e.preventDefault(),!1):70==e.keyCode?(pauseGame=!0,SceneManager.update(),e.preventDefault(),!1):void(Input.keyDown=!0)}static isKeyDown(key){return Input.keyState[""+key]}static onKeyUp(e){delete Input.keyState[e.keyCode],delete Input.lastKey,Input.keyDown=!1}static watch(){Input.buffer.unshift(Input.lastKey),2==Input.buffer.length&&Input.buffer.pop()}static readKeyPress(){var k=this.keyPress;return delete this.keyPress,k}static readBuffer(){return 1==Input.buffer.length?Input.buffer[0]:null}}document.onkeydown=Input.onKeyDown,document.onkeyup=Input.onKeyUp;class Sound{static mute=!1;static initialize(cb){var AudioContext=window.AudioContext||window.webkitAudioContext;this.context=new AudioContext,this.loadSound("res/sfx_lo.ogg").then(buffer=>{this.sounds=buffer,cb.call(this)})}static playing={};static sfx={intro:{start:.743,end:39.364},Atype:{start:39.804,end:78.414},Btype:{start:78.623,end:113.982},Ctype:{start:114.423,end:152.781},EnterScoreIntro:{start:153.076,end:154.284},EnterScore:{start:154.284,end:180.024},MenuBeep:{start:180.345,end:180.484},MenuConfirm:{start:180.712,end:180.766},PieceMove:{start:182.319,end:182.366},PieceRotate:{start:181.056,end:181.365},PieceLock:{start:181.773,end:181.986},LevelUp:{start:191.134,end:191.688},RowClear:{start:182.677,end:183.732},Tetris:{start:189.925,end:190.869},RowDrop:{start:184.049,end:184.292},Lose:{start:184.599,end:187.157},Win:{start:187.679,end:188.61},PauseGame:{start:188.971,end:189.465},BWin0:{start:192.374,end:195.605},BWin1:{start:197,end:200},BWin2:{start:201.27,end:204.525},BWin3:{start:205.5,end:208.8},BWin4:{start:209,end:216},BWin5:{start:216.818,end:229.785},Rocket:{start:229.8,end:271.3}};static musicType="A";static playBGMusic(type){(type=type||this.musicType)!=this.musicType&&this.stopBGMusic(),"OFF"!=type?(this.musicType=type,this.playLoop(this.musicType+"type")):this.musicType=null}static stopBGMusic(){"OFF"==this.musicType&&null!=this.musicType||this.stop(this.musicType+"type")}static playLoop(fx){if(!this.mute&&this.sfx[fx]&&!this.sfx[fx].source){this.playing[fx]=!0;var source=this.context.createBufferSource();source.buffer=this.sounds,source.loop=!0;var loop=this.sfx[fx];return source.loopStart=loop.start,source.loopEnd=loop.end,source.connect(this.context.destination),source.start(0,loop.start),this.sfx[fx].source=source,source.addEventListener("ended",()=>{delete this.sfx[fx].source,delete this.playing[fx]}),source}}static forcePlay(fx){var source=this.sfx[fx].source;source?(source.addEventListener("ended",()=>{this.playOnce(fx)}),source.stop()):this.playOnce(fx)}static playOnce(fx,cb){if(!this.mute&&this.sfx[fx]&&!this.sfx[fx].source){var source=this.context.createBufferSource();source.buffer=this.sounds;var clip=this.sfx[fx];return source.connect(this.context.destination),source.start(0,clip.start,clip.end-clip.start),this.sfx[fx].playing=!0,this.sfx[fx].source=source,source.addEventListener("ended",()=>{this.sfx[fx].source.stopped||cb&&cb.call(this),delete this.sfx[fx].source}),source}}static stop(fx){this.sfx[fx]&&this.sfx[fx].source&&(this.sfx[fx].source.stopped=!0,this.sfx[fx].source.stop())}static stopAll(){for(var fx in this.playing)this.stop(fx)}static resume(){this.context&&this.context.resume()}static suspend(){this.context&&this.context.suspend()}static loadSound(url){return new Promise((resolve,reject)=>{var request=new XMLHttpRequest;request.open("GET",url,!0),request.responseType="arraybuffer",request.onload=()=>{this.context.decodeAudioData(request.response,(function(buffer){resolve(buffer)}))},request.send()})}}class Vector{static ZERO={x:0,y:0};static LEFT={x:-1,y:0};static RIGHT={x:1,y:0};static UP={x:0,y:-1};static DOWN={x:0,y:1};static add(t1,t2){return{x:t1.x+t2.x,y:t1.y+t2.y}}static distance(t1,t2){return Math.sqrt(Math.pow(t1.x-t2.x,2)+Math.pow(t1.y-t2.y,2))}static inverse(v){return{x:-v.x,y:-v.y}}static clone(v){return{x:v.x,y:v.y}}static equals(v1,v2){return v1.x==v2.x&&v1.y==v2.y}}class Timer{constructor(){}start(ticks,callback,wait){this.wait=wait,this.originalTicks=ticks,this.ticks=ticks,this.callback=callback,ticks<=0&&(this.ticks=0,this.callback.call(this))}reset(ticks){this.ticks=ticks||this.originalTicks}stop(){this.ticks=0}tick(){return this.ticks>0&&(this.ticks--,0==this.ticks&&this.callback&&this.callback.call(this)),this.wait||this.ticks>0}}class Scene{constructor(context){this.context=context}tick(){}draw(){this.context.clearRect(0,0,SCREEN.width,SCREEN.height)}}class CreditsScene extends Scene{constructor(context){super(context)}tick(){var keyPress;13!=Input.readKeyPress()||SceneManager.replaceScene(new TitleScene(this.context))}draw(){this.context.drawImage(RESOURCE.sprites,0,0,160,144,0,0,160,144)}}class TitleScene extends Scene{constructor(context){super(context),this.soundLoaded=!1,Sound.initialize(()=>{this.soundLoaded=!0,Sound.playOnce("intro")}),this.arrow=new Sprite(this,8,112,8,8,272,8)}tick(){var keyPress;if(13==Input.readKeyPress()&&this.soundLoaded)return Sound.stop("intro"),void SceneManager.replaceScene(SceneManager.GameMenuScene)}draw(){this.context.drawImage(RESOURCE.sprites,0,144,160,144,0,0,160,144),this.arrow.draw()}}class GameMenuScene extends Scene{constructor(context){super(context),this.gameType="A",this.gameTypeFlashCtr=0,this.musicTypeFlashCtr=0,this.menuItem=0,this.typeGame=new Text(this,"A-TYPE",24,40),this.typeMusic=new Text(this,"A-TYPE",24,96)}tick(){Sound.playBGMusic(),Scene.prototype.tick.call(this);var keyPress=Input.readKeyPress();if(0==this.menuItem){var oldGameType=this.gameType;this.gameTypeFlashCtr=(this.gameTypeFlashCtr+1)%32,39==keyPress||37==keyPress?"A"==this.gameType?(this.gameType="B",this.typeGame.x=88):(this.gameType="A",this.typeGame.x=24):65==keyPress&&(this.menuItem=1,this.gameTypeFlashCtr=0),oldGameType!=this.gameType&&Sound.playOnce("MenuBeep")}else this.musicTypeFlashCtr=(this.musicTypeFlashCtr+1)%32,39==keyPress?"A"==Sound.musicType?(Sound.playBGMusic("B"),this.typeMusic.x=88):"C"==Sound.musicType&&(Sound.playBGMusic("OFF"),this.typeMusic.x=96):37==keyPress?"B"==Sound.musicType?(Sound.playBGMusic("A"),this.typeMusic.x=24):null==Sound.musicType&&(Sound.playBGMusic("C"),this.typeMusic.x=24):40==keyPress?"A"==Sound.musicType?(Sound.playBGMusic("C"),this.typeMusic.y=112):"B"==Sound.musicType&&(Sound.playBGMusic("OFF"),this.typeMusic.x=96,this.typeMusic.y=112):38==keyPress?"C"==Sound.musicType?(Sound.playBGMusic("A"),this.typeMusic.y=96):null==Sound.musicType&&(Sound.playBGMusic("B"),this.typeMusic.x=88,this.typeMusic.y=96):83==keyPress?this.menuItem=0:65==keyPress&&(keyPress=13);13==keyPress&&(this.menuItem=0,Sound.stopBGMusic(),Sound.playOnce("MenuConfirm"),SceneManager.pushScene(SceneManager[this.gameType+"LevelSelectScene"]))}draw(){this.context.drawImage(RESOURCE.sprites,0,288,160,144,0,0,160,144),0==this.menuItem?(this.typeGame.text=this.gameType+"-TYPE",this.gameTypeFlashCtr<16&&this.typeGame.draw(),this.typeMusic.draw()):(Sound.musicType?this.typeMusic.text=Sound.musicType+"-TYPE":this.typeMusic.text="OFF",this.musicTypeFlashCtr<16&&this.typeMusic.draw(),this.typeGame.draw())}}class LevelSelectScene extends Scene{constructor(context,type){super(context),this.type=type,this.highSpriteOffsetX=104,this.highSpriteOffsetY=48,this.highText=new Text(this,"0",this.highSpriteOffsetX,this.highSpriteOffsetY),this.highFlashCtr=0,this.high=0,"A"==type?this.highText.hide():this.highText.show(),this.levelSpriteOffsetX=8*("A"==type?5:2),this.levelSpriteOffsetY=48,this.levelText=new Text(this,"0",this.levelSpriteOffsetX,this.levelSpriteOffsetY),this.levelFlashCtr=0,this.level=0,this.menuItem=0,this.enterScore=0,this.nameTexts=[new Text(this,"",32,104),new Text(this,"",32,112),new Text(this,"",32,120)],this.scoreTexts=[new Text(this,"",136,104,"right"),new Text(this,"",136,112,"right"),new Text(this,"",136,120,"right")],this.cursorBlink=0,this.cursorLocation=0}tick(){this.enterScore&&!this.musicStarted?(this.musicStarted=!0,Sound.stopBGMusic(),Sound.playOnce("EnterScoreIntro",()=>{Sound.playLoop("EnterScore")})):this.enterScore?this.enterScore&&(this.cursorBlink=(this.cursorBlink+1)%14):Sound.playBGMusic(),Scene.prototype.tick.call(this);var keyPress=Input.readKeyPress();if(this.enterScore){var name=TOP_SCORES[this.type][this.enterScore-1].name,currentLetter=name[this.cursorLocation];40==keyPress?(Sound.forcePlay("MenuBeep"),currentLetter=Text.previousLetter(currentLetter),TOP_SCORES[this.type][this.enterScore-1].name=name.substr(0,this.cursorLocation)+currentLetter+name.substr(this.cursorLocation+1)):38==keyPress?(Sound.forcePlay("MenuBeep"),currentLetter=Text.nextLetter(currentLetter),TOP_SCORES[this.type][this.enterScore-1].name=name.substr(0,this.cursorLocation)+currentLetter+name.substr(this.cursorLocation+1)):65==keyPress?this.cursorLocation<5?(this.cursorLocation++,Sound.playOnce("MenuConfirm"),this.cursorLocation==name.length&&(TOP_SCORES[this.type][this.enterScore-1].name+="A")):keyPress=13:83==keyPress&&this.cursorLocation>0&&this.cursorLocation--,13==keyPress&&(Sound.stop("EnterScoreIntro"),Sound.stop("EnterScore"),this.enterScore=!1,this.musicStarted=!1)}else{if(this.cursorLocation=0,this.cursorBlink=0,0==this.menuItem){this.levelFlashCtr=(this.levelFlashCtr+1)%32;var nextLevel=0;37==keyPress?nextLevel=-1:39==keyPress?nextLevel=1:38==keyPress?nextLevel=-5:40==keyPress&&(nextLevel=5);var oldLevel=this.level,checkLevel=this.level+nextLevel;checkLevel>=0&&checkLevel<=9&&(this.level=checkLevel),this.level!=oldLevel&&Sound.forcePlay("MenuBeep"),65==keyPress?"A"==this.type?keyPress=13:this.menuItem=1:83==keyPress&&(Sound.stopBGMusic(),SceneManager.popScene())}else{this.highFlashCtr=(this.highFlashCtr+1)%32;var nextHigh=0;37==keyPress?nextHigh=-1:39==keyPress?nextHigh=1:38==keyPress?nextHigh=-3:40==keyPress&&(nextHigh=3);var oldHigh=this.high,checkHigh=this.high+nextHigh;checkHigh>=0&&checkHigh<=5&&(this.high=checkHigh),this.high!=oldHigh&&Sound.forcePlay("MenuBeep"),83==keyPress?this.menuItem=0:65==keyPress&&(keyPress=13)}13==keyPress&&SceneManager.pushScene(new GameScene(this.context,this.type,this.level,this.high))}}get levelIndicatorOffsets(){var x,y;return{x:this.level%5*16,y:16*Math.floor(this.level/5)}}get highIndicatorOffsets(){var x,y;return{x:this.high%3*16,y:16*Math.floor(this.high/3)}}draw(){"A"==this.type?this.context.drawImage(RESOURCE.sprites,0,432,160,144,0,0,160,144):this.context.drawImage(RESOURCE.sprites,0,576,160,144,0,0,160,144),this.levelText.text=""+this.level;var levelTextOffsets=this.levelIndicatorOffsets;this.levelText.x=this.levelSpriteOffsetX+levelTextOffsets.x,this.levelText.y=this.levelSpriteOffsetY+levelTextOffsets.y,(this.levelFlashCtr<16||1==this.menuItem||this.enterScore)&&this.levelText.draw(),this.highText.text=""+this.high;var highTextOffsets=this.highIndicatorOffsets;this.highText.x=this.highSpriteOffsetX+highTextOffsets.x,this.highText.y=this.highSpriteOffsetY+highTextOffsets.y,(this.highFlashCtr<16||0==this.menuItem||this.enterScore)&&this.highText.draw();for(var i=0;i<TOP_SCORES[this.type].length;i++)this.nameTexts[i].text=TOP_SCORES[this.type][i].name,this.scoreTexts[i].text=""+TOP_SCORES[this.type][i].score;this.nameTexts.forEach(t=>t.draw()),this.scoreTexts.forEach(t=>t.draw()),this.enterScore&&this.cursorBlink>=7&&(this.context.fillStyle="#80832c",this.context.fillRect(8*this.cursorLocation+32,8*this.enterScore+96,8,8))}}class BTypeScoringScene extends Scene{constructor(context,level,scoring,high){super(context),this.curtain=Board.HEIGHT,this.scoring=scoring,this.high=high,this.level=level,this.multiplier=this.level+1,this.totalScore=this.scoring[1]*this.multiplier*40+this.scoring[2]*this.multiplier*100+this.scoring[3]*this.multiplier*300+this.scoring[4]*this.multiplier*1200+this.scoring.drops,this.singleCount=new Text(this,"0",24,8,"right"),this.singleScore=new Text(this,"0",80,16,"right"),this.doubleCount=new Text(this,"0",24,32,"right"),this.doubleScore=new Text(this,"0",80,40,"right"),this.tripleCount=new Text(this,"0",24,56,"right"),this.tripleScore=new Text(this,"0",80,64,"right"),this.tetrisCount=new Text(this,"0",24,80,"right"),this.tetrisScore=new Text(this,"0",80,88,"right"),this.dropsScore=new Text(this,"0",80,104,"right"),this.stageScore=new Text(this,"0",80,136,"right"),this.levelText=new Text(this,""+this.level,128,16,"right"),this.highText=new Text(this,""+this.high,128,40,"right"),this.linesText=new Text(this,"0",136,80,"right"),this.startCountTimer=new Timer,this.scoreTexts=[[],[this.singleCount,this.singleScore],[this.doubleCount,this.doubleScore],[this.tripleCount,this.tripleScore],[this.tetrisCount,this.tetrisScore],[this.dropsScore]],this.points=[0,40,100,300,1200,1],this.drawables=[new Text(this,"SINGLE",16,0),new Text(this,"x "+40*(level+1),40,8),this.singleCount,this.singleScore,new Text(this,"DOUBLE",16,24),new Text(this,"x "+100*(level+1),40,32),this.doubleCount,this.doubleScore,new Text(this,"TRIPLE",16,48),new Text(this,"x "+300*(level+1),40,56),this.tripleCount,this.tripleScore,new Text(this,"TETRIS",16,72),new Text(this,"x "+1200*(level+1),40,80),this.tetrisCount,this.tetrisScore,new Text(this,"DROPS",16,96),this.dropsScore,new Text(this,"__________",16,120,"dotted"),new Text(this,"THIS STAGE",16,128),this.stageScore,this.levelText,this.highText,this.linesText]}tick(){var keyPress=Input.readKeyPress();if(this.startCountTimer.tick(),this.curtain>0)this.curtain--,0==this.curtain&&this.startCountTimer.start(80,()=>{this.counting=1,this.currentCount=0,this.totalScore=0,this.countDelay=0});else if(this.counting)if(4==this.countDelay||5==this.counting){if(this.countDelay=0,this.currentCount==this.scoring[this.counting]){var lastCount=this.counting;this.counting=!1,this.startCountTimer.start(35,()=>{this.counting=lastCount+1,this.currentCount=0,5==this.counting?this.multiplier=1:6==this.counting&&(this.counting=0,this.done=!0)})}else{this.currentCount++;var texts=this.scoreTexts[this.counting],points=this.points[this.counting];texts[0].text=""+this.currentCount,this.counting<5&&(texts[1].text=""+points*this.currentCount*this.multiplier),this.totalScore+=points*this.multiplier,Sound.forcePlay("MenuConfirm")}this.stageScore.text=""+this.totalScore}else this.countDelay++;else this.done&&(13!=keyPress&&65!=keyPress||SceneManager.popScene())}draw(){var cOffset=8*this.curtain;this.context.drawImage(RESOURCE.sprites,160,288+cOffset,160,144-cOffset,0,cOffset,160,144-cOffset),this.drawables.forEach(t=>{t.y>=8*this.curtain&&t.draw()})}}class BTypeWinScene extends Scene{constructor(context,scoring,high){super(context),this.curtain=Board.HEIGHT,this.high=high,this.level=9,this.scoring=scoring,this.animator=0,this.violin=new Sprite(this,50,40,16,16,352,104,16,16),this.violin.high=0,this.violin.animator=56,this.guitar=new Sprite(this,18,40,16,16,320,104,16,16),this.guitar.high=1,this.guitar.animator=30,this.bass=new Sprite(this,34,40,16,16,336,104,16,16),this.bass.high=2,this.bass.animator=31,this.drum=new Sprite(this,17,96,16,16,368,104,16,16),this.drum.high=3,this.drum.animator=100,this.flutes=new Sprite(this,57,112,16,16,400,104,16,16),this.flutes.high=4,this.flutes.animator=64,this.cymbal=new Sprite(this,72,112,16,16,416,104,16,16),this.cymbal.high=5,this.cymbal.animator=48,this.dancer1=new Sprite(this,62,80,16,16,448,104,16,16),this.dancer1.high=5,this.dancer1.animator=76,this.dancer1.jumper=!0,this.dancer2=new Sprite(this,78,80,16,16,384,104,16,16),this.dancer2.high=5,this.dancer2.animator=59,this.dancer3=new Sprite(this,121,120,16,16,432,104,16,16),this.dancer3.high=5,this.dancer3.animator=86,this.dancer4=new Sprite(this,137,120,16,16,432,104,16,16),this.dancer4.high=5,this.dancer4.animator=86,this.sprites=[this.violin,this.guitar,this.bass,this.drum,this.flutes,this.cymbal,this.dancer1,this.dancer2,this.dancer3,this.dancer4]}tick(){this.curtain>0&&(this.curtain--,0==this.curtain&&Sound.playOnce("BWin"+this.high,()=>{SceneManager.replaceScene(new BTypeScoringScene(this.context,9,this.scoring,this.high)),5==this.high&&SceneManager.pushScene(new RocketScene(this.context,4))})),this.animator++}draw(){var cOffset=8*this.curtain;this.context.drawImage(RESOURCE.sprites,336,144+cOffset,160,144-cOffset,16,cOffset,160,144-cOffset),this.sprites.forEach(s=>{var animator=s==this.dancer4?this.animator:this.animator+16,frame=Math.floor(animator%s.animator/(s.animator/2));s.textureY=104+16*frame,s.jumper&&(s.y=80-11*frame),s.high<=this.high&&s.draw()})}}class GameScene extends Scene{static PIECES=[OrangeRicky,BlueRicky,Hero,Smashboy,ClevelandZ,RhodeIslandZ,Teewee];static DELAY_AUTO_SHIFT=[1,22,8];constructor(context,gameType,level,high){super(context),this.board=new Board(this,gameType,level,high),this.gameType=gameType,this.startLevel=level,this.level=level,this.high=high,"A"==this.gameType?(this.lines=0,this.linesText=new Text(this,"0",136,80,"right"),this.levelText=new Text(this,""+this.level,136,56,"right"),this.scoreText=new Text(this,"0",144,24,"right")):(this.lines=25,this.linesText=new Text(this,"0",136,80,"right"),this.levelText=new Text(this,""+this.level,128,16,"right"),this.highText=new Text(this,""+this.high,128,40,"right")),this.scoring={1:0,2:0,3:0,4:0,5:0},this.canHardDrop=!0,this.score=0,this.currentPiece=this.chooseRandomPiece(),this.currentPiece.x=32,this.currentPiece.y=8,this.previewPiece=this.chooseRandomPiece(),this.onDeckPiece=this.pieceRandomizer(),this.gravityTickCtr=0,this.dasIndex=0,this.dasFrameCtr=0,this.endGameTimer=new Timer,this.rowClearTimer=new Timer,this.curtainTimer=new Timer}chooseRandomPiece(){var choice=Math.floor(7*Math.random());return new GameScene.PIECES[choice](this,112,112)}pieceRandomizer(){for(var choice,chances=3,attempt=0;attempt<3;){var currentIndex,previewIndex,or;if((choice=Math.floor(7*Math.random()))!=(GameScene.PIECES.indexOf(this.currentPiece.constructor)|GameScene.PIECES.indexOf(this.previewPiece.constructor)|choice))break;attempt++}var piece=new GameScene.PIECES[choice](this,112,112);return piece.hide(),piece}releasePreviewPiece(){this.currentPiece=this.previewPiece,this.currentPiece.tileOrigin={x:4,y:1},this.previewPiece=this.onDeckPiece,this.previewPiece.show(),this.onDeckPiece=this.pieceRandomizer()}shiftPiece(direction){this.dasFrameCtr==GameScene.DELAY_AUTO_SHIFT[this.dasIndex]?(this.dasIndex=Math.min(++this.dasIndex,2),this.dasFrameCtr=0,this.currentPiece.move(direction)):this.dasFrameCtr++,delete this.hardDrop}lose(){this.losing=!0,this.curtainTimer.start(30,()=>{delete this.previewPiece,this.curtain=0,this.curtainStatus="cover",Sound.playOnce("Lose"),this.curtainTimer.start(18,()=>{this.curtainStatus="wait",this.curtainTimer.start(52,()=>{this.gameOver()})})})}gameOver(){delete this.previewPiece,Sound.stopBGMusic();for(var topScores=TOP_SCORES[this.gameType],place=1,score=this.score,i=0;i<topScores.length&&score<=topScores[i].score;i++)place++;if(("A"==this.gameType||this.gameComplete)&&place<4){topScores.splice(place-1,0,{name:"A",score:score}),TOP_SCORES[this.gameType]=topScores.slice(0,3);try{localStorage.TOP_SCORES=JSON.stringify(TOP_SCORES)}catch(ex){}SceneManager[this.gameType+"LevelSelectScene"].enterScore=place}if(this.gameComplete)9==this.level?SceneManager.replaceScene(new BTypeWinScene(this.context,this.scoring,this.high)):SceneManager.replaceScene(new BTypeScoringScene(this.context,this.level,this.scoring,this.high));else{if("A"==this.gameType){var rocket=0;this.score>=1e5?rocket=1:this.score>=15e4?rocket=2:this.score>=2e5&&(rocket=3),SceneManager.LoseScene.rocket=rocket}SceneManager.replaceScene(SceneManager.LoseScene)}}tick(){Scene.prototype.tick.call(this);var keyPress=Input.readKeyPress();if(!this.rowClearTimer.tick()&&!this.endGameTimer.tick())if(this.curtainTimer.tick(),this.curtain>=0||this.losing)switch(this.curtainStatus){case"cover":this.board.curtainCover(17-this.curtain),this.curtain++}else if(this.gameComplete)this.gameOver();else{if(Sound.playBGMusic(),13==keyPress)return Sound.stopBGMusic(),Sound.playOnce("PauseGame"),void SceneManager.pushScene(SceneManager.PauseScene);if(Input.isKeyDown(37)?this.shiftPiece(Vector.LEFT):Input.isKeyDown(39)?this.shiftPiece(Vector.RIGHT):Input.isKeyDown(40)&&this.canHardDrop?(this.dasFrameCtr=0,this.dasIndex=0,this.hardDrop||(this.gravityTickCtr=0,this.hardDrop=this.currentPiece.tileOrigin.y)):(this.dasFrameCtr=0,this.dasIndex=0,delete this.hardDrop,Input.isKeyDown(40)||(this.canHardDrop=!0)),65==keyPress?this.currentPiece.rotate(!1):81==keyPress&&this.currentPiece.rotate(!0),83==keyPress&&(Sound.stopBGMusic(),SceneManager.popScene()),16==keyPress&&(this.hidePreviewPiece=!this.hidePreviewPiece),this.gravityTickCtr++,this.gravityTickCtr==this.gravity){this.gravityTickCtr=0;var clearRows=this.currentPiece.fall();if(clearRows){if(-1==clearRows)return Sound.forcePlay("PieceLock"),Sound.stopBGMusic(),void this.lose();if(this.hardDrop){var hardDistance=this.currentPiece.tileOrigin.y-this.hardDrop;this.scoring[5]+=hardDistance,this.score+=hardDistance,delete this.hardDrop}this.canHardDrop=!1,clearRows.length?(4==clearRows.length?Sound.playOnce("Tetris"):Sound.playOnce("RowClear"),this.rowClearTimer.start(77,()=>{this.scoring[clearRows.length]++;var oldLevel=this.level;this.score+=this.getPoints(clearRows.length),"A"==this.gameType?(this.lines+=clearRows.length,this.level=Math.max(this.startLevel,Math.floor(this.lines/10))):(this.lines-=clearRows.length,this.lines=Math.max(this.lines,0),this.lines<=0&&(this.gameComplete=!0,this.endGameTimer.start(120),Sound.stopBGMusic(),setTimeout(()=>{Sound.playOnce("Win")},500))),this.board.clearRows(clearRows),this.releasePreviewPiece(),Sound.playOnce("RowDrop",()=>{oldLevel!=this.level&&Sound.playOnce("LevelUp")})})):(this.releasePreviewPiece(),Sound.forcePlay("PieceLock"))}}}}draw(){Scene.prototype.draw.call(this),this.board.draw(this.currentPiece),this.linesText.text=""+this.lines,this.linesText.draw(),this.levelText.text=""+this.level,this.levelText.draw(),"A"==this.gameType?(this.scoreText.text=""+this.score,this.scoreText.draw()):this.highText.draw(),this.previewPiece&&!this.hidePreviewPiece&&this.previewPiece.draw()}getTotalScore(){var multiplier="A"==this.gameType?1:this.level+1,single,double,triple,tetris,drops;return 40*this.scoring[1]*multiplier+100*this.scoring[2]*multiplier+300*this.scoring[3]*multiplier+1200*this.scoring[4]*multiplier+this.scoring[5]}getPoints(rows){return[40,100,300,1200][rows-1]*(this.level+1)}get gravity(){if(this.hardDrop)return 3;switch(this.level){case 0:return 53;case 1:return 49;case 2:return 45;case 3:return 41;case 4:return 37;case 5:return 33;case 6:return 28;case 7:return 22;case 8:return 17;case 9:return 11;case 10:return 10;case 11:return 9;case 12:return 8;case 13:return 7;case 14:case 15:return 6;case 16:case 17:return 5;case 18:case 19:return 4;default:return 3}}}class LoseScene extends Scene{constructor(context){super(context),this.curtain=Board.HEIGHT}tick(){var keyPress=Input.readKeyPress();this.curtain>0?(this.curtain--,0==this.curtain&&setTimeout(()=>{this.rocket?(SceneManager.pushScene(new RocketScene(this.context,this.rocket)),delete this.rocket,this.forceExit=!0,this.canExit=!0):this.canExit=!0},1800)):this.canExit&&(13==keyPress||65==keyPress||this.forceExit)&&(SceneManager.popScene(),this.canExit=!1,this.forceExit=!1,this.curtain=Board.HEIGHT)}draw(){this.context.drawImage(RESOURCE.sprites,160,144,160,144,0,0,160,144);for(var y=0;y<this.curtain;y++)for(var x=0;x<Board.WIDTH;x++)this.context.drawImage(RESOURCE.sprites,256,16,8,8,8*x+16,8*y,8,8)}}class PauseScene extends Scene{constructor(context){super(context),this.hit=new Text(this,"HIT",40,24,"dotted"),this.start=new Text(this,"START",32,40,"dotted"),this.to=new Text(this,"TO",48,56,"dotted"),this.continue=new Text(this,"CONTINUE",24,72,"dotted"),this.game=new Text(this,"GAME",40,88,"dotted"),this.drawables=[this.hit,this.start,this.to,this.continue,this.game]}tick(){var keyPress;13!=Input.readKeyPress()||SceneManager.popScene()}draw(){this.context.drawImage(RESOURCE.sprites,176,288,80,144,16,0,80,144),this.drawables.forEach(t=>{t.draw()})}}class RocketScene extends Scene{constructor(context,type){switch(super(context),Sound.playOnce("Rocket",()=>{SceneManager.popScene()}),this.type=type,this.type){case 1:this.rocket=new Sprite(this,72,84,16,28,346,576);break;case 2:this.rocket=new Sprite(this,72,74,16,38,360,576);break;case 3:this.rocket=new Sprite(this,72,56,16,56,377,576);break;case 4:this.rocket=new Sprite(this,64,48,32,64,398,576)}if(this.drawables=[this.rocket],this.leftExhaust=[],this.rightExhaust=[],this.smallFlames=[],this.largeFlames=[],this.type<4)this.leftExhaust.push(new Sprite(this,55,106,19,7,328,605)),this.rightExhaust.push(new Sprite(this,86,106,19,7,328,613)),this.smallFlames.push(new Sprite(this,77,112,6,7,351,605)),this.largeFlames.push(new Sprite(this,76,112,8,16,350,614));else{for(var i=0;i<3;i++)this.smallFlames.push(new Sprite(this,68+8*i,112,8,16,350,614)),this.largeFlames.push(new Sprite(this,68+8*i,112,8,21,350,632));this.leftExhaust.push(new Sprite(this,55,106,19,7,328,605)),this.rightExhaust.push(new Sprite(this,86,106,19,7,328,613)),this.leftExhaust.push(new Sprite(this,49,98,24,15,397,645)),this.rightExhaust.push(new Sprite(this,87,98,24,15,397,662)),this.arm=new Sprite(this,64,64,12,16,333,580),this.drawables.unshift(new Sprite(this,54,60,10,52,320,634)),this.drawables.unshift(this.arm),this.congrats=new Text(this,"",16,24,"underline"),this.speller=0,this.drawables.push(this.congrats)}this.leftExhaust.forEach(f=>f.hide()),this.rightExhaust.forEach(f=>f.hide()),this.smallFlames.forEach(f=>f.hide()),this.largeFlames.forEach(f=>f.hide()),this.tickCtr=0,this.state="launchpad",this.animationCtr=0,this.rocketCtr=0,this.exhaustCtr=0}tick(){this.tickCtr++,200==this.tickCtr?this.state="ignition":436==this.tickCtr?(this.leftExhaust.forEach(e=>e.hide()),this.rightExhaust.forEach(e=>e.hide()),this.exhaustCtr=(this.exhaustCtr+1)%this.leftExhaust.length):780==this.tickCtr?(this.state="liftoff",4==this.type&&this.arm.hide()):2130==this.tickCtr&&4!=this.type?(Sound.stop("Rocket"),SceneManager.popScene()):this.tickCtr>=2136&&!(this.tickCtr%6)&&this.speller<"CONGRATULATIONS!".length&&(this.speller++,this.congrats.text="CONGRATULATIONS!".substr(0,this.speller)),this.tickCtr>=770&&(this.rocketCtr||(this.rocket.y--,this.smallFlames.forEach(f=>f.y=this.rocket.y+this.rocket.height-1),this.largeFlames.forEach(f=>f.y=this.rocket.y+this.rocket.height-1)),this.rocketCtr=(this.rocketCtr+1)%10),"ignition"==this.state?Math.floor(this.animationCtr/10)%2?(this.leftExhaust[this.exhaustCtr].hide(),this.rightExhaust[this.exhaustCtr].hide()):(this.leftExhaust[this.exhaustCtr].show(),this.rightExhaust[this.exhaustCtr].show()):"liftoff"==this.state&&(Math.floor(this.animationCtr/6)%2?(this.smallFlames.forEach(f=>f.show()),this.largeFlames.forEach(f=>f.hide())):(this.smallFlames.forEach(f=>f.hide()),this.largeFlames.forEach(f=>f.show()))),this.animationCtr++}draw(){this.context.drawImage(RESOURCE.sprites,160,576,160,144,0,0,160,144),this.drawables.forEach(d=>d.draw()),this.leftExhaust.forEach(e=>e.draw()),this.rightExhaust.forEach(e=>e.draw()),this.largeFlames.forEach(f=>f.draw()),this.smallFlames.forEach(f=>f.draw())}}class SceneManager{static stack=[];static pushScene(scene){this.stack.push(scene)}static popScene(){this.stack.pop()}static replaceScene(scene){this.popScene(),this.pushScene(scene)}static currentScene(){return this.stack.length?this.stack[this.stack.length-1]:null}static update(){Input.watch();var scene=this.currentScene();scene&&(scene.tick(),scene.draw())}}var RESOURCE={sprites:document.createElement("img")};RESOURCE.sprites.src="res/tetris.png";var SCREEN=document.createElement("canvas"),context=SCREEN.getContext("2d"),scale=4,TOP_SCORES;SCREEN.id="screen",SCREEN.width=160*scale,SCREEN.height=144*scale,context.webkitImageSmoothingEnabled=!1,context.mozImageSmoothingEnabled=!1,context.imageSmoothingEnabled=!1,context.scale(scale,scale),SCREEN.style.background="#80832c",SCREEN.style.border="solid",document.body.appendChild(SCREEN);try{TOP_SCORES=localStorage.TOP_SCORES?JSON.parse(localStorage.TOP_SCORES):{A:[],B:[]}}catch(ex){TOP_SCORES={A:[],B:[]}}function loop(){pauseGame||SceneManager.update(),pauseGame&&!wasPaused?Sound.suspend():!pauseGame&&wasPaused&&Sound.resume(),wasPaused=pauseGame,window.requestAnimationFrame(loop)}window.requestAnimationFrame(loop);var creditsScene=new CreditsScene(context);SceneManager.GameMenuScene=new GameMenuScene(context),SceneManager.PauseScene=new PauseScene(context),SceneManager.LoseScene=new LoseScene(context),SceneManager.ALevelSelectScene=new LevelSelectScene(context,"A"),SceneManager.BLevelSelectScene=new LevelSelectScene(context,"B"),SceneManager.pushScene(creditsScene);var pauseGame=!1,wasPaused=!1;