FlashDevelopでFlexアプリ LifeGameを作りました。

無料Flashの開発環境FlashDevelopでLifeGame(ライフゲーム)を作ってみました。
LifeGameは「もっとも多く組まれたゲーム」ということで、たぶん皆さんもどういうものかご存知だと思いますが、一応解説みたいなのを。

LifeGameは生物の活動を簡易的なモデルで再現したシミュレーションゲームです。

まず、フィールドとなる場所を作って、これを細かくマス目で区切ります。
この区切られたマスひとつひとつが個々の生命を表しており、この生命は周囲(周囲8マス)の生命の状態によって影響を受けたり、与えたりしながら変化していきます。
この変化の様子をひたすら見守るという地味なゲームなんですが、実際に見てみるとなかなか面白いです。

今回作ったゲームでは(たぶん)一般的なルールである以下のようなものにしました。

  • 周囲2マスに生命がいた場合には維持
  • 周囲3マスに生命がいた場合には誕生
  • その他の場合は死


画面の黒い部分をクリックすると生命が一つ誕生します。
[Start]で進行スタート、[Stop]で停止、[Step]で一つ進行を進め、[Clear]で全消去します。[Random]はランダムで生命を誕生させます。


タイトルにFlexと銘打ってますが、実はFlashFlexの違いがいまいち解っていないんですよねw

ちょっと検索してみると、大雑把には

ということみたいです。

VBみたいにビジュアルにコンポーネントをぺちぺち貼り付けて、それに対するコードを書いていくってのが、私のFlexのイメージだったので、間違ってはいない……と思います。たぶん。

気付いたことのメモ。

配置したコンポーネントを取得する

コンポーネントはidで指定した名前で取得できるようになります。
JavaScriptのdocument.getElementByIdと似た感じですが、より扱いやすいです。

mxml部分

コード部分

    // ラベルを変更
    StartButton.label = "change";  	

Spriteを描画したい場合

Spriteを画面に直接描画するといったことはできないようです。
Spriteなどで画像の描画なんかを行いたい場合は、UIComponentというコンポーネントを貼り付けて、これにAddChildしてこのコンポーネント内に描画するという形になります。

mxml部分

コード部分

    var img:Sprite= new Sprite();
    Screen.addChild(img);  	

コンポーネントにイベントを記述する

これはJavaScriptと同じような方法になります。
mxml部分

コード部分

function start():void {
    mx.controls.Alert.show("Hello,world");
}	

まとめ

FlashActionScriptを使っていた方や、JavaScriptの経験がある方ならば、流儀さえ覚えれば特に迷うことなく開発していけるんじゃないかと思います。
個人的には、Javaアプレット)の出番はもうないかなと思いました。(そもそもそんなに使われていない?)
Javaアプレットは起動も遅いですし、同じUIなら使う側としても絶対Flashのほうがいいですよね。

さらに(私の好みという話になるかもしれませんが)、Javaと比べても開発の効率が全然違うと思います。
FlashDevelopではビジュアルにコンポーネントを貼り付けるといった操作はできないので、有償版の開発環境に比べれば劣りますが、入力補完が強力ですので気になりません。
少なくとも個人レベルで作るものでブラウザ上で動作させたいなら、完全にJavaの出番はない気がしてます。

さて、いつものごとくアルゴリズム的には何の参考にもならないコードですが、このゲームの全コードを置いておきます。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Panel id="MainPanel" title="LifeGame"
        paddingTop="10" paddingBottom="10" 
        paddingLeft="10" paddingRight="10" 
        width="340" initialize="init()" enterFrame="enterFrame()">

        <mx:UIComponent width="300" height="300" id="Field" click="click(event)" />
        
        <mx:HBox direction="vertical" borderStyle="none">
            <mx:Label text="Generation:" />
            <mx:Label id="GenerationLabel" text="0" />      
        </mx:HBox>
        <mx:HBox direction="vertical" paddingLeft="60" borderStyle="none">
            <mx:Button id="StartButton" label="Start" click="start()" />
            <mx:Button id="StopButton" label="Stop" click="stop()" enabled="false" />
            <mx:Button id="StepButton" label="Step" click="step()" />
        </mx:HBox>
        <mx:HBox direction="vertical" paddingLeft="85" borderStyle="none">
            <mx:Button id="ClearButton" label="Clear" click="clear()" />
            <mx:Button id="RandomButton" label="Random" click="random()" />
        </mx:HBox>
        <mx:HBox direction="vertical" borderStyle="none" >
            <mx:HSlider id="SpeedSlider" tickInterval="5" minimum="0" maximum="30" snapInterval="1" value="15" labels="['Slow', 'Quick']" width="300"/>
        </mx:HBox>

    </mx:Panel>
    
    <mx:Script>
        <![CDATA[
            // 行数・列数
            private var ROW:int = 60;
            private var COL:int = 60;
            // 世代数
            private var generation:int = 0;
            // 動作中か?
            private var isMove:Boolean;
            // 生物たち
            private var lifeList:Array;
            // フレーム進行用カウント
            private var frameCount:int;
            
            // 初期化関数
            private function init():void {
                // フィールドを描画
                var field:Sprite= new Sprite();                
                field.graphics.beginFill(0x000000);
                field.graphics.lineStyle(0,0x000000);
                field.graphics.drawRect(0,0,300,300);
                field.graphics.endFill();
                Field.addChild(field);              
                // 生物たちを作成
                lifeList = new Array(ROW);
                var i:int, j:int;
                for (i = 0; i < lifeList.length; ++i ) {
                    lifeList[i] = new Array(COL);
                }
                for (i = 0; i < ROW; ++i ) {
                    for (j = 0; j < COL; ++j ) {
                        lifeList[i][j] = new Object;                        
                        var life:Sprite= new Sprite();                
                        life.graphics.beginFill(0xFFFF00);
                        life.graphics.lineStyle(0,0x000000);
                        life.graphics.drawRect(j*5,i*5,5,5);
                        life.graphics.endFill();
                        life.visible = false;
                        Field.addChild(life);                       
                        lifeList[i][j].cell = life;
                        lifeList[i][j].nextState = false;
                    }
                }
                isMove = false;
                frameCount = 0;
            }
            // メインループ
            private function enterFrame(event:flash.events.Event = null):void {
                if (!isMove) return;
                if( ++frameCount > stage.frameRate - SpeedSlider.value ){
                    frameCount = 0;
                    update();
                }
            }
    
            // 1ステップの処理                  
            private function update():void {
                // 状態更新
                var i:int, j:int;
                for (i = 0; i < ROW; ++i ) {
                    for (j = 0; j < COL; ++j ) {
                        switch(checkAround(j, i)) {
                            case 2:
                                lifeList[i][j].nextState = lifeList[i][j].cell.visible;
                            break;
                            case 3:
                                lifeList[i][j].nextState = true;
                            break;
                            default:
                                lifeList[i][j].nextState = false;
                        }
                    }
                }
                // 反映&生きている数をカウント
                var amount:int = 0;
                for (i = 0; i < ROW; ++i ) {
                    for (j = 0; j < COL; ++j ) {
                        lifeList[i][j].cell.visible = lifeList[i][j].nextState;
                        if (lifeList[i][j].cell.visible) {
                            ++amount;
                        }
                    }
                }
                // 全滅すれば
                if (amount == 0) {
                    if ( generation > 0 )
                        mx.controls.Alert.show(generation + "世代で終了", "終了");
                    generation = 0;
                    stop();
                }else
                    ++generation
                GenerationLabel.text = generation.toString();
            }
            // 周囲を調べる
            private function checkAround(x:int, y:int):int {
                if ( x == 0 || x == COL - 1 || y == 0 || y == ROW - 1 ) return 1;
                var count:int = 0;
                count += lifeList[y][x - 1].cell.visible     ? 1:0;
                count += lifeList[y - 1][x - 1].cell.visible ? 1:0;
                count += lifeList[y + 1][x - 1].cell.visible ? 1:0;
                count += lifeList[y][x + 1].cell.visible     ? 1:0;
                count += lifeList[y - 1][x + 1].cell.visible ? 1:0;
                count += lifeList[y + 1][x + 1].cell.visible ? 1:0;
                count += lifeList[y - 1][x].cell.visible     ? 1:0;
                count += lifeList[y + 1][x].cell.visible     ? 1:0;
                return count;
            }
            // フィールドがクリックされるとその位置に生物を追加
            private function click(event:flash.events.MouseEvent):void {
                if (isMove) return;
                var x:int = event.localX / 5;
                var y:int = event.localY / 5;
                lifeList[y][x].cell.visible = true;             
            }
            // 以下、ボタンイベント用関数
            private function start():void {
                isMove = true;
                frameCount = 0;
                StartButton.enabled = false;
                StepButton.enabled = false;
                RandomButton.enabled = false;
                StopButton.enabled = true;
            }
            private function step():void {
                if (isMove) return;
                update();
            }
            private function stop():void {
                isMove = false;
                StopButton.enabled = false;
                StartButton.enabled = true;
                StepButton.enabled = true;
                RandomButton.enabled = true;
            }
            private function clear():void {
                stop();
                GenerationLabel.text = (generation = 0).toString();
                for (var i:int = 0; i < ROW; ++i ) {
                    for (var j:int = 0; j < COL; ++j ) {
                        lifeList[i][j].cell.visible = false;
                    }
                }
            }
            private function random():void {
                if (isMove) return;
                for (var i:int = 0; i < ROW; ++i ) {
                    for (var j:int = 0; j < COL; ++j ) {
                        lifeList[i][j].cell.visible = Math.round (Math.random () * 3) == 0;
                    }
                }               
            }           
        ]]>
    </mx:Script>    
</mx:Application>