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

Ant Colony

アリの巣を見てるだけ
Get Adobe Flash player
by tencho 24 Oct 2011
/**
 * Copyright tencho ( http://wonderfl.net/user/tencho )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/thmg
 */

/**
 * アリの巣を見てるだけ
 * 
 * @author tencho
 */
package
{
    import com.bit101.components.Style;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.UncaughtErrorEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    public class AntColony  extends Sprite
    {
        private var _time:int;
        private var _world:World;
        private var _speed:int;
        private var _drag:DragManager = new DragManager();
        private var _tool:ToolMenu = new ToolMenu();
        private var _tracking:TrackingMenu = new TrackingMenu();
        private var _isTracking:Boolean = false;
        private var _webScroll:WebScrollStopper;
        
        public function AntColony()
        {
            loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onGlobalError);
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function onGlobalError(e:UncaughtErrorEvent):void 
        {
            e.preventDefault();
        }
        
        private function init(e:Event = null):void
        {
            Style.LABEL_TEXT = 0xFFFFFF;
            Display.init(stage, 465, 465);
            stage.frameRate = 30;
            stage.quality = "low";
            Image.load(onReady);
        }
        
        private function onReady():void
        {
            _world = new World();
            _world.init();
            Status.init();
            _speed = Param.normalSpeed;
            
            _webScroll = new WebScrollStopper(Display.width, Display.height);
            var cp:Point = _world.toCanvasXY(_world.colony.getRooms(Primitive.QUEEN)[0].point);
            _drag.init(_webScroll, cp.x, cp.y, 1);
            _drag.addEventListener(MouseEvent.MOUSE_MOVE, onDragCanvas);
            _drag.addEventListener(MouseEvent.CLICK, onClickCanvas);
            _drag.setScaleRange(0.3, 2.5);
            _drag.setDragArea(new Rectangle(0, 0, 150 * 6, 170 * 6));
            _drag.dragSpeed.x = _drag.dragSpeed.y = -1;
            
            addChild(SpriteUtil.box((Display.width - 4000) / 2, (Display.height - 4000) / 2, 4000, 4000, 0));
            addChild(_world.canvas);
            addChild(_webScroll);
            addChild(_tool.sprite);
            addChild(_tracking.sprite);
            addChild(SpriteUtil.box((Display.width - 4000) / 2, - 2000, 4000, 2000, 0));
            
            _tool.init();
            _tracking.init();
            _tracking.sprite.x = 205;
            _tool.addEventListener(Event.CHANGE, onChangeSpeed);
            _tracking.addEventListener(MouseEvent.CLICK, onCloseTracking);
            
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        private function onCloseTracking(e:MouseEvent):void 
        {
            cancelTracking();
            cancelAnt();
        }
        
        private function onChangeSpeed(e:Event):void 
        {
            _speed = _tool.speed? Param.highSpeed : Param.normalSpeed;
        }
        
        private function onDragCanvas(e:MouseEvent):void 
        {
            cancelTracking();
        }
        
        private function onEnterFrame(e:Event = null):void
        {
            _time++;
            
            for (var i:int = 1; i <= _speed; i++) _world.simulate();
            
            var p:Point = _isTracking? _world.toCanvasXY(_world.trackingAnt.point) : _drag.position;
            _world.updateDraw(p, _drag.scale);
            
            if(_world.trackingAnt) _tracking.update(_world.trackingAnt);
            if(_time % 10 == 0) _tool.update(_world);
        }
        
        private function onClickCanvas(e:MouseEvent = null):void
        {
            cancelTracking();
            var a:Ant = _world.getAntXY(_world.toColonyXY(mouseX, mouseY));
            if (!a) return;
            cancelAnt();
            _isTracking = true;
            _world.trackingAnt = a;
            _world.trackingAnt.addEventListener(Event.REMOVED, onRemoveAnt);
            _tracking.sprite.visible = true;
        }
        
        private function cancelAnt():void 
        {
            _tracking.sprite.visible = false;
            if (!_world.trackingAnt) return;
            _world.trackingAnt.removeEventListener(Event.REMOVED, onRemoveAnt);
            _world.trackingAnt = null;
        }
        
        private function cancelTracking():void 
        {
            
            if (_isTracking)
            {
                _drag.setPoint(_world.toCanvasXY(_world.trackingAnt.point));
            }
            _isTracking = false;
        }
        
        private function onRemoveAnt(e:Event):void 
        {
            cancelTracking();
            cancelAnt();
        }
        
    }

}

import com.bit101.components.Component;
import com.bit101.components.FPSMeter;
import com.bit101.components.HBox;
import com.bit101.components.Label;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Graphics;
import flash.display.InteractiveObject;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import flash.filters.DropShadowFilter;
import flash.filters.GlowFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.utils.describeType;
import flash.utils.Dictionary;

class Param
{
    static public var antNum:int = 40;
    static public var antMax:int = 1000;
    static public var normalSpeed:int = 1;
    static public var highSpeed:int = 10;
    
    static public var clumpNum:Array = [30, 70];
    static public var foodNutrients:Array = [1500, 2200];
    static public var antSpeed:Array = [0.3, 0.45];
    static public var queenSpeed:Number = 0.25;
    static public var workerLife:Array = [40000, 70000];
    static public var queenLife:int = 5000000;
    static public var foodLife:int = 50;
    static public var clumpLife:int = 100;
    static public var wanderFreq:Number = 0.7;
    static public var exploreFreq:Number = 0.1;
    static public var assetPath:String = "http://assets.wonderfl.net/images/related_images/f/fe/fe2b/fe2b013816f9c4d2328e8ed533b930b8d5b97a01";
}

class WebScrollStopper extends Sprite
{
    private var _txt:TextField = new TextField();
    
    public function WebScrollStopper(width:Number, height:Number)
    {
        _txt.width = width;
        _txt.height = height;
        _txt.alpha = 0;
        _txt.selectable = false;
        _txt.addEventListener(Event.ENTER_FRAME, onTick);
        _txt.addEventListener(Event.SCROLL, function(e:Event):void { _txt.scrollV = 2; } );
        addChild(_txt);
    }
    
    private function onTick(e:Event):void 
    {
        _txt.removeEventListener(Event.ENTER_FRAME, onTick);
        while (_txt.maxScrollV <= 3) _txt.appendText(" \n");
    }
    
}

class TrackingMenu extends EventDispatcher
{
    public var sprite:Sprite = new Sprite();
    
    private var _container:HBox;
    private var _button:Sprite = new Sprite();
    private var _action:Label;
    private var _life:Label;
    private var _hungry:Label;
    
    public function TrackingMenu()
    {
    }
    
    public function init():void
    {
        _container = new HBox(null, 5, 6);
        sprite.visible = false;
        
        _button.buttonMode = true;
        _button.addEventListener(MouseEvent.CLICK, onClickClose);
        _button.addChild(new Bitmap(Image.icons[7]));
        
        _container.addChild(_button)
        new Component(_container).width = -1;
        
        var ct:ColorTransform = new ColorTransform();
        ct.color = 0xFFFF00;
        sprite.transform.colorTransform = ct;
        
        _container.addChild(new Bitmap(Image.icons[0]));
        _action = new Label(_container, 0, 0, "");
        new Component(_container).width = 45;
        _container.addChild(new Bitmap(Image.icons[4]));
        _life = new Label(_container, 0, 0, "");
        new Component(_container).width = 30;
        _container.addChild(new Bitmap(Image.icons[3]));
        _hungry = new Label(_container, 0, 0, "");
        new Component(_container).width = 5;
        
        sprite.addChild(_container);
    }
    
    private function onClickClose(e:MouseEvent):void 
    {
        dispatchEvent(e);
    }
    
    public function update(a:Ant):void
    {
        _action.text = Status.LABEL[a.status];
        _hungry.text = String(a.hungry);
        _life.text = String(a.life);
    }
    
}

class ToolMenu extends EventDispatcher
{
    public var sprite:Sprite = new Sprite();
    
    private var _bg:Sprite;
    private var _container:HBox;
    private var _status:HBox;
    private var _antNum:Label;
    private var _foodNum:Label;
    private var _eggNum:Label;
    private var _pupaeNum:Label;
    private var _button:Sprite;
    private var _buttonImage:Bitmap;
    private var _speed:Boolean = false;
    
    public function ToolMenu()
    {
    }
    
    public function get speed():Boolean {return _speed;}
    
    public function init():void
    {
        _bg = SpriteUtil.box(0, 0, Display.width, 30, 0, 0.7);
        _container = new HBox(null, 5, 6);
        _button = new Sprite();
        _button.buttonMode = true;
        _button.addEventListener(MouseEvent.CLICK, onClickSpeed);
        _buttonImage = _button.addChild(new Bitmap(Image.icons[6])) as Bitmap;
        
        _container.addChild(_button);
        new Component(_container).width = -1;
        
        _container.addChild(new Bitmap(Image.icons[0]));
        _antNum = new Label(_container, 0, 0, "");
        new Component(_container).width = 15;
        _container.addChild(new Bitmap(Image.icons[1]));
        _eggNum = new Label(_container, 0, 0, "");
        new Component(_container).width = 5;
        _container.addChild(new Bitmap(Image.icons[2]));
        _pupaeNum = new Label(_container, 0, 0, "");
        new Component(_container).width = 5;
        _container.addChild(new Bitmap(Image.icons[3]));
        _foodNum = new Label(_container, 0, 0, "");
        new Component(_container).width = 5;
        
        sprite.addChild(_bg);
        sprite.addChild(_container);
        new FPSMeter(sprite, Display.width - 45, 6);
    }
    
    private function onClickSpeed(e:MouseEvent):void 
    {
        _speed = !_speed;
        _buttonImage.bitmapData = _speed? Image.icons[5] : Image.icons[6];
        dispatchEvent(new Event(Event.CHANGE));
    }
    
    public function update(w:World):void
    {
        _antNum.text = String(w.ants.length);
        _foodNum.text = String(w.colony.getFoodNum());
        _eggNum.text = String(w.colony.getItemNum(Primitive.EGG));
        _pupaeNum.text = String(w.colony.getItemNum(Primitive.PUPAE));
    }
    
}

class World
{
    public const SCALE:Number = 6;
    public const PI2:Number = Math.PI * 2;
    private const XY:int = 4;
    
    public var canvas:Sprite = new Sprite();
    public var queen:Ant;
    public var ants:Vector.<Ant> = new Vector.<Ant>();
    public var colony:Colony;
    public var ground:Ground;
    public var time:int = 0;
    
    public var exploringRate:Number = 0;
    public var foodRate:Number = 0;
    public var trackingAnt:Ant;
    public var wanderFreq:Number = 0.7;
    
    private var _xy:Array = [];
    private var _updateImage:Boolean = false;
    private var _updatePath:Boolean = false;
    
    private var _bgCanvas:BitmapData;
    private var _antsCanvas:BitmapData;
    private var _shadow:DropShadowFilter = new DropShadowFilter(3, 90, 0, 0.7, 4, 4, 1, 1);
    private var _drawMatrix:Matrix = new Matrix();
    
    public function World()
    {
    }
    
    public function init():void
    {
        var i:int, ix:int, iy:int;
        //コロニー
        colony = new Colony(150, 170, SCALE);
        colony.build(8);
        colony.initPath();
        
        //地上
        ground = new Ground();
        ground.init(this, 30, Random.rangeInt(1, 300));
        _antsCanvas = colony.canvas.clone();
        _bgCanvas = new BitmapData(200, 200, false);
        _bgCanvas.noise(1234, 0x80, 0xFF, 7, true);
        _bgCanvas.colorTransform(_bgCanvas.rect, Color.dirtColor);
        colony.updateColonyImage();
        
        //蟻
        for (i = 0; i < Param.antNum; i++) addAnt(colony.rooms[0].getSpacePoint(), Ant.JOB_WORKER);
        queen = addAnt(colony.rooms[0].getSpacePoint(), Ant.JOB_QUEEN);
        
        //蟻の空間探査処理用配列の初期化
        for (iy = -XY; iy <= XY; iy++)
        for (ix = -XY; ix <= XY; ix++)
        {
            var d:int = Math.max(Math.abs(ix), Math.abs(iy));
            if (!d) continue;
            if (!_xy[d]) _xy[d] = [];
            _xy[d].push([ix, iy]);
        }
    }
    
    /**
     * キャンバス座標とスケールでワールド描画
     * @param    tx
     * @param    ty
     * @param    scale
     */
    public function draw(tx:Number, ty:Number, scale:Number):void
    {
        _drawMatrix.identity();
        _drawMatrix.scale(scale, scale);
        _drawMatrix.translate(-tx * scale + Display.width / 2, -ty * scale + Display.height / 2);
        var g:Graphics = canvas.graphics;
        g.clear();
        g.beginBitmapFill(_bgCanvas, _drawMatrix, true, false);
        g.drawRect(0, 0, Display.width, Display.height);
        g.beginBitmapFill(colony.canvas, _drawMatrix, false, false);
        g.drawRect(0, 0, Display.width, Display.height);
        
        var gy:Number = _drawMatrix.ty;
        g.beginBitmapFill(ground.canvas, _drawMatrix, true, false);
        g.drawRect(0, gy, Display.width, ground.canvas.height * scale);
        g.beginFill(Color.sky);
        g.drawRect(0, gy + 2 * scale, Display.width, -1000);
        
        g.beginBitmapFill(_antsCanvas, _drawMatrix, false, false);
        g.drawRect(0, 0, Display.width, Display.height);
        g.endFill();
    }
    
    /**
     * シミュレーション
     */
    public function simulate():void
    {
        time++;
        wanderFreq = Math.min(time / 25000, Param.wanderFreq);
        var enum:int = 0;
        //蟻の処理
        for each (var a:Ant in ants)
        {
            a.simulate();
            if (a.mode != Ant.MODE_WANDER) enum++;
            if (a.digging) _updatePath = _updateImage = true;
        }
        exploringRate = enum / ants.length;
        foodRate = colony.getFoodNum() / 10 / (ants.length + colony.getItemNum(Primitive.EGG) + colony.getItemNum(Primitive.PUPAE));
        
        //通路検索
        if (time % 5 == 0 && _updatePath)
        {
            _updatePath = false;
            colony.nextPath();
        }
        
        //部屋の時間経過
        if (time % 50 == 0) colony.grow();
        
        //トンネル描画
        if (time % 30 == 0 && _updateImage)
        {
            _updateImage = false;
            colony.updateColonyImage();
        }
    }
    
    /**
     * キャンバス座標で描画更新
     */
    public function updateDraw(p:Point, scale:Number):void
    {
        var img:BitmapData;
        
        _antsCanvas.lock();
        
        //部屋内のアイテムを描画
        _antsCanvas.fillRect(_antsCanvas.rect, 0);
        for each(var room:Room in colony.rooms)
        for (var k:* in room.item)
        for each(var item:Primitive in room.item[k])
        {
            img = getItemImage(item);
            item.setSize(img.width, img.height);
            item.updatePosition(this);
            _antsCanvas.copyPixels(img, img.rect, item.plot, null, null, true);
        }
        
        //蟻の処理
        for each (var a:Ant in ants)
        {
            if (!a.visible) continue;
            //蟻描画
            img = getItemImage(a);
            _antsCanvas.copyPixels(img, img.rect, a.plot, null, null, true);
            //咥えている物描画
            if (a.baggage)
            {
                img = getItemImage(a.baggage);
                a.mouth.x -= img.width / 2;
                a.mouth.y -= img.height / 2;
                _antsCanvas.copyPixels(img, img.rect, a.mouth, null, null, true);
            }
        }
        
        //蟻の影
        _antsCanvas.applyFilter(_antsCanvas, _antsCanvas.rect, PointUtil.ZERO, _shadow);
        _antsCanvas.unlock();
        
        draw(p.x, p.y, scale);
        //draw(ants[0].plot.x, ants[0].plot.y, 1);
    }
    
    /**
     * 各種アイテムの画像取得
     * @param    item
     * @return
     */
    private function getItemImage(item:Primitive):BitmapData
    {
        var img:BitmapData;
        switch(item.type)
        {
            case Primitive.ANT:
                var a:Ant = Ant(item);
                var aimg:Vector.<BitmapData> = (a.job != Ant.JOB_QUEEN)? (trackingAnt === a)? Image.antsSelected : Image.ants : Image.queens;
                img = aimg[(a.rotation % PI2 + PI2) % PI2 / PI2 * Image.DIRECTIONS | 0];
                break;
            case Primitive.PUPAE: img = Image.pupae; break;
            case Primitive.DIRT: img = Image.dirts[Dirt(item).seed]; break;
            case Primitive.EGG: img = Image.eggs[Egg(item).rate * (Image.eggs.length - 1) | 0]; break;
            case Primitive.FOOD: img = Image.foods[Food(item).seed]; break;
            case Primitive.CLUMP: img = Image.clumps[Clump(item).seed]; break;
            case Primitive.GARBAGE: img = Image.garbages[Garbage(item).seed]; break;
        }
        return img;
    }
    
    /**
     * 卵追加
     * @param    p
     * @return
     */
    public function addEgg(p:Point):Egg
    {
        var egg:Egg = new Egg(p.x, p.y);
        egg.setSize(Image.eggs[0].width, Image.eggs[0].height);
        egg.onEclose = onEcloseEgg;
        colony.dropItem(egg, p.x, p.y);
        return egg;
    }
    
    /**
     * ゴミ追加
     * @param    p
     * @return
     */
    public function addGarbage(p:Point, type:int):Garbage
    {
        var g:Garbage = new Garbage(p.x, p.y, type);
        g.setSize(Image.garbages[type].width, Image.garbages[type].height);
        colony.dropItem(g, p.x, p.y);
        return g;
    }
    
    /**
     * 蛹追加
     * @param    p
     * @return
     */
    public function addPupae(p:Point):Pupae
    {
        var pp:Pupae = new Pupae(p.x, p.y);
        pp.setSize(Image.pupae.width, Image.pupae.height);
        pp.onEclose = onEclosePupae;
        colony.dropItem(pp, p.x, p.y);
        return pp;
    }
    
    /**
     * 卵成長MAX時
     * @param    e
     */
    private function onEcloseEgg(e:Egg):void
    {
        addPupae(e.point);
    }
    
    /**
     * 蛹羽化時
     * @param    e
     */
    private function onEclosePupae(e:Egg):void
    {
        var xy:Point = e.point.clone();
        for (var i:int = 0; i < 2; i++) 
        {
            addGarbage(new Point(e.point.x + Random.range(-1, 1), e.point.y + Random.range( -2, 2)), Garbage.FILAMENT);
        }
        addAnt(e.point, Ant.JOB_WORKER);
    }
    
    /**
     * アリ追加
     * @param    p
     * @param    job
     * @return
     */
    public function addAnt(p:Point, job:int):Ant
    {
        var a:Ant = new Ant(p.x, p.y, job);
        a.id = ants.length;
        a.world = this;
        var size:int = (job == Ant.JOB_QUEEN)? Image.QUEEN_SIZE : Image.WORKER_SIZE;
        a.setSize(size, size);
        a.currentRoom = colony.getRoomXY(p.x, p.y);
        a.rotation = Math.random() * PI2;
        a.speed = (job == Ant.JOB_QUEEN)? Param.queenSpeed : Random.range.apply(null, Param.antSpeed);
        a.onRemove = onRemoveAnt;
        a.onLayEgg = onLayEgg;
        a.onKill = onKill;
        a.think();
        a.updatePosition(this);
        ants.push(a);
        return a;
    }
    
    private function onLayEgg(a:Ant):void 
    {
        if (ants.length + colony.getItemNum(Primitive.EGG) + colony.getItemNum(Primitive.PUPAE) < Param.antMax)
        {
            addEgg(a.point);
        }
    }
    
    private function onKill(a:Ant):void
    {
        addGarbage(a.point, Garbage.DEADANT);
    }
    
    private function onRemoveAnt(a:Ant):void 
    {
        var i:int = ants.indexOf(a);
        if (i != -1) ants.splice(i, 1);
    }
    
    /**
     * 一番近い空間へ出るための角度を調べる
     * @param    p
     * @return
     */
    public function getRotation(p:Point):Number
    {
        var r:Number = 0, vx:Number = 0, vy:Number = 0;
        var d:int = 0;
        while (++d <= XY)
        {
            var loop:Boolean = true;
            for each(var xy:Array in _xy[d])
            {
                var argb:uint = colony.map.getPixel32(p.x + xy[0], p.y + xy[1]);
                if (argb >>> 8 & 0xFF)
                {
                    vx += xy[0];
                    vy += xy[1];
                    loop = false;
                }
            }
            if (!loop) break;
        }
        return Math.atan2(vy, vx);
    }
    
    /**
     * マウス座標をコロニー座標に変換
     * @param    mouseX
     * @param    mouseY
     * @return
     */
    public function toColonyXY(mouseX:Number, mouseY:Number):Point 
    {
        var s:Number = SCALE * _drawMatrix.a;
        return new Point((mouseX - _drawMatrix.tx) / s, (mouseY - _drawMatrix.ty) / s);
    }
    
    /**
     * コロニー座標をキャンバス座標に変換する
     * @param    point
     * @return
     */
    public function toCanvasXY(point:Point):Point
    {
        return new Point(point.x * SCALE, point.y * SCALE);
    }
    
    /**
     * 地上で餌を取得する
     * @return
     */
    public function getClump():Clump
    {
        var c:Clump = new Clump(Random.rangeInt.apply(null, Param.clumpNum));
        c.onRot = onRotFood;
        return c;
    }
    
    /**
     * コロニー座標で近いアリを取得
     * @param    p
     */
    public function getAntXY(p:Point):Ant 
    {
        var ds:Array = [];
        for each (var a:Ant in ants) 
        {
            var d:Number = Point.distance(a.point, p);
            if (d < 4) ds.push( { distance:d, ant:a } );
        }
        if (!ds.length) return null;
        return ds.sortOn("distance", Array.NUMERIC)[0].ant;
    }
    
    private function onRotFood(f:Food):void 
    {
        var g:Garbage = addGarbage(f.point, Garbage.FUNGUS);
        g.offset.x = f.offset.x;
        g.offset.y = f.offset.y;
    }
    
}

class Display
{
    static public var width:int = 465;
    static public var height:int = 465;
    static public var stage:Stage;
    
    static public function init(stage:Stage, w:int, h:int):void
    {
        Display.stage = stage;
        width = w;
        height = h;
    }
}

/**
 * 地上
 */
class Ground
{
    public var canvas:BitmapData;
    public var edges:Vector.<Target> = new Vector.<Target>();
    private var _scale:Number;
    private var _heights:Vector.<Number> = new Vector.<Number>();
    private var _angles:Vector.<Number> = new Vector.<Number>();
    private var _grass:uint = 0x5CA849;
        
    public function Ground()
    {
    }
    
    /**
     * サイズ指定で初期化
     * @param    w
     * @param    height
     * @param    seed    地上の起伏シード値
     */
    public function init(w:World, height:int, seed:int):void
    {
        _scale = w.SCALE;
        var bmd:BitmapData = new BitmapData(w.colony.width * w.SCALE, 1, false);
        bmd.perlinNoise(bmd.width / 2, 1, 4, seed, true, true, 7, true);
        var ty:Number = w.colony.gateway.point.y * w.SCALE;
        canvas = new BitmapData(w.colony.width * w.SCALE, height + ty + 1, true);
        bmd.draw(SpriteUtil.gradientBox(w.colony.gateway.point.x * w.SCALE - 75, 0, 150, 1, true, 0, [0x505050, 0x505050, 0x505050, 0x505050], [0, 1, 1, 0], [0, 0.44, 0.55, 1]));
        
        var min:uint = 80, max:uint = 175, h2:int = 0;
        for (var ix:int = 0; ix < canvas.width; ix++)
        {
            var h:int = ((bmd.getPixel(ix, 0) & 0xFF) - min) / (max - min) * height + ty;
            for (var iy:int = 0; iy < canvas.height; iy++)
            {
                var rgb:uint = iy > h - 5 ? _grass : Color.mix(Color.sky, Color.horizon, iy / canvas.height);
                canvas.setPixel32(ix, iy, (!iy || iy > h)? 0 : 0xFF << 24 | rgb);
            }
            _heights.push(h / _scale);
            _angles[ix] = ix? Math.atan2(h - h2, 1) : 0;
            h2 = h;
        }
        bmd.dispose();
        var hh:Number = getHeight(0);
        edges.push(new Target(new Point(0, hh), 5));
        edges.push(new Target(new Point(w.colony.width, hh), 5));
    }
    
    public function getAngle(x:Number):Number
    {
        var i:int = x * _scale | 0;
        i = (i % canvas.width + canvas.width) % canvas.width;
        return _angles[i];
    }
    
    public function getHeight(x:Number):Number
    {
        var i:int = x * _scale | 0;
        i = (i % canvas.width + canvas.width) % canvas.width;
        return _heights[i] - 1;
    }
    
}

class Color
{
    static public var sky:uint = 0xC4E1FF;
    static public var dirt:uint = 0xAE8E5A;
    static public var tunnel:uint = 0x876E46;
    static public var food1:uint = 0xDD8800;
    static public var food2:uint = 0xDDBB00;
    static public var dirtColor:ColorTransform = new ColorTransform((dirt >>> 16) / 0xFF, (dirt >>> 8 & 0xFF) / 0xFF, (dirt & 0xFF) / 0xFF);
    static public var tunnelColor:ColorTransform = new ColorTransform((tunnel >>> 16) / 0xFF, (tunnel >>> 8 & 0xFF) / 0xFF, (tunnel & 0xFF) / 0xFF);
    static public var filament:uint = 0xCED1D4;
    static public var fungus:uint = 0xCC66CC;
    static public var horizon:uint = 0xE7F3FF;
    
    static public function mix(rgb1:uint, rgb2:uint, per:Number):uint 
    {
        var r:uint = (rgb2 >> 16) * per + (rgb1 >> 16) * (1 - per);
        var g:uint = (rgb2 >> 8 & 0xFF) * per + (rgb1 >> 8 & 0xFF) * (1 - per);
        var b:uint = (rgb2 & 0xFF) * per + (rgb1 & 0xFF) * (1 - per);
        return r << 16 | g << 8 | b;
    }
}

class Image
{
    static public var icons:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var ants:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var antsSelected:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var queens:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var dirts:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var foods:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var clumps:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var eggs:Vector.<BitmapData> = new Vector.<BitmapData>();
    static public var garbages:Vector.<BitmapData> = new Vector.<BitmapData>(3);
    static public var pupae:BitmapData;
    static public const DIRECTIONS:int = 360;
    static public const WORKER_SIZE:int = 38;
    static public const QUEEN_SIZE:int = 57;
    public function Image()
    {
    }
    
    static public function load(complete:Function):void
    {
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void
        {
            loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, arguments.callee);
            onLoad(e);
            complete();
        });
        loader.load(new URLRequest(Param.assetPath), new LoaderContext(true));
    }
    
    static private function onLoad(e:Event):void
    {
        var i:int, j:int, bmd:BitmapData;
        
        var img:BitmapData = Bitmap(LoaderInfo(e.currentTarget).content).bitmapData;
        var a:BitmapData = trim(img, new Rectangle(0, 0, 47, 31));
        for (i = 0; i < 19; i++) icons.push(trim(img, new Rectangle(47 + 13 * i, 0, 13, 18)));
        
        eggs = icons.splice(0, 7);
        pupae = icons.splice(0, 1)[0];
        for (i = 0; i < eggs.length; i++) eggs[i] = cutTransparent(eggs[i]);
        garbages[Garbage.FUNGUS] = cutTransparent(icons.shift());
        garbages[Garbage.FILAMENT] = cutTransparent(icons.shift());
        garbages[Garbage.DEADANT] = cutTransparent(icons.shift());
        
        for (i = 0; i < 10; i++) dirts.push(createDrop(7, 7, 0, Color.dirt, i + 1));
        for (i = 0; i < 10; i++) foods.push(createDrop(4, 4, 0, Color.mix(Color.food1, Color.food2, i/10), i + 10));
        for (i = 0; i < 10; i++) clumps.push(createDrop(i + 6, i + 6, 0, Color.mix(Color.food2, Color.food1, i/10), i + 1));
        
        Display.stage.quality = StageQuality.BEST;
        for (j = 0; j <= 1; j++)
        for (i = 0; i < DIRECTIONS; i++)
        {
            var size:int = j? QUEEN_SIZE : WORKER_SIZE;
            bmd = new BitmapData(size, size, true, 0);
            var mtx:Matrix = new Matrix();
            mtx.translate(-a.width / 2, -a.height / 2);
            mtx.rotate(Math.PI * 2 * i / DIRECTIONS);
            mtx.scale(size / 80, size / 80);
            mtx.translate(size / 2, size / 2);
            bmd.draw(a, mtx);
            if (!j)
            {
                ants.push(bmd);
                bmd = bmd.clone();
                bmd.colorTransform(bmd.rect, new ColorTransform(1, 1, 10, 1, 0, 0, 0));
                antsSelected.push(bmd);
            }
            else queens.push(bmd);
        }
        Display.stage.quality = StageQuality.LOW;
    }
    
    static public function createDrop(width:int, height:int, glow:int = 2, color:uint = 0xFFFFFF, seed:int = 1234):BitmapData
    {
        var img:BitmapData = new BitmapData(width, height, true, 0);
        img.perlinNoise(width / 2, height / 2, 3, seed, false, true, 7, true);
        img.draw(SpriteUtil.gradientBox(0, 0, img.width, img.height, false, 0, [0x0, 0x0], [0, 1], [0.5, 1]));
        img.threshold(img, img.rect, PointUtil.ZERO, "<", 0xFF606060, 0x00000000, 0xFFFFFFFF, true);
        img.colorTransform(img.rect, new ColorTransform(0, 0, 0, 1, color >> 16, color >> 8 & 0xFF, color & 0xFF));
        if(glow > 0) img.applyFilter(img, img.rect, PointUtil.ZERO, new GlowFilter(color, 1, glow, glow, 100, 2));
        var rect:Rectangle = img.getColorBoundsRect(0xFF000000, 0x00000000, false);
        img = cutTransparent(img);
        return img;
    }
    
    static public function trim(bmd:BitmapData, rect:Rectangle):BitmapData
    {
        var img:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
        img.copyPixels(bmd, rect, PointUtil.ZERO);
        return img;
    }
    
    static public function cutTransparent(bmd:BitmapData):BitmapData
    {
        return trim(bmd, bmd.getColorBoundsRect(0xFF000000, 0x00000000, false));
    }
    
}

