forked from: Cart

by uwi forked from Cart (diff: 310)
スリックカート (Box2D版)
[↑] アクセル
[←] [→] ステアリング
[↓] ブレーキ
[SPC] リセット
♥0 | Line 504 | Modified 2010-02-11 17:07:54 | MIT License
play

ActionScript3 source code

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

// forked from flashrod's Cart
// スリックカート (Box2D版)
// [↑] アクセル
// [←] [→] ステアリング
// [↓] ブレーキ
// [SPC] リセット
package {
    import Box2D.Collision.Shapes.b2CircleDef;
    import Box2D.Collision.Shapes.b2PolygonDef;
    import Box2D.Collision.b2AABB;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2World;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.Shape;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.utils.getTimer;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.display.Loader;

    public class Cart2D extends Sprite {
        // ビューポート
        private static const VW:Number = 465;
        private static const VH:Number = 465;
        // ワールド
        private static const W:Number = 465;
        private static const H:Number = 3000;
        private static const CX:Number = W/2;
        private static const CY:Number = H/2;
        // スタート・ゴールライン(ワールド座標系)
        private static const START_Y:Number = H-50;
        private static const GOAL_Y:Number = 50;
        // パラメータ類
        private static const A_FORCE:Number = 20000;
        private static const S_FORCE:Number = A_FORCE * 0.7;
        private static const B_DAMP:Number = 1.0;
        private static const NB_DAMP:Number = 0.1;
        // インタラクション
        private static const VK_LEFT:int = 37;
        private static const VK_UP:int = 38;
        private static const VK_RIGHT:int = 39;
        private static const VK_DOWN:int = 40;
        private static const VK_SPC:int = 32;

        /** アクセルを踏んでいるときは真 */
        private var acl:Boolean = false;
        /** ブレーキを踏んでいるときは真 */
        private var brk:Boolean = false;
        /** ステアリング(左:-90°,右:90°)(radian)*/
        private var steering:Number = 0;

        /** ワールド */
        private var world:b2World;
        /** 車モデル */
        private var car:b2Body;
        /** 壁モデル */
        private var fixed:Array; // of b2Body
        /** 障害物モデル */
        private var float:Array; // of b2Body

        /** ワールドビュー */
        private var worldView:Sprite;

        /** スタートからの経過時間(ミリ秒) */
        private var time:int;

        /** カート2D構築 */
        public function Cart2D() {
            var keyTableDOWN:Array = [];
            keyTableDOWN[VK_UP] = function():void { acl = true; };
            keyTableDOWN[VK_DOWN] = function():void { brk = true; };
            keyTableDOWN[VK_LEFT] = function():void { steering =-Math.PI/2; };
            keyTableDOWN[VK_RIGHT] = function():void { steering = Math.PI/2; };
            keyTableDOWN[VK_SPC] = reset;
            stage.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent):void {
                var f:Function = keyTableDOWN[e.keyCode];
                if (f != null) {
                    f();
                }
            });
            var keyTableUP:Array = [];
            keyTableUP[VK_UP] = function():void { acl = false; };
            keyTableUP[VK_DOWN] = function():void { brk = false; };
            keyTableUP[VK_LEFT] = function():void { steering = 0; };
            keyTableUP[VK_RIGHT] = function():void { steering = 0; };
            stage.addEventListener(KeyboardEvent.KEY_UP, function(e:KeyboardEvent):void {
                var f:Function = keyTableUP[e.keyCode];
                if (f != null) {
                    f();
                }
            });

            // モデルを作る
            this.world = createWorld(); // ワールドを作る
            this.car = createCarBody(); // 車モデルを作る
            this.fixed = createFixed(); // 壁を作る
            this.float = createFloat(); // 障害物を作る

            // ビューを作る
            /*
            this.worldView = new Sprite();
            addChild(worldView);
            createBackground();
            createFixedView();
            createFloatView();

            // 時間表示
            var tf:TextField = new TextField();
            var fmt:TextFormat = new TextFormat();
            fmt.color = 0xFFFFFF;
            fmt.font  = 'Verdana';
            tf.defaultTextFormat = fmt;
            addChild(tf);

            // 背景を塗る
            graphics.beginFill(0x888888);
            graphics.drawRect(0, 0, VW, VH);
            graphics.endFill();

            // 車ビューを作る
            var carView:DisplayObject = createCarView();
            worldView.addChild(carView);
            car.SetUserData(carView); // 車モデルに車ビューを関連付け

            addEventListener(Event.ENTER_FRAME, function(e:Event):void {
                step();
                if (countup) {
                    var t:int = getTimer() - time;
                    var s:int = int(t / 1000);
                    var ms:int = (t - s * 1000) / 10;
                    tf.text = s+'"'+ms;
                }
            });
            */
            
            var tff : TextField = new TextField();
            addChild(tff);
            tff.height = 465;
            
            var a : Algo = new Algo();
            var s : int = getTimer();
            for(var kk : int = 0;kk < 100;kk++){
	            a.step();
            }
            var g : int = getTimer();
            for each(var c : Object in a.OptimizedCommand){
            		tff.appendText("" + c.t + "," + c.type + "\n");
            }
            tff.appendText("" + a.OptimizedScore + "\n");
            tff.appendText("" + (g - s) + " ms\n");
        }

        /** リセット
         */
        private function reset():void {
            car.SetXForm(new b2Vec2(CX, START_Y+16), 0);
            car.SetLinearVelocity(new b2Vec2());
            car.SetAngularVelocity(0);
        }

        /** ワールドを作る
         * @return ワールド
         */
        private function createWorld():b2World {
            var aabb:b2AABB = new b2AABB();
            aabb.lowerBound.Set(0, 0);
            aabb.upperBound.Set(W, H);
            var gravity:b2Vec2 = new b2Vec2();
            return new b2World(aabb, gravity, true);
        }

        /** 車モデルを作る
         * @return 車モデル
         */
        private function createCarBody():b2Body {
            var def:b2BodyDef = new b2BodyDef();
            def.position.Set(CX, START_Y+16);
            def.angularDamping = 0.75; // 回転の減衰
            var body:b2Body = world.CreateBody(def);
            var rect:b2PolygonDef = new b2PolygonDef();
            rect.SetAsBox(16, 16);
            rect.density = 1;       // 0:固定 kg/m^2
            rect.restitution = 0.4; // 反発係数[0,1]
            rect.friction = 0.1;    // 摩擦[0,1]
            body.CreateShape(rect);
            body.SetMassFromShapes();
            return body;
        }

        /** 車ビューを作る
         * @return 車表示オブジェクト
         */
        private function createCarView():DisplayObject {
            var sprite:Sprite = new Sprite();
            var image:Loader = new Loader();
            image.load(new URLRequest("http://img.f.hatena.ne.jp/images/fotolife/f/flashrod/20100121/20100121222408.png?1264080327"));
            image.x = -32/2;
            image.y = -32/2;
            sprite.addChild(image);
            return sprite;
        }

        /** 動かない壁をつくる
         */
        private function createFixed():Array {
            var a:Array = [
                {x:388, y:120, w:100, h:8, a:0.7853981633974483},
                {x:73, y:120, w:100, h:8, a:-0.7853981633974483},
                {x:124, y:764, w:116.25, h:8, a:0},
                {x:343, y:1226, w:116.25, h:8, a:0},
                {x:119, y:1612, w:116.25, h:8, a:0},
                {x:348, y:2000, w:140, h:8, a:0.7853981633974483},
                {x:116, y:2500, w:140, h:8, a:-0.7853981633974483},
                {x:0, y:1500, w:8, h:1500, a:0},
                {x:465, y:1500, w:8, h:1500, a:0},
                {x:232, y:3000, w:232, h:8, a:0},
            ];
            var list:Array = [];
            for each (var o:* in a) {
                var def:b2BodyDef = new b2BodyDef();
                def.position.Set(o.x, o.y);
                def.angle = o.a;
                var body:b2Body = world.CreateBody(def);
                var rect:b2PolygonDef = new b2PolygonDef();
                rect.SetAsBox(o.w, o.h);
                rect.restitution = 0.4;
                rect.friction = 0.1;
                body.CreateShape(rect);
                body.SetUserData({w:o.w, h:o.h});
                list.push(body);
            }
            return list;
        }

        /** [B2D]動く障害物をつくる
         * @return 障害物リスト
         */
        private function createFloat():Array {
            var a:Array = [
                {x:283, y:341, r:8},
                {x:112, y:178, r:8},
                {x:355, y:654, r:8},
                {x:170, y:473, r:8},
            ];
            var list:Array = [];
            for each (var o:* in a) {
                var def:b2BodyDef = new b2BodyDef();
                def.position.Set(o.x, o.y);
                def.linearDamping = 1.0;
                def.angularDamping = 1.0;
                var body:b2Body = world.CreateBody(def);
                var circle:b2CircleDef = new b2CircleDef();
                circle.density = 0.01;
                circle.radius = o.r;
                circle.restitution = 0.6;
                circle.friction = 0.2;
                body.CreateShape(circle);
                body.SetMassFromShapes();
                body.SetUserData({r:o.r});
                list.push(body);
            }
            return list;
        }

        /** 壁を表示
         */
        private function createFixedView():void {
            for each (var b:b2Body in fixed) {
                var o:* = b.GetUserData();
                var shape:Shape = new Shape();
                shape.graphics.lineStyle(1, 0x000000);
                shape.graphics.beginFill(0xCCCCCC);
                shape.graphics.drawRect(-o.w, -o.h, 2*o.w, 2*o.h);
                shape.graphics.endFill();
                var p:b2Vec2 = b.GetPosition();
                shape.x = p.x;
                shape.y = p.y;
                shape.rotation = b.GetAngle() * (180 / Math.PI);
                o.s = shape;
                worldView.addChild(shape);
            }
        }

        /** 障害物を作成
         */
        private function createFloatView():void {
            for each (var b:b2Body in float) {
                var o:* = b.GetUserData();
                var shape:Shape = new Shape();
                shape.graphics.lineStyle(1, 0x000000);
                shape.graphics.beginFill(0x444444);
                shape.graphics.drawCircle(0, 0, o.r);
                shape.graphics.endFill();
                var p:b2Vec2 = b.GetPosition();
                shape.x = p.x;
                shape.y = p.y;
                o.s = shape;
                worldView.addChild(shape);
            }
        }

        /** 障害物の位置を更新
         */
        private function updateFloatView():void {
            for each (var b:b2Body in float) {
                var o:* = b.GetUserData();
                var shape:Shape = o.s;
                var p:b2Vec2 = b.GetPosition();
                shape.x = p.x;
                shape.y = p.y;
                shape.rotation = b.GetAngle() * (180 / Math.PI);
            }
        }

        /** 背景を作成
         */
        private function createBackground():void {
            for each (var j:int in [START_Y, GOAL_Y]) {
                var shape:Shape = new Shape();
                var k:int = 0;
                for (var i:int = 0; i < W; i += 8) {
                    shape.graphics.beginFill(0xFFFFFF);
                    shape.graphics.drawRect(i, k, 8, 8);
                    shape.graphics.endFill();
                    k = k == 0 ? 8 : 0;
                }
                shape.x = 0;
                shape.y = j;
                worldView.addChild(shape);
            }
        }

        /** 物理エンジンの1ステップを実行します
         */
        private function step():void {
            world.Step(1 / 9, 10);
            // 車の位置
            var p:b2Vec2 = car.GetPosition(); // 車の中心
            var t:Number = car.GetAngle(); // 0が上向き
            var vy:Number =-Math.cos(t); // (vx,vy)進行方向ベクトル
            var vx:Number = Math.sin(t);
            // アクセル
            if (acl) {
                // 力を働かせる点は車の後方
                var rp:b2Vec2 = new b2Vec2(p.x - 16 *vx, p.y - 16 *vy);
                car.ApplyForce(new b2Vec2(A_FORCE *vx, A_FORCE *vy), rp);
            }
            // ブレーキ
            car.m_linearDamping = brk ? B_DAMP : NB_DAMP;
            // ステアリング
            if (steering != 0) {
                // 力を働かせる点は車の前方
                var sy:Number = S_FORCE *-Math.cos(t + steering);
                var sx:Number = S_FORCE * Math.sin(t + steering);
                var fp:b2Vec2 = new b2Vec2(p.x + 16 *vx, p.y + 16 *vy);
                car.ApplyForce(new b2Vec2(sx, sy), fp);
            }
            // モデルをビューに反映する
            var d:DisplayObject = DisplayObject(car.GetUserData());
            d.x = p.x;
            d.y = p.y;
            d.rotation = car.GetAngle() * (180 / Math.PI);
            updateFloatView();
            // スクロール
            //worldView.x = -(p.x - VW/2);
            worldView.y = -(p.y - VH/2);
            // 時間計測
            countup = (GOAL_Y <= p.y && p.y < START_Y);
        }

        private var _countup:Boolean = false;
        internal function get countup():Boolean { return _countup; }
        internal function set countup(newValue:Boolean):void {
            var oldValue:Boolean = this._countup;
            this._countup = newValue;
            if (!oldValue && newValue) { // false→true
                time = getTimer();
            }
        }
    }
}

import Box2D.Collision.Shapes.b2CircleDef;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Collision.b2AABB;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2World;

class VirtualCart
{
        // ワールド
        private static const W:Number = 465;
        private static const H:Number = 3000;
        private static const CX:Number = W/2;
        private static const CY:Number = H/2;
        // スタート・ゴールライン(ワールド座標系)
        private static const START_Y:Number = H-50;
        private static const GOAL_Y:Number = 50;
        // パラメータ類
        private static const A_FORCE:Number = 20000;
        private static const S_FORCE:Number = A_FORCE * 0.7;
        private static const B_DAMP:Number = 1.0;
        private static const NB_DAMP:Number = 0.1;
        
        /** ワールド */
        private var world:b2World;
        /** 車モデル */
        private var car:b2Body;
        /** 壁モデル */
        private var fixed:Array; // of b2Body
        /** 障害物モデル */
        private var float:Array; // of b2Body
        
        /** スタートからの経過時間(ミリ秒) */
        private var time:int;
        
        public function VirtualCart()
        {
            // モデルを作る
            this.world = createWorld(); // ワールドを作る
            this.car = createCarBody(); // 車モデルを作る
            this.fixed = createFixed(); // 壁を作る
            this.float = createFloat(); // 障害物を作る
        }

        /** ワールドを作る
         * @return ワールド
         */
        private function createWorld():b2World {
            var aabb:b2AABB = new b2AABB();
            aabb.lowerBound.Set(0, 0);
            aabb.upperBound.Set(W, H);
            var gravity:b2Vec2 = new b2Vec2();
            return new b2World(aabb, gravity, true);
        }

        /** 車モデルを作る
         * @return 車モデル
         */
        private function createCarBody():b2Body {
            var def:b2BodyDef = new b2BodyDef();
            def.position.Set(CX, START_Y+16);
            def.angularDamping = 0.75; // 回転の減衰
            var body:b2Body = world.CreateBody(def);
            var rect:b2PolygonDef = new b2PolygonDef();
            rect.SetAsBox(16, 16);
            rect.density = 1;       // 0:固定 kg/m^2
            rect.restitution = 0.4; // 反発係数[0,1]
            rect.friction = 0.1;    // 摩擦[0,1]
            body.CreateShape(rect);
            body.SetMassFromShapes();
            return body;
        }

        /** 動かない壁をつくる
         */
        private function createFixed():Array {
            var a:Array = [
                {x:388, y:120, w:100, h:8, a:0.7853981633974483},
                {x:73, y:120, w:100, h:8, a:-0.7853981633974483},
                {x:124, y:764, w:116.25, h:8, a:0},
                {x:343, y:1226, w:116.25, h:8, a:0},
                {x:119, y:1612, w:116.25, h:8, a:0},
                {x:348, y:2000, w:140, h:8, a:0.7853981633974483},
                {x:116, y:2500, w:140, h:8, a:-0.7853981633974483},
                {x:0, y:1500, w:8, h:1500, a:0},
                {x:465, y:1500, w:8, h:1500, a:0},
                {x:232, y:3000, w:232, h:8, a:0},
            ];
            var list:Array = [];
            for each (var o:* in a) {
                var def:b2BodyDef = new b2BodyDef();
                def.position.Set(o.x, o.y);
                def.angle = o.a;
                var body:b2Body = world.CreateBody(def);
                var rect:b2PolygonDef = new b2PolygonDef();
                rect.SetAsBox(o.w, o.h);
                rect.restitution = 0.4;
                rect.friction = 0.1;
                body.CreateShape(rect);
//                body.SetUserData({w:o.w, h:o.h});
                list.push(body);
            }
            return list;
        }

        /** [B2D]動く障害物をつくる
         * @return 障害物リスト
         */
        private function createFloat():Array {
            var a:Array = [
                {x:283, y:341, r:8},
                {x:112, y:178, r:8},
                {x:355, y:654, r:8},
                {x:170, y:473, r:8},
            ];
            var list:Array = [];
            for each (var o:* in a) {
                var def:b2BodyDef = new b2BodyDef();
                def.position.Set(o.x, o.y);
                def.linearDamping = 1.0;
                def.angularDamping = 1.0;
                var body:b2Body = world.CreateBody(def);
                var circle:b2CircleDef = new b2CircleDef();
                circle.density = 0.01;
                circle.radius = o.r;
                circle.restitution = 0.6;
                circle.friction = 0.2;
                body.CreateShape(circle);
                body.SetMassFromShapes();
//                body.SetUserData({r:o.r});
                list.push(body);
            }
            return list;
        }

        /** アクセルを踏んでいるときは真 */
        public var acl:Boolean = false;
        /** ブレーキを踏んでいるときは真 */
        public var brk:Boolean = false;
        /** ステアリング(左:-90°,右:90°)(radian)*/
        public var steering:Number = 0;
        
        public var minY : Number = 9999;
        
        /** 物理エンジンの1ステップを実行します
         */
        public function step():Boolean {
            world.Step(3 / 9, 10);
            // 車の位置
            var p:b2Vec2 = car.GetPosition(); // 車の中心
            var t:Number = car.GetAngle(); // 0が上向き
            var vy:Number =-Math.cos(t); // (vx,vy)進行方向ベクトル
            var vx:Number = Math.sin(t);
            // アクセル
            if (acl) {
                // 力を働かせる点は車の後方
                var rp:b2Vec2 = new b2Vec2(p.x - 16 *vx, p.y - 16 *vy);
                car.ApplyForce(new b2Vec2(A_FORCE *vx, A_FORCE *vy), rp);
            }
            // ブレーキ
            car.m_linearDamping = brk ? B_DAMP : NB_DAMP;
            // ステアリング
            if (steering != 0) {
                // 力を働かせる点は車の前方
                var sy:Number = S_FORCE *-Math.cos(t + steering);
                var sx:Number = S_FORCE * Math.sin(t + steering);
                var fp:b2Vec2 = new b2Vec2(p.x + 16 *vx, p.y + 16 *vy);
                car.ApplyForce(new b2Vec2(sx, sy), fp);
            }
            if(p.y < minY)minY = p.y;
            
            // 時間計測
            return GOAL_Y > p.y;
        }
}

import mx.utils.ObjectUtil;

class Algo
{
	private var _curScore : uint;
	private var _optScore : uint;
	private var _curCommand : Array;
	private var _optCommand : Array;
	private var _iTemp : Number;
	
	public function Algo()
	{
		_curCommand = [];
		_curScore = simulate(_curCommand);
		_optScore = _curScore;
		_optCommand = _curCommand.concat();
		_iTemp = 0.01;
	}
	
	private static const TLIM : int = 200;
	
	public function step() : void
	{
		var nextCommand : Array = _curCommand.concat();
		for(var i : uint = 0;i < 3;i++){
		if(Math.random() < 0.6){
			// append
			// 二分探索
			var f : uint = Math.random() * (TLIM - 1) + 1;
			var ind : int = binarySearch(nextCommand, f, function(a : int, b : Object) : int 	{
				return a - b.t;
			});
			var c : Object = {t : f, type : ["acl", "brk", "left", "right", "forward"][uint(Math.random() * 5)]};
			if(ind >= 0){
				nextCommand[ind] = c;
			}else{
				nextCommand.splice(-ind-1, 0, c);
			}
		}else{
			// remove
			var removedInd : uint = Math.random() * _curCommand.length;
			nextCommand.splice(removedInd, 1);
		}
		}
		
		var score : uint = simulate(nextCommand);
		var p : Number = Math.exp(-(score - _curScore) * _iTemp);
		if(Math.random() < p){
			// adopt
			_curCommand = nextCommand;
			_curScore = score;
			if(_curScore < _optScore){
				_optScore = _curScore;
				_optCommand = _curCommand.concat();
			}
		}
	}
	
	public function get OptimizedCommand() : Object
	{
		return _optCommand;
	}
	
	public function get OptimizedScore() : Object
	{
		return _optScore;
	}
	
	public function simulate(commands : Array) : uint
	{
		var cart : VirtualCart = new VirtualCart();
		cart.acl = true;
		var p : uint = 0;
		for(var t : uint = 0;t < TLIM;t++){
			if(p < commands.length && commands[p].t == t){
				switch(commands[p].type){
				case "acl" : cart.acl = !cart.acl; break;
				case "brk" : cart.brk = !cart.brk; break;
				case "left" : cart.steering = -Math.PI / 2; break;
				case "right" : cart.steering = Math.PI / 2; break;
				case "forward" : cart.steering = 0; break;
				default:
				}
				p++;
			}
			if(cart.step())break;
		}
		if(t == TLIM)return t + cart.minY;
		return t;
	}
	
    private static function binarySearch(a : Array, v : *, comp : Function) : int
    {
    		if(a.length == 0)return -1;
    		var s : uint = 0;
    		var g : uint = a.length;
    		if(comp(v, a[g-1]) > 0)return -g-1;
    		var c0 : int = comp(v, a[0]);
    		if(c0 < 0)return -1;
    		if(c0 == 0)return 0;
    		do{
        		var m : uint = (s + g) >> 1;
        		var c : int = comp(v, a[m]);
        		if(c == 0)return m;
        		if(c < 0){
        			g = m;
        		}else{
        			s = m;
        		}
   		}while(g - s > 1);
    		return -s - 2;
	}
}