#include "includes.h" #include "knightcap.h" /* the "brain" is an implementation of the permanent brain idea common in chess programs. In KnightCap it is also used to develop its own opening book. This is done by pondering the positions in the brain while idle, constantly refining them. */ extern struct state *state; #define MAX_BRAIN_SIZE 10000 #define MAX_BRAIN_MOVE 30 #define MIN_MULL_TIME (30) #define BRAIN_TEMP_SIZE MAX_GAME_MOVES #define WHITE_THRESHOLD (tanh(0.0*EVAL_SCALE*PAWN_VALUE)) #define BLACK_THRESHOLD (tanh(0.0*EVAL_SCALE*PAWN_VALUE)) #define FLAG_NEEDS_RESEARCH (1<<0) #define FLAG_NEVER_RESEARCH (1<<1) #define MIN_DEPTH 12 #define SAVE_FREQUENCY 300 /* in seconds */ static int mulled_one_opening; static int max_mull_time[4] = {30, 30, 120, 120}; int mull_stage; extern int fill_hash; int testing = 0; struct brain_entry { uint32 hash1; uint32 hash2; int eval_low; int eval_high; char depth_low; char depth_high; Square from, to; int search_time; /* how long we've spent searching this pos */ time_t last_play; /* when we last used this entry */ time_t last_search; /* when we did the last search */ Piece board[NUM_SQUARES]; uint32 flags; uint32 flags2; unsigned short move_num; Square enpassent; uint32 white_wins, black_wins; uint32 unused; }; static int done_lookup; static int brain_size; static struct brain_entry brain[MAX_BRAIN_SIZE + BRAIN_TEMP_SIZE]; static struct brain_entry *brain_temp = &brain[MAX_BRAIN_SIZE]; static int brain_temp_size; static int mull_entry; static int last_hit; static char *brain_fname; static int load_failed; static int refined_count; int mulling=0; extern int learning; static void consistency_check1(struct brain_entry *br); static void consistency_check2(struct brain_entry *br); static inline int converged(struct hash_entry *t) { return (t->low.v + MTD_THRESHOLD >= t->high.v) && (t->depth_low == t->depth_high); } static inline int consistent(struct hash_entry *t1, struct hash_entry *t2) { return ((t1->from == t2->from && t1->to == t2->to) && !(t1->low.v > t2->high.v || t1->high.v < t2->low.v)); } static inline void save_blunder(Position *b, struct brain_entry *br, Move *next_move) { static int initialised; static FILE *f; if (!initialised) { initialised = 1; f = fopen("blunder.dat", "a"); } fprintf(f, "%s %s%s %s %d %d\n", position_to_ppn(b), posstr(br->from), posstr(br->to), short_movestr(b, next_move), br->depth_low, br->depth_high); } static inline int play_offset(int move_num) { return 60*60*24*move_num; } /* return a string describing a brain entry */ static char *entry_str(struct brain_entry *br) { Move m1; static char s[1000]; m1.from = br->from; m1.to = br->to; sprintf(s,"depth=%d/%d num=%d time=%d v=%d/%d move=%s hash1=%08x %s", br->depth_low, br->depth_high, br->move_num, br->search_time, br->eval_low, br->eval_high, short_movestr(NULL, &m1), br->hash1, ctime(&br->last_play)); return s; } static int brain_compare(const void *bb1, const void *bb2) { struct brain_entry *b1; struct brain_entry *b2; b1 = (struct brain_entry *)bb1; b2 = (struct brain_entry *)bb2; if (b1->hash1 == 0) return 1; if (b2->hash1 == 0) return -1; if (b1->depth_low == -1) return 1; if (b2->depth_low == -1) return -1; if (b2->hash1 < b1->hash1) return 1; if (b2->hash1 > b1->hash1) return -1; return memcmp(b1->board, b2->board, sizeof(b1->board)); } /* this one is used when saving to disk so the file remains the same with no changes (good for rsync) */ static int brain_comp2(const void *bb1, const void *bb2) { struct brain_entry *b1; struct brain_entry *b2; b1 = (struct brain_entry *)bb1; b2 = (struct brain_entry *)bb2; if (b1->hash1 == 0) return 1; if (b2->hash1 == 0) return -1; if (b1->last_play < b2->last_play) return 1; if (b1->last_play > b2->last_play) return -1; if (b2->move_num < b1->move_num) return 1; if (b2->move_num > b1->move_num) return -1; return memcmp(b1->board, b2->board, sizeof(b1->board)); } static int setup_brain_pos(Position *b, struct brain_entry *br) { memset(b, 0, sizeof(*b)); memcpy(b->board, br->board, sizeof(b->board)); b->move_num = br->move_num; b->flags = br->flags; b->enpassent = br->enpassent; b->fifty_count = 0; br->hash1 = 0; br->hash2 = 0; if (!create_pboard(b)) { lprintf(0,"brain entry corrupt\n"); br->depth_low = -1; br->depth_high = -1; return 0; } br->hash1 = b->hash1; br->hash2 = b->hash2; return 1; } static void brain_resort(void) { int i; for (i=0;i sum2) { lprintf(0,"byte swapping brain\n"); for (i=0;i t1 - play_offset(brain[i].move_num)) { brain[i].last_play = t1 - play_offset(b.move_num); } } brain_resort(); lprintf(0,"rehashed brain\n"); } /* find a brain entry using only hash1 */ static int brain_find_hash1(uint32 hash1) { int low,high, t; if (brain_size == 0) return -1; low = 0; high = brain_size-1; while (low < high) { t = (low+high)/2; if (brain[t].hash1 == hash1) return t; if (brain[t].hash1 < hash1) { low = t+1; } else { high = t-1; } } if (brain[low].hash1 == hash1) return low; if (state->brain_inserting) { for (t=MAX_BRAIN_SIZE;thash1); if (t == -1) return -1; if (brain[t].hash1 == b->hash1 && brain[t].hash2 == b->hash2) return t; return -1; } void brain_fill_hash(Position *b) { unsigned size; int i, num; struct hash_entry *t; if (brain_size == 0) return; if (mulling) num = brain[mull_entry].move_num; else num = state->position.move_num; for (i=0;i num + 20) continue; if (mulling && i == mull_entry) { lprintf(0, "not inserted (mull entry): %08x %s", brain[i].flags2, entry_str(&brain[i])); continue; } if (brain[i].flags2 & FLAG_NEEDS_RESEARCH) { lprintf(0, "not inserted (needs research): %08x %s", brain[i].flags2, entry_str(&brain[i])); continue; } if (ABS(brain[i].eval_low) < FORCED_WIN && brain[i].eval_low + 0.01*PAWN_VALUE < brain[i].eval_high) { lprintf(0, "not inserted (unconverged): %08x %s", brain[i].flags2, entry_str(&brain[i])); brain[i].flags2 |= FLAG_NEEDS_RESEARCH; continue; } if (brain[i].depth_low == -1) { lprintf(0, "not inserted (?): %08x %s", brain[i].flags2, entry_str(&brain[i])); continue; } t = hash_ptr(brain[i].hash1); size = hts(); if (num > 0 && t->move_num >= num) { /* hash collision */ if (brain[i].move_num > t->move_num) { lprintf(0,"hash collision %08x %08x %d %d\n", brain[i].hash1, t->hash1, brain[i].move_num, t->move_num); continue; } } t->hash1 = brain[i].hash1; t->hash2 = brain[i].hash2; t->from = brain[i].from; t->to = brain[i].to; t->tag = 0; t->move_num = brain[i].move_num; t->depth_low = brain[i].depth_low; t->low.v = brain[i].eval_low; t->depth_high = brain[i].depth_high; t->high.v = brain[i].eval_low; } lprintf(0,"filled hash table\n"); } static void brain_save(void) { int fd; char tmpname[200]; if (brain_size+brain_temp_size == 0) return; sprintf(tmpname,"%s.tmp",brain_fname); fd = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd == -1) { lprintf(0,"can't open %s\n", tmpname); return; } brain_resort(); qsort(brain, brain_size, sizeof(brain[0]), brain_comp2); if (state->brain_inserting && brain_temp_size) { int t = MAX_BRAIN_SIZE - brain_temp_size; if (t > brain_size) { t = brain_size; } else { int i; lprintf(0,"overwriting %d entries\n", brain_size - t); for (i=t;i t && brain_temp_size < BRAIN_TEMP_SIZE/2) return; lastt = t; brain_save(); } int brain_open(char *fname) { struct stat st; int fd; brain_size = 0; load_failed = 1; brain_fname = fname; fd = open(fname,O_RDWR); if (fd == -1) { lprintf(0,"can't open %s\n", fname); return 0; } if (fstat(fd, &st) != 0) { lprintf(0,"fstat %s failed\n", fname); close(fd); return 0; } if (st.st_size % sizeof(brain[0]) != 0) { lprintf(0,"corrupt brain file %s\n", fname); close(fd); return 0; } brain_size = st.st_size / sizeof(brain[0]); if (Read(fd, (char *)brain, st.st_size) != st.st_size) { lprintf(0,"brain read failed\n"); close(fd); return 0; } close(fd); load_failed=0; brain_rehash(); brain_clean(); brain_update(); return 1; } /* lookup a position - using the brain as an opening book */ int brain_lookup(Position *b, Move *move, Eval *v, int pondering) { int t, num_moves, m, bm; Position b1; etype v1, max; static Move moves[MAX_MOVES]; if (mulling || !brain_size || !state->use_pbrain) return 0; #if USE_EGTB if (egtb(b)) { EGInit(); if (EGTBScore(b, &v1)) { num_moves = generate_moves(b, moves); max = -INFINITY; bm = -1; for (m=0;m max) { max = v1; bm = m; } } } if (bm < 0) return 0; (*v) = makeeval(b, v1); move->from = moves[bm].from; move->to = moves[bm].to; move->v = max; if (!legal_move(b, move) || !do_move(&b1, b, move)) { lprintf(0,"Illegal move from EGTB! move=%s\n", short_movestr(b, move)); return 0; } lprintf(0, "EGTB: %s %s\n", short_movestr(b, move), evalstr(move->v)); if (!pondering && state->ics_robot) { prog_printf("whisper EGTB: %s %s\n", short_movestr(b, move), evalstr(move->v)); } return 2; } } #endif if (done_lookup == 0) state->last_book_move = 0; done_lookup = b->move_num; t = brain_find(b); if (t == -1) { lprintf(0,"no brain entry\n"); return 0; } brain[t].last_play = timer_start_time() - play_offset(b->move_num); if (brain[t].from == A1 && brain[t].to == A1) { lprintf(0, "null move in brain entry\n"); return 0; } if (brain[t].eval_low + MTD_THRESHOLD < brain[t].eval_high) { lprintf(0, "unconverged brain lookup %s\n", entry_str(&brain[t])); return 0; } #if REJECT_LOW_BRAIN_MOVES if (brain[t].eval_low < -0.4*PAWN_VALUE) { lprintf(0,"book move too weak: %s", entry_str(&brain[t])); return 0; } #endif last_hit = b->move_num; (*v) = makeeval(b, brain[t].eval_low); move->from = brain[t].from; move->to = brain[t].to; move->v = v->v; brain_timer(time(NULL)); if (!legal_move(b, move) || !do_move(&b1, b, move)) { lprintf(0,"Illegal move from book! move=%s\n", short_movestr(b, move)); return 0; } if (check_repitition(&b1, 1)) return 0; lprintf(0, "book: %s low=%d high=%d ww/bw=%d/%d depth=%d/%d searcht=%d\n", short_movestr(b, move), brain[t].eval_low, brain[t].eval_high, brain[t].white_wins, brain[t].black_wins, brain[t].depth_low, brain[t].depth_high, brain[t].search_time); if (state->first_book_move) { state->first_book_move = 0; state->first_book_move_index = t; } if (!pondering) { brain_temp[brain_temp_size] = brain[t]; lprintf(0,"inserted entry %s", entry_str(&(brain_temp[brain_temp_size]))); brain_temp_size++; if (state->ics_robot) { prog_printf("whisper Book: %s low=%5.3lf high=%5.3lf ww/bw=%d/%d depth=%d/%d searcht=%d\n", short_movestr(b, move), (double)brain[t].eval_low/PAWN_VALUE, (double)brain[t].eval_high/PAWN_VALUE, brain[t].white_wins, brain[t].black_wins, brain[t].depth_low, brain[t].depth_high, brain[t].search_time); } } return 1; } /* possibly insert an entry into the brain */ int brain_insert(Position *b, Move *move) { int nohash = 0; int t; struct brain_entry *br; Position b1; struct hash_entry *h; if (load_failed) return 0; t = brain_find(b); if (mulling && t != mull_entry) { return 0; } if (state->brain_inserting && t != -1) { br = &brain[t]; lprintf(0, "index: %d\n", t); } else { br = &brain_temp[brain_temp_size]; if (!mulling) { brain_temp_size++; memset(br, 0, sizeof(*br)); } } if (!do_move(&b1, b, move)) { lprintf(0,"illegal move passed to brain_insert\n"); return 0; } h = fetch_hash(&b1); if (!h) { h = fetch_hash(b); if (!h) { lprintf(0,"no hash entry for brain_insert move\n"); return 0; } nohash = 1; } if (!converged(h)) { if (!nohash) { struct hash_entry *h1 = h; h = fetch_hash(b); if (!h) { h = h1; lprintf(0, "No hash entry for brain entry\n"); } else nohash = 1; } } if (!converged(h)) { if (mulling && ABS(h->low.v) < FORCED_WIN && h->low.v != -INFINITY) { lprintf(0, "not inserting: not converged %d/%d %d/%d\n",h->low.v, h->high.v, h->depth_low, h->depth_high); return 0; } lprintf(0, "inserting unconverged entry: %d/%d %d/%d %s%s\n", h->low.v, h->high.v, h->depth_low, h->depth_high, posstr(h->from), posstr(h->to)); state->converged = 0; } /* check if it is worth overwriting the old entry. at present we just base the decision on the depths of the entries */ /* we are happy with this entry: reset the flags */ br->flags2 &= ~FLAG_BLUNDER; br->flags2 &= ~FLAG_NEEDS_RESEARCH; /* we've decided where to put it, finish it off */ br->hash1 = b->hash1; br->hash2 = b->hash2; if (nohash) { br->eval_low = h->low.v; br->eval_high = h->high.v; } else { br->eval_low = -h->high.v; br->eval_high = -h->low.v; } if (nohash) { br->depth_low = h->depth_low; br->depth_high = h->depth_high; } else { br->depth_low = h->depth_high+1; br->depth_high = h->depth_low+1; } br->from = move->from; br->to = move->to; br->search_time = timer_elapsed(); memcpy(br->board, b->board, sizeof(b->board)); br->flags = b->flags; br->move_num = b->move_num; br->enpassent = b->enpassent; br->last_search = time(NULL); if (!mulling) { br->last_play = time(NULL) - play_offset(b->move_num); } lprintf(0,"inserted entry %s", entry_str(br)); if (br->depth_low < state->current_depth) state->converged = 0; else state->converged = 1; if (mulling) { br->depth_low = MIN_DEPTH; br->depth_high = MIN_DEPTH; consistency_check1(br); consistency_check2(br); brain_save(); } brain_timer(time(NULL)); return 1; } void brain_close(void) { brain_save(); } static int choose_mull(void) { int t, r; int bestt; int last_move; time_t last_play; #if SEARCH_OPENING_VARIATIONS Position b; Move m; #endif lprintf(0,"stage 0 mulling: blunders\n"); state->open = 0; /* the first priority is blunders, most recent ones first */ mull_stage= 0; bestt = 0; while (bestt != -1) { bestt = -1; last_play = time(NULL); for (t=0;t= last_move || bestt == -1) { bestt = t; last_move = brain[t].move_num; } } } if (bestt != -1) { return bestt; } } lprintf(0,"all entries searched\n"); if (!brain_clean()) goto stage2; state->open = 1; if (state->notified) { prog_printf("tell %s My brain is now clean and I am accepting challenges\n", state->notifyee); prog_printf("accept\n"); state->notified = 0; } prog_printf("seeks\n"); if ((random() % 1) == 0 && mulled_one_opening) { /* choose randomly, weighted towards guys near the start and those that have not been searched for a while */ mull_stage = 2; lprintf(0, "using random mull\n"); bestt = 0; while (!state->quit && state->computer == 0) { t = random() % brain_size; if (brain[t].hash1 == 0) continue; if (brain[t].flags2 & FLAG_NEVER_RESEARCH) continue; if (imax(brain[t].depth_low, brain[t].depth_high) > MAX_DEPTH/2) continue; /* don't mull on mate! */ if (brain[t].eval_low > (WIN-100) || brain[t].eval_high < -(WIN-100)) continue; r = 100; if (brain[t].move_num < 20) r += (20 - brain[t].move_num)*(20 - brain[t].move_num)*100; if (random() % 40100 >= r) continue; return t; } } else { mulled_one_opening = 1; #if SEARCH_OPENING_VARIATIONS /* pick a very early move in the brain, follow the resulting variation as far as it goes and then add the end position to the brain and mull it. Choose the opening in the last game played first. */ mull_stage = 3; lprintf(0, "mulling opening variations\n"); memset(&b, 0, sizeof(b)); while (!state->quit && state->computer == 0) { if (!state->first_book_move) { t = state->first_book_move_index; state->first_book_move = 1; } t = random() % brain_size; if (brain[t].move_num > 10) continue; r = (10 - brain[t].move_num)*(10 - brain[t].move_num); if (random() % 100 >= r) continue; if (brain[t].hash1 == 0) continue; if (brain[t].flags2 & FLAG_NEVER_RESEARCH) continue; if (imax(brain[t].depth_low, brain[t].depth_high) > MAX_DEPTH/2) continue; /* don't mull on mate! */ if (brain[t].eval_low > (WIN-100) || brain[t].eval_high < -(WIN-100)) continue; if ((brain[t].move_num & 1) != (brain[t].hash1 & 1)) { brain[t].depth_low = -1; continue; } /* now follow the variation */ while (!state->quit && state->computer == 0 && t != -1) { if (!setup_brain_pos(&b, &brain[t])) { lprintf(0,"removing brain entry\n"); brain[t].depth_low = -1; break; } m.from = brain[t].from; m.to = brain[t].to; if (!legal_move(&b, &m)) { lprintf(0,"illegal move %s", entry_str(&brain[t])); brain[t].depth_low = -1; break; } if (!do_move(&b, &b, &m)) { lprintf(0,"removing brain entry\n"); brain[t].depth_low = -1; break; } t = brain_find(&b); } if (t != -1) continue; create_pboard(&b); print_board(b.board); /* yucky hack to get the entry in the brain without too much effort */ mulling = 0; state->mulling = 0; testing = 1; timer_reset(); hash_reset(); order_reset(); state->move_time = 1; zero_move(&m); state->brain_inserting = 1; make_move(&b, &m); mulling = 1; state->mulling = 1; testing = 0; brain_save(); state->brain_inserting = 0; t = brain_find(&b); if (t == -1) { lprintf(0, "brain insert unsuccessful\n"); continue; } return t; } #endif } return -1; } void brain_mull(void) { Position b; int t; Move move; float old_move_time=0; static int first_mull = 1; if (brain_size == 0) { state->open = 1; return; } if (state->auto_exit && state->moves_mulled >= MAX_MOVES_MULLED && state->open) return; lprintf(0,"starting mulling on %d brain entries\n", brain_size); mulled_one_opening = 0; mulling = 1; state->mulling = 1; state->brain_inserting = 0; brain_temp_size = 0; brain_clean(); while (!state->quit && state->computer == 0 && state->use_mulling && (!state->auto_exit || (state->moves_mulled < MAX_MOVES_MULLED || !state->open))) { t = choose_mull(); if (first_mull && state->ics_robot && state->open && state->computer == 0 && state->autoplay) { prog_printf("seeks\n"); first_mull = 0; } if (t == -1) continue; if (!setup_brain_pos(&b, &brain[t])) continue; old_move_time = state->move_time; state->move_time = max_mull_time[mull_stage]; mull_entry = t; timer_reset(); hash_reset(); order_reset(); move.from = brain[t].from; move.to = brain[t].to; if (imax(brain[t].depth_low, brain[t].depth_high) > 40) brain[t].depth_low = brain[t].depth_high = 1; lprintf(0,"mulling %d depth=%d/%d mtime=%2.1f ww=%d bw=%d move=%d v=%d/%d %s\n", t, brain[t].depth_low, brain[t].depth_high, state->move_time, brain[t].white_wins, brain[t].black_wins, brain[t].move_num, brain[t].eval_low, brain[t].eval_high, short_movestr(&b, &move)); if (state->display_position) { dump_ppn(&b); } if (mull_stage == 0) { if ((brain[t].depth_low < MIN_DEPTH) || (brain[t].depth_high < MIN_DEPTH)) state->current_depth = imax(7,imax(brain[t].depth_low, brain[t].depth_high)); } else { state->current_depth = 7; } lprintf(0,"current depth: %d\n", state->current_depth); state->brain_inserting = 1; make_move(&b, &move); state->brain_inserting = 0; brain[t].flags2 &= ~FLAG_NEEDS_RESEARCH; brain[t].flags2 &= ~FLAG_BLUNDER; brain_save(); ++state->moves_mulled; lprintf(0,"mulled ***%d\n",state->moves_mulled); state->move_time = old_move_time; if (state->computer) continue; /* This resumes any available match in the adjourned list (possibly ICS only) */ if (state->ics_robot && state->autoplay && state->open) prog_printf("resume\n"); } mulling = 0; state->mulling = 0; } /* This is used when cleaning the brain */ static void consistency_check0(struct brain_entry *br) { int t1, num_moves, m; Position b, b1; static Move moves[MAX_MOVES]; uint32 research_flags; if (mulling && mull_stage == 0) research_flags = (FLAG_NEEDS_RESEARCH | FLAG_BLUNDER); else research_flags = FLAG_NEEDS_RESEARCH; /* first some basic sanity checks */ if (br->search_time < 0) br->search_time = 0; if (!setup_brain_pos(&b, br)) { lprintf(0,"removing brain entry\n"); br->depth_low = -1; return; } if ((br->move_num & 1) != (br->hash1 & 1)) { lprintf(0,"wrong player in consistency_check0 %s", entry_str(br)); br->depth_low = -1; return; } num_moves = generate_moves(&b, moves); for (m=0;meval_high < -brain[t1].eval_high) { br->flags2 |= research_flags; brain[t1].flags2 |= research_flags; lprintf(0, "refined entry0: %s", entry_str(br)); lprintf(0, "by: %s\n", entry_str(&brain[t1])); } } } /* this is used when inserting a move into the brain. The insert may obsolete later brain entries */ static void consistency_check1(struct brain_entry *br) { int t2, num_moves, m; Move m1; Position b, b1, b2; static Move moves[MAX_MOVES]; struct hash_entry *h; uint32 research_flags; if (mulling && mull_stage == 0) research_flags = (FLAG_NEEDS_RESEARCH | FLAG_BLUNDER); else research_flags = FLAG_NEEDS_RESEARCH; /* first some basic sanity checks */ if (br->search_time < 0) br->search_time = 0; if (!setup_brain_pos(&b, br)) { lprintf(0,"removing brain entry\n"); br->depth_low = -1; return; } if ((br->move_num & 1) != (br->hash1 & 1)) { lprintf(0,"wrong player in consistency_check1 %s", entry_str(br)); br->depth_low = -1; return; } /* Now see if this brain entry leads to another brain entry. If it does then the value of that brain entry is bounded by the value of the position in the hash table. This relies on the fact that the brain entry would have been overwritten by the hash table entry, and hence the hash table entry must be "better". However, this may not be the best thing to do. For example, if the hash table entry has the same move as the brain entry, but a significantly wider gap between eval_high and eval_low, and in addition has only been searched 1-ply deeper, then it is not really a better entry. A hack at improving this is given below */ m1.from = br->from; m1.to = br->to; if (!do_move(&b1, &b, &m1)) { lprintf(0,"removing brain entry\n"); br->depth_low = -1; return; } t2 = brain_find(&b1); if (t2 != -1 && (h = fetch_hash(&b1)) && brain[t2].move_num > br->move_num) { /* only replace the brain entry if the move has changed or if the new eval is inconsistent with the old eval */ if (brain[t2].from != h->from || brain[t2].to != h->to || h->high.v < brain[t2].eval_low || h->low.v > brain[t2].eval_high) { brain[t2].eval_high = h->high.v; brain[t2].eval_low = h->low.v; brain[t2].depth_low = h->depth_low; brain[t2].depth_high = h->depth_high; brain[t2].from = h->from; brain[t2].to = h->to; if (!converged(h)) { brain[t2].flags2 |= research_flags; } lprintf(0,"refined entry1 %s", entry_str(&brain[t2])); } else { if (h->depth_low > brain[t2].depth_low && h->low.v > -INFINITY) { brain[t2].eval_low = h->low.v; brain[t2].depth_low = h->depth_low; brain[t2].flags2 |= research_flags; lprintf(0,"refined entry1 %s", entry_str(&brain[t2])); } if (h->depth_high > brain[t2].depth_high && h->high.v < INFINITY) { brain[t2].eval_high = h->high.v; brain[t2].depth_high = h->depth_high; brain[t2].flags2 |= research_flags; lprintf(0,"refined entry1 %s", entry_str(&brain[t2])); } } } /* now generate and try all moves from this position. If they lead to positions in the brain then the hash entries for these positions also bound the brain entry evals */ num_moves = generate_moves(&b1, moves); for (m=0;m br->move_num) { /* only replace the brain entry if the move has changed or if the new eval is inconsistent with the old eval */ if (brain[t2].from != h->from || brain[t2].to != h->to || h->high.v < brain[t2].eval_low || h->low.v > brain[t2].eval_high) { brain[t2].eval_high = h->high.v; brain[t2].eval_low = h->low.v; brain[t2].depth_low = h->depth_low;; brain[t2].depth_high = h->depth_high; brain[t2].from = h->from; brain[t2].to = h->to; if (!converged(h)) brain[t2].flags2 |= research_flags; lprintf(0,"refined entry2 %s", entry_str(&brain[t2])); } else { if (h->depth_low > brain[t2].depth_low && h->low.v > -INFINITY) { brain[t2].eval_low = h->low.v; brain[t2].depth_low = h->depth_low; brain[t2].flags2 |= research_flags; lprintf(0,"refined entry2 %s", entry_str(&brain[t2])); } if (h->depth_high > brain[t2].depth_high && h->high.v < INFINITY) { brain[t2].eval_high = h->high.v; brain[t2].depth_high = h->depth_high; brain[t2].flags2 |= research_flags; lprintf(0,"refined entry2 %s", entry_str(&brain[t2])); } } } } /* we don't go any deeper because it is very unlikely that any brain entries this deep would have been overwritten in the hash table (?) */ } /* this is used when thinking about using an entry in the brain, or when mulling entries. It may invalidate the current entry based on later entries */ static void consistency_check2(struct brain_entry *br) { int t1, t2, num_moves, m; Move m1; Position b, b1, b2; static Move moves[MAX_MOVES]; struct hash_entry *h; uint32 research_flags; if (mulling && mull_stage == 0) research_flags = (FLAG_NEEDS_RESEARCH | FLAG_BLUNDER); else research_flags = FLAG_NEEDS_RESEARCH; /* first some basic sanity checks */ if (br->search_time < 0) br->search_time = 0; /* now see if this brain entry leads to another brain entry. If it does then the value of this position is bounded by the value of the other position */ if (!setup_brain_pos(&b, br)) { lprintf(0,"removing brain entry\n"); br->depth_low = -1; return; } if ((br->move_num & 1) != (br->hash1 & 1)) { lprintf(0,"wrong player in consistency_check2 %s", entry_str(br)); br->depth_low = -1; return; } m1.from = br->from; m1.to = br->to; if (!legal_move(&b, &m1)) { lprintf(0,"illegal move %s", entry_str(br)); br->depth_low = -1; return; } if (!do_move(&b1, &b, &m1)) { lprintf(0,"removing brain entry\n"); br->depth_low = -1; return; } t1 = brain_find(&b1); h = fetch_hash(&b1); if (t1 != -1 && !(brain[t1].flags2 & FLAG_NEEDS_RESEARCH) && brain[t1].move_num > br->move_num) { if (br->eval_low > -brain[t1].eval_high) { br->eval_low = -brain[t1].eval_high; if (br->eval_high + brain[t1].eval_high > 0.01*PAWN_VALUE) { br->flags2 |= research_flags; brain[t1].flags2 |= research_flags; lprintf(0,"refined entry3: %s", entry_str(br)); lprintf(0, "by %s", entry_str(&brain[t1])); if (h) lprintf(0, "hash entry: %d/%d %d/%d\n", h->low.v, h->high.v, h->depth_low, h->depth_high); else lprintf(0,"brain entry not in hash table!\n"); } } } /* now generate and try all moves from this position. If they lead to positions in the brain then those positions also bound the eval at the current position */ num_moves = generate_moves(&b1, moves); for (m=0;mhash1 && b2.hash2 == br->hash2) continue; t2 = brain_find(&b2); if (t2 == -1) continue; h = fetch_hash(&b2); if (!((brain[t2].flags2 & FLAG_NEEDS_RESEARCH) || (brain[t2].flags2 & FLAG_BLUNDER)) && brain[t2].move_num > br->move_num) { if (brain[t2].eval_low + 0.01*PAWN_VALUE < brain[t2].eval_high) { brain[t2].flags2 |= research_flags; lprintf(0, "unconverged entry: %s", entry_str(&brain[t2])); } else { if (br->eval_low > brain[t2].eval_low) { if (br->eval_low - brain[t2].eval_low > 0.01*PAWN_VALUE) { br->flags2 |= research_flags; lprintf(0,"refined entry4: %s", entry_str(br)); lprintf(0,"by: %s", entry_str(&brain[t2])); if (t1 != -1) { lprintf(0, "after: %s", entry_str(&brain[t1])); brain[t1].flags2 |= research_flags; } br->eval_low = brain[t2].eval_low; if (h) lprintf(0, "hash entry: %d/%d %d/%d\n", h->low.v, h->high.v, h->depth_low, h->depth_high); else { lprintf(0,"brain entry not in hash table!\n"); } brain[t2].flags2 |= research_flags; ++refined_count; if (mulling) state->stop_search = 1; } } } } } } void analyse_game(void) { int i, j, t; int winner=0; int num_moves = state->position.move_num; int losing_move=0; int insert=0; int last_move=0; int first_move = -1; double threshold; FILE *result; done_lookup = 0; if (state->analysed) { if (!state->use_mulling) state->open = 1; return; } state->open = 0; #if LEARN_EVAL if (learning) { td_update(); td_dump("coeffs.dat"); } #endif /* couldn't think of a more reliable place to reset this */ state->rating_change = -2; lprintf(0,"analysis: %d moves\n", num_moves); /* work out who won (or was winning when it ended) */ for (i=num_moves-1;i>=0;i--) { lprintf(0,"move %d eval=%d\n", i, state->game_record[i].v); if (state->game_record[i].v != INFINITY) { if (state->game_record[i].v > 0.4*PAWN_VALUE) { winner = 1; } else if (state->game_record[i].v < -0.4*PAWN_VALUE) { winner = -1; } break; } } for (i=0; igame_record[i].v != 0) first_move = i; if (state->game_record[i].v != INFINITY && first_move != -1) lprintf(0,"%d %d\n", i - first_move, state->converged_record[i].v); } if (num_moves - first_move < 6) { if (!state->brain_inserting) brain_temp_size = 0; state->analysed = 1; if (!state->use_mulling) { state->open = 1; if (state->ics_robot && state->computer == 0 && state->autoplay) { prog_printf("seeks\n"); } } return; } lprintf(0,"game analysis winner=%d moves=%d colour=%d\n", winner, num_moves, state->colour); result = fopen("result.txt", "a"); if (winner == state->colour) fprintf(result, "1\n"); else if (winner == 0) fprintf(result, "0\n"); else fprintf(result, "-1\n"); fclose(result); dump_history(); if (!state->use_mulling) { state->open = 1; state->analysed = 1; if (state->ics_robot && state->computer == 0 && state->notified) { prog_printf("tell %s My brain is now clean and I am accepting challenges\n", state->notifyee); prog_printf("accept\n"); state->notified = 0; } if (state->ics_robot && state->computer == 0 && state->autoplay) { prog_printf("seeks\n"); } return; } if (!state->brain_inserting) { if (winner == -state->colour || winner == 0) { int forced_win=0; etype last_eval; double tanhv[MAX_GAME_MOVES]; /* find the "losing move". we look for the largest transition from above -half a pawn to below 0 pawns. Do this for losses but not the inverse for wins because often a win comes through a mistake of the opponent, a mistake the opponent could well repeat. draws are tough because they often only show up when the 50 move rule is reached, so we handle them separately */ for (i=first_move, j=0; igame_record[i].v != INFINITY) { /* kludge to fix up problem with wrong values after mate */ if (!forced_win && ABS(state->converged_record[i].v) > FORCED_WIN && ABS(state->converged_record[i].v) < INFINITY) { forced_win = 1; last_eval = state->converged_record[i].v; } if (forced_win) state->converged_record[i].v = last_eval; tanhv[j] = tanh(EVAL_SCALE*state->converged_record[i].v); lprintf(0,"%d %lf\n", j, tanhv[j]); if (i==state->last_book_move) state->last_book_move = j; ++j; } } tanhv[j] = -1; last_move = j; memset(state->converged_record, 0, MAX_GAME_MOVES*sizeof(state->converged_record[0])); memset(state->game_record, 0, MAX_GAME_MOVES*sizeof(state->game_record[0])); /* now look for the last time the evaluation was above 0.0 pawns for more than three consecutive moves (-0.2 pawns for black) */ losing_move = 0; if (state->colour == 1) { threshold = WHITE_THRESHOLD; } else { threshold = BLACK_THRESHOLD; } for (i=last_move; i>=2; i--) { if (tanhv[i] > threshold && tanhv[i-1] > threshold && tanhv[i-2] > threshold) { losing_move = i; break; } } lprintf(0,"losing move: %d \n", losing_move); result = fopen("losing_move.txt", "a"); fprintf(result, "%d\n", losing_move); fclose(result); insert = 1; } else if (!state->auto_exit && state->won == STALEMATE) { /* just record the last position, we can't reliably conclude anything else losing_move = brain_temp_size-1; lprintf(0,"losing move (draw): %d \n", losing_move); */ } if ((losing_move > 0 && losing_move < 120) || insert) { int new_entries = 0; state->brain_inserting = 0; if (losing_move < state->last_book_move) last_move = imin(state->last_book_move+6, brain_temp_size); else last_move = imin(losing_move+MAX_MOVES_MULLED, brain_temp_size); for (i=losing_move; ibrain_inserting = 1; brain_save(); state->brain_inserting = 0; } brain_temp_size = 0; } for (i=num_moves-3;i>=0;i--) { t = brain_find_hash1(state->hash_list[i]); if (t == -1) continue; if (winner == 1) brain[t].white_wins++; else if (winner == -1) brain[t].black_wins++; } lprintf(0,"game analysis did %d moves\n", num_moves); brain_save(); if (losing_move == 0 && state->auto_exit) state->moves_mulled = MAX_MOVES_MULLED; state->analysed = 1; } static int clean_comp(const void *ii1, const void *ii2) { int *i1; int *i2; i1 = (int *)ii1; i2 = (int *)ii2; if (brain[*i1].move_num != brain[*i2].move_num) return brain[*i1].move_num - brain[*i2].move_num; return memcmp(brain[*i1].board, brain[*i2].board, sizeof(brain[0].board)); } /* clean the brain. This means finding inconsisant brain entries and fixing them */ int brain_clean(void) { int i, t; int clean=1; int brindex[MAX_BRAIN_SIZE]; int duplicates=0; uint32 research_flags; brain_save(); if (!brain_size) return clean; lprintf(0,"cleaning brain\n"); if (mulling && mull_stage == 0) research_flags = (FLAG_NEEDS_RESEARCH | FLAG_BLUNDER); else research_flags = FLAG_NEEDS_RESEARCH; for (i=0;i INFINITY || ABS(brain[i].eval_high) > INFINITY) { brain[i].flags2 &= ~FLAG_NEVER_RESEARCH; brain[i].flags2 |= research_flags; lprintf(0, "out of bounds: %s", entry_str(&brain[i])); clean = 0; } else if ((brain[i].depth_low == MAX_DEPTH || brain[i].depth_high == MAX_DEPTH) && !((ABS(brain[i].eval_low) >= FORCED_WIN && ABS(brain[i].eval_low) <= WIN) || (ABS(brain[i].eval_high) >= FORCED_WIN && ABS(brain[i].eval_high) <= WIN))) { brain[i].flags2 &= ~FLAG_NEVER_RESEARCH; brain[i].flags2 |= research_flags; clean = 0; } else { /* don't research mates */ if ((ABS(brain[i].eval_low) >= FORCED_WIN && ABS(brain[i].eval_low) <= WIN) || (ABS(brain[i].eval_high) >= FORCED_WIN && ABS(brain[i].eval_high) <= WIN)) { brain[i].flags2 &= ~research_flags; brain[i].flags2 |= FLAG_NEVER_RESEARCH; brain[i].depth_low = brain[i].depth_high = MAX_DEPTH; } else if (brain[i].flags2 & FLAG_NEVER_RESEARCH) { brain[i].depth_low = brain[i].depth_high = -1; } else if (brain[i].eval_low + 0.01*PAWN_VALUE < brain[i].eval_high && ABS(brain[i].eval_low) < FORCED_WIN) { lprintf(0,"unconverged brain entry: %d %s", i, entry_str(&brain[i])); brain[i].flags2 |= research_flags; clean = 0; } else if (((brain[i].depth_low <= 5 && brain[i].depth_low > 0) || (brain[i].depth_high <= 5 && brain[i].depth_high > 0)) && ABS(brain[i].eval_low) < FORCED_WIN) { lprintf(0,"shallow brain entry: %s", entry_str(&brain[i])); brain[i].flags2 |= research_flags; clean = 0; } } if (brain[i].move_num > 200) { printf("removing late entry %s", entry_str(&brain[i])); brain[i].depth_low = -1; brain[i].depth_high = -1; } #if STORE_ONLY_NEGATIVE if (!(brain[i].flags2 & FLAG_NEEDS_RESEARCH) && (((brain[i].eval_high > -0.25*PAWN_VALUE) && (brain[i].move_num & 1)) || ((brain[i].eval_high > 0) && !(brain[i].move_num & 1)))) { printf("removing positive entry %s", entry_str(&brain[i])); brain[i].depth_low = -1; brain[i].depth_high = -1; } #endif if (brain[i].hash1 == brain[i+1].hash1) { if (brain[i].hash2 == brain[i+1].hash2) { lprintf(0,"duplicate entry %s", entry_str(&brain[i])); lprintf(0,"duplicate entry %s", entry_str(&brain[i+1])); brain[i+1].depth_low = -1; brain[i+1].depth_high = -1; duplicates++; } else { lprintf(0,"duplicate hash1 values\n"); } } } brain_resort(); /* build a list of indexes sorted by move number */ for (i=0;icomputer != 0) return clean; } brain_save(); lprintf(0,"finished cleaning brain\n"); return clean; } void brain_update() { int i; for (i=0; i 15) brain[i].flags2 &= ~FLAG_NEEDS_RESEARCH; brain_save(); }