-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTrapBot.java
359 lines (343 loc) · 13.4 KB
/
TrapBot.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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
package bot;
import java.util.*;
/**
* This bot is obsessed with traps.
* If this bot can win, it tries to.
* If the opponent would win, it tries to prevent it.
* If it can set up a trap, it attempts to.
* If the opponent can set up a trap, it attempts to block it.
* In all other cases, it chooses the column with the best chances of
* winning the game.
*
* @author RyanPachauri
* @version 5/12/16
*/
public class TrapBot extends BotStarter {
public TrapBot(int rows, int columns) {
super(rows, columns);
}
@Override
public int makeTurn() {
int oppID = 3 - this.myID;
int winColumn = this.scrapeToWin(this.myID);//attempts to win immediately
if (winColumn != 7) {
return winColumn;
}
winColumn = this.scrapeToWin(oppID);
if (winColumn != 7) {
return winColumn;
}
this.scrapeAgainstAlmostWins(oppID);
this.scrapeThreats(oppID);
this.scrapeMiddleTraps(this.myID);
this.scrapeMiddleTraps(oppID);
this.scrapeStackTraps(this.myID);
this.scrapeStackTraps(oppID);
return bestAvailableLocation();
}
/**
* Looking at the id, finds an available location(s) that could win the game
* for the player with that id
* i.e. playing in this column wins the game for the player
*
*/
private int scrapeToWin(int id) {
for (Integer column : this.availableMoves.keySet()) {
if (super.positionToWin(this.availableMoves.get(column), column, id)) {
return column;
}
}
return 7;
}
/**
* A player would not want to play in a location that would lead to:
* the other player winning OR
* the other player preventing the player's win
* We want to scrape for locations that would avoid this
* If there are none, then it does not scrape any locations.
* If all available locations are in this situation, then it does not
* scrape any locations
*/
private void scrapeAgainstAlmostWins(int id) {
Set<Integer> almostWinningColumns = new HashSet<Integer>();
for (Integer column : this.availableMoves.keySet()) {
int row = this.availableMoves.get(column);
if (!super.belowAPositionToWin(row, column, id)) {
almostWinningColumns.add(column);
}
}
this.scrapeLocations(almostWinningColumns);
}
/**
* Finds any middle traps that the player with the given id could set.
* A middle trap is where the player's tokens are in the middle of two
* options. The player has an option on either side to win.
*
* @param id the id of a player
* @return Set of columns where the player could set stack traps
*/
private void scrapeMiddleTraps(int id) {
Set<Integer> middleTrapColumns = new HashSet<Integer>();
for (Integer column : this.availableMoves.keySet()) {
//list of other availablelocations that are in possible wins for the
//available location we are considering
List<Integer> availableLocs =
this.getAvailableLocs(this.availableMoves.get(column), column, id);
//we can only set a middle trap if
//there are more than 1 other available locs
if (availableLocs.size() > 1) {
middleTrapColumns.add(column);
}
}
this.scrapeLocations(middleTrapColumns);
}
private List<Integer> getAvailableLocs(int row, int column, int id) {
List<Integer> availableLocs = new ArrayList<Integer>();
Set<Integer[][]> possibleWins =
super.getPossibleWins(row, column, id);
for (Integer[][] line : possibleWins) {
//finds the available location in the line
Integer[] freeLoc = super.findAvailableLocation(line, row, column);
//checks that there is indeed an available location in the line
//(other than the possible available location given to us)
if (freeLoc != null) {
availableLocs.add(freeLoc[0]);
}
}
return availableLocs;
}
/**
* Finds any stack traps that the player with the given id could set.
* A stack trap is where the player could play in one column and win OR
* the opponent plays in that column and the player stacks another
* token on top to win anyway.
* i.e. The opponent has one of two options:
* 1. Prevent the player's win in the bottom row
* -> This would allow the player to win in the "stacked" row
* 2. Allow the player's win in the bottom row
*/
private void scrapeStackTraps(int id) {
List<Integer>[] freeLocations = super.getFreeLocations();
for (int row = freeLocations.length - 1; row > 0; row--) {
Set<Integer> availableColumns = new HashSet<Integer>();
for (Integer column : freeLocations[row]) {
if (freeLocations[row - 1].contains(column)) {
List<Integer> bRowLocs =
this.getAvailableLocs(row, column, id);
List<Integer> tRowLocs =
this.getAvailableLocs(row - 1, column, id);
if (super.positionToWin(row, column, id)) {
for (Integer col : tRowLocs) {
availableColumns.add(col);
}
} else if (super.positionToWin(row - 1, column, id)) {
for (Integer col : bRowLocs) {
availableColumns.add(col);
}
} else {
for (Integer bCol : bRowLocs) {
for (Integer tCol : tRowLocs) {
if (bCol == tCol) {
availableColumns.add(bCol);
}
}
}
}
}
this.scrapeLocations(availableColumns);
}
}
}
/**
* There are some points in the game when it is possible to create threats.
* A threat is a possible win of a free location
* (that is not an available location).
* We will not reach this point if there is an available location that
* has a possible win because we will have already returned it at
* that point.
* Threats are stored in a Map using the same format used for available
* columns because there can only be one type of threat per player per
* column.
*
*/
private void scrapeThreats(int oppID) {
Map<Integer, Integer> myOddThreats = this.getOddThreats(this.myID);
Map<Integer, Integer> myEvenThreats = this.getEvenThreats(this.myID);
Map<Integer, Integer> oppOddThreats = this.getOddThreats(oppID);
Map<Integer, Integer> oppEvenThreats = this.getEvenThreats(oppID);
boolean scrapeOkay = true;
//neither player can have no threats
if (myOddThreats.isEmpty() && myEvenThreats.isEmpty() &&
oppOddThreats.isEmpty() && oppEvenThreats.isEmpty()) {
if (this.myID == 1) {
this.scrapeToMakeOddThreat(myID);//TODO make this more efficient
this.scrapeToMakeEvenThreat(myID);
this.scrapeToMakeOddThreat(oppID);
this.scrapeToMakeEvenThreat(oppID);
} else {
this.scrapeToMakeOddThreat(oppID);
this.scrapeToMakeEvenThreat(oppID);
this.scrapeToMakeOddThreat(myID);//TODO make this more efficient
this.scrapeToMakeEvenThreat(myID);
}
scrapeOkay = false;
}//each player can have at most one type of threat
else if (!myOddThreats.isEmpty() && !myEvenThreats.isEmpty()) {
scrapeOkay = false;
} else if (!oppOddThreats.isEmpty() && !oppEvenThreats.isEmpty()) {
scrapeOkay = false;
}
if (scrapeOkay) {
if (oppOddThreats.isEmpty() && oppEvenThreats.isEmpty()) {
this.scrapeForOtherThreat(myOddThreats, myEvenThreats, myID);
} else if (myOddThreats.isEmpty() && myEvenThreats.isEmpty()) {
this.scrapeForOtherThreat(oppOddThreats, oppEvenThreats, oppID);
} else {
if (this.myID == 1) {
if (oppOddThreats.size() > 0) {
if (myEvenThreats.isEmpty()) {
this.scrapeToMakeEvenThreat(myID);
} else {
this.scrapeAgainstAlmostWins(myID);
}
} else if (oppEvenThreats.size() > 0) {
if (myOddThreats.isEmpty()) {
this.scrapeToMakeOddThreat(myID);
} else {
this.scrapeAgainstAlmostWins(myID);
}
}
} else if (this.myID == 2) {
if (oppOddThreats.size() > 0) {
if (myOddThreats.isEmpty()) {
this.scrapeToMakeOddThreat(myID);
} else {
this.scrapeAgainstAlmostWins(myID);
}
} else if (oppEvenThreats.size() > 0) {
if (myEvenThreats.isEmpty()) {
this.scrapeToMakeEvenThreat(myID);
} else {
//this is the one different case
this.scrapeAgainstAlmostWins(oppID);
}
}
}
}
}
}
private Map<Integer, Integer> getOddThreats(int id) {
List<Integer>[] freeLocs = super.getFreeLocations();
Map<Integer, Integer> oddThreats = new HashMap<Integer, Integer>();
for (int row = 1; row < freeLocs.length; row = row + 2) {
for (Integer column : freeLocs[row]) {
if (super.positionToWin(row, column, id)) {
oddThreats.put(column, row);
}
}
}
return oddThreats;
}
private Map<Integer, Integer> getEvenThreats(int id) {
List<Integer>[] freeLocs = super.getFreeLocations();
Map<Integer, Integer> evenThreats = new HashMap<Integer, Integer>();
for (int row = 0; row < freeLocs.length; row = row + 2) {
for (Integer column : freeLocs[row]) {
if (super.positionToWin(row, column, id)) {
evenThreats.put(column, row);
}
}
}
return evenThreats;
}
/**
* @Precondition: only one of oddThreats or evenThreats can be empty;
* if oddThreats & evenThreats are empty or neither are,
* throws an IllegalArgumentException
* @param oddThreats
* @param evenThreats
* @param id
*/
private void scrapeForOtherThreat(Map<Integer, Integer> oddThreats,
Map<Integer, Integer> evenThreats, int id) {
if ((oddThreats.isEmpty() && evenThreats.isEmpty()) ||
(!oddThreats.isEmpty() && !evenThreats.isEmpty())) {
throw new IllegalArgumentException();
}
if (oddThreats.isEmpty()) {
this.scrapeToMakeEvenThreat(id);
} else if (evenThreats.isEmpty()) {
this.scrapeToMakeOddThreat(id);
}
}
private void scrapeToMakeOddThreat(int id) {
List<Integer>[] freeLocs = super.getFreeLocations();
for (int row = freeLocs.length - 1; row >= 0; row = row - 2) {
Set<Integer> columns = new HashSet<Integer>();
for (Integer column : freeLocs[row]) {
List<Integer> availableLocs = this.getAvailableLocs(row, column, id);
for (Integer availableCol : availableLocs) {
columns.add(availableCol);
}
}
this.scrapeLocations(columns);
}
}
private void scrapeToMakeEvenThreat(int id) {
List<Integer>[] freeLocs = super.getFreeLocations();
for (int row = freeLocs.length - 2; row >= 0; row = row - 2) {
Set<Integer> columns = new HashSet<Integer>();
for (Integer column : freeLocs[row]) {
List<Integer> availableLocs = this.getAvailableLocs(row, column, id);
for (Integer availableCol : availableLocs) {
columns.add(availableCol);
}
}
this.scrapeLocations(columns);
}
}
/**
* Looks at the available locations and picks the one with the most possible
* wins for the given id
*
* @param id the id of the player we are considering
* @return the column of the best location
*/
private int bestAvailableLocation() {
int maxWins = -1;
int maxColumn = -1;
for (Integer column : this.availableMoves.keySet()) {
int wins = this.getPossibleWins(this.availableMoves.get(column),
column).size();
if (wins > maxWins) {
maxWins = wins;
maxColumn = column;
}
}
return maxColumn;
}
/**
* @Precondition: columns not null; otherwise,
* throws IllegalArgumentException
*
* @Postcondition: availableLocations field keyset only contains the given
* columns
* if size of columns is 0, does not change
* availableLocations
* @param columns the columns we want to keep
*/
private void scrapeLocations(Set<Integer> columns) {
if (columns == null) {
throw new IllegalArgumentException();
}
if (columns.size() > 0) {
for (Iterator<Integer> i = this.availableMoves.keySet().iterator();
i.hasNext();) {
Integer column = i.next();
if (!columns.contains(column)) {
i.remove();;
}
}
}
}
}