/**
 * 移動先座標
 */
class Target
{
    public var type:int = Primitive.OTHER;
    public var point:Point;
    public var radian:Number;
    public var onDrop:Function = null;
    
    public function Target(p:Point = null, radian:Number = 1)
    {
        if (p) point = p.clone();
        this.radian = radian;
    }
    
    public function drop(obj:Primitive, p:Point):void
    {
        if (onDrop != null) onDrop(obj, p);
    }
    
}

/**
 * 移動先
 */
class Destination extends Target
{
    
    public var allMap:RectPathFinding = new RectPathFinding();
    public var foundMap:RectPathFinding = new RectPathFinding();
    
    public function Destination()
    {
        type = Primitive.OTHER;
    }
    
    public function updateFound(bmd:BitmapData, mask:uint = 0x0000FF00):void
    {
        foundMap.setEnabledByImage(bmd, mask);
        foundMap.search();
    }
    
    public function setGoal(bmd:BitmapData, mask:uint):void
    {
        allMap.createNodesByImage(bmd, mask);
        foundMap.createNodesByImage(bmd, mask);
        allMap.goals.push(allMap.getNodeXY(point.x, point.y));
        foundMap.goals.push(foundMap.getNodeXY(point.x, point.y));
        allMap.search();
    }
    
}

