-
Notifications
You must be signed in to change notification settings - Fork 0
/
client_minimax_player.cpp
299 lines (262 loc) · 8.44 KB
/
client_minimax_player.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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
* client_player.cpp
*
* Created on: Nov 18, 2017
* Author: derekhancock
*/
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sstream>
#include <stddef.h>
#include <vector>
#include "client_player.cpp"
#include "node.cpp"
#include "stopwatch.hpp"
using namespace std;
class ClientMiniMaxPlayer : public ClientPlayer {
public:
Node* root;
int depth_limit;
unsigned int total_nodes_expanded;
std::string message_prefix;
double time_left;
bool hit_time_limit;
bool run_omp;
int times_hit_time_limit;
double total_time_spent;
/**
* host - The host to connect to
* port - the port to connect to
* in_me - the player number that this player is
* depth_limit - the max depth that the minimax algorithm should explore to
* run_omp - specifies whether this agent uses openmp parallelization or not
*/
ClientMiniMaxPlayer(string host, int port, int in_me, int depth_limit=5, bool run_omp=false) : ClientPlayer(host, port, in_me) {
this->depth_limit = depth_limit;
this->root = NULL;
total_nodes_expanded = 0;
stringstream ss;
if (!run_omp)
ss << "[baseline minimax " << me << "] ";
else
ss << "[openmp minimax " << me << "] ";
message_prefix = ss.str();
time_left = -1;
hit_time_limit = false;
this->run_omp = run_omp;
times_hit_time_limit = 0;
total_time_spent = 0;
}
virtual ~ClientMiniMaxPlayer() { }
/**
* Makes a random move. Only used at the beginning of the game
*/
int randomMove() {
cout << message_prefix << "Returning random move\n";
int validMoves[64];
int numValidMoves = get_valid_moves(state, me, validMoves);
cout << message_prefix << "numValidMoves: ";
cout << numValidMoves << "\n";
int myMove;
if (numValidMoves > 0) {
int randomIndex = rand() % numValidMoves;
//int randomIndex = 17 % numValidMoves; //to make repatable. DELETE ME LATER
myMove = validMoves[randomIndex];
}
else {
myMove = -1;
}
return myMove;
}
/**
* Make a move by expanding the tree, using minimax, and finding the best child node
*/
int move() {
cout << message_prefix << "Entering move function\n";
if (round < 4) {
//can only choose middle 4 squares, so just choose one randomly
return randomMove();
}
else {
//make the tree
std::cout << message_prefix << "Creating minimax tree\n";
root = new Node(true, me, state, NULL, -1);
int validMoves[64];
int numValidMoves = get_valid_moves(state, me, validMoves);
std::cout << "\n";
std::cout << message_prefix << " Expanding root\n";
unsigned int old_total = total_nodes_expanded;
time_left = (me == 1)? t1 : t2;
stopwatch<std::milli, double> sw;
sw.start();
expandNode(root, 1,sw);
sw.stop();
total_time_spent += sw.count();
//print useful information on the expansion
std::cout << message_prefix << " Nodes expanded on this move: " << total_nodes_expanded - old_total << "\n";
std::cout << message_prefix << " Total nodes expanded so far: " << total_nodes_expanded << "\n";
std::cout << message_prefix << " Time spent on this move: " << sw.count() << "\n";
if (hit_time_limit) {
std::cout << message_prefix << " Hit time limit on expansion\n";
times_hit_time_limit++;
}
std::cout << message_prefix << " Times hit time limit: " << times_hit_time_limit << "\n";
std::cout << message_prefix << " Total time spent so far: " << total_time_spent << "\n";
std::cout << message_prefix << " Current node/time spent ratio: " << total_nodes_expanded/total_time_spent << "\n";
hit_time_limit = false;
//get best child node and return it
std::cout << message_prefix << " Searching children for best node\n";
int bestValue = INT_MIN;
Node* bestNode = NULL;
int k = 0;
std::cout << "root children size: " << root->children.size() << "\n";
for (int i = 0; i < root->children.size(); i++) {
Node* child = root->children[i];
if (child->value > bestValue) {
bestValue = child->value;
bestNode = child;
k = i;
}
}
if (bestValue == INT_MIN) {
//handle edge cases at end
//delete tree
delete root;
return randomMove();
}
else {
int validMoves[64];
int numValidMoves = get_valid_moves(state, me, validMoves);
int move = validMoves[k];
std::cout << message_prefix << "Best Move: " << (move/8) << "," << (move%8) << " val: " << bestValue << "\n";
//delete tree
delete root;
return move;
}
}
}
/**
* Expand the current node and all of its children recursively until it reaches the max depth
*/
void expandNode(Node* cur_node, int current_depth, stopwatch<std::milli, double>& sw) {
total_nodes_expanded++;
//Flip next player
int next_player = -1;
if (cur_node->player == 1)
next_player = 2;
else
next_player = 1;
sw.stop();
if (sw.count() > 2000) { //give max 2 seconds per move so finish in time
hit_time_limit = true;
cur_node->value = cur_node->getHeuristicValue(me);
}
else if (current_depth < depth_limit) {
int validMoves[64];
int numValidMoves = get_valid_moves(cur_node->state, cur_node->player, validMoves);
//if you reach point where no valid moves, this node would be other player's turn
if (numValidMoves == 0) {
//cout << message_prefix << " No valid moves at this node \n";
//discourage not being able to move and giving other guy another turn
if (cur_node->player == me) //no move for me
cur_node->value = cur_node->calculateValue(me) - 120;
else //no move for him
cur_node->value = cur_node->calculateValue(me) + 120;
return;
}
int n = ((current_depth == 1 && run_omp) ? 6 : 1);
//cur_node->children.reserve(numValidMoves);
#pragma omp parallel num_threads(n)
{
#pragma omp for schedule(dynamic)
for (int i = 0; i < numValidMoves; i++) {
int move = validMoves[i];
int next_state[8][8];
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
next_state[i][j] = cur_node->state[i][j];
}
}
next_state[(move/8)][(move % 8)] = cur_node->player;
updateState(move,cur_node->player,next_state);
Node* next_node = new Node(!cur_node->isMax, next_player, next_state, cur_node, move);
#pragma omp critical
cur_node->children.push_back(next_node);
//cur_node->children[i] = next_node;
expandNode(next_node, current_depth + 1, sw);
}
}
cur_node->calculateValue(me);
}
else {
cur_node->calculateValue(me);
}
}
/**
* Given a move, a state, and the player making the move, update the board with the necessary
* flips as specified in the Othello rules
*/
void updateState(int move,int player,int in_state[8][8]) {
int opponent = -1;
if (player == 1)
opponent = 2;
else {
opponent = 1;
}
int directions[8][2] = {{1,0},{0,1},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}};
for (int i = 0; i < 8; i++) {
int rdir = directions[i][0];
int cdir = directions[i][1];
int starting_row = (move / 8);
int starting_col = (move % 8);
int row = starting_row + rdir;
int col = starting_col + cdir;
if (row >= 0 && col >= 0 && row < 8 && col < 8 && in_state[row][col] == opponent) {
int sequence[8][2];
int seq_len = 0;
while (row >= 0 && col >= 0 && row < 8 && col < 8 && in_state[row][col] == opponent) {
sequence[seq_len][0] = row;
sequence[seq_len][1] = col;
seq_len++;
if (seq_len >= 8) {
cout << message_prefix << "WARNING: Seq len >= 8\n";
cout << message_prefix << "instate = opponent at " << row << "," << col << "\n";
}
row += rdir;
col += cdir;
}
if (row >= 0 && col >= 0 && row < 8 && col < 8 && in_state[row][col] == player) {
for (int j = 0; j < seq_len; j++) {
int r = sequence[j][0];
int c = sequence[j][1];
if (r >= 0 && c >= 0 && r < 8 && c < 8) {
in_state[r][c] = player;
}
}
}
}
}
}
/**
* Simple debugging function to show a few levels of the minimax tree
*/
void print_nodes() {
for (int i = 0; i < root->children.size(); i++) {
std::cout << " node: " << root->children[i]->value << "\n";
for (int j = 0; j < root->children[i]->children.size(); j++) {
std::cout << " node: " << root->children[i]->children[j]->value << "\n";
for (int k = 0; k < root->children[i]->children[j]->children.size(); k++) {
std::cout << " node: " << root->children[i]->children[j]->children[k]->value << "\n";
}
}
}
}
};