// CSE 142 Homework 8 (Critters) // Authors: Stuart Reges and Marty Stepp // // Class CritterModel keeps track of the state of the critter simulation. import java.util.*; import java.awt.*; import java.lang.reflect.*; public class CritterModel { private int height; private int width; private Critter[][] grid; private Map info; private SortedMapcritterCount; private boolean debugView; private int simulationCount; public CritterModel(int width, int height) { this.width = width; this.height = height; grid = new Critter[width][height]; info = new HashMap(); critterCount = new TreeMap(); this.debugView = false; } public Iterator iterator() { return info.keySet().iterator(); } public Point getPoint(Critter c) { return info.get(c).p; } public Color getColor(Critter c) { return info.get(c).color; } public String getString(Critter c) { return info.get(c).string; } public void add(int number, Class critter) { Random r = new Random(); Critter.Direction[] directions = Critter.Direction.values(); if (info.size() + number > width * height) throw new RuntimeException("adding too many critters"); for (int i = 0; i < number; i++) { Critter next; try { next = makeCritter(critter); } catch (Exception e) { throw new RuntimeException("" + e); } int x, y; do { x = r.nextInt(width); y = r.nextInt(height); } while (grid[x][y] != null); grid[x][y] = next; Critter.Direction d = directions[r.nextInt(directions.length)]; info.put(next, new PrivateData(new Point(x, y), d, 0, next.getColor(), next.toString())); } String name = critter.getName(); if (!critterCount.containsKey(name)) critterCount.put(name, number); else critterCount.put(name, critterCount.get(name) + number); } @SuppressWarnings("unchecked") private Critter makeCritter(Class critter) throws Exception { Constructor c = critter.getConstructors()[0]; if (critter.toString().equals("class Bear")) { // flip a coin boolean b = Math.random() < 0.5; return (Critter) c.newInstance(new Object[] {b}); } else { return (Critter) c.newInstance(); } } public int getWidth() { return width; } public int getHeight() { return height; } public String getAppearance(Critter c) { // Override specified toString if debug flag is true if (!debugView) return info.get(c).string; else { PrivateData data = info.get(c); if (data.direction == Critter.Direction.NORTH) return "^"; else if (data.direction == Critter.Direction.SOUTH) return "v"; else if (data.direction == Critter.Direction.EAST) return ">"; else return "<"; } } public void toggleDebug() { this.debugView = !this.debugView; } private boolean inBounds(int x, int y) { return (x >= 0 && x < width && y >= 0 && y < height); } private boolean inBounds(Point p) { return inBounds(p.x, p.y); } // returns the result of rotating the given direction clockwise private Critter.Direction rotate(Critter.Direction d) { if (d == Critter.Direction.NORTH) return Critter.Direction.EAST; else if (d == Critter.Direction.SOUTH) return Critter.Direction.WEST; else if (d == Critter.Direction.EAST) return Critter.Direction.SOUTH; else return Critter.Direction.NORTH; } private Point pointAt(Point p, Critter.Direction d) { if (d == Critter.Direction.NORTH) return new Point(p.x, p.y - 1); else if (d == Critter.Direction.SOUTH) return new Point(p.x, p.y + 1); else if (d == Critter.Direction.EAST) return new Point(p.x + 1, p.y); else return new Point(p.x - 1, p.y); } private Info getInfo(PrivateData data, Class original) { Critter.Neighbor[] neighbors = new Critter.Neighbor[4]; Critter.Direction d = data.direction; for (int i = 0; i < 4; i++) { neighbors[i] = getStatus(pointAt(data.p, d), original); d = rotate(d); } return new Info(neighbors, data.direction, data.infectCount); } private Critter.Neighbor getStatus(Point p, Class original) { if (!inBounds(p)) return Critter.Neighbor.WALL; else if (grid[p.x][p.y] == null) return Critter.Neighbor.EMPTY; else if (grid[p.x][p.y].getClass() == original) return Critter.Neighbor.SAME; else return Critter.Neighbor.OTHER; } @SuppressWarnings("unchecked") public void update() { simulationCount++; Object[] list = info.keySet().toArray(); Collections.shuffle(Arrays.asList(list)); Arrays.sort(list, new Comparator() { public int compare(Object x, Object y) { return Math.min(10, info.get(x).infectCount) - Math.min(10, info.get(y).infectCount); } }); for (int i = 0; i < list.length; i++) { Critter next = (Critter)list[i]; PrivateData data = info.get(next); if (data == null) { // happens when creature was infected earlier in this round continue; } Point p = data.p; Point p2 = pointAt(p, data.direction); Critter.Action move = next.getMove(getInfo(data, next.getClass())); data.color = next.getColor(); data.string = next.toString(); if (move == Critter.Action.LEFT) data.direction = rotate(rotate(rotate(data.direction))); else if (move == Critter.Action.RIGHT) data.direction = rotate(data.direction); else if (move == Critter.Action.HOP) { if (inBounds(p2) && grid[p2.x][p2.y] == null) { grid[p2.x][p2.y] = grid[p.x][p.y]; grid[p.x][p.y] = null; data.p = p2; } } else if (move == Critter.Action.INFECT) { if (inBounds(p2) && grid[p2.x][p2.y] != null && grid[p2.x][p2.y].getClass() != next.getClass()) { Critter other = grid[p2.x][p2.y]; // remember the old critter's private data PrivateData oldData = info.get(other); // then remove that old critter String c1 = other.getClass().getName(); critterCount.put(c1, critterCount.get(c1) - 1); String c2 = next.getClass().getName(); critterCount.put(c2, critterCount.get(c2) + 1); info.remove(other); // and add a new one to the grid try { grid[p2.x][p2.y] = makeCritter(next.getClass()); } catch (Exception e) { throw new RuntimeException("" + e); } // and add to the map info.put(grid[p2.x][p2.y], oldData); // and remember that we infected a critter data.infectCount++; } } } } public Set> getCounts() { return Collections.unmodifiableSet(critterCount.entrySet()); } public int getSimulationCount() { return simulationCount; } private class PrivateData { public Point p; public Critter.Direction direction; public int infectCount; public Color color; public String string; public PrivateData(Point p, Critter.Direction d, int infectCount, Color color, String string) { this.p = p; this.direction = d; this.infectCount = infectCount; this.color = color; this.string = string; } public String toString() { return p + " " + direction + " " + infectCount; } } // an object used to query a critter's state (neighbors, direction) private static class Info implements CritterInfo { private Critter.Neighbor[] neighbors; private Critter.Direction direction; private int infectCount; public Info(Critter.Neighbor[] neighbors, Critter.Direction d, int infectCount) { this.neighbors = neighbors; this.direction = d; this.infectCount = infectCount; } public Critter.Neighbor getFront() { return neighbors[0]; } public Critter.Neighbor getBack() { return neighbors[2]; } public Critter.Neighbor getLeft() { return neighbors[3]; } public Critter.Neighbor getRight() { return neighbors[1]; } public Critter.Direction getDirection() { return direction; } public int getInfectCount() { return infectCount; } } }