Garbage TETRIS

by nemu90kWw
◆ ごみテトリス -ごみはゴミ箱へ-

しばらくゲーム作りから離れていたので、練習がてら作るのが簡単そうなテトリスを。
慣れてる人なら一時間あればできるらしいけど、しばらくぶりだったので一週間以上かかってしまいました。
勘を取り戻すのには時間がかかりそうです。

◆ 操作方法

←・→ 移動 ↓ ソフトドロップ ↑ ハードドロップ Z・X 回転(クラシック準拠)
♥48 | Line 1049 | Modified 2010-09-15 03:36:24 | MIT License
play

ActionScript3 source code

/**
 * Copyright nemu90kWw ( http://wonderfl.net/user/nemu90kWw )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/dhEj
 */

/*
◆ ごみテトリス -ごみはゴミ箱へ-

しばらくゲーム作りから離れていたので、練習がてら作るのが簡単そうなテトリスを。
慣れてる人なら一時間あればできるらしいけど、しばらくぶりだったので一週間以上かかってしまいました。
勘を取り戻すのには時間がかかりそうです。

◆ 操作方法

←・→ 移動 ↓ ソフトドロップ ↑ ハードドロップ Z・X 回転(クラシック準拠)
*/
package
{
	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;
	import com.bit101.components.*;
	import net.wonderfl.score.basic.*;
	
	[SWF(width="465", height="465", backgroundColor="0xFFFFFF", frameRate="60")]
	public class Tetris extends Sprite
	{
		private var label:Label;
		private var btn_start:PushButton;
		private var btn_ranking:PushButton;
		private var btn_clean:PushButton;
		private var play:Boolean = false;
		
		private var form_score:BasicScoreForm;
		private var form_ranking:BasicScoreRecordViewer;
		
		private var buffer:BitmapData = new BitmapData(465, 465, false, 0x000000);
		private var screen:Bitmap = new Bitmap(buffer);
		
		private var gamedata:GameData;
		private var field:Field;
		
		function Tetris()
		{
			Key.setListener(this);
			KeyWatcher.watch("LEFT",  37, 100, 65);
			KeyWatcher.watch("RIGHT", 39, 102, 68);
			KeyWatcher.watch("UP",    38, 104, 87);
			KeyWatcher.watch("DOWN",  40, 98, 83);
			KeyWatcher.watch("ROT_L", 90, 75);
			KeyWatcher.watch("ROT_R", 16, 88, 74);
			func_key = new KeyWatcher();
			
			addChild(screen);
			
			Style.LABEL_TEXT = 0x000000;
			label = new Label(this, 0, 0);
			message("Garbage TETRIS");
			
			btn_start = new PushButton(this, 182, 300, "START", startGame);
			btn_ranking = new PushButton(this, 182, 330, "RANKING", showRanking);
			btn_clean = new PushButton(this, 182, 380, "CLEAN UP!", clean);
			btn_clean.visible = false;
			
			gamedata = new GameData();
			field = new Field(gamedata, this);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		
		public function message(text:String):void
		{
			if(label != null) {this.removeChild(label);}
			
			label = new Label(this, 0, 0, text);
			label.x = 465/2 - label.width*3;
			label.y = 465/2 - label.height*3 - 50;
			label.scaleX = label.scaleY = 6;
			label.blendMode = BlendMode.INVERT;
		}
		
		public function startGame(e:Event):void
		{
			label.visible = false;
			btn_start.visible = false;
			btn_ranking.visible = false;
			btn_clean.visible = false;
			
			play = true;
			field.init();
			return;
		}
		
		public function entryRanking():void
		{
			label.visible = false;
			form_score = new BasicScoreForm(this, (465-280)/2, (465-160)/2, gamedata.score, "ENTRY", onCloseScoreForm);
			return;
		}
		
		private function onCloseScoreForm(succeeded:Boolean):void
		{
			if (form_score != null) {removeChild(form_score);}
			form_ranking = new BasicScoreRecordViewer(this, (465-220)/2, (465-240)/2, "RANKING", 30, true, onCloseRankingForm);
			return;
		}
		
		private function showRanking(e:Event):void
		{
			label.visible = false;
			btn_start.visible = false;
			btn_ranking.visible = false;
			btn_clean.visible = false;
			
			form_ranking = new BasicScoreRecordViewer(this, (465-220)/2, (465-240)/2, "RANKING", 30, true, onCloseRankingForm);
			return;
		}
		
		private function onCloseRankingForm():void
		{
			if (form_ranking != null) {removeChild(form_ranking);}
			
			btn_start.visible = true;
			btn_ranking.visible = true;
			
			if(play == true) {btn_clean.visible = true;}
			
			message("Garbage TETRIS");
			return;
		}
		
		private function clean(e:Event):void
		{
			field.garbage.fillRect(field.garbage.rect, 0x00000000);
			return;
		}
		
		public function onEnterFrame(e:Event):void
		{
			func_key.update();
			field.main();
			draw();
			return;
		}
		
		public function draw():void
		{
			field.draw(buffer);
			return;
		}
	}
}

	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;
	import frocessing.color.FColor;
	
	var func_key:KeyWatcher;
	
	class GameData
	{
		public var score:int = 0;
	}
	
	class Field
	{
		public static const WIDTH:int = 10;
		public static const INVISIBLE_HEIGHT:int = 1;
		public static const HEIGHT:int = 20+INVISIBLE_HEIGHT;
		public static const G:int = 0x10000;
		
		private var gamedata:GameData;
		private var root:Tetris;
		
		public var map:Vector.<Vector.<int>>;
		private var minopool:ActorPool = new ActorPool();
		private var effectpool:ActorPool = new ActorPool();
		private var particlepool:ActorPool = new ActorPool();
		public var garbage:BitmapData = new BitmapData(465, 465, true, 0x00000000);
		
		public var x:int = Math.floor((465/2) - (20*WIDTH)/2);
		public var y:int = Math.floor((465/2) - (20*HEIGHT)/2) - (10*INVISIBLE_HEIGHT);
		
		private var tetromino:Tetromino;
		private var ghost:Tetromino;
		private var next:Tetromino;
		
		private var table:Array;
		private var drop_speed:int;
		private var lockdown_length:int;
		private var lockdown_time:int;
		private var halflock:Boolean;
		
		private var action:Function;
		private var count:int;
		
		function Field(gamedata:GameData, root:Tetris)
		{
			this.gamedata = gamedata;
			this.root = root;
			
			changeAction("act_blank");
			return;
		}
		
		private function changeAction(name:String):void
		{
			action = this[name];
			count = -1;
			return;
		}
		
		public function init():void
		{
			map = new Vector.<Vector.<int>>(HEIGHT);
			for(var y:int = 0; y < HEIGHT; y++) {
				map[y] = new Vector.<int>(WIDTH);
				for(var x:int = 0; x < WIDTH; x++) {
					map[y][x] = -1;
				}
			}
			
			do {
				table = MathEx.shuffle([0, 1, 2, 3, 4, 5, 6]);
			}while(
				table[0] == MinoPattern.S || table[0] == MinoPattern.Z
			 || table[1] == MinoPattern.S || table[1] == MinoPattern.Z)
			
			drop_speed = G / 30;
			lockdown_length = 60;
			
			gamedata.score = 0;
			changeAction("act_ready");
			return;
		}
		
		public function main():void
		{
			count++;
			action();
			
			minopool.main();
			effectpool.main();
			particlepool.main();
			
			return;
		}
		
		// --------------------------------//
		// 状態
		// --------------------------------//
		private function act_blank():void
		{
			return;
		}
		
		private function act_ready():void
		{
			if(count == 0) {
				root.message("Ready");
			}
			
			if(count == 60)
			{
				root.message("");
				changeAction("act_move");
			}
			return;
		}
		
		private function act_move():void
		{
			if(count == 0)
			{
				shiftMino();
				
				// ゲームオーバー判定
				if(tetromino.hitTest() == true)
				{
					tetromino.lock();
					tetromino = null;
					next = null;
					changeAction("act_gameover");
					return;
				}
			}
			
			// 回転
			if(func_key.isPress("ROT_L")) {tetromino.rotate(1);}
			else if(func_key.isPress("ROT_R")) {tetromino.rotate(-1);}
			
			// 横移動
			if(func_key.isPress("LEFT", true)) {tetromino.slide(-1);}
			else if(func_key.isPress("RIGHT", true)) {tetromino.slide(1);}
			
			if(func_key.isPress("UP") == true)
			{
				// ハードドロップ
				tetromino.drop(G*20);
				lockdown_time = lockdown_length;
			}
			else if(tetromino.isLanding() == false)
			{
				// ソフトドロップ
				if(func_key.isDown("DOWN") && drop_speed < G/2) {tetromino.drop(G/2);}
				else {tetromino.drop(drop_speed);}
				lockdown_time = 0;
				halflock = false;
			}
			
			// 接地
			if(tetromino.isLanding() == true)
			{
				lockdown_time++;
				
				if(lockdown_time >= lockdown_length || func_key.isPress("DOWN"))
				{
					changeAction("act_lockdown");
				}
				else if(func_key.isDown("DOWN"))
				{
					if(halflock == false)
					{
						halflock = true;
						lockdown_time = lockdown_length-7;
					}
				}
			}
			
			// ゴースト処理
			ghost = tetromino.clone();
			ghost.ghost = true;
			ghost.drop(G*20);
			return;
		}
		
		private function act_lockdown():void
		{
			if(count == 0)
			{
				tetromino.lock();
				tetromino = null;
				ghost = null;
				
				// ライン判定
				for(var i:int = 0; i < HEIGHT; i++)
				{
					if(checkLine(i) == true)
					{
						changeAction("act_erase");
						return;
					}
				}
			}
			
			if(count == 15)
			{
				changeAction("act_move");
				return;
			}
		}
		
		private function act_erase():void
		{
			var i:int;
			if(count == 0)
			{
				for(i = 0; i < HEIGHT; i++) {
					if(checkLine(i) == true) {
						for each(var mino:Mino in minopool.list) {
							if (mino.py == i) {
								mino.changeAction("act_break");
							}
						}
						gamedata.score++;
					}
				}
			}
			if(count == 45)
			{
				for(i = 0; i < HEIGHT; i++) {
					if(checkLine(i) == true) {
						eraseLine(i);
					}
				}
			}
			
			if(count == 110)
			{
				changeAction("act_move");
				
				if(gamedata.score >= 50) {
					drop_speed += G/100;
				}
			}
		}
		
		private function act_gameover():void
		{
			if(count == 0)
			{
				root.message("GAME OVER");
				
				for(var i:int = INVISIBLE_HEIGHT; i < HEIGHT; i++)
				{
					for(var j:int = 0; j < WIDTH; j++)
					{
						if(map[i][j] != -1) {
							map[i][j] = 7;
						}
					}
					for each(var mino:Mino in minopool.list) {
						mino.changeAction("act_gameover");
					}
				}
			}
			
			if(count == 350) {
				root.entryRanking();
			}
		}
		
		// --------------------------------//
		// フィールド操作
		// --------------------------------//
		public function shiftMino():void
		{
			if(next == null)
			{
				tetromino = new Tetromino(this);
				tetromino.x = 3;
				tetromino.y = 0;
				tetromino.type = table.shift();
				tetromino.rot = 0;
			}
			else {
				tetromino = next;
				tetromino.ghost = false;
			}
			
			next = new Tetromino(this);
			next.x = 3;
			next.y = 0;
			next.type = table.shift();
			next.ghost = true;
			next.rot = 0;
			
			if(func_key.isDown("ROT_L") && tetromino.hitTest(0, 0, 1) == false) {tetromino.rotate(1);}
			else if(func_key.isDown("ROT_R") && tetromino.hitTest(0, 0, -1) == false) {tetromino.rotate(-1);}
			
			if(table.length == 0) {
				table = MathEx.shuffle([0, 1, 2, 3, 4, 5, 6]);
			}
			
			lockdown_time = 0;
			halflock = false;
			return;
		}
		
		public function writeMino(x:int, y:int, type:int):void
		{
			map[y][x] = type;
			minopool.add(new Mino(this, x, y, type));
		}
		
		public function eraseLine(h:int):void
		{
			for(var y:int = h; y >= 1; y--) {
				map[y] = map[y-1];
			}
			map[0] = new Vector.<int>(WIDTH);
			for(var x:int = 0; x < WIDTH; x++) {
				map[0][x] = -1;
			}
			for each(var mino:Mino in minopool.list) {
				if(mino.py < h) {
					mino.py++;
					mino.y += 20;
				}
			}
			return;
		}
		
		// --------------------------------//
		// 生成
		// --------------------------------//
		public function addBreakingEffect(px:int, py:int, type:int):void
		{
			effectpool.add(new BreakingMino(this, x+px*20+10, y+py*20+10, type));
			
			for(var i:int = 0; i < 5; i++) {
				particlepool.add(new Particle(x+px*20+10, y+py*20+10));
			}
		}
		
		public function addParticle(x:Number, y:Number):void
		{
			particlepool.add(new Particle(x, y));
		}
		
		// --------------------------------//
		// 判定
		// --------------------------------//
		public function hitTest(x:int, y:int):Boolean
		{
			if(x < 0 || x >= WIDTH || y >= HEIGHT) {return true;}
			if(y < 0) {return false;}
			return (map[y][x] >= 0 && map[y][x] <= 6);
		}
		
		public function checkLine(h:int):Boolean
		{
			for(var i:int = 0; i < WIDTH; i++) {
				if(hitTest(i, h) == false) {return false;}
			}
			return true;
		}
		
		// --------------------------------//
		// 描画
		// --------------------------------//
		public function draw(buffer:BitmapData):void
		{
			buffer.colorTransform(buffer.rect, new ColorTransform(1, 1, 1, 1, 24, 32, 48, 0));
			buffer.fillRect(new Rectangle(x, y+INVISIBLE_HEIGHT*20, WIDTH*20, (HEIGHT-INVISIBLE_HEIGHT)*20), 0x404040);
			for(var i:int = INVISIBLE_HEIGHT; i < HEIGHT; i++) {
				for(var j:int = 0; j < WIDTH; j++) {
					buffer.fillRect(new Rectangle(x+j*20, y+i*20, 19, 19), 0x000000);
				}
			}
			
			minopool.draw(buffer);
			
			if(next != null) {next.draw(buffer)};
			if(tetromino != null) {tetromino.draw(buffer);}
			
			buffer.copyPixels(garbage, garbage.rect, new Point(0, 0));
			particlepool.draw(buffer);
			effectpool.draw(buffer);
			
			if(ghost != null) {ghost.draw(buffer);}
			return;
		}
	}
	
	class Tetromino
	{
		private var field:Field;
		public var x:int;
		public var y:int;
		public var type:int;
		public var ghost:Boolean = false;
		
		private var _rot:int;
		public function get rot():int {return _rot;}
		public function set rot(value:int):void
		{
			_rot = value;
			if(_rot < 0) {_rot = MinoPattern.TABLE[type].length-1;}
			if(_rot >= MinoPattern.TABLE[type].length) {_rot = 0;}
		}
		
		function Tetromino(field:Field)
		{
			this.field = field;
		}
		
		public function clone():Tetromino
		{
			var temp:Tetromino = new Tetromino(field);
			temp.x = x;
			temp.y = y;
			temp.type = type;
			temp.rot = rot;
			
			return temp;
		}
		
		// --------------------------------//
		// 移動・回転
		// --------------------------------//
		public function slide(vx:int):void
		{
			if(hitTest(vx) == true) {return;}
			
			x += vx;
			return;
		}
		
		public function drop(vy:int):void
		{
			if(isLanding() == true) {return;}
			
			while(vy >= 0)
			{
				y += Math.min(vy, 0x10000);
				vy -= 0x10000;
				
				if(isLanding() == true) {
					y = (y >> 16) << 16;
					break;
				}
			}
		}
		
		public function rotate(r:int):void
		{
			if(hitTest(0, 0, r) == false)
			{
				rot += r;
				return;
			}
			
			// Iは補正しない
			if(type == MinoPattern.I) {return;}
			
			// JLTは中央列以外にブロックが存在しない場合は補正しない
			if(type == MinoPattern.J || type == MinoPattern.L || type == MinoPattern.T)
			{
				var side:Boolean = false;
				loop : for(var i:int = 0; i < 3; i++) {
					for(var j:int = 0; j < 3; j+=2) {
						if(field.hitTest(x+j, ((y+0x8000)>>16)+i) == true) {side = true; break loop;}
						if(field.hitTest(x+j, ((y+0xFFFF)>>16)+i) == true) {side = true; break loop;}
					}
				}
				if(side == false) {
					return;
				}
			}
			
			// 回転補正
			if(hitTest(1, 0, r) == false)
			{
				rot += r;
				x++;
				return;
			}
			if(hitTest(-1, 0, r) == false)
			{
				rot += r;
				x--;
				return;
			}
			
			return;
		}
		
		// --------------------------------//
		// 当たり判定
		// --------------------------------//
		public function hitTest(offset_x:int = 0, offset_y:int = 0, offset_rot:int = 0):Boolean
		{
			var temp:Tetromino = this.clone();
			temp.x += offset_x;
			temp.y += offset_y;
			temp.rot += offset_rot;
			
			var pattern:Array = MinoPattern.TABLE[temp.type][temp.rot];
			
			for(var i:int = 0; i < pattern.length; i++) {
				for(var j:int = 0; j < pattern[i].length; j++) {
					if(pattern[i][j] != 0) {
						if(field.hitTest(temp.x+j, ((temp.y+0x8000)>>16)+i) == true) {return true;}
						if(field.hitTest(temp.x+j, ((temp.y+0xFFFF)>>16)+i) == true) {return true;}
					}
				}
			}
			return false;
		}
		
		public function isLanding():Boolean
		{
			var temp:Tetromino = this.clone();
			var pattern:Array = MinoPattern.TABLE[temp.type][temp.rot];
			
			for(var i:int = 0; i < pattern.length; i++) {
				for(var j:int = 0; j < pattern[i].length; j++) {
					if(pattern[i][j] != 0) {
						if(field.hitTest(temp.x+j, (temp.y>>16)+(i+1)) == true) {return true;}
					}
				}
			}
			return false;
		}
		
		public function lock():void
		{
			var pattern:Array = MinoPattern.TABLE[type][rot];
			
			for(var i:int = 0; i < pattern.length; i++) {
				for(var j:int = 0; j < pattern[i].length; j++) {
					if(pattern[i][j] != 0) {
						field.writeMino(x+j, (y>>16)+i, type);
					}
				}
			}
			return;
		}
		
		// --------------------------------//
		// 描画
		// --------------------------------//
		public function draw(buffer:BitmapData):void
		{
			var i:int, j:int;
			var pattern:Array = MinoPattern.TABLE[type][rot];
			
			if(ghost == false)
			{
				for(i = 0; i < pattern.length; i++) {
					for(j = 0; j < pattern[i].length; j++) {
						if(pattern[i][j] != 0) {
							buffer.fillRect(
								new Rectangle(
									field.x + (x+j)*20 - 1, field.y + (y/0x10000+i)*20 - 1,
									21, 21
								),
								FColor.HSVtoValue(
									FColor.hue(MinoPattern.COLOR[type]),
									FColor.saturation(MinoPattern.COLOR[type]),
									0.5
								)
							);
							buffer.fillRect(
								new Rectangle(
									field.x + (x+j)*20, field.y + (y/0x10000+i)*20,
									19, 19
								),
								MinoPattern.COLOR[type]
							);
						}
					}
				}
			}
			else {
				i = -1;
				do {
					j = -1;
					do {
						var pc:Boolean, py:Boolean, px:Boolean;
						pc = pattern[i]   == undefined || pattern[i][j]   == undefined || pattern[i][j]   == 0;
						py = pattern[i+1] == undefined || pattern[i+1][j] == undefined || pattern[i+1][j] == 0;
						px = pattern[i]   == undefined || pattern[i][j+1] == undefined || pattern[i][j+1] == 0;
						
						if(pc != py)
						{
							buffer.fillRect(
								new Rectangle(
									field.x + (x+j)*20, field.y + (y/0x10000+i+1)*20-1,
									20, 2
								),
								MinoPattern.COLOR[type]
							);
						}
						if(pc != px)
						{
							buffer.fillRect(
								new Rectangle(
									field.x + (x+j+1)*20-1, field.y + (y/0x10000+i)*20,
									2, 20
								),
								MinoPattern.COLOR[type]
							);
						}
						j++;
					} while(j < pattern[0].length)
					i++;
				} while(i < pattern.length)
			}
			return;
		}
	}
	
	class Actor
	{
		protected var count:int = -1;
		public var deleteflag:Boolean = false;
		
		public var x:Number = 0;
		public var y:Number = 0;
		public var visible:Boolean = true;
		
		public var action:String = "act_blank";
		
		public function main():void
		{
			count++;
			this[action]();
		}
		
		public function act_blank():void {}
		
		public function draw(buffer:BitmapData):void {}
		
		public function changeAction(name:String):void
		{
			action = name;
			count = -1;
		}
		
		public function vanish():void
		{
			deleteflag = true;
		}
	}
	
	class ActorPool
	{
		public var list:Array;
		
		function ActorPool()
		{
			list = new Array();
		}
		
		public function get length():uint {return list.length;}
		
		public function add(actor:Actor):Actor
		{
			list.push(actor);
			return actor;
		}
		
		public function main():void
		{
			for(var i:int = 0; i < list.length; i++)
			{
				list[i].main();
				if(list[i].deleteflag == true)
				{
					list.splice(i, 1);
					i--;
				}
			}
		}
		
		public function draw(bmp:BitmapData):void
		{
			for(var i:int = 0; i < list.length; i++)
			{
				if(list[i].visible == false) {continue;}
				list[i].draw(bmp);
			}
		}
	}
	
	class Mino extends Actor
	{
		private var field:Field;
		public var px:int;
		public var py:int;
		public var type:int;
		public var color:uint;
		
		function Mino(field:Field, px:int, py:int, type:int):void
		{
			this.px = px;
			this.py = py;
			this.type = type;
			this.field = field;
			
			x = px*20;
			y = py*20;
			
			changeAction("act_flash");
		}
		
		public function act_flash():void
		{
			if(count < 2) {
				color = 0xFFFFFF;
			}
			else if(count < 4) {
				color = 0x000000;
			}
			else if(count < 150) {
				color = FColor.RGBtoValue(
					(0xFF + FColor.red(MinoPattern.COLOR[type]) * (count-4)) / (count-3),
					(0xFF + FColor.green(MinoPattern.COLOR[type]) * (count-4)) / (count-3),
					(0xFF + FColor.blue(MinoPattern.COLOR[type]) * (count-4)) / (count-3)
				);
			}
			else {
				color = MinoPattern.COLOR[type];
			}
		}
		
		public function act_break():void
		{
			if(count % 6 < 3) {
				color = 0xFFFFFF;
			}
			else {
				color = MinoPattern.COLOR[type];
			}
			
			if(count == 12+Math.abs((px - Field.WIDTH/2) * 3)) {
				field.addBreakingEffect(px, py, type);
				vanish();
			}
		}
		
		public function act_gameover():void
		{
			if(count == 0) {
				color = MinoPattern.COLOR[type];
			}
			
			if(21 - Math.floor(count/3) == py) {
				color = 0xA0A0A0;
			}
			
			if(count == 200) {
				changeAction("act_break");
			}
		}
		
		override public function draw(buffer:BitmapData):void
		{
			buffer.fillRect(new Rectangle(field.x + x, field.y + y, 19, 19), color+0xFF000000);
		}
	}
	
	class BreakingMino extends Actor
	{
		private var field:Field;
		
		private var shape:Shape;
		private var matrix:Matrix = new Matrix();
		
		private var vx:Number;
		private var vy:Number;
		private var bound:int;
		private var angle:Number;
		private var rot:Number;
		private var h:Number;
		
		function BreakingMino(field:Field, x:Number, y:Number, type:int)
		{
			this.field = field;
			this.x = x;
			this.y = y;
			
			vx = Math.random() * 10 - 5;
			vy = Math.random() * -6 - 6;
			angle = 0;
			rot = Math.random() * 20 - 10;
			h = y - Math.random() * 20;
			
			shape = new Shape();
			shape.graphics.beginFill(MinoPattern.COLOR[type]);
			shape.graphics.drawRect(-9, -9, 18, 18);
			
			changeAction("act_main");
		}
		
		public function act_main():void
		{
			if(count <= 15 && count % 5 == 0) {
				field.addParticle(x, y);
			}
			
			x += vx;
			y += vy;
			angle += rot;
			
			if(x < 0) {
				x = 0;
				vx = -vx;
				vx /= 1.5;
			}
			if(x > 465) {
				x = 465
				vx = -vx;
				vx /= 1.5;
			}
			
			if(field.garbage.getPixel32(x, y+5) != 0 || y > 465)
			{
				if(bound > 10)
				{
					draw(field.garbage);
					vanish();
					return;
				}
				else if(vy > 0 && y > h)
				{
					vx /= 1.2;
					vy /= -1.5;
					rot = Math.random() * 20 - 10;
					bound++;
				}
			}
			
			vy += 0.5;
		}
		
		override public function draw(buffer:BitmapData):void
		{
			matrix.identity();
			matrix.rotate(angle);
			matrix.translate(x, y);
			
			buffer.draw(shape, matrix);
		}
	}
	
	class Particle extends Actor
	{
		public var vx:Number;
		public var vy:Number;
		public var size:Number;
		
		function Particle(x:Number, y:Number)
		{
			this.x = x;
			this.y = y;
			
			vx = Math.random() * 10 - 5;
			vy = Math.random() * 6 - 6;
			size = 30;
			
			changeAction("act_main");
		}
		
		public function act_main():void
		{
			x += vx;
			y += vy;
			
			vx /= 1.1;
			vy += 0.3;
			size /= 1.1;
			
			if(size < 1) {
				vanish();
			}
		}
		
		override public function draw(buffer:BitmapData):void
		{
			buffer.fillRect(new Rectangle(x-size/2, y-size/2, size, size), 0xFFFFFFFF);
		}
	}
	
	class MinoPattern
	{
		public static const I:int = 0, O:int = 1, S:int = 2, Z:int = 3, J:int = 4, L:int = 5, T:int = 6;
		public static const COLOR:Array = [0xFF0040, 0xE8FF00, 0xFF40FF, 0x20F000, 0x8040FF, 0xFF8000, 0x00E0FF];
		public static const TABLE:Array = new Array();
		
		TABLE[I] = [[
			[0, 0, 0, 0],
			[1, 1, 1, 1],
			[0, 0, 0, 0],
			[0, 0, 0, 0]
		], [
			[0, 0, 1, 0],
			[0, 0, 1, 0],
			[0, 0, 1, 0],
			[0, 0, 1, 0]
		]];
		TABLE[O] = [[
			[0, 0, 0],
			[0, 1, 1],
			[0, 1, 1]
		]];
		TABLE[S] = [[
			[0, 0, 0],
			[0, 1, 1],
			[1, 1, 0]
		],[
			[1, 0, 0],
			[1, 1, 0],
			[0, 1, 0]
		]];
		TABLE[Z] = [[
			[0, 0, 0],
			[1, 1, 0],
			[0, 1, 1]
		],[
			[0, 0, 1],
			[0, 1, 1],
			[0, 1, 0]
		]];
		TABLE[J] = [[
			[0, 0, 0],
			[1, 1, 1],
			[0, 0, 1]
		],[
			[0, 1, 0],
			[0, 1, 0],
			[1, 1, 0]
		],[
			[0, 0, 0],
			[1, 0, 0],
			[1, 1, 1]
		],[
			[0, 1, 1],
			[0, 1, 0],
			[0, 1, 0]
		]];
		TABLE[L] = [[
			[0, 0, 0],
			[1, 1, 1],
			[1, 0, 0]
		],[
			[1, 1, 0],
			[0, 1, 0],
			[0, 1, 0]
		],[
			[0, 0, 0],
			[0, 0, 1],
			[1, 1, 1]
		],[
			[0, 1, 0],
			[0, 1, 0],
			[0, 1, 1]
		]];
		TABLE[T] = [[
			[0, 0, 0],
			[1, 1, 1],
			[0, 1, 0]
		],[
			[0, 1, 0],
			[1, 1, 0],
			[0, 1, 0]
		],[
			[0, 0, 0],
			[0, 1, 0],
			[1, 1, 1]
		],[
			[0, 1, 0],
			[0, 1, 1],
			[0, 1, 0]
		]];
	}
	
	class Key
	{
		private static var down:Array = new Array(256);
		
		public static function setListener(target:InteractiveObject):void
		{
			target.stage.focus = target;
			
			target.addEventListener(KeyboardEvent.KEY_DOWN, function (event:KeyboardEvent):void {down[event.keyCode] = true;});
			target.addEventListener(KeyboardEvent.KEY_UP, function (event:KeyboardEvent):void {down[event.keyCode] = false;});
			target.addEventListener(FocusEvent.FOCUS_OUT, onFocusOut);
		}
		
		private static function onFocusOut(event:FocusEvent):void
		{
			event.currentTarget.stage.focus = event.currentTarget;
			for(var i:String in down) {down[i] = false;}
		}
		
		public static function isDown(keycode:int):Boolean
		{
			return down[keycode];
		}
	}
	
	class KeyWatcher
	{
		private static var watchlist:Object = new Object();
		private static var repeat_wait:uint = 12;
		private static var repeat_rate:uint = 2;
		
		private var keylist:Object = new Object();
		public var lock:Boolean = false;
		
		public function KeyWatcher():void
		{
			for(var name:String in watchlist)
			{
				keylist[name] = new Object();
				keylist[name].down    = false;
				keylist[name].press   = false;
				keylist[name].release = false;
				keylist[name].repeat = false;
				keylist[name].repeatcount = 0;
			}
			return;
		}
		
		public static function watch(name:String, ...keycodes):void {watchlist[name] = keycodes;}
		public static function unwatch(name:String):void {delete watchlist[name];}
		public static function unwatchAll():void {watchlist = new Object();}
		
		public function isDown(name:String):Boolean
		{
			if(keylist[name] is Object == false) {return false;}
			
			return keylist[name].down;
		}
		
		public function isPress(name:String, getrepeat:Boolean = false):Boolean
		{
			if(keylist[name] is Object == false) {return false;}
			
			if(getrepeat == false) {return keylist[name].press;}
			else {return keylist[name].press || keylist[name].repeat;}
		}
		
		public function isRelease(name:String):Boolean
		{
			if(keylist[name] is Object == false) {return false;}
			return keylist[name].release;
		}
		
		public function update():void
		{
			var name:String, keycode:int;
			var input:Object = new Object();
			
			if(lock == false) {
				for(name in watchlist) {
					for each(keycode in watchlist[name]) {
						if(Key.isDown(keycode) == true) {input[name] = true; break;}
					}
				}
			}
			else {
				for each(keycode in watchlist[name]) {input[name] = false;}
			}
			
			for(name in keylist) {
				var key:Object = keylist[name];
				if(input[name] == true) {
					if(key.down == false) {
						key.press = true;
						key.repeat = true;
					}
					else {
						key.press = false;
						key.repeat = false;
					}
					key.down = true;
					key.release = false;
					
					key.repeatcount++;
					if(key.repeatcount == repeat_wait) {
						key.repeatcount -= repeat_rate;
						key.repeat = true;
					}
				}
				else {
					if(key.down == true) {
						key.release = true;
					}
					else {
						key.release = false;
					}
					key.down = false;
					key.press = false;
					
					key.repeatcount = 0;
					key.repeat = false;
				}
			}
			return;
		}
	}
	
	class MathEx
	{
		public static function shuffle(array:Array):Array
		{
			for(var j:int, t:Number, i:int = array.length, a:Array = array.slice();i; j = Math.random() * i, t = a[--i], a[i] = a[j], a[j] = t);
			return a;
		}
	}
	

Forked