/**
 * 蟻塚
 */
class Gateway extends Destination
{
    public function Gateway()
    {
        type = Primitive.DIRT | Primitive.GARBAGE;
        radian = 3;
    }
    
}

/**
 * 各種オブジェクト
 */
class Primitive extends EventDispatcher
{
    static public const OTHER:int     = 0;
    static public const ALL:int     = parseInt("111111111", 2);
    static public const GARBAGE:int = parseInt("100000000", 2);
    static public const EGG:int     = parseInt("010000000", 2);
    static public const PUPAE:int     = parseInt("001000000", 2);
    static public const ANT:int     = parseInt("000100000", 2);
    static public const FOOD:int     = parseInt("000010000", 2);
    static public const CLUMP:int     = parseInt("000001000", 2);
    static public const DIRT:int     = parseInt("000000100", 2);
    static public const QUEEN:int     = parseInt("000000010", 2);
    static public const REST:int     = parseInt("000000001", 2);
    static public const ITEMS:Array = [EGG, FOOD, CLUMP, PUPAE, GARBAGE];
    
    public var type:int;
    public var plot:Point = new Point();
    public var point:Point = new Point();
    public var offset:Point = new Point();
    public var width:Number = 0;
    public var height:Number = 0;
    public var onRemove:Function = null;
    
