-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathboardNode.cpp
280 lines (254 loc) · 9.05 KB
/
boardNode.cpp
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
#include "boardNode.hpp"
int nodeCount = 0;
int maxNodeCount = 0;
time_t startTime;
/**
* Constructs a child node
* @param parentBoard Board from parent node
* @param m Move made to get to this node from parent
*/
BoardNode::BoardNode(Board* parentBoard, Move m) : lastMove(BLACK) {
board = parentBoard->copy();
board->doMove(m);
lastMove = m;
sideToMove = !m.getSide();
children = vector<BoardNode*>();
nodeCount += 1;
if(nodeCount > maxNodeCount){
maxNodeCount = nodeCount;
}
}
/**
* Constructs a base node
* @param board Current board for the game
* @param ourSide The side that our player is on
*/
BoardNode::BoardNode(Board* board, bool ourSide) :
BoardNode(board, NULL_MOVE(!ourSide)) {
nodeCount = 0;
maxNodeCount = 0;
startTime = time(nullptr);
}
/**
* Deconstructs node
*/
BoardNode::~BoardNode(){
if (board) delete board;
for(int i = 0; i < (int)children.size(); i++){
delete children[i];
}
children.clear();
nodeCount -= 1;
}
/**
* Gets the move that was used to get from the previous board to this one
* @return Move that was used to get from previous board to this one
*/
Move BoardNode::getMove(){
return lastMove;
}
/**
* Gets the children of a node
* @return Vector of node's children
*/
vector<BoardNode*> BoardNode::getChildren(){
return children;
}
/**
* Searches a tree using negamax and A/B pruning to find the heuristic score
* for this board
* @param depth How deep to search the node tree
* @param alpha The highest overall score found so far
* @param beta The opponent's best overall score found so far
* @param heuristic Heuristic function that defines the score of a board
* @return Score of the board accounting for future possible moves
*/
float BoardNode::searchTreeAB(int depth, float alpha, float beta,
Heuristic* heuristic){
if(depth == 0){
return heuristic->getScore(board, sideToMove);
}
vector<Move> possibleMoves = board->possibleMoves(sideToMove);
for(int i = 0; i < (int)possibleMoves.size(); i++){
children.push_back(new BoardNode(board, possibleMoves[i]));
float score = -children[i]->searchTreeAB(depth - 1, -beta, -alpha, heuristic);
alpha = max(alpha, score);
if(alpha >= beta) break;
}
for(int i = 0; i < (int)children.size(); i++){
delete children[i];
}
children.clear();
possibleMoves.clear();
return alpha;
}
/**
* Searches a tree using negamax and PVS pruning to find the heuristic score
* for this board
* @param depth How deep to search the node tree
* @param alpha The highest overall score found so far
* @param beta The opponent's best overall score found so far
* @param heuristic Heuristic function that defines the score of a board
* @return Score of the board accounting for future possible moves
*/
float BoardNode::searchTreePVS(int depth, float alpha, float beta,
Heuristic* heuristic, TransTableEntry* tTable){
if(depth == 0){
return heuristic->getScore(board, sideToMove);
}
vector<Move> possibleMoves = board->possibleMoves(sideToMove);
if(depth > 4){
possibleMoves = sortMoves(possibleMoves, heuristic, 1);
}
if (tTable) {
TransTableEntry entry = tTable[board->getHash() % NUM_T_TABLE_ENTRIES];
if (entry.hash == board->getHash() && entry.move.getSide() == sideToMove) {
//if (depth == 9) cerr << "match" << endl;
for(int i = 0; i < (int)possibleMoves.size(); i++){
if (possibleMoves[i] == entry.move) {
//if (depth == 9) cerr << "move found at index " << i << " with depth " << (int)entry.depth << endl;
possibleMoves.erase(possibleMoves.begin() + i);
possibleMoves.insert(possibleMoves.begin(), entry.move);
break;
}
}
}
else {
//if (depth == 9) cerr << entry.hash << " " << (int)entry.depth << endl;
}
}
Move bestMove = possibleMoves[0];
for(int i = 0; i < (int)possibleMoves.size(); i++){
children.push_back(new BoardNode(board, possibleMoves[i]));
float score;
if(i == 0){
score = -children[i]->searchTreePVS(depth - 1, -beta, -alpha, heuristic, tTable);
}
else{
score = -children[i]->searchTreePVS(depth - 1, -alpha-PVS_WINDOW, -alpha, heuristic, tTable);
if(alpha < score && score < beta){
score = -children[i]->searchTreePVS(depth - 1, -beta, -alpha, heuristic, tTable);
}
}
if (score > alpha) {
alpha = score;
bestMove = possibleMoves[i];
}
if(alpha >= beta) break;
}
if (tTable) {
int index = board->getHash() % NUM_T_TABLE_ENTRIES;
if (depth >= tTable[index].depth-2) {
tTable[index].hash = board->getHash();
tTable[index].depth = depth;
tTable[index].move = bestMove;
}
}
for(int i = 0; i < (int)children.size(); i++){
delete children[i];
}
children.clear();
possibleMoves.clear();
return alpha;
}
/**
* Searches a tree using a modified minimax algorithm to completely map out
* winning moveset
* @param heuristic Heuristic to describe boardstate. Should give 1 for win and
* -1 for loss.
* @param ourSide Side of the board to scan for winning moveset for
* @return Worst case score for this node
*/
float BoardNode::searchTreeEndGame(Heuristic* heuristic, bool ourSide){
if(maxNodeCount >= 2500000 || difftime(time(nullptr), startTime) > 60){
return -1;
}
if(board->isDone()){
return heuristic->getScore(board, ourSide);
}
vector<Move> possibleMoves = board->possibleMoves(sideToMove);
if(sideToMove == ourSide){
for(int i = 0; i < (int)possibleMoves.size(); i++){
BoardNode *testNode = new BoardNode(board, possibleMoves[i]);
float score = testNode->searchTreeEndGame(heuristic, ourSide);
if(score > 0){
children.push_back(testNode);
return 1;
}
else{
delete testNode;
}
}
return -1;
}
else{
for(int i = 0; i < (int)possibleMoves.size(); i++){
BoardNode *testNode = new BoardNode(board, possibleMoves[i]);
float score = testNode->searchTreeEndGame(heuristic, ourSide);
children.push_back(testNode);
if(score <= 0){
for(int j = 0; j <= i; j++){
delete children[j];
}
children.clear();
return -1;
}
}
return 1;
}
}
/**
* Finds the best move to make this round using minimax and A/B pruning
* @param depth Depth to search in the node tree
* @param heuristic Heuristic function that defines the score for each position
* @param ourSide The side that you are playing on. Passed into heuristic function.
* @return The most optimal move based on the heuristic function
*/
Move BoardNode::getBestChoice(int depth, Heuristic* heuristic,
TransTableEntry* tTable){
vector<Move> possibleMoves = board->possibleMoves(sideToMove);
if(possibleMoves.size() == 1){
return possibleMoves[0];
}
for(int i = 0; i < (int)possibleMoves.size(); i++){
children.push_back(new BoardNode(board, possibleMoves[i]));
}
float alpha = -numeric_limits<float>::max();
float beta = numeric_limits<float>::max();
Move ret = NULL_MOVE(sideToMove);
for(int i = 0; i < (int)children.size(); i++){
float score = -children[i]->searchTreePVS(depth - 1, -beta, -alpha, heuristic, tTable);
if(score > alpha){
alpha = score;
ret = children[i]->getMove();
}
}
for(int i = 0; i < (int)children.size(); i++){
delete children[i];
}
children.clear();
return ret;
}
/**
* Sorts a vector of moves by predicted heuristic value through a short negamax scan
* @param moves Vector of moves to sort
* @param heuristic Heuristic function to use while sorting
* @param depth How deep the search will go
* @return The sorted vector of moves
*/
vector<Move> BoardNode::sortMoves(vector<Move> moves, Heuristic* heuristic,
int depth){
vector<pair<float, int>> indexScores;
float alpha = -numeric_limits<float>::max();
float beta = numeric_limits<float>::max();
for(int i = 0; i < (int)moves.size(); i++){
BoardNode node = BoardNode(board, moves[i]);
indexScores.push_back(make_pair(node.searchTreeAB(depth, alpha, beta, heuristic), i));
}
std::sort(indexScores.begin(), indexScores.end());
vector<Move> sorted;
for(int i = 0; i < (int)indexScores.size(); i++){
sorted.push_back(moves[indexScores[i].second]);
}
return sorted;
}