sample4 (remade)
Keep it complicated, stupid!
♥0 |
Line 257 |
Modified 2011-05-15 07:55:58 |
MIT License
archived:2017-03-10 11:17:02
| (replaced)
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);
}
}