// AI Tank // // Physical tank, sensory and movement systems. Intelligence is held in a brain object. // // Author Matthew Caryl // Created 30.10.96 // // Although under copywrite to the author (Matthew Caryl) this code can be copied and modified for non-commercial // purposes as long as any derivatives contain this condition. package ThinkTank; import java.awt.Graphics; import java.awt.Color; import java.awt.Polygon; import java.awt.Rectangle; import java.lang.Math; import ADT.Physical; import ADT.Random; public final class Tank implements TankSensor, TankAction, Physical { // Maths routines static final float PI = (float) (Math.PI); static final float TWOPI = (float) (2.0 * Math.PI); static final float sin(float r) {return (float) Math.sin(r);}; static final float cos(float r) {return (float) Math.cos(r);}; static final float atan(float r) {return (float) Math.atan(r);}; static final float sqr(float n) {return n * n;}; static final float sqrt(float n) {return (float) Math.sqrt(n);}; // // Private interface of tank // // Global tank data static int SHELLSPEED = 16; static int STARTTIME = 800; static int TAKETIME = 8; // time stolen from other tank if shell hits static int GAINTIME = 8; // static int TICKTIME = 1; static int FIRETIME = 1; // cost of firing a shell // polygon outlines converted to polar coordinates for easy manipulation private static final int[] xBodyOutline = {9, 11, -11, -9}; private static final int[] yBodyOutline = {20, -20, -20, 20}; private static float[] aBodyOutline; // polar coordinates private static float[] rBodyOutline; private static final int[] xTurretOutline = {2, 2, -2, -2}; private static final int[] yTurretOutline = {18, -3, -3, 18}; private static float[] aTurretOutline; // polar coordinates private static float[] rTurretOutline; private static int radius = 0; // Calculate polar outline co-ordinates static { aBodyOutline = new float[4]; rBodyOutline = new float[4]; for (int i = 0; i < 4; i++) { aBodyOutline[i] = (float) atan((float) xBodyOutline[i] / (float) yBodyOutline[i]); if (yBodyOutline[i] < 0) aBodyOutline[i] += PI; rBodyOutline[i] = sqrt(sqr(xBodyOutline[i]) + sqr(yBodyOutline[i])); } aTurretOutline = new float[4]; rTurretOutline = new float[4]; for (int i = 0; i < 4; i++) { aTurretOutline[i] = (float) atan((float) xTurretOutline[i] / (float) yTurretOutline[i]); if (yTurretOutline[i] < 0) aTurretOutline[i] += PI; rTurretOutline[i] = sqrt(sqr(xTurretOutline[i]) + sqr(yTurretOutline[i])); } float r = 0; for (int i = 0; i < 4; i++) { if (rBodyOutline[i] > r) r = rBodyOutline[i]; if (rTurretOutline[i] > r) r = rTurretOutline[i]; } radius = (int) r + 2; } // individual tank data private World world; private float x, y; private float heading; private float turret = 0; private Brain brain; private int time = STARTTIME; private float physicalCollisionAngle; private boolean physicalCollision = false; private float oldX, oldY; private float oldHeading; private float oldTurret; // individual tank display data private int[][][] outline = new int[2][2][5]; private boolean dirtyOutline = true; private Rectangle bounds = new Rectangle(); private boolean dirtyBound = true; private Color color; // generate attractive random colour private Color randomColor() { Random random = world.random(); int[] colorlist = {random.range(64) + 196, random.range(64) + 128, random.range(64) + 64}; for (int i = 3; i > 0; i--) { int p = random.range(3); int q = random.range(3); int temporary; temporary = colorlist[p]; colorlist[p] = colorlist[q]; colorlist[q] = temporary; } return new Color(colorlist[0], colorlist[1], colorlist[2]); } // remember previous position to return to after a collision private void remember() { oldX = x; oldY = y; oldHeading = heading; oldTurret = turret; } // is first angle less than second private boolean less(float first, float second) { if (first < second) return first + PI > second; else return first > second + PI; } // ensure angle is between [0..TWOPI] private float normalise(float angle) { if (angle < 0) return angle + TWOPI; else if (angle > TWOPI) return angle - TWOPI; else return angle; } // // Public interface of tank // // initialise tank, standard classifier brain public Tank(World w) { world = w; brain = new Classifier(w.random()); color = randomColor(); teleport(); } // initialise tank public Tank(World w, Brain b) { world = w; brain = b; color = randomColor(); teleport(); } // new mutated tank public Tank breed() { return new Tank(world, brain.breed(world.random())); } // new crossbreed tank // -- not implemented -- public Tank breed(Tank mate) { return new Tank(world, brain.breed(mate.brain, world.random())); } // random position and angle public void teleport() { set(world.random().range(world.width()), world.random().range(world.height())); heading = (float) world.random().range(360) * PI / 180f; } // report of shell hitting wall public void hit(Shell s) { world.subPhysical((Physical) s); } // report of shell hitting physical object public void hit(Shell s, Physical p) { world.subPhysical((Physical) s); if (p instanceof Tank) { ((Tank) p).time -= TAKETIME; time += GAINTIME; } else ; // do nothing for the moment } // tank heading public float heading() { return heading; } // turret offset public float turret() { return turret; } // // Public interface of physical // // retrieve outlines of tank components public int[][][] outline() { if (dirtyOutline) { for (int i = 0; i < 4; i++) { outline[0][0][i] = (int) (x + rBodyOutline[i] * sin(heading + aBodyOutline[i])); outline[0][1][i] = (int) (y - rBodyOutline[i] * cos(heading + aBodyOutline[i])); } outline[0][0][4] = outline[0][0][0]; outline[0][1][4] = outline[0][1][0]; for (int i = 0; i < 4; i++) { outline[1][0][i] = (int) (x + rTurretOutline[i] * sin(heading + turret + aTurretOutline[i])); outline[1][1][i] = (int) (y - rTurretOutline[i] * cos(heading + turret + aTurretOutline[i])); } outline[1][0][4] = outline[1][0][0]; outline[1][1][4] = outline[1][1][0]; dirtyOutline = false; } return outline; } // retrieve bounding rectangle of tank components public Rectangle bounds() { if (dirtyBound) { int minx, maxx, miny, maxy; int tx, ty; minx = maxx = (int) (x + rBodyOutline[0] * sin(heading + aBodyOutline[0])); miny = maxy = (int) (y - rBodyOutline[0] * cos(heading + aBodyOutline[0])); for (int i = 1; i < 4; i++) { tx = (int) (x + rBodyOutline[i] * sin(heading + aBodyOutline[i])); ty = (int) (y - rBodyOutline[i] * cos(heading + aBodyOutline[i])); if (tx < minx) tx = minx; else if (tx > maxx) tx = maxx; if (ty < miny) ty = miny; else if (ty < maxy) ty = maxy; } bounds.reshape(minx, miny, maxx - minx, maxy - miny); dirtyBound = false; } return bounds; } // retrieve the maximum radius of the tank public int radius() { return radius; } public int x() { return (int) x; } public int y() { return (int) y; } // set coordinates, remember previous public void set(int X, int Y) { remember(); x = X; y = Y; dirtyOutline = true; dirtyBound = true; } // has tank moved from remembered location public boolean moved() { return x != oldX || y != oldY || heading != oldHeading || turret != oldTurret; } // gather sensory information public void sensor() { brain.sensor(this); physicalCollision = false; } // take action public void action() { remember(); brain.action(this); time -= TICKTIME; if (time < 0) world.subPhysical(this); } // sensory and action public void tick() { sensor(); action(); } // revert tank back to its old position, wall collision public void collision(boolean first) { x = oldX; y = oldY; heading = oldHeading; turret = oldTurret; dirtyOutline = true; dirtyBound = true; } // Revert tank back to its old position, physical collision public void collision(Physical p, boolean first) { x = oldX; y = oldY; heading = oldHeading; turret = oldTurret; dirtyOutline = true; dirtyBound = true; // work out collision angle if (first) { int X = p.x(); int Y = p.y(); int x = x(); int y = y(); if (X != x) physicalCollisionAngle = atan((float) (X - x) / (float) (y - Y)); else physicalCollisionAngle = Y < y ? 0 : PI; if (y < Y) physicalCollisionAngle += PI; physicalCollisionAngle = normalise(physicalCollisionAngle); physicalCollision = true; } } // draw new outline in tank colour public void paint(Graphics g) { g.setColor(color); outline(); for (int i = outline.length - 1; i >= 0; i--) g.drawPolygon(outline[i][0], outline[i][1], outline[i][0].length); } // draw current outline in current colour public void erase(Graphics g) { for (int i = outline.length - 1; i >= 0; i--) g.drawPolygon(outline[i][0], outline[i][1], outline[i][0].length); } // // Public interface of tank sensor // // any tanks visible between start and finish angle relative to the turret within distance public boolean turretRadar(float start, float finish, float range) { int distance = (int) (radius * range); return world.searchAngle(this, heading + turret + start, heading + turret + finish, distance); } // any tanks visible between start and finish angle relative to the tank within distance public boolean tankRadar(float start, float finish, float range) { int distance = (int) (radius * range); return world.searchAngle(this, heading + start, heading + finish, distance); } // did physical collision occur in this view field public boolean collisionSensor(float start, float finish) { if (!physicalCollision) return false; start = normalise(start + heading); finish = normalise(finish + heading); return (less(start, physicalCollisionAngle) && less(physicalCollisionAngle, finish)); } // did wall collision occur in this view field public boolean obstacleSensor(float start, float finish, float range) { float distance = radius * range; int x1 = (int) (x + sin(heading + start) * distance); int y1 = (int) (y - cos(heading + start) * distance); int x2 = (int) (x + sin(heading + finish) * distance); int y2 = (int) (y - cos(heading + finish) * distance); return world.checkLine((int) x, (int) y, x2, y2); } // // Public interface of tank action // // turn tank by angle public void turn(float angle) { heading += angle; if (heading < 0) heading += TWOPI; else if (heading > TWOPI) heading -= TWOPI; dirtyOutline = true; dirtyBound = true; } // move distance forward public void move(float distance) { x += sin(heading) * distance; y -= cos(heading) * distance; dirtyOutline = true; dirtyBound = true; } // turn turret by angle public void aim(float angle) { turret += angle; if (turret < 0) turret += TWOPI; else if (turret > TWOPI) turret -= TWOPI; dirtyOutline = true; dirtyBound = true; } // fire gun public void fire() { float direction = heading + turret; float distance = radius + Shell.radius; Shell s; s = new Shell(this, (int) (x + sin(direction) * distance), (int) (y - cos(direction) * distance), direction, SHELLSPEED, color); world.addPhysical((Physical) s); time -= FIRETIME; } }