File: ai.cc

package info (click to toggle)
atom4 4.1-9
  • links: PTS
  • area: main
  • in suites: bookworm, bullseye, buster, sid, trixie
  • size: 836 kB
  • ctags: 1,222
  • sloc: cpp: 4,451; makefile: 46; perl: 6
file content (339 lines) | stat: -rw-r--r-- 8,849 bytes parent folder | download | duplicates (4)
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
/*
 * Atom-4 AI player
 * Implementation file
 *
 * $Id: ai.cc,v 1.23 2003/04/10 15:39:54 hsteoh Exp hsteoh $
 */


#include <sys/types.h>
#include <sys/wait.h>

#include <stdlib.h>
#include "ai.h"
#include "color4.h"
#include "exception.h"


/*
 * AI behaviour settings
 */

#define DEFAULT_SEARCH_DEPTH	2

// Heuristics for nodes at maxdepth=0
#define COLOR_CHANGE_WEIGHT	1.0
#define PCREATED_WEIGHT		1.7
#define PDESTROYED_WEIGHT	1.5
#define WINNING_WEIGHT		1000.0


#ifdef DEBUG_AI
void atom4ai::reopen_log() {
  fclose(logfile);			// reopen log file per round
  logfile=fopen("/tmp/atom4ai.log","w");
  if (!logfile) throw exception("atom4ai: failed to open logfile\n");
}
#endif //DEBUG_AI

#ifdef PROFILE_AI
#include <sys/gmon.h>
extern "C" {
  extern void _start(void), etext(void);
}
#endif //PROFILE_AI

void atom4ai::childmon::read_ready(eventloop *loop, int fd) {
  int x,y;
  int count;
  int status;				// child status

  if (read(fd, &x, sizeof(x))==-1)
    throw exception("Error while reading from AI subprocess");
  if (read(fd, &y, sizeof(y))==-1)
    throw exception("Error while reading from AI subprocess");

  // Remove handler from event loop
  loop->unregister_handler(eventloop::READER, fd);
  close(fd);
  waitpid(ai->child, &status, 0);	// wait for child to terminate
  ai->child = -1;

  if (!ai->move(ai->playernum, x,y)) {
    throw exception("@Unable to move to (%d,%d) as requested by AI subprocess",
                    x,y);
  }
}

void atom4ai::childmon::write_ready(eventloop *loop, int fd) {
  throw exception("atom4ai::childmon registered as a write handler??\n");
}

void atom4ai::find_legal_moves(board4 &board, elist<atom4ai::move_t> &moves) {
  move_t m;

  // Scan for all possible legal moves
  moves.clear();
  for (m.y=0; m.y<board.height(); m.y++) {
    for (m.x=0; m.x<board.width(); m.x++) {
      if (board.check_legal(m.x, m.y)) {
        moves.append(m);
      }
    } // endfor(x)
  } // endfor(y)
}

float atom4ai::score_opponent_turn(board4 &board, color4 nexttile, float min,
                                   float max, hdelta h, int maxdepth) {
  elist<move_t> moves;
  elistiter<move_t> it;

  // Find opponent's best response to our move
  find_legal_moves(board, moves);
  for (it=moves.headp(); it; it++) {

#ifdef DEBUG_AI
    fprintf(logfile, "  (%d) Considering: (%d,%d), min=%4.3f max=%4.3f <<\n",
            maxdepth, (*it).x, (*it).y, min, max);
#endif //DEBUG_AI

    float score = score_move(board, nexttile, *it, min, max, h, maxdepth);
    if (score > min) min = score;

#ifdef DEBUG_AI
    fprintf(logfile, "  >>(%d) move(%d,%d): %4.3f\n", maxdepth, (*it).x,
            (*it).y, score);
#endif //DEBUG_AI

    // Alpha prune
    if (score > max) {
#ifdef DEBUG_AI
      fprintf(logfile, "  [(%d) PRUNED: %4.3f > (%4.3f)]\n", maxdepth, score,
              max);
#endif //DEBUG_AI
      return 1e9;			// anything > max will do, actually.
    }
  }

  return min;
}

void atom4ai::update_heuristic(board4 &board, color4 our_prop,
                               elist<boardchange> &undo, hdelta &h) {
  elistiter<boardchange> it;
  color4 enemy_prop = our_prop.complement();

  for (it=undo.headp(); it; it++, h.changes++) {
    color4 newcell = board.getcell((*it).x, (*it).y);
    color4 oldcell = (*it).cell;

    if (newcell==our_prop) h.pcreated++;
    if (oldcell==enemy_prop) h.pdestroyed++;
  }
}

float atom4ai::calc_heuristic(hdelta h) {
  return COLOR_CHANGE_WEIGHT*h.changes +
         PCREATED_WEIGHT*h.pcreated +
         PDESTROYED_WEIGHT*h.pdestroyed;
}

float atom4ai::score_move(board4 &board, color4 tile, move_t m, float min,
                          float max, hdelta h, int maxdepth) {
  color4 our_prop = tile.propagator();
  elist<boardchange> splash, unsplash;
  float score;
  int win;

  // Compute changes caused by move
  board.compute_splash(m.x, m.y, tile, splash);
  board.applychanges(splash, &unsplash);
  update_heuristic(board, our_prop, unsplash, h);

  if (board.check_win(splash, our_prop, WIN_ROW)) {
    // Adjust based on real, hard data :-)
    score = WINNING_WEIGHT + calc_heuristic(h);
  } else if (maxdepth >0) {
    // Swap and negate min and max: if opponent is able to make a move that
    // scores higher than current (-min), we won't make that choice.
    score = -score_opponent_turn(board, nextcolor(tile), -1e9, -min, -h,
                                 maxdepth-1);
  } else {
    score = calc_heuristic(h);		// exhausted search depth
  }

  board.applychanges(unsplash, NULL);	// restore original board
  return score;
}