    public function Primitive(type:int, x:Number = 0, y:Number = 0)
    {
        this.type = type;
        point.x = x;
        point.y = y;
    }
    
    protected function randomOffset():void 
    {
        offset.x = Random.rangeInt( -2, 2);
        offset.y = Random.rangeInt( -2, 2);
    }
    
    public function setSize(width:Number = 0, height:Number = 0):void
    {
        this.width = width;
        this.height = height;
    }
    
    public function remove():void
    {
        dispatchEvent(new Event(Event.REMOVED));
        if (onRemove != null) onRemove(this);
    }
    
    public function updatePosition(w:World):void
    {
        plot.x = point.x * w.SCALE - width / 2 + offset.x;
        plot.y = point.y * w.SCALE - height / 2 + offset.y;
    }
    
}

/**
 * ゴミ
 */
class Garbage extends Primitive
{
    public var seed:int = 0;
    public var time:int = 50;
    
    static public const FILAMENT:int = 0;
    static public const FUNGUS:int = 1;
    static public const DEADANT:int = 2;
    
    public function Garbage(x:Number = 0, y:Number = 0, type:int = 0)
    {
        super(GARBAGE, x, y);
        seed = type;
        randomOffset();
    }
    
    public function tick():void
    {
        if (--time <= 0)
        {
            remove();
        }
    }
    
}

/**
 * 卵
 */
class Egg extends Primitive
{
    public var age:int = 0;
    public var max:int = 25;//100
    public var rate:Number = 0;
    public var onEclose:Function = null;
    
    public function Egg(x:Number = 0, y:Number = 0)
    {
        super(EGG, x, y);
        randomOffset();
    }
    
    public function grow():void
    {
        age++;
        rate = age / max;
        if (rate > 1) rate = 1;
        if (rate == 1)
        {
            if (onEclose != null) onEclose(this);
            remove();
        }
    }
    
}

/**
 * 蛹
 */
class Pupae extends Egg
{
    public function Pupae(x:Number = 0, y:Number = 0)
    {
        super(x, y);
        type = PUPAE;
        max = 20;
    }
    
}

/**
 * 土
 */
class Dirt extends Primitive
{
    public var seed:int;
    public function Dirt(seed:int = 0)
    {
        super(DIRT);
        this.seed = seed;
    }
    
}

/**
 * 餌の粒
 */
class Food extends Primitive
{
    public var seed:int;
    public var life:int;
    public var mass:int;
    public var onRot:Function = null;
    public function Food(mass:int)
    {
        super(FOOD);
        life = Param.foodLife;
        this.mass = mass;
        this.seed = Math.random() * Image.foods.length;
        randomOffset();
    }
    
    public function cut(num:int):Food
    {
        if (num > mass) num = mass;
        mass -= num;
        if (mass <= 0) remove();
        var f:Food = new Food(num);
        f.onRot = onRot;
        return f;
    }
    
    public function tick():void 
    {
        if (--life <= 0)
        {
            remove();
            if (onRot != null) onRot(this);
        }
    }
    
}

/**
 * 餌の塊
 */
class Clump extends Food
{
    public function Clump(mass:int)
    {
        super(mass);
        type = CLUMP;
        life = Param.clumpLife;
    }
    
    override public function cut(num:int):Food 
    {
        var f:Food = super.cut(num);
        seed = Math.min(mass / 10 | 0, Image.clumps.length - 1);
        return f;
    }
}


class Status
{
    static public const RESTING:int = 0;
    static public const WANDERING:int = 1;
    static public const DIGGING:int = 2;
    static public const EXPLORING:int = 3;
    static public const CARRYING:int = 4;
    static public const MOVING:int = 5;
    static public const SEACHING:int = 6;
    static public const EATING:int = 7;
    static public const LABEL:Array = [];
    
    public function Status()
    {
    }
    
    static public function init():void
    {
        for each (var name:String in describeType(Status).constant.@name)
        {
            if (name != "LABEL") LABEL[Status[name]] = name;
        }
    }
    
}

/**
 * 蟻
 */
class Ant extends Primitive
{
    static public const MODE_WANDER:int = 0;
    static public const MODE_EXPLORE:int = 1;
    static public const MODE_BACK:int = 2;
    
    static public const JOB_QUEEN:int = 0;
    static public const JOB_WORKER:int = 1;
    static public const JOB_SOLDIER:int = 2;
    
    public var world:World;
    public var rotation:Number = 0;
    public var speed:Number = 0.2;
    public var id:int = 0;
    public var age:int = 0;
    public var status:int = Status.RESTING;
    public var hungry:int = 1000;
    public var life:int = 50000;
    public var job:int;
    public var mode:int = MODE_WANDER;
    public var mouth:Point = new Point();
    
    public var target:Target = null;
    public var currentRoom:Room = null;
    public var baggage:Primitive = null;
    
    public var xray:Boolean = false;
    public var digEnabled:Boolean = true;
    public var waitTime:int = 0;
    public var carryWait:int = 0;
    public var movableTime:int = 0;
    public var visible:Boolean = true;
    public var digging:Boolean = false;
    
    private var _offsetAngle:Number = 0;
    private var _offsetCnt:int = 0;
    private var _nextLay:int;
    
    public var onLayEgg:Function = null;
    public var onKill:Function = null;
    
    public function Ant(x:Number = 0, y:Number = 0, job:int = JOB_WORKER)
    {
        super(ANT, x, y);
        this.job = job;
        if (job == JOB_WORKER) life = Random.rangeInt.apply(null, Param.workerLife);
        if (job == JOB_QUEEN) life = Param.queenLife;
    }
    
    /**
     * メイン処理
     */
    public function simulate():void
    {
        ++age;
        digging = false;
        if (carryWait > 0) carryWait--;
        if (movableTime-- < 0)
        {
            movableTime = Random.rangeInt(30, 250);
            wait(0, 40);
        }
        if (job == JOB_QUEEN && age > _nextLay)
        {
            _nextLay = age + 250 / (world.colony.digNum / 4000 + 1);
            onLayEgg(this);
        }
        
        if (waitTime-- > 0)
        {
            updatePosition(world);
            return;
        }
        if (_offsetCnt-- < 0)
        {
            _offsetCnt = 30;
            _offsetAngle = Random.range( -0.25, 0.25);
        }
        
        visible = true;
        switch(mode)
        {
            case MODE_WANDER: wander(); break;
            case MODE_EXPLORE: case MODE_BACK: explore(); break;
        }
        updatePosition(world);
        
        if (!baggage && digEnabled && job != JOB_QUEEN) checkMap();
        if (age % 4 == 0) checkDestination();
        if (--hungry <= 0) life--;
        life--;
        if (life < 0) life = 0;
        if (hungry < 0) hungry = 0;
    }
    
