// CSE 142, Autumn 2006 // Homework 8 (Critter Safari) // Authors: Marty Stepp, Stuart Reges, Steve Gribble // // Represents the model of all critters in the simulation. // import java.awt.Color; import java.awt.Point; import java.lang.reflect.*; import java.util.*; public class CritterModel extends Observable implements Iterable { // class constants private static final int LION_MAX = 10; // fields private int height; private int width; private Critter[][] grid; private String[][] display; private Color[][] colorDisplay; private Random rand; private List critterList; private Map locationMap; private SortedMap countMap; // Constructs a new model of the given size. public CritterModel(int width, int height) { // check for invalid model size if (width <= 0 || height <= 0) { throw new IllegalArgumentException(); } this.width = width; this.height = height; this.grid = new Critter[width][height]; this.display = new String[width][height]; this.colorDisplay = new Color[width][height]; updateDisplay(); rand = new Random(); // initialize various data structures critterList = new ArrayList(); locationMap = new HashMap(); countMap = new TreeMap(); } // Adds the given number of critters of the given type to the simulation. public void add(int number, Class critterClass) { try { // call private helper add method many times for (int i = 0; i < number; i++) { add(critterClass); } } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } updateDisplay(); } // Returns the color that should be displayed on the given (x, y) location, // or black if nothing is there. public Color getColor(int x, int y) { return colorDisplay[x][y]; } // Returns a set of [class name, count] entry pairs in this model. public Set> getCounts() { return Collections.unmodifiableSet(countMap.entrySet()); } // Returns the height of this model. public int getHeight() { return height; } // Returns the String of text to display at the given (x, y) location. public String getString(int x, int y) { return display[x][y]; } // Returns the total number of critters in this model. public int getTotalCount() { return locationMap.keySet().size(); } // Returns the width of this model. public int getWidth() { return width; } // Returns an iterator of the critters in this model. public Iterator iterator() { return critterList.iterator(); } // Moves the position of all critters and handles collisions. public synchronized void update() { // reorder the list to be fair about move/collision order Collections.shuffle(critterList); // move each critter to its new position for (int i = 0; i < critterList.size(); i++) { Critter critter1 = critterList.get(i); // move the critter Point location = locationMap.get(critter1); grid[location.x][location.y] = null; int move = critter1.getMove(new CritterInfoImpl(new Point(location))); movePoint(location, move); Critter critter2 = grid[location.x][location.y]; Critter winner = critter1; if (critter2 != null) { // square is already occupied; play rock-paper-scissors winner = playGame(critter1, critter2); Critter loser = (winner == critter1) ? critter2 : critter1; int indexToRemove = (winner == critter1) ? critterList.indexOf(critter2) : i; critterList.remove(indexToRemove); if (indexToRemove <= i) { i--; // so we won't skip a critter by mistake } // decrement class counter for the losing critter String className = loser.getClass().getName(); countMap.put(className, countMap.get(className) - 1); } grid[location.x][location.y] = winner; } updateDisplay(); } // Adds a single instance of the given type to this model. // If the critter's constructor needs any parameters, gives random values. private void add(Class critterClass) throws IllegalAccessException, InvocationTargetException, InstantiationException { if (getTotalCount() >= width * height) { throw new RuntimeException("adding too many critters"); } // create critter Constructor ctor = getConstructor(critterClass); Object[] params = createRandomParameters(ctor); Critter critter = ctor.newInstance(params); critterList.add(critter); // place critter on board Point location = randomOpenLocation(); locationMap.put(critter, location); grid[location.x][location.y] = critter; // count # of critters String className = critterClass.getName(); if (!countMap.containsKey(className)) { countMap.put(className, 1); } else { countMap.put(className, countMap.get(className) + 1); } } // Fills and returns an array of random values of the proper types // for the given constructor. private Object[] createRandomParameters(Constructor ctor) { Class[] paramTypes = ctor.getParameterTypes(); Object[] params = new Object[paramTypes.length]; // build random parameters for (int j = 0; j < params.length; j++) { if (paramTypes[j] == Integer.TYPE) { params[j] = new Integer(rand.nextInt(LION_MAX) + 1); } else if (paramTypes[j] == Color.class) { params[j] = randomColor(); } else { throw new RuntimeException("unknown constructor parameter type: " + paramTypes[j]); } } return params; } // Gets and returns the constructor for the given class by reflection. private Constructor getConstructor(Class critterClass) { Constructor[] ctors = (Constructor[]) critterClass.getConstructors(); if (ctors.length != 1) { throw new RuntimeException("wrong number of constructors for " + critterClass); } return ctors[0]; } // Called to log various events in the game. /* private void log(String text) { if (logEnabled) { System.out.print(text); } } */ // Translates a point's coordinates 1 unit in a particular direction. private Point movePoint(Point p, int direction) { if (direction == Critter.NORTH) { p.y = (p.y + height - 1) % height; } else if (direction == Critter.SOUTH) { p.y = (p.y + 1) % height; } else if (direction == Critter.EAST) { p.x = (p.x + 1) % width; } else if (direction == Critter.WEST) { p.x = (p.x + width - 1) % width; } else if (direction != Critter.CENTER) { throw new RuntimeException("Illegal direction"); } return p; } // Plays rock-paper-scissors between the given two critters. // Returns which critter won the game. The other must die! private Critter playGame(Critter critter1, Critter critter2) { int weapon1 = critter1.fight(critter2.toString()); verifyWeapon(weapon1); int weapon2 = critter2.fight(critter1.toString()); verifyWeapon(weapon2); if (weapon1 == Critter.ROCK && weapon2 == Critter.SCISSORS || weapon1 == Critter.SCISSORS && weapon2 == Critter.PAPER || weapon1 == Critter.PAPER && weapon2 == Critter.ROCK) { // playor 1 wins return critter1; } else if (weapon1 == weapon2) { // tie return Math.random() < 0.5 ? critter1 : critter2; } else { // player 2 wins return critter2; } // log("[" + money1 + ", " + money2 + "] "); // log("\n"); } // Returns a random color. private Color randomColor() { // return new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256)); double r = Math.random(); if (r < 0.333) { return Color.YELLOW; } else if (r < 0.667) { return Color.GREEN; } else { return Color.BLUE; } } // Returns a random point that is unoccupied by any critters. private Point randomOpenLocation() { Point p = new Point(); do { p.x = rand.nextInt(width); p.y = rand.nextInt(height); } while (grid[p.x][p.y] != null); return p; } // Updates the internal string array representing the text to display. private void updateDisplay() { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (grid[i][j] == null) { display[i][j] = "."; colorDisplay[i][j] = Color.BLACK; } else { display[i][j] = grid[i][j].toString(); colorDisplay[i][j] = grid[i][j].getColor(); } } } setChanged(); notifyObservers(); } // Throws an exception if the given weapon is not ROCK, PAPER, or SCISSORS. private void verifyWeapon(int weapon) { if (weapon != Critter.ROCK && weapon != Critter.PAPER && weapon != Critter.SCISSORS) { throw new IllegalArgumentException("Invalid weapon for fight:\n" + "must be one of ROCK, PAPER, or SCISSORS: " + weapon); } } // Inner class to represent the state of a given critter. // Really the critters should store this stuff, but we don't trust // the author of the critter classes; they could cheat! public class CritterState { public Point location; public int money; public boolean alive; // Constructs critter state at the given location. public CritterState(Point location) { this.location = location; alive = true; } // Returns whether this critter is currently alive (may move, etc). public boolean isAlive() { return alive; } // Sets this critter to be living or dead. public void setAlive(boolean alive) { this.alive = alive; } } // Used to query a critter's state (position, neighbors, etc). // See CritterInfo.java for documentation of methods. private class CritterInfoImpl implements CritterInfo { private Point location; public CritterInfoImpl(Point location) { this.location = location; } public int getHeight() { return height; } public String getNeighbor(int direction) { Point other = new Point(location); movePoint(other, direction); return display[other.x][other.y]; } public int getWidth() { return width; } public int getX() { return location.x; } public int getY() { return location.y; } // For debugging; dumps the state of this object as text. public String toString() { return "Info{location=(" + location.x + ", " + location.y + "),width=" + width + ",height=" + height + ",neighbors=[N=" + getNeighbor(Critter.NORTH) + ",S=" + getNeighbor(Critter.SOUTH) + ",W=" + getNeighbor(Critter.WEST) + ",E=" + getNeighbor(Critter.EAST) + ",C=" + getNeighbor(Critter.CENTER) + "]}"; } } }