sample4 (remade) Keep it complicated, stupid! wh0 forked:1favorite:0lines:257license : MIT License modified : 2011-05-15 07:55:58 Embed Tweet // 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); } } Code Fullscreen Preview Fullscreen infinite-tank-entry value lineTo moveTo lineStyle rotation Shape LineScaleMode.NORMAL CapsStyle.NONE Math.min Math.abs rotate ty tx translate graphics Math.atan drawCircle copy Math.cos alpha sort new page view favorite forked pv241 forked from: sample4 (remade) bradsedito forked:0 favorite:0lines:257 (diff:3)