//
// 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
}