    public function kill():void
    {
        remove();
        onKill(this);
    }
    
    /**
     * 室内アイテムをチェック
     */
    private function checkMap():void
    {
        var c:Colony = world.colony;
        var nx:int = point.x, ny:int = point.y;
        var lv:int = world.ants.length;        
        var argb:uint = c.map.getPixel32(nx, ny);
        var troom:Destination;
        if (!carryWait)
        {
            for each (var map:ItemMap in c.itemMap)
            {
                if (!map.bmd.getPixel(nx, ny)) continue;
                var item:Primitive = c.getItem(nx, ny, map.type);
                if (!item) continue;
                //塊があったら削る
                if (map.type == Primitive.CLUMP) {
                    if (hungry <= 0)
                    {
                        eat(item, 10);
                        break;
                    }
                    troom = c.getDestination(Primitive.FOOD, true, lv, currentRoom);
                    if (troom && carryTo(Clump(item).cut(10), troom)) break;
                }
                //餌があったら食べる
                if (map.type == Primitive.FOOD)
                {
                    if (hungry <= 0)
                    {
                        eat(item, 10);
                        break;
                    }
                    //卵部屋でないなら餌を移動する
                    if (currentRoom && !(currentRoom.type & Primitive.EGG))
                    {
                        if (carryTo(item, c.getDestination(Primitive.FOOD | Primitive.EGG, true, lv, currentRoom))) break;
                    }
                }
                //アイテムと部屋のタイプが一致していれば運ばない
                if (c.getRoomXY(nx, ny).type & map.type) continue;
                //運べる部屋が他にあれば運ぶ
                if (carryTo(item, c.getDestination(map.type, true, lv, currentRoom)))
                {
                    wait(30, 60);
                    break;
                }
            }
        }
        if (baggage) return;
        //土を削る
        if ((argb >>> 16 & 0xFF) == 0xFF && (argb >>> 8 & 0xFF) == 0)
        {
            digging = true;
            carryTo(c.dig(nx, ny), c.gateway);
            status = Status.DIGGING;
            wait(30, 60);
        }
    }
    
    /**
     * 何かを食べる
     * @param    item
     */
    public function eat(item:Primitive, num:int = 10):void 
    {
        wait(120, 200);
        hungry = Random.rangeInt.apply(null, Param.foodNutrients);
        status = Status.EATING;
        if (item as Food) Food(item).cut(num);
        else item.remove();
    }
    
    //探索行動
    private function explore():void
    {
        var angle:Number = world.ground.getAngle(point.x);
        if (point.x > target.point.x) angle += Math.PI; 
        easeRotation(angle, 0.25);
        point.x += Math.cos(rotation) * speed;
        point.y = world.ground.getHeight(point.x);
    }
    
    //コロニー内徘徊行動
    private function wander():void
    {
        var node:PathNode, r:Number, d:Number, a:Number, ease:Number = 0.16;
        if (!target) return;
        if (!(target is Destination))
        {
            r = Math.atan2(target.point.y - point.y, target.point.x - point.x);
            d = Point.distance(target.point, point);
            if (d < 5) ease = 1;
        }
        if (target is Destination)
        {
            var tgt:RectPathFinding = (xray)? Destination(target).allMap : Destination(target).foundMap;
            node = tgt.getNodeXY(point.x, point.y);
            a = (d < 5)? 0 : _offsetAngle;
            r = (node && node.enabled)? node.rotation + a : world.getRotation(point);
        }
        easeRotation(r, ease);
        point.x += Math.cos(rotation) * speed;
        point.y += Math.sin(rotation) * speed;
    }
    
    //回転処理
    private function easeRotation(r:Number,  ease:Number = 0.16):void
    {
        var r2:Number = ((rotation - r) % world.PI2 + world.PI2) % world.PI2;
        if (r2 > Math.PI) r2 -= world.PI2;
        rotation = r2 + r;
        rotation += (r - rotation) * ease;
    }
    
    /**
     * 一定時間停止
     * @param    min
     * @param    max
     */
    public function wait(min:int, max:int):void
    {
        waitTime = Math.random() * (max + 1) + min;
    }
    
    /**
     * 何かをどこかへ運ぶ
     * @param    p
     * @param    target
     * @return
     */
    public function carryTo(p:Primitive, target:Target):Boolean
    {
        if (!p || !target) return false;
        status = Status.CARRYING;
        mode = MODE_WANDER;
        baggage = p;
        goto(target);
        p.remove();
        return true;
    }
    
    //static private const TO_ROOM:int = 0;
    
    /**
     * 次の行動を決める
     */
    public function think():void
    {
        var c:Colony = world.colony;
        var lv:int = world.ants.length;
        if (life <= 0 && currentRoom)
        {
            kill();
            return;
        }
        //女王
        if (job == JOB_QUEEN)
        {
            wait(100, 1000);
            status = Status.WANDERING;
            goto(new Target(currentRoom.getSpacePoint(), 1), true);
            return;
        }
        //帰宅中-----------------------------------------------------
        if (mode == MODE_BACK)
        {
            status = Status.MOVING;
            goto(c.getDestination(baggage? baggage.type : Primitive.ALL, !!baggage, lv, currentRoom), !baggage);
            return;
        }
        //探索中-----------------------------------------------------
        if (mode == MODE_EXPLORE)
        {
            visible = false;
            rotation += Math.PI;
            if (world.foodRate < 1)
            {
                wait(500, 1500);    
                baggage = world.getClump();
            }
            else wait(100, 200);
            goto(c.anthill, false, true, MODE_BACK);
            return;
        }
        
        //コロニー内-----------------------------------------------------
        
        //暇ならどこかの部屋へ向かう
        if (!target)
        {
            status = Status.MOVING;
            goto(c.getDestination(Primitive.ALL, ! !baggage, lv, currentRoom), !baggage);
            return;
        }
        //巣穴に着いたら一定確率で外に出る
        if (target is Gateway)
        {
            wait(20, 50);
            if (world.exploringRate < Param.exploreFreq && Math.random() < 0.4)
            {
                status = Status.EXPLORING;
                goto(world.ground.edges[Math.random() * world.ground.edges.length | 0], false, false, MODE_EXPLORE);
                return;
            }
            status = Status.MOVING;
            goto(c.getDestination(Primitive.ALL, ! !baggage, lv), !baggage);
            return;
        }
        //部屋に着いた時
        var room:Room = currentRoom as Room;
        if (!room)
        {
            status = Status.MOVING;
            goto(c.getDestination(Primitive.ALL, ! !baggage, lv, currentRoom), true);
            return;
        }
        //土を掘りにいく
        if (room.dirts.length && !baggage && (room.spaces.length < 20 || Math.random() < 0.5))
        {
            carryWait = 100;
            goto(new Target(room.getDirtPoint(), 1), true);
            return;
        }
        //部屋の中を散策
        if (Math.random() < world.wanderFreq && room.spaces.length)
        {
            if (currentRoom.type & Primitive.REST) wait(300, 1800);
            else wait(30, 180);
            status = Status.WANDERING;
            goto(new Target(room.getSpacePoint(), 1), true);
            return;
        }
        var items:Vector.<Primitive> = room.getItems(Primitive.ALL);
        //食料、卵、ゴミを探す
        if (Math.random() < Math.min(items.length / (items.length + 10), 0.7))
        {
            status = Status.SEACHING;
            goto(new Target(items[Math.random() * items.length | 0].point, 1), true);
            return;
        }
        //一直線に巣穴に向かう
        if (world.exploringRate < 0.2 && Math.random() < 0.2)
        {
            status = Status.MOVING;
            goto(c.gateway, !!baggage, false);
            return;
        }
        //
        status = Status.MOVING;
        goto(c.getDestination(Primitive.ALL, !!baggage, lv, currentRoom), true);
    }
    
    /**
     * 目的地セット
     * @param    target
     * @param    xray
     * @param    dig
     * @param    mode
     */
    public function goto(target:Target, xray:Boolean = false, dig:Boolean = true, mode:int = MODE_WANDER):void
    {
        this.target = target;
        this.xray = xray;
        digEnabled = dig;
        this.mode = mode;
    }
    
    /**
     * 目的地についたかチェック
     */
    public function checkDestination():void
    {
        var arrive:Boolean = false;
        if (target && (!(target is Destination) || target is Gateway) && Point.distance(target.point, point) < target.radian)
        {
            if (target is Gateway) currentRoom = null;
            arrive = true;
        }
        var room:Room = target as Room;
        if (room && room.contains(point.x, point.y))
        {
            currentRoom = room;
            if (baggage && (room.type & baggage.type))
            {
                target = new Target(room.getSpacePoint(), 1);
                target.type = room.type;
                target.onDrop = room.onDrop;
            }
            else
            {
                arrive = true;
            }
        }
        if (arrive)
        {
            if (baggage && (baggage.type & target.type))
            {
                target.drop(baggage, point);
                carryWait = 100;
                baggage = null;
            }
            think();
        }
    }
    
    override public function updatePosition(w:World):void
    {
        super.updatePosition(w);
        mouth.x = plot.x + width / 2 + Math.cos(rotation) * width / 3.5;
        mouth.y = plot.y + height / 2 + Math.sin(rotation) * width / 3.5;
    }
    
}

class ItemMap
{
    public var bmd:BitmapData;
    public var type:int;
    
    public function ItemMap(width:int, height:int, type:int)
    {
        bmd = new BitmapData(width, height, false, 0);
        this.type = type;
    }
    
    public function addItem(x:int, y:int):Boolean
    {
        var rgb:uint = bmd.getPixel(x, y);
        if (rgb == 0xFFFFFF) return false;
        bmd.setPixel32(x, y, ++rgb);
        return true;
    }
    
    public function removeItem(x:int, y:int):Boolean
    {
        var rgb:uint = bmd.getPixel(x, y);
        if (!rgb) return false;
        bmd.setPixel32(x, y, --rgb);
        return true;
    }
    
}

/**
 * コロニー
 */
