-
Notifications
You must be signed in to change notification settings - Fork 0
/
AI.java
170 lines (152 loc) · 6.04 KB
/
AI.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
/* Skeleton code copyright (C) 2008, 2022 Paul N. Hilfinger and the
* Regents of the University of California. Do not distribute this or any
* derivative work without permission. */
package ataxx;
import java.util.ArrayList;
import java.util.Random;
import static ataxx.PieceColor.*;
import static java.lang.Math.min;
import static java.lang.Math.max;
/** A Player that computes its own moves.
* @author Rae Xin
*/
class AI extends Player {
/** Maximum minimax search depth before going to static evaluation. */
private static final int MAX_DEPTH = 4;
/** A position magnitude indicating a win (for red if positive, blue
* if negative). */
private static final int WINNING_VALUE = Integer.MAX_VALUE - 20;
/** A magnitude greater than a normal value. */
private static final int INFTY = Integer.MAX_VALUE;
/** A new AI for GAME that will play MYCOLOR. SEED is used to initialize
* a random-number generator for use in move computations. Identical
* seeds produce identical behaviour. */
AI(Game game, PieceColor myColor, long seed) {
super(game, myColor);
_random = new Random(seed);
}
@Override
boolean isAuto() {
return true;
}
@Override
String getMove() {
if (!getBoard().canMove(myColor())) {
game().reportMove(Move.pass(), myColor());
return "-";
}
Main.startTiming();
Move move = findMove();
Main.endTiming();
game().reportMove(move, myColor());
return move.toString();
}
/** Return a move for me from the current position, assuming there
* is a move. */
private Move findMove() {
Board b = new Board(getBoard());
_lastFoundMove = null;
if (myColor() == RED) {
minMax(b, MAX_DEPTH, true, 1, -INFTY, INFTY);
} else {
minMax(b, MAX_DEPTH, true, -1, -INFTY, INFTY);
}
return _lastFoundMove;
}
/** The move found by the last call to the findMove method
* above. */
private Move _lastFoundMove;
/** Find a move from position BOARD and return its value, recording
* the move found in _foundMove iff SAVEMOVE. The move
* should have maximal value or have value > BETA if SENSE==1,
* and minimal value or value < ALPHA if SENSE==-1. Searches up to
* DEPTH levels. Searching at level 0 simply returns a static estimate
* of the board value and does not set _foundMove. If the game is over
* on BOARD, does not set _foundMove. */
private int minMax(Board board, int depth, boolean saveMove, int sense,
int alpha, int beta) {
if (depth == 0 || board.getWinner() != null) {
return staticScore(board, WINNING_VALUE + depth);
}
Move best = null;
int bestScore;
if (sense == 1) {
bestScore = -INFTY;
ArrayList<Move> allMyMoves = findLegalMoves(board, RED);
for (Move currentMove : allMyMoves) {
Board newBoardCopy = new Board(board);
newBoardCopy.makeMove(currentMove);
int currentScore = minMax(newBoardCopy, depth - 1,
false, -1, alpha, beta);
if (currentScore > bestScore || currentMove.isPass()) {
best = currentMove;
bestScore = currentScore;
alpha = max(alpha, bestScore);
if (alpha >= beta) {
break;
}
}
}
} else {
bestScore = INFTY;
ArrayList<Move> allMyMoves = findLegalMoves(board, BLUE);
for (Move currentMove : allMyMoves) {
Board newBoardCopy = new Board(board);
newBoardCopy.makeMove(currentMove);
int currentScore = minMax(newBoardCopy, depth - 1,
false, 1, alpha, beta);
if (currentScore < bestScore || currentMove.isPass()) {
best = currentMove;
bestScore = currentScore;
beta = min(beta, bestScore);
if (alpha >= beta) {
break;
}
}
}
}
if (saveMove) {
_lastFoundMove = best;
}
return bestScore;
}
/** Helper method that finds all legal moves of a COLOR on a BOARD. Return
* ArrayList of Moves that are legal.
* **/
private ArrayList<Move> findLegalMoves(Board board, PieceColor color) {
ArrayList<Move> returnArray = new ArrayList<>();
for (char c = 'a'; c <= 'g'; c++) {
for (char r = '1'; r <= '7'; r++) {
if (board.get(c, r).compareTo(color) == 0) {
for (char c2 = 'a'; c2 <= 'g'; c2++) {
for (char r2 = '1'; r2 <= '7'; r2++) {
Move currentMove = Move.move(c, r, c2, r2);
if (board.legalMove(currentMove)) {
returnArray.add(currentMove);
}
}
}
}
}
}
if (returnArray.isEmpty()) {
returnArray.add(Move.pass());
}
return returnArray;
}
/** Return a heuristic value for BOARD. This value is +- WINNINGVALUE in
* won positions, and 0 for ties. */
private int staticScore(Board board, int winningValue) {
PieceColor winner = board.getWinner();
if (winner != null) {
return switch (winner) {
case RED -> winningValue;
case BLUE -> -winningValue;
default -> 0;
};
}
return board.redPieces() - board.bluePieces();
}
/** Pseudo-random number generator for move computation. */
private Random _random = new Random();
}