In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

某テトロミノゲーム

某テトロミノゲーム

数年前に自分用に作った ezアプリを wonderfl に移植してみました。
コードはきれいではないですが、某テトロミノゲームを作りたい人の参考になれば嬉しいです。

- 機能 -
ホールド
回転入れ
ネクスト
ゴースト
出現ミノの偏り補正

- 設定メニュー -
DROP LEVEL 
- DROP TIME 経過時にどれだけミノが落ちるか

DROP TIME
- ミノが落ちる間隔

SLIDE TIME
- 横タメ

FIX TIME
- 設置してから固定するまでの時間

CLEAR LINE TIME
- ライン消去時間

ARE
- 固定してから次のミノが出現するまでの時間

KEY SETTING 
         1      2

右移動      左カーソル  sキー
左移動      右カーソル  eキー
ハードドロップ  上カーソル  fキー
ソフトドロップ  下カーソル  dキー
左回転(決定)  zキー    jキー
右回転      xキー    kキー
左回転2     cキー    lキー
ホールド     シフトキー  スペースキー

@author tkinjo
Get Adobe Flash player
by tkinjo 05 Jan 2010
    Embed
/**
 * Copyright tkinjo ( http://wonderfl.net/user/tkinjo )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/mp3f
 */

package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    
    [SWF(width="465", height="465", backgroundColor="0xffffff", frameRate="60")] 
    /**
     * 某テトロミノゲーム
     * 
     * 数年前に自分用に作った ezアプリを wonderfl に移植してみました。
     * コードはきれいではないですが、某テトロミノゲームを作りたい人の参考になれば嬉しいです。
     * 
     * - 機能 -
     * ホールド
     * 回転入れ
     * ネクスト
     * ゴースト
     * 出現ミノの偏り補正
     * 
     * - 設定メニュー -
     * DROP LEVEL 
     *  - DROP TIME 経過時にどれだけミノが落ちるか
     * 
     * DROP TIME
     *  - ミノが落ちる間隔
     * 
     * SLIDE TIME
     *  - 横タメ
     * 
     * FIX TIME
     *  - 設置してから固定するまでの時間
     * 
     * CLEAR LINE TIME
     *  - ライン消去時間
     * 
     * ARE
     *  - 固定してから次のミノが出現するまでの時間
     * 
     * KEY SETTING 
     *          1      2
     * 
     * 右移動      左カーソル  sキー
     * 左移動      右カーソル  eキー
     * ハードドロップ  上カーソル  fキー
     * ソフトドロップ  下カーソル  dキー
     * 左回転(決定)  zキー    jキー
     * 右回転      xキー    kキー
     * 左回転2     cキー    lキー
     * ホールド     シフトキー  スペースキー
     * 
     * @author tkinjo
     */
    public class Main extends Sprite
    {
        public static const APPLICATION_NAME:String = "Test";
        
        private var sceneManager:SceneManager;
        
        public function Main() 
        {
            Setting.stageWidth = stage.stageWidth;
            Setting.stageHeight = stage.stageHeight;
            
            sceneManager = SceneManager.instance;
            
            SceneManager.scene = GameSettingScene.instance;
            //SceneManager.scene = GameScene.instance;
            
            addEventListener(Event.ENTER_FRAME, enterFrameHandler);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
        }
        
        private function enterFrameHandler( event:Event ):void {
            
            sceneManager.run( graphics );
        }
        
        private function keyDownHandler( event:KeyboardEvent ):void {
            
            KeyState.pressAtKeyCode( event.keyCode );
        }
        
        private function keyUpHandler( event:KeyboardEvent ):void {
            
            KeyState.releaseAtKeyCode( event.keyCode );
        }
    }
    
}

    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.display.BitmapData;
    import flash.display.CapsStyle;
    import flash.display.JointStyle;
    import flash.display.LineScaleMode;
    import flash.display.Sprite;
    import flash.display.DisplayObject;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.net.SharedObject;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    
    interface IScene 
    {
        function init():void;
        function run():void;
        function paint( graphics:Graphics ):void;
    }
    
    
    class SceneManager 
    {
        /** --------------------------------------------------
         * 
         */
        
        private static var _scene:IScene;
        public static function get scene():IScene { return _scene; }
        public static function set scene(value:IScene):void 
        {
            _scene = value;
            scene.init();
        }
        
        private static var _previousScene:IScene;
        public static function get previousScene():IScene { return _previousScene; }
        
        /** ==================================================
         * 
         */
        
        public function SceneManager() 
        {
            if ( _instance )
                throw new IllegalOperationError ("Use instance property to get the instance");
        }
        
        public function run( graphics:Graphics ):void {
            
            scene.run();
            scene.paint( graphics );
        }
        
        /** --------------------------------------------------
         * 
         */
        
        private static var _instance:SceneManager;
        public static function get instance():SceneManager {
            
            if ( _instance == null )
                _instance = new SceneManager();
            
            return _instance;
        }
        
    }
    
    
    
    class GameSettingScene implements IScene
    {
        private var textField:TextField;
        
        private const MENU_ITEM_SIZE:Number = 30;
        private const MENU_ITEM_Y:Number = 100;
        private const MENU_ITEM_NAME_X:Number = 100;
        private const MENU_ITEM_VALUE_X:Number = 300;
        
        private const MENU_FONT:String = "_sans";
        private const MENU_FONT_SIZE:Number = 20;
        private const MENU_FONT_BOLD:Boolean = true;
        
        private const SELECT_MARKER_RADIUS:Number = 5;
        private const SELECT_MARKER_OFFSET_X:Number = -10;
        private const SELECT_MARKER_OFFSET_Y:Number = MENU_FONT_SIZE / 2 + 4;
        
        private const KEY_FIRST_REPEAT_INTERVAL:Number = 20;
        private const KEY_REPEAT_INTERVAL:Number = 2;
        
        private var selectedIndex:uint;
        private var keyRepeatedCounter:Number;
        
        
        public function GameSettingScene():void {
            
            if ( _instance )
                throw new IllegalOperationError ("Use instance property to get the instance");
            
            textField = new TextField();
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.defaultTextFormat = new TextFormat( MENU_FONT, MENU_FONT_SIZE, null, MENU_FONT_BOLD );
        }
        
        public function init():void {
            
        }
        
        public function run():void {
            
            var keyRepeatCount:uint;
            
            if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_UP_INDEX ] ) && selectedIndex > 0 ) {
                
                selectedIndex--;
                
            } else if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_DOWN_INDEX ] ) && selectedIndex < Setting.GAME_SETTINGS.length - 1 ) {
                
                selectedIndex++;
                
            } else if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_LEFT_INDEX ] ) && 
                    Setting.gameSettingValues[ selectedIndex ] > Setting.GAME_SETTINGS[ selectedIndex ][ Setting.GAME_SETTING_ITEM_MIN_VALUE_INDEX ] ) {
                
                Setting.gameSettingValues[ selectedIndex ]--;
                
            } else if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_RIGHT_INDEX ] ) && 
                    Setting.gameSettingValues[ selectedIndex ] < Setting.GAME_SETTINGS[ selectedIndex ][ Setting.GAME_SETTING_ITEM_MAX_VALUE_INDEX ] ) {
                
                Setting.gameSettingValues[ selectedIndex ]++;
                
            } else if ( KeyState.getReleased( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_A_INDEX ] ) ) {
                
                if ( Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_KEY_SETTING_INDEX ] == 1 )
                    settingKeys( Setting.KEY_SETTINGS );
                
                else 
                    settingKeys( Setting.KEY_SETTINGS_2 );
                
                Setting.flush();
                
                SceneManager.scene = GameScene.instance;
            }
        }
        
        private function settingKeys( keySetting:Array ):void {
            
            for ( var i:uint = 0; i < keySetting.length; i++ )
                Setting.keySettingValues[ i ] = keySetting[ i ][ Setting.KEY_SETTING_ITEM_DEFAULT_VALUE_INDEX ];
        }
        
        private function isRepeatInterval( keyString:String ):Boolean {
            
            return KeyState.isRepeatInterval( keyString, KEY_FIRST_REPEAT_INTERVAL, KEY_REPEAT_INTERVAL );
        }
        
        public function paint( graphics:Graphics ):void {
            
            graphics.clear();
            
            for ( var i:uint = 0; i < Setting.GAME_SETTINGS.length; i++ )
                drawString( graphics, textField, MENU_ITEM_NAME_X, MENU_ITEM_Y + MENU_ITEM_SIZE * i, Setting.GAME_SETTINGS[ i ][ Setting.GAME_SETTING_ITEM_NAME_INDEX ] );
            
            for ( i = 0; i < Setting.GAME_SETTINGS.length; i++ )
                drawString( graphics, textField, MENU_ITEM_VALUE_X, MENU_ITEM_Y + MENU_ITEM_SIZE * i, Setting.gameSettingValues[ i ].toString() );
            
            graphics.beginFill( 0 );
            graphics.drawCircle( MENU_ITEM_NAME_X + SELECT_MARKER_OFFSET_X, MENU_ITEM_Y + MENU_ITEM_SIZE * selectedIndex + SELECT_MARKER_OFFSET_Y, SELECT_MARKER_RADIUS );
            graphics.endFill();
        }
        
        
        
        private static var _instance:GameSettingScene;
        public static function get instance():GameSettingScene {
            
            if ( _instance == null )
                _instance = new GameSettingScene();
            
            return _instance;
        }
        
    }
    
    class KeyState 
    {
        private static var repeated:Vector.<uint> = new Vector.<uint>( 255 );
        private static var pressed :Vector.<Boolean> = new Vector.<Boolean>( 255 );
        private static var released:Vector.<Boolean> = new Vector.<Boolean>( 255 );
        
        private static var manualRepeatedCounter:Vector.<uint> = new Vector.<uint>( 255 );
        
        /** --------------------------------------------------
         * 
         */
        
        public static const SHIFT:String = "shift";
        public static const CTRL :String = "ctrl";
        public static const ALT  :String = "alt";
        public static const SPACE:String = "space";
        
        public static const LEFT :String = "left";
        public static const UP   :String = "up";
        public static const RIGHT:String = "right";
        public static const DOWN :String = "down";
        
        /** --------------------------------------------------
         * 
         */
        
        public static const KEY_SHIFT:uint = 16;
        public static const KEY_CTRL :uint = 17;
        public static const KEY_ALT  :uint = 18;
        public static const KEY_SPACE:uint = 32;
        
        public static const KEY_LEFT :uint = 37;
        public static const KEY_UP   :uint = 38;
        public static const KEY_RIGHT:uint = 39;
        public static const KEY_DOWN :uint = 40;
        
        
        
        public static const CHAR_LARGE_A:uint = 65;
        public static const CHAR_SMALL_A:uint = 97;
        public static const KEY_A       :uint = 65;
        
        public static const NUM_ALPHABET:uint = 26;
        
        
        
        public static const CHAR_0      :uint = 48;
        public static const KEY_0       :uint = 0;
        public static const KEY_NUMPAD_0:uint = 96;
        
        public static const NUM_NUMBER:uint = 10;
        
        
        /** ==================================================
         * 
         */
        
        public static function reset():void {
            
            repeated = new Vector.<uint>( 255 );
            pressed  = new Vector.<Boolean>( 255 );
            released = new Vector.<Boolean>( 255 );
            
            manualRepeatedCounter = new Vector.<uint>( 255 );
        }
        
        public static function press( string:String ):void {
            
            var keyCode:Number = getKeyCode( string );
            
            if ( keyCode > 0 )
                pressAtKeyCode( keyCode );
        }
        
        public static function release( string:String ):void {
            
            var keyCode:Number = getKeyCode( string );
            
            if ( keyCode > 0 )
                releaseAtKeyCode( keyCode );
        }
        
        public static function manualRepeatCount( string:String ):void {
            
            var keyCode:Number = getKeyCode( string );
            
            if ( keyCode > 0 )
                manualRepeatCountAtKeyCode( keyCode );
        }
        
        /** --------------------------------------------------
         * 
         */
        
        public static function pressAtKeyCode( keyCode:uint ):void {
            
            if ( repeated[ keyCode ] ) {
                repeated[ keyCode ]++;
                return;
            }
            
            repeated[ keyCode ] = 1;
            pressed[  keyCode ] = true;
            released[ keyCode ] = false;
            
            manualRepeatedCounter[ keyCode ] = 1;
        }
        
        public static function releaseAtKeyCode( keyCode:uint ):void {
            
            repeated[ keyCode ] = 0;
            pressed[  keyCode ] = false;
            released[ keyCode ] = true;
            
            manualRepeatedCounter[ keyCode ] = 0;
        }
        
        public static function manualRepeatCountAtKeyCode( keyCode:uint ):void {
            
            manualRepeatedCounter[ keyCode ]++;
        }
        
        
        
        /** --------------------------------------------------
         * 
         */
        
        public static function getRepeated( string:String ):Boolean {
            
            var keyCode:Number = getKeyCode( string );
            
            if( keyCode > 0 )
                return getRepeatedAtKeyCode( keyCode );
            
            return false;
        }
        public static function getRepeatedCount( string:String ):uint {
            
            var keyCode:Number = getKeyCode( string );
            
            if( keyCode > 0 )
                return getRepeatedCountAtKeyCode( keyCode );
            
            return 0;
        }
        
        public static function getReleased( string:String ):Boolean {
            
            var keyCode:Number = getKeyCode( string );
            
            if( keyCode > 0 )
                return getReleasedAtKeyCode( keyCode );
            
            return false;
        }
        
        public static function getPressed( string:String ):Boolean {
            
            var keyCode:Number = getKeyCode( string );
            
            if( keyCode > 0 )
                return getPressedAtKeyCode( keyCode );
            
            return false;
        }
        
        public static function getManualRepeatedCount( string:String ):uint {
            
            var keyCode:Number = getKeyCode( string );
            
            if( keyCode > 0 )
                return getManualRepeatedCountAtKeyCode( keyCode );
            
            return 0;
        }
        
        
        /** --------------------------------------------------
         * 
         */
        
        public static function getRepeatedAtKeyCode( keyCode:uint ):Boolean {
            
            return ( repeated[ keyCode ] > 0 ) ? true : false;
        }
        public static function getRepeatedCountAtKeyCode( keyCode:uint ):uint {
            
            return repeated[ keyCode ];
        }
        
        public static function getReleasedAtKeyCode( keyCode:uint ):Boolean {
            
            var result:Boolean = released[ keyCode ];
            released[ keyCode ] = false;
            
            return result;
        }
        
        public static function getPressedAtKeyCode( keyCode:uint ):Boolean {
            
            var result:Boolean = pressed[ keyCode ];
            pressed[ keyCode ] = false;
            
            return result;
        }
        
        public static function getManualRepeatedCountAtKeyCode( keyCode:uint ):uint {
            
            return manualRepeatedCounter[ keyCode ];
        }
        
        
        /** --------------------------------------------------
         * 
         */
        
        public static function getKeyCode( string:String ):Number {
            
            if( string.length == 1 ) {
                
                return charCodeToKeyCode( string.charCodeAt() );
            }
            
            switch ( string ) {
                
                case SHIFT:
                    return KEY_SHIFT;
                
                case CTRL:
                    return KEY_CTRL;
                
                case ALT:
                    return KEY_ALT;
                
                case SPACE:
                    return KEY_SPACE;
                
                case LEFT:
                    return KEY_LEFT;
                
                case UP:
                    return KEY_UP;
                
                case RIGHT:
                    return KEY_RIGHT;
                
                case DOWN:
                    return KEY_DOWN;
            }
            
            return -1;
        }
        
        
        
        public static function charCodeToKeyCode( charCode:uint ):Number {
            
            // A-Z
            if ( CHAR_LARGE_A <= charCode && ( charCode - CHAR_LARGE_A ) < NUM_ALPHABET ) {
                
                return charCode;
                
            // a-z
            } else if ( CHAR_SMALL_A <= charCode && ( charCode - CHAR_SMALL_A ) < NUM_ALPHABET ) {
                
                return KEY_A + ( charCode - CHAR_SMALL_A );
                
            // 0-9
            } else if ( CHAR_0 <= charCode && ( charCode - CHAR_0 ) < NUM_NUMBER ) {
                
                return charCode;
            }
            
            return -1;
        }
        
        
        public static function isRepeatInterval( keyString:String, keyFirstRepeatInterval:uint, keyRepeatInterval:uint ):Boolean {
            
            if ( !KeyState.getRepeated( keyString ) )
                return false;
            
            if ( KeyState.getPressed( keyString ) )
                return true;
            
            
            
            KeyState.manualRepeatCount( keyString );
            
            var keyRepeatCount:uint = KeyState.getManualRepeatedCount( keyString );
            
            if ( 
                keyRepeatCount == keyFirstRepeatInterval || 
                ( 
                    keyRepeatCount > keyFirstRepeatInterval && 
                    ( keyRepeatCount - keyFirstRepeatInterval ) % keyRepeatInterval == 0 
                )
            ) {
                return true;
            }
            
            return false;
        }
    }
    
    class Setting 
    {
        public static const GAME_SETTING_ITEM_NAME_INDEX:uint          = 0;
        public static const GAME_SETTING_ITEM_DEFAULT_VALUE_INDEX:uint = 1;
        public static const GAME_SETTING_ITEM_MIN_VALUE_INDEX:uint     = 2;
        public static const GAME_SETTING_ITEM_MAX_VALUE_INDEX:uint     = 3;
        public static const GAME_SETTINGS:Array  = [ 
                [ "DROP LEVEL" , 1 , 0, 20  ], 
                [ "DROP TIME"  , 100, 0, 100 ], 
                [ "SLIDE TIME" , 10, 0, 100 ], 
                [ "FIX TIME"   , 100, 0, 100 ], 
                [ "CLEAR LINE TIME" , 10, 0, 100 ], 
                [ "ARE"        , 10, 0, 100 ], 
                [ "KEY SETTING", 1,  1, 2   ] 
            ];
        public static const GAME_SETTING_VALUE_DROP_LEVEL_INDEX:uint  = 0;
        public static const GAME_SETTING_VALUE_DROP_TIME_INDEX:uint   = 1;
        public static const GAME_SETTING_VALUE_SLIDE_TIME_INDEX:uint  = 2;
        public static const GAME_SETTING_VALUE_FIX_TIME_INDEX:uint    = 3;
        public static const GAME_SETTING_VALUE_CLEAR_TIME_INDEX:uint  = 4;
        public static const GAME_SETTING_VALUE_ARE_INDEX:uint         = 5;
        public static const GAME_SETTING_VALUE_KEY_SETTING_INDEX:uint = 6;
        
        
        /** --------------------------------------------------
         * 
         */
        
        public static const KEY_SETTING_ITEM_NAME_INDEX:uint          = 0;
        public static const KEY_SETTING_ITEM_DEFAULT_VALUE_INDEX:uint = 1;
        public static const KEY_SETTINGS:Array  = [ 
                [ "LEFT" , KeyState.LEFT  ], 
                [ "UP"   , KeyState.UP    ], 
                [ "RIGHT", KeyState.RIGHT ], 
                [ "DOWN" , KeyState.DOWN  ], 
                [ "A", "z" ], 
                [ "B", "x" ], 
                [ "C", "c" ], 
                [ "D", KeyState.SHIFT ] 
            ];
        public static const KEY_SETTINGS_2:Array  = [ // temp
                [ "LEFT" , "s"  ], 
                [ "UP"   , "e" ], 
                [ "RIGHT", "f" ], 
                [ "DOWN" , "d" ], 
                [ "A", "j" ], 
                [ "B", "k" ], 
                [ "C", "l" ], 
                [ "D", KeyState.SPACE ] 
            ];
        
        public static const KEY_SETTING_VALUE_LEFT_INDEX:uint  = 0;
        public static const KEY_SETTING_VALUE_UP_INDEX:uint    = 1;
        public static const KEY_SETTING_VALUE_RIGHT_INDEX:uint = 2;
        public static const KEY_SETTING_VALUE_DOWN_INDEX:uint  = 3;
        public static const KEY_SETTING_VALUE_A_INDEX:uint = 4;
        public static const KEY_SETTING_VALUE_B_INDEX:uint = 5;
        public static const KEY_SETTING_VALUE_C_INDEX:uint = 6;
        public static const KEY_SETTING_VALUE_D_INDEX:uint = 7;
        
        /** ==================================================
         * 
         */
        private static var sharedObject:SharedObject;
        
        public static var gameSettingValues:Vector.<Number>;
        public static var keySettingValues:Vector.<String>;
        
        public static var stageWidth:Number;
        public static var stageHeight:Number;
        
        /** ==================================================
         * 
         */
        
        public function Setting():void {
            
            if ( _instance )
                throw new IllegalOperationError ("Use instance property to get the instance");
            
            
            
            // load
            sharedObject = SharedObject.getLocal( Main.APPLICATION_NAME );
            
            
            
            var i:uint;
            
            
            
            //gameSettingValues
            var tempGameSettingValues:Vector.<Number> = sharedObject.data.gameSettingValues as Vector.<Number>;
            
            if ( !tempGameSettingValues ) {
                
                gameSettingValues = new Vector.<Number>( GAME_SETTINGS.length )
                
                for ( i = 0; i < GAME_SETTINGS.length; i++ )
                    gameSettingValues[ i ] = GAME_SETTINGS[ i ][ GAME_SETTING_ITEM_DEFAULT_VALUE_INDEX ];
                
            } else {
                
                gameSettingValues = tempGameSettingValues.concat();
            }
            
            
            
            
            //keySettingValues
            //keySettingValues = sharedObject.data.keySettingValues; //cast error
            
            var tempKeySettingValues:Vector.<Object> = sharedObject.data.keySettingValues as Vector.<Object>;
            if ( tempKeySettingValues ) {
                keySettingValues = new Vector.<String>( KEY_SETTINGS.length );
                
                for ( var key:String in tempKeySettingValues )
                    keySettingValues[ key ] = tempKeySettingValues[ key ] as String;
            }
            
            if ( !keySettingValues ) {
                
                keySettingValues = new Vector.<String>( KEY_SETTINGS.length );
                
                for ( i = 0; i < KEY_SETTINGS.length; i++ )
                    keySettingValues[ i ] = KEY_SETTINGS[ i ][ KEY_SETTING_ITEM_DEFAULT_VALUE_INDEX ];
            }
        }
        
        public static function flush():void {
            
            sharedObject.data.gameSettingValues = gameSettingValues;
            sharedObject.data.keySettingValues = keySettingValues;
            
            sharedObject.flush();
        }
        
        public static function flushGameSettingValues():void {
            
            sharedObject.data.gameSettingValues = gameSettingValues;
            sharedObject.flush();
        }
        
        public static function flushKeySettingValues():void {
            
            sharedObject.data.keySettingValues = keySettingValues;
            sharedObject.flush();
        }
        
        public static function clear():void {
            
            sharedObject.clear();
        }
        
        private static var _instance:Setting = instance;
        public static function get instance():Setting {
            
            if ( _instance == null )
                _instance = new Setting();
            
            return _instance;
        }
        
    }
    
    class GameSystem extends EventDispatcher
    {
        /**
         * ...
         * @eventType com.tkinjo.tetromino.system.Field.GAME_OVER
         */
        [Event(name = "gameOver", type = "com.tkinjo.tetromino.system.GameSystem")] 
        public static const GAME_OVER:String = "gameOver";
        /**
         * ...
         * @eventType com.tkinjo.tetromino.system.Field.GAME_OVER
         */
        [Event(name = "entry", type = "com.tkinjo.tetromino.system.GameSystem")] 
        public static const ENTRY:String = "entry";
        
        /**
         * ...
         * @eventType com.tkinjo.tetromino.event.ClearLineEvent.CLEAR_LINE
         */
        [Event(name = "clearLine", type = "com.tkinjo.tetromino.event.ClearLineEvent")] 
        
        /** ==================================================
         * 
         */
        
        public static const FIELD_HORIZONTAL_LENGTH:Number  = 10;
        public static const FIELD_VERTICAL_LENGTH:Number    = 20;
        
        
        private var _data:Array;
        public function get data():Array { return _data; }
        
        public function get drawData():Array { 
            
            var drawData:Array = new Array( FIELD_HORIZONTAL_LENGTH );
            
            for ( var i:uint = 0; i < drawData.length; i++ ) {
                
                drawData[ i ] = new Array( FIELD_VERTICAL_LENGTH );
                
                for ( var j:uint = 0; j < drawData[ i ].length; j++ ) {
                    
                    drawData[ i ][ j ] = data[ i + 4 ][ j + 4 ];
                }
            }
            
            if( currentMino && !( areTimerRunning || clearLineTimerRunning ) ) {
            
                for ( i = 0; i < currentMino.length; i++ ) {
                    
                    for ( j = 0; j < currentMino.length; j++ ) {
                        
                        if ( currentMino.data[ i ][ j ] == 0 || currentMinoY + j - 4 < 0 )
                            continue
                        
                        drawData[ currentMinoX + i - 4 ][ currentMinoY + j - 4 ] = currentMino.data[ i ][ j ];
                    }
                }
            }
            
            return drawData;
        }
        
        
        
        private var minoManager:MinoManager;
        
        private var _currentMino:Mino;
        public function get currentMino():Mino { return _currentMino; }
        
        private var currentMinoX:uint;
        public function get ghostMinoX():uint { return currentMinoX - 4; }
        
        private var currentMinoY:uint;
        public function get ghostMinoY():uint { return dropY - 4; }
        
        private var maxDroppedY:uint;
        
        private var rotationSystem:IRotationSystem;
        
        private var held:Boolean;
        
        public var dropLevel:uint;
        
        public var dropTime:uint;
        private var dropTimer:int;
        
        public var fixTime:uint;
        private var fixTimer:int;
        private var _fixTimerRunning:Boolean;
        public function get fixTimerRunning():Boolean { return _fixTimerRunning; }
        
        public var clearLineTime:uint;
        private var clearLineTimer:int;
        private var _clearLineTimerRunning:Boolean;
        public function get clearLineTimerRunning():Boolean { return _clearLineTimerRunning; }
        private var _clearLineNums:Vector.<uint>;
        public function get clearLineNums():Vector.<uint> { return _clearLineNums; }
        
        public var are:uint;
        private var areTimer:int;
        private var _areTimerRunning:Boolean;
        public function get areTimerRunning():Boolean { return _areTimerRunning; }
        
        private var time:uint;
        
        private var _running:Boolean;
        public function get running():Boolean { return _running; }
        
        /** ==================================================
         * 
         */
        
        public function GameSystem( minoManager:MinoManager, rotationSystem:IRotationSystem ) 
        {
            this.minoManager = minoManager;
            this.rotationSystem = rotationSystem;
            
            createField();
        }
        
        private function createField():void {
            
            var i:uint, j:uint;
            
            _data = new Array( FIELD_HORIZONTAL_LENGTH + 4 * 2 );
            
            for ( i = 0; i < FIELD_HORIZONTAL_LENGTH + 4 * 2; i++ ) {
                
                _data[ i ] = new Array( FIELD_VERTICAL_LENGTH + 4 * 2 );
                
                for ( j = 0; j < data[ 0 ].length; j++ ) {
                    
                    if ( i < 4 || i >= data.length - 4 || j >= data[ 0 ].length - 4 ) {
                        
                        data[ i ][ j ] = -1;
                        
                    } else {
                        
                        data[ i ][ j ] = 0;
                    }
                }
            }
        }
        
        public function start():void {
            
            _running = true;
            next();
        }
        
        /** --------------------------------------------------
         * run
         */
        public function run():void {
            //*
            if ( !running )
                return;
            
            if ( dropTimer <= 0 ) {
                
                for ( var i:uint = 0; i < dropLevel; i++ ) {
                    
                    if ( !softDrop() )
                        break;
                }
                
                dropTimer = dropTime;
            }
            
            if ( fixTimerRunning && !areTimerRunning && !clearLineTimerRunning ) {
                
                if ( fixTimer <= 0 ) {
                    
                    fix();
                    _fixTimerRunning = false;
                    
                    setClearLineNums();
                    
                    if ( isClearLine() ) {
                        
                        _clearLineTimerRunning = true;
                        dispatchEvent( new ClearLineEvent( ClearLineEvent.CLEAR, clearLineNums ) );
                        
                    } else {
                        
                        _areTimerRunning = true;
                    }
                }
                
                fixTimer--;
            }
            
            if ( clearLineTimerRunning ) {
                
                if ( clearLineTimer <= 0 ) {
                    
                    _clearLineTimerRunning = false;
                    
                    clearLine();
                    dispatchEvent( new ClearLineEvent( ClearLineEvent.CLEARED, clearLineNums ) );
                    
                    _areTimerRunning = true;
                }
                
                clearLineTimer--;
            }
            
            if ( areTimerRunning ) {
                
                if ( areTimer <= 0 ) {
                    
                    _areTimerRunning = false;
                    
                    next();
                    dispatchEvent( new Event( ENTRY ) );
                    
                    if ( isGameOver() ) {
                        
                        dispatchEvent( new Event( GAME_OVER ) );
                        _running = false;
                        
                        return;
                    }
                }
                
                areTimer--;
            }
            
            
            if( !( clearLineTimerRunning || areTimerRunning ) )
                dropTimer--;
            
            time++;//*/
        }
        
        
        /** --------------------------------------------------
         * control
         */
        
        public function moveLeft():Boolean {
            
            if ( !isOverlap( -1, 0 ) ) {
                
                currentMinoX--;
                return true;
            }
            
            return false;
        }
        
        public function moveRight():Boolean {
            
            if ( !isOverlap( 1, 0 ) ) {
                
                currentMinoX++;
                return true;
            }
            
            return false;
        }
        
        public function softDrop():Boolean {
            
            if ( !isOverlap( 0, 1 ) ) {
                
                currentMinoY++;
                
                if ( currentMinoY > maxDroppedY ) {
                    
                    maxDroppedY = currentMinoY;
                    fixTimer = fixTime;
                    _fixTimerRunning = false;
                }
                
                return true;
            }
            
            _fixTimerRunning = true;
            
            return false;
        }
        
        public function hardDrop():void {
            
            _fixTimerRunning = true;
            fixTimer = 0;
        }
        
        private function get dropY():uint { 
            
            for ( var i:uint = 0; i < FIELD_VERTICAL_LENGTH + 4; i++ ) {
                
                if ( isOverlap( 0, i ) )
                    return  i - 1 + currentMinoY;
            }
            
            return 0;
        }
        
        
        
        
        public function hold():Boolean {
            
            if ( held || !running )
                return false;
            
            held = true;
            
            minoManager.hold();
            
            reset();
            
            return true;
        }
        
        public function spinLeft():Boolean {
            
            return trySpin( SpinDirection.LEFT );
        }
        
        public function spinRight():Boolean {
            
            return trySpin( SpinDirection.RIGHT );
        }
        
        private function trySpin( direction:String ):Boolean {
            
            spinCurrentMino( direction );
            
            var spunPoint:Point = rotationSystem.getSpunPoint( this, direction );
            
            if ( spunPoint ) {
                
                currentMinoX += spunPoint.x;
                currentMinoY += spunPoint.y;
                
                return true;
            }
            
            spinCurrentMino( SpinDirection.reverse( direction ) );
            
            return false;
        }
        
        private function spinCurrentMino( direction:String ):void {
            
            if( !currentMino )
                return;
            
            if( direction == SpinDirection.LEFT )
                currentMino.spinLeft();
            
            else if( direction == SpinDirection.RIGHT )
                currentMino.spinRight();
        }
        
        /** --------------------------------------------------
         * 
         * @param    dx
         * @param    dy
         * @return
         */
        public function isOverlap( dx:int, dy:int ):Boolean {
            
            if ( !currentMino )
                return true;
            
            for ( var i:uint = 0; i < currentMino.length; i++ ) {
                
                for ( var j:uint = 0; j < currentMino.length; j++ ) {
                    
                    if( currentMino.data[ i ][ j ] > 0 && data[ currentMinoX + i + dx ][ currentMinoY + j + dy ] != 0 )
                        return true;
                }
            }
            
            return false;
        }
        
        /** --------------------------------------------------
         * fix
         */
        public function fix():void {
            
            if ( !currentMino ) {
                
                next();
                return;
            }
            
            currentMinoY = dropY;
            
            for ( var i:uint = 0; i < currentMino.length; i++ ) {
                
                for ( var j:uint = 0; j < currentMino.length; j++ ) {
                    
                    if ( currentMino.data[ i ][ j ] == 0 )
                        continue;
                    
                    data[ currentMinoX + i ][ currentMinoY + j ] = currentMino.data[ i ][ j ];
                }
            }
        }
        
        /** --------------------------------------------------
         * clearLine
         */
        
        private function isClearLine():Boolean {
            
            if ( clearLineNums.length > 0 )
                return true;
            
            return false;
        }
        
        private function clearLine():void {
            
            var clearLineCounter:uint;
            
            for ( var i:int = FIELD_VERTICAL_LENGTH + 4 - 1; i >= 0; i-- ) {
                
                if ( isClearLineAt( i ) ) {
                    
                    clearLineAt( i );
                    clearLineCounter++;
                    
                } else {
                    
                    moveLine( i, clearLineCounter );
                }
            }
        }
        
        private function setClearLineNums():Vector.<uint> {
            
            _clearLineNums = new Vector.<uint>();
            
            for ( var i:int = FIELD_VERTICAL_LENGTH + 4 - 1; i >= 0; i-- ) {
                
                if ( isClearLineAt( i ) )
                    clearLineNums.push( i );
            }
            
            if ( clearLineNums.length > 0 )
                return clearLineNums;
            
            return null;
        }
        
        private function clearLineAt( lineNum:uint ):void {
            
            for ( var i:uint = 0; i < FIELD_HORIZONTAL_LENGTH; i++ ) 
                data[ i + 4 ][ lineNum ] = 0;
        }
        
        private function moveLine( lineNum:uint, how:uint ):void {
            
            for ( var i:uint = 0; i < FIELD_HORIZONTAL_LENGTH; i++ ) 
                data[ i + 4 ][ lineNum + how ] = data[ i + 4 ][ lineNum ];
        }
        
        private function isClearLineAt( lineNum:uint ):Boolean {
            
            var blockCounter:uint;
            
            for ( var i:uint = 0; i < FIELD_HORIZONTAL_LENGTH; i++ ) {
                
                if ( data[ i + 4 ][ lineNum ] > 0 )
                    blockCounter++;
            }
            
            if ( blockCounter == FIELD_HORIZONTAL_LENGTH )
                return true;
            
            return false;
        }
        
        /** --------------------------------------------------
         * next
         */
        public function next():void {
            
            held = false;
            
            minoManager.next();
            
            reset();
        }
        
        private function reset():void {
            
            _currentMino = minoManager.currentMino;
            
            currentMinoX = 7;
            currentMinoY = currentMino.length == 3 ? 2 : 1;
            
            dropTimer = dropTime;
            
            areTimer = are;
            _areTimerRunning = false;
            
            fixTimer = fixTime;
            _fixTimerRunning = false;
            
            clearLineTimer = clearLineTime;
            _clearLineTimerRunning = false;
        }
        
        /** --------------------------------------------------
         * gameOver
         */
        private function isGameOver():Boolean {
            
            for ( var i:uint = 0; i < currentMino.length; i++ ) {
                
                for ( var j:uint = 0; j < currentMino.length; j++ ) {
                    
                    if ( currentMino.data[ i ][ j ] > 0 && data[ currentMinoX + i ][ currentMinoY + j ] > 0 )
                        return true;
                }
            }
            
            return false;
        }
        
    }
    
    class ClearLineEvent extends Event
    {
        public static const CLEAR:String = "clearLine";
        public static const CLEARED:String = "cleared";
        
        private var _lineNums:Vector.<uint>;
        public function get lineNums():Vector.<uint> { return _lineNums; }
        
        public function ClearLineEvent( type:String, lineNums:Vector.<uint>, bubbles:Boolean = false, cancelable:Boolean = false ) 
        {
            _lineNums = lineNums;
            
            super( type, bubbles, cancelable );
        }
    }
    
    class MinoManager 
    {
        public static const MINO_I:String = "minoI";
        public static const MINO_I_COLOR:uint = 0x0087ceeb;
        public static const MINO_I_DATA:Array = [ 
            /*[ 0, 0, 0, 0 ],
            [ 1, 1, 1, 1 ],
            [ 0, 0, 0, 0 ],
            [ 0, 0, 0, 0 ]//*/
            [ 0, 1, 0, 0 ],
            [ 0, 1, 0, 0 ],
            [ 0, 1, 0, 0 ],
            [ 0, 1, 0, 0 ]
        ];
        
        public static const MINO_O:String = "minoO";
        public static const MINO_O_COLOR:uint = 0x00ffff00;
        public static const MINO_O_DATA:Array = [ 
            [ 0, 0, 0, 0 ],
            [ 0, 2, 2, 0 ],
            [ 0, 2, 2, 0 ],
            [ 0, 0, 0, 0 ]
        ];
        
        public static const MINO_S:String = "minoS";
        public static const MINO_S_COLOR:uint    = 0x0000ff00;
        public static const MINO_S_DATA:Array = [ 
            /*[ 0, 3, 3 ],
            [ 3, 3, 0 ],
            [ 0, 0, 0 ]//*/
            [ 0, 3, 0 ],
            [ 3, 3, 0 ],
            [ 3, 0, 0 ]
        ];
        
        public static const MINO_Z:String = "minoZ";
        public static const MINO_Z_COLOR:uint = 0x00ff0000;
        public static const MINO_Z_DATA:Array = [ 
            /*[ 4, 4, 0 ],
            [ 0, 4, 4 ],
            [ 0, 0, 0 ]//*/
            [ 4, 0, 0 ],
            [ 4, 4, 0 ],
            [ 0, 4, 0 ]
        ];
        
        public static const MINO_J:String = "minoJ";
        public static const MINO_J_COLOR:uint = 0x000000ff;
        public static const MINO_J_DATA:Array = [ 
            /*[ 5, 0, 0 ],
            [ 5, 5, 5 ],
            [ 0, 0, 0 ]//*/
            [ 5, 5, 0 ],
            [ 0, 5, 0 ],
            [ 0, 5 ,0 ]
        ];
        
        public static const MINO_L:String = "minoL";
        public static const MINO_L_COLOR:uint = 0x00ffa500;
        public static const MINO_L_DATA:Array = [ 
            /*[ 0, 0, 6 ],
            [ 6, 6, 6 ],
            [ 0, 0, 0 ]//*/
            [ 0, 6, 0 ],
            [ 0, 6, 0 ],
            [ 6, 6, 0 ]
        ];
        
        public static const MINO_T:String = "minoT";
        public static const MINO_T_COLOR:uint = 0x00800080;
        public static const MINO_T_DATA:Array = [ 
            /*[ 0, 7, 0 ],
            [ 7, 7, 7 ],
            [ 0, 0, 0 ]//*/
            [ 0, 7, 0 ],
            [ 7, 7, 0 ],
            [ 0, 7, 0 ]
        ];
        
        public static const NUM_NEXT_MINO:Number = 3;
        
        public static const MINO_TYPES:Vector.<String> = Vector.<String>( [ MINO_I, MINO_O, MINO_S, MINO_Z, MINO_J, MINO_L, MINO_T ] );
        public static const MINO_DATAS:Vector.<Array> = Vector.<Array>( [ MINO_I_DATA, MINO_O_DATA, MINO_S_DATA, MINO_Z_DATA, MINO_J_DATA, MINO_L_DATA, MINO_T_DATA ] );
        public static const MINO_COLORS:Vector.<uint> = Vector.<uint>( [ MINO_I_COLOR, MINO_O_COLOR, MINO_S_COLOR, MINO_Z_COLOR, MINO_J_COLOR, MINO_L_COLOR, MINO_T_COLOR ] );
        
        /** --------------------------------------------------
         * 
         */
        private var _currentMino:Mino;
        public function get currentMino():Mino { return _currentMino; }
        
        private var _holdMino:Mino;
        public function get holdMino():Mino { return _holdMino; }
        
        private var _nextMinos:Vector.<Mino>;
        public function get nextMinos():Vector.<Mino> { return _nextMinos; }
        
        private var nextMinoStock:Vector.<uint>;
        
        /** ==================================================
         * 
         */
        
        public function MinoManager() 
        {
            resetNextMinoStock();
            
            _nextMinos = new Vector.<Mino>( NUM_NEXT_MINO );
            for ( var i:uint = 0; i < NUM_NEXT_MINO; i++ )
                next();
        }
        
        public function next():void {
            
            var nextMinoStockIndex:uint = Math.random() * nextMinoStock.length;
            
            var nextMinoIndex:uint = nextMinoStock[ nextMinoStockIndex ];
            nextMinoStock.splice( nextMinoStockIndex, 1 );
            
            if ( nextMinoStock.length == 0 )
                resetNextMinoStock();
            
            
            
            // set currentMino
            _currentMino = _nextMinos[ 0 ];
            
            // set nextMinos
            for ( var i:uint = 0; i < nextMinos.length - 1; i++ )
                _nextMinos[ i ] = nextMinos[ i + 1 ];
            
            _nextMinos[ nextMinos.length - 1 ] = new Mino( MINO_TYPES[ nextMinoIndex ] );
        }
        
        private function resetNextMinoStock():void {
            
            nextMinoStock = new Vector.<uint>( MINO_TYPES.length );
            
            for ( var i:uint = 0; i < nextMinoStock.length; i++ )
                nextMinoStock[ i ] = i;
        }
        
        public function hold():void {
            
            
            if ( holdMino ) {
                
                var heldMino:Mino = holdMino;
                _holdMino = new Mino( currentMino.type );
                _currentMino = heldMino;
                
            } else {
                
                _holdMino = currentMino;
                next();
            }
        }
        
        public static function getMinoData( type:String ):Array {
            
            switch( type ) {
                
                case MINO_I:
                    return MINO_I_DATA;
                
                case MINO_O:
                    return MINO_O_DATA;
                
                case MINO_S:
                    return MINO_S_DATA;
                
                case MINO_Z:
                    return MINO_Z_DATA;
                
                case MINO_J:
                    return MINO_J_DATA;
                
                case MINO_L:
                    return MINO_L_DATA;
                
                case MINO_T:
                    return MINO_T_DATA;
            }
            
            return null;
        }
        
        public static function getMinoColor( type:String ):uint {
            
            switch( type ) {
                
                case MINO_I:
                    return MINO_I_COLOR;
                
                case MINO_O:
                    return MINO_O_COLOR;
                
                case MINO_S:
                    return MINO_S_COLOR;
                
                case MINO_Z:
                    return MINO_Z_COLOR;
                
                case MINO_J:
                    return MINO_J_COLOR;
                
                case MINO_L:
                    return MINO_L_COLOR;
                
                case MINO_T:
                    return MINO_T_COLOR;
            }
            
            return 0;
        }
        
        public static function getMinoColorAtDataValue( index:uint ):uint {
            
            return MINO_COLORS[ index - 1 ];
        }
    }
    
    class Mino 
    {
        public static const ROTATION_0:uint   = 0;
        public static const ROTATION_90:uint  = 1;
        public static const ROTATION_180:uint = 2;
        public static const ROTATION_270:uint = 3;
        
        private var _type:String;
        public function get type():String { return _type; }
        
        private var _rotationState:uint;
        public function get rotationState():uint { return _rotationState; }
        
        private var _data:Array;
        public function get data():Array { return _data; }
        
        private var _length:uint;
        public function get length():uint { return _length; }
        
        private var _color:uint;
        public function get color():uint { return _color; }
        
        public function Mino( type:String ) 
        {
            _type = type;
            _color = MinoManager.getMinoColor( type );
            _data = MinoManager.getMinoData( type );
            _length = data.length;
        }
        
        public function spinLeft():void {
            
            if( rotationState - 1 >= ROTATION_0 )
                _rotationState--;
            
            else 
                _rotationState = ROTATION_270;
            
            spin( SpinDirection.LEFT );
        }
        
        public function spinRight():void {
            
            if( rotationState + 1 <= ROTATION_270 )
                _rotationState++;
            
            else 
                _rotationState = ROTATION_0;
            
            spin( SpinDirection.RIGHT );
        }
        
        private function spin( direction:String ):void {
            
            var i:uint, j:uint;
            
            var spunData:Array = new Array( length );
            for ( i = 0; i < length; i++ )
                spunData[ i ] = new Array( length );
            
            for ( i = 0; i < length; i++ ) {
                
                for ( j = 0; j < length; j++ ) {
                    
                    if ( direction == SpinDirection.LEFT ) {
                        
                        if( length == 3 )
                            spunData[ j ][ 2 - i ] = data[ i ][ j ];
                        
                        if( length == 4 )
                            spunData[ j ][ 3 - i ] = data[ i ][ j ];
                        
                    } else if ( direction == SpinDirection.RIGHT ) {
                        
                        if( length == 3 )
                            spunData[ 2 - j ][ i ] = data[ i ][ j ];
                        
                        if( length == 4 )
                            spunData[ 3 - j ][ i ] = data[ i ][ j ];
                    }
                }
            }
            
            _data = spunData;
        }
    }
    
    
    interface IRotationSystem 
    {
        function getSpunPoint( gameSystem:GameSystem, direction:String ):Point;
    }
    
    class SpinDirection 
    {
        public static const LEFT:String  = "left";
        public static const RIGHT:String = "right";
        
        public static function reverse( direction:String ):String {
            
            if ( direction == LEFT )
                return RIGHT;
            
            else ( direction == RIGHT )
                return LEFT;
        }
    }
    
    /**
     * --- 参考 ---
     * [SONIC KONTROLL] TETRiS-Side(Category-TOP - 小ネタ - テトラミノ回転法則(WORLDルール)
     * http://www.din.or.jp/~koryan/tetris/index_cj.htm
     */
    class World implements IRotationSystem
    {
        private static const X_INDEX:uint = 0;
        private static const Y_INDEX:uint = 1;
        
        /*
        private static const MINO_SIZE_3_LEFT_SPIN_RULE:Array = [
            [ [  1, 0 ], [  1, -1 ], [ 0,  2 ], [  1,  2 ] ], // L0 > L1
            [ [ -1, 0 ], [ -1,  1 ], [ 0, -2 ], [ -1, -2 ] ], // L1 > L2
            [ [ -1, 0 ], [ -1, -1 ], [ 0,  2 ], [ -1,  2 ] ], // L2 > L3
            [ [  1, 0 ], [  1,  1 ], [ 0, -2 ], [  1, -2 ] ]  // L3 > L0
        ];
        
        private static const MINO_SIZE_3_RIGHT_SPIN_RULE:Array = [
            [ [ -1, 0 ], [ -1,  1 ], [ 0, -2 ], [ -1, -2 ] ], // R3 > R0
            [ [  1, 0 ], [  1, -1 ], [ 0,  2 ], [  1,  2 ] ], // R2 > R3
            [ [  1, 0 ], [  1,  1 ], [ 0, -2 ], [  1, -2 ] ], // R1 > R2
            [ [ -1, 0 ], [ -1, -1 ], [ 0,  2 ], [ -1,  2 ] ]  // R0 > R1
        ];
        
        private static const MINO_SIZE_4_LEFT_SPIN_RULE:Array = [
            [ [ -1, 0 ], [  2, 0 ], [ -1, -2 ], [  2, 1 ] ], // 0 > 1
            [ [ -2, 0 ], [  1, 0 ], [  1, -2 ], [ -2, 1 ] ], // 1 > 2
            [ [ -2, 0 ], [  1, 0 ], [ -2, -1 ], [  1, 1 ] ], // 2 > 3
            [ [  2, 0 ], [ -1, 0 ], [  2, -1 ], [ -1, 2 ] ]  // 3 > 0
        ];
        
        private static const MINO_SIZE_4_RIGHT_SPIN_RULE:Array = [
            [ [ -2, 0 ], [  1, 0 ], [  1, -2 ], [ -2, 1 ] ], // 0 > 3
            [ [  2, 0 ], [ -1, 0 ], [ -1, -2 ], [  2, 1 ] ], // 3 > 2
            [ [  2, 0 ], [ -1, 0 ], [  2, -1 ], [ -1, 1 ] ], // 2 > 1
            [ [ -2, 0 ], [  1, 0 ], [ -2, -1 ], [  1, 2 ] ]  // 1 > 0
        ];
        //*/
        private static const MINO_SIZE_3_LEFT_SPIN_RULE:Array = [
            [ [  1, 0 ], [  1,  1 ], [ 0, -2 ], [  1, -2 ] ], // L3 > L0
            [ [ -1, 0 ], [ -1, -1 ], [ 0,  2 ], [ -1,  2 ] ], // L2 > L3
            [ [ -1, 0 ], [ -1,  1 ], [ 0, -2 ], [ -1, -2 ] ], // L1 > L2
            [ [  1, 0 ], [  1, -1 ], [ 0,  2 ], [  1,  2 ] ]  // L0 > L1
        ];
        
        private static const MINO_SIZE_3_RIGHT_SPIN_RULE:Array = [
            [ [ -1, 0 ], [ -1,  1 ], [ 0, -2 ], [ -1, -2 ] ], // R3 > R0
            [ [ -1, 0 ], [ -1, -1 ], [ 0,  2 ], [ -1,  2 ] ], // R0 > R1
            [ [  1, 0 ], [  1,  1 ], [ 0, -2 ], [  1, -2 ] ], // R1 > R2
            [ [  1, 0 ], [  1, -1 ], [ 0,  2 ], [  1,  2 ] ]  // R2 > R3
        ];
        
        private static const MINO_SIZE_4_LEFT_SPIN_RULE:Array = [
            [ [  2, 0 ], [ -1, 0 ], [  2, -1 ], [ -1, 2 ] ], // 3 > 0
            [ [ -2, 0 ], [  1, 0 ], [ -2, -1 ], [  1, 1 ] ], // 2 > 3
            [ [ -2, 0 ], [  1, 0 ], [  1, -2 ], [ -2, 1 ] ], // 1 > 2
            [ [ -1, 0 ], [  2, 0 ], [ -1, -2 ], [  2, 1 ] ]  // 0 > 1
        ];
        
        private static const MINO_SIZE_4_RIGHT_SPIN_RULE:Array = [
            [ [ -2, 0 ], [  1, 0 ], [ -2, -1 ], [  1, 2 ] ], // 1 > 0
            [ [ -2, 0 ], [  1, 0 ], [  1, -2 ], [ -2, 1 ] ], // 0 > 3
            [ [  2, 0 ], [ -1, 0 ], [ -1, -2 ], [  2, 1 ] ], // 3 > 2
            [ [  2, 0 ], [ -1, 0 ], [  2, -1 ], [ -1, 1 ] ]  // 2 > 1
        ];
        public function World() 
        {
            if ( _instance )
                throw new IllegalOperationError ("Use instance property to get the instance");
            
        }
        
        public function getSpunPoint( gameSystem:GameSystem, direction:String ):Point {
            
            if( !gameSystem.isOverlap( 0, 0 ) )
                return new Point( 0, 0 );
            
            
            
            var rule:Array;
            
            if ( !gameSystem.currentMino )
                return null;
            
            if ( gameSystem.currentMino.length == 3 ) {
                
                if ( direction == SpinDirection.LEFT )
                    rule = MINO_SIZE_3_LEFT_SPIN_RULE;
                
                else 
                    rule = MINO_SIZE_3_RIGHT_SPIN_RULE;
                
            } else {
                
                if ( direction == SpinDirection.LEFT )
                    rule = MINO_SIZE_4_LEFT_SPIN_RULE;
                
                else 
                    rule = MINO_SIZE_4_RIGHT_SPIN_RULE;
            }
            
            var xSign:int = 1;// ( gameSystem.currentMino.type == MinoManager.MINO_L || gameSystem.currentMino.type == MinoManager.MINO_S ) ? -1 : 1;
            
            for ( var i:uint = 0; i < rule[ gameSystem.currentMino.rotationState ].length; i++ ) {
                
                if( !gameSystem.isOverlap( xSign * rule[ gameSystem.currentMino.rotationState ][ i ][ X_INDEX ], rule[ gameSystem.currentMino.rotationState ][ i ][ Y_INDEX ] ) )
                    return new Point(  xSign * rule[ gameSystem.currentMino.rotationState ][ i ][ X_INDEX ], rule[ gameSystem.currentMino.rotationState ][ i ][ Y_INDEX ] );
            }
            
            
            return null;
        }
        
        private static var _instance:World;
        public static function get instance():World {
            
            if ( _instance == null )
                _instance = new World();
            
            return _instance;
        }
    }
    
    
    class GameScene implements IScene
    {
        public static const NEXT_FRAME_BORDER_THICKNESS:Number = 4;
        public static const NEXT_FRAME_BORDER_COLOR:uint = 0xcccccc;
        public static const NEXT_FRAME_WIDTH:Number  = 70;
        public static const NEXT_FRAME_HEIGHT:Number = 70;
        public static const NEXT_FRAME_X:Number = 370;
        public static const NEXT_FRAME_Y:Number = 50;
        public static const NEXT_FRAME_OFFSET_Y:Number = 10;
        
        public static const HOLD_FRAME_BORDER_THICKNESS:Number = 4;
        public static const HOLD_FRAME_BORDER_COLOR:uint = 0xcccccc;
        public static const HOLD_FRAME_WIDTH:Number  = 70;
        public static const HOLD_FRAME_HEIGHT:Number = 70;
        public static const HOLD_FRAME_X:Number = 25;
        public static const HOLD_FRAME_Y:Number = 50;
        
        public static const FIELD_BORDER_THICKNESS:Number = 2;
        public static const FIELD_BORDER_COLOR:uint = 0xdddddd;
        
        public static const BLOCK_WIDTH:Number = 20;
        
        public static const FIELD_WIDTH:Number  = GameSystem.FIELD_HORIZONTAL_LENGTH * ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) + FIELD_BORDER_THICKNESS;
        public static const FIELD_HEIGHT:Number = GameSystem.FIELD_VERTICAL_LENGTH   * ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) + FIELD_BORDER_THICKNESS;
        
        private static const GAME_OVER_BLOCK_COLOR:uint = 0xcccccc;
        private static const GAME_OVER_MESSAGE_FONT:String = "_sans";
        private static const GAME_OVER_MESSAGE_FONT_SIZE:Number = 50;
        private static const GAME_OVER_MESSAGE_FONT_BOLD:Boolean = true;
        
        private static const SCORE_X:uint = 10;
        private static const SCORE_Y:uint = 200;
        private static const SCORE_FONT:String = "_sans";
        private static const SCORE_FONT_SIZE:Number = 20;
        private static const SCORE_FONT_BOLD:Boolean = true;
        
        private const KEY_FIRST_REPEAT_INTERVAL:Number = 20;
        private const KEY_REPEAT_INTERVAL:Number = 2;
        
        /** --------------------------------------------------
         * 
         */
        
        private var backgroundBitmapData:BitmapData;
        
        private var scoreTextField:TextField;
        private var gameOverTextField:TextField;
        
        private var minoManager:MinoManager;
        
        private var gameSystem:GameSystem;
        
        private var gameOver:Boolean;
        
        private var clearLineCounter:uint;
        
        /** ==================================================
         * 
         */
        public function GameScene() 
        {
            if ( _instance )
                throw new IllegalOperationError ("Use instance property to get the instance");
            
            scoreTextField = new TextField();
            scoreTextField.autoSize = TextFieldAutoSize.LEFT;
            scoreTextField.defaultTextFormat = new TextFormat( SCORE_FONT, SCORE_FONT_SIZE, null, SCORE_FONT_BOLD );
            
            gameOverTextField = new TextField();
            gameOverTextField.autoSize = TextFieldAutoSize.LEFT;
            gameOverTextField.defaultTextFormat = new TextFormat( GAME_OVER_MESSAGE_FONT, GAME_OVER_MESSAGE_FONT_SIZE, null, GAME_OVER_MESSAGE_FONT_BOLD );
            
            createBackground();
        }
        
        public function init():void {
            
            gameOver = false;
            
            clearLineCounter = 0;
            
            minoManager = new MinoManager();
            gameSystem = new GameSystem( minoManager, World.instance );
            
            gameSystem.dropLevel = Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_DROP_LEVEL_INDEX ];
            gameSystem.dropTime = Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_DROP_TIME_INDEX ];
            gameSystem.fixTime = Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_FIX_TIME_INDEX ];
            gameSystem.clearLineTime = Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_CLEAR_TIME_INDEX ];
            gameSystem.are = Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_ARE_INDEX ];
            gameSystem.start();
            
            gameSystem.addEventListener( GameSystem.GAME_OVER, gameOverHandler );
            gameSystem.addEventListener( ClearLineEvent.CLEARED, clearLineHandler );
            gameSystem.addEventListener( GameSystem.ENTRY, entryHandler );
        }
        
        private function gameOverHandler( event:Event ):void {
            
            gameOver = true;
            KeyState.reset();
        }
        
        private function clearLineHandler( event:ClearLineEvent ):void {
            
            clearLineCounter += event.lineNums.length;
        }
        
        private function entryHandler( event:Event ):void {
            
            if ( KeyState.getRepeated( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_A_INDEX ] ) )
                gameSystem.spinLeft();
            
            if ( KeyState.getRepeated( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_B_INDEX ] ) )
                gameSystem.spinRight();
            
            if ( KeyState.getRepeated( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_C_INDEX ] ) )
                gameSystem.spinLeft();
        }
        
        private function get fieldX():uint { return ( Setting.stageWidth  - FIELD_WIDTH  ) / 2; }
        private function get fieldY():uint { return ( Setting.stageHeight - FIELD_HEIGHT ) / 2; }
        
        private function createBackground():void {
            
            var backgroundCanvas:Sprite = new Sprite();
            var graphics:Graphics = backgroundCanvas.graphics;
            backgroundBitmapData = new BitmapData( Setting.stageWidth, Setting.stageHeight );
            
            var i:uint;
            
            graphics.lineStyle( FIELD_BORDER_THICKNESS, FIELD_BORDER_COLOR, 1, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.MITER );
            
            
            
            /** --------------------------------------------------
             * gameSystem frame
             */
            
            // vertical line
            for ( i = 0; i < GameSystem.FIELD_HORIZONTAL_LENGTH + 1; i++ ) {
                
                graphics.moveTo( ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * i + FIELD_BORDER_THICKNESS / 2 + fieldX,                fieldY );
                graphics.lineTo( ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * i + FIELD_BORDER_THICKNESS / 2 + fieldX, FIELD_HEIGHT + fieldY );
            }
            
            // horizontal line
            for ( i = 0; i < GameSystem.FIELD_VERTICAL_LENGTH + 1; i++ ) {
                
                graphics.moveTo(               fieldX, ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * i + FIELD_BORDER_THICKNESS / 2 + fieldY );
                graphics.lineTo( FIELD_WIDTH + fieldX, ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * i + FIELD_BORDER_THICKNESS / 2 + fieldY );
            }
            
            
            
            /** --------------------------------------------------
             * next frame
             */
            /*
            graphics.lineStyle( NEXT_FRAME_BORDER_THICKNESS, NEXT_FRAME_BORDER_COLOR, 1, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.MITER );
            
            for ( i = 0; i < MinoManager.NUM_NEXT; i++ )
                graphics.drawRect( NEXT_FRAME_X, NEXT_FRAME_Y + ( NEXT_FRAME_HEIGHT + NEXT_FRAME_OFFSET_Y ) * i, NEXT_FRAME_WIDTH, NEXT_FRAME_HEIGHT );
            //*/
            
            
            /** --------------------------------------------------
             * hold frame
             */
            /*
            graphics.lineStyle( HOLD_FRAME_BORDER_THICKNESS, HOLD_FRAME_BORDER_COLOR, 1, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.MITER );
            graphics.drawRect( HOLD_FRAME_X, HOLD_FRAME_Y, HOLD_FRAME_WIDTH, HOLD_FRAME_HEIGHT );
            //*/
            
            
            /** --------------------------------------------------
             * 
             */
            
            backgroundBitmapData.draw( backgroundCanvas );
        }
        
        private function isRepeatInterval( keyString:String ):Boolean {
            
            return KeyState.isRepeatInterval( keyString, Setting.gameSettingValues[ Setting.GAME_SETTING_VALUE_SLIDE_TIME_INDEX ], KEY_REPEAT_INTERVAL );
        }
        
        public function run():void {
            
            if ( gameOver ) {
                
                if ( KeyState.getReleased( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_A_INDEX ] ) )
                    SceneManager.scene = GameSettingScene.instance;
                
                return;
            }
            
            if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_LEFT_INDEX ] ) )
                gameSystem.moveLeft();
            
            if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_RIGHT_INDEX ] ) )
                gameSystem.moveRight();
            
            if ( isRepeatInterval( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_DOWN_INDEX ] ) )
                gameSystem.softDrop();
            
            if ( KeyState.getPressed( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_UP_INDEX ] ) )
                gameSystem.hardDrop();
            
            if ( KeyState.getPressed( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_A_INDEX ] ) )
                gameSystem.spinLeft();
                //Setting.clear();
            
            if ( KeyState.getPressed( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_B_INDEX ] ) )
                gameSystem.spinRight();
            
            if ( KeyState.getPressed( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_C_INDEX ] ) )
                gameSystem.spinLeft();
            
            if ( KeyState.getPressed( Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_D_INDEX ] ) )
                gameSystem.hold();
            
            gameSystem.run();
        }
        
        public function paint( graphics:Graphics ):void {
            
            graphics.clear();
            
            // background
            graphics.beginBitmapFill( backgroundBitmapData );
            graphics.drawRect( 0, 0, backgroundBitmapData.width, backgroundBitmapData.height );
            graphics.endFill();
            
            // score
            drawString( graphics, scoreTextField, SCORE_X, SCORE_Y, clearLineCounter.toString() );
            
            var i:uint, j:uint, k:uint;
            
            // ghost
            if ( minoManager.currentMino && !( gameSystem.areTimerRunning || gameSystem.clearLineTimerRunning ) ) {
                
                drawMino( graphics, gameSystem.currentMino, 
                        ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * gameSystem.ghostMinoX + FIELD_BORDER_THICKNESS + fieldX, 
                        ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * gameSystem.ghostMinoY + FIELD_BORDER_THICKNESS + fieldY, true );
            }
            
            // nexts
            for ( i = 0; i < minoManager.nextMinos.length; i++ ) {
                
                if( minoManager.nextMinos[ i ] == null )
                    break;
                
                var nextMinoX:uint = NEXT_FRAME_X;
                var nextMinoY:uint = NEXT_FRAME_Y + ( NEXT_FRAME_HEIGHT + NEXT_FRAME_OFFSET_Y ) * i;
                
                drawMino( graphics, minoManager.nextMinos[ i ], nextMinoX, nextMinoY );
            }
            
            // hold
            if ( minoManager.holdMino )
                drawMino( graphics, minoManager.holdMino, HOLD_FRAME_X, HOLD_FRAME_Y );
            
            // gameSystem
            var drawData:Array = gameSystem.drawData;
            for ( i = 0; i < drawData.length; i++ ) {
                
                for ( j = 0; j < drawData[ i ].length; j++ ) {
                    
                    if ( drawData[ i ][ j ] > 0 ) {
                        
                        if( !gameOver )
                            graphics.beginFill( MinoManager.getMinoColorAtDataValue( drawData[ i ][ j ] ) );
                        
                        else
                            graphics.beginFill( GAME_OVER_BLOCK_COLOR );
                        
                        graphics.drawRect( ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * i + FIELD_BORDER_THICKNESS + fieldX, ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) * j + FIELD_BORDER_THICKNESS + fieldY, BLOCK_WIDTH, BLOCK_WIDTH );
                    }
                }
            }
            
            
            // gameOver
            if( gameOver )
                drawString( graphics, gameOverTextField, ( Setting.stageWidth - gameOverTextField.width ) / 2, ( Setting.stageHeight - gameOverTextField.height ) / 2, "Game Over\nPress '" + Setting.keySettingValues[ Setting.KEY_SETTING_VALUE_A_INDEX ] + "' key." );
            
            graphics.endFill();
        }
        
        private function drawMino( graphics:Graphics, mino:Mino, x:uint, y:uint, ghost:Boolean = false ):void {
            
            for ( var i:uint = 0; i < mino.length; i++ ) {
                
                for ( var j:uint = 0; j < mino.length; j++ ) {
                    
                    // no block
                    if ( mino.data[ i ][ j ] == 0 )
                        continue;
                    
                    var blockX:uint = x + i * ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) + ( ( mino.length == 3 || ghost ) ? 0 : - BLOCK_WIDTH / 2 );
                    var blockY:uint = y + j * ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) + ( ( mino.length == 3 || ghost ) ? 0 : - ( BLOCK_WIDTH + FIELD_BORDER_THICKNESS ) );
                    
                    graphics.beginFill( mino.color, ( ghost ) ? 0.5 : 1 );
                    graphics.drawRect( blockX, blockY, BLOCK_WIDTH, BLOCK_WIDTH );
                }
            }
            
            //graphics.endFill();
        }
        
        
        
        
        private static var _instance:GameScene;
        public static function get instance():GameScene {
            
            if ( _instance == null )
                _instance = new GameScene();
            
            return _instance;
        }
    }
    function drawString( graphics:Graphics, textField:TextField, x:Number, y:Number, text:String = "" ):void {
        
        if ( text )
            textField.text = text;
        
        var bitmapData:BitmapData = new BitmapData( textField.width, textField.height, true, 0 );
        
        bitmapData.draw( textField );
        
        drawStringMatrix.identity();
        drawStringMatrix.translate( x, y );
        
        graphics.beginBitmapFill( bitmapData, drawStringMatrix );
        graphics.drawRect( x, y, textField.width, textField.height );
        graphics.endFill();
    }
var drawStringMatrix:Matrix = new Matrix();