class Colony
{
    public var width:Number;
    public var height:Number;
    public var map:BitmapData;
    public var itemMap:Dictionary = new Dictionary();
    public var canvas:BitmapData;
    public var rooms:Vector.<Room> = new Vector.<Room>();
    public var gateway:Gateway = new Gateway();
    public var anthill:Target;
    public var digNum:int = 0;
    
    private var _lines:Vector.<Line>;
    private var _ends:Vector.<Point>;
    
    private var _filter1:GlowFilter = new GlowFilter(0xFFFFFF, 1, 15, 15, 3, 1, false);
    private var _filter2:GlowFilter = new GlowFilter(0x0, 0.4, 40, 40, 2, 1, true);
    private var _temp:BitmapData;
    private var _dirtImage:BitmapData;
    private var _scaleMatrix:Matrix = new Matrix();
    private var _aa:Array = [];
    private var _pathCnt:int = -1;
    private var _types:Array =    [Primitive.QUEEN | Primitive.FOOD | Primitive.CLUMP
                                ,Primitive.FOOD | Primitive.EGG
                                ,Primitive.PUPAE
                                ,Primitive.CLUMP
                                ,Primitive.FOOD | Primitive.EGG
                                ,Primitive.REST | Primitive.GARBAGE
                                ,Primitive.FOOD
                                ,Primitive.REST];
    
    public function Colony(width:Number, height:Number, scale:Number)
    {
        this.width = width;
        this.height = height;
        map = new BitmapData(width, height, true, 0);
        
        for each (var type:int in Primitive.ITEMS)
        {
            itemMap[type] = new ItemMap(width, height, type);
        }
        
        canvas = new BitmapData(width * scale, height * scale, true, 0);
        _scaleMatrix.scale(scale, scale);
        _temp = canvas.clone();
        _dirtImage = canvas.clone();
        _dirtImage.noise(1234, 0x80, 0xFF, 7, true);
        _dirtImage.colorTransform(_dirtImage.rect, Color.tunnelColor);
        for (var i:int = 0; i <= 0xFF; i++) _aa.push(i < 0x85? 0x00000000 : 0xFF000000);
        _filter1.blurX = _filter1.blurY = 2.4 * scale;
        _filter2.blurX = _filter2.blurY = 6.4 * scale;
    }
        
    public function updateColonyImage():void
    {
        canvas.lock();
        _temp.lock();
        _temp.fillRect(_temp.rect, 0);
        _temp.draw(map, _scaleMatrix, null, null, null, false);
        _temp.threshold(_temp, _temp.rect, PointUtil.ZERO, "!=", 0xFFFFFFFF, 0x0, 0x000FF00);
        _temp.applyFilter(_temp, _temp.rect, PointUtil.ZERO, _filter1);
        _temp.paletteMap(_temp, _temp.rect, PointUtil.ZERO, null, null, null, _aa);
        canvas.copyPixels(_dirtImage, _dirtImage.rect, PointUtil.ZERO);
        canvas.copyChannel(_temp, _temp.rect, PointUtil.ZERO, 8, 8);
        canvas.applyFilter(canvas, canvas.rect, PointUtil.ZERO, _filter2);
        _temp.unlock();
        canvas.unlock();
    }
    
    public function build(num:int = 8):void
    {
        gateway.point = new Point(width / 2 + Random.range( -width, width) / 6, 5);
        anthill = new Target(gateway.point, 3);
        map.fillRect(map.rect, 0);
        var map2:BitmapData = map.clone();
        rooms.length = 0;
        
        var found:Sprite = new Sprite();
        var sprite:Sprite = new Sprite();
        var padding:Number = 4;
        var topPadding:Number = 15;
        var paddingRect:Rectangle = new Rectangle(padding, topPadding, width - padding * 2, height - (padding + topPadding));
        var segments:int = 15;
        
        var ss:SpatialSearch = new SpatialSearch(map.width, map.height);
        ss.image.fillRect(ss.image.rect, 0xFFFFFFFF);
        ss.image.fillRect(paddingRect, 0);
        
        var line:Line;
        _lines = Vector.<Line>([new Line()]);
        _lines[0].origin = gateway.point.clone();
        _lines[0].end = gateway.point.add(new Point(0, topPadding));
        _lines[0].draw(sprite.graphics, 0xFFFF00, 3);
        map.draw(sprite);
        
        var room:Room;
        for (var i:int = 0; i < num; i++)
        {
            room = new Room(25, 7);
            room.onDrop = onDropRoom;
            room.lv = (i - 1) * 35;
            var margin:int = i? 10 : 50;
            var p:Point = ss.getRandom(room.rect.width + margin, room.rect.height + margin);
            if (!p) continue;
            rooms.push(room);
            room.setCenter(p);
            room.type = _types[i % _types.length];
            map.copyPixels(room.image, room.image.rect, room.rect.topLeft, null, null, true);
            ss.image.copyPixels(room.image, room.image.rect, room.rect.topLeft, null, null, true);
            
            var lines:Vector.<Line> = createLines(room.point, _lines[Math.random() * _lines.length | 0].end, segments, i? 30 : 20);
            var tunnel:Vector.<Line> = new Vector.<Line>();
            for each (line in lines)
            {
                tunnel.push(line);
                var cp:Point = line.cut(_lines);
                line.flat(paddingRect);
                if (cp) break;
            }
            _lines = _lines.concat(tunnel);
            sprite.graphics.clear();
            for each (line in tunnel)
            {
                line.draw(sprite.graphics, 0xFF0000, Random.range(2, 3));
            }
            map.draw(sprite);
            ss.image.draw(sprite);
            if (!i)
            {
                room.digAll()
                for each (line in _lines)
                {
                    line.draw(found.graphics, 0xFFFF00, 3);
                }
            }
        }
        _lines.length = 0;
        ss.dispose();
        
        map2.draw(found);
        smooth(map2, 2, 0xFFFF00);
        smooth(map, 2, 0xFF0000);
        map.copyPixels(map2, map2.rect, PointUtil.ZERO, null, null, true);
        
        var ct:ColorTransform = new ColorTransform();
        for each (room in rooms)
        {
            smooth(room.image);
            var tl:Point = room.rect.topLeft;
            ct.color = (room.type & Primitive.QUEEN)? 0xFFFFFF : 0xFF00FF;
            map.draw(room.image, new Matrix(1,0,0,1,tl.x, tl.y), ct);
        }
    }
    
    private function onDropRoom(obj:Primitive, p:Point):void
    {
        dropItem(obj, p.x, p.y);
    }
    
    public function initPath():void
    {
        gateway.setGoal(map, 0x00FF0000);
        for each (var room:Room in rooms) room.setGoal(map, 0x00FF0000);
        for (var i:int = 0; i < rooms.length + 1; i++) nextPath();
    }
    
    public function smooth(bmd:BitmapData, size:int = 2, color:uint = 0xFFFFFF):void
    {
        var img:BitmapData = new BitmapData(bmd.width + size * 2, bmd.height + size * 2, true, 0);
        img.copyPixels(bmd, bmd.rect, new Point(size, size));
        img.applyFilter(img, img.rect, PointUtil.ZERO, new GlowFilter(color, 1, size * 2, size * 2, 1000, 1));
        img.applyFilter(img, img.rect, PointUtil.ZERO, new GlowFilter(0x000000, 1, size * 2, size * 2, 1000, 1, true));
        img.threshold(img, img.rect, PointUtil.ZERO, "==", 0xFF000000, 0);
        bmd.copyPixels(img, img.rect, new Point( -size, -size));
    }
    
    public function createLines(origin:Point, end:Point, segments:int = 10, curve:Number = 30):Vector.<Line>
    {
        var lines:Vector.<Line> = new Vector.<Line>();
        var line:Line;
        var p:Point = new Point();
        var radian:Number = 0;
        for (var i:int = 0; i < segments; i++)
        {
            line = new Line();
            line.origin = p.clone();
            line.end = line.origin.add(new Point(Math.cos(radian), Math.sin(radian)));
            radian += Random.range( -1, 1) * Math.PI / 180 * curve;
            p = line.end;
            lines.push(line);
        }
        var mtx:Matrix = new Matrix();
        var scale:Number = !p.length? 0 : Point.distance(origin, end) / p.length;
        mtx.scale(scale, scale);
        mtx.rotate( -Math.atan2(p.y, p.x) + Math.atan2(end.y - origin.y, end.x - origin.x));
        mtx.translate(origin.x, origin.y);
        for each (line in lines)
        {
            line.origin = mtx.transformPoint(line.origin);
            line.end = mtx.transformPoint(line.end);
        }
        return lines;
    }
    
    public function nextPath():void
    {
        _pathCnt = ++_pathCnt % (rooms.length + 1);
        var d:Destination = (!_pathCnt)? gateway : rooms[_pathCnt - 1];
        d.updateFound(map, 0x0000FF00);
    }
    
    public function getRoomXY(x:int, y:int):Room
    {
        for each (var room:Room in rooms)
        {
            if (room.contains(x, y)) return room;
        }
        return null;
    }
    
    /**
     * 条件に一致する部屋、出口等を1つ選ぶ
     * @param    type
     * @param    checkOpen
     * @param    lv
     * @param    ignore
     * @return
     */
    public function getDestination(type:int, checkOpen:Boolean = true, lv:int = int.MAX_VALUE, ignore:Room = null):Destination
    {
        var rooms:Vector.<Room> = getRooms.apply(null, arguments);
        var list:Array = [];
        for each (var r:Room in rooms) list.push(r);
        if (type & gateway.type) list.push(gateway);
        return (!list.length)? null : list[list.length * Math.random() | 0];
    }
    
    public function getRooms(type:int, checkOpen:Boolean = true, lv:int = int.MAX_VALUE, ignore:Room = null):Vector.<Room>
    {
        var list:Vector.<Room> = new Vector.<Room>();
        for each (var room:Room in rooms)
        {
            if ((room.type & type)
                && ignore !== room
                && (!checkOpen || room.spaces.length)
                && (room.lv <= lv)
                ) list.push(room);
        }
        return list;
    }
    
