/* * Nathaniel Lim * 4/29/08 * Darwin 2.0 Part 2 Lab * CS136 Williams College * njl2@williams.edu */ import java.util.Random; import java.util.Stack; import java.util.concurrent.atomic.*; import java.awt.Point; import java.awt.Dimension; import java.util.ArrayList; import java.lang.Boolean; public class Chimera extends Creature { /* * The idea behind Chimera is to exist in two forms, one form which * moves around sort of like a Pirate does, and another form which moves * like a sheep (clumping together). This design choice relies on the global * understanding of the map only for the positions of other instances of Chimera in * order to promote clumping * I chose to do this because, the understanding of where the enemies are is transient * and what is known about where other guys are is current. * * Clumped up Creatures tend to do better as they can cover each other * But they don't get anywhere if they clump up all the time, so * some other Chimeras go explore. */ //Static Variables shared by all instances of SmartCreature public static ObservationArray2D map; public static AtomicInteger numAlive = new AtomicInteger(0); public static AtomicInteger numScouts = new AtomicInteger(0); public static AtomicInteger numSheep = new AtomicInteger(0); public static Boolean hasMadeMap = new Boolean(false); //Constants public static final double SHEEP_TO_SCOUTS = 1.7; public static final double DELTA = 0.3; public static final int SHEEP = 1; public static final int SCOUT = 2; public static final int NOTHING = 0; //Instance Variables private Stack<Point> lastVisited = new Stack<Point>(); private boolean isScout = false; public String getAuthorName() { return "Nathaniel Lim"; } public String getDescription() { return "Chimera's strategy is to either be a scout, which wanders around looking for things, or be a sheep which tries to clump up with other Chimeras "; } /* * Figures out what type of Chimera needs to be added in order to * maintain a certain ratio of sheep to scouts */ public int populationAssay(){ int sheep = numSheep.get(); int scouts = numScouts.get(); if (scouts == 0){ return SCOUT; } else if ( sheep == 0){ return SHEEP; } else { double sheepDouble = (double)sheep; double scoutsDouble = (double)scouts; //System.out.println("Sheep: " + sheepDouble + " Scouts: " + scoutsDouble); if ( (sheepDouble/scoutsDouble) > (SHEEP_TO_SCOUTS + DELTA) ){ //need more scouts return SCOUT; } else if ( (sheepDouble/scoutsDouble) < (SHEEP_TO_SCOUTS - DELTA)) { //need more sheep return SHEEP; } else { return NOTHING; } } } public void addScout(){ isScout = true; numScouts.getAndAdd(1); } public void addSheep(){ isScout = false; numSheep.getAndAdd(1); } public void run(){ synchronized(hasMadeMap){ if (map == null) { map = new ObservationArray2D(getMapDimensions(), getClass().getName()); } } update(); numAlive.getAndAdd(1); //What should this Chimera spawn as? int decision = populationAssay(); if(decision == SHEEP){ addSheep(); } else if (decision == SCOUT){ addScout(); } else { Random r = new Random(); if (r.nextInt(2) == 0){ addScout(); } else { addSheep(); } } try{ while (true){ //Help maintain population ratio //The Chimera can change its character //depending on the population of scouts and //sheep. Does nothing if decision==NOTHING decision = populationAssay(); if (decision == SHEEP) { if (isScout){ //Convert Self to a Sheep synchronized(numSheep) { isScout = false; numScouts.getAndAdd(-1); numSheep.getAndAdd(1); } } } else if (decision == SCOUT){ if (!isScout){ //Its a sheep //Convert Self to a Scout synchronized(numSheep) { isScout = true; numSheep.getAndAdd(-1); numScouts.getAndAdd(1); } } } //End of population management //Scouts and Sheep act differently if (isScout){ scout(); } else { sheep(); } } } catch (ConvertedError e){ //Conversion occured synchronized(map){ map.set(getPosition(), new Observation(getPosition(), "ENEMY", 0, Direction.EAST, getTime())); } } finally{ //Regardless of how the creature died/stopped running //The population must be atomically decremented. numAlive.getAndAdd(-1); if (isScout){ numScouts.getAndAdd(-1); } else { numSheep.getAndAdd(-1); } } } public void scout(){ //Is aggressive and does a certain amount of exploration Observation o = look(); int d = distance(o.position); if (isEnemy(o)){ //If its right in front, attack() !!! if (o.position.equals(getMovePosition())){ attack(); turn90Random(); } else { //Move towards the enemy this turn, //if something jumps in front, attack it! if(!moveForward()){ attack(); turn90Random(); } } } else { if (o.position.equals(getMovePosition())){ //Obstacle turn90Random(); } else { wander(d); } } } public void sheep(){ //Tries to stick together, as well as attack stuff //Gets Information about whats in front //and where friends are. Observation o = look(); int d = distance(o.position); ArrayList<Observation> friends = new ArrayList<Observation>(); synchronized(map){ friends = map.getFriends(getPosition()); } Point p = avgPosition(friends); if (isEnemy(o)){ //If its right in front, attack() !!! if (o.position.equals(getMovePosition())){ attack(); turn90Random(); } else { //Move towards the enemy this turn, //if something jumps in front, attack it! if(!moveForward()){ attack(); turn90Random(); } } } else if (!isFriend(o) && o.position.equals(getMovePosition())){ //obstacle turn90Random(); }else if(isFriend(o)){ // see a friend in front, go to them if (d > 1){ if(!moveForward(d)){ attack(); turn90Random(); } } else { //Next to friends, look for enemies turn180(); } } else if (p!= null && distance(p) > 4){ //This Chimera is far from its buddies //turn towards the average position of its //buddies, go forward attacking anything // that gets in its way turn(p); o = look(); if (o.position.equals(getMovePosition())){ if (isEnemy(o)){ attack(); } turn90Random(); } else { moveForward(); } } else { wander(d); } } private void wander(int d) { //1/10 of the time it turns //Rest of the time it moves a random //Number of times forward //to the obstacle Random r = new Random(); int i = r.nextInt(10); if(i == 0){ turn90Random(); } else { int spaces = 1; if ( (d-2) > spaces){ spaces = d-2; } if(!moveForward( r.nextInt(spaces) + 1 )){ attack(); turn90Random(); } } } private Point avgPosition(ArrayList<Observation> things) { int xSum = 0; int ySum = 0; if (things == null || things.size() == 0){ return null; } for (Observation o: things) { xSum+=o.position.x; ySum+= o.position.y; } return new Point(xSum/things.size(), ySum/things.size()); } private Observation closestObservation(ArrayList<Observation> things) { Observation out = null; int testDistance = Integer.MAX_VALUE; for (Observation o : things){ int d = distance (o.position); if ( d < testDistance){ out = o; testDistance = d; } } return out; } private boolean isFriend(Observation o){ if (o.type == Type.CREATURE && o.className.equals(getClass().getName())){ return true; } else { return false; } } public void turn180(){ turnRight(); turnRight(); } //Does nothing if this is facing d already public void turn(Direction d){ Direction current = getDirection(); if(current.opposite() == d){ turn180(); } else if (current.left() == d){ turnLeft(); } else if( current.right() == d){ turnRight(); } } public void turn90Random(){ Random r = new Random(); int x = r.nextInt(2);// 0 or 1 if(x == 0){ turnLeft(); } else { turnRight(); } } /* * Returns false if it didn't move n spaces * True if it was unimpeded. * If it was impeded, it stops trying to move * forward. */ public boolean moveForward(int n){ boolean moved = false; for (int i = 0; i < n; i++) { moved = moveForward(); if (!moved){ break; } } return moved; } /* * Figures out which axis[ (N-S) or (E-W) ] has the greatest * distance to be traveled on to get closer to p, and \ * determines which way to point towards p. */ public Direction directionTo(Point p){ Point here = getPosition(); int x2 = p.y; int y2 = p.x; int x1 = here.y; int y1 = here.x; int dx = x2-x1; int dy = x2-x1; if (Math.abs(dx) > Math.abs(dy)){ if (dx > 0){ return Direction.EAST; } else { return Direction.WEST; } } else { if (dy > 0){ return Direction.SOUTH; } else { return Direction.NORTH; } } } //Turn to the direction that will //get SmartCreature to p. public void turn(Point p) { turn(directionTo(p)); } public int getPopulation(){ return numAlive.intValue(); } public void update(){ // System.out.println("Num Alive: " + numAlive.intValue()); Point here = getPosition(); Point last = null; if (!lastVisited.isEmpty()){ last = lastVisited.peek(); } if (!here.equals(last)){ //Add the current position to the Stack of last visited points lastVisited.push(here); //This insures that the multiple Runnable SmartCreatures on the map //do not try to update their understanding of the map at the same time. //This prevents problems with writing to the same spot at the same time. synchronized(map){ //Set the spot that the creature moved out of to be empty if (last!= null){ map.set(new Observation(last, Type.EMPTY, getTime())); } //Set the spot the creature is currently on to an Observation of itself //System.out.println("Width: " + map.map.size() + " Height: " + map.map.get(0).size()); map.set(here, new Observation(here, map.myName, getId(), getDirection(), getTime())); } } //If the creature didn't move, there is no new information, no update needed. // System.out.println(this); } /* * These moving methods are overridden to * update the understanding of the map after * each move; */ public boolean moveForward(){ boolean b = super.moveForward(); update(); return b; } public void turnLeft(){ super.turnLeft(); update(); } public void turnRight(){ super.turnRight(); update(); } /* * Returns the global understanding of the map * Static var: map * (Should be the same for all instances) */ public String toString(){ synchronized(map){ return map.toString(); } } public synchronized Observation recall(Point p){ Observation o = map.get(p); return o; } protected synchronized Observation look() { //Although no other instance of SmartCreature can look //while another is looking, one SmartCreature could be //looking while another is updating, potentially doing //operations on the map at the same time, leaving the threat //of interruption. Therefore, within this method, map operations //must be synchronized. Observation o = super.look(); synchronized(map){ map.set(o); } //The spaces between the SmartCreature instance and the position //of the observed object are all empty. And these "observations" //occurred at the time time as o int space = 1; while (!(this.getMovePosition(space).equals(o.position))){ Point spotOn = this.getMovePosition(space); synchronized(map){ map.set(spotOn, new Observation(spotOn, Type.EMPTY, o.time)); } space++; } return o; } //General Helper Class for a 2D Map protected static class Array2D<T>{ ArrayList<ArrayList<T>> map; public Array2D(Dimension d){ map = new ArrayList<ArrayList<T>>(); for (int i = 0; i < d.height; i ++) { ArrayList<T> row = new ArrayList<T>(); for (int j = 0; j < d.width; j++){ row.add(null); } map.add(row); } } public T get(Point p){ return get(p.x, p.y); } public T get(int x, int y){ return (map.get(y)).get(x); } public void set(int x, int y, T v){ (map.get(y)).set(x, v); } public void set (Point p, T v){ set(p.x, p.y, v); } public int getWidth(){ return (map.get(0)).size(); } public int getHeight(){ return map.size(); } public boolean inBounds(Point p){ return inBounds(p.x, p.y); } public boolean inBounds(int x, int y) { return (x < getWidth() && x >= 0) && (y < getHeight() && y >=0); } public String toString(){ String out = ""; for (ArrayList<T> row : map){ for (T item: row){ out+= toString(item); } out+= "\n"; } return out; } protected String toString (T t) { return t.toString(); } } //Helper Class: Extension of Array2D, for Observations protected static class ObservationArray2D extends Array2D<Observation>{ private String myName; public ObservationArray2D(Dimension d, String myClassName){ super(d); myName = myClassName; } private ArrayList<Observation> getFriends(Point here) { ArrayList<Observation> out = new ArrayList<Observation>(); for (ArrayList<Observation> row : map){ for (Observation obv : row){ if (obv != null && obv.type == Type.CREATURE && obv.className.equals(myName) && !obv.position.equals(here)){ out.add(obv); } } } return out; } public void set(Observation obs){ Point p = obs.position; set(p, obs); } private ArrayList<Observation> getApplesAndEnemies(){ //Iterate Through the map to get all wanted things ArrayList<Observation> out = new ArrayList<Observation>(); for (ArrayList<Observation> row : map){ for (Observation obv : row){ if (isAppleOrEnemy(obv)){ out.add(obv); } } } return out; } private boolean isAppleOrEnemy(Observation o){ if (o == null){ return false; } if (o.type == Type.CREATURE){ String ident = o.className; if (ident.equals(myName)){ return false; } else if (ident.equals( "Apple")){ return true; } else if (ident.equals("Flytrap")){ return false; } else { //Must be an enemy return true; } } return false; } protected String toString(Observation obs) { if (obs == null){ return "?"; } if (obs.type == Type.EMPTY){ return " " ; } else if (obs.type == Type.WALL){ return "X"; } else if (obs.type == Type.CREATURE){ String ident = obs.className; if (ident.equals( myName)){ return "m"; } else if (ident.equals( "Apple")){ return "a"; } else if (ident.equals("Flytrap")){ return "f"; } else { return "c"; } } else { return "?"; } } } }