sample4 (remade)

by wh0
Keep it complicated, stupid!
♥0 | Line 257 | Modified 2011-05-15 07:55:58 | MIT License | (replaced)
play

ActionScript3 source code

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

// Keep it complicated, stupid!
    
package {
    import net.wonderfl.game.infinity_tank.development.*;
    import net.wonderfl.math.*;
    import flash.utils.getTimer;
    import flash.display.*;
    import flash.text.TextField;
    import flash.geom.Matrix;
    [SWF(backgroundColor="#000000")]
    public class Tank extends TankBase {
        
        private static const tankColor:int = 0x00c0ff;
        private static const gunColor:int = 0x00c0ff;
        private static const tacticsColor:int = 0x00c0ff;
        
        private const debug:TextField = new TextField();
        private const tank:Shape = prepareTank();
        private const gun:Shape = prepareGun();
        private const tactics:Sprite = prepareTactics();
        
        private function prepareTank():Shape {
            var s:Shape = new Shape();
            var g:Graphics = s.graphics;
            g.lineStyle(0, tankColor, 0.5);
            g.moveTo(25, 10); g.lineTo(25, 15); g.lineTo(20, 15);
            g.moveTo(-25, 10); g.lineTo(-25, 15); g.lineTo(-20, 15);
            g.moveTo(25, -10); g.lineTo(25, -15); g.lineTo(20, -15);
            g.moveTo(-25, -10); g.lineTo(-25, -15); g.lineTo(-20, -15);
            g.lineStyle(0, tankColor);
            g.moveTo(15, 5); g.lineTo(20, 0); g.lineTo(15, -5);
            g.moveTo(20, 0); g.lineTo(-20, 0);
            return s;
        }
        
        private function prepareGun():Shape {
            var s:Shape = new Shape();
            var g:Graphics = s.graphics;
            g.lineStyle(1, gunColor, 0.25);
            g.drawCircle(0, 0, 30);
            g.lineStyle(1, gunColor);
            g.moveTo(27, 0);
            g.lineTo(33, 0);
            return s;
        }
        
        private function prepareTactics():Sprite {
            var s:Sprite = new Sprite();
            s.alpha = 0.1;
            debug.textColor = tacticsColor;
            s.addChild(debug);
            return s;
        }
        
        public function Tank() {
            _bulletRenderer = 'http://swf.wonderfl.net/swf/usercode/4/4f/4fee/4fee29a14b1179c798151aed6be0f21005c9bd3f.swf';
            super();
        }
        
        // called every 5 frames
        override public function action():int {
            /*
            updateBullet();
            var g:Graphics = tactics.graphics;
            g.clear();
            for (var x:Number = 8; x < 600; x += 16) {
                for (var y:Number = 8; y < 550; y += 16) {
                    var v:Number = eval(new WVector2D(x, y), 0);
                    g.beginFill(v < 0 ? 0xff0000 : v > 0.8 ? 0xff00 : 0xff, 0.25);
                    g.drawRect(x - v * 4, y - v * 4, v * 8, v * 8);
                    g.endFill();
                }
            }
            return 0;
            */
            var start:int = getTimer();
            tactics.graphics.clear();
            // call stuff
            var c:int = getMovement() | getAttack();
            // done
            var end:int = getTimer();
            debug.x = _scene.myTankPosition.x;
            debug.y = _scene.myTankPosition.y;
            debug.text = (end - start).toString() + ' ms';
            return c;
        }
        
        // --- movement
        private var move:int;
        private var value:Number;
        
        /** position tank strategically, avoiding enemy bullets */
        private function getMovement():int {
            updateBullet();
            // consider current situation
            value = eval(_scene.myTankPosition, 0);
            move = Command.DO_NOTHING;
            var g:Graphics = tactics.graphics;
            g.lineStyle();
            g.beginFill(value < 0 ? 0xff0000 : 0xff00, 0.5);
            g.drawCircle(_scene.myTankPosition.x, _scene.myTankPosition.y, 10 * Math.abs(value));
            g.endFill();
            var e:WVector2D = view(_scene.myTankPosition, _scene.myTankAngle, _scene.enemyTankPosition);
            if (value >= 0.8) {
                // decent position; turn to maximize evasion
                move = e.x * e.y < 0 ? Command.TANK_TURN_LEFT : Command.TANK_TURN_RIGHT;
            } else {
                // unsatisfactory; consider moving
                var d:Boolean = value < 0;
                // always consider forward and backward
                if (visit(0, 0, 1, d)) { move = Command.TANK_MOVE_FORWARD; }
                if (visit(0, 0, -1, d)) { move = Command.TANK_MOVE_BACKWARD; }
                // consider turns if in danger or resulting in better evasion
                if ((d || e.x / e.y > -0.5) && visit(0, 1, 1, d)) { move = Command.TANK_MOVE_FORWARD | Command.TANK_TURN_RIGHT; }
                if ((d || e.x / e.y < 0.5) && visit(0, -1, 1, d)) { move = Command.TANK_MOVE_FORWARD | Command.TANK_TURN_LEFT; }
                if ((d || e.x / e.y > -0.5) && visit(0, 1, -1, d)) { move = Command.TANK_MOVE_BACKWARD | Command.TANK_TURN_RIGHT; }
                if ((d || e.x / e.y < 0.5) && visit(0, -1, -1, d)) { move = Command.TANK_MOVE_BACKWARD | Command.TANK_TURN_LEFT; }
                if (!d) {
                    // safe; consider turning in place
                    if (e.x / e.y > -0.5 && visit(Math.PI / 3, 0, 1, d)) { move = Command.TANK_TURN_RIGHT; }
                    if (e.x / e.y < 0.5 && visit(-Math.PI / 3, 0, 1, d)) { move = Command.TANK_TURN_LEFT; }
                    if (e.x / e.y > -0.5 && visit(Math.PI / 3, 0, -1, d)) { move = Command.TANK_TURN_RIGHT; }
                    if (e.x / e.y < 0.5 && visit(-Math.PI / 3, 0, -1, d)) { move = Command.TANK_TURN_LEFT; }
                }
            }
            if (value < 0)
                move |= e.x * e.y < 0 ? Command.TANK_TURN_LEFT : Command.TANK_TURN_RIGHT;
            return move;
        }
        
        /** consider a course of action */
        private function visit(preRotate:Number, angularVelocity:Number, throttle:Number, danger:Boolean):Boolean {
            var r:Boolean = false;
            var p:Projection = new Projection(_scene.myTankPosition, _scene.myTankAngle + preRotate, _scene.myTankLinearVelocity, angularVelocity, throttle);
            var g:Graphics = tactics.graphics;
            g.moveTo(p.x, p.y);
            for (var i:int = 1; i < 50; i++) {
                var e:WVector2D = p.next();
                if (e == null)
                    break;
                var v:Number = eval(e, i * Projection.step);
                g.lineStyle(10 * Math.abs(v), v < 0 ? 0xff0000 : 0xff00, 0.1, false, LineScaleMode.NORMAL, CapsStyle.NONE);
                g.lineTo(e.x, e.y);
                if (danger) {
                    // danger; try to evade
                    if (v <= -1 && value > -1) {
                        // hit; stop immediately
                        break;
                    } else if (v >= 0) {
                        // escaped; compare with other escapes
                        if (v > value) {
                            value = v;
                            return true;
                        } else {
                            break;
                        }
                    } else if (v > value) {
                        // postponed hit
                        value = v;
                        r = true;
                    }
                } else {
                    // safe; optimize strategic value
                    if (v < 0) {
                        // danger; don't bother
                        break;
                    } else if (v > value) {
                        // better position found
                        value = v;
                        r = true;
                    }
                }
            }
            return r;
        }
        
        /** evaluate the strategic value of a position upon reaching it */
        private function eval(p:WVector2D, t:Number):Number {
            var v:Number = 0;
            // treat enemy as special bullet to avoid close quarters
            var e:WVector2D = view(_scene.enemyTankPosition, _scene.enemyTankAngle, p);
            if (e.lengthSquared < 1600)
                v = Math.min(v, -1);
            else if (e.x > -200 && e.x < 200 && e.y > -100 && e.y < 100)
                v = Math.min(v, (Math.abs(e.x) - 200) / 200);
            // check danger from bullets
            for (var b:BoundBox = _scene.enemyBulletList; b; b = b.next) {
                // calculate future position
                e = new WVector2D(b.position.x + t * 1.3125 * b.linearVelocity.x, b.position.y + t * 1.3125 * b.linearVelocity.y);
                e = view(e, b.rotation, p);
                if (e.lengthSquared < 2500)
                    v = Math.min(v, -1);
                else if (e.x > -60 && e.x < 600 && e.y > -50 && e.y < 50)
                    v = Math.min(v, (e.x - 600) / 600);
            }
            if (v == 0) {
                // safe; consider strategic factors
                // proximity to center
                v = (165625 - (300 - p.x) * (300 - p.x) - (275 - p.y) * (275 - p.y)) / 119400;
                // enemy orientation
                e = view(_scene.enemyTankPosition, _scene.enemyTankAngle, p);
                v += Math.abs(Math.atan(e.x / e.y)) / Math.PI - 0.25;
            }
            return v;
        }
        
        /** alter enemy bullets, storing actual velocity information */
        private function updateBullet():void {
            var g:Graphics = tactics.graphics;
            g.lineStyle(4, tacticsColor, 0.5);
            var b:BoundBox;
            for (b = _scene.enemyBulletList; b; b = b.next) {
                if (b.extents.x == 4 && b.extents.y == 6 && b.rotation == 0) {
                    // fresh bullet (note that this configuration is impossible otherwise)
                    b.extents = b.position;
                    b.rotation = Math.atan2(b.linearVelocity.y, b.linearVelocity.x);
                    continue;
                }
                // compute displacement and scale by time
                b.linearVelocity = new WVector2D((b.position.x - b.extents.x) * 6, (b.position.y - b.extents.y) * 6);
                b.extents = b.position.copy();
                g.moveTo(b.position.x, b.position.y);
                g.lineTo(b.position.x + b.linearVelocity.x / 6, b.position.y + b.linearVelocity.y / 6);
            }
        }
        
        // --- attack
        private var averageThrottle:Number = 0;
        private var averageAngularVelocity:Number = 0;
        
        /** extrapolate enemy position, aim, and fire if appropriate */
        private function getAttack():int {
            // update averages
            averageThrottle = averageThrottle * 0.875 + (_scene.enemyTankLinearVelocity.x * Math.cos(_scene.enemyTankAngle) + _scene.enemyTankLinearVelocity.y * Math.sin(_scene.enemyTankAngle)) * 0.000625;
            averageAngularVelocity = averageAngularVelocity * 0.875 + _scene.enemyTankAngularVelocity * 0.125;
            var m:WVector2D = _scene.myTankPosition;
            // extrapolate enemy-bullet intersection
            var e:WVector2D;
            var d:WVector2D = _scene.myTankLinearVelocity;
            d.scale(-0.5 * Projection.step);
            var p:Projection = new Projection(_scene.enemyTankPosition, _scene.enemyTankAngle, _scene.enemyTankLinearVelocity, averageAngularVelocity, averageThrottle);
            var g:Graphics = tactics.graphics;
            g.lineStyle(0, tacticsColor, 0.5, false, LineScaleMode.NORMAL, CapsStyle.NONE);
            g.moveTo(p.x, p.y);
            for (var i:int = 1; i < 30; i++) {
                e = p.next();
                if (e == null)
                    break;
                g.lineTo(e.x, e.y);
                if (WMath.distance(m, e) <= 160 * Projection.step * i + 30)
                    break;
                p.x += d.x; p.y += d.y;
            }
            g.drawRect(e.x - 5, e.y - 5, 10, 10);
            // issue commands
            e = view(m, _scene.myTankAngle + _scene.myGunAngle, e);
            var aim:int = e.y < 0 ? Command.GUN_TURN_RIGHT : Command.GUN_TURN_LEFT;
            if (e.x > 0 && e.y > -30 && e.y < 30 && (_scene.myBulletCount < 2 || e.x < 100))
                aim |= Command.FIRE;
            return aim;
        }
        
        private static function view(s:WVector2D, a:Number, d:WVector2D):WVector2D {
            var dx:Number = d.x - s.x, dy:Number = d.y - s.y;
            var ax:Number = Math.cos(a), ay:Number = Math.sin(a);
            return new WVector2D(dx * ax + dy * ay, dx * ay - dy * ax);
        }
        
        // called when a bullet hits this tank
        override public function hit():void {
        }
        
        // called when this tank fires
        override public function fire():void {
        }
        /*
        // bullet cheat :D
        private namespace tank_internal = 'http://flash-games.wonderfl.net/tank';
        override tank_internal function __setScene(b:BattleScene):void {
            super.tank_internal::__setScene(b);
            var sentinel:BoundBox = new BoundBox(null, null, 0, new WVector2D());
            for (var i:int = 0; i < 30; i++)
                _scene.tank_internal::removeBullet(sentinel, _scene.tank_internal::id);
        }
        */
        override public function draw(d:BitmapData):void {
            var p:WVector2D = _scene.myTankPosition;
            d.lock();
            d.fillRect(d.rect, 0);
            
            var m:Matrix = new Matrix();
            m.rotate(_scene.myTankAngle);
            m.translate(p.x, p.y);
            d.draw(tank, m);
            
            m.identity();
            m.rotate(_scene.myGunAngle + _scene.myTankAngle);
            m.translate(p.x, p.y);
            d.draw(gun, m);
            
            d.draw(tactics);
            
            d.unlock();
        }
        
        override protected function _init():void {
            // minimize CPU usage during development
            super._init();
            stage.frameRate = 1;
        }
        
    }
}

