// AI Tank World // // Applet that controls simulation. // // Author Matthew Caryl // Created 30.10.96 // Modified 2.3.01 // // 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.Event; import java.applet.Applet; import java.io.InputStream; import java.io.IOException; import java.net.URL; import java.lang.Math; import java.lang.System; import ADT.Queue; import ADT.QueueEmpty; import ADT.QueueItemAbsent; import ADT.Random; import ADT.Physical; import ADT.BSPT; import ADT.Model; public final class World extends Applet implements Runnable { // Maths interace private static final float PI = (float) Math.PI; private static final float TWOPI = (float) Math.PI * 2f; private static final float atan(float a) {return (float) Math.atan(a);}; private static final int sqr(int n) {return n * n;}; // // Private world interface // public static final Color backgroundColor = Color.black; public static final Color outlineColor = Color.white; private Thread animationThread; private float frameTime = 40; private Control control; private Graphics graphics; private Random random = new Random(); private long initial_seed; private int width; private int height; private BSPT bspt; private Polygon[] polygons; private int round = 1; private Queue currentPhysicals; private Queue newPhysicals; private Queue oldPhysicals; private Queue oldTanks; private Physical[] physicals = new Physical[0]; // download polygon description of world private void downloadWorld(String worldString) { try { InputStream is = new URL(getCodeBase(), worldString).openStream(); polygons = BSPT.parsePolygons(is, width(), height()); bspt = new BSPT(polygons); } catch (IOException e) { int[] wallx = {1, 1, width() - 2, width() - 2, 1}; int[] wally = {1, height() - 2, height() - 2, 1, 1}; polygons = new Polygon[1]; polygons[0] = new Polygon(wallx, wally, 5); bspt = new BSPT(polygons); } } // place tank without collision with walls or curret objects private Tank placeTank(Tank t) { while(bspt.in(t.x(), t.y(), t.radius()) || Model.collision(currentPhysicals, t)) t.teleport(); return t; } // 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; } // battle tanks until all are dead private void combat() { try { while (!currentPhysicals.empty()) { synchronized (currentPhysicals) { int size = currentPhysicals.size(); Physical p; // switch to fast array if (physicals.length < size) physicals = new Physical[size * 2]; for (int i = size - 1; i >= 0; i--) physicals[i] = (Physical) currentPhysicals.requeue(); // give a sensor to each physical for (int i = size - 1; i >= 0; i--) physicals[i].sensor(); // give a tick to each physical for (int i = size - 1; i >= 0; i--) { p = physicals[i]; p.action(); // resolve physical-static collision if (bspt.in(p.x(), p.y(), p.radius())) p.collision(true); } // resolve physical-physical collisions if (Model.collision(currentPhysicals, true)) while (Model.collision(currentPhysicals, false)) ; // do nothing // remove some of the physicals graphics.setColor(backgroundColor); while (!oldPhysicals.empty()) { p = (Physical) oldPhysicals.dequeue(); p.erase(graphics); } // paint and redraw for (int i = currentPhysicals.size(); i > 0; i--) { p = (Physical) currentPhysicals.requeue(); graphics.setColor(backgroundColor); p.erase(graphics); p.paint(graphics); } // add some of the physicals while (!newPhysicals.empty()) { p = (Physical) newPhysicals.dequeue(); currentPhysicals.enqueue(p); p.paint(graphics); } } } } catch (QueueEmpty e) { System.err.println("report to programmer - combat"); } } // new tanks, two from winner, none from looser, one from the others private void breed() { try { int size = oldTanks.size(); Tank[] tanks = new Tank[size]; for (int i = 0; i < size; i++) tanks[i] = (Tank) oldTanks.dequeue(); for (int i = size; i > 0; i--) { Tank t; if (i == size) t = tanks[0]; else t = tanks[i]; t = t.breed(); t = placeTank(t); currentPhysicals.enqueue(t); } } catch (QueueEmpty e) { System.err.println("report to programmer - breed"); } } // display round number in center of applet private void round(int round) { graphics.setColor(backgroundColor); graphics.fillRect(0, 0, width(), height()); graphics.setColor(outlineColor); String message = new String("Round " + Integer.toString(round)); int messageWidth = graphics.getFontMetrics(graphics.getFont()).stringWidth(message); int messageHeight = graphics.getFontMetrics(graphics.getFont()).getHeight(); graphics.drawString(message, width() / 2 - messageWidth / 2, height() / 2 - messageHeight / 2); } // sleep for a brief while private void delay(int delay) { try { Thread.sleep(delay); } catch (InterruptedException e) { long finish = System.currentTimeMillis() + delay; while (System.currentTimeMillis() < finish) ; // do nothing } } // // Public applet interface // public void init() { initial_seed = random.seed(); setBackground(backgroundColor); // Read in width, height, and block size parameters. String widthString = this.getParameter("width"); try { width = Integer.parseInt(widthString); if (width < 1) throw new NumberFormatException("Width must be postive"); } catch (NumberFormatException e) {width = 512;} String heightString = this.getParameter("height"); try { height = Integer.parseInt(heightString); if (height < 1) throw new NumberFormatException("Height must be postive"); } catch (NumberFormatException e) {height = 512;} // Download world description downloadWorld(this.getParameter("worldDescription")); // Save graphics context for later // Note: this seems to reduce garbage collection problems graphics = this.getGraphics(); // how fast do we go String frameRateString = this.getParameter("frameRate"); try { frameTime = (float) 1000 / (float) Integer.parseInt(frameRateString); } catch (NumberFormatException e) { // do nothing } reset(); } // start again from round 1 public void reset() { int tanks; // Read in number of tanks in the world String tanksString = this.getParameter("tankNumber"); try { tanks = Integer.parseInt(tanksString); if (tanks < 0) throw new NumberFormatException("Tank number must be positive"); } catch (NumberFormatException e) {tanks = 5;} // Make first tanks currentPhysicals = new Queue(); newPhysicals = new Queue(); oldPhysicals = new Queue(); oldTanks = new Queue(); for (int i = tanks; i > 0; i--) currentPhysicals.enqueue(placeTank(new Tank(this))); round = 1; } // stop animation thread before disappearing public void finalize() throws Throwable { if (animationThread != null && animationThread.isAlive()) animationThread.stop(); animationThread = null; super.finalize(); } // resume rather start from the beginning public void start() { if (animationThread == null) { animationThread = new Thread(this); animationThread.start(); } else animationThread.resume(); } // suspend rather than stop public void stop() { if (animationThread != null && animationThread.isAlive()) animationThread.suspend(); } // start animation and remove control panel public void restart() { start(); control = null; } // main combat round cycle public void run() { while (true) { long Time = System.currentTimeMillis(); combat(); breed(); round++; round(round); delay(2000); repaint(); // slow thread down try { long sleepTime = (long)(frameTime - (float) System.currentTimeMillis() + (float) Time); System.out.println(sleepTime); animationThread.sleep(Math.max(0, sleepTime)); } catch (InterruptedException e) { // do nothing } } } // redisplay landscape public void repaint() { paint(graphics); } // display landscape public void paint(Graphics g) { g.setColor(backgroundColor); g.fillRect(0, 0, width(), height()); g.setColor(outlineColor); bspt.draw(g); } // bring control panel up public boolean mouseDown(Event e, int x, int y) { if (control == null) { stop(); control = new Control(this, initial_seed); } return true; } public String[][] getParameterInfo() { String[][] info = { {"width ", "positive integer ", "width of world in pixels"}, {"height ", "positive integer ", "height of world in pixels"}, {"tankNumber ", "positive integer ", "number of tanks"}, {"worldDescription", "polygon file name", "description of landscape"} }; return info; } // // Public world interface // public int width() { return width; } public int height() { return height; } public Random random() { return random; } // polygon description of landscape public Polygon[] polygons() { return polygons; } // load as many of current physical objects as possible into array public int physicals(Physical[] physicals) { synchronized (currentPhysicals) { try { int size = currentPhysicals.size(); int limit = physicals.length - 1; int i; for (i = size - 1; i > limit; i--) currentPhysicals.requeue(); for (; i >= 0; i--) physicals[i] = (Physical) currentPhysicals.requeue(); return size; } catch (QueueEmpty e) { System.err.println("report to programmer - physicals"); return 0; } } } // search for Tanks within the physicals point of view public boolean searchAngle(Physical p, float start, float finish, int distance) { int x = p.x(); int y = p.y(); int X; int Y; float a; int d = sqr(distance); start = normalise(start); finish = normalise(finish); for (int i = currentPhysicals.size() - 1; i >= 0; i--) { if (physicals[i] == p) continue; if (physicals[i] instanceof Shell) continue; X = physicals[i].x(); Y = physicals[i].y(); if (X != x) a = atan((float) (X - x) / (float) (y - Y)); else a = Y < y ? 0 : PI; if (y < Y) a += PI; a = normalise(a); if (less(start, a) && less(a, finish) && sqr(X - x) + sqr(Y - y) < d && !bspt.in(x, y, X, Y)) return true; } return false; } // check for line collision with landscape polygons public boolean checkLine(int x1, int y1, int x2, int y2) { return bspt.in(x1, y1, x2, y2); } // try and add new phyiscal to world, may cause collision public void addPhysical(Physical p) { newPhysicals.enqueue(p); if (bspt.in(p.x(), p.y(), p.radius())) p.collision(true); } // try and remove physical from world, have to check current and new physicals public void subPhysical(Physical p) { try { oldPhysicals.enqueue(p); currentPhysicals.remove(p); if (p instanceof Tank) oldTanks.enqueue(p); } catch (QueueItemAbsent e) { try { newPhysicals.remove(p); } catch (QueueItemAbsent f) { if (p instanceof Tank) System.err.println("problem tank"); else System.err.println("problem shell"); } } } }