    public function dropItem(p:Primitive, x:Number, y:Number):void
    {
        var room:Room = getRoomXY(x, y);
        if (!room) return;
        p.onRemove = onRemoveItem;
        if (room.addItem(p, x, y))
        {
            ItemMap(itemMap[p.type]).addItem(x, y);
        }
    }
    
    private function onRemoveItem(p:Primitive):void
    {
        ItemMap(itemMap[p.type]).removeItem(p.point.x, p.point.y);
        var room:Room = getRoomXY(p.point.x, p.point.y);
        room.removeItem(p);
    }
    
    public function getItem(x:int, y:int, type:int):Primitive
    {
        var room:Room = getRoomXY(x, y);
        return room? room.getItemXY(x, y, type) : null;
    }
    
    public function dig(x:int, y:int):Dirt
    {
        map.setPixel32(x, y, map.getPixel32(x, y) | 0xFF00);
        var room:Room = getRoomXY(x, y);
        if (room) room.removeDirt(x, y);
        digNum++;
        return new Dirt(Image.dirts.length * Math.random());
    }
    
    public function getItemNum(type:int):int 
    {
        var n:int = 0;
        for each(var r:Room in rooms) n += r.item[type].length;
        return n;
    }
    
    public function getFoodNum():int
    {
        var n:int = 0;
        for each(var r:Room in rooms)
        {
            for each(var f:Food in r.item[Primitive.FOOD]) n += f.mass;
            for each(var c:Clump in r.item[Primitive.CLUMP]) n += c.mass;
        }
        return n;
    }
    
    public function grow():void 
    {
        for each (var r:Room in rooms) r.tick();
    }
    
}

class Space
{
    public var point:Point;
    public var item:Dictionary = new Dictionary();
    public var center:Boolean = true;
    
    public function Space(x:int, y:int, center:Boolean)
    {
        point = new Point(x, y);
        this.center = center;
    }
    
}

class Room extends Destination
{
    public var rect:Rectangle;
    public var image:BitmapData;
    public var spaces:Vector.<Space> = new Vector.<Space>();
    public var dirts:Vector.<Space> = new Vector.<Space>();
    public var item:Dictionary = new Dictionary();
    public var dirtXY:Array = [];
    public var size:int;
    public var lv:int;
    private var _isFound:Boolean = false;
    
    public function Room(rh:Number, rv:Number)
    {
        type = Primitive.OTHER;
        image = Image.createDrop(rh * 2, rv * 2, 2, 0xFFFFFF, Random.rangeInt(1, 300));
        rect = image.rect.clone();
        for each (var id:String in Primitive.ITEMS) 
        {
            item[id] = new Vector.<Primitive>();
        }
    }
    
    public function setCenter(p:Point):void
    {
        point = p.clone();
        rect.x = int(point.x - rect.width / 2);
        rect.y = int(point.y - rect.height / 2);
        image.applyFilter(image, image.rect, PointUtil.ZERO, new GlowFilter(0x000000, 1, 3, 3, 10, 1, true));
        for (var ix:int = 0; ix < image.width; ix++)
        {
            if (!dirtXY[ix]) dirtXY[ix] = [];
            for (var iy:int = 0; iy < image.height; iy++)
            {
                var argb:uint = image.getPixel32(ix, iy);
                if (argb >>> 24 == 0xFF)
                {
                    var sp:Space = new Space(ix + rect.x, iy + rect.y, (argb >>> 16 & 0xFF) != 0x00);
                    dirts.push(sp);
                    dirtXY[ix][iy] = sp;
                }
            }
        }
        size = dirts.length;
        image.colorTransform(image.rect, new ColorTransform(0, 0, 0, 1, 0xFF, 0xFF, 0xFF, 0));
    }
    
    public function getSpaceXY(x:int, y:int):Space
    {
        return (!dirtXY[x - rect.x])? null : dirtXY[x - rect.x][y - rect.y];
    }
    
    public function removeDirt(x:int, y:int):void
    {
        var sp:Space = getSpaceXY(x, y);
        if (!sp) return;
        if (!_isFound) initFoundPath(sp);
        dirts.splice(dirts.indexOf(sp), 1);
        if (sp.center) spaces.push(sp);
    }
    
    private function initFoundPath(sp:Space):void
    {
        _isFound = true;
        foundMap.goals[0] = foundMap.getNodeXY(sp.point.x, sp.point.y);
    }
    
    public function getSpacePoint():Point
    {
        return (!spaces.length)? null : spaces[Math.random() * spaces.length | 0].point;
    }
    
    public function getDirtPoint():Point
    {
        return (!dirts.length)? null : dirts[Math.random() * dirts.length | 0].point;
    }
    
    public function digAll():void
    {
        for each (var sp:Space in dirts) if (sp.center) spaces.push(sp);
        dirts.length = 0;
    }
    
    public function addItem(p:Primitive, x:Number, y:Number):Boolean
    {
        var sp:Space = getSpaceXY(x, y);
        if (!sp) return false;
        if(!sp.item[p.type]) sp.item[p.type] = new Vector.<Primitive>();
        sp.item[p.type].push(p);
        item[p.type].push(p);
        p.point.x = x;
        p.point.y = y;
        return true;
    }
    
    public function getItems(type:int):Vector.<Primitive>
    {
        if (type == Primitive.ALL)
        {
            var prs:Vector.<Primitive> = new Vector.<Primitive>();
            return prs.concat(item[Primitive.CLUMP], item[Primitive.EGG]);
        }
        return item[type];
    }
    
    public function getItemXY(x:int, y:int, type:int):Primitive
    {
        var sp:Space = getSpaceXY(x, y);
        if (!sp) return null;
        var items:Vector.<Primitive> = sp.item[type];
        if (!items || !items.length) return null;
        return items[items.length - 1];
    }
    
    public function removeItem(p:Primitive):void
    {
        item[p.type].splice(item[p.type].indexOf(p), 1);
        var sp:Space = getSpaceXY(p.point.x, p.point.y);
        if(sp) sp.item[p.type].splice(sp.item[p.type].indexOf(p), 1);
    }
    
    public function tick():void
    {
        for each (var p:Pupae in item[Primitive.PUPAE]) p.grow();
        for each (var egg:Egg in item[Primitive.EGG])
        {
            if (item[Primitive.FOOD].length) item[Primitive.FOOD][0].cut(1);
            else if (item[Primitive.CLUMP].length) item[Primitive.CLUMP][0].cut(1);
            else break;
            egg.grow();
        }
        for each (var g:Garbage in item[Primitive.GARBAGE]) g.tick();
        for each (var f:Food in item[Primitive.FOOD]) f.tick();
        for each (var c:Food in item[Primitive.CLUMP]) c.tick();
    }
    
    public function contains(x:Number, y:Number):Boolean 
    {
        return rect.contains(x, y) && getSpaceXY(x, y) != null;
    }
    
}

class Line
{
    public var origin:Point = new Point();
    public var end:Point = new Point();
    
    public function Line()
    {
    }
    
    public function draw(g:Graphics, color:uint = 0x0, thickness:Number = 0):void
    {
        g.lineStyle(thickness, color);
        g.moveTo(origin.x, origin.y);
        g.lineTo(end.x, end.y);
    }
    
    public function getCrossPoint(line:Line):Point
    {
        var ax1:Number = line.origin.x, ay1:Number = line.origin.y, ax2:Number = line.end.x, ay2:Number = line.end.y;
        var bx1:Number = origin.x, by1:Number = origin.y, bx2:Number = end.x, by2:Number = end.y;
        if (((ax1 - ax2) * (by1 - ay1) + (ay1 - ay2) * (ax1 - bx1)) * ((ax1 - ax2) * (by2 - ay1) + (ay1 - ay2) * (ax1 - bx2)) > 0) return null;
        if (((bx1 - bx2) * (ay1 - by1) + (by1 - by2) * (bx1 - ax1)) * ((bx1 - bx2) * (ay2 - by1) + (by1 - by2) * (bx1 - ax2)) > 0) return null;
        var v1x:Number = ax2 - ax1, v1y:Number = ay2 - ay1;
        var v2x:Number = bx1 - ax1, v2y:Number = by1 - ay1;
        var v3x:Number = bx2 - bx1, v3y:Number = by2 - by1;
        var cross:Number = v3x * v1y - v3y * v1x;
        if (!cross) return null;
        var scale:Number = (v3x * v2y - v3y * v2x) / cross;
        return new Point(v1x * scale + ax1, v1y * scale + ay1);    
    }
    
    public function cut(lines:Vector.<Line>):Point
    {
        var cp:Point = null;
        for each (var item:Line in lines)
        {
            var p:Point = getCrossPoint(item);
            if (p)
            {
                if (p.equals(origin)) continue;
                end = p;
                cp = p.clone();
            }
        }
        return cp;
    }
    
    public function flat(rect:Rectangle):void
    {
        flatPoint(origin, rect);
        flatPoint(end, rect);
    }
    
    private function flatPoint(point:Point, rect:Rectangle):void
    {
        if (rect.containsPoint(point)) return;
        if (point.x < rect.left) point.x = rect.left;
        if (point.x > rect.right) point.x = rect.right;
        if (point.y < rect.top) point.y = rect.top;
        if (point.y > rect.bottom) point.y = rect.bottom;
    }
    
}

class PointUtil
{
    static public const ZERO:Point = new Point();
}

class SpatialSearch
{
    private var _image:BitmapData;
    private var _tmp:BitmapData;
    
    public function SpatialSearch(width:int, height:int)
    {
        _image = new BitmapData(width, height, true, 0);
        _tmp = _image.clone();
    }
    
    public function get image():BitmapData { return _image; }
    
    public function getRandom(width:Number, height:Number, mask:uint = 0xFF000000):Point
    {
        _tmp.applyFilter(_image, _image.rect, PointUtil.ZERO, new GlowFilter(0xFFFFFF, 1, width, height, 1000, 1));
        var pixels:Vector.<uint> = _tmp.getVector(_tmp.rect);
        var spaces:Vector.<int> = new Vector.<int>();
        var l:int = pixels.length;
        for (var i:int = 0; i < l; i++)
        {
            if (!(pixels[i] & mask)) spaces.push(i);
        }
        if (!spaces.length) return null;
        var index:int = spaces[Math.random() * spaces.length | 0];
        return new Point(index % _tmp.width, index / _tmp.width | 0);
    }
    
