Spaces:
Sleeping
Sleeping
| using namespace std::chrono; | |
| static inline int CTZ(uint32_t v) { | |
| int c = 0; if (!v) return 32; | |
| while (!(v & 1)) { ++c; v >>= 1; } return c; | |
| } | |
| // ============================================================ | |
| // КОНСТАНТЫ И ГЛОБАЛЬНЫЕ СТРУКТУРЫ | |
| // ============================================================ | |
| 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; | |
| 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; | |
| 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<uint64_t> g_epoch{1}; | |
| static std::atomic<uint64_t> g_dtm_epoch{1}; | |
| // Пул отмен расширен до 1 000 000 слотов | |
| static std::atomic<bool> 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<milliseconds>(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) { | |
| 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<<i)) : (ob|(1u<<i)))) return 1; | |
| } | |
| if (t >= 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<std::mutex> 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<<i)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<i)) : ob; | |
| if (fetch_val_impl(t+1, nx, no) != 2) continue; | |
| int dist = dtm_max(nx, no, t+1, 3-p, sid); | |
| if (dist < min_dist) { | |
| min_dist = dist; | |
| if (min_dist == 1) break; | |
| } | |
| } | |
| int result = (min_dist >= DTM_INF) ? DTM_INF : min_dist + 1; | |
| { | |
| std::lock_guard<std::mutex> 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<<i)) : (ob|(1u<<i)))) return 1; | |
| } | |
| if (t >= 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<std::mutex> 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<<i)) : xb; | |
| uint32_t no = (opp==2) ? (ob|(1u<<i)) : ob; | |
| if (fetch_val_impl(t+1, nx, no) != 1) continue; | |
| int dist = dtm_min(nx, no, t+1, 3-opp, sid); | |
| if (dist > max_dist) max_dist = dist; | |
| } | |
| int result = (max_dist == -1) ? 0 : max_dist + 1; | |
| { | |
| std::lock_guard<std::mutex> 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<wcnt; ++i) { | |
| int m = wins[i]; | |
| if (check_win(p==1 ? (xb|(1u<<m)) : (ob|(1u<<m)))) return m; | |
| } | |
| int best_move = wins[0]; | |
| int min_path = DTM_INF; | |
| for (int i=0; i<wcnt; ++i) { | |
| int m = wins[i]; | |
| t_dtm_nodes = 0; | |
| uint32_t nx = (p==1) ? (xb|(1u<<m)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<m)) : ob; | |
| int path = dtm_max(nx, no, t+1, 3-p, sid); | |
| if (path < min_path) { | |
| min_path = path; best_move = m; | |
| if (min_path <= 2) break; | |
| } | |
| } | |
| return best_move; | |
| } | |
| // ============================================================ | |
| // EXPECTIMAX С STRIPED LOCKS | |
| // ============================================================ | |
| static double trap_search(uint32_t xb, uint32_t ob, int t, int p, int opp, int depth_left, int sid) { | |
| if (check_time(sid)) return 0.0; | |
| if (t >= 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<std::mutex> 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<<j)) : ox; | |
| uint32_t no = (p==2) ? (oo|(1u<<j)) : oo; | |
| if (fetch_val_impl(t+2, nx, no) == 0) { | |
| double fut = trap_search(nx, no, t+2, p, opp, depth_left-1, sid); | |
| if (fut > 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<std::mutex> 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<<i)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<i)) : ob; | |
| int val = fetch_val_impl(t+1, nx, no); | |
| if (val == 2) wins[wcnt++] = i; | |
| else if (val == 0) draws[dcnt++] = i; | |
| else if (val == 1) loses[lcnt++] = i; | |
| } | |
| if (wcnt > 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<dcnt; ++i) if (in_arr(ADJACENT_CORNERS, 8, draws[i])) cands[c_cnt++] = draws[i]; | |
| if (c_cnt > 0) return cands[rng() % c_cnt]; | |
| for (int i=0; i<dcnt; ++i) if (in_arr(CORNERS, 4, draws[i])) cands[c_cnt++] = draws[i]; | |
| if (c_cnt > 0) return cands[rng() % c_cnt]; | |
| } | |
| else if (t < 4) { | |
| for (int i=0; i<dcnt; ++i) if (draws[i] == 12) return 12; | |
| } | |
| if (ai_style != 2) return draws[rng() % dcnt]; | |
| g_epoch.fetch_add(1, std::memory_order_relaxed); | |
| g_dtm_epoch.fetch_add(1, std::memory_order_relaxed); | |
| int best_move = draws[0]; | |
| double best_score = -1.0; | |
| std::vector<std::pair<double, int>> ranked; | |
| ranked.reserve(dcnt); | |
| for (int i=0; i<dcnt; ++i) { | |
| int m = draws[i]; | |
| uint32_t nx = (p==1) ? (xb|(1u<<m)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<m)) : ob; | |
| ranked.push_back({trap_search(nx, no, t+1, p, opp, 1, sid), m}); | |
| } | |
| std::partial_sort(ranked.begin(), ranked.begin() + 1, ranked.end(), | |
| [](const auto& a, const auto& b){ return a.first > 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<<m)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<m)) : ob; | |
| double sc = trap_search(nx, no, t+1, p, opp, depth, sid); | |
| prev_sc = sc; | |
| if (sc > 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<lcnt; ++i) { | |
| int m = loses[i]; | |
| t_dtm_nodes = 0; | |
| uint32_t nx = (p==1) ? (xb|(1u<<m)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<m)) : ob; | |
| int path = dtm_min(nx, no, t+1, opp, sid); | |
| if (path > 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<<i)) : xb; | |
| uint32_t no = (p==2) ? (ob|(1u<<i)) : ob; | |
| int val = fetch_val_impl(t+1, nx, no); | |
| if (val == 2) out[i] = 2; | |
| else if (val == 1) out[i] = 1; | |
| else if (ai_style == 2) { | |
| double sc = trap_search(nx, no, t+1, p, opp, 2, sid); | |
| out[i] = (sc > 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; } | |
| } | |