atom4ai::move_t atom4ai::pick_random(elist<atom4ai::move_t> &list) {
  int p;				// 1/probability
  elistiter<move_t> it;
  move_t pick;

  for (p=1, it=list.headp(); it; p++, it++) {
    if (rand()%p == 0) {
      pick = *it;
    }
  }
  return pick;
}

void atom4ai::pick_best_move(int fd) {
  elist<move_t> moves;
  elist<move_t> bestmoves;		// in case >1 moves have equal score
  elistiter<move_t> it;
  float min=-1e9, max=+1e9;
  hdelta h = { 0, 0, 0 };		// heuristic function accumulator

  find_legal_moves(*get_board(), moves);

  // compute score for each move
  for (it=moves.headp(); it; it++) {
    move_t m = *it;

#ifdef DEBUG_AI
    fprintf(logfile, "AI: considering move (%d,%d), max depth=%d {{\n",
            m.x, m.y, search_depth);
#endif //DEBUG_AI

    float score = score_move(*get_board(), current_tile(), m, min, max, h,
                             search_depth);

#ifdef DEBUG_AI
    fprintf(logfile, "}} move (%d,%d): %.3f\n", m.x, m.y, score);
#endif //DEBUG_AI

    // Update list of good moves
    if (score > min) {
      min = score;
      bestmoves.clear();		// prev moves are worse than this one
      bestmoves.append(m);		// this move is now the best
    } else if (score==min) {
      bestmoves.append(m);		// more than one best move so far
    }
  }

  // randomly pick a move from the list of highest-scored moves
  if (bestmoves.num_elem() < 1)
    throw exception("atom4ai: unable to find valid move?");
  move_t m = pick_random(bestmoves);

#ifdef DEBUG_AI
  fprintf(logfile, "AI: moving (%d,%d) --> %.3f\n", m.x, m.y, min);
#endif //DEBUG_AI

  // Send move back to parent process
  write(fd, &m.x, sizeof(m.x));
  write(fd, &m.y, sizeof(m.y));

#ifdef PROFILE_AI
  _mcleanup();
#endif //PROFILE_AI
}

// TESTING: spawn subprocess to do the computation, so that the event loop
// can continue processing events.
void atom4ai::make_move() {
  int pipefd[2];

  if (current_player()!=playernum || child != -1)
    return;				// not our turn

  if (pipe(pipefd)==-1)
    throw exception("Unable to create communications channel with AI "
                    "subprocess");

  switch(child=fork()) {
  case -1:
    throw exception("Unable to create AI subprocess\n");
  case 0:				// in child
    close(pipefd[0]);			// close read fd

#ifdef PROFILE_AI
    monstartup((u_long)&_start, (u_long)&etext);
#endif //PROFILE_AI

    pick_best_move(pipefd[1]);
    exit(0);				// never return
  default:				// in parent
    close(pipefd[1]);			// close write fd
    childpipe = pipefd[0];		// remember read fd
    loop->register_handler(eventloop::READER, childpipe, &babysitter);
    break;
  }
}

void atom4ai::notify_move(int player, elist<boardchange> &chg) {
  atom4::notify_move(player, chg);	// forward notification first
  make_move();				// if it's now our turn, make a move
}

void atom4ai::notify_clear() {
  atom4::notify_clear();		// forward notification
  make_move();				// move if it's our turn
}

atom4ai::atom4ai(eventloop *eloop, unsigned int width, unsigned int height,
                 int which_player) :
	atom4local(width,height), playernum(which_player),
	search_depth(DEFAULT_SEARCH_DEPTH),
	loop(eloop), babysitter(this) {

#ifdef DEBUG_AI
  logfile=fopen("/tmp/atom4ai.log","w");
  if (!logfile) throw exception("atom4ai: failed to open logfile\n");
#endif //DEBUG_AI

  child = -1;				// indicate not waiting for process

  // Make first move if we're starting
  make_move();
}

atom4ai::~atom4ai() {
  if (child != -1) {
    loop->unregister_handler(eventloop::READER, childpipe);

    // TODO: kill child process
  }

#ifdef DEBUG_AI
  fclose(logfile);
#endif //DEBUG_AI
}

void atom4ai::reset() {
  // NOTE: may want to insert resetting code here if we keep state between
  // turns
  atom4local::reset();

#ifdef DEBUG_AI
  reopen_log();
#endif //DEBUG_AI

  make_move();				// move if it's our turn after reset
}

atom4::mode_t atom4ai::game_mode() {
  return atom4::PEER;
}

int atom4ai::local_playernum() {
  // FIXME: can be more general
  return playernum==1 ? 2 : 1;
}

int atom4ai::is_local_turn() {
  return current_player()!=playernum && !round_over();
}

int atom4ai::newround() {
  atom4local::newround();

#ifdef DEBUG_AI
  reopen_log();
#endif //DEBUG_AI

  make_move();				// move if it's our turn after reset
}