    public function dispose():void
    {
        _image.dispose();
        _tmp.dispose();
    }
    
}

class RectPathFinding
{
    public var width:int;
    public var height:int;
    public var goals:Vector.<PathNode> = new Vector.<PathNode>();
    public var nodes:Vector.<PathNode>;
    
    public function RectPathFinding()
    {
    }
    
    public function setEnabledByImage(bmd:BitmapData, mask:uint = 0xFF000000):void
    {
        for each (var node:PathNode in nodes)
        {
            if (!node) continue;
            node.enabled = ((bmd.getPixel32(node.x, node.y) & mask) != 0);
        }
    }
    
    public function createNodesByImage(bmd:BitmapData, mask:uint = 0xFF000000):void
    {
        this.width = bmd.width;
        this.height = bmd.height;
        nodes = new Vector.<PathNode>(width * height);
        
        var ix:int, iy:int, nx:int, ny:int, node:PathNode;
        
        for (iy = 0; iy < height; iy++)
        for (ix = 0; ix < width; ix++)
        {
            if ((bmd.getPixel32(ix, iy) & mask) == 0) continue;
            node = new PathNode(ix, iy);
            nodes[ix + iy * width] = node;
        }
        
        for (iy = 0; iy < height; iy++)
        for (ix = 0; ix < width; ix++)
        {
            node = nodes[ix + iy * width];
            if (!node) continue;
            for (ny = -1; ny <= 1; ny++) 
            for (nx = -1; nx <= 1; nx++)
            {
                var px:int = ix + nx;
                var py:int = iy + ny;
                if (px < 0 || py < 0 || px >= width || py >= height || (!nx && !ny)) continue;
                var link:PathNode = nodes[px + py * width];
                if (!link) continue;
                node.addLink(Math.sqrt(nx * nx + ny * ny), link);
            }
        }
    }
    
    public function getNodeXY(x:int, y:int):PathNode
    {
        var index:int = x + y * width;
        if (index < 0 || index >= nodes.length) return null;
        return nodes[index];
    }
    
    public function search():void
    {
        var node:PathNode;
        for each (node in nodes)
        {
            if (!node) continue;
            node.cost = -1;
            node.next = null;
        }
        for each (node in goals)
        {
            node.cost = 0;
            scanNode(node);
        }
        for each (node in nodes)
        {
            if (!node || !node.next) continue;
            node.rotation = Math.atan2(node.next.y - node.y, node.next.x - node.x);
        }
    }
    
    public function scanNode(target:PathNode):void
    {
        var scanList:Vector.<PathNode> = Vector.<PathNode>([target]);
        var nextList:Vector.<PathNode> = Vector.<PathNode>([]);
        while (scanList.length)
        {
            for each (var node:PathNode in scanList)
            for each (var link:PathLink in node.links)
            {
                var cost:Number = node.cost + link.cost;
                if (!link.to.enabled || link.to.cost != -1 && link.to.cost <= cost) continue;
                link.to.next = node;
                link.to.cost = node.cost + link.cost;
                nextList.push(link.to);
            }
            scanList = nextList.concat();
            nextList.length = 0;
        }
    }
    
}

class PathLink
{
    public var cost:Number = 1;
    public var to:PathNode = null;
    
    public function PathLink(cost:Number, to:PathNode)
    {
        this.cost = cost;
        this.to = to;
    }
    
}

class PathNode
{
    public var x:int = 0;
    public var y:int = 0;
    public var rotation:Number = 0;
    public var enabled:Boolean = true;
    public var cost:Number = -1;
    public var links:Vector.<PathLink> = new Vector.<PathLink>();
    public var next:PathNode = null;
    
    public function PathNode(x:int = 0, y:int = 0)
    {
        this.x = x;
        this.y = y;
    }
    
    public function addLink(cost:Number, node:PathNode):PathLink
    {
        var link:PathLink = new PathLink(cost, node);
        links.push(link);
        return link;
    }
    
}

class DragManager extends EventDispatcher
{
    public var position:Point = new Point();
    public var dragSpeed:Point = new Point(1, 1);
    public var scaleSpeed:Number = 1.2;
    public var scale:Number = 1;
    public var clickRange:Number = 2;
    public var wheelEnabled:Boolean = true;
    
    private var _iobject:InteractiveObject;
    private var _stage:Stage;
    private var _isDragged:Boolean = false;
    private var _isMouseDown:Boolean = false;
    private var _savePosition:Point = new Point();
    private var _saveMousePos:Point;
    private var _scaleMin:Number = Number.MIN_VALUE;
    private var _scaleMax:Number = Number.MAX_VALUE;
    private var _dragTop:Number = Number.MIN_VALUE;
    private var _dragBottom:Number = Number.MAX_VALUE;
    private var _dragLeft:Number = Number.MIN_VALUE;
    private var _dragRight:Number = Number.MAX_VALUE;
    
    public function get isMouseDown():Boolean { return _isMouseDown; }
    public function get isDragged():Boolean { return _isDragged; }
    
    public function DragManager()
    {
    }
    
    public function init(target:InteractiveObject, x:Number = 0, y:Number = 0, scale:Number = 1):void
    {
        this.scale = scale;
        position.x = x;
        position.y = y;
        _iobject = target;
        _iobject.addEventListener(MouseEvent.MOUSE_DOWN, onMsDown);
        _iobject.addEventListener(MouseEvent.MOUSE_WHEEL, onMsWheel);
    }
    
    public function setDragArea(rect:Rectangle):void
    {
        _dragTop = rect.y;
        _dragLeft = rect.x;
        _dragBottom = rect.y + rect.height;
        _dragRight = rect.x + rect.width;
    }
    
    public function setScaleRange(min:Number, max:Number):void 
    {
        _scaleMin = min;
        _scaleMax = max;
    }
    
    private function onMsWheel(e:MouseEvent):void 
    {
        if (!wheelEnabled) return;
        var d:int = e.delta < 0? -1 : 1;
        scale *= Math.pow(scaleSpeed, d);
        if (scale < _scaleMin) scale = _scaleMin;  
        if (scale > _scaleMax) scale = _scaleMax; 
        dispatchEvent(new MouseEvent(MouseEvent.MOUSE_WHEEL));
    }
    
    private function onMsDown(e:Event = null):void
    {
        _isMouseDown = true;
        _isDragged = false;
        _stage = _iobject.stage;
        _stage.addEventListener(MouseEvent.MOUSE_UP, onMsUp);
        _stage.addEventListener(Event.MOUSE_LEAVE, onMsUp);
        _stage.addEventListener(MouseEvent.MOUSE_MOVE, onMsMove);
        _savePosition = position.clone();
        _saveMousePos = new Point(_iobject.mouseX, _iobject.mouseY);
        dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN));
    }
    
    private function onMsUp(e:Event = null):void
    {
        _isMouseDown = false;
        _stage.removeEventListener(MouseEvent.MOUSE_UP, onMsUp);
        _stage.removeEventListener(Event.MOUSE_LEAVE, onMsUp);
        _stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMsMove);
        checkDrag();
        dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP));
        if (!_isDragged) dispatchEvent(new MouseEvent(MouseEvent.CLICK));
    }
    
    private function onMsMove(e:Event = null):void
    {
        checkDrag();
    }
    
    private function checkDrag():void
    {
        var drag:Point = new Point(_iobject.mouseX, _iobject.mouseY).subtract(_saveMousePos);
        if (!_isDragged && drag.length > clickRange) _isDragged = true;
        if (_isDragged)
        {
            position.x = _savePosition.x + drag.x * dragSpeed.x / scale;
            position.y = _savePosition.y + drag.y * dragSpeed.y / scale;
            if (position.x < _dragLeft) position.x = _dragLeft; 
            if (position.x > _dragRight) position.x = _dragRight; 
            if (position.y < _dragTop) position.y = _dragTop; 
            if (position.y > _dragBottom) position.y = _dragBottom; 
            dispatchEvent(new MouseEvent(MouseEvent.MOUSE_MOVE));
        }
    }
    
    public function setPoint(p:Point):void
    {
        _savePosition.x = position.x = p.x;
        _savePosition.y = position.y = p.y;
    }
    
}

class SpriteUtil
{
    static public function box(x:Number, y:Number, width:Number, height:Number, rgb:uint = 0x000000, alpha:Number = 1, tx:Number = 0, ty:Number = 0):Sprite
    {
        var sp:Sprite = new Sprite();
        sp.graphics.beginFill(rgb, alpha);
        sp.graphics.drawRect(x, y, width, height);
        sp.graphics.endFill();
        sp.x = tx;
        sp.y = ty;
        return sp;
    }
    
    static public function gradientBox(x:Number, y:Number, width:Number, height:Number, isLinear:Boolean, rotation:Number, rgbs:Array, alphas:Array, ratios:Array = null):Sprite
    {
        var sp:Sprite = new Sprite();
        var i:int, mtx:Matrix = new Matrix(), len:int = rgbs.length;
        mtx.createGradientBox(width, height, rotation * Math.PI / 180, x, y);
        if (!ratios)
        {
            ratios = [];
            for (i = 0; i < len; i++) ratios[i] = i / (len - 1);
        }
        for (i = 0; i < len; i++) ratios[i] *= 0xFF;
        sp.graphics.beginGradientFill(isLinear? GradientType.LINEAR : GradientType.RADIAL, rgbs, alphas, ratios, mtx);
        sp.graphics.drawRect(x, y, width, height);
        sp.graphics.endFill();
        return sp;
    }
    
}

class Random
{
    static public function range(min:Number, max:Number):Number
    {
        return Math.random() * (max - min) + min;
    }
    
    static public function rangeInt(min:int, max:int):int
    {
        return int(Math.random() * (max + 1 - min)) + min;
    }
}