-
Notifications
You must be signed in to change notification settings - Fork 0
/
CritterModel.java
337 lines (293 loc) · 12.1 KB
/
CritterModel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// Class CritterModel keeps track of the state of the critter simulation.
import java.util.*;
import java.awt.Point;
import java.awt.Color;
import java.lang.reflect.*;
public class CritterModel {
// the following constant indicates how often infect should fail for
// critters who didn't hop on their previous move (0.0 means no advantage,
// 1.0 means 100% advantage)
public static final double HOP_ADVANTAGE = 0.2; // 20% advantage
private int height;
private int width;
private Critter[][] grid;
private Map<Critter, PrivateData> info;
private SortedMap<String, Integer>critterCount;
private boolean debugView;
private int simulationCount;
private static boolean created;
public CritterModel(int width, int height) {
// this prevents someone from trying to create their own copy of
// the GUI components
if (created)
throw new RuntimeException("Only one world allowed");
created = true;
this.width = width;
this.height = height;
grid = new Critter[width][height];
info = new HashMap<Critter, PrivateData>();
critterCount = new TreeMap<String, Integer>();
this.debugView = false;
}
public Iterator<Critter> 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<? extends Critter> 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 (IllegalArgumentException e) {
System.out.println("ERROR: " + critter + " does not have" +
" the appropriate constructor.");
System.exit(1);
return;
} catch (Exception e) {
System.out.println("ERROR: " + critter + " threw an " +
" exception in its constructor.");
System.exit(1);
return;
}
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));
}
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;
boolean[] neighborThreats = new boolean[4];
for (int i = 0; i < 4; i++) {
neighbors[i] = getStatus(pointAt(data.p, d), original);
if (neighbors[i] == Critter.Neighbor.OTHER) {
Point p = pointAt(data.p, d);
PrivateData oldData = info.get(grid[p.x][p.y]);
neighborThreats[i] = d == rotate(rotate(oldData.direction));
}
d = rotate(d);
}
return new Info(neighbors, data.direction, neighborThreats);
}
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));
// This keeps track of critters that are locked and cannot be
// infected this turn. The happens when:
// * a Critter is infected
// * a Critter hops
Set<Critter> locked = new HashSet<Critter>();
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;
}
// clear any prior setting for having hopped in the past
boolean hadHopped = data.justHopped;
data.justHopped = false;
Point p = data.p;
Point p2 = pointAt(p, data.direction);
// try to perform the critter's action
Critter.Action move = next.getMove(getInfo(data, next.getClass()));
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;
locked.add(next); //successful hop locks a critter from
// being infected for the rest of the
// turn
data.justHopped = true; // remember a successful hop
}
} else if (move == Critter.Action.INFECT) {
if (inBounds(p2) && grid[p2.x][p2.y] != null
&& grid[p2.x][p2.y].getClass() != next.getClass()
&& !locked.contains(grid[p2.x][p2.y])
&& (hadHopped || Math.random() >= HOP_ADVANTAGE)) {
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());
// This critter has been infected and is now locked
// for the rest of this turn
locked.add(grid[p2.x][p2.y]);
} catch (Exception e) {
throw new RuntimeException("" + e);
}
// and add to the map
info.put(grid[p2.x][p2.y], oldData);
// but it's new, so it didn't just hop
oldData.justHopped = false;
}
}
}
updateColorString();
}
// calling this method causes each critter to update the stored color and
// text for toString; should be called each time update is performed and
// once before the simulation begins
public void updateColorString() {
for (Critter next : info.keySet()) {
info.get(next).color = next.getColor();
info.get(next).string = next.toString();
}
}
public Set<Map.Entry<String, Integer>> getCounts() {
return Collections.unmodifiableSet(critterCount.entrySet());
}
public int getSimulationCount() {
return simulationCount;
}
private class PrivateData {
public Point p;
public Critter.Direction direction;
public Color color;
public String string;
public boolean justHopped;
public PrivateData(Point p, Critter.Direction d) {
this.p = p;
this.direction = d;
}
public String toString() {
return p + " " + direction;
}
}
// 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 boolean[] neighborThreats;
public Info(Critter.Neighbor[] neighbors, Critter.Direction d,
boolean[] neighborThreats) {
this.neighbors = neighbors;
this.direction = d;
this.neighborThreats = neighborThreats;
}
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 boolean frontThreat() {
return neighborThreats[0];
}
public boolean backThreat() {
return neighborThreats[2];
}
public boolean leftThreat() {
return neighborThreats[3];
}
public boolean rightThreat() {
return neighborThreats[1];
}
}
}