-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchState.java
More file actions
375 lines (320 loc) · 12.6 KB
/
SearchState.java
File metadata and controls
375 lines (320 loc) · 12.6 KB
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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
import java.util.LinkedList;
import java.util.ListIterator;
/* The SearchState class represents a state of the game that is found whilst searching through the statespace.
* In addition to the game state, it keeps track of heuristics and costs, possible actions, previous actions.
* It also has a search mode, that controls some of its behaviour, e.g. what actions it sees as possible */
public class SearchState extends State implements Comparable<SearchState> {
private LinkedList<Tile> targets;
private LinkedList<SearchState> prevStates;
private char prevAction;
private int cost;
private int heuristic = Integer.MAX_VALUE;
private SearchMode mode = SearchMode.SAFE;
/* Helper constructor, sets the parameters that are shared between Agent and SearchState objects */
private SearchState(State state) {
this.map = state.map;
this.posX = state.posX;
this.posY = state.posY;
this.dynamites = state.dynamites;
this.hasDynamite = state.hasDynamite;
this.hasAxe = state.hasAxe;
this.hasKey = state.hasKey;
this.hasRaft = state.hasRaft;
this.hasTreasure = state.hasTreasure;
this.direction = state.direction;
this.doorsOpened = shallowCopyLL(state.doorsOpened);
this.treesChopped = shallowCopyLL(state.treesChopped);
this.tilesBlownUp = shallowCopyLL(state.tilesBlownUp);
this.knownTrees = deepCopyLL(state.knownTrees);
this.knownItems = deepCopyLL(state.knownItems);
this.knownTreasures = deepCopyLL(state.knownTreasures);
}
/* Constructor for creating the initial SearchState from the current state of the agent */
SearchState(Agent agent, LinkedList<Tile> targets, SearchMode mode) {
this(agent); // Uses SearchState(State state)
this.targets = targets;
this.mode = mode;
prevStates = new LinkedList<>();
prevAction = Character.MIN_VALUE; // null
setCost(0);
setHeuristic();
}
/* Helper constructor for creating a new SearchState from an existing one */
private SearchState(SearchState state) {
this((State) state);
this.targets = state.targets;
this.mode = state.mode;
prevStates = shallowCopyLL(state.prevStates);
prevStates.addLast(state);
// Doesn't set cost, heuristic or prevAction
}
/* Constructor for creating a SearchState that expands a previous state by doing an action */
private SearchState(SearchState state, char action) {
this(state);
// If map is about to change, do a deep copy so as to not mess up for other states.
switch (action) {
case 'u':
case 'c':
case 'b':
this.map = deepCopyMap();
break;
case 'f':
Position nextPos = getNextPos();
if (getTile(nextPos.getX(), nextPos.getY()).getItem() != '0') {
this.map = deepCopyMap();
}
}
prevAction = action;
setCost(state.cost);
increaseCost(action);
updateState(action);
setHeuristic();
}
/* Expands this state by creating all the new states that can be reached from it */
LinkedList<SearchState> expandState() {
LinkedList<SearchState> newStates = new LinkedList<>();
Position nextPos = getNextPos();
Tile nextTile = getTile(nextPos.getX(), nextPos.getY());
newStates.add(new SearchState(this, 'r'));
newStates.add(new SearchState(this, 'l'));
// Can't plan a path into unexplored territory
if (nextTile != null) {
if (canMoveForward(nextTile)) {
newStates.add(new SearchState(this, 'f'));
}
if (canCutTree(nextTile)) {
newStates.add(new SearchState(this, 'c'));
}
if (canUnlock(nextTile)) {
newStates.add(new SearchState(this, 'u'));
}
if (canBlowUp(nextTile)) {
newStates.add(new SearchState(this, 'b'));
}
}
removeRepeatStates(newStates);
return newStates;
}
/* Goes through a List of SearchStates and removes all states that have a state S in their prevStates list
* that satisfies state.sameState(S) */
private static void removeRepeatStates(LinkedList<SearchState> states) {
SearchState state;
ListIterator<SearchState> it = states.listIterator();
while (it.hasNext()) {
state = it.next();
if (state.prevStates != null && state.prevStates.contains(state)) {
it.remove();
}
}
}
/* Find the actions necessary to reach this state from the start state */
LinkedList<Character> getPathHere() {
LinkedList<Character> path;
if (prevAction != Character.MIN_VALUE) {
path = prevStates.peekLast().getPathHere();
path.addLast(prevAction);
return path;
} else {
return new LinkedList<>();
}
}
/* Gets the path cost of moving to this state */
private int getCost() {
return cost;
}
/* Sets the path cost of this state */
private void setCost(int cost) {
this.cost = cost;
}
/* Sets the cost of going to this state, dependent on the action that was taken to reach it */
private void increaseCost(char action) {
Tile currentTile = getTileAtPos();
Tile nextTile = getTile(getNextPos().getX(), getNextPos().getY());
switch (action) {
// Cost of turning
case 'r':
case 'l':
this.cost++;
break;
// Cost of moving forward
case 'f':
// Cost of moving from raft to land
if (currentTile.getType() == '~' && nextTile.getType() == ' ' && hasRaft) {
this.cost += 5;
}
// Cost of moving from land to raft. Cheaper if there are many trees on the map
else if (currentTile.getType() == ' ' && nextTile.getType() == '~' && hasRaft) {
if (knownTrees.size() > 0) {
this.cost += Math.ceil(5 / knownTrees.size());
} else {
this.cost += 5;
}
}
// Cost of moving forward in the same environment
else {
this.cost++;
}
break;
// Cost of chopping down tree.
case 'c':
// Discourage chopping trees if agent already has a raft, and there are few trees
if (hasRaft) {
if (knownTrees.size() > 0) {
this.cost += Math.max(12 / knownTrees.size(), 1);
} else {
this.cost += 12;
}
}
// If agent doesn't have a raft there is no downside to chopping a tree, so make it cheap
else {
this.cost++;
}
break;
// Cost of unlocking a door, always cheap as there is no downside
case 'u':
this.cost++;
break;
// Cost of using dynamite. Discourage blowing up tiles that can be removed using other tools
case 'b':
// Discourage blowing up tiles that can be removed in other ways
switch (nextTile.getType()) {
case '*':
this.cost += 15;
break;
case '-':
case 't':
this.cost += 20;
}
// Discourage blowing up tiles from water, as this can lead to not being able to get off an island
if (currentTile.getType() == '~') {
this.cost += 5;
}
break;
}
}
/* Get the heuristic value of this state, that is the approximate cost of reaching the target */
private int getHeuristic() {
return heuristic;
}
/* Calculate the heuristic for this state. Uses the Manhattan distance to the closes target.
* If there are no targets, set the heuristic to zero. This makes uniform cost search possible with A* algorithm */
private void setHeuristic() {
int newHeuristic;
if (targets == null || targets.isEmpty()) {
this.heuristic = 0;
return;
}
for (Tile target : targets) {
newHeuristic = Math.abs(target.getX() - posX) + Math.abs(target.getY() - posY);
if (newHeuristic < this.heuristic) {
this.heuristic = newHeuristic;
}
}
}
/* Get the estimate total cost of reaching the goal from the start state */
int getFCost() {
return getCost() + getHeuristic();
}
/* Check if the agent can move forward from this state */
private boolean canMoveForward(Tile nextTile) {
if (nextTile == null) {
return false;
}
Tile currentTile = getTileAtPos();
switch (mode) {
case SAFE:
// Do not go from land to water, or from water to land. To avoid wasting raft
return currentTile.getType() == nextTile.getType();
case MODERATE:
case FREE:
// Can always move forward to land, but can only go into water if agent has a raft
switch (nextTile.getType()) {
case ' ':
return true;
case '~':
return hasRaft;
default:
return false;
}
default:
return true;
}
}
/* Check if agent can perform the chop tree action from this state */
private boolean canCutTree(Tile nextTile) {
if (nextTile == null) {
return false;
}
switch (mode) {
case SAFE:
// Force agent to not cut trees in safe search mode
return false;
case MODERATE:
case FREE:
return nextTile.getType() == 't' && hasAxe;
default:
return false;
}
}
/* Check if agent can perform the unlock door action from this state */
private boolean canUnlock(Tile nextTile) {
return nextTile != null && nextTile.getType() == '-' && hasKey;
}
/* Check if agent can perform the blow up tile action from this state */
private boolean canBlowUp(Tile nextTile) {
if (nextTile == null) {
return false;
}
switch (mode) {
case SAFE:
case MODERATE:
// Never use dynamite when in safe or moderate search mode
return false;
case FREE:
// Can use dynamite if the tile in front of the agent can be blown up, and it has dynamite in inventory
switch (nextTile.getType()) {
case '*':
case 't':
case '-':
return hasDynamite;
default:
return false;
}
default:
return false;
}
}
/* Helper method that does a shallow copy of a linked list */
private <T> LinkedList<T> shallowCopyLL(LinkedList<T> list) {
LinkedList<T> newList = new LinkedList<>();
for (T item : list) {
newList.addLast(item);
}
return newList;
}
/* Helper method that does a deep copy of a linked list of tiles */
private LinkedList<Tile> deepCopyLL(LinkedList<Tile> list) {
LinkedList<Tile> tiles = new LinkedList<>();
Tile newTile;
for (Tile tile : list) {
newTile = new Tile(tile);
tiles.add(newTile);
}
return tiles;
}
/* Overload the compareTo method. Needed for sorting in priority queue.
* Sorts by fCost, and uses the heuristic as a tie breaker*/
public int compareTo(SearchState state) {
int comparison;
comparison = Integer.compare(this.getFCost(), state.getFCost());
if (comparison == 0) {
comparison = Integer.compare(this.getHeuristic(), state.getHeuristic());
}
return comparison;
}
/* Override the equals method. Needed for contains() method for e.g. linked lists */
@Override
public boolean equals(Object o) {
return (o instanceof SearchState) && sameState((SearchState) o);
}
}