#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono; #if defined(__GNUC__) || defined(__clang__) #define FORCE_INLINE __attribute__((always_inline)) inline #define UNLIKELY(x) __builtin_expect(!!(x), 0) #define CTZ(x) __builtin_ctz(x) #else #define FORCE_INLINE inline #define UNLIKELY(x) (x) static inline int CTZ(uint32_t v) { int c = 0; if (!v) return 32; while (!(v & 1)) { ++c; v >>= 1; } return c; } #endif // ============================================================ // КОНСТАНТЫ И ГЛОБАЛЬНЫЕ СТРУКТУРЫ // ============================================================ static const int TT_SIZE_BITS = 22; // 64 МБ RAM static const int TT_SIZE = 1 << TT_SIZE_BITS; static const int TT_MASK = TT_SIZE - 1; static const int DTM_CACHE_SIZE_BITS = 19; static const int DTM_CACHE_SIZE = 1 << DTM_CACHE_SIZE_BITS; static const int DTM_CACHE_MASK = DTM_CACHE_SIZE - 1; static const int TIME_LIMIT_MS = 2800; static const int DTM_INF = 9999; static uint64_t C[26][26]; static const int SYM_MAPS[8][25] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24}, { 4, 9,14,19,24, 3, 8,13,18,23, 2, 7,12,17,22, 1, 6,11,16,21, 0, 5,10,15,20}, {24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, {20,15,10, 5, 0,21,16,11, 6, 1,22,17,12, 7, 2,23,18,13, 8, 3,24,19,14, 9, 4}, {20,21,22,23,24,15,16,17,18,19,10,11,12,13,14, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4}, { 4, 3, 2, 1, 0, 9, 8, 7, 6, 5,14,13,12,11,10,19,18,17,16,15,24,23,22,21,20}, { 0, 5,10,15,20, 1, 6,11,16,21, 2, 7,12,17,22, 3, 8,13,18,23, 4, 9,14,19,24}, {24,19,14, 9, 4,23,18,13, 8, 3,22,17,12, 7, 2,21,16,11, 6, 1,20,15,10, 5, 0} }; static const uint32_t WIN_MASKS[28] = { 0x0000000f,0x0000001e,0x000001e0,0x000003c0,0x00003c00,0x00007800, 0x00078000,0x000f0000,0x00f00000,0x01e00000,0x00008421,0x00010842, 0x00021084,0x00042108,0x00084210,0x00108420,0x00210840,0x00421080, 0x00842100,0x01084200,0x00041041,0x00082082,0x00820820,0x01041040, 0x00008888,0x00011110,0x00111100,0x00222200 }; static const int ADJACENT_CORNERS[8] = {1, 5, 3, 9, 21, 15, 23, 19}; static const int CORNERS[4] = {0, 4, 20, 24}; alignas(64) static uint32_t SYM_BIT_LUT[8][25]; alignas(64) static uint32_t SYM_BYTE_LUT[8][3][256]; alignas(64) static uint32_t SYM_TOP_LUT[8][2]; static void build_sym_luts() { for (int s = 0; s < 8; ++s) { for (int i = 0; i < 25; ++i) SYM_BIT_LUT[s][i] = (1u << SYM_MAPS[s][i]); for (int g = 0; g < 3; ++g) { for (int v = 0; v < 256; ++v) { uint32_t res = 0; for (int b = 0; b < 8; ++b) if ((v >> b) & 1) res |= SYM_BIT_LUT[s][g * 8 + b]; SYM_BYTE_LUT[s][g][v] = res; } } SYM_TOP_LUT[s][0] = 0; SYM_TOP_LUT[s][1] = SYM_BIT_LUT[s][24]; } } static FORCE_INLINE uint32_t apply_sym_fast(uint32_t b, int s) { return SYM_BYTE_LUT[s][0][ b & 0xFF] | SYM_BYTE_LUT[s][1][(b >> 8) & 0xFF] | SYM_BYTE_LUT[s][2][(b >> 16) & 0xFF] | SYM_TOP_LUT [s] [(b >> 24) & 1 ]; } static FORCE_INLINE uint64_t canonical_key(uint32_t xb, uint32_t ob) { uint64_t best = ((uint64_t)xb << 25) | ob; #pragma GCC unroll 7 for (int s = 1; s < 8; ++s) { uint64_t k = ((uint64_t)apply_sym_fast(xb, s) << 25) | apply_sym_fast(ob, s); if (k < best) best = k; } return best; } static FORCE_INLINE void get_canonical(uint32_t xb, uint32_t ob, uint32_t& bx, uint32_t& bo) { bx = xb; bo = ob; uint64_t bk = ((uint64_t)xb << 25) | ob; #pragma GCC unroll 7 for (int s = 1; s < 8; ++s) { uint32_t sx = apply_sym_fast(xb, s), so = apply_sym_fast(ob, s); uint64_t k = ((uint64_t)sx << 25) | so; if (k < bk) { bk = k; bx = sx; bo = so; } } } // ============================================================ // ПОТОКОБЕЗОПАСНЫЕ КЭШИ (STRIPED LOCKS + ATOMICS) // ============================================================ struct alignas(16) TTEntry { uint64_t key; float score; int16_t depth; uint64_t epoch; }; struct alignas(16) DTMCacheEntry { uint64_t key; int16_t dist; uint64_t epoch; }; static TTEntry* TT = nullptr; static DTMCacheEntry* s_dtm_cache = nullptr; static std::mutex tt_locks[256]; static std::mutex dtm_locks[256]; static std::atomic g_epoch{1}; static std::atomic g_dtm_epoch{1}; // Пул отмен расширен до 1 000 000 слотов static std::atomic g_cancel_flags[1000000]; static uint8_t* layers[26] = {}; static size_t layer_sizes[26] = {}; // ============================================================ // THREAD-LOCAL КОНТЕКСТ ПОИСКА // ============================================================ thread_local int t_dtm_nodes = 0; thread_local int t_dtm_node_limit = 50000; thread_local int t_total_nodes = 0; thread_local steady_clock::time_point t_start; thread_local std::mt19937 rng(std::random_device{}()); static FORCE_INLINE bool check_time(int sid) { if (sid >= 0 && sid < 1000000 && g_cancel_flags[sid].load(std::memory_order_relaxed)) return true; if (UNLIKELY((++t_total_nodes & 1023) == 0)) { if (duration_cast(steady_clock::now() - t_start).count() >= TIME_LIMIT_MS) { if (sid >= 0 && sid < 1000000) g_cancel_flags[sid].store(true, std::memory_order_relaxed); return true; } } return false; } // ============================================================ // ИНИЦИАЛИЗАЦИЯ И БД // ============================================================ static void precompute() { for (int i = 0; i < 26; ++i) { C[i][0] = 1; for (int j = 1; j <= i; ++j) C[i][j] = C[i-1][j-1] + C[i-1][j]; } build_sym_luts(); if (!TT) TT = new TTEntry[TT_SIZE](); if (!s_dtm_cache) s_dtm_cache = new DTMCacheEntry[DTM_CACHE_SIZE](); for(int i=0; i<1000000; ++i) g_cancel_flags[i].store(false); } static size_t get_expected_size(int t) { if (t < 0 || t > 25) return 0; uint64_t expected_states = (uint64_t)C[25][t] * C[t][(t + 1) / 2]; return (expected_states * 2 + 7) / 8; } static int load_layer_internal(int t, const char* path) { int fd = open(path, O_RDONLY); if (fd < 0) return 0; struct stat sb; if (fstat(fd, &sb) == 0 && sb.st_size > 0) { if ((size_t)sb.st_size < get_expected_size(t)) { close(fd); return 0; } layers[t] = (uint8_t*)mmap(nullptr, (size_t)sb.st_size, PROT_READ, MAP_SHARED, fd, 0); if (layers[t] != MAP_FAILED) { layer_sizes[t] = sb.st_size; if (t < 10) madvise(layers[t], sb.st_size, MADV_WILLNEED); else madvise(layers[t], sb.st_size, MADV_RANDOM); close(fd); return 1; } } close(fd); return 0; } static uint64_t board_to_index(uint32_t xb, uint32_t ob) { int t = __builtin_popcount(xb | ob); int xc = __builtin_popcount(xb); uint64_t idx_occ = 0, idx_x = 0; uint32_t occ = xb | ob; int i_occ = 0, i_x = 0; while (occ) { int bit = CTZ(occ); idx_occ += C[bit][i_occ + 1]; if (xb & (1u << bit)) { idx_x += C[i_occ][i_x + 1]; i_x++; } i_occ++; occ &= occ - 1; } return idx_occ * C[t][xc] + idx_x; } static FORCE_INLINE int fetch_val_impl(int t, uint32_t xb, uint32_t ob) { if (UNLIKELY(t < 0 || t > 25 || !layers[t])) return 0; uint32_t cx, co; get_canonical(xb, ob, cx, co); uint64_t idx = board_to_index(cx, co); return (layers[t][idx >> 2] >> ((idx & 3) << 1)) & 3; } static FORCE_INLINE bool check_win(uint32_t b) { #pragma GCC unroll 28 for (int i = 0; i < 28; ++i) if ((b & WIN_MASKS[i]) == WIN_MASKS[i]) return true; return false; } // ============================================================ // DTM С ЗАЩИТОЙ ОТ КОЛЛИЗИЙ И УЛУЧШЕННЫМ СМЕШИВАНИЕМ // ============================================================ static int dtm_max(uint32_t xb, uint32_t ob, int t, int opp, int sid); static int dtm_min(uint32_t xb, uint32_t ob, int t, int p, int sid) { if (check_time(sid)) return DTM_INF; if (UNLIKELY(++t_dtm_nodes > t_dtm_node_limit)) return DTM_INF; for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) continue; if (check_win(p==1 ? (xb|(1u<= 24) return DTM_INF; uint64_t key = canonical_key(xb, ob) ^ ((uint64_t)(p & 1) << 51) ^ (0ULL << 52); uint64_t hash = key ^ (key >> 17) ^ (key >> 34); int idx = (int)(hash & DTM_CACHE_MASK); // ИСПРАВЛЕН БАГ 1: lock_idx вычисляется строго из пространства маски idx во избежание Data Race int lock_idx = (idx >> 11) & 0xFF; uint64_t current_epoch = g_dtm_epoch.load(std::memory_order_relaxed); { std::lock_guard lock(dtm_locks[lock_idx]); if (s_dtm_cache[idx].key == key && s_dtm_cache[idx].epoch == current_epoch) return s_dtm_cache[idx].dist; } int min_dist = DTM_INF; for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) continue; uint32_t nx = (p==1) ? (xb|(1u<= DTM_INF) ? DTM_INF : min_dist + 1; { std::lock_guard lock(dtm_locks[lock_idx]); s_dtm_cache[idx].key = key; s_dtm_cache[idx].dist = (int16_t)result; s_dtm_cache[idx].epoch = current_epoch; } return result; } static int dtm_max(uint32_t xb, uint32_t ob, int t, int opp, int sid) { if (check_time(sid)) return DTM_INF; if (UNLIKELY(++t_dtm_nodes > t_dtm_node_limit)) return DTM_INF; for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) continue; if (check_win(opp==1 ? (xb|(1u<= 24) return 0; uint64_t key = canonical_key(xb, ob) ^ ((uint64_t)(opp & 1) << 51) ^ (1ULL << 52); uint64_t hash = key ^ (key >> 17) ^ (key >> 34); int idx = (int)(hash & DTM_CACHE_MASK); // ИСПРАВЛЕН БАГ 1: lock_idx вычисляется строго из пространства маски idx int lock_idx = (idx >> 11) & 0xFF; uint64_t current_epoch = g_dtm_epoch.load(std::memory_order_relaxed); { std::lock_guard lock(dtm_locks[lock_idx]); if (s_dtm_cache[idx].key == key && s_dtm_cache[idx].epoch == current_epoch) return s_dtm_cache[idx].dist; } int max_dist = -1; for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) continue; uint32_t nx = (opp==1) ? (xb|(1u< max_dist) max_dist = dist; } int result = (max_dist == -1) ? 0 : max_dist + 1; { std::lock_guard lock(dtm_locks[lock_idx]); s_dtm_cache[idx].key = key; s_dtm_cache[idx].dist = (int16_t)result; s_dtm_cache[idx].epoch = current_epoch; } return result; } static int dtm_best_move(uint32_t xb, uint32_t ob, int t, int p, const int* wins, int wcnt, int sid) { g_dtm_epoch.fetch_add(1, std::memory_order_relaxed); for (int i=0; i= 25 || depth_left <= 0) return 0.0; uint64_t key = canonical_key(xb, ob); int tt_idx = (int)(key & TT_MASK); int lock_idx = (tt_idx >> 14) & 0xFF; // Согласовано с маской TT_SIZE_BITS=22 uint64_t current_epoch = g_epoch.load(std::memory_order_relaxed); { std::lock_guard lock(tt_locks[lock_idx]); if (TT[tt_idx].key == key && TT[tt_idx].epoch == current_epoch && TT[tt_idx].depth >= depth_left) return TT[tt_idx].score; } int total_moves = 0; double weight_sum = 0.0, future_sum = 0.0; uint32_t fm = (~(xb | ob)) & 0x1FFFFFF; while (fm) { int i = CTZ(fm); fm &= fm - 1; uint32_t ox = (opp == 1) ? (xb | (1u << i)) : xb; uint32_t oo = (opp == 2) ? (ob | (1u << i)) : ob; ++total_moves; int val = fetch_val_impl(t+1, ox, oo); if (val == 1) { t_dtm_nodes = 0; int old_limit = t_dtm_node_limit; t_dtm_node_limit = 200; int dtm_dist = dtm_min(ox, oo, t+1, p, sid); t_dtm_node_limit = old_limit; if (dtm_dist >= DTM_INF) dtm_dist = 5; weight_sum += 1.0 / (double)dtm_dist; } else if (val == 0 && depth_left > 1) { double best_our = 0.0; uint32_t our_fm = (~(ox | oo)) & 0x1FFFFFF; while (our_fm) { int j = CTZ(our_fm); our_fm &= our_fm - 1; uint32_t nx = (p==1) ? (ox|(1u< best_our) best_our = fut; if (sid >= 0 && sid < 1000000 && g_cancel_flags[sid].load(std::memory_order_relaxed)) break; } } future_sum += best_our; } if (sid >= 0 && sid < 1000000 && g_cancel_flags[sid].load(std::memory_order_relaxed)) break; } if (total_moves == 0) return 0.0; double result = (weight_sum + future_sum * 0.85) / (double)total_moves; if (sid >= 0 && sid < 1000000 && !g_cancel_flags[sid].load(std::memory_order_relaxed)) { std::lock_guard lock(tt_locks[lock_idx]); if (TT[tt_idx].epoch != current_epoch || TT[tt_idx].depth <= depth_left) { TT[tt_idx].key = key; TT[tt_idx].score = (float)result; TT[tt_idx].depth = (int16_t)depth_left; TT[tt_idx].epoch = current_epoch; } } return result; } // ============================================================ // ВЫБОР ЛУЧШЕГО ХОДА // ============================================================ static inline bool in_arr(const int* a, int n, int m) { for (int k = 0; k < n; ++k) if (a[k] == m) return true; return false; } static int get_best_move_internal(int t, uint32_t xb, uint32_t ob, int p, int ai_style, int sid) { t_start = steady_clock::now(); t_total_nodes = 0; if (sid >= 0 && sid < 1000000) g_cancel_flags[sid].store(false, std::memory_order_relaxed); const int opp = 3 - p; int wins[25], draws[25], loses[25]; int wcnt=0, dcnt=0, lcnt=0; for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) continue; uint32_t nx = (p==1) ? (xb|(1u< 0) { t_dtm_node_limit = 50000; return dtm_best_move(xb, ob, t, p, wins, wcnt, sid); } if (dcnt > 0) { // Удержание центра на ранних ходах if (t == 0) { int cands[25]; int c_cnt = 0; for (int i=0; i 0) return cands[rng() % c_cnt]; for (int i=0; i 0) return cands[rng() % c_cnt]; } else if (t < 4) { for (int i=0; i> ranked; ranked.reserve(dcnt); for (int i=0; i b.first; }); if (!ranked.empty()) { best_move = ranked[0].second; best_score = ranked[0].first; } for (int depth = 2; sid >= 0 && sid < 1000000 && !g_cancel_flags[sid].load(std::memory_order_relaxed) && depth <= 20; ++depth) { double iter_best = -1.0; int iter_move = best_move; for (auto& [prev_sc, m] : ranked) { if (g_cancel_flags[sid].load(std::memory_order_relaxed)) break; uint32_t nx = (p==1) ? (xb|(1u< iter_best) { iter_best = sc; iter_move = m; } } if (!g_cancel_flags[sid].load(std::memory_order_relaxed)) { best_move = iter_move; best_score = iter_best; std::partial_sort(ranked.begin(), ranked.begin() + 1, ranked.end(), [](const auto& a, const auto& b){ return a.first > b.first; }); } if (best_score >= 1.0 || t + depth * 2 >= 24) break; } return best_move; } if (lcnt > 0) { g_dtm_epoch.fetch_add(1, std::memory_order_relaxed); t_dtm_node_limit = 50000; int best_move = loses[0], max_path = -1; for (int i=0; i max_path) { max_path = path; best_move = m; } } return best_move; } return -1; } static void get_cell_colors_internal(int t, uint32_t xb, uint32_t ob, int p, int ai_style, int* out, int sid) { const int opp = 3 - p; if (ai_style == 2) { g_epoch.fetch_add(1, std::memory_order_relaxed); g_dtm_epoch.fetch_add(1, std::memory_order_relaxed); } t_start = steady_clock::now(); t_total_nodes = 0; if (sid >= 0 && sid < 1000000) g_cancel_flags[sid].store(false, std::memory_order_relaxed); for (int i = 0; i < 25; ++i) { if ((xb | ob) & (1u << i)) { out[i] = -1; continue; } uint32_t nx = (p==1) ? (xb|(1u< 0.0) ? 3 : 4; } else out[i] = 4; } } extern "C" { void init_engine() { precompute(); } int load_layer(int t, const char* path) { return load_layer_internal(t, path); } void unload_layers() { for (int i = 0; i < 26; ++i) { if (layers[i]) { munmap(layers[i], layer_sizes[i]); layers[i] = nullptr; } } } int fetch_val(int t, uint32_t xb, uint32_t ob) { return fetch_val_impl(t, xb, ob); } int get_best_move(int t, uint32_t xb, uint32_t ob, int p, int ai_style, int sid) { return get_best_move_internal(t, xb, ob, p, ai_style, sid); } void get_cell_colors(int t, uint32_t xb, uint32_t ob, int p, int ai_style, int* out_colors, int sid) { get_cell_colors_internal(t, xb, ob, p, ai_style, out_colors, sid); } void abort_search(int sid) { if (sid >= 0 && sid < 1000000) g_cancel_flags[sid].store(true, std::memory_order_relaxed); } int check_win_export(uint32_t mask) { return check_win(mask) ? 1 : 0; } }