home / fornax / fornax-v4-0 / src / supplementary / time_management.cpp

time_management.cpp



//
//  time_management.cpp
//  fornax3
//
//  Created by Anders on 05/04/2020.
//

#include <stdio.h>
#include <thread>
#include <atomic>
#include <stdlib.h>
#include "time_management.hpp"
#include "../libs/strings.h"
#include "../libs/stopwatch.h"
#include "../libs/require.h"
#include "../libs/iolog.hpp"
#include "../libs/ints.h"
#include "../search/search.hpp"
#include "../board.h"
#include "../moving/movegen.hpp"
#include "../moving/makemove.hpp"
#include "../parsing.hpp"
#include "../eval_defs.h"

static long configured_overhead = TIME_MANAGEMENT_OVERHEAD_DEFAULT;
static bool is_ponder_enabled = false;

static std::atomic<long> start(-1);
static std::atomic<long> allotted_time(-1);
static std::atomic<uint64_t> time_manage_state(0);

static long get_divisor_from_game_phase(const Board* board) {
  long gamephase = board_get_phase(board);
  long m = PHASE_START / 4;
  long phase1 = m * 3;
  long phase2 = m;
  if (gamephase > phase1) return 45;
  else if (gamephase > phase2) return 35;
  else return 25;
}

static long get_standard_time(long remaining_time, long increment, long divisor) {
  return remaining_time / divisor + increment - configured_overhead;
}

static long time_management_calculate(const Board* board, long remaining_time, long increment, long movestogo) {
  
  if ((remaining_time + increment - configured_overhead) < 20) return -1;
  else if (remaining_time <= increment * 2) return increment / 2;
  else if (movestogo > 0) return get_standard_time(remaining_time - 20, increment, movestogo);
  
  long divisor = get_divisor_from_game_phase(board);
    if (increment > 50) divisor -= 5;
    if (increment > 250) divisor -= 5;
    if (is_ponder_enabled) divisor -= 10;
  
  long standard_time = get_standard_time(remaining_time, increment, divisor);
  standard_time = MAX_INT(10, standard_time - 10);

  long noise = 20 - rand() % 40;
  long noised = standard_time + (standard_time / 100 * noise);
  return noised;
}

std::future<move> time_management_parse_uci(const Board* board, char** tokens, int length) {
  
  bool ponder = false;
  long wtime = 0;
  long winc = 0;
  long btime = 0;
  long binc = 0;
  long movestogo = 0;
  
  for (int i = 0; i < length; ++i) {
    if (string_equals("ponder", tokens[i])) ponder = true;
  }
  
  for (int i = 0; i < length - 1; ++i) {
    char* token = tokens[i];
    char* next = tokens[i + 1];
    if (string_equals("wtime", token)) wtime = string_to_long(next);
    else if (string_equals("winc", token)) winc = string_to_long(next);
    else if (string_equals("btime", token)) btime = string_to_long(next);
    else if (string_equals("binc", token)) binc = string_to_long(next);
    else if (string_equals("movestogo", token)) movestogo = string_to_long(next);
  }
  
  long mytime = board->active ? btime : wtime;
  long myinc = board->active ? binc : winc;
  
  return time_management_search(board, mytime, myinc, movestogo, ponder);
}

static inline move get_only_legal_move(const Board* board) {
  evalmove moves[128];
  uint8_t move_count = movegen_generatef(board, moves);
  return move_count == 1 ? (move) moves[0] : 0;
}

static inline void stop_if_still_searching(uint64_t id) {
  if (time_manage_state == id) {
    search_async_stop();
  }
}

static inline void time_management_trigger_timer(long time, uint64_t id) {
#ifdef DEBUGLOG
  IO_PRINT("info string - time management: searching for = %ld\n", time);
#endif
  time_manage_state = id;
  start = stopwatch_wall_start();
  std::function<void()> and_then = [id]() { stop_if_still_searching(id); };
  std::thread sleep_thread (stopwatch_sleep, time, and_then);
  sleep_thread.detach();
}

static inline std::future<move> time_management_go_infinite(const Board* board, long time) {
  /* If there is time, we also do an extra tt maintenance.
   Giving less value to older moves */
  if (time > 250) {
    transpositions_maintenance();
  }
  
  return search_async_infinite(board);
}

void time_management_ponderhit(const Board* board) {
  if (!is_ponder_enabled) return;
  
  if (!search_is_searching()) { // either max depth was reached or mate in 1 was found
    search_async_infinite(board);
  }
  
  long time = stopwatch_wall_stop(start);
  
#ifdef DEBUGLOG
  long alloted = allotted_time;
  IO_PRINT("info string - ponderhit after %ld/%ld\n", time, alloted);
#endif
  move onlylegal = get_only_legal_move(board);
  if (onlylegal || allotted_time <= 0) {
    search_async_stop();
    return;
  }
  if (time > allotted_time) {
    allotted_time = allotted_time / 2;
  }
  else if (time * 2 > allotted_time) {
    allotted_time = allotted_time - time / 2;
  }
  
  time_management_trigger_timer(allotted_time, board_get_hash(board));
}

std::future<move> time_management_search(const Board* board, long remainingTime, long increment, long movestogo, bool ponder) {
  require(time_manage_state == 0, "info string - time management BUG: mode was already on.\n");
  
  long time = time_management_calculate(board, remainingTime, increment, movestogo);
  allotted_time = time;
  
  if (ponder) {
    if (!is_ponder_enabled) return move_none();
#ifdef DEBUGLOG
    IO_PRINT("info string - start ponder (alloted time = %ld)\n", time);
#endif
    start = stopwatch_wall_start();
    return time_management_go_infinite(board, time);
  }
  
  if (time <= 0) {
#ifdef DEBUGLOG
    IO_PRINT("info string - time management: instant mode\n");
#endif
    return search_async_depth(board, 3);
  }
  
  move onlylegal = get_only_legal_move(board);
  if (onlylegal) {
    parsing_printbestmove(onlylegal, 0);
    return move_now(onlylegal);
  }

  time_management_trigger_timer(time, board_get_hash(board));
  return time_management_go_infinite(board, time);
}

void time_management_send_stopped(void) {
  if (time_manage_state > 0) {
    time_manage_state = 0;
    allotted_time = -1;
    start = -1;
  }
}

bool time_management_check_stop(void) {
  if (time_manage_state == 0) return false;

  long time = stopwatch_wall_stop(start);
  
  bool stop = time > (allotted_time / 4) * 3;
  
#ifdef DEBUGLOG
  if (stop) {
    long alloted = allotted_time;
    IO_PRINT("info string - time management: stopping early after %ld/%ld\n", time, alloted);
  }
#endif
  
  return stop;
}

void time_management_set_overhead(long overhead) {
  require(overhead <= TIME_MANAGEMENT_OVERHEAD_MAX, "overhead was too big %d vs max %d\n", overhead, TIME_MANAGEMENT_OVERHEAD_MAX);
  require(overhead >= TIME_MANAGEMENT_OVERHEAD_MIN, "overhead was too small %d vs min %d\n", overhead, TIME_MANAGEMENT_OVERHEAD_MIN);
  configured_overhead = overhead;
  
#ifdef DEBUGLOG
  IO_PRINT("ok Overhead = %ld\n", overhead);
#endif
}

void time_management_set_ponder_enabled(bool enabled) {
  is_ponder_enabled = enabled;
#ifdef DEBUGLOG
  IO_PRINT("ok Ponder = %s\n", enabled ? "true" : "false");
#endif
}