/*
This file is part of ct.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with ct. If not, see .
*/
#include "negamax.h"
// ===================================================================
// Variables
// ===================================================================
const float infty = 3.0;
char negamax_ptn[9];
uint8_t negamax_search_depth = 3;
static uint64_t negamax_best_action;
// ===================================================================
// Helper declarations
// ===================================================================
static float negamax(const uint8_t cur_depth, const uint8_t init_depth,
float alpha, float beta,
const float colour, const uint64_t hash);
// ===================================================================
// Exported functions
// ===================================================================
void negamax_init(const uint8_t new_board_size) {
board_size = new_board_size;
action_lq_init();
zobrist_init();
tt_init();
}
void negamax_free(void) {
zobrist_free();
}
float negamax_generate(void) {
// We need to start with something outside of [-∞,∞] because those
// values are wins
const float safe_infty = infty + 1;
float result = -infty;
negamax_best_action = -1;
tt_init();
for (int d = 1; d <= negamax_search_depth; d++) {
result = negamax(d, d, -safe_infty, safe_infty,
(ply & 1) ? +1.0 : -1.0, zobrist_compute());
}
tt_free();
action_to_ptn(negamax_best_action, negamax_ptn);
return result;
}
// ===================================================================
// α-β negamax with iterative deepening, using the cnn1986 evaluation
// function and transposition tables using Zobrist hasing and a
// chaining hash table
// ===================================================================
static enum TT_FLAG flag;
static enum WIN_TYPE w;
static float negamax(const uint8_t cur_depth, const uint8_t init_depth,
float alpha, float beta,
const float colour, const uint64_t hash) {
action_lq_t *lq = NULL;
tt_entry_t *entry = tt_seek(hash);
if (entry != NULL && entry->depth >= cur_depth) {
// CAUTION: ≥ breaks search stability (vs =) on shallow depths
if (entry->flag == TT_EXACT) {
return entry->value;
} else if (entry->flag == TT_LOWERBOUND && entry->value > alpha) {
alpha = entry->value;
} else if (entry->flag == TT_UPPERBOUND && entry->value < beta) {
beta = entry->value;
}
if (alpha >= beta) return entry->value;
}
// lq = action_lq_copy_with_mtf(entry->lq, entry->best);
if ((lq = action_lq_generate()) == NULL)
return alpha; // should never happen!
if (entry != NULL) {
action_move_to_front(entry->best, lq);
}
if (init_depth > 1 && cur_depth == init_depth) {
action_move_to_front(negamax_best_action, lq);
}
// TODO: what to do if this is never written to?
action_t best_action = lq->actions[lq->i_f];
float best_value = -infty;
for (int k=lq->i_f; ki_b; k++) {
negamax_display_progress(cur_depth, init_depth, lq->length);
action_take(lq->actions[k]);
// Compute the value of the node
float node_value;
if (ply >= 2*board_size - 2 && (w = check_win()) < 0xFF) {
node_value = -colour*infty;
// Check win if far enough into the game
if (w == WIN_ROAD_BLACK || w == WIN_FLAT_BLACK) {
node_value = colour*infty;
} else if (w == WIN_DRAW) {
node_value = 0;
}
} else if (cur_depth > 1) {
// If nobody won, or too early and not leaf, recurse
node_value = -negamax(cur_depth - 1, init_depth,
-beta, -alpha,
-colour, zobrist_compute());
} else {
node_value = colour * cnn1986_evaluate_black_win();
}
action_undo(lq->actions[k]);
if (node_value > best_value) {
best_value = node_value;
best_action = lq->actions[k];
}
if (best_value > alpha) alpha = best_value;
if (alpha >= beta) break;
}
action_lq_free(lq);
if (cur_depth == init_depth) negamax_best_action = best_action;
flag = TT_EXACT;
if (best_value >= beta) flag = TT_LOWERBOUND;
else if (best_value <= alpha) flag = TT_UPPERBOUND;
if (entry == NULL) {
tt_insert(hash, flag, cur_depth, best_value, best_action);
} else {
entry->flag = flag;
entry->best = best_action;
entry->value = best_value;
entry->depth = cur_depth;
}
return best_value;
}