Beautifl - Flash Gallery

Preview

Garbage TETRIS
nemu90kWw 2010年9月15日 MIT License
?
◆ ごみテトリス -ごみはゴミ箱へ-  
  
しばらくゲーム作りから離れていたので、練習がてら作るのが簡単そうなテトリスを。  
慣れてる人なら一時間あればできるらしいけど、しばらくぶりだったので一週間以上かかってしまいました。  
勘を取り戻すのには時間がかかりそうです。  
  
◆ 操作方法  
  
←・→ 移動 ↓ ソフトドロップ ↑ ハードドロップ Z・X 回転(クラシック準拠)
      /*
◆ ごみテトリス -ごみはゴミ箱へ-

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

◆ 操作方法

←・→ 移動 ↓ ソフトドロップ ↑ ハードドロップ 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;
        }
    }