-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAI.java
More file actions
357 lines (288 loc) · 19.1 KB
/
Copy pathAI.java
File metadata and controls
357 lines (288 loc) · 19.1 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
import java.util.ArrayList;
import java.util.Arrays;
public class AI {
private AI(){} //Private constructor
public static AI instance; //Singleton instance
public static AI getInstance(){
if(instance == null){
instance = new AI(); //Instantiates singleton instance only ever once
}
return instance;
}
//Object references
Logic_Board logic_board;
Pawn pawn;
Rook rook;
Bishop bishop;
Knight knight;
Queen queen;
King king;
//Private local variables (used throughout class)
private String[][] validMoves = new String[1000][2];
private int size = 0;
private String pieceToMove = "";
private String coordToMove = "";
private boolean staleMate = false;
private String difficulty ="Medium";
private int depthSet = 3;
//Instantiates object references
public void init(){
logic_board = Logic_Board.getInstance();
pawn = new Pawn();
rook = new Rook();
bishop = new Bishop();
knight = new Knight();
queen = new Queen();
king = new King();
}
//Used for the AI to perform a move
public void AIMakeMove(boolean isWhitePlaying){
//Checks the difficulty being played at and sets the depth at which the optimised minimax tree search will be carried out, depthSet, accordingly, with a higher difficulty resulting in a higher depth search to make the AI play at a harder level and play more optimal moves
if(difficulty == "Easy") {
depthSet = 2; //Depth at which search will be carried out
}
else if(difficulty == "Medium") {
depthSet = 3;
}
else if(difficulty == "Hard") {
depthSet = 4;
}
//Calls minimising function first as AI plays black, which is the minimiser
alphaBetaMin(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, depthSet);
//Upon completion of the alpha-beta optimised minimax tree traversal, the variables pieceToMove and coordToMove would have been updated with the piece to move and the coordinates of the square to move it to, which are then inputted as parameters into the makeMove() method of the Logic_Board class to perform the move
logic_board.makeMove(pieceToMove, coordToMove, isWhitePlaying);
}
//Maximising alpha-beta optimised minimax function
private double alphaBetaMax(double alpha, double beta, int depth){
double score = 0;
if(depth == 0){ //At leaf node layer
return logic_board.evaluateBoard(); //Evaluates leaf node
}
//Aggregates all possible moves maximising side can do in current board state held in the node the algorithm is currently at to produce child nodes that are part of the next layer in the tree
getAllPossibleMoves(true);
String[][] copyValidMoves = new String[1000][2];
String[][] boardLogicCopy = new String[8][8];
//Copies board state held in current node to a temporary array
for(int i = 0; i<8; i++){
boardLogicCopy[i] = Arrays.copyOf(logic_board.logicBoard[i], 8);
}
//Copies all valid moves aggregated from local validMoves array to a temporary array, and copies size of this instance of the validMoves array
int sizeCopy = size;
for(int i = 0; i<sizeCopy; i++){
copyValidMoves[i][0] = validMoves[i][0];
copyValidMoves[i][1] = validMoves[i][1];
}
//Iterates through temporary array containing copy of validMoves
for(int i=0; i<sizeCopy; i++){
//Extracts piece that can be moved and one of the corresponding moves that can be made from array
String piece = copyValidMoves[i][1];
String coord = copyValidMoves[i][0];
//Makes the move using tryMove() in the logicBoard array, which creates an arc to a child node in the next lower layer
logic_board.tryMove(piece, coord);
//Obtains value (score) of child node that corresponds with the arc (the move) that the current iteration is on, where the value of the child node itself is obtained through a call to alphaBetaMax, and so on using recursion where the values are established through back-tracking the values of the evaluated leaf nodes
score = alphaBetaMin(alpha, beta, depth-1);
//System.out.println("Maximising back-tracked node value: " + score);
//If score (value of the current child node being assessed) is greater than or equal to the lowest value that the minimiser is currently able to obtain at that point in the tree or above (beta)
if(score>=beta){
//Reverts move made in logicBoard in preparation to make next move and the corresponding next arc leading to the next child node
for(int j = 0; j<8; j++){
logic_board.logicBoard[j] = Arrays.copyOf(boardLogicCopy[j], 8);
}
if(depth == depthSet) {
pieceToMove = piece;
coordToMove = coord;
}
//As there is a minimising node above this node which wants the lowest evaluation value possible and has a value of beta less than or equal to the value of the child node just checked, beta is immediately returned as the minimising node above this node will NEVER choose this node, as it already has a node that will give it a lower value due to its beta value being lower, as beta represents the lowest value the minimiser can get at that point in the tree and above. Furthermore, it is aware this node's value will be equal to the score generated or higher as it is a maximising node. Hence this node, and ALL BRANCHES ATTACHED to it, are pruned off.
//HOWEVER, if node is a root node then obviously there is no minimising layer above it, thus this if statement is never called as beta is set at +infinity initially which is greater than everything and the beta value associated with this node does not change as only its alpha value changes from backtracking, and there are no nodes above it to feed it a new beta value
return beta;
}
//If score (value of the current child node being assessed) is greater than the highest value that the maximiser is currently able to obtain at that point in the tree or above
if(score>alpha){
//Updates alpha value to this score to correctly represent highest value obtainable
alpha = score;
System.out.println("New alpha value: " + alpha);
//If at root node, and alpha is updated indicating highest score obtained so far, copies piece and coordinates piece is moved to to obtain this evaluation value, as this piece and coordinates combination may be the optimal move
if(depth == depthSet) {
pieceToMove = piece;
coordToMove = coord;
}
}
//Reverts move made in logicBoard in preparation to test next move (held in the next arc leading to next child node)
for(int j = 0; j<8; j++){
logic_board.logicBoard[j] = Arrays.copyOf(boardLogicCopy[j], 8);
}
}
//Returns highest value obtainable at that point in tree or above as maximising node (if beta was not returned due to pruning)
return alpha;
}
//Minimising alpha-beta optimised minimax function
private double alphaBetaMin(double alpha, double beta, int depth){
double score = 0;
if(depth == 0){ //At leaf node layer
return logic_board.evaluateBoard(); //Evaluates leaf node
}
//Aggregates all possible moves minimising side can do in current board state held in the node the algorithm is currently at to produce child nodes that are part of the next layer in the tree
getAllPossibleMoves(false);
String[][] copyValidMoves = new String[1000][2];
String[][] boardLogicCopy = new String[8][8];
//Copies board state held in current node to a temporary array
for(int i = 0; i<8; i++){
boardLogicCopy[i] = Arrays.copyOf(logic_board.logicBoard[i], 8);
}
//Copies all valid moves aggregated from local validMoves array to a temporary array, and copies size of this instance of the validMoves array
int sizeCopy = size;
for(int i = 0; i<sizeCopy; i++){
copyValidMoves[i][0] = validMoves[i][0];
copyValidMoves[i][1] = validMoves[i][1];
}
//Iterates through temporary array containing copy of validMoves
for(int i=0; i<sizeCopy; i++){
//Extracts piece that can be moved and one of the corresponding moves that can be made from array
String piece = copyValidMoves[i][1];
String coord = copyValidMoves[i][0];
//Makes the move using tryMove() in the logicBoard array, which creates an arc to a child node in the next lower layer
logic_board.tryMove(piece, coord);
//Obtains value (score) of child node that corresponds with the arc (the move) that the current iteration is on, where the value of the child node itself is obtained through a call to alphaBetaMax, and so on using recursion where the values are established through back-tracking the values of the evaluated leaf nodes
score = alphaBetaMax(alpha, beta, depth-1);
//System.out.println("Minimising back-tracked node value: " + score);
//If score (value of the current child node being assessed) is less than or equal to the highest value that the maximiser is currently able to obtain at that point in the tree or above (alpha)
if(score<=alpha){
//Reverts move made in logicBoard in preparation to make next move and the corresponding next arc leading to the next child node
for(int j = 0; j<8; j++){
logic_board.logicBoard[j] = Arrays.copyOf(boardLogicCopy[j], 8);
}
if(depth == depthSet) {
pieceToMove = piece;
coordToMove = coord;
}
//As there is a maximising node above this node which wants the highest evaluation value possible and has a value of alpha greater than or equal to the value of the child node just checked, alpha is immediately returned as the maximising node above this node will NEVER choose this node, as it already has a node that will give it a higher value or the same due to its alpha value being higher, as alpha represents the highest value the maximiser can get at that point in the tree or above. Furthermore, it is aware this node's value will be equal to the score generated or lower as it is a minimising node. Hence ALL BRANCHES ATTACHED to the node are pruned off.
//HOWEVER, if node is a root node then obviously there is no maximising layer above it, thus this if statement is never called as alpha is set at -infinity initially which is lower than everything and the alpha value associated with this node does not change as only its beta value changes from backtracking, and there are no nodes above it to feed it a new alpha value
return alpha;
}
//If score (value of the current child node being assessed) is lower than the lowest value that the minimiser is currently able to obtain at that point in the tree or above
if(score<beta){
//Updates beta value to this score to correctly represent lowest value obtainable
beta = score;
System.out.println("New beta value: " + beta);
//If at root node, and beta is updated indicating lowest score obtained so far, copies piece and coordinates the piece is moved to to obtain this evaluation value, as this piece and coordinates combination may be the optimal move
if(depth == depthSet) {
pieceToMove = piece;
coordToMove = coord;
}
}
//Reverts move made in logicBoard in preparation to test next move (held in the next arc leading to next child node)
for(int j = 0; j<8; j++){
logic_board.logicBoard[j] = Arrays.copyOf(boardLogicCopy[j], 8);
}
}
//Returns highest value obtainable at that point in tree or above as maximising node (if beta was not returned due to pruning)
return beta;
}
//Aggregates all possible valid moves a certain side can perform in the current board state
public void getAllPossibleMoves(boolean isWhitePlaying)
{
//Clears validMoves two-dimensional array, which stores each move by having one column represent the piece being moved, and the other column representing the coordinates that can be moved to
for(int i = 0; i<1000; i++){
validMoves[i][0] = null;
validMoves[i][1] = null;
}
staleMate = true; //Stalemate boolean property
ArrayList<String> possibleMoves = new ArrayList<String>();
String coord = "";
String piece = "";
int counter;
size = 0;
//Iterates through logicBoard array
for(int r = 0; r<8; r++){
for(int c = 0; c<8; c++){
if(logic_board.logicBoard[r][c]!="") { //If square is not empty
//Checks what type the piece in square and if it is on the same side that is playing, then obtains all possible moves that piece can do in the current board state by using getPossibleMoves() function of appropriate piece type class and calling filterMoves() on List returned
if ((logic_board.logicBoard[r][c].charAt(0) == 'p' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'P' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1); //Concatenates coordinates of current square to input into getPossibleMoves()
piece = logic_board.logicBoard[r][c]; //Gets specific piece string value
possibleMoves = logic_board.filterMoves(piece, pawn.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
if ((logic_board.logicBoard[r][c].charAt(0) == 'r' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'R' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1);
piece = logic_board.logicBoard[r][c];
possibleMoves = logic_board.filterMoves(piece, rook.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
if ((logic_board.logicBoard[r][c].charAt(0) == 'n' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'N' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1);
piece = logic_board.logicBoard[r][c];
possibleMoves = logic_board.filterMoves(piece, knight.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
if ((logic_board.logicBoard[r][c].charAt(0) == 'b' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'B' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1);
piece = logic_board.logicBoard[r][c];
possibleMoves = logic_board.filterMoves(piece, bishop.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
if ((logic_board.logicBoard[r][c].charAt(0) == 'q' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'Q' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1);
piece = logic_board.logicBoard[r][c];
possibleMoves = logic_board.filterMoves(piece, queen.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
if ((logic_board.logicBoard[r][c].charAt(0) == 'k' && !isWhitePlaying) || (logic_board.logicBoard[r][c].charAt(0) == 'K' && isWhitePlaying)) {
coord = Integer.toString(c) + Integer.toString(r + 1);
piece = logic_board.logicBoard[r][c];
possibleMoves = logic_board.filterMoves(piece, king.getPossibleMoves(coord, isWhitePlaying), isWhitePlaying);
}
counter = 0;
if(possibleMoves.size()!=0) {
staleMate = false; //If there are any possible moves that can be played by the playing side whatsoever, stalemate has not occurred
//Appends possible moves generated by current piece in current square to validMoves array
for (int i = size; i < possibleMoves.size() + size; i++) { //Starts at index equal to size of validMoves array so far so that moves are appended and do not override already added moves
//Adds coordinates of square piece can move to in GridPane format to first column of array
validMoves[i][0] = logic_board.convertCoord(possibleMoves.get(counter), true);
//Adds piece that is moving to second column of array
validMoves[i][1] = piece;
counter++;
}
size += possibleMoves.size(); //Incremented such that size always points to the next available index in array so that moves are only appended
}
}
possibleMoves.clear();
}
}
}
//Used in tandem with recommendMove() method
public stack calculateMove(boolean isWhitePlaying){
stack recommendedMove = new stack(); //Stores recommended move in stack
depthSet = 2; //Depth at which minimax tree will be searched at to get recommended move
//Calls maximising or minimising function dependent on whether white (maximiser) or black (minimiser) is playing
if(isWhitePlaying){
alphaBetaMax(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, depthSet);
}
else{
alphaBetaMin(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, depthSet);
}
//Pushes the piece to move for recommended move onto stack
recommendedMove.push(pieceToMove);
//Extracts row and column values from coordToMove generated from minimax
String row = coordToMove.substring(0,1);
String col = coordToMove.substring(1);
//Pushes row and column values onto stack
recommendedMove.push(row);
recommendedMove.push(col);
//Returns stack
return recommendedMove;
}
//Getters and setters
public boolean getStalemateValue(){
return staleMate;
}
public String getDifficulty(){
return difficulty;
}
public void setDifficulty(String difficultyInput){
difficulty = difficultyInput;
}
public boolean anyValidMoves(){
if(validMoves[0][0] == null){
return false;
}
else{
return true;
}
}
}