internal class Projection {
    
    import net.wonderfl.math.*;
    
    public static const step:Number = 0.0333;
    public static const dampen:Number = 0.910;
    public static const power:Number = 0.720;
    
    public var x:Number, y:Number;
    public var dx:Number, dy:Number;
    public var ddx:Number, ddy:Number;
    public var dddx:Number, dddy:Number;
    
    public function Projection(position:WVector2D, angle:Number, linearVelocity:WVector2D, angularVelocity:Number, throttle:Number) {
        x = position.x; y = position.y;
        dx = linearVelocity.x * step; dy = linearVelocity.y * step;
        ddx = power * throttle * Math.cos(angle); ddy = power * throttle * Math.sin(angle);
        dddx = Math.cos(angularVelocity * step); dddy = Math.sin(angularVelocity * step);
    }
    
    public function next():WVector2D {
        var tx:Number = ddx * dddx - ddy * dddy, ty:Number = ddx * dddy + ddy * dddx;
        ddx = tx; ddy = ty;
        dx = dx * dampen + ddx; dy = dy * dampen + ddy;
        x = Math.max(15, Math.min(585, x + dx)); y = Math.max(15, Math.min(535, y + dy));
        if (
            x >= 585 && dx > 4 ||
            x <= 15 && dx < -4 ||
            y >= 535 && dy > 4 ||
            y <= 15 && dy < -4
        )
            return null;
        return new WVector2D(x, y);
    }
    
}