diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..9940ab179 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# EditorConfig helps editors and agents match project formatting defaults. +# C++ indentation rules are also documented in +# .github/instructions/cpp.instructions.md. + +root = true + +[*.{c,cc,cpp,h,hh,hpp}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 032939f5c..e6bd2c1f6 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -15,6 +15,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + # Fail fast on C++ indent drift (4 spaces, no tabs). Full-tree scan against + # the checkout — not Bazel runfiles — so we avoid per-package source filegroups. + # Unit helpers also run under `bazelisk test //python:cpp_indentation_test`. + - name: Check C++ indentation + run: python3 python/tests/cpp_indentation_test.py + # 2️⃣ Free up disk space (LLVM toolchain ~4 GB extracted; runners ship ~14 GB free) - name: Free disk space uses: jlumbroso/free-disk-space@v1.3.1 diff --git a/benchmarks/dds_replay_main.cpp b/benchmarks/dds_replay_main.cpp index e9684295c..575839003 100644 --- a/benchmarks/dds_replay_main.cpp +++ b/benchmarks/dds_replay_main.cpp @@ -41,324 +41,324 @@ using dds_replay::ReplayStats; struct Options { - std::string path; - std::vector threads; - std::vector purpose; - std::vector boards; - std::vector solutions; - int dds_mode = -1; - int repeat = 1; - int min_trick = 0; - int max_trick = 0; - int limit = 0; - bool warmup = false; - bool no_par = false; - bool no_verify = false; - bool tricks = false; - bool list = false; + std::string path; + std::vector threads; + std::vector purpose; + std::vector boards; + std::vector solutions; + int dds_mode = -1; + int repeat = 1; + int min_trick = 0; + int max_trick = 0; + int limit = 0; + bool warmup = false; + bool no_par = false; + bool no_verify = false; + bool tricks = false; + bool list = false; }; auto usage() -> int { - std::printf( - "Replay a recorded DDS workload as a benchmark.\n\n" - "Usage: dds_replay [options]\n\n" - " --threads N worker threads; repeat to sweep several (default: recorded)\n" - " --dds-mode N solver mode to replay with (default: recorded)\n" - " --repeat N run the workload N times, report the best\n" - " --warmup one untimed pass first\n" - " --purpose P only this purpose (bid/lead/play/claimcheck/par); repeatable\n" - " --board N only this board number; repeatable\n" - " --solutions N only calls with this solutions value; repeatable\n" - " --min-trick N / --max-trick N\n" - " --limit N stop after N calls\n" - " --no-par skip par calculations\n" - " --no-verify do not compare results against the recording\n" - " --tricks also break the report down by trick number\n" - " --list summarize the recording and exit\n"); - return 2; + std::printf( + "Replay a recorded DDS workload as a benchmark.\n\n" + "Usage: dds_replay [options]\n\n" + " --threads N worker threads; repeat to sweep several (default: recorded)\n" + " --dds-mode N solver mode to replay with (default: recorded)\n" + " --repeat N run the workload N times, report the best\n" + " --warmup one untimed pass first\n" + " --purpose P only this purpose (bid/lead/play/claimcheck/par); repeatable\n" + " --board N only this board number; repeatable\n" + " --solutions N only calls with this solutions value; repeatable\n" + " --min-trick N / --max-trick N\n" + " --limit N stop after N calls\n" + " --no-par skip par calculations\n" + " --no-verify do not compare results against the recording\n" + " --tricks also break the report down by trick number\n" + " --list summarize the recording and exit\n"); + return 2; } auto contains(const std::vector& v, int x) -> bool { - return std::find(v.begin(), v.end(), x) != v.end(); + return std::find(v.begin(), v.end(), x) != v.end(); } auto contains(const std::vector& v, const std::string& x) -> bool { - return std::find(v.begin(), v.end(), x) != v.end(); + return std::find(v.begin(), v.end(), x) != v.end(); } auto select(const Recording& rec, const Options& opt) -> std::vector { - std::vector out; - for (const Call& c : rec.calls) { - if (c.kind == Call::Kind::Par) { - // --purpose names the buckets the report uses, and par is one of them, - // so `--purpose bid` must exclude par rather than silently leave it in. - if (opt.no_par) - continue; - if (!opt.purpose.empty() && !contains(opt.purpose, "par")) - continue; - } else { - if (!opt.purpose.empty() && !contains(opt.purpose, c.purpose)) - continue; - if (opt.min_trick != 0 && c.trick < opt.min_trick) - continue; - if (opt.max_trick != 0 && c.trick > opt.max_trick) - continue; - if (!opt.solutions.empty() && !contains(opt.solutions, c.solutions)) - continue; + std::vector out; + for (const Call& c : rec.calls) { + if (c.kind == Call::Kind::Par) { + // --purpose names the buckets the report uses, and par is one of them, + // so `--purpose bid` must exclude par rather than silently leave it in. + if (opt.no_par) + continue; + if (!opt.purpose.empty() && !contains(opt.purpose, "par")) + continue; + } else { + if (!opt.purpose.empty() && !contains(opt.purpose, c.purpose)) + continue; + if (opt.min_trick != 0 && c.trick < opt.min_trick) + continue; + if (opt.max_trick != 0 && c.trick > opt.max_trick) + continue; + if (!opt.solutions.empty() && !contains(opt.solutions, c.solutions)) + continue; + } + if (!opt.boards.empty() && !contains(opt.boards, c.board)) + continue; + out.push_back(c); + if (opt.limit != 0 && static_cast(out.size()) >= opt.limit) + break; } - if (!opt.boards.empty() && !contains(opt.boards, c.board)) - continue; - out.push_back(c); - if (opt.limit != 0 && static_cast(out.size()) >= opt.limit) - break; - } - return out; + return out; } auto header() -> void { - std::printf("%-12s%8s%10s%10s%11s%11s%13s\n", - "", "calls", "boards", "seconds", "ms/call", "ms/board", - "rec ms/call"); + std::printf("%-12s%8s%10s%10s%11s%11s%13s\n", + "", "calls", "boards", "seconds", "ms/call", "ms/board", + "rec ms/call"); } auto row(const std::string& name, const Bucket& b) -> void { - const double ms_call = b.calls != 0 ? b.seconds * 1000 / b.calls : 0.0; - const double ms_board = b.boards != 0 - ? b.seconds * 1000 / static_cast(b.boards) : 0.0; - const double rec_call = b.calls != 0 ? b.recorded_ms / b.calls : 0.0; - std::printf("%-12s%8d%10lld%10.3f%11.2f%11.3f%13.2f\n", - name.c_str(), b.calls, b.boards, b.seconds, ms_call, ms_board, - rec_call); + const double ms_call = b.calls != 0 ? b.seconds * 1000 / b.calls : 0.0; + const double ms_board = b.boards != 0 + ? b.seconds * 1000 / static_cast(b.boards) : 0.0; + const double rec_call = b.calls != 0 ? b.recorded_ms / b.calls : 0.0; + std::printf("%-12s%8d%10lld%10.3f%11.2f%11.3f%13.2f\n", + name.c_str(), b.calls, b.boards, b.seconds, ms_call, ms_board, + rec_call); } auto print_list(const Recording& rec, const std::vector& calls, - const std::string& path) -> void + const std::string& path) -> void { - int n_solve = 0; - long long n_boards = 0; - double rec_s = 0.0; - std::set boards; - std::map by_purpose; - - for (const Call& c : calls) { - const long long b = (c.kind == Call::Kind::Solve) - ? static_cast(c.hands_pbn.size()) : 1; - if (c.kind == Call::Kind::Solve) { ++n_solve; n_boards += b; } - rec_s += c.recorded_ms / 1000.0; - boards.insert(c.board); - by_purpose[c.purpose.empty() ? "(none)" : c.purpose] - .add(b, 0.0, c.recorded_ms); - } - - std::printf("%s\n", path.c_str()); - std::printf(" recorded %s on %s with DDS %s mode %d %d threads\n", - rec.created.c_str(), rec.host.c_str(), rec.dds_version.c_str(), - rec.dds_mode, rec.threads); - std::printf(" %d deals, %d solve calls (%lld boards), %d par, " - "%.1f s of DDS when recorded\n", - static_cast(boards.size()), n_solve, n_boards, - static_cast(calls.size()) - n_solve, rec_s); - std::printf(" %-14s%8s%10s%10s\n", "purpose", "calls", "boards", "rec s"); - for (const auto& [purpose, b] : by_purpose) - std::printf(" %-14s%8d%10lld%10.3f\n", purpose.c_str(), b.calls, b.boards, - b.recorded_ms / 1000.0); + int n_solve = 0; + long long n_boards = 0; + double rec_s = 0.0; + std::set boards; + std::map by_purpose; + + for (const Call& c : calls) { + const long long b = (c.kind == Call::Kind::Solve) + ? static_cast(c.hands_pbn.size()) : 1; + if (c.kind == Call::Kind::Solve) { ++n_solve; n_boards += b; } + rec_s += c.recorded_ms / 1000.0; + boards.insert(c.board); + by_purpose[c.purpose.empty() ? "(none)" : c.purpose] + .add(b, 0.0, c.recorded_ms); + } + + std::printf("%s\n", path.c_str()); + std::printf(" recorded %s on %s with DDS %s mode %d %d threads\n", + rec.created.c_str(), rec.host.c_str(), rec.dds_version.c_str(), + rec.dds_mode, rec.threads); + std::printf(" %d deals, %d solve calls (%lld boards), %d par, " + "%.1f s of DDS when recorded\n", + static_cast(boards.size()), n_solve, n_boards, + static_cast(calls.size()) - n_solve, rec_s); + std::printf(" %-14s%8s%10s%10s\n", "purpose", "calls", "boards", "rec s"); + for (const auto& [purpose, b] : by_purpose) + std::printf(" %-14s%8d%10lld%10.3f\n", purpose.c_str(), b.calls, b.boards, + b.recorded_ms / 1000.0); } auto print_report(const Recording& rec, const std::vector& calls, - const ReplayStats& stats, const std::vector& runs, - int threads, int dds_mode, bool show_tricks) -> void + const ReplayStats& stats, const std::vector& runs, + int threads, int dds_mode, bool show_tricks) -> void { - int n_solve = 0; - long long n_boards = 0; - std::set boards; - for (const Call& c : calls) { - if (c.kind == Call::Kind::Solve) { - ++n_solve; - n_boards += static_cast(c.hands_pbn.size()); + int n_solve = 0; + long long n_boards = 0; + std::set boards; + for (const Call& c : calls) { + if (c.kind == Call::Kind::Solve) { + ++n_solve; + n_boards += static_cast(c.hands_pbn.size()); + } + boards.insert(c.board); } - boards.insert(c.board); - } - - std::printf("\n recorded : %s on %s (%s) DDS %s mode %d %d threads\n", - rec.created.c_str(), rec.host.c_str(), rec.platform.c_str(), - rec.dds_version.c_str(), rec.dds_mode, rec.threads); - std::printf(" replaying: mode %d %d threads\n", dds_mode, threads); - std::printf(" workload : %d solve calls (%lld boards) + %d par, over %d deals\n\n", - n_solve, n_boards, static_cast(calls.size()) - n_solve, - static_cast(boards.size())); - - header(); - std::printf("%s\n", std::string(75, '-').c_str()); - std::vector> ordered(stats.by_purpose.begin(), - stats.by_purpose.end()); - std::sort(ordered.begin(), ordered.end(), - [](const auto& a, const auto& b) { return a.second.seconds > b.second.seconds; }); - for (const auto& [purpose, b] : ordered) - row(purpose, b); - std::printf("%s\n", std::string(75, '-').c_str()); - row("TOTAL", stats.total); - - if (show_tricks) { - std::printf("\nby trick\n"); + + std::printf("\n recorded : %s on %s (%s) DDS %s mode %d %d threads\n", + rec.created.c_str(), rec.host.c_str(), rec.platform.c_str(), + rec.dds_version.c_str(), rec.dds_mode, rec.threads); + std::printf(" replaying: mode %d %d threads\n", dds_mode, threads); + std::printf(" workload : %d solve calls (%lld boards) + %d par, over %d deals\n\n", + n_solve, n_boards, static_cast(calls.size()) - n_solve, + static_cast(boards.size())); + header(); std::printf("%s\n", std::string(75, '-').c_str()); - for (const auto& [trick, b] : stats.by_trick) { - char label[16]; - if (trick == 0) - std::snprintf(label, sizeof(label), "par"); - else - std::snprintf(label, sizeof(label), "t%02d", trick); - row(label, b); + std::vector> ordered(stats.by_purpose.begin(), + stats.by_purpose.end()); + std::sort(ordered.begin(), ordered.end(), + [](const auto& a, const auto& b) { return a.second.seconds > b.second.seconds; }); + for (const auto& [purpose, b] : ordered) + row(purpose, b); + std::printf("%s\n", std::string(75, '-').c_str()); + row("TOTAL", stats.total); + + if (show_tricks) { + std::printf("\nby trick\n"); + header(); + std::printf("%s\n", std::string(75, '-').c_str()); + for (const auto& [trick, b] : stats.by_trick) { + char label[16]; + if (trick == 0) + std::snprintf(label, sizeof(label), "par"); + else + std::snprintf(label, sizeof(label), "t%02d", trick); + row(label, b); + } } - } - std::printf("\n"); - if (runs.size() > 1) { - std::string all; - for (double r : runs) - all += (all.empty() ? "" : " ") + + std::printf("\n"); + if (runs.size() > 1) { + std::string all; + for (double r : runs) + all += (all.empty() ? "" : " ") + std::string(std::to_string(r).substr(0, 6)); - std::printf("DDS time: %.3f s (best of %d: %s)\n", stats.total_seconds, - static_cast(runs.size()), all.c_str()); - } else { - std::printf("DDS time: %.3f s\n", stats.total_seconds); - } - - const double rec_s = stats.total.recorded_ms / 1000.0; - if (rec_s > 0.0) - std::printf("recorded : %.3f s (this run is %.2fx the recorded time)\n", - rec_s, stats.total_seconds / rec_s); - - if (!stats.mismatches.empty()) { - std::printf("VERIFY: %d of %d calls returned a different result than recorded\n", - static_cast(stats.mismatches.size()), - static_cast(calls.size())); - for (size_t i = 0; i < stats.mismatches.size() && i < 10; ++i) - std::printf(" seq %d (%s): %s\n", stats.mismatches[i].seq, - stats.mismatches[i].purpose.c_str(), - stats.mismatches[i].why.c_str()); - if (stats.mismatches.size() > 10) - std::printf(" ... and %d more\n", - static_cast(stats.mismatches.size()) - 10); - } else { - std::printf("VERIFY: all %d calls returned the recorded result\n", - static_cast(calls.size())); - } + std::printf("DDS time: %.3f s (best of %d: %s)\n", stats.total_seconds, + static_cast(runs.size()), all.c_str()); + } else { + std::printf("DDS time: %.3f s\n", stats.total_seconds); + } + + const double rec_s = stats.total.recorded_ms / 1000.0; + if (rec_s > 0.0) + std::printf("recorded : %.3f s (this run is %.2fx the recorded time)\n", + rec_s, stats.total_seconds / rec_s); + + if (!stats.mismatches.empty()) { + std::printf("VERIFY: %d of %d calls returned a different result than recorded\n", + static_cast(stats.mismatches.size()), + static_cast(calls.size())); + for (size_t i = 0; i < stats.mismatches.size() && i < 10; ++i) + std::printf(" seq %d (%s): %s\n", stats.mismatches[i].seq, + stats.mismatches[i].purpose.c_str(), + stats.mismatches[i].why.c_str()); + if (stats.mismatches.size() > 10) + std::printf(" ... and %d more\n", + static_cast(stats.mismatches.size()) - 10); + } else { + std::printf("VERIFY: all %d calls returned the recorded result\n", + static_cast(calls.size())); + } } } // namespace auto main(int argc, char* argv[]) -> int { - Options opt; - bool missing_value = false; - for (int i = 1; i < argc; ++i) { - const std::string a = argv[i]; - // A numeric option with no following value is a usage error, not a silent - // no-op -- otherwise a typo like `--repeat` at the end of the line just runs - // with the default and looks like it worked. - auto next_int = [&](int& dst) { - if (i + 1 < argc) - dst = std::atoi(argv[++i]); - else { - std::fprintf(stderr, "%s requires a value\n", a.c_str()); - missing_value = true; - } - }; - if (a == "--threads" && i + 1 < argc) opt.threads.push_back(std::atoi(argv[++i])); - else if (a == "--purpose" && i + 1 < argc) opt.purpose.emplace_back(argv[++i]); - else if (a == "--board" && i + 1 < argc) opt.boards.push_back(std::atoi(argv[++i])); - else if (a == "--solutions" && i + 1 < argc) opt.solutions.push_back(std::atoi(argv[++i])); - else if (a == "--dds-mode") next_int(opt.dds_mode); - else if (a == "--repeat") next_int(opt.repeat); - else if (a == "--min-trick") next_int(opt.min_trick); - else if (a == "--max-trick") next_int(opt.max_trick); - else if (a == "--limit") next_int(opt.limit); - else if (a == "--warmup") opt.warmup = true; - else if (a == "--no-par") opt.no_par = true; - else if (a == "--no-verify") opt.no_verify = true; - else if (a == "--tricks") opt.tricks = true; - else if (a == "--list") opt.list = true; - else if (a == "-h" || a == "--help") return usage(); - else if (!a.empty() && a[0] == '-') { std::fprintf(stderr, "unknown option %s\n", a.c_str()); return usage(); } - else if (opt.path.empty()) opt.path = a; - else { std::fprintf(stderr, "unexpected argument %s\n", a.c_str()); return usage(); } - - if (missing_value) - return usage(); - } - - if (opt.path.empty()) { - // Default to the workload committed alongside this benchmark, so - // `bazel run //benchmarks:dds_replay` works with no arguments. - opt.path = dds_replay::find_runfile( - "_main/benchmarks/testdata/dds-camrose-1-32.jsonl", - argc > 0 ? argv[0] : ""); + Options opt; + bool missing_value = false; + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + // A numeric option with no following value is a usage error, not a silent + // no-op -- otherwise a typo like `--repeat` at the end of the line just runs + // with the default and looks like it worked. + auto next_int = [&](int& dst) { + if (i + 1 < argc) + dst = std::atoi(argv[++i]); + else { + std::fprintf(stderr, "%s requires a value\n", a.c_str()); + missing_value = true; + } + }; + if (a == "--threads" && i + 1 < argc) opt.threads.push_back(std::atoi(argv[++i])); + else if (a == "--purpose" && i + 1 < argc) opt.purpose.emplace_back(argv[++i]); + else if (a == "--board" && i + 1 < argc) opt.boards.push_back(std::atoi(argv[++i])); + else if (a == "--solutions" && i + 1 < argc) opt.solutions.push_back(std::atoi(argv[++i])); + else if (a == "--dds-mode") next_int(opt.dds_mode); + else if (a == "--repeat") next_int(opt.repeat); + else if (a == "--min-trick") next_int(opt.min_trick); + else if (a == "--max-trick") next_int(opt.max_trick); + else if (a == "--limit") next_int(opt.limit); + else if (a == "--warmup") opt.warmup = true; + else if (a == "--no-par") opt.no_par = true; + else if (a == "--no-verify") opt.no_verify = true; + else if (a == "--tricks") opt.tricks = true; + else if (a == "--list") opt.list = true; + else if (a == "-h" || a == "--help") return usage(); + else if (!a.empty() && a[0] == '-') { std::fprintf(stderr, "unknown option %s\n", a.c_str()); return usage(); } + else if (opt.path.empty()) opt.path = a; + else { std::fprintf(stderr, "unexpected argument %s\n", a.c_str()); return usage(); } + + if (missing_value) + return usage(); + } + if (opt.path.empty()) { - std::fprintf(stderr, "no recording given, and the bundled one was not " + // Default to the workload committed alongside this benchmark, so + // `bazel run //benchmarks:dds_replay` works with no arguments. + opt.path = dds_replay::find_runfile( + "_main/benchmarks/testdata/dds-camrose-1-32.jsonl", + argc > 0 ? argv[0] : ""); + if (opt.path.empty()) { + std::fprintf(stderr, "no recording given, and the bundled one was not " "found in the runfiles\n"); - return usage(); + return usage(); + } + } + + Recording rec; + std::string error; + if (!dds_replay::load_recording(opt.path, rec, error)) { + std::fprintf(stderr, "%s\n", error.c_str()); + return 1; + } + + const std::vector calls = select(rec, opt); + if (calls.empty()) { + std::fprintf(stderr, "no calls left after filtering\n"); + return 1; } - } - - Recording rec; - std::string error; - if (!dds_replay::load_recording(opt.path, rec, error)) { - std::fprintf(stderr, "%s\n", error.c_str()); - return 1; - } - - const std::vector calls = select(rec, opt); - if (calls.empty()) { - std::fprintf(stderr, "no calls left after filtering\n"); - return 1; - } - - if (opt.list) { - print_list(rec, calls, opt.path); - return 0; - } - - const int dds_mode = (opt.dds_mode >= 0) ? opt.dds_mode : rec.dds_mode; - std::vector thread_counts = opt.threads; - if (thread_counts.empty()) { - int t = rec.threads; - if (t <= 0) - t = static_cast(std::thread::hardware_concurrency()); - thread_counts.push_back(std::max(1, t)); - } - - int exit_code = 0; - for (const int threads : thread_counts) { - ReplayEngine engine(std::max(1, threads), dds_mode); - - if (opt.warmup) - (void) engine.run(calls, /*verify=*/false); - - std::vector runs; - ReplayStats best; - for (int r = 0; r < std::max(1, opt.repeat); ++r) { - ReplayStats stats = engine.run(calls, !opt.no_verify); - runs.push_back(stats.total_seconds); - if (r == 0 || stats.total_seconds < best.total_seconds) - best = std::move(stats); + + if (opt.list) { + print_list(rec, calls, opt.path); + return 0; } - std::printf("\n%s\n", std::string(75, '=').c_str()); - std::printf("DDS replay - %s\n", opt.path.c_str()); - std::printf("%s\n", std::string(75, '=').c_str()); - print_report(rec, calls, best, runs, std::max(1, threads), dds_mode, + const int dds_mode = (opt.dds_mode >= 0) ? opt.dds_mode : rec.dds_mode; + std::vector thread_counts = opt.threads; + if (thread_counts.empty()) { + int t = rec.threads; + if (t <= 0) + t = static_cast(std::thread::hardware_concurrency()); + thread_counts.push_back(std::max(1, t)); + } + + int exit_code = 0; + for (const int threads : thread_counts) { + ReplayEngine engine(std::max(1, threads), dds_mode); + + if (opt.warmup) + (void) engine.run(calls, /*verify=*/false); + + std::vector runs; + ReplayStats best; + for (int r = 0; r < std::max(1, opt.repeat); ++r) { + ReplayStats stats = engine.run(calls, !opt.no_verify); + runs.push_back(stats.total_seconds); + if (r == 0 || stats.total_seconds < best.total_seconds) + best = std::move(stats); + } + + std::printf("\n%s\n", std::string(75, '=').c_str()); + std::printf("DDS replay - %s\n", opt.path.c_str()); + std::printf("%s\n", std::string(75, '=').c_str()); + print_report(rec, calls, best, runs, std::max(1, threads), dds_mode, opt.tricks); - if (!best.mismatches.empty()) - exit_code = 1; - } + if (!best.mismatches.empty()) + exit_code = 1; + } - return exit_code; + return exit_code; } diff --git a/benchmarks/recording.cpp b/benchmarks/recording.cpp index 65c210aa6..6b0e67891 100644 --- a/benchmarks/recording.cpp +++ b/benchmarks/recording.cpp @@ -26,177 +26,177 @@ namespace { struct Parser { - const std::string& s; - size_t i = 0; - std::string error; + const std::string& s; + size_t i = 0; + std::string error; - explicit Parser(const std::string& text) : s(text) {} + explicit Parser(const std::string& text) : s(text) {} - auto skip_ws() -> void - { - while (i < s.size() && + auto skip_ws() -> void + { + while (i < s.size() && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) - ++i; - } - - auto fail(const char* what) -> bool - { - if (error.empty()) - error = std::string(what) + " at offset " + std::to_string(i); - return false; - } - - auto literal(const char* lit) -> bool - { - const size_t n = std::char_traits::length(lit); - if (s.compare(i, n, lit) != 0) - return fail("bad literal"); - i += n; - return true; - } - - auto parse_string(std::string& out) -> bool - { - if (i >= s.size() || s[i] != '"') - return fail("expected string"); - ++i; - out.clear(); - while (i < s.size() && s[i] != '"') { - char c = s[i++]; - if (c != '\\') { - out += c; - continue; - } - if (i >= s.size()) - return fail("truncated escape"); - const char e = s[i++]; - switch (e) { - case 'n': out += '\n'; break; - case 't': out += '\t'; break; - case 'r': out += '\r'; break; - case 'b': out += '\b'; break; - case 'f': out += '\f'; break; - case '/': out += '/'; break; - case '"': out += '"'; break; - case '\\': out += '\\'; break; - case 'u': { - if (i + 4 > s.size()) - return fail("truncated \\u escape"); - const unsigned cp = static_cast( - std::strtoul(s.substr(i, 4).c_str(), nullptr, 16)); - i += 4; - // The recorder only ever writes ASCII (PBN strings and short tags), - // so a minimal UTF-8 encoding of the BMP range is enough here. - if (cp < 0x80) { - out += static_cast(cp); - } else if (cp < 0x800) { - out += static_cast(0xC0 | (cp >> 6)); - out += static_cast(0x80 | (cp & 0x3F)); - } else { - out += static_cast(0xE0 | (cp >> 12)); - out += static_cast(0x80 | ((cp >> 6) & 0x3F)); - out += static_cast(0x80 | (cp & 0x3F)); - } - break; - } - default: return fail("unknown escape"); - } + ++i; } - if (i >= s.size()) - return fail("unterminated string"); - ++i; // closing quote - return true; - } - - auto parse_value(JsonValue& v) -> bool - { - skip_ws(); - if (i >= s.size()) - return fail("unexpected end"); - - const char c = s[i]; - if (c == '{') { - ++i; - v.kind = JsonValue::Kind::Object; - skip_ws(); - if (i < s.size() && s[i] == '}') { ++i; return true; } - for (;;) { - skip_ws(); - std::string key; - if (!parse_string(key)) - return false; - skip_ws(); - if (i >= s.size() || s[i] != ':') - return fail("expected ':'"); - ++i; - JsonValue child; - if (!parse_value(child)) - return false; - v.members.emplace_back(std::move(key), std::move(child)); - skip_ws(); - if (i < s.size() && s[i] == ',') { ++i; continue; } - if (i < s.size() && s[i] == '}') { ++i; return true; } - return fail("expected ',' or '}'"); - } + + auto fail(const char* what) -> bool + { + if (error.empty()) + error = std::string(what) + " at offset " + std::to_string(i); + return false; } - if (c == '[') { - ++i; - v.kind = JsonValue::Kind::Array; - skip_ws(); - if (i < s.size() && s[i] == ']') { ++i; return true; } - for (;;) { - JsonValue child; - if (!parse_value(child)) - return false; - v.items.push_back(std::move(child)); - skip_ws(); - if (i < s.size() && s[i] == ',') { ++i; continue; } - if (i < s.size() && s[i] == ']') { ++i; return true; } - return fail("expected ',' or ']'"); - } + + auto literal(const char* lit) -> bool + { + const size_t n = std::char_traits::length(lit); + if (s.compare(i, n, lit) != 0) + return fail("bad literal"); + i += n; + return true; } - if (c == '"') { - v.kind = JsonValue::Kind::String; - return parse_string(v.text); + + auto parse_string(std::string& out) -> bool + { + if (i >= s.size() || s[i] != '"') + return fail("expected string"); + ++i; + out.clear(); + while (i < s.size() && s[i] != '"') { + char c = s[i++]; + if (c != '\\') { + out += c; + continue; + } + if (i >= s.size()) + return fail("truncated escape"); + const char e = s[i++]; + switch (e) { + case 'n': out += '\n'; break; + case 't': out += '\t'; break; + case 'r': out += '\r'; break; + case 'b': out += '\b'; break; + case 'f': out += '\f'; break; + case '/': out += '/'; break; + case '"': out += '"'; break; + case '\\': out += '\\'; break; + case 'u': { + if (i + 4 > s.size()) + return fail("truncated \\u escape"); + const unsigned cp = static_cast( + std::strtoul(s.substr(i, 4).c_str(), nullptr, 16)); + i += 4; + // The recorder only ever writes ASCII (PBN strings and short tags), + // so a minimal UTF-8 encoding of the BMP range is enough here. + if (cp < 0x80) { + out += static_cast(cp); + } else if (cp < 0x800) { + out += static_cast(0xC0 | (cp >> 6)); + out += static_cast(0x80 | (cp & 0x3F)); + } else { + out += static_cast(0xE0 | (cp >> 12)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + } + break; + } + default: return fail("unknown escape"); + } + } + if (i >= s.size()) + return fail("unterminated string"); + ++i; // closing quote + return true; } - if (c == 't') { v.kind = JsonValue::Kind::Bool; v.boolean = true; return literal("true"); } - if (c == 'f') { v.kind = JsonValue::Kind::Bool; v.boolean = false; return literal("false"); } - if (c == 'n') { v.kind = JsonValue::Kind::Null; return literal("null"); } - - // number - const size_t start = i; - if (i < s.size() && (s[i] == '-' || s[i] == '+')) ++i; - while (i < s.size() && + + auto parse_value(JsonValue& v) -> bool + { + skip_ws(); + if (i >= s.size()) + return fail("unexpected end"); + + const char c = s[i]; + if (c == '{') { + ++i; + v.kind = JsonValue::Kind::Object; + skip_ws(); + if (i < s.size() && s[i] == '}') { ++i; return true; } + for (;;) { + skip_ws(); + std::string key; + if (!parse_string(key)) + return false; + skip_ws(); + if (i >= s.size() || s[i] != ':') + return fail("expected ':'"); + ++i; + JsonValue child; + if (!parse_value(child)) + return false; + v.members.emplace_back(std::move(key), std::move(child)); + skip_ws(); + if (i < s.size() && s[i] == ',') { ++i; continue; } + if (i < s.size() && s[i] == '}') { ++i; return true; } + return fail("expected ',' or '}'"); + } + } + if (c == '[') { + ++i; + v.kind = JsonValue::Kind::Array; + skip_ws(); + if (i < s.size() && s[i] == ']') { ++i; return true; } + for (;;) { + JsonValue child; + if (!parse_value(child)) + return false; + v.items.push_back(std::move(child)); + skip_ws(); + if (i < s.size() && s[i] == ',') { ++i; continue; } + if (i < s.size() && s[i] == ']') { ++i; return true; } + return fail("expected ',' or ']'"); + } + } + if (c == '"') { + v.kind = JsonValue::Kind::String; + return parse_string(v.text); + } + if (c == 't') { v.kind = JsonValue::Kind::Bool; v.boolean = true; return literal("true"); } + if (c == 'f') { v.kind = JsonValue::Kind::Bool; v.boolean = false; return literal("false"); } + if (c == 'n') { v.kind = JsonValue::Kind::Null; return literal("null"); } + + // number + const size_t start = i; + if (i < s.size() && (s[i] == '-' || s[i] == '+')) ++i; + while (i < s.size() && ((s[i] >= '0' && s[i] <= '9') || s[i] == '.' || - s[i] == 'e' || s[i] == 'E' || s[i] == '-' || s[i] == '+')) - ++i; - if (i == start) - return fail("expected value"); - v.kind = JsonValue::Kind::Number; - v.number = std::strtod(s.substr(start, i - start).c_str(), nullptr); - return true; - } + s[i] == 'e' || s[i] == 'E' || s[i] == '-' || s[i] == '+')) + ++i; + if (i == start) + return fail("expected value"); + v.kind = JsonValue::Kind::Number; + v.number = std::strtod(s.substr(start, i - start).c_str(), nullptr); + return true; + } }; auto to_int_vector(const JsonValue* v) -> std::vector { - std::vector out; - if (v == nullptr || v->kind != JsonValue::Kind::Array) + std::vector out; + if (v == nullptr || v->kind != JsonValue::Kind::Array) + return out; + for (const auto& item : v->items) + if (item.kind == JsonValue::Kind::Number) + out.push_back(static_cast(item.number)); return out; - for (const auto& item : v->items) - if (item.kind == JsonValue::Kind::Number) - out.push_back(static_cast(item.number)); - return out; } auto to_result_map(const JsonValue* v) -> ResultMap { - ResultMap out; - if (v == nullptr || v->kind != JsonValue::Kind::Object) + ResultMap out; + if (v == nullptr || v->kind != JsonValue::Kind::Object) + return out; + for (const auto& [key, values] : v->members) + out[key] = to_int_vector(&values); return out; - for (const auto& [key, values] : v->members) - out[key] = to_int_vector(&values); - return out; } } // namespace @@ -205,183 +205,183 @@ namespace { auto readable(const std::string& path) -> bool { - std::ifstream f(path); - return static_cast(f); + std::ifstream f(path); + return static_cast(f); } } // namespace auto find_runfile(const std::string& logical, const std::string& argv0) - -> std::string + -> std::string { - // Preferred: the manifest, which is authoritative and is the only mechanism - // available on Windows. - std::vector manifests; - if (const char* m = std::getenv("RUNFILES_MANIFEST_FILE")) - manifests.emplace_back(m); - for (const char* var : {"TEST_SRCDIR", "RUNFILES_DIR"}) - if (const char* root = std::getenv(var)) - manifests.push_back(std::string(root) + "/MANIFEST"); - // `bazel run` leaves the manifest beside the binary instead. - if (!argv0.empty()) { - manifests.push_back(argv0 + ".runfiles/MANIFEST"); - manifests.push_back(argv0 + ".runfiles_manifest"); - manifests.push_back(argv0 + ".exe.runfiles/MANIFEST"); - manifests.push_back(argv0 + ".exe.runfiles_manifest"); - } - - for (const std::string& manifest : manifests) { - std::ifstream in(manifest); - if (!in) - continue; - std::string line; - while (std::getline(in, line)) { - const size_t sep = line.find(' '); - if (sep == std::string::npos || line.compare(0, sep, logical) != 0) - continue; - std::string real = line.substr(sep + 1); - while (!real.empty() && (real.back() == '\r' || real.back() == '\n')) - real.pop_back(); - if (readable(real)) - return real; + // Preferred: the manifest, which is authoritative and is the only mechanism + // available on Windows. + std::vector manifests; + if (const char* m = std::getenv("RUNFILES_MANIFEST_FILE")) + manifests.emplace_back(m); + for (const char* var : {"TEST_SRCDIR", "RUNFILES_DIR"}) + if (const char* root = std::getenv(var)) + manifests.push_back(std::string(root) + "/MANIFEST"); + // `bazel run` leaves the manifest beside the binary instead. + if (!argv0.empty()) { + manifests.push_back(argv0 + ".runfiles/MANIFEST"); + manifests.push_back(argv0 + ".runfiles_manifest"); + manifests.push_back(argv0 + ".exe.runfiles/MANIFEST"); + manifests.push_back(argv0 + ".exe.runfiles_manifest"); } - } - - // Materialized runfiles tree (Linux/macOS), or a run from the workspace root. - std::vector candidates; - for (const char* var : {"TEST_SRCDIR", "RUNFILES_DIR"}) - if (const char* root = std::getenv(var)) - candidates.push_back(std::string(root) + "/" + logical); - if (!argv0.empty()) - candidates.push_back(argv0 + ".runfiles/" + logical); - candidates.push_back(logical); - // Same path with the leading repository component removed. - if (const size_t slash = logical.find('/'); slash != std::string::npos) - candidates.push_back(logical.substr(slash + 1)); - - for (const std::string& p : candidates) - if (readable(p)) - return p; - return {}; + + for (const std::string& manifest : manifests) { + std::ifstream in(manifest); + if (!in) + continue; + std::string line; + while (std::getline(in, line)) { + const size_t sep = line.find(' '); + if (sep == std::string::npos || line.compare(0, sep, logical) != 0) + continue; + std::string real = line.substr(sep + 1); + while (!real.empty() && (real.back() == '\r' || real.back() == '\n')) + real.pop_back(); + if (readable(real)) + return real; + } + } + + // Materialized runfiles tree (Linux/macOS), or a run from the workspace root. + std::vector candidates; + for (const char* var : {"TEST_SRCDIR", "RUNFILES_DIR"}) + if (const char* root = std::getenv(var)) + candidates.push_back(std::string(root) + "/" + logical); + if (!argv0.empty()) + candidates.push_back(argv0 + ".runfiles/" + logical); + candidates.push_back(logical); + // Same path with the leading repository component removed. + if (const size_t slash = logical.find('/'); slash != std::string::npos) + candidates.push_back(logical.substr(slash + 1)); + + for (const std::string& p : candidates) + if (readable(p)) + return p; + return {}; } auto parse_json(const std::string& text, JsonValue& out, std::string& error) - -> bool + -> bool { - Parser p(text); - if (!p.parse_value(out)) { - error = p.error; - return false; - } - // A recording line is exactly one JSON value; anything after it (past - // whitespace) means the line is malformed, not a value we should accept. - p.skip_ws(); - if (p.i != text.size()) { - p.fail("trailing characters after JSON value"); - error = p.error; - return false; - } - return true; + Parser p(text); + if (!p.parse_value(out)) { + error = p.error; + return false; + } + // A recording line is exactly one JSON value; anything after it (past + // whitespace) means the line is malformed, not a value we should accept. + p.skip_ws(); + if (p.i != text.size()) { + p.fail("trailing characters after JSON value"); + error = p.error; + return false; + } + return true; } auto load_recording(const std::string& path, Recording& out, std::string& error) - -> bool + -> bool { - std::ifstream in(path); - if (!in) { - error = "cannot open " + path; - return false; - } - - // Start from a clean slate so reloading into a reused Recording (common in a - // CLI or benchmark loop) does not accumulate calls from a previous load. Only - // after the file opened, so a failed open leaves the caller's value untouched. - out = Recording{}; - - std::set boards; - std::string line; - size_t line_no = 0; - - while (std::getline(in, line)) { - ++line_no; - // Tolerate CRLF recordings on POSIX. - while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) - line.pop_back(); - if (line.empty()) - continue; - - JsonValue rec; - std::string parse_error; - if (!parse_json(line, rec, parse_error)) { - std::fprintf(stderr, "%s:%zu: skipping unparseable line (%s)\n", - path.c_str(), line_no, parse_error.c_str()); - continue; - } - if (rec.kind != JsonValue::Kind::Object) - continue; - - const std::string kind = rec.string_or("t", ""); - - if (kind == "meta") { - out.created = rec.string_or("created", "?"); - out.host = rec.string_or("host", "?"); - out.platform = rec.string_or("platform", "?"); - out.dds_version = rec.string_or("dds_version", "?"); - out.dds_mode = rec.int_or("dds_mode", 1); - out.threads = rec.int_or("threads", 0); - continue; + std::ifstream in(path); + if (!in) { + error = "cannot open " + path; + return false; } - if (kind == "board") - continue; // a marker; the calls carry their own board number - - Call call; - call.seq = rec.int_or("seq", 0); - call.board = rec.int_or("board", 0); - call.recorded_ms = rec.double_or("ms", 0.0); - - if (kind == "solve") { - call.kind = Call::Kind::Solve; - call.purpose = rec.string_or("purpose", ""); - call.trick = rec.int_or("trick", 0); - call.strain_i = rec.int_or("strain_i", 0); - call.leader_i = rec.int_or("leader_i", 0); - call.solutions = rec.int_or("solutions", 1); - call.current_trick = to_int_vector(rec.find("current_trick")); - - const JsonValue* hands = rec.find("hands_pbn"); - if (hands == nullptr || hands->kind != JsonValue::Kind::Array || - hands->items.empty()) - continue; // nothing to solve - for (const auto& h : hands->items) - if (h.kind == JsonValue::Kind::String) - call.hands_pbn.push_back(h.text); - - call.result = to_result_map(rec.find("result")); - } else if (kind == "par") { - call.kind = Call::Kind::Par; - call.purpose = "par"; - call.trick = 0; - call.hand = rec.string_or("hand", ""); - const JsonValue* v = rec.find("vuln"); - if (v != nullptr && v->kind == JsonValue::Kind::Array) - for (const auto& b : v->items) - call.vuln.push_back(b.kind == JsonValue::Kind::Bool ? b.boolean + + // Start from a clean slate so reloading into a reused Recording (common in a + // CLI or benchmark loop) does not accumulate calls from a previous load. Only + // after the file opened, so a failed open leaves the caller's value untouched. + out = Recording{}; + + std::set boards; + std::string line; + size_t line_no = 0; + + while (std::getline(in, line)) { + ++line_no; + // Tolerate CRLF recordings on POSIX. + while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) + line.pop_back(); + if (line.empty()) + continue; + + JsonValue rec; + std::string parse_error; + if (!parse_json(line, rec, parse_error)) { + std::fprintf(stderr, "%s:%zu: skipping unparseable line (%s)\n", + path.c_str(), line_no, parse_error.c_str()); + continue; + } + if (rec.kind != JsonValue::Kind::Object) + continue; + + const std::string kind = rec.string_or("t", ""); + + if (kind == "meta") { + out.created = rec.string_or("created", "?"); + out.host = rec.string_or("host", "?"); + out.platform = rec.string_or("platform", "?"); + out.dds_version = rec.string_or("dds_version", "?"); + out.dds_mode = rec.int_or("dds_mode", 1); + out.threads = rec.int_or("threads", 0); + continue; + } + if (kind == "board") + continue; // a marker; the calls carry their own board number + + Call call; + call.seq = rec.int_or("seq", 0); + call.board = rec.int_or("board", 0); + call.recorded_ms = rec.double_or("ms", 0.0); + + if (kind == "solve") { + call.kind = Call::Kind::Solve; + call.purpose = rec.string_or("purpose", ""); + call.trick = rec.int_or("trick", 0); + call.strain_i = rec.int_or("strain_i", 0); + call.leader_i = rec.int_or("leader_i", 0); + call.solutions = rec.int_or("solutions", 1); + call.current_trick = to_int_vector(rec.find("current_trick")); + + const JsonValue* hands = rec.find("hands_pbn"); + if (hands == nullptr || hands->kind != JsonValue::Kind::Array || + hands->items.empty()) + continue; // nothing to solve + for (const auto& h : hands->items) + if (h.kind == JsonValue::Kind::String) + call.hands_pbn.push_back(h.text); + + call.result = to_result_map(rec.find("result")); + } else if (kind == "par") { + call.kind = Call::Kind::Par; + call.purpose = "par"; + call.trick = 0; + call.hand = rec.string_or("hand", ""); + const JsonValue* v = rec.find("vuln"); + if (v != nullptr && v->kind == JsonValue::Kind::Array) + for (const auto& b : v->items) + call.vuln.push_back(b.kind == JsonValue::Kind::Bool ? b.boolean : b.number != 0); - const JsonValue* r = rec.find("result"); - call.par_result = (r != nullptr && r->kind == JsonValue::Kind::Number) - ? static_cast(r->number) : 0; - if (call.hand.empty()) - continue; - } else { - continue; // unknown record type - } + const JsonValue* r = rec.find("result"); + call.par_result = (r != nullptr && r->kind == JsonValue::Kind::Number) + ? static_cast(r->number) : 0; + if (call.hand.empty()) + continue; + } else { + continue; // unknown record type + } - boards.insert(call.board); - out.calls.push_back(std::move(call)); - } + boards.insert(call.board); + out.calls.push_back(std::move(call)); + } - out.deals = static_cast(boards.size()); - return true; + out.deals = static_cast(boards.size()); + return true; } } // namespace dds_replay diff --git a/benchmarks/recording.hpp b/benchmarks/recording.hpp index 28a6c6157..62b4aa41e 100644 --- a/benchmarks/recording.hpp +++ b/benchmarks/recording.hpp @@ -30,47 +30,47 @@ namespace dds_replay { struct JsonValue { - enum class Kind { Null, Bool, Number, String, Array, Object }; - - Kind kind = Kind::Null; - bool boolean = false; - double number = 0.0; - std::string text; - std::vector items; - std::vector> members; - - auto find(const std::string& key) const -> const JsonValue* - { - for (const auto& [k, v] : members) - if (k == key) - return &v; - return nullptr; - } - - auto int_or(const std::string& key, int fallback) const -> int - { - const JsonValue* v = find(key); - return (v != nullptr && v->kind == Kind::Number) - ? static_cast(v->number) : fallback; - } - - auto double_or(const std::string& key, double fallback) const -> double - { - const JsonValue* v = find(key); - return (v != nullptr && v->kind == Kind::Number) ? v->number : fallback; - } - - auto string_or(const std::string& key, const std::string& fallback) const - -> std::string - { - const JsonValue* v = find(key); - return (v != nullptr && v->kind == Kind::String) ? v->text : fallback; - } + enum class Kind { Null, Bool, Number, String, Array, Object }; + + Kind kind = Kind::Null; + bool boolean = false; + double number = 0.0; + std::string text; + std::vector items; + std::vector> members; + + auto find(const std::string& key) const -> const JsonValue* + { + for (const auto& [k, v] : members) + if (k == key) + return &v; + return nullptr; + } + + auto int_or(const std::string& key, int fallback) const -> int + { + const JsonValue* v = find(key); + return (v != nullptr && v->kind == Kind::Number) + ? static_cast(v->number) : fallback; + } + + auto double_or(const std::string& key, double fallback) const -> double + { + const JsonValue* v = find(key); + return (v != nullptr && v->kind == Kind::Number) ? v->number : fallback; + } + + auto string_or(const std::string& key, const std::string& fallback) const + -> std::string + { + const JsonValue* v = find(key); + return (v != nullptr && v->kind == Kind::String) ? v->text : fallback; + } }; // Parse one JSON document. Returns false (and sets `error`) on malformed input. auto parse_json(const std::string& text, JsonValue& out, std::string& error) - -> bool; + -> bool; // --------------------------------------------------------------------------- // Recorded calls @@ -84,45 +84,45 @@ using ResultMap = std::map>; struct Call { - enum class Kind { Solve, Par }; - - Kind kind = Kind::Solve; - int seq = 0; - int board = 0; - std::string purpose; - int trick = 0; - double recorded_ms = 0.0; - - // Solve - int strain_i = 0; - int leader_i = 0; - int solutions = 1; - std::vector current_trick; // card codes, at most 3 - std::vector hands_pbn; // one PBN deal per sampled board - ResultMap result; - - // Par - std::string hand; // PBN body, without the "N:" prefix - std::vector vuln; // {NS, EW} - int par_result = 0; + enum class Kind { Solve, Par }; + + Kind kind = Kind::Solve; + int seq = 0; + int board = 0; + std::string purpose; + int trick = 0; + double recorded_ms = 0.0; + + // Solve + int strain_i = 0; + int leader_i = 0; + int solutions = 1; + std::vector current_trick; // card codes, at most 3 + std::vector hands_pbn; // one PBN deal per sampled board + ResultMap result; + + // Par + std::string hand; // PBN body, without the "N:" prefix + std::vector vuln; // {NS, EW} + int par_result = 0; }; struct Recording { - // From the "meta" record; all optional. - std::string created, host, platform, dds_version; - int dds_mode = 1; - int threads = 0; + // From the "meta" record; all optional. + std::string created, host, platform, dds_version; + int dds_mode = 1; + int threads = 0; - std::vector calls; - int deals = 0; // distinct board numbers among the calls + std::vector calls; + int deals = 0; // distinct board numbers among the calls }; // Load a recording. Unparseable lines are skipped with a warning on stderr (a // run killed mid-write leaves a truncated last line; that should not cost the // whole recording). Returns false only if the file cannot be opened. auto load_recording(const std::string& path, Recording& out, std::string& error) - -> bool; + -> bool; // Resolve a Bazel runfile (e.g. "_main/benchmarks/testdata/x.jsonl") to a real // path, or "" if it cannot be found. Windows does not materialize the runfiles @@ -130,6 +130,6 @@ auto load_recording(const std::string& path, Recording& out, std::string& error) // `bazel test` exports the manifest location in the environment; `bazel run` // does not, so pass argv[0] and the manifest beside the binary will be used. auto find_runfile(const std::string& logical, const std::string& argv0 = "") - -> std::string; + -> std::string; } // namespace dds_replay diff --git a/benchmarks/replay.cpp b/benchmarks/replay.cpp index 976ae31e4..576ca0ed9 100644 --- a/benchmarks/replay.cpp +++ b/benchmarks/replay.cpp @@ -29,19 +29,19 @@ using Clock = std::chrono::steady_clock; auto seconds_since(Clock::time_point t0) -> double { - return std::chrono::duration(Clock::now() - t0).count(); + return std::chrono::duration(Clock::now() - t0).count(); } auto rank_of(char c) -> int { - switch (c) { - case 'A': case 'a': return 14; - case 'K': case 'k': return 13; - case 'Q': case 'q': return 12; - case 'J': case 'j': return 11; - case 'T': case 't': return 10; - default: return (c >= '2' && c <= '9') ? (c - '0') : -1; - } + switch (c) { + case 'A': case 'a': return 14; + case 'K': case 'k': return 13; + case 'Q': case 'q': return 12; + case 'J': case 'j': return 11; + case 'T': case 't': return 10; + default: return (c >= '2' && c <= '9') ? (c - '0') : -1; + } } // The recorder encodes a card as suit * 13 + (14 - rank), i.e. 0 = SA .. 12 = S2, @@ -51,91 +51,91 @@ auto card_code(int suit, int rank) -> int { return suit * 13 + 14 - rank; } } // namespace auto pbn_to_remain_cards(const std::string& pbn, unsigned int remain[4][4]) - -> bool + -> bool { - for (int h = 0; h < 4; ++h) - for (int s = 0; s < 4; ++s) - remain[h][s] = 0; - - // ": ", hands running clockwise from . - const size_t colon = pbn.find(':'); - if (colon == std::string::npos || colon == 0) - return false; - - int first_hand; - switch (pbn[colon - 1]) { - case 'N': case 'n': first_hand = 0; break; - case 'E': case 'e': first_hand = 1; break; - case 'S': case 's': first_hand = 2; break; - case 'W': case 'w': first_hand = 3; break; - default: return false; - } - - // Exactly four dot-separated hands, each with exactly four suits (three dots), - // running clockwise from . Reject anything else -- a truncated line, a - // hand missing a suit, an extra hand -- so the caller reports it as - // unparseable rather than silently solving a partial, wrong deal. - int hand = first_hand; - int suit = 0; - int hands_seen = 0; - bool in_hand = false; // any suit/rank content since the last separator? - - for (size_t i = colon + 1; i <= pbn.size(); ++i) { - const char c = (i < pbn.size()) ? pbn[i] : ' '; // trailing sentinel - if (c == ' ' || c == '\t') { - if (in_hand) { // a hand just ended; it must have held four suits - if (suit != 3) - return false; - ++hands_seen; - hand = (hand + 1) % 4; - suit = 0; - in_hand = false; - } - continue; // fold runs of separators, ignore leading ones - } - in_hand = true; - if (c == '.') { - if (++suit > 3) + for (int h = 0; h < 4; ++h) + for (int s = 0; s < 4; ++s) + remain[h][s] = 0; + + // ": ", hands running clockwise from . + const size_t colon = pbn.find(':'); + if (colon == std::string::npos || colon == 0) return false; - continue; + + int first_hand; + switch (pbn[colon - 1]) { + case 'N': case 'n': first_hand = 0; break; + case 'E': case 'e': first_hand = 1; break; + case 'S': case 's': first_hand = 2; break; + case 'W': case 'w': first_hand = 3; break; + default: return false; + } + + // Exactly four dot-separated hands, each with exactly four suits (three dots), + // running clockwise from . Reject anything else -- a truncated line, a + // hand missing a suit, an extra hand -- so the caller reports it as + // unparseable rather than silently solving a partial, wrong deal. + int hand = first_hand; + int suit = 0; + int hands_seen = 0; + bool in_hand = false; // any suit/rank content since the last separator? + + for (size_t i = colon + 1; i <= pbn.size(); ++i) { + const char c = (i < pbn.size()) ? pbn[i] : ' '; // trailing sentinel + if (c == ' ' || c == '\t') { + if (in_hand) { // a hand just ended; it must have held four suits + if (suit != 3) + return false; + ++hands_seen; + hand = (hand + 1) % 4; + suit = 0; + in_hand = false; + } + continue; // fold runs of separators, ignore leading ones + } + in_hand = true; + if (c == '.') { + if (++suit > 3) + return false; + continue; + } + const int r = rank_of(c); + if (r < 0) + return false; + remain[hand][suit] |= (1u << static_cast(r)); } - const int r = rank_of(c); - if (r < 0) - return false; - remain[hand][suit] |= (1u << static_cast(r)); - } - return hands_seen == 4; + return hands_seen == 4; } auto describe_mismatch(const ResultMap& expected, const ResultMap& actual) - -> std::string + -> std::string { - if (actual.empty() && !expected.empty()) - return "replay returned nothing (DDS error)"; - - std::string missing, extra; - int n_missing = 0, n_extra = 0; - for (const auto& [k, v] : expected) - if (actual.find(k) == actual.end() && n_missing++ < 4) - missing += (missing.empty() ? "" : ",") + k; - for (const auto& [k, v] : actual) - if (expected.find(k) == expected.end() && n_extra++ < 4) - extra += (extra.empty() ? "" : ",") + k; - if (n_missing != 0 || n_extra != 0) - return "keys differ (missing [" + missing + "], extra [" + extra + "])"; - - for (const auto& [k, want] : expected) { - const auto& got = actual.at(k); - if (want == got) - continue; - std::string w, g; - for (size_t i = 0; i < want.size() && i < 6; ++i) - w += (i ? "," : "") + std::to_string(want[i]); - for (size_t i = 0; i < got.size() && i < 6; ++i) - g += (i ? "," : "") + std::to_string(got[i]); - return "key " + k + ": recorded [" + w + "] vs replayed [" + g + "]"; - } - return "unknown difference"; + if (actual.empty() && !expected.empty()) + return "replay returned nothing (DDS error)"; + + std::string missing, extra; + int n_missing = 0, n_extra = 0; + for (const auto& [k, v] : expected) + if (actual.find(k) == actual.end() && n_missing++ < 4) + missing += (missing.empty() ? "" : ",") + k; + for (const auto& [k, v] : actual) + if (expected.find(k) == expected.end() && n_extra++ < 4) + extra += (extra.empty() ? "" : ",") + k; + if (n_missing != 0 || n_extra != 0) + return "keys differ (missing [" + missing + "], extra [" + extra + "])"; + + for (const auto& [k, want] : expected) { + const auto& got = actual.at(k); + if (want == got) + continue; + std::string w, g; + for (size_t i = 0; i < want.size() && i < 6; ++i) + w += (i ? "," : "") + std::to_string(want[i]); + for (size_t i = 0; i < got.size() && i < 6; ++i) + g += (i ? "," : "") + std::to_string(got[i]); + return "key " + k + ": recorded [" + w + "] vs replayed [" + g + "]"; + } + return "unknown difference"; } // --------------------------------------------------------------------------- @@ -145,305 +145,305 @@ auto describe_mismatch(const ResultMap& expected, const ResultMap& actual) class ReplayEngine::Impl { public: - // The DDS mode is not held here: it travels per call, as the `mode` argument - // to solve_batch(), so a single pool can replay calls that used different - // modes. - explicit Impl(int threads) - : n_(threads), results_(nullptr), deals_(nullptr) - { - workers_.reserve(static_cast(n_)); - for (int t = 0; t < n_; ++t) - workers_.emplace_back([this] { worker_loop(); }); - } - - ~Impl() - { + // The DDS mode is not held here: it travels per call, as the `mode` argument + // to solve_batch(), so a single pool can replay calls that used different + // modes. + explicit Impl(int threads) + : n_(threads), results_(nullptr), deals_(nullptr) { - std::lock_guard lock(m_); - stop_ = true; - ++generation_; - } - cv_.notify_all(); - for (auto& w : workers_) - if (w.joinable()) - w.join(); - } - - // Solve one batch. Output is indexed by board, so results stay in recorded - // order no matter how the work was distributed. - auto solve_batch(const std::vector& deals, - std::vector& out, - int target, int solutions, int mode) -> bool - { - out.assign(deals.size(), FutureTricks{}); - ok_.store(true, std::memory_order_relaxed); - - if (n_ <= 1) { - // Single-threaded: reuse one context directly, no handoff cost. - if (solo_ == nullptr) - solo_ = dds_c_create_solvercontext_default(); - for (size_t i = 0; i < deals.size(); ++i) { - if (dds_c_solve_board(solo_, &deals[i], target, solutions, mode, - &out[i]) != RETURN_NO_FAULT) - return false; - } - return true; + workers_.reserve(static_cast(n_)); + for (int t = 0; t < n_; ++t) + workers_.emplace_back([this] { worker_loop(); }); } + ~Impl() { - std::lock_guard lock(m_); - deals_ = &deals; - results_ = &out; - target_ = target; - solutions_ = solutions; - mode_ = mode; - next_.store(0, std::memory_order_relaxed); - done_ = 0; - ++generation_; + { + std::lock_guard lock(m_); + stop_ = true; + ++generation_; + } + cv_.notify_all(); + for (auto& w : workers_) + if (w.joinable()) + w.join(); } - cv_.notify_all(); - std::unique_lock lock(m_); - done_cv_.wait(lock, [this] { return done_ == n_; }); - deals_ = nullptr; - results_ = nullptr; - return ok_.load(std::memory_order_relaxed); - } + // Solve one batch. Output is indexed by board, so results stay in recorded + // order no matter how the work was distributed. + auto solve_batch(const std::vector& deals, + std::vector& out, + int target, int solutions, int mode) -> bool + { + out.assign(deals.size(), FutureTricks{}); + ok_.store(true, std::memory_order_relaxed); + + if (n_ <= 1) { + // Single-threaded: reuse one context directly, no handoff cost. + if (solo_ == nullptr) + solo_ = dds_c_create_solvercontext_default(); + for (size_t i = 0; i < deals.size(); ++i) { + if (dds_c_solve_board(solo_, &deals[i], target, solutions, mode, + &out[i]) != RETURN_NO_FAULT) + return false; + } + return true; + } + + { + std::lock_guard lock(m_); + deals_ = &deals; + results_ = &out; + target_ = target; + solutions_ = solutions; + mode_ = mode; + next_.store(0, std::memory_order_relaxed); + done_ = 0; + ++generation_; + } + cv_.notify_all(); + + std::unique_lock lock(m_); + done_cv_.wait(lock, [this] { return done_ == n_; }); + deals_ = nullptr; + results_ = nullptr; + return ok_.load(std::memory_order_relaxed); + } private: - auto worker_loop() -> void - { - // One context per worker, created once and kept warm for the whole replay. - DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); - unsigned long long seen = 0; - - for (;;) { - std::unique_lock lock(m_); - cv_.wait(lock, [this, &seen] { return stop_ || generation_ != seen; }); - if (stop_) - break; - seen = generation_; - const std::vector* deals = deals_; - std::vector* out = results_; - const int target = target_, solutions = solutions_, mode = mode_; - lock.unlock(); - - if (deals != nullptr && out != nullptr) { + auto worker_loop() -> void + { + // One context per worker, created once and kept warm for the whole replay. + DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); + unsigned long long seen = 0; + for (;;) { - const size_t i = next_.fetch_add(1, std::memory_order_relaxed); - if (i >= deals->size()) - break; - if (dds_c_solve_board(ctx, &(*deals)[i], target, solutions, mode, - &(*out)[i]) != RETURN_NO_FAULT) - ok_.store(false, std::memory_order_relaxed); + std::unique_lock lock(m_); + cv_.wait(lock, [this, &seen] { return stop_ || generation_ != seen; }); + if (stop_) + break; + seen = generation_; + const std::vector* deals = deals_; + std::vector* out = results_; + const int target = target_, solutions = solutions_, mode = mode_; + lock.unlock(); + + if (deals != nullptr && out != nullptr) { + for (;;) { + const size_t i = next_.fetch_add(1, std::memory_order_relaxed); + if (i >= deals->size()) + break; + if (dds_c_solve_board(ctx, &(*deals)[i], target, solutions, mode, + &(*out)[i]) != RETURN_NO_FAULT) + ok_.store(false, std::memory_order_relaxed); + } + } + + { + std::lock_guard lock2(m_); + ++done_; + } + done_cv_.notify_one(); } - } - { - std::lock_guard lock2(m_); - ++done_; - } - done_cv_.notify_one(); + if (ctx != nullptr) + dds_c_destroy_solvercontext(ctx); } - if (ctx != nullptr) - dds_c_destroy_solvercontext(ctx); - } - - int n_; - std::vector workers_; + int n_; + std::vector workers_; - std::mutex m_; - std::condition_variable cv_, done_cv_; - bool stop_ = false; - unsigned long long generation_ = 0; - int done_ = 0; + std::mutex m_; + std::condition_variable cv_, done_cv_; + bool stop_ = false; + unsigned long long generation_ = 0; + int done_ = 0; - std::vector* results_; - const std::vector* deals_; - std::atomic next_{0}; - std::atomic ok_{true}; - int target_ = -1, solutions_ = 1, mode_ = 1; + std::vector* results_; + const std::vector* deals_; + std::atomic next_{0}; + std::atomic ok_{true}; + int target_ = -1, solutions_ = 1, mode_ = 1; - DDS_C_SOLVER_CTX solo_ = nullptr; + DDS_C_SOLVER_CTX solo_ = nullptr; public: - auto destroy_solo() -> void - { - if (solo_ != nullptr) { - dds_c_destroy_solvercontext(solo_); - solo_ = nullptr; + auto destroy_solo() -> void + { + if (solo_ != nullptr) { + dds_c_destroy_solvercontext(solo_); + solo_ = nullptr; + } } - } }; ReplayEngine::ReplayEngine(int threads, int dds_mode) - : impl_(new Impl(threads)), threads_(threads), dds_mode_(dds_mode) + : impl_(new Impl(threads)), threads_(threads), dds_mode_(dds_mode) { } ReplayEngine::~ReplayEngine() { - impl_->destroy_solo(); - delete impl_; + impl_->destroy_solo(); + delete impl_; } auto ReplayEngine::run(const std::vector& calls, bool verify) - -> ReplayStats + -> ReplayStats { - ReplayStats stats; - std::vector deals; - std::vector solved; - - for (const Call& call : calls) { - double elapsed = 0.0; - long long boards = 1; - ResultMap actual; - - if (call.kind == Call::Kind::Solve) { - // Build the batch. Every board in a call shares the strain, the leader - // and the cards already played to the current trick. - const int trump = ((call.strain_i - 1) % 5 + 5) % 5; - - Deal proto{}; - proto.trump = trump; - proto.first = call.leader_i; - for (int k = 0; k < 3; ++k) { - proto.currentTrickSuit[k] = 0; - proto.currentTrickRank[k] = 0; - } - for (size_t k = 0; k < call.current_trick.size() && k < 3; ++k) { - proto.currentTrickSuit[k] = call.current_trick[k] / 13; - proto.currentTrickRank[k] = 14 - call.current_trick[k] % 13; - } - - deals.clear(); - deals.reserve(call.hands_pbn.size()); - bool parsed = true; - for (const std::string& pbn : call.hands_pbn) { - Deal dl = proto; - if (!pbn_to_remain_cards(pbn, dl.remainCards)) { - parsed = false; - break; - } - deals.push_back(dl); - } - boards = static_cast(deals.size()); - - if (!parsed) { - if (verify) - stats.mismatches.push_back( - {call.seq, call.purpose, "unparseable PBN in recording"}); - continue; - } - - const auto t0 = Clock::now(); - const bool ok = impl_->solve_batch(deals, solved, -1, call.solutions, + ReplayStats stats; + std::vector deals; + std::vector solved; + + for (const Call& call : calls) { + double elapsed = 0.0; + long long boards = 1; + ResultMap actual; + + if (call.kind == Call::Kind::Solve) { + // Build the batch. Every board in a call shares the strain, the leader + // and the cards already played to the current trick. + const int trump = ((call.strain_i - 1) % 5 + 5) % 5; + + Deal proto{}; + proto.trump = trump; + proto.first = call.leader_i; + for (int k = 0; k < 3; ++k) { + proto.currentTrickSuit[k] = 0; + proto.currentTrickRank[k] = 0; + } + for (size_t k = 0; k < call.current_trick.size() && k < 3; ++k) { + proto.currentTrickSuit[k] = call.current_trick[k] / 13; + proto.currentTrickRank[k] = 14 - call.current_trick[k] % 13; + } + + deals.clear(); + deals.reserve(call.hands_pbn.size()); + bool parsed = true; + for (const std::string& pbn : call.hands_pbn) { + Deal dl = proto; + if (!pbn_to_remain_cards(pbn, dl.remainCards)) { + parsed = false; + break; + } + deals.push_back(dl); + } + boards = static_cast(deals.size()); + + if (!parsed) { + if (verify) + stats.mismatches.push_back( + {call.seq, call.purpose, "unparseable PBN in recording"}); + continue; + } + + const auto t0 = Clock::now(); + const bool ok = impl_->solve_batch(deals, solved, -1, call.solutions, dds_mode_); - elapsed = seconds_since(t0); - - // `solved_ok` tracks whether we have a well-formed result to compare. It - // stays false when the solve failed, or when a board came back with no - // cards -- both of which must be reported, not silently skipped. - bool solved_ok = ok; - - if (!ok) { - if (verify) - stats.mismatches.push_back({call.seq, call.purpose, "DDS error"}); - } else if (call.solutions == 1) { - // Only the best and worst trick counts for the side to play. A board - // with no cards is a solver fault, not a 0 to index blindly. - bool no_cards = false; - for (const FutureTricks& fut : solved) - if (fut.cards <= 0) { no_cards = true; break; } - if (no_cards) { - solved_ok = false; - if (verify) - stats.mismatches.push_back( - {call.seq, call.purpose, "DDS returned a board with no cards"}); + elapsed = seconds_since(t0); + + // `solved_ok` tracks whether we have a well-formed result to compare. It + // stays false when the solve failed, or when a board came back with no + // cards -- both of which must be reported, not silently skipped. + bool solved_ok = ok; + + if (!ok) { + if (verify) + stats.mismatches.push_back({call.seq, call.purpose, "DDS error"}); + } else if (call.solutions == 1) { + // Only the best and worst trick counts for the side to play. A board + // with no cards is a solver fault, not a 0 to index blindly. + bool no_cards = false; + for (const FutureTricks& fut : solved) + if (fut.cards <= 0) { no_cards = true; break; } + if (no_cards) { + solved_ok = false; + if (verify) + stats.mismatches.push_back( + {call.seq, call.purpose, "DDS returned a board with no cards"}); + } else { + std::vector& mx = actual["max"]; + std::vector& mn = actual["min"]; + mx.reserve(solved.size()); + mn.reserve(solved.size()); + for (const FutureTricks& fut : solved) { + mx.push_back(fut.score[0]); + mn.push_back(fut.score[fut.cards - 1]); + } + } + } else { + // One list per playable card, including cards that are equivalent to + // the one DDS reported (the `equals` bitmap), in board order. + for (const FutureTricks& fut : solved) { + for (int i = 0; i < fut.cards; ++i) { + const int suit = fut.suit[i]; + actual[std::to_string(card_code(suit, fut.rank[i]))] + .push_back(fut.score[i]); + const int eq = fut.equals[i]; + for (int rank = 2; rank <= 14; ++rank) { + if ((eq & (1 << rank)) != 0) + actual[std::to_string(card_code(suit, rank))] + .push_back(fut.score[i]); + } + } + } + } + + // Compare whenever the solve produced a result, even an empty one: an + // empty map where the recording has entries is itself a regression and + // must surface, not be skipped by an `!actual.empty()` guard. + if (verify && solved_ok && actual != call.result) + stats.mismatches.push_back( + {call.seq, call.purpose, describe_mismatch(call.result, actual)}); } else { - std::vector& mx = actual["max"]; - std::vector& mn = actual["min"]; - mx.reserve(solved.size()); - mn.reserve(solved.size()); - for (const FutureTricks& fut : solved) { - mx.push_back(fut.score[0]); - mn.push_back(fut.score[fut.cards - 1]); - } - } - } else { - // One list per playable card, including cards that are equivalent to - // the one DDS reported (the `equals` bitmap), in board order. - for (const FutureTricks& fut : solved) { - for (int i = 0; i < fut.cards; ++i) { - const int suit = fut.suit[i]; - actual[std::to_string(card_code(suit, fut.rank[i]))] - .push_back(fut.score[i]); - const int eq = fut.equals[i]; - for (int rank = 2; rank <= 14; ++rank) { - if ((eq & (1 << rank)) != 0) - actual[std::to_string(card_code(suit, rank))] - .push_back(fut.score[i]); + // Par: the double dummy table plus the par score for the vulnerability. + int v = 0; + if (call.vuln.size() >= 2) { + if (call.vuln[0]) v = 2; + if (call.vuln[1]) v = 3; + if (call.vuln[0] && call.vuln[1]) v = 1; } - } - } - } - - // Compare whenever the solve produced a result, even an empty one: an - // empty map where the recording has entries is itself a regression and - // must surface, not be skipped by an `!actual.empty()` guard. - if (verify && solved_ok && actual != call.result) - stats.mismatches.push_back( - {call.seq, call.purpose, describe_mismatch(call.result, actual)}); - } else { - // Par: the double dummy table plus the par score for the vulnerability. - int v = 0; - if (call.vuln.size() >= 2) { - if (call.vuln[0]) v = 2; - if (call.vuln[1]) v = 3; - if (call.vuln[0] && call.vuln[1]) v = 1; - } - - DdTableDealPBN table_deal{}; - const std::string pbn = "N:" + call.hand; - const size_t n = pbn.size() < sizeof(table_deal.cards) - 1 - ? pbn.size() : sizeof(table_deal.cards) - 1; - for (size_t k = 0; k < n; ++k) - table_deal.cards[k] = pbn[k]; - table_deal.cards[n] = '\0'; - - // SidesParBin is what the string-returning Par() computes internally - // before formatting; sides[0].score is the NS-view par, the signed - // integer the recorder stored (Par() prints it as "NS "). - DdTableResults table{}; - ParResultsMaster sides[2]{}; - const auto t0 = Clock::now(); - const int rc1 = CalcDDtablePBN(table_deal, &table); - const int rc2 = (rc1 == RETURN_NO_FAULT) ? SidesParBin(&table, sides, v) : rc1; - elapsed = seconds_since(t0); - - if (verify) { - if (rc2 != RETURN_NO_FAULT) - stats.mismatches.push_back( - {call.seq, "par", "DDS error " + std::to_string(rc2)}); - else if (sides[0].score != call.par_result) - // Same shape as describe_mismatch's line, so par regressions read - // like solve regressions in the report. - stats.mismatches.push_back( - {call.seq, "par", + + DdTableDealPBN table_deal{}; + const std::string pbn = "N:" + call.hand; + const size_t n = pbn.size() < sizeof(table_deal.cards) - 1 + ? pbn.size() : sizeof(table_deal.cards) - 1; + for (size_t k = 0; k < n; ++k) + table_deal.cards[k] = pbn[k]; + table_deal.cards[n] = '\0'; + + // SidesParBin is what the string-returning Par() computes internally + // before formatting; sides[0].score is the NS-view par, the signed + // integer the recorder stored (Par() prints it as "NS "). + DdTableResults table{}; + ParResultsMaster sides[2]{}; + const auto t0 = Clock::now(); + const int rc1 = CalcDDtablePBN(table_deal, &table); + const int rc2 = (rc1 == RETURN_NO_FAULT) ? SidesParBin(&table, sides, v) : rc1; + elapsed = seconds_since(t0); + + if (verify) { + if (rc2 != RETURN_NO_FAULT) + stats.mismatches.push_back( + {call.seq, "par", "DDS error " + std::to_string(rc2)}); + else if (sides[0].score != call.par_result) + // Same shape as describe_mismatch's line, so par regressions read + // like solve regressions in the report. + stats.mismatches.push_back( + {call.seq, "par", "recorded [" + std::to_string(call.par_result) + "] vs replayed [" + std::to_string(sides[0].score) + "]"}); - } - } + } + } - const std::string purpose = call.purpose.empty() ? "(none)" : call.purpose; - stats.by_purpose[purpose].add(boards, elapsed, call.recorded_ms); - stats.by_trick[call.trick].add(boards, elapsed, call.recorded_ms); - stats.total.add(boards, elapsed, call.recorded_ms); - stats.total_seconds += elapsed; - } + const std::string purpose = call.purpose.empty() ? "(none)" : call.purpose; + stats.by_purpose[purpose].add(boards, elapsed, call.recorded_ms); + stats.by_trick[call.trick].add(boards, elapsed, call.recorded_ms); + stats.total.add(boards, elapsed, call.recorded_ms); + stats.total_seconds += elapsed; + } - return stats; + return stats; } } // namespace dds_replay diff --git a/benchmarks/replay.hpp b/benchmarks/replay.hpp index 23c6f38e3..c44217f62 100644 --- a/benchmarks/replay.hpp +++ b/benchmarks/replay.hpp @@ -18,34 +18,34 @@ namespace dds_replay { struct Bucket { - int calls = 0; - long long boards = 0; - double seconds = 0.0; - double recorded_ms = 0.0; - - auto add(long long n_boards, double elapsed_s, double rec_ms) -> void - { - ++calls; - boards += n_boards; - seconds += elapsed_s; - recorded_ms += rec_ms; - } + int calls = 0; + long long boards = 0; + double seconds = 0.0; + double recorded_ms = 0.0; + + auto add(long long n_boards, double elapsed_s, double rec_ms) -> void + { + ++calls; + boards += n_boards; + seconds += elapsed_s; + recorded_ms += rec_ms; + } }; struct Mismatch { - int seq = 0; - std::string purpose; - std::string why; + int seq = 0; + std::string purpose; + std::string why; }; struct ReplayStats { - std::map by_purpose; - std::map by_trick; - Bucket total; - std::vector mismatches; - double total_seconds = 0.0; + std::map by_purpose; + std::map by_trick; + Bucket total; + std::vector mismatches; + double total_seconds = 0.0; }; // One SolverContext per worker thread, kept alive for the whole replay -- the @@ -54,23 +54,23 @@ struct ReplayStats class ReplayEngine { public: - explicit ReplayEngine(int threads, int dds_mode); - ~ReplayEngine(); + explicit ReplayEngine(int threads, int dds_mode); + ~ReplayEngine(); - ReplayEngine(const ReplayEngine&) = delete; - auto operator=(const ReplayEngine&) -> ReplayEngine& = delete; + ReplayEngine(const ReplayEngine&) = delete; + auto operator=(const ReplayEngine&) -> ReplayEngine& = delete; - // Issue every call once, in recorded order. When `verify` is set, each - // result is compared against the recording. - auto run(const std::vector& calls, bool verify) -> ReplayStats; + // Issue every call once, in recorded order. When `verify` is set, each + // result is compared against the recording. + auto run(const std::vector& calls, bool verify) -> ReplayStats; - auto threads() const -> int { return threads_; } + auto threads() const -> int { return threads_; } private: - class Impl; - Impl* impl_; - int threads_; - int dds_mode_; + class Impl; + Impl* impl_; + int threads_; + int dds_mode_; }; // Convert a PBN deal ("N:AK3.Q42... ...") into DDS remainCards bitmaps. @@ -80,6 +80,6 @@ auto pbn_to_remain_cards(const std::string& pbn, // Render a result map the way the recorder does, for diffing. auto describe_mismatch(const ResultMap& expected, const ResultMap& actual) - -> std::string; + -> std::string; } // namespace dds_replay diff --git a/benchmarks/replay_test.cpp b/benchmarks/replay_test.cpp index b1c4bd743..70dd04aa6 100644 --- a/benchmarks/replay_test.cpp +++ b/benchmarks/replay_test.cpp @@ -25,88 +25,88 @@ using dds_replay::ReplayEngine; auto sample_path() -> std::string { - const std::string p = dds_replay::find_runfile( - "_main/benchmarks/testdata/sample-recording.jsonl"); - return p.empty() ? "benchmarks/testdata/sample-recording.jsonl" : p; + const std::string p = dds_replay::find_runfile( + "_main/benchmarks/testdata/sample-recording.jsonl"); + return p.empty() ? "benchmarks/testdata/sample-recording.jsonl" : p; } TEST(Recording, ParsesTheSampleRecording) { - Recording rec; - std::string error; - ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; - EXPECT_FALSE(rec.calls.empty()); - EXPECT_EQ("3.0.0", rec.dds_version); - - int solves = 0, pars = 0; - for (const Call& c : rec.calls) - (c.kind == Call::Kind::Solve ? solves : pars)++; - EXPECT_GT(solves, 0); - EXPECT_GT(pars, 0); - - // Every solve must carry at least one deal and a recorded result to check. - for (const Call& c : rec.calls) { - if (c.kind != Call::Kind::Solve) - continue; - EXPECT_FALSE(c.hands_pbn.empty()); - EXPECT_FALSE(c.result.empty()) << "seq " << c.seq; - } + Recording rec; + std::string error; + ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; + EXPECT_FALSE(rec.calls.empty()); + EXPECT_EQ("3.0.0", rec.dds_version); + + int solves = 0, pars = 0; + for (const Call& c : rec.calls) + (c.kind == Call::Kind::Solve ? solves : pars)++; + EXPECT_GT(solves, 0); + EXPECT_GT(pars, 0); + + // Every solve must carry at least one deal and a recorded result to check. + for (const Call& c : rec.calls) { + if (c.kind != Call::Kind::Solve) + continue; + EXPECT_FALSE(c.hands_pbn.empty()); + EXPECT_FALSE(c.result.empty()) << "seq " << c.seq; + } } TEST(Recording, DecodesPbnHoldings) { - // North holds the four aces, one per suit; the rest is filler. - unsigned int remain[4][4]; - ASSERT_TRUE(dds_replay::pbn_to_remain_cards( - "N:A.A.A.A 2.2.2.2 3.3.3.3 4.4.4.4", remain)); - - for (int suit = 0; suit < 4; ++suit) { - EXPECT_EQ(1u << 14, remain[0][suit]) << "north suit " << suit; - EXPECT_EQ(1u << 2, remain[1][suit]) << "east suit " << suit; - EXPECT_EQ(1u << 3, remain[2][suit]) << "south suit " << suit; - EXPECT_EQ(1u << 4, remain[3][suit]) << "west suit " << suit; - } - - // A non-North first seat must rotate the hands. - unsigned int rot[4][4]; - ASSERT_TRUE(dds_replay::pbn_to_remain_cards( - "W:A.A.A.A 2.2.2.2 3.3.3.3 4.4.4.4", rot)); - EXPECT_EQ(1u << 14, rot[3][0]); // first listed hand is West - EXPECT_EQ(1u << 2, rot[0][0]); // then North - - EXPECT_FALSE(dds_replay::pbn_to_remain_cards("garbage", remain)); + // North holds the four aces, one per suit; the rest is filler. + unsigned int remain[4][4]; + ASSERT_TRUE(dds_replay::pbn_to_remain_cards( + "N:A.A.A.A 2.2.2.2 3.3.3.3 4.4.4.4", remain)); + + for (int suit = 0; suit < 4; ++suit) { + EXPECT_EQ(1u << 14, remain[0][suit]) << "north suit " << suit; + EXPECT_EQ(1u << 2, remain[1][suit]) << "east suit " << suit; + EXPECT_EQ(1u << 3, remain[2][suit]) << "south suit " << suit; + EXPECT_EQ(1u << 4, remain[3][suit]) << "west suit " << suit; + } + + // A non-North first seat must rotate the hands. + unsigned int rot[4][4]; + ASSERT_TRUE(dds_replay::pbn_to_remain_cards( + "W:A.A.A.A 2.2.2.2 3.3.3.3 4.4.4.4", rot)); + EXPECT_EQ(1u << 14, rot[3][0]); // first listed hand is West + EXPECT_EQ(1u << 2, rot[0][0]); // then North + + EXPECT_FALSE(dds_replay::pbn_to_remain_cards("garbage", remain)); } // The point of the whole exercise: replaying the recording reproduces it. TEST(Replay, ReproducesRecordedResults) { - Recording rec; - std::string error; - ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; - ASSERT_FALSE(rec.calls.empty()); + Recording rec; + std::string error; + ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; + ASSERT_FALSE(rec.calls.empty()); - ReplayEngine engine(/*threads=*/2, rec.dds_mode); - const dds_replay::ReplayStats stats = engine.run(rec.calls, /*verify=*/true); + ReplayEngine engine(/*threads=*/2, rec.dds_mode); + const dds_replay::ReplayStats stats = engine.run(rec.calls, /*verify=*/true); - for (const auto& m : stats.mismatches) - ADD_FAILURE() << "seq " << m.seq << " (" << m.purpose << "): " << m.why; + for (const auto& m : stats.mismatches) + ADD_FAILURE() << "seq " << m.seq << " (" << m.purpose << "): " << m.why; - EXPECT_EQ(rec.calls.size(), static_cast(stats.total.calls)); - EXPECT_GT(stats.total.boards, 0); + EXPECT_EQ(rec.calls.size(), static_cast(stats.total.calls)); + EXPECT_GT(stats.total.boards, 0); } // Single-threaded and multi-threaded replay must agree: results are collected // per board index, so thread count must not affect the answers. TEST(Replay, ThreadCountDoesNotChangeResults) { - Recording rec; - std::string error; - ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; - - ReplayEngine solo(1, rec.dds_mode); - ReplayEngine pool(4, rec.dds_mode); - EXPECT_TRUE(solo.run(rec.calls, true).mismatches.empty()); - EXPECT_TRUE(pool.run(rec.calls, true).mismatches.empty()); + Recording rec; + std::string error; + ASSERT_TRUE(dds_replay::load_recording(sample_path(), rec, error)) << error; + + ReplayEngine solo(1, rec.dds_mode); + ReplayEngine pool(4, rec.dds_mode); + EXPECT_TRUE(solo.run(rec.calls, true).mismatches.empty()); + EXPECT_TRUE(pool.run(rec.calls, true).mismatches.empty()); } } // namespace diff --git a/benchmarks/warm_tt_benchmark.cpp b/benchmarks/warm_tt_benchmark.cpp index b6e2c42d2..a31be343f 100644 --- a/benchmarks/warm_tt_benchmark.cpp +++ b/benchmarks/warm_tt_benchmark.cpp @@ -68,26 +68,26 @@ constexpr int kMode = 0; // this releases the handle however the scope is left. class ScopedContext { public: - ScopedContext() : ctx_(dds_c_create_solvercontext_default()) {} - ~ScopedContext() { destroy(); } + ScopedContext() : ctx_(dds_c_create_solvercontext_default()) {} + ~ScopedContext() { destroy(); } - ScopedContext(const ScopedContext&) = delete; - auto operator=(const ScopedContext&) -> ScopedContext& = delete; + ScopedContext(const ScopedContext&) = delete; + auto operator=(const ScopedContext&) -> ScopedContext& = delete; - [[nodiscard]] auto get() const -> DDS_C_SOLVER_CTX { return ctx_; } + [[nodiscard]] auto get() const -> DDS_C_SOLVER_CTX { return ctx_; } - // Release early, so the cost of destruction lands inside the timed region - // rather than at the end of the enclosing scope. Idempotent. - auto destroy() -> void - { - if (ctx_ != nullptr) { - dds_c_destroy_solvercontext(ctx_); - ctx_ = nullptr; + // Release early, so the cost of destruction lands inside the timed region + // rather than at the end of the enclosing scope. Idempotent. + auto destroy() -> void + { + if (ctx_ != nullptr) { + dds_c_destroy_solvercontext(ctx_); + ctx_ = nullptr; + } } - } private: - DDS_C_SOLVER_CTX ctx_; + DDS_C_SOLVER_CTX ctx_; }; // Deal 52 cards into four hands from the caller's generator. This is only as @@ -95,16 +95,16 @@ class ScopedContext { // not from anything here. See the seeding comment in the test case below. auto make_deal(std::mt19937& rng) -> Hands { - std::vector deck; - for (int s = 0; s < 4; ++s) - for (int r = 2; r <= 14; ++r) - deck.emplace_back(s, r); - std::shuffle(deck.begin(), deck.end(), rng); - Hands hands; - for (int h = 0; h < 4; ++h) - for (int i = 0; i < 13; ++i) - hands[static_cast(h)].push_back(deck[static_cast(h * 13 + i)]); - return hands; + std::vector deck; + for (int s = 0; s < 4; ++s) + for (int r = 2; r <= 14; ++r) + deck.emplace_back(s, r); + std::shuffle(deck.begin(), deck.end(), rng); + Hands hands; + for (int h = 0; h < 4; ++h) + for (int i = 0; i < 13; ++i) + hands[static_cast(h)].push_back(deck[static_cast(h * 13 + i)]); + return hands; } // Build a binary Deal. With `lead` supplied, the position is the one AFTER the @@ -113,47 +113,47 @@ auto make_deal(std::mt19937& rng) -> Hands auto make_position(const Hands& hands, int trump, int leader, const Card* lead = nullptr) -> Deal { - Deal dl{}; - dl.trump = trump; - dl.first = leader; - for (int h = 0; h < DDS_HANDS; ++h) - for (int s = 0; s < DDS_SUITS; ++s) - dl.remainCards[h][s] = 0; - - for (int h = 0; h < DDS_HANDS; ++h) { - for (const auto& [s, r] : hands[static_cast(h)]) { - if (lead != nullptr && h == leader && s == lead->first && r == lead->second) - continue; // the card just led - dl.remainCards[h][s] |= (1u << static_cast(r)); + Deal dl{}; + dl.trump = trump; + dl.first = leader; + for (int h = 0; h < DDS_HANDS; ++h) + for (int s = 0; s < DDS_SUITS; ++s) + dl.remainCards[h][s] = 0; + + for (int h = 0; h < DDS_HANDS; ++h) { + for (const auto& [s, r] : hands[static_cast(h)]) { + if (lead != nullptr && h == leader && s == lead->first && r == lead->second) + continue; // the card just led + dl.remainCards[h][s] |= (1u << static_cast(r)); + } } - } - if (lead != nullptr) { - dl.currentTrickSuit[0] = lead->first; - dl.currentTrickRank[0] = lead->second; - } - return dl; + if (lead != nullptr) { + dl.currentTrickSuit[0] = lead->first; + dl.currentTrickRank[0] = lead->second; + } + return dl; } // Top-K candidate leads, ranked by the solutions=3 result `fut` (a stand-in for // an engine's NN shortlist: here simply the double-dummy-best leads). auto top_k_leads(const FutureTricks& fut, int k) -> std::vector { - std::vector idx; - for (int i = 0; i < fut.cards; ++i) idx.push_back(i); - std::stable_sort(idx.begin(), idx.end(), - [&](int a, int b) { return fut.score[a] > fut.score[b]; }); - std::vector leads; - for (int i = 0; i < static_cast(idx.size()) && i < k; ++i) - leads.emplace_back(fut.suit[idx[static_cast(i)]], + std::vector idx; + for (int i = 0; i < fut.cards; ++i) idx.push_back(i); + std::stable_sort(idx.begin(), idx.end(), + [&](int a, int b) { return fut.score[a] > fut.score[b]; }); + std::vector leads; + for (int i = 0; i < static_cast(idx.size()) && i < k; ++i) + leads.emplace_back(fut.suit[idx[static_cast(i)]], fut.rank[idx[static_cast(i)]]); - return leads; + return leads; } using Clock = std::chrono::steady_clock; auto ms_since(Clock::time_point t0) -> double { - return std::chrono::duration(Clock::now() - t0).count(); + return std::chrono::duration(Clock::now() - t0).count(); } struct Totals { double a = 0, b_cold = 0, b_warm = 0; int deals = 0; }; @@ -164,131 +164,131 @@ struct Totals { double a = 0, b_cold = 0, b_warm = 0; int deals = 0; }; auto run(const std::vector& deals, int trump, int leader, int k, const char* label, Totals& t) -> void { - for (size_t d = 0; d < deals.size(); ++d) { - const Hands& hands = deals[d]; - - // --- A: one fresh-context solutions=3 solve valuing all leads. --- - const Deal full = make_position(hands, trump, leader); - FutureTricks futA{}; - int rcA = RETURN_UNKNOWN_FAULT; - { - const auto tA = Clock::now(); - ScopedContext ctx_a; - ASSERT_NE(nullptr, ctx_a.get()) << label << " deal " << d << ": context alloc failed"; - rcA = dds_c_solve_board(ctx_a.get(), &full, -1, 3, kMode, &futA); - ctx_a.destroy(); - t.a += ms_since(tA); - } - ASSERT_EQ(RETURN_NO_FAULT, rcA) << label << " deal " << d << ": A solve failed"; - // A full-deal solve always has playable cards; none would mean the solver - // answered something we cannot compare, not a deal worth skipping. - ASSERT_GT(futA.cards, 0) << label << " deal " << d << ": A returned no cards"; - - // Precompute the post-lead positions (excluded from the timings). - std::vector positions; - for (const Card lead : top_k_leads(futA, k)) - positions.push_back(make_position(hands, trump, leader, &lead)); - - // --- B_cold: a fresh context (cold TT) per lead. --- - std::vector cold_scores; - const auto tCold = Clock::now(); - for (const auto& pos : positions) { - ScopedContext ctx; - ASSERT_NE(nullptr, ctx.get()) << label << " deal " << d << ": context alloc failed"; - FutureTricks fut{}; - const int rc = dds_c_solve_board(ctx.get(), &pos, -1, 1, kMode, &fut); - ctx.destroy(); - ASSERT_EQ(RETURN_NO_FAULT, rc) << label << " deal " << d << ": B_cold solve failed"; - ASSERT_GT(fut.cards, 0) << label << " deal " << d << ": B_cold returned no cards"; - cold_scores.push_back(fut.score[0]); - } - t.b_cold += ms_since(tCold); - - // --- B_warm: ONE context reused across the K leads (warm TT). --- - std::vector warm_scores; - { - const auto tWarm = Clock::now(); - ScopedContext ctx_w; - ASSERT_NE(nullptr, ctx_w.get()) << label << " deal " << d << ": context alloc failed"; - for (const auto& pos : positions) { - FutureTricks fut{}; - const int rc = dds_c_solve_board(ctx_w.get(), &pos, -1, 1, kMode, &fut); - ASSERT_EQ(RETURN_NO_FAULT, rc) << label << " deal " << d << ": B_warm solve failed"; - ASSERT_GT(fut.cards, 0) << label << " deal " << d << ": B_warm returned no cards"; - warm_scores.push_back(fut.score[0]); - } - ctx_w.destroy(); - t.b_warm += ms_since(tWarm); + for (size_t d = 0; d < deals.size(); ++d) { + const Hands& hands = deals[d]; + + // --- A: one fresh-context solutions=3 solve valuing all leads. --- + const Deal full = make_position(hands, trump, leader); + FutureTricks futA{}; + int rcA = RETURN_UNKNOWN_FAULT; + { + const auto tA = Clock::now(); + ScopedContext ctx_a; + ASSERT_NE(nullptr, ctx_a.get()) << label << " deal " << d << ": context alloc failed"; + rcA = dds_c_solve_board(ctx_a.get(), &full, -1, 3, kMode, &futA); + ctx_a.destroy(); + t.a += ms_since(tA); + } + ASSERT_EQ(RETURN_NO_FAULT, rcA) << label << " deal " << d << ": A solve failed"; + // A full-deal solve always has playable cards; none would mean the solver + // answered something we cannot compare, not a deal worth skipping. + ASSERT_GT(futA.cards, 0) << label << " deal " << d << ": A returned no cards"; + + // Precompute the post-lead positions (excluded from the timings). + std::vector positions; + for (const Card lead : top_k_leads(futA, k)) + positions.push_back(make_position(hands, trump, leader, &lead)); + + // --- B_cold: a fresh context (cold TT) per lead. --- + std::vector cold_scores; + const auto tCold = Clock::now(); + for (const auto& pos : positions) { + ScopedContext ctx; + ASSERT_NE(nullptr, ctx.get()) << label << " deal " << d << ": context alloc failed"; + FutureTricks fut{}; + const int rc = dds_c_solve_board(ctx.get(), &pos, -1, 1, kMode, &fut); + ctx.destroy(); + ASSERT_EQ(RETURN_NO_FAULT, rc) << label << " deal " << d << ": B_cold solve failed"; + ASSERT_GT(fut.cards, 0) << label << " deal " << d << ": B_cold returned no cards"; + cold_scores.push_back(fut.score[0]); + } + t.b_cold += ms_since(tCold); + + // --- B_warm: ONE context reused across the K leads (warm TT). --- + std::vector warm_scores; + { + const auto tWarm = Clock::now(); + ScopedContext ctx_w; + ASSERT_NE(nullptr, ctx_w.get()) << label << " deal " << d << ": context alloc failed"; + for (const auto& pos : positions) { + FutureTricks fut{}; + const int rc = dds_c_solve_board(ctx_w.get(), &pos, -1, 1, kMode, &fut); + ASSERT_EQ(RETURN_NO_FAULT, rc) << label << " deal " << d << ": B_warm solve failed"; + ASSERT_GT(fut.cards, 0) << label << " deal " << d << ": B_warm returned no cards"; + warm_scores.push_back(fut.score[0]); + } + ctx_w.destroy(); + t.b_warm += ms_since(tWarm); + } + + // --- Correctness: warm TT reuse must not change any answer. --- + ASSERT_EQ(cold_scores, warm_scores) + << label << " deal " << d << ": warm-TT reuse changed a lead's score"; + ++t.deals; } - - // --- Correctness: warm TT reuse must not change any answer. --- - ASSERT_EQ(cold_scores, warm_scores) - << label << " deal " << d << ": warm-TT reuse changed a lead's score"; - ++t.deals; - } } auto report(const char* tag, int k, const Totals& t) -> void { - const double a = t.a, bc = t.b_cold, bw = t.b_warm; - std::printf( - " %-10s K=%d over %3d deals: A(sol=3)=%8.1f ms " - "B_cold=%8.1f ms (%.2fx A) B_warm=%8.1f ms (%.2fx A) warm/cold=%.2f\n", - tag, k, t.deals, a, bc, (a > 0 ? bc / a : 0.0), - bw, (a > 0 ? bw / a : 0.0), (bc > 0 ? bw / bc : 0.0)); + const double a = t.a, bc = t.b_cold, bw = t.b_warm; + std::printf( + " %-10s K=%d over %3d deals: A(sol=3)=%8.1f ms " + "B_cold=%8.1f ms (%.2fx A) B_warm=%8.1f ms (%.2fx A) warm/cold=%.2f\n", + tag, k, t.deals, a, bc, (a > 0 ? bc / a : 0.0), + bw, (a > 0 ? bw / a : 0.0), (bc > 0 ? bw / bc : 0.0)); } TEST(WarmTtBenchmark, ContextReuseKeepsTranspositionTableWarm) { - // Fixed seed, on purpose: it is what makes this benchmark reproducible. - // std::mt19937 is specified by the standard, so a given seed yields the same - // sequence on every platform and library version -- so every run, on every - // machine, measures the same deals. That matters here because the numbers - // are meant to be compared across runs (and across changes to the solver): a - // fresh random sample each time would move the timings for reasons unrelated - // to the code under test, and would also make any failure of the warm/cold - // agreement check below impossible to reproduce from the failure output alone. - // - // Deal count is sized for Bazel's short (60s) timeout on slow CI hosts - // (notably Windows runners); keep it large enough for a stable warm/cold - // story, small enough that opt builds finish with headroom under 60s. - std::mt19937 rng(20260717u); - constexpr int kDeals = 40; - std::vector deals; - deals.reserve(kDeals); - for (int d = 0; d < kDeals; ++d) deals.push_back(make_deal(rng)); - - // Warm up process-wide one-time init so it doesn't bias the first timer. - { - const Deal w = make_position(deals[0], kStrainNT, kWest); - FutureTricks fut{}; - ScopedContext ctx; - ASSERT_NE(nullptr, ctx.get()); - (void) dds_c_solve_board(ctx.get(), &w, -1, 3, kMode, &fut); - } - - std::printf("\n[warm-TT benchmark] declarer South; opening leader West; " - "%d deals x {3N, 4S}\n", kDeals); - - double warm_sum = 0, cold_sum = 0; - for (const int k : {4, 6}) { - Totals nt, sp; - run(deals, kStrainNT, kWest, k, "3N", nt); - run(deals, kStrainSpades, kWest, k, "4S", sp); - if (::testing::Test::HasFatalFailure()) return; - report("3N", k, nt); - report("4S", k, sp); - Totals both; - both.a = nt.a + sp.a; both.b_cold = nt.b_cold + sp.b_cold; - both.b_warm = nt.b_warm + sp.b_warm; both.deals = nt.deals + sp.deals; - report("3N+4S", k, both); - warm_sum += both.b_warm; cold_sum += both.b_cold; - } - - // Sanity (not a timing gate): reusing a warm TT must not be materially - // slower than rebuilding a cold one. The real speed story is in the table. - EXPECT_LE(warm_sum, cold_sum * 1.25) - << "context reuse was unexpectedly slower than fresh contexts"; + // Fixed seed, on purpose: it is what makes this benchmark reproducible. + // std::mt19937 is specified by the standard, so a given seed yields the same + // sequence on every platform and library version -- so every run, on every + // machine, measures the same deals. That matters here because the numbers + // are meant to be compared across runs (and across changes to the solver): a + // fresh random sample each time would move the timings for reasons unrelated + // to the code under test, and would also make any failure of the warm/cold + // agreement check below impossible to reproduce from the failure output alone. + // + // Deal count is sized for Bazel's short (60s) timeout on slow CI hosts + // (notably Windows runners); keep it large enough for a stable warm/cold + // story, small enough that opt builds finish with headroom under 60s. + std::mt19937 rng(20260717u); + constexpr int kDeals = 40; + std::vector deals; + deals.reserve(kDeals); + for (int d = 0; d < kDeals; ++d) deals.push_back(make_deal(rng)); + + // Warm up process-wide one-time init so it doesn't bias the first timer. + { + const Deal w = make_position(deals[0], kStrainNT, kWest); + FutureTricks fut{}; + ScopedContext ctx; + ASSERT_NE(nullptr, ctx.get()); + (void) dds_c_solve_board(ctx.get(), &w, -1, 3, kMode, &fut); + } + + std::printf("\n[warm-TT benchmark] declarer South; opening leader West; " + "%d deals x {3N, 4S}\n", kDeals); + + double warm_sum = 0, cold_sum = 0; + for (const int k : {4, 6}) { + Totals nt, sp; + run(deals, kStrainNT, kWest, k, "3N", nt); + run(deals, kStrainSpades, kWest, k, "4S", sp); + if (::testing::Test::HasFatalFailure()) return; + report("3N", k, nt); + report("4S", k, sp); + Totals both; + both.a = nt.a + sp.a; both.b_cold = nt.b_cold + sp.b_cold; + both.b_warm = nt.b_warm + sp.b_warm; both.deals = nt.deals + sp.deals; + report("3N+4S", k, both); + warm_sum += both.b_warm; cold_sum += both.b_cold; + } + + // Sanity (not a timing gate): reusing a warm TT must not be materially + // slower than rebuilding a cold one. The real speed story is in the table. + EXPECT_LE(warm_sum, cold_sum * 1.25) + << "context reuse was unexpectedly slower than fresh contexts"; } } // namespace diff --git a/examples/analyse_all_plays_bin.cpp b/examples/analyse_all_plays_bin.cpp index 4593ee24b..dd256ccc9 100644 --- a/examples/analyse_all_plays_bin.cpp +++ b/examples/analyse_all_plays_bin.cpp @@ -22,60 +22,60 @@ extern unsigned char card_suit_chars_[5], card_rank_chars_[16]; auto main() -> int { - Boards bo; - PlayTracesBin DDplays; - SolvedPlays solved; + Boards bo; + PlayTracesBin DDplays; + SolvedPlays solved; - int chunkSize = 1, res; - char line[80]; - bool match; + int chunkSize = 1, res; + char line[80]; + bool match; - bo.no_of_boards = 3; - DDplays.no_of_boards = 3; + bo.no_of_boards = 3; + DDplays.no_of_boards = 3; - for (int handno = 0; handno < 3; handno++) - { - bo.deals[handno].trump = trump_suit_[handno]; - bo.deals[handno].first = first_hand_[handno]; - - bo.deals[handno].currentTrickSuit[0] = 0; - bo.deals[handno].currentTrickSuit[1] = 0; - bo.deals[handno].currentTrickSuit[2] = 0; - - bo.deals[handno].currentTrickRank[0] = 0; - bo.deals[handno].currentTrickRank[1] = 0; - bo.deals[handno].currentTrickRank[2] = 0; - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - bo.deals[handno].remainCards[h][s] = holdings_[handno][s][h]; - - DDplays.plays[handno].number = play_count_[handno]; - for (int i = 0; i < play_count_[handno]; i++) + for (int handno = 0; handno < 3; handno++) { - DDplays.plays[handno].suit[i] = play_suit_[handno][i]; - DDplays.plays[handno].rank[i] = play_rank_[handno][i]; + bo.deals[handno].trump = trump_suit_[handno]; + bo.deals[handno].first = first_hand_[handno]; + + bo.deals[handno].currentTrickSuit[0] = 0; + bo.deals[handno].currentTrickSuit[1] = 0; + bo.deals[handno].currentTrickSuit[2] = 0; + + bo.deals[handno].currentTrickRank[0] = 0; + bo.deals[handno].currentTrickRank[1] = 0; + bo.deals[handno].currentTrickRank[2] = 0; + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + bo.deals[handno].remainCards[h][s] = holdings_[handno][s][h]; + + DDplays.plays[handno].number = play_count_[handno]; + for (int i = 0; i < play_count_[handno]; i++) + { + DDplays.plays[handno].suit[i] = play_suit_[handno][i]; + DDplays.plays[handno].rank[i] = play_rank_[handno][i]; + } } - } - res = AnalyseAllPlaysBin(&bo, &DDplays, &solved, chunkSize); + res = AnalyseAllPlaysBin(&bo, &DDplays, &solved, chunkSize); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - for (int handno = 0; handno < 3; handno++) - { - match = compare_play(&solved.solved[handno], handno); + for (int handno = 0; handno < 3; handno++) + { + match = compare_play(&solved.solved[handno], handno); - sprintf(line, "AnalyseAllPlaysBin, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, "AnalyseAllPlaysBin, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_hand(line, bo.deals[handno].remainCards); + print_hand(line, bo.deals[handno].remainCards); - print_bin_play(&DDplays.plays[handno], &solved.solved[handno]); - } + print_bin_play(&DDplays.plays[handno], &solved.solved[handno]); + } } diff --git a/examples/analyse_all_plays_pbn.cpp b/examples/analyse_all_plays_pbn.cpp index d13884a44..b7faf111a 100644 --- a/examples/analyse_all_plays_pbn.cpp +++ b/examples/analyse_all_plays_pbn.cpp @@ -22,54 +22,54 @@ extern unsigned char card_suit_chars_[5], card_rank_chars_[16]; auto main() -> int { - BoardsPBN bo; - PlayTracesPBN DDplays; - SolvedPlays solved; + BoardsPBN bo; + PlayTracesPBN DDplays; + SolvedPlays solved; - int chunkSize = 1, res; - char line[80]; - bool match; + int chunkSize = 1, res; + char line[80]; + bool match; - bo.no_of_boards = 3; - DDplays.no_of_boards = 3; + bo.no_of_boards = 3; + DDplays.no_of_boards = 3; - for (int handno = 0; handno < 3; handno++) - { - bo.deals[handno].trump = trump_suit_[handno]; - bo.deals[handno].first = first_hand_[handno]; + for (int handno = 0; handno < 3; handno++) + { + bo.deals[handno].trump = trump_suit_[handno]; + bo.deals[handno].first = first_hand_[handno]; - bo.deals[handno].currentTrickSuit[0] = 0; - bo.deals[handno].currentTrickSuit[1] = 0; - bo.deals[handno].currentTrickSuit[2] = 0; + bo.deals[handno].currentTrickSuit[0] = 0; + bo.deals[handno].currentTrickSuit[1] = 0; + bo.deals[handno].currentTrickSuit[2] = 0; - bo.deals[handno].currentTrickRank[0] = 0; - bo.deals[handno].currentTrickRank[1] = 0; - bo.deals[handno].currentTrickRank[2] = 0; + bo.deals[handno].currentTrickRank[0] = 0; + bo.deals[handno].currentTrickRank[1] = 0; + bo.deals[handno].currentTrickRank[2] = 0; - strcpy(bo.deals[handno].remainCards, pbn_hands_[handno]); + strcpy(bo.deals[handno].remainCards, pbn_hands_[handno]); - DDplays.plays[handno].number = play_count_[handno]; - strcpy(DDplays.plays[handno].cards, play_sequence_[handno]); - } + DDplays.plays[handno].number = play_count_[handno]; + strcpy(DDplays.plays[handno].cards, play_sequence_[handno]); + } - res = AnalyseAllPlaysPBN(&bo, &DDplays, &solved, chunkSize); + res = AnalyseAllPlaysPBN(&bo, &DDplays, &solved, chunkSize); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - for (int handno = 0; handno < 3; handno++) - { - match = compare_play(&solved.solved[handno], handno); + for (int handno = 0; handno < 3; handno++) + { + match = compare_play(&solved.solved[handno], handno); - sprintf(line, "AnalyseAllPlaysBin, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, "AnalyseAllPlaysBin, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_pbn_hand(line, bo.deals[handno].remainCards); + print_pbn_hand(line, bo.deals[handno].remainCards); - print_pbn_play(&DDplays.plays[handno], &solved.solved[handno]); - } + print_pbn_play(&DDplays.plays[handno], &solved.solved[handno]); + } } diff --git a/examples/analyse_play_bin.cpp b/examples/analyse_play_bin.cpp index 985b4438c..dc9e44820 100644 --- a/examples/analyse_play_bin.cpp +++ b/examples/analyse_play_bin.cpp @@ -22,54 +22,54 @@ extern unsigned char card_suit_chars_[5], card_rank_chars_[16]; auto main() -> int { - Deal dl; - PlayTraceBin DDplay; - SolvedPlay solved; + Deal dl; + PlayTraceBin DDplay; + SolvedPlay solved; - int threadIndex = 0, res; - char line[80]; - bool match; + int threadIndex = 0, res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { - dl.trump = trump_suit_[handno]; - dl.first = first_hand_[handno]; + for (int handno = 0; handno < 3; handno++) + { + dl.trump = trump_suit_[handno]; + dl.first = first_hand_[handno]; - dl.currentTrickSuit[0] = 0; - dl.currentTrickSuit[1] = 0; - dl.currentTrickSuit[2] = 0; + dl.currentTrickSuit[0] = 0; + dl.currentTrickSuit[1] = 0; + dl.currentTrickSuit[2] = 0; - dl.currentTrickRank[0] = 0; - dl.currentTrickRank[1] = 0; - dl.currentTrickRank[2] = 0; + dl.currentTrickRank[0] = 0; + dl.currentTrickRank[1] = 0; + dl.currentTrickRank[2] = 0; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = holdings_[handno][s][h]; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = holdings_[handno][s][h]; - DDplay.number = play_count_[handno]; - for (int i = 0; i < play_count_[handno]; i++) - { - DDplay.suit[i] = play_suit_[handno][i]; - DDplay.rank[i] = play_rank_[handno][i]; - } + DDplay.number = play_count_[handno]; + for (int i = 0; i < play_count_[handno]; i++) + { + DDplay.suit[i] = play_suit_[handno][i]; + DDplay.rank[i] = play_rank_[handno][i]; + } - res = AnalysePlayBin(dl, DDplay, &solved, threadIndex); + res = AnalysePlayBin(dl, DDplay, &solved, threadIndex); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_play(&solved, handno); + match = compare_play(&solved, handno); - sprintf(line, "AnalysePlayBin, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, "AnalysePlayBin, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_hand(line, dl.remainCards); + print_hand(line, dl.remainCards); - print_bin_play(&DDplay, &solved); - } + print_bin_play(&DDplay, &solved); + } } diff --git a/examples/analyse_play_pbn.cpp b/examples/analyse_play_pbn.cpp index 35197edde..16312043b 100644 --- a/examples/analyse_play_pbn.cpp +++ b/examples/analyse_play_pbn.cpp @@ -20,48 +20,48 @@ auto main() -> int { - DealPBN dlPBN; - PlayTracePBN DDplayPBN; - SolvedPlay solved; + DealPBN dlPBN; + PlayTracePBN DDplayPBN; + SolvedPlay solved; - int threadIndex = 0, res; - char line[80]; - bool match; + int threadIndex = 0, res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { - dlPBN.trump = trump_suit_[handno]; - dlPBN.first = first_hand_[handno]; + for (int handno = 0; handno < 3; handno++) + { + dlPBN.trump = trump_suit_[handno]; + dlPBN.first = first_hand_[handno]; - dlPBN.currentTrickSuit[0] = 0; - dlPBN.currentTrickSuit[1] = 0; - dlPBN.currentTrickSuit[2] = 0; + dlPBN.currentTrickSuit[0] = 0; + dlPBN.currentTrickSuit[1] = 0; + dlPBN.currentTrickSuit[2] = 0; - dlPBN.currentTrickRank[0] = 0; - dlPBN.currentTrickRank[1] = 0; - dlPBN.currentTrickRank[2] = 0; + dlPBN.currentTrickRank[0] = 0; + dlPBN.currentTrickRank[1] = 0; + dlPBN.currentTrickRank[2] = 0; - strcpy(dlPBN.remainCards, pbn_hands_[handno]); + strcpy(dlPBN.remainCards, pbn_hands_[handno]); - DDplayPBN.number = play_count_[handno]; - strcpy(DDplayPBN.cards, play_sequence_[handno]); + DDplayPBN.number = play_count_[handno]; + strcpy(DDplayPBN.cards, play_sequence_[handno]); - res = AnalysePlayPBN(dlPBN, DDplayPBN, &solved, threadIndex); + res = AnalysePlayPBN(dlPBN, DDplayPBN, &solved, threadIndex); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_play(&solved, handno); + match = compare_play(&solved, handno); - sprintf(line, "AnalysePlayPBNBin, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, "AnalysePlayPBNBin, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_pbn_hand(line, dlPBN.remainCards); + print_pbn_hand(line, dlPBN.remainCards); - print_pbn_play(&DDplayPBN, &solved); - } + print_pbn_play(&DDplayPBN, &solved); + } } diff --git a/examples/calc_all_tables.cpp b/examples/calc_all_tables.cpp index b7b7f7c54..60d7326b3 100644 --- a/examples/calc_all_tables.cpp +++ b/examples/calc_all_tables.cpp @@ -20,44 +20,44 @@ auto main() -> int { - DdTableDeals DDdeals; - DdTablesRes tableRes; - AllParResults pres; + DdTableDeals DDdeals; + DdTablesRes tableRes; + AllParResults pres; - int mode = 0; // No par calculation - int trumpFilter[DDS_STRAINS] = {0, 0, 0, 0, 0}; // All - int res; - char line[80]; - bool match; + int mode = 0; // No par calculation + int trumpFilter[DDS_STRAINS] = {0, 0, 0, 0, 0}; // All + int res; + char line[80]; + bool match; - DDdeals.no_of_tables = 3; + DDdeals.no_of_tables = 3; - for (int handno = 0; handno < 3; handno++) - { - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - DDdeals.deals[handno].cards[h][s] = holdings_[handno][s][h]; - } + for (int handno = 0; handno < 3; handno++) + { + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + DDdeals.deals[handno].cards[h][s] = holdings_[handno][s][h]; + } - res = CalcAllTables(&DDdeals, mode, trumpFilter, &tableRes, &pres); + res = CalcAllTables(&DDdeals, mode, trumpFilter, &tableRes, &pres); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - for (int handno = 0; handno < 3; handno++) - { - match = compare_table(&tableRes.results[handno], handno); + for (int handno = 0; handno < 3; handno++) + { + match = compare_table(&tableRes.results[handno], handno); - sprintf(line, - "CalcDDtable, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, + "CalcDDtable, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_hand(line, DDdeals.deals[handno].cards); + print_hand(line, DDdeals.deals[handno].cards); - print_table(&tableRes.results[handno]); - } + print_table(&tableRes.results[handno]); + } } diff --git a/examples/calc_all_tables_pbn.cpp b/examples/calc_all_tables_pbn.cpp index c304d9768..0b4b6d496 100644 --- a/examples/calc_all_tables_pbn.cpp +++ b/examples/calc_all_tables_pbn.cpp @@ -20,43 +20,43 @@ auto main() -> int { - DdTableDealsPBN DDdealsPBN; - DdTablesRes tableRes; - AllParResults pres; + DdTableDealsPBN DDdealsPBN; + DdTablesRes tableRes; + AllParResults pres; - int mode = 0; // No par calculation - int trumpFilter[DDS_STRAINS] = {0, 0, 0, 0, 0}; // All - int res; - char line[80]; - bool match; + int mode = 0; // No par calculation + int trumpFilter[DDS_STRAINS] = {0, 0, 0, 0, 0}; // All + int res; + char line[80]; + bool match; - DDdealsPBN.no_of_tables = 3; + DDdealsPBN.no_of_tables = 3; - for (int handno = 0; handno < 3; handno++) - { - strcpy(DDdealsPBN.deals[handno].cards, pbn_hands_[handno]); - } + for (int handno = 0; handno < 3; handno++) + { + strcpy(DDdealsPBN.deals[handno].cards, pbn_hands_[handno]); + } - res = CalcAllTablesPBN(&DDdealsPBN, mode, trumpFilter, + res = CalcAllTablesPBN(&DDdealsPBN, mode, trumpFilter, &tableRes, &pres); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - for (int handno = 0; handno < 3; handno++) - { - match = compare_table(&tableRes.results[handno], handno); + for (int handno = 0; handno < 3; handno++) + { + match = compare_table(&tableRes.results[handno], handno); - sprintf(line, - "CalcDDtable, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, + "CalcDDtable, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_pbn_hand(line, DDdealsPBN.deals[handno].cards); + print_pbn_hand(line, DDdealsPBN.deals[handno].cards); - print_table(&tableRes.results[handno]); - } + print_table(&tableRes.results[handno]); + } } diff --git a/examples/calc_dd_table.cpp b/examples/calc_dd_table.cpp index 0c1a53c88..73ae58c85 100644 --- a/examples/calc_dd_table.cpp +++ b/examples/calc_dd_table.cpp @@ -20,36 +20,36 @@ auto main() -> int { - DdTableDeal tableDeal; - DdTableResults table; + DdTableDeal tableDeal; + DdTableResults table; - int res; - char line[80]; - bool match; + int res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { + for (int handno = 0; handno < 3; handno++) + { - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - tableDeal.cards[h][s] = holdings_[handno][s][h]; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + tableDeal.cards[h][s] = holdings_[handno][s][h]; - res = CalcDDtable(tableDeal, &table); + res = CalcDDtable(tableDeal, &table); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_table(&table, handno); + match = compare_table(&table, handno); - sprintf(line, - "CalcDDtable, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, + "CalcDDtable, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_hand(line, tableDeal.cards); + print_hand(line, tableDeal.cards); - print_table(&table); - } + print_table(&table); + } } diff --git a/examples/calc_dd_table_pbn.cpp b/examples/calc_dd_table_pbn.cpp index a3b4ceab3..654c27022 100644 --- a/examples/calc_dd_table_pbn.cpp +++ b/examples/calc_dd_table_pbn.cpp @@ -20,33 +20,33 @@ auto main() -> int { - DdTableDealPBN tableDealPBN; - DdTableResults table; + DdTableDealPBN tableDealPBN; + DdTableResults table; - int res; - char line[80]; - bool match; + int res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { - strcpy(tableDealPBN.cards, pbn_hands_[handno]); + for (int handno = 0; handno < 3; handno++) + { + strcpy(tableDealPBN.cards, pbn_hands_[handno]); - res = CalcDDtablePBN(tableDealPBN, &table); + res = CalcDDtablePBN(tableDealPBN, &table); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_table(&table, handno); + match = compare_table(&table, handno); - sprintf(line, - "CalcDDtable, hand %d: %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, + "CalcDDtable, hand %d: %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_pbn_hand(line, tableDealPBN.cards); + print_pbn_hand(line, tableDealPBN.cards); - print_table(&table); - } + print_table(&table); + } } diff --git a/examples/calc_par_context_example.cpp b/examples/calc_par_context_example.cpp index ffd0d22a4..52cd1b5a1 100644 --- a/examples/calc_par_context_example.cpp +++ b/examples/calc_par_context_example.cpp @@ -23,167 +23,167 @@ void example_without_context() { - printf("\n=== Par Calculation (Traditional Approach) ===\n\n"); + printf("\n=== Par Calculation (Traditional Approach) ===\n\n"); - auto start_time = std::chrono::high_resolution_clock::now(); + auto start_time = std::chrono::high_resolution_clock::now(); - for (int handno = 0; handno < 3; handno++) - { - DdTableResults ddtable; - set_table(&ddtable, handno); + for (int handno = 0; handno < 3; handno++) + { + DdTableResults ddtable; + set_table(&ddtable, handno); - ParResults pres; - int res = Par(&ddtable, &pres, vulnerability_[handno]); + ParResults pres; + int res = Par(&ddtable, &pres, vulnerability_[handno]); - if (res == RETURN_NO_FAULT) - { - const char* suit_name = - (trump_suit_[handno] == 0 ? "♠" : + if (res == RETURN_NO_FAULT) + { + const char* suit_name = + (trump_suit_[handno] == 0 ? "♠" : trump_suit_[handno] == 1 ? "♥" : trump_suit_[handno] == 2 ? "♦" : trump_suit_[handno] == 3 ? "♣" : "NT"); - - printf("Hand %d (%s): Par score = %s\n", + + printf("Hand %d (%s): Par score = %s\n", handno + 1, suit_name, pres.par_score[0]); + } } - } - auto end_time = std::chrono::high_resolution_clock::now(); - auto elapsed = std::chrono::duration_cast( - end_time - start_time).count(); - printf("\nTime (traditional): %lld ms\n\n", static_cast(elapsed)); + auto end_time = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast( + end_time - start_time).count(); + printf("\nTime (traditional): %lld ms\n\n", static_cast(elapsed)); } void example_with_context() { - printf("\n=== Par Calculation WITH SolverContext ===\n\n"); + printf("\n=== Par Calculation WITH SolverContext ===\n\n"); - // Create a single solver context for resource management - SolverContext context; + // Create a single solver context for resource management + SolverContext context; - printf("SolverContext provides:\n"); - printf(" - Reuse of allocated solver resources across calculations\n"); - printf(" - Persistent solver resources (no per-call allocation overhead)\n"); - printf(" - Consistent API with other context-aware DDS operations\n\n"); + printf("SolverContext provides:\n"); + printf(" - Reuse of allocated solver resources across calculations\n"); + printf(" - Persistent solver resources (no per-call allocation overhead)\n"); + printf(" - Consistent API with other context-aware DDS operations\n\n"); - auto start_time = std::chrono::high_resolution_clock::now(); + auto start_time = std::chrono::high_resolution_clock::now(); - for (int handno = 0; handno < 3; handno++) - { - DdTableDeal table_deal{}; - for (int h = 0; h < DDS_HANDS; ++h) { - for (int s = 0; s < DDS_SUITS; ++s) { - table_deal.cards[h][s] = holdings_[handno][s][h]; - } - } - - DdTableResults ddtable; - ParResults pres; - // Use context-aware calc_par API - int res = calc_par( - context, - table_deal, - vulnerability_[handno], - &ddtable, - &pres); - - if (res == RETURN_NO_FAULT) + for (int handno = 0; handno < 3; handno++) { - const char* suit_name = - (trump_suit_[handno] == 0 ? "♠" : + DdTableDeal table_deal{}; + for (int h = 0; h < DDS_HANDS; ++h) { + for (int s = 0; s < DDS_SUITS; ++s) { + table_deal.cards[h][s] = holdings_[handno][s][h]; + } + } + + DdTableResults ddtable; + ParResults pres; + // Use context-aware calc_par API + int res = calc_par( + context, + table_deal, + vulnerability_[handno], + &ddtable, + &pres); + + if (res == RETURN_NO_FAULT) + { + const char* suit_name = + (trump_suit_[handno] == 0 ? "♠" : trump_suit_[handno] == 1 ? "♥" : trump_suit_[handno] == 2 ? "♦" : trump_suit_[handno] == 3 ? "♣" : "NT"); - - printf("Hand %d (%s): Par score = %s\n", + + printf("Hand %d (%s): Par score = %s\n", handno + 1, suit_name, pres.par_score[0]); + } } - } - auto end_time = std::chrono::high_resolution_clock::now(); - auto elapsed = std::chrono::duration_cast( - end_time - start_time).count(); - printf("\nTime (with context): %lld ms\n\n", static_cast(elapsed)); + auto end_time = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast( + end_time - start_time).count(); + printf("\nTime (with context): %lld ms\n\n", static_cast(elapsed)); } void example_mixed_usage() { - printf("\n=== Context for Sequential Operations ===\n\n"); - - // Create a context - useful for managing resources across calls - SolverContext context; - - printf("Best practices when using SolverContext:\n\n"); - - printf("1. Create context once:\n"); - printf(" SolverContext context;\n\n"); - - printf("2. Reuse for multiple calculations:\n"); - for (int i = 0; i < 3; i++) { - DdTableDeal table_deal{}; - for (int h = 0; h < DDS_HANDS; ++h) { - for (int s = 0; s < DDS_SUITS; ++s) { - table_deal.cards[h][s] = holdings_[i][s][h]; - } - } - - DdTableResults ddtable; - ParResults pres; - int res = calc_par( - context, - table_deal, - vulnerability_[i], - &ddtable, - &pres); - if (res == RETURN_NO_FAULT) { - printf(" Hand %d Par: Score = %s\n", i + 1, pres.par_score[0]); + printf("\n=== Context for Sequential Operations ===\n\n"); + + // Create a context - useful for managing resources across calls + SolverContext context; + + printf("Best practices when using SolverContext:\n\n"); + + printf("1. Create context once:\n"); + printf(" SolverContext context;\n\n"); + + printf("2. Reuse for multiple calculations:\n"); + for (int i = 0; i < 3; i++) { + DdTableDeal table_deal{}; + for (int h = 0; h < DDS_HANDS; ++h) { + for (int s = 0; s < DDS_SUITS; ++s) { + table_deal.cards[h][s] = holdings_[i][s][h]; + } + } + + DdTableResults ddtable; + ParResults pres; + int res = calc_par( + context, + table_deal, + vulnerability_[i], + &ddtable, + &pres); + if (res == RETURN_NO_FAULT) { + printf(" Hand %d Par: Score = %s\n", i + 1, pres.par_score[0]); + } } - } - printf("\n3. Solver resources are reused across operations\n"); - printf(" This is active now: calc_par(ctx, ...) uses the provided context\n\n"); + printf("\n3. Solver resources are reused across operations\n"); + printf(" This is active now: calc_par(ctx, ...) uses the provided context\n\n"); } void print_python_example() { - printf("\n=== Python Equivalent Usage ===\n\n"); - printf("Python code for context reuse:\n\n"); - printf(" from dds3 import SolverContext, solve_board\n\n"); - printf(" # Create context once\n"); - printf(" ctx = SolverContext()\n\n"); - printf(" # Reuse for multiple operations\n"); - printf(" for deal in deals:\n"); - printf(" result = solve_board(deal, context=ctx)\n"); - printf(" print(f'Score: {result[\"score\"]}')\n\n"); + printf("\n=== Python Equivalent Usage ===\n\n"); + printf("Python code for context reuse:\n\n"); + printf(" from dds3 import SolverContext, solve_board\n\n"); + printf(" # Create context once\n"); + printf(" ctx = SolverContext()\n\n"); + printf(" # Reuse for multiple operations\n"); + printf(" for deal in deals:\n"); + printf(" result = solve_board(deal, context=ctx)\n"); + printf(" print(f'Score: {result[\"score\"]}')\n\n"); } auto main() -> int { - printf("DDS Examples: Par Calculation with SolverContext\n"); - printf("================================================\n"); - - // Run examples - example_without_context(); - example_with_context(); - example_mixed_usage(); - print_python_example(); - - printf("\n=== Summary ===\n"); - printf("✓ SolverContext provides resource management\n"); - printf("✓ Backward compatible - traditional API still works\n"); - printf("✓ Python bindings demonstrate context advantages\n"); - printf("✓ Transposition table reuse across operations\n"); - printf("✓ Improved efficiency for batch processing\n"); - - return 0; + printf("DDS Examples: Par Calculation with SolverContext\n"); + printf("================================================\n"); + + // Run examples + example_without_context(); + example_with_context(); + example_mixed_usage(); + print_python_example(); + + printf("\n=== Summary ===\n"); + printf("✓ SolverContext provides resource management\n"); + printf("✓ Backward compatible - traditional API still works\n"); + printf("✓ Python bindings demonstrate context advantages\n"); + printf("✓ Transposition table reuse across operations\n"); + printf("✓ Improved efficiency for batch processing\n"); + + return 0; } diff --git a/examples/dealer_par.cpp b/examples/dealer_par.cpp index 72a408a70..d1a6efee2 100644 --- a/examples/dealer_par.cpp +++ b/examples/dealer_par.cpp @@ -20,32 +20,32 @@ auto main() -> int { - DdTableResults DDtable; - ParResultsDealer pres; + DdTableResults DDtable; + ParResultsDealer pres; - int res; - char line[80]; - bool match; + int res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { - set_table(&DDtable, handno); + for (int handno = 0; handno < 3; handno++) + { + set_table(&DDtable, handno); - res = DealerPar(&DDtable, &pres, dealer_hand_[handno], vulnerability_[handno]); + res = DealerPar(&DDtable, &pres, dealer_hand_[handno], vulnerability_[handno]); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_dealer_par(&pres, handno); + match = compare_dealer_par(&pres, handno); - printf("DealerPar, hand %d: %s\n\n", + printf("DealerPar, hand %d: %s\n\n", handno + 1, (match ? "OK" : "ERROR")); - print_table(&DDtable); + print_table(&DDtable); - print_dealer_par(&pres); - } + print_dealer_par(&pres); + } } diff --git a/examples/hands.cpp b/examples/hands.cpp index cf45b2e6d..aa8bc9797 100644 --- a/examples/hands.cpp +++ b/examples/hands.cpp @@ -85,21 +85,21 @@ char pbn_hands_[3][80] = { // third index is hand. unsigned int holdings_[3][4][4] = { - { // North East South West - { RQ|RJ|R6, R8|R7|R3, RK|R5, RA|RT|R9|R4|R2 } , // spades - { RK|R6|R5|R2, RJ|R9|R7, RT|R8|R3, RA|RQ|R4 } , // hearts - { RJ|R8|R5, RA|RT|R7|R6|R4, RK|RQ|R9, R3|R2 } , // diamonds - { RT|R9|R8, RQ|R4, RA|R7|R6|R5|R2, RK|RJ|R3 }}, // clubs - { - { RA|RK|R9|R6, RQ|RJ|RT|R5|R4|R3|R2, 0, R8|R7}, - { RK|RQ|R8, RT, RJ|R9|R7|R5|R4|R3, RA|R6|R2 }, - { RA|R9|R8, R6, RK|R7|R5|R3|R2, RQ|RJ|RT|R4 }, - { RK|R6|R3, RQ|RJ|R8|R2, R9|R4, RA|RT|R7|R5 }}, - { - { R7|R3, RQ|RT|R6, R5, RA|RK|RJ|R9|R8|R4|R2 }, - { RQ|RJ|RT, R8|R7|R6, RA|R9|R5|R4|R3|R2, RK }, - { RA|RQ|R5|R4, RK|RJ|R9, R7|R6|R3|R2, RT|R8 }, - { RT|R7|R5|R2, RA|RQ|R8|R4, RK|R6, RJ|R9|R3 }} + { // North East South West + { RQ|RJ|R6, R8|R7|R3, RK|R5, RA|RT|R9|R4|R2 } , // spades + { RK|R6|R5|R2, RJ|R9|R7, RT|R8|R3, RA|RQ|R4 } , // hearts + { RJ|R8|R5, RA|RT|R7|R6|R4, RK|RQ|R9, R3|R2 } , // diamonds + { RT|R9|R8, RQ|R4, RA|R7|R6|R5|R2, RK|RJ|R3 }}, // clubs + { + { RA|RK|R9|R6, RQ|RJ|RT|R5|R4|R3|R2, 0, R8|R7}, + { RK|RQ|R8, RT, RJ|R9|R7|R5|R4|R3, RA|R6|R2 }, + { RA|R9|R8, R6, RK|R7|R5|R3|R2, RQ|RJ|RT|R4 }, + { RK|R6|R3, RQ|RJ|R8|R2, R9|R4, RA|RT|R7|R5 }}, + { + { R7|R3, RQ|RT|R6, R5, RA|RK|RJ|R9|R8|R4|R2 }, + { RQ|RJ|RT, R8|R7|R6, RA|R9|R5|R4|R3|R2, RK }, + { RA|RQ|R5|R4, RK|RJ|R9, R7|R6|R3|R2, RT|R8 }, + { RT|R7|R5|R2, RA|RQ|R8|R4, RK|R6, RJ|R9|R3 }} }; @@ -114,37 +114,37 @@ char play_sequence_[3][106] = { }; int play_suit_[3][52] = { - { CL, CL, CL, CL, HE, HE, HE, HE, DI, DI, DI, DI, - SP, SP, SP, SP, DI, DI, DI, DI, HE, HE, HE, HE, - CL, CL, CL, CL, SP, SP, SP, SP, HE, HE, HE, HE, - CL, CL, DI, CL, SP, SP, SP, CL, DI, -1, -1, -1, - -1, -1, -1, -1 }, - { SP, DI, SP, SP, HE, HE, HE, HE, HE, SP, HE, HE, - HE, DI, HE, HE, SP, SP, SP, CL, DI, CL, DI, DI, - HE, CL, SP, SP, HE, CL, CL, SP, HE, CL, DI, SP, - DI, DI, DI, CL, SP, SP, CL, DI, CL, DI, CL, CL, - DI, CL, CL, DI }, - { HE, HE, HE, HE, DI, DI, DI, DI, CL, CL, CL, CL, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1 } + { CL, CL, CL, CL, HE, HE, HE, HE, DI, DI, DI, DI, + SP, SP, SP, SP, DI, DI, DI, DI, HE, HE, HE, HE, + CL, CL, CL, CL, SP, SP, SP, SP, HE, HE, HE, HE, + CL, CL, DI, CL, SP, SP, SP, CL, DI, -1, -1, -1, + -1, -1, -1, -1 }, + { SP, DI, SP, SP, HE, HE, HE, HE, HE, SP, HE, HE, + HE, DI, HE, HE, SP, SP, SP, CL, DI, CL, DI, DI, + HE, CL, SP, SP, HE, CL, CL, SP, HE, CL, DI, SP, + DI, DI, DI, CL, SP, SP, CL, DI, CL, DI, CL, CL, + DI, CL, CL, DI }, + { HE, HE, HE, HE, DI, DI, DI, DI, CL, CL, CL, CL, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1 } }; int play_rank_[3][52] = { - { KT, K4, KA, KJ, K8, K4, KK, K9, K5, KA, K9, K2, - K7, K5, K2, KQ, K8, K4, KQ, K3, K3, KA, K6, K7, - K3, K8, KQ, K2, K3, KK, KA, K6, KQ, K5, KJ, KT, - KK, K9, K6, K5, K4, KJ, K8, K6, KJ, -1, -1, -1, - -1, -1, -1, -1 }, - { KQ, K2, K8, KA, KK, KT, K3, K2, KQ, K2, K4, K6, - K8, K6, KJ, KA, K7, KK, K4, K4, K8, K2, KK, K4, - K9, K5, K6, K3, K7, K7, K3, K5, K5, KT, K9, KT, - K3, KQ, KA, K8, K9, KJ, K9, KT, KQ, K5, KA, K6, - KJ, KK, KJ, K7 }, - { KA, KK, KQ, K7, K7, K8, KA, K9, K5, KA, K6, K3, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1 } + { KT, K4, KA, KJ, K8, K4, KK, K9, K5, KA, K9, K2, + K7, K5, K2, KQ, K8, K4, KQ, K3, K3, KA, K6, K7, + K3, K8, KQ, K2, K3, KK, KA, K6, KQ, K5, KJ, KT, + KK, K9, K6, K5, K4, KJ, K8, K6, KJ, -1, -1, -1, + -1, -1, -1, -1 }, + { KQ, K2, K8, KA, KK, KT, K3, K2, KQ, K2, K4, K6, + K8, K6, KJ, KA, K7, KK, K4, K4, K8, K2, KK, K4, + K9, K5, K6, K3, K7, K7, K3, K5, K5, KT, K9, KT, + K3, KQ, KA, K8, K9, KJ, K9, KT, KQ, K5, KA, K6, + KJ, KK, KJ, K7 }, + { KA, KK, KQ, K7, K7, K8, KA, K9, K5, KA, K6, K3, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1 } }; @@ -163,30 +163,30 @@ int cards_solutions3_[3] = { 9, 7, 8 }; // Suits of cards returned. Padded with zeroes. int card_suits_[3][13] = { - { 2, 2, 2, 3, 0, 0, 1, 1, 1, 0, 0, 0, 0 }, - { 3, 3, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1, 2, 2, 0, 1, 1, 3, 3, 0, 0, 0, 0, 0 } + { 2, 2, 2, 3, 0, 0, 1, 1, 1, 0, 0, 0, 0 }, + { 3, 3, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 2, 2, 0, 1, 1, 3, 3, 0, 0, 0, 0, 0 } }; // Ranks for cards returned (2 .. 14). Padded with zeroes. int card_ranks_[3][13] = { - { 5, 8,11,10, 6,12, 2, 6,13, 0, 0, 0, 0 }, - { 2, 8,12,10, 6,12, 5, 0, 0, 0, 0, 0, 0 }, - {14, 3, 7, 5, 5, 9, 6,13, 0, 0, 0, 0, 0 } + { 5, 8,11,10, 6,12, 2, 6,13, 0, 0, 0, 0 }, + { 2, 8,12,10, 6,12, 5, 0, 0, 0, 0, 0, 0 }, + {14, 3, 7, 5, 5, 9, 6,13, 0, 0, 0, 0, 0 } }; // Scores for cards returned. int card_scores_[3][13] = { - { 5, 5, 5, 5, 5, 5, 4, 4, 4, 0, 0, 0, 0 }, - { 4, 4, 4, 3, 3, 3, 2, 0, 0, 0, 0, 0, 0 }, - { 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0 } + { 5, 5, 5, 5, 5, 5, 4, 4, 4, 0, 0, 0, 0 }, + { 4, 4, 4, 3, 3, 3, 2, 0, 0, 0, 0, 0, 0 }, + { 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0 } }; // Equals for cards returned, i.e. equivalent cards (rank vectors). int card_equals_[3][13] = { - { 0, 0, 0, 768, 0,2048, 0, 32, 0, 0,0,0,0}, - { 0, 0,2048, 0, 0,3072, 28, 0,0,0,0,0,0}, - { 0, 4, 64, 0, 28, 0, 0, 0, 0,0,0,0,0} + { 0, 0, 0, 768, 0,2048, 0, 32, 0, 0,0,0,0}, + { 0, 0,2048, 0, 0,3072, 28, 0,0,0,0,0,0}, + { 0, 4, 64, 0, 28, 0, 0, 0, 0,0,0,0,0} }; // Double dummy table. The order here is: @@ -194,9 +194,9 @@ int card_equals_[3][13] = { // Hearts: same // etc. int dd_table_[3][20] = { - { 5, 8, 5, 8, 6, 6, 6, 6, 5, 7, 5, 7, 7, 5, 7, 5, 6, 6, 6, 6 }, - { 4, 9, 4, 9, 10, 2,10, 2, 8, 3, 8, 3, 6, 7, 6, 7, 9, 3, 9, 3 }, - { 3,10, 3,10, 9, 4, 9, 4, 8, 4, 8, 4, 3, 9, 3, 9, 4, 8, 4, 8 } + { 5, 8, 5, 8, 6, 6, 6, 6, 5, 7, 5, 7, 7, 5, 7, 5, 6, 6, 6, 6 }, + { 4, 9, 4, 9, 10, 2,10, 2, 8, 3, 8, 3, 6, 7, 6, 7, 9, 3, 9, 3 }, + { 3,10, 3,10, 9, 4, 9, 4, 8, 4, 8, 4, 3, 9, 3, 9, 4, 8, 4, 8 } }; // Number of results expected for the play analysis. @@ -208,30 +208,30 @@ int trace_count_[3] = { 46, 49, 13 }; // Results expected from the play analysis. Padded with zeroes here. int trace_results_[3][53] = { - {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, - 0, 0, 0, 0 }, - {9, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, + {8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, + 0, 0, 0, 0 }, + {9, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 0, 0, 0, 0 }, - {10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0 } + 0, 0, 0, 0 }, + {10, 10,10,10,10, 10,10,10,10, 10,10,10,10, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 } }; - + char par_scores_[3][2][10] = { - { "NS -110", "EW 110" }, - { "NS 100" , "EW -100" }, - { "NS -300", "EW 300" } + { "NS -110", "EW 110" }, + { "NS 100" , "EW -100" }, + { "NS -300", "EW 300" } }; char par_strings_[3][2][10] = { - { "NS:EW 2S" , "EW:EW 2S" }, - { "NS:EW 4Sx", "EW:EW 4Sx" }, - { "NS:NS 5Hx", "EW:NS 5Hx" } + { "NS:EW 2S" , "EW:EW 2S" }, + { "NS:EW 4Sx", "EW:EW 4Sx" }, + { "NS:NS 5Hx", "EW:NS 5Hx" } }; // Number of dealer par contracts expected. @@ -243,9 +243,9 @@ int dealer_scores_[3] = { -110, 100, -300 }; // Dealer par contracts expected, here only one per Deal. // That is not always the case. char dealer_contracts_[3][4][10] = { - { "2S-EW" , "", "", "" }, - { "4S*-EW-1", "", "", "" }, - { "5H*-NS-2", "", "", "" } + { "2S-EW" , "", "", "" }, + { "4S*-EW-1", "", "", "" }, + { "5H*-NS-2", "", "", "" } }; @@ -255,14 +255,14 @@ char dealer_contracts_[3][4][10] = { unsigned short int bit_map_rank_[16] = { - 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, - 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000 + 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, + 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000 }; unsigned char card_rank_chars_[16] = { - 'x', 'x', '2', '3', '4', '5', '6', '7', - '8', '9', 'T', 'J', 'Q', 'K', 'A', '-' + 'x', 'x', '2', '3', '4', '5', '6', '7', + '8', '9', 'T', 'J', 'Q', 'K', 'A', '-' }; unsigned char card_suit_chars_[5] = { 'S', 'H', 'D', 'C', 'N' }; @@ -272,211 +272,211 @@ unsigned char card_hand_chars_[4] = { 'N', 'E', 'S', 'W' }; auto print_future_tricks(char title[], FutureTricks * fut) -> void { - printf("%s\n", title); + printf("%s\n", title); - printf("%6s %-6s %-6s %-6s %-6s\n", + printf("%6s %-6s %-6s %-6s %-6s\n", "card", "suit", "rank", "equals", "score"); - for (int i = 0; i < fut->cards; i++) - { - char res[15] = ""; - equals_to_string(fut->equals[i], res); - printf("%6d %-6c %-6c %-6s %-6d\n", + for (int i = 0; i < fut->cards; i++) + { + char res[15] = ""; + equals_to_string(fut->equals[i], res); + printf("%6d %-6c %-6c %-6s %-6d\n", i, card_suit_chars_[ fut->suit[i] ], card_rank_chars_[ fut->rank[i] ], res, fut->score[i]); - } - printf("\n"); + } + printf("\n"); } auto equals_to_string(int equals, char * res) -> void { - int pos = 0; - int mask = equals >> 2; - for (int i = 15; i >= 2; i--) - { - if (mask & static_cast(bit_map_rank_[i])) - res[pos++] = static_cast(card_rank_chars_[i]); - } - res[pos] = 0; + int pos = 0; + int mask = equals >> 2; + for (int i = 15; i >= 2; i--) + { + if (mask & static_cast(bit_map_rank_[i])) + res[pos++] = static_cast(card_rank_chars_[i]); + } + res[pos] = 0; } auto compare_future_tricks(FutureTricks * fut, int handno, int solutions) -> bool { - if (solutions == 2) - { - if (fut->cards != cards_solutions2_[handno]) - return false; - } - else if (fut->cards != cards_solutions3_[handno]) - return false; - - for (int i = 0; i < fut->cards; i++) - { - if (fut->suit [i] != card_suits_ [handno][i]) return false; - if (fut->rank [i] != card_ranks_ [handno][i]) return false; - if (fut->equals[i] != card_equals_[handno][i]) return false; - if (fut->score [i] != card_scores_[handno][i]) return false; - } - return true; + if (solutions == 2) + { + if (fut->cards != cards_solutions2_[handno]) + return false; + } + else if (fut->cards != cards_solutions3_[handno]) + return false; + + for (int i = 0; i < fut->cards; i++) + { + if (fut->suit [i] != card_suits_ [handno][i]) return false; + if (fut->rank [i] != card_ranks_ [handno][i]) return false; + if (fut->equals[i] != card_equals_[handno][i]) return false; + if (fut->score [i] != card_scores_[handno][i]) return false; + } + return true; } auto set_table(DdTableResults * table, int handno) -> void { - for (int suit = 0; suit < DDS_STRAINS; suit++) - for (int pl = 0; pl <= 3; pl++) - table->res_table[suit][pl] = dd_table_[handno][4 * suit + pl]; + for (int suit = 0; suit < DDS_STRAINS; suit++) + for (int pl = 0; pl <= 3; pl++) + table->res_table[suit][pl] = dd_table_[handno][4 * suit + pl]; } auto compare_table(DdTableResults * table, int handno) -> bool { - for (int suit = 0; suit < DDS_STRAINS; suit++) - { - for (int pl = 0; pl <= 3; pl++) + for (int suit = 0; suit < DDS_STRAINS; suit++) { - if (table->res_table[suit][pl] != dd_table_[handno][4 * suit + pl]) - return false; + for (int pl = 0; pl <= 3; pl++) + { + if (table->res_table[suit][pl] != dd_table_[handno][4 * suit + pl]) + return false; + } } - } - return true; + return true; } auto print_table(DdTableResults * table) -> void { - printf("%5s %-5s %-5s %-5s %-5s\n", + printf("%5s %-5s %-5s %-5s %-5s\n", "", "North", "South", "East", "West"); - printf("%5s %5d %5d %5d %5d\n", + printf("%5s %5d %5d %5d %5d\n", "NT", table->res_table[4][0], table->res_table[4][2], table->res_table[4][1], table->res_table[4][3]); - for (int suit = 0; suit < DDS_SUITS; suit++) - { - printf("%5c %5d %5d %5d %5d\n", + for (int suit = 0; suit < DDS_SUITS; suit++) + { + printf("%5c %5d %5d %5d %5d\n", card_suit_chars_[suit], table->res_table[suit][0], table->res_table[suit][2], table->res_table[suit][1], table->res_table[suit][3]); - } - printf("\n"); + } + printf("\n"); } auto compare_par(ParResults * par, int handno) -> bool { - if (strcmp(par->par_score[0], par_scores_[handno][0])) return false; - if (strcmp(par->par_score[1], par_scores_[handno][1])) return false; - - if (strcmp(par->par_contracts_string[0], par_strings_[handno][0])) - return false; - if (strcmp(par->par_contracts_string[1], par_strings_[handno][1])) - return false; - return true; + if (strcmp(par->par_score[0], par_scores_[handno][0])) return false; + if (strcmp(par->par_score[1], par_scores_[handno][1])) return false; + + if (strcmp(par->par_contracts_string[0], par_strings_[handno][0])) + return false; + if (strcmp(par->par_contracts_string[1], par_strings_[handno][1])) + return false; + return true; } auto compare_dealer_par(ParResultsDealer * par, int handno) -> bool { - if (par->number != dealer_par_count_[handno]) return false; - if (par->score != dealer_scores_[handno]) return false; - - for (int i = 0; i < par->number; i++) - { - if (strcmp(par->contracts[i], dealer_contracts_[handno][i])) - return false; - } - return true; + if (par->number != dealer_par_count_[handno]) return false; + if (par->score != dealer_scores_[handno]) return false; + + for (int i = 0; i < par->number; i++) + { + if (strcmp(par->contracts[i], dealer_contracts_[handno][i])) + return false; + } + return true; } auto print_par(ParResults * par) -> void { - printf("NS score: %s\n", par->par_score[0]); - printf("EW score: %s\n", par->par_score[1]); - printf("NS list : %s\n", par->par_contracts_string[0]); - printf("EW list : %s\n", par->par_contracts_string[1]); - printf("\n"); + printf("NS score: %s\n", par->par_score[0]); + printf("EW score: %s\n", par->par_score[1]); + printf("NS list : %s\n", par->par_contracts_string[0]); + printf("EW list : %s\n", par->par_contracts_string[1]); + printf("\n"); } auto print_dealer_par(ParResultsDealer * par) -> void { - printf("Score : %d\n", par->score); - printf("Pars : %d\n", par->number); + printf("Score : %d\n", par->score); + printf("Pars : %d\n", par->number); - for (int i = 0; i < par->number; i++) - printf("Par %d : %s\n", i, par->contracts[i]); + for (int i = 0; i < par->number; i++) + printf("Par %d : %s\n", i, par->contracts[i]); - printf("\n"); + printf("\n"); } auto compare_play(SolvedPlay * solved, int handno) -> bool { - if (solved->number != trace_count_[handno]) - { - printf("err %d %d\n", solved->number, trace_count_[handno]); - return false; - } - - for (int i = 0; i < solved->number; i++) - if (solved->tricks[i] != trace_results_[handno][i]) + if (solved->number != trace_count_[handno]) { - printf("error %d %d %d\n", i, solved->tricks[i], - trace_results_[handno][i]); - return false; + printf("err %d %d\n", solved->number, trace_count_[handno]); + return false; } - return true; + for (int i = 0; i < solved->number; i++) + if (solved->tricks[i] != trace_results_[handno][i]) + { + printf("error %d %d %d\n", i, solved->tricks[i], + trace_results_[handno][i]); + return false; + } + + return true; } auto print_bin_play(PlayTraceBin * playp, SolvedPlay * solved) -> void { - printf("Number : %d\n", solved->number); + printf("Number : %d\n", solved->number); - printf("Play %2d: %s %d\n", + printf("Play %2d: %s %d\n", 0, "--", solved->tricks[0]); - for (int i = 1; i < solved->number; i++) - { - printf("Play %2d: %c%c %d\n", + for (int i = 1; i < solved->number; i++) + { + printf("Play %2d: %c%c %d\n", i, card_suit_chars_[playp->suit[i - 1]], card_rank_chars_[playp->rank[i - 1]], solved->tricks[i]); - } - printf("\n"); + } + printf("\n"); } auto print_pbn_play(PlayTracePBN * playp, SolvedPlay * solved) -> void { - printf("Number : %d\n", solved->number); + printf("Number : %d\n", solved->number); - printf("Play %2d: %s %d\n", + printf("Play %2d: %s %d\n", 0, "--", solved->tricks[0]); - for (int i = 1; i < solved->number; i++) - { - printf("Play %2d: %c%c %2d\n", + for (int i = 1; i < solved->number; i++) + { + printf("Play %2d: %c%c %2d\n", i, playp->cards[2 * (i - 1)], playp->cards[2 * i - 1], solved->tricks[i]); - } - printf("\n"); + } + printf("\n"); } @@ -493,201 +493,201 @@ auto print_pbn_play(PlayTracePBN * playp, SolvedPlay * solved) -> void auto print_hand(char title[], unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> void { - int c, h, s, r; - char text[DDS_HAND_LINES][DDS_FULL_LINE]; - - for (int l = 0; l < DDS_HAND_LINES; l++) - { - memset(text[l], ' ', DDS_FULL_LINE); - text[l][DDS_FULL_LINE - 1] = '\0'; - } - - for (h = 0; h < DDS_HANDS; h++) - { - int offset, line; - if (h == 0) - { - offset = DDS_HAND_OFFSET; - line = 0; - } - else if (h == 1) - { - offset = 2 * DDS_HAND_OFFSET; - line = 4; - } - else if (h == 2) - { - offset = DDS_HAND_OFFSET; - line = 8; - } - else + int c, h, s, r; + char text[DDS_HAND_LINES][DDS_FULL_LINE]; + + for (int l = 0; l < DDS_HAND_LINES; l++) { - offset = 0; - line = 4; + memset(text[l], ' ', DDS_FULL_LINE); + text[l][DDS_FULL_LINE - 1] = '\0'; } - for (s = 0; s < DDS_SUITS; s++) + for (h = 0; h < DDS_HANDS; h++) { - c = offset; - for (r = 14; r >= 2; r--) - { - if ((remainCards[h][s] >> 2) & bit_map_rank_[r]) - text[line + s][c++] = static_cast(card_rank_chars_[r]); - } - - if (c == offset) - text[line + s][c++] = '-'; - - if (h != 3) - text[line + s][c] = '\0'; + int offset, line; + if (h == 0) + { + offset = DDS_HAND_OFFSET; + line = 0; + } + else if (h == 1) + { + offset = 2 * DDS_HAND_OFFSET; + line = 4; + } + else if (h == 2) + { + offset = DDS_HAND_OFFSET; + line = 8; + } + else + { + offset = 0; + line = 4; + } + + for (s = 0; s < DDS_SUITS; s++) + { + c = offset; + for (r = 14; r >= 2; r--) + { + if ((remainCards[h][s] >> 2) & bit_map_rank_[r]) + text[line + s][c++] = static_cast(card_rank_chars_[r]); + } + + if (c == offset) + text[line + s][c++] = '-'; + + if (h != 3) + text[line + s][c] = '\0'; + } } - } - printf("%s", title); - char dashes[80]; - int l = static_cast(strlen(title)) - 1; - for (int i = 0; i < l; i++) - dashes[i] = '-'; - dashes[l] = '\0'; - printf("%s\n", dashes); - for (int i = 0; i < DDS_HAND_LINES; i++) - printf("%s\n", text[i]); - printf("\n"); + printf("%s", title); + char dashes[80]; + int l = static_cast(strlen(title)) - 1; + for (int i = 0; i < l; i++) + dashes[i] = '-'; + dashes[l] = '\0'; + printf("%s\n", dashes); + for (int i = 0; i < DDS_HAND_LINES; i++) + printf("%s\n", text[i]); + printf("\n"); } auto print_pbn_hand(char title[], char remainCardsPBN[]) -> void { - unsigned int remainCards[DDS_HANDS][DDS_SUITS]; - convert_pbn(remainCardsPBN, remainCards); - print_hand(title, remainCards); + unsigned int remainCards[DDS_HANDS][DDS_SUITS]; + convert_pbn(remainCardsPBN, remainCards); + print_hand(title, remainCards); } auto convert_pbn(char * dealBuff, unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int { - int buffer_pos = 0, first_hand, card, hand, hand_rel_first, suit_in_hand, h, s; - - for (h = 0; h < DDS_HANDS; h++) - for (s = 0; s < DDS_SUITS; s++) - remainCards[h][s] = 0; + int buffer_pos = 0, first_hand, card, hand, hand_rel_first, suit_in_hand, h, s; + + for (h = 0; h < DDS_HANDS; h++) + for (s = 0; s < DDS_SUITS; s++) + remainCards[h][s] = 0; + + while (((dealBuff[buffer_pos] != 'W') && (dealBuff[buffer_pos] != 'N') && + (dealBuff[buffer_pos] != 'E') && (dealBuff[buffer_pos] != 'S') && + (dealBuff[buffer_pos] != 'w') && (dealBuff[buffer_pos] != 'n') && + (dealBuff[buffer_pos] != 'e') && (dealBuff[buffer_pos] != 's')) && (buffer_pos < 3)) + buffer_pos++; + + if (buffer_pos >= 3) + return 0; + + if ((dealBuff[buffer_pos] == 'N') || (dealBuff[buffer_pos] == 'n')) + first_hand = 0; + else if ((dealBuff[buffer_pos] == 'E') || (dealBuff[buffer_pos] == 'e')) + first_hand = 1; + else if ((dealBuff[buffer_pos] == 'S') || (dealBuff[buffer_pos] == 's')) + first_hand = 2; + else + first_hand = 3; - while (((dealBuff[buffer_pos] != 'W') && (dealBuff[buffer_pos] != 'N') && - (dealBuff[buffer_pos] != 'E') && (dealBuff[buffer_pos] != 'S') && - (dealBuff[buffer_pos] != 'w') && (dealBuff[buffer_pos] != 'n') && - (dealBuff[buffer_pos] != 'e') && (dealBuff[buffer_pos] != 's')) && (buffer_pos < 3)) + buffer_pos++; buffer_pos++; - if (buffer_pos >= 3) - return 0; - - if ((dealBuff[buffer_pos] == 'N') || (dealBuff[buffer_pos] == 'n')) - first_hand = 0; - else if ((dealBuff[buffer_pos] == 'E') || (dealBuff[buffer_pos] == 'e')) - first_hand = 1; - else if ((dealBuff[buffer_pos] == 'S') || (dealBuff[buffer_pos] == 's')) - first_hand = 2; - else - first_hand = 3; - - buffer_pos++; - buffer_pos++; - - hand_rel_first = 0; - suit_in_hand = 0; - - while ((buffer_pos < 80) && (dealBuff[buffer_pos] != '\0')) - { - card = is_a_card(dealBuff[buffer_pos]); - if (card) - { - switch (first_hand) - { - case 0: - hand = hand_rel_first; - break; - case 1: - if (hand_rel_first == 0) - hand = 1; - else if (hand_rel_first == 3) - hand = 0; - else - hand = hand_rel_first + 1; - break; - case 2: - if (hand_rel_first == 0) - hand = 2; - else if (hand_rel_first == 1) - hand = 3; - else - hand = hand_rel_first - 2; - break; - default: - if (hand_rel_first == 0) - hand = 3; - else - hand = hand_rel_first - 1; - } - - remainCards[hand][suit_in_hand] |= - static_cast((bit_map_rank_[card] << 2)); + hand_rel_first = 0; + suit_in_hand = 0; - } - else if (dealBuff[buffer_pos] == '.') - suit_in_hand++; - else if (dealBuff[buffer_pos] == ' ') + while ((buffer_pos < 80) && (dealBuff[buffer_pos] != '\0')) { - hand_rel_first++; - suit_in_hand = 0; + card = is_a_card(dealBuff[buffer_pos]); + if (card) + { + switch (first_hand) + { + case 0: + hand = hand_rel_first; + break; + case 1: + if (hand_rel_first == 0) + hand = 1; + else if (hand_rel_first == 3) + hand = 0; + else + hand = hand_rel_first + 1; + break; + case 2: + if (hand_rel_first == 0) + hand = 2; + else if (hand_rel_first == 1) + hand = 3; + else + hand = hand_rel_first - 2; + break; + default: + if (hand_rel_first == 0) + hand = 3; + else + hand = hand_rel_first - 1; + } + + remainCards[hand][suit_in_hand] |= + static_cast((bit_map_rank_[card] << 2)); + + } + else if (dealBuff[buffer_pos] == '.') + suit_in_hand++; + else if (dealBuff[buffer_pos] == ' ') + { + hand_rel_first++; + suit_in_hand = 0; + } + buffer_pos++; } - buffer_pos++; - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto is_a_card(char cardChar) -> int { - switch (cardChar) - { - case '2': - return 2; - case '3': - return 3; - case '4': - return 4; - case '5': - return 5; - case '6': - return 6; - case '7': - return 7; - case '8': - return 8; - case '9': - return 9; - case 'T': - return 10; - case 'J': - return 11; - case 'Q': - return 12; - case 'K': - return 13; - case 'A': - return 14; - case 't': - return 10; - case 'j': - return 11; - case 'q': - return 12; - case 'k': - return 13; - case 'a': - return 14; - default : - return 0; - } + switch (cardChar) + { + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'T': + return 10; + case 'J': + return 11; + case 'Q': + return 12; + case 'K': + return 13; + case 'A': + return 14; + case 't': + return 10; + case 'j': + return 11; + case 'q': + return 12; + case 'k': + return 13; + case 'a': + return 14; + default : + return 0; + } } diff --git a/examples/hands.hpp b/examples/hands.hpp index 0cda2180b..15b41f877 100644 --- a/examples/hands.hpp +++ b/examples/hands.hpp @@ -47,12 +47,12 @@ auto print_pbn_play(PlayTracePBN * play, SolvedPlay * solved) -> void; auto print_hand(char title[], - unsigned int rank_in_suit[DDS_HANDS][DDS_SUITS]) -> void; + unsigned int rank_in_suit[DDS_HANDS][DDS_SUITS]) -> void; auto print_pbn_hand(char title[], char remainCards[]) -> void; auto convert_pbn(char * dealBuff, - unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; + unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; auto is_a_card(char cardChar) -> int; diff --git a/examples/par.cpp b/examples/par.cpp index eaf65f320..ad6317c04 100644 --- a/examples/par.cpp +++ b/examples/par.cpp @@ -20,32 +20,32 @@ auto main() -> int { - DdTableResults DDtable; - ParResults pres; + DdTableResults DDtable; + ParResults pres; - int res; - char line[80]; - bool match; + int res; + char line[80]; + bool match; - for (int handno = 0; handno < 3; handno++) - { - set_table(&DDtable, handno); + for (int handno = 0; handno < 3; handno++) + { + set_table(&DDtable, handno); - res = Par(&DDtable, &pres, vulnerability_[handno]); + res = Par(&DDtable, &pres, vulnerability_[handno]); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - match = compare_par(&pres, handno); + match = compare_par(&pres, handno); - printf("Par, hand %d: %s\n\n", + printf("Par, hand %d: %s\n\n", handno + 1, (match ? "OK" : "ERROR")); - print_table(&DDtable); + print_table(&DDtable); - print_par(&pres); - } + print_par(&pres); + } } diff --git a/examples/solve_all_boards.cpp b/examples/solve_all_boards.cpp index 5bd0b7fb3..d8f9befc7 100644 --- a/examples/solve_all_boards.cpp +++ b/examples/solve_all_boards.cpp @@ -20,52 +20,52 @@ auto main() -> int { - BoardsPBN bo; - SolvedBoards solved; + BoardsPBN bo; + SolvedBoards solved; - int res; - char line[80]; - bool match; + int res; + char line[80]; + bool match; - bo.no_of_boards = 3; - for (int handno = 0; handno < 3; handno++) - { - bo.deals[handno].trump = trump_suit_[handno]; - bo.deals[handno].first = first_hand_[handno]; + bo.no_of_boards = 3; + for (int handno = 0; handno < 3; handno++) + { + bo.deals[handno].trump = trump_suit_[handno]; + bo.deals[handno].first = first_hand_[handno]; - bo.deals[handno].currentTrickSuit[0] = 0; - bo.deals[handno].currentTrickSuit[1] = 0; - bo.deals[handno].currentTrickSuit[2] = 0; + bo.deals[handno].currentTrickSuit[0] = 0; + bo.deals[handno].currentTrickSuit[1] = 0; + bo.deals[handno].currentTrickSuit[2] = 0; - bo.deals[handno].currentTrickRank[0] = 0; - bo.deals[handno].currentTrickRank[1] = 0; - bo.deals[handno].currentTrickRank[2] = 0; + bo.deals[handno].currentTrickRank[0] = 0; + bo.deals[handno].currentTrickRank[1] = 0; + bo.deals[handno].currentTrickRank[2] = 0; - strcpy(bo.deals[handno].remainCards, pbn_hands_[handno]); + strcpy(bo.deals[handno].remainCards, pbn_hands_[handno]); - bo.target [handno] = -1; - bo.solutions[handno] = 3; - bo.mode [handno] = 0; - } + bo.target [handno] = -1; + bo.solutions[handno] = 3; + bo.mode [handno] = 0; + } - res = SolveAllBoards(&bo, &solved); + res = SolveAllBoards(&bo, &solved); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } - for (int handno = 0; handno < 3; handno++) - { - match = compare_future_tricks(&solved.solved_board[handno], handno, 3); + for (int handno = 0; handno < 3; handno++) + { + match = compare_future_tricks(&solved.solved_board[handno], handno, 3); - sprintf(line, - "SolveAllBoards, hand %d: solutions 3 %s\n", - handno + 1, (match ? "OK" : "ERROR")); + sprintf(line, + "SolveAllBoards, hand %d: solutions 3 %s\n", + handno + 1, (match ? "OK" : "ERROR")); - print_pbn_hand(line, bo.deals[handno].remainCards); + print_pbn_hand(line, bo.deals[handno].remainCards); - print_future_tricks(line, &solved.solved_board[handno]); - } + print_future_tricks(line, &solved.solved_board[handno]); + } } diff --git a/examples/solve_board.cpp b/examples/solve_board.cpp index 196cb3c3b..5bc4f2e08 100644 --- a/examples/solve_board.cpp +++ b/examples/solve_board.cpp @@ -20,70 +20,70 @@ auto main() -> int { - Deal dl; - FutureTricks fut2, // solutions == 2 - fut3; // solutions == 3 - - int target; - int solutions; - int mode; - int threadIndex = 0; - int res; - char line[80]; - bool match2; - bool match3; - - for (int handno = 0; handno < 3; handno++) - { - dl.trump = trump_suit_[handno]; - dl.first = first_hand_[handno]; - - dl.currentTrickSuit[0] = 0; - dl.currentTrickSuit[1] = 0; - dl.currentTrickSuit[2] = 0; - - dl.currentTrickRank[0] = 0; - dl.currentTrickRank[1] = 0; - dl.currentTrickRank[2] = 0; - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = holdings_[handno][s][h]; - - target = -1; - solutions = 3; - mode = 0; - res = SolveBoard(dl, target, solutions, mode, &fut3, threadIndex); - - if (res != RETURN_NO_FAULT) + Deal dl; + FutureTricks fut2, // solutions == 2 + fut3; // solutions == 3 + + int target; + int solutions; + int mode; + int threadIndex = 0; + int res; + char line[80]; + bool match2; + bool match3; + + for (int handno = 0; handno < 3; handno++) { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); + dl.trump = trump_suit_[handno]; + dl.first = first_hand_[handno]; + + dl.currentTrickSuit[0] = 0; + dl.currentTrickSuit[1] = 0; + dl.currentTrickSuit[2] = 0; + + dl.currentTrickRank[0] = 0; + dl.currentTrickRank[1] = 0; + dl.currentTrickRank[2] = 0; + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = holdings_[handno][s][h]; + + target = -1; + solutions = 3; + mode = 0; + res = SolveBoard(dl, target, solutions, mode, &fut3, threadIndex); + + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } + + match3 = compare_future_tricks(&fut3, handno, solutions); + + solutions = 2; + res = SolveBoard(dl, target, solutions, mode, &fut2, threadIndex); + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } + + match2 = compare_future_tricks(&fut2, handno, solutions); + + sprintf(line, + "SolveBoard, hand %d: solutions 3 %s, solutions 2 %s\n", + handno + 1, + (match3 ? "OK" : "ERROR"), + (match2 ? "OK" : "ERROR")); + + print_hand(line, dl.remainCards); + + sprintf(line, "solutions == 3\n"); + print_future_tricks(line, &fut3); + sprintf(line, "solutions == 2\n"); + print_future_tricks(line, &fut2); } - - match3 = compare_future_tricks(&fut3, handno, solutions); - - solutions = 2; - res = SolveBoard(dl, target, solutions, mode, &fut2, threadIndex); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } - - match2 = compare_future_tricks(&fut2, handno, solutions); - - sprintf(line, - "SolveBoard, hand %d: solutions 3 %s, solutions 2 %s\n", - handno + 1, - (match3 ? "OK" : "ERROR"), - (match2 ? "OK" : "ERROR")); - - print_hand(line, dl.remainCards); - - sprintf(line, "solutions == 3\n"); - print_future_tricks(line, &fut3); - sprintf(line, "solutions == 2\n"); - print_future_tricks(line, &fut2); - } } diff --git a/examples/solve_board_pbn.cpp b/examples/solve_board_pbn.cpp index b42b432ca..23327f995 100644 --- a/examples/solve_board_pbn.cpp +++ b/examples/solve_board_pbn.cpp @@ -20,67 +20,67 @@ auto main() -> int { - DealPBN dlPBN; - FutureTricks fut2, // solutions == 2 - fut3; // solutions == 3 - - int target; - int solutions; - int mode; - int res; - char line[80]; - bool match2, - match3; - - for (int handno = 0; handno < 3; handno++) - { - dlPBN.trump = trump_suit_[handno]; - dlPBN.first = first_hand_[handno]; - - dlPBN.currentTrickSuit[0] = 0; - dlPBN.currentTrickSuit[1] = 0; - dlPBN.currentTrickSuit[2] = 0; - - dlPBN.currentTrickRank[0] = 0; - dlPBN.currentTrickRank[1] = 0; - dlPBN.currentTrickRank[2] = 0; - - strcpy(dlPBN.remainCards, pbn_hands_[handno]); - - target = -1; - solutions = 3; - mode = 0; - res = SolveBoardPBN(dlPBN, target, solutions, mode, &fut3, 0); - - if (res != RETURN_NO_FAULT) + DealPBN dlPBN; + FutureTricks fut2, // solutions == 2 + fut3; // solutions == 3 + + int target; + int solutions; + int mode; + int res; + char line[80]; + bool match2, + match3; + + for (int handno = 0; handno < 3; handno++) { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); + dlPBN.trump = trump_suit_[handno]; + dlPBN.first = first_hand_[handno]; + + dlPBN.currentTrickSuit[0] = 0; + dlPBN.currentTrickSuit[1] = 0; + dlPBN.currentTrickSuit[2] = 0; + + dlPBN.currentTrickRank[0] = 0; + dlPBN.currentTrickRank[1] = 0; + dlPBN.currentTrickRank[2] = 0; + + strcpy(dlPBN.remainCards, pbn_hands_[handno]); + + target = -1; + solutions = 3; + mode = 0; + res = SolveBoardPBN(dlPBN, target, solutions, mode, &fut3, 0); + + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } + + match3 = compare_future_tricks(&fut3, handno, solutions); + + solutions = 2; + res = SolveBoardPBN(dlPBN, target, solutions, mode, &fut2, 0); + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + printf("DDS error: %s\n", line); + } + + match2 = compare_future_tricks(&fut2, handno, solutions); + + sprintf(line, + "SolveBoardPBN, hand %d: solutions 3 %s, solutions 2 %s\n", + handno + 1, + (match3 ? "OK" : "ERROR"), + (match2 ? "OK" : "ERROR")); + + print_pbn_hand(line, dlPBN.remainCards); + + sprintf(line, "solutions == 3\n"); + print_future_tricks(line, &fut3); + sprintf(line, "solutions == 2\n"); + print_future_tricks(line, &fut2); } - - match3 = compare_future_tricks(&fut3, handno, solutions); - - solutions = 2; - res = SolveBoardPBN(dlPBN, target, solutions, mode, &fut2, 0); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - printf("DDS error: %s\n", line); - } - - match2 = compare_future_tricks(&fut2, handno, solutions); - - sprintf(line, - "SolveBoardPBN, hand %d: solutions 3 %s, solutions 2 %s\n", - handno + 1, - (match3 ? "OK" : "ERROR"), - (match2 ? "OK" : "ERROR")); - - print_pbn_hand(line, dlPBN.remainCards); - - sprintf(line, "solutions == 3\n"); - print_future_tricks(line, &fut3); - sprintf(line, "solutions == 2\n"); - print_future_tricks(line, &fut2); - } } diff --git a/library/src/ab_search.cpp b/library/src/ab_search.cpp index 5ee42dda9..a487262f8 100644 --- a/library/src/ab_search.cpp +++ b/library/src/ab_search.cpp @@ -32,950 +32,950 @@ const int handDelta[DDS_SUITS] = { 256, 16, 1, 0 }; auto apply_ab_tt_lookup( - Pos * posPoint, - const int target, - const int depth, - const int tricks, - const int hand, - SolverContext& ctx, - bool& scoreFlag) -> bool + Pos * posPoint, + const int target, + const int depth, + const int tricks, + const int hand, + SolverContext& ctx, + bool& scoreFlag) -> bool { - [[maybe_unused]] ThreadData* thrp = ctx.thread_ptr(); - - int limit; - if (ctx.search().node_type_store(0) == MAXNODE) - limit = target - posPoint->tricks_max - 1; - else - limit = tricks - (target - posPoint->tricks_max - 1); - - bool lowerFlag; - TIMER_START(TIMER_NO_LOOKUP, depth); - NodeCards const * cardsP = - ctx.trans_table()->lookup( - tricks, hand, posPoint->aggr, posPoint->hand_dist, - limit, lowerFlag); - TIMER_END(TIMER_NO_LOOKUP, depth); - - // Instrumentation: per-thread TT lookup/hit counters - if (thrp) { - ++thrp->tt_lookup_count; - if (cardsP) - ++thrp->tt_hit_count; - } - - if (!cardsP) - return false; + [[maybe_unused]] ThreadData* thrp = ctx.thread_ptr(); + + int limit; + if (ctx.search().node_type_store(0) == MAXNODE) + limit = target - posPoint->tricks_max - 1; + else + limit = tricks - (target - posPoint->tricks_max - 1); + + bool lowerFlag; + TIMER_START(TIMER_NO_LOOKUP, depth); + NodeCards const * cardsP = + ctx.trans_table()->lookup( + tricks, hand, posPoint->aggr, posPoint->hand_dist, + limit, lowerFlag); + TIMER_END(TIMER_NO_LOOKUP, depth); + + // Instrumentation: per-thread TT lookup/hit counters + if (thrp) { + ++thrp->tt_lookup_count; + if (cardsP) + ++thrp->tt_hit_count; + } + + if (!cardsP) + return false; #ifdef DDS_AB_HITS - DumpRetrieved(thrp->fileRetrieved.GetStream(), - * posPoint, *cardsP, target, depth); + DumpRetrieved(thrp->fileRetrieved.GetStream(), + * posPoint, *cardsP, target, depth); #endif - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = - win_ranks[ posPoint->aggr[ss] ] - [ static_cast(cardsP->least_win[ss]) ]; + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = + win_ranks[ posPoint->aggr[ss] ] + [ static_cast(cardsP->least_win[ss]) ]; - if (cardsP->best_move_rank != 0) - { - ctx.search().best_move_tt(depth).suit = static_cast(cardsP->best_move_suit); - ctx.search().best_move_tt(depth).rank = static_cast(cardsP->best_move_rank); - } + if (cardsP->best_move_rank != 0) + { + ctx.search().best_move_tt(depth).suit = static_cast(cardsP->best_move_suit); + ctx.search().best_move_tt(depth).rank = static_cast(cardsP->best_move_rank); + } - scoreFlag = (ctx.search().node_type_store(0) == MAXNODE ? lowerFlag : ! lowerFlag); + scoreFlag = (ctx.search().node_type_store(0) == MAXNODE ? lowerFlag : ! lowerFlag); - AB_COUNT(AB_MAIN_LOOKUP, scoreFlag, depth); - return true; + AB_COUNT(AB_MAIN_LOOKUP, scoreFlag, depth); + return true; } auto store_ab_tt_result( - Pos * posPoint, - const int target, - const int depth, - const int tricks, - const int hand, - const bool value, - SolverContext& ctx, - const unsigned short our_win_ranks[]) -> void + Pos * posPoint, + const int target, + const int depth, + const int tricks, + const int hand, + const bool value, + SolverContext& ctx, + const unsigned short our_win_ranks[]) -> void { - [[maybe_unused]] ThreadData* thrp = ctx.thread_ptr(); + [[maybe_unused]] ThreadData* thrp = ctx.thread_ptr(); - NodeCards first; - if (value) - { - if (ctx.search().node_type_store(0) == MAXNODE) - { - first.upper_bound = static_cast(tricks + 1); - first.lower_bound = static_cast(target - posPoint->tricks_max); - } - else + NodeCards first; + if (value) { - first.upper_bound = static_cast + if (ctx.search().node_type_store(0) == MAXNODE) + { + first.upper_bound = static_cast(tricks + 1); + first.lower_bound = static_cast(target - posPoint->tricks_max); + } + else + { + first.upper_bound = static_cast (tricks + 1 - target + posPoint->tricks_max); - first.lower_bound = 0; - } - } - else - { - if (ctx.search().node_type_store(0) == MAXNODE) - { - first.upper_bound = static_cast - (target - posPoint->tricks_max - 1); - first.lower_bound = 0; + first.lower_bound = 0; + } } else { - first.upper_bound = static_cast(tricks + 1); - first.lower_bound = static_cast + if (ctx.search().node_type_store(0) == MAXNODE) + { + first.upper_bound = static_cast + (target - posPoint->tricks_max - 1); + first.lower_bound = 0; + } + else + { + first.upper_bound = static_cast(tricks + 1); + first.lower_bound = static_cast (tricks + 1 - target + posPoint->tricks_max + 1); + } } - } - first.best_move_suit = static_cast(ctx.search().best_move(depth).suit); - first.best_move_rank = static_cast(ctx.search().best_move(depth).rank); + first.best_move_suit = static_cast(ctx.search().best_move(depth).suit); + first.best_move_rank = static_cast(ctx.search().best_move(depth).rank); - bool flag = - ((ctx.search().node_type_store(hand) == MAXNODE && value) || + bool flag = + ((ctx.search().node_type_store(hand) == MAXNODE && value) || (ctx.search().node_type_store(hand) == MINNODE && !value)) - ? true : false; - - TIMER_START(TIMER_NO_BUILD, depth); - ctx.trans_table()->add( - tricks, - hand, - posPoint->aggr, - our_win_ranks, - first, - flag); - TIMER_END(TIMER_NO_BUILD, depth); + ? true : false; + + TIMER_START(TIMER_NO_BUILD, depth); + ctx.trans_table()->add( + tricks, + hand, + posPoint->aggr, + our_win_ranks, + first, + flag); + TIMER_END(TIMER_NO_BUILD, depth); #ifdef DDS_AB_HITS - DumpStored(thrp->fileStored.GetStream(), - * posPoint, ctx, first, target, depth); + DumpStored(thrp->fileStored.GetStream(), + * posPoint, ctx, first, target, depth); #endif } static void remove_card( - Pos * posPoint, - const int hand, - MoveType const * mply) + Pos * posPoint, + const int hand, + MoveType const * mply) { - int s = mply->suit; - int r = mply->rank; + int s = mply->suit; + int r = mply->rank; - posPoint->rank_in_suit[hand][s] &= (~bit_map_rank[r]); - posPoint->aggr[s] ^= bit_map_rank[r]; - posPoint->hand_dist[hand] -= handDelta[s]; - posPoint->length[hand][s]--; + posPoint->rank_in_suit[hand][s] &= (~bit_map_rank[r]); + posPoint->aggr[s] ^= bit_map_rank[r]; + posPoint->hand_dist[hand] -= handDelta[s]; + posPoint->length[hand][s]--; } static void restore_card( - Pos * posPoint, - const int hand, - const MoveType& mply) + Pos * posPoint, + const int hand, + const MoveType& mply) { - int s = mply.suit; - int r = mply.rank; + int s = mply.suit; + int r = mply.rank; - posPoint->rank_in_suit[hand][s] |= bit_map_rank[r]; - posPoint->aggr[s] |= bit_map_rank[r]; - posPoint->hand_dist[hand] += handDelta[s]; - posPoint->length[hand][s]++; + posPoint->rank_in_suit[hand][s] |= bit_map_rank[r]; + posPoint->aggr[s] |= bit_map_rank[r]; + posPoint->hand_dist[hand] += handDelta[s]; + posPoint->length[hand][s]++; } bool ab_search( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - /* posPoint points to the current look-ahead position, + /* posPoint points to the current look-ahead position, target is number of tricks to take for the player, depth is the remaining search length, must be positive, the value of the subtree is returned. This is a specialized AB function for hand_rel_first == 0. */ - ThreadData* thrp = ctx.thread_ptr(); - int hand = posPoint->first[depth]; - int tricks = depth >> 2; - bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); - bool value = ! success; + ThreadData* thrp = ctx.thread_ptr(); + int hand = posPoint->first[depth]; + int tricks = depth >> 2; + bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); + bool value = ! success; #ifdef DDS_TOP_LEVEL - ctx.search().nodes()++; + ctx.search().nodes()++; #endif - TIMER_START(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - ctx.search().lowest_win(depth, ss) = 0; + TIMER_START(TIMER_NO_MOVEGEN, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + ctx.search().lowest_win(depth, ss) = 0; - ctx.move_gen().move_gen_0( - tricks, - * posPoint, - ctx.search().best_move(depth), - ctx.search().best_move_tt(depth), - thrp->rel); - ctx.move_gen().purge(tricks, 0, ctx.search().forbidden_moves()); + ctx.move_gen().move_gen_0( + tricks, + * posPoint, + ctx.search().best_move(depth), + ctx.search().best_move_tt(depth), + thrp->rel); + ctx.move_gen().purge(tricks, 0, ctx.search().forbidden_moves()); - TIMER_END(TIMER_NO_MOVEGEN, depth); + TIMER_END(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; - while (1) - { - TIMER_START(TIMER_NO_MAKE, depth); - MoveType const * mply = ctx.move_gen().make_next(tricks, 0, - posPoint->win_ranks[depth]); + while (1) + { + TIMER_START(TIMER_NO_MAKE, depth); + MoveType const * mply = ctx.move_gen().make_next(tricks, 0, + posPoint->win_ranks[depth]); #ifdef DDS_AB_STATS - thrp->ABStats.IncrNode(depth); + thrp->ABStats.IncrNode(depth); #endif - TIMER_END(TIMER_NO_MAKE, depth); + TIMER_END(TIMER_NO_MAKE, depth); - if (mply == NULL) - break; + if (mply == NULL) + break; - make_0(posPoint, depth, mply); + make_0(posPoint, depth, mply); - TIMER_START(TIMER_NO_AB, depth - 1); - value = ab_search_1_ctx(posPoint, target, depth - 1, ctx); - TIMER_END(TIMER_NO_AB, depth - 1); + TIMER_START(TIMER_NO_AB, depth - 1); + value = ab_search_1_ctx(posPoint, target, depth - 1, ctx); + TIMER_END(TIMER_NO_AB, depth - 1); - TIMER_START(TIMER_NO_UNDO, depth); - undo_1(posPoint, depth, * mply); - TIMER_END(TIMER_NO_UNDO, depth); + TIMER_START(TIMER_NO_UNDO, depth); + undo_1(posPoint, depth, * mply); + TIMER_END(TIMER_NO_UNDO, depth); - if (value == success) /* A cut-off? */ - { - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = - posPoint->win_ranks[depth - 1][ss]; + if (value == success) /* A cut-off? */ + { + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = + posPoint->win_ranks[depth - 1][ss]; - ctx.search().best_move(depth) = * mply; + ctx.search().best_move(depth) = * mply; #ifdef DDS_MOVES - ctx.move_gen().register_hit(tricks, 0); + ctx.move_gen().register_hit(tricks, 0); #endif - goto ABexit; + goto ABexit; + } + // Accumulate win_ranks from the explored child to inform subsequent moves + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; + + TIMER_START(TIMER_NO_NEXTMOVE, depth); + TIMER_END(TIMER_NO_NEXTMOVE, depth); } - // Accumulate win_ranks from the explored child to inform subsequent moves - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; - - TIMER_START(TIMER_NO_NEXTMOVE, depth); - TIMER_END(TIMER_NO_NEXTMOVE, depth); - } ABexit: - AB_COUNT(AB_MOVE_LOOP, value, depth); + AB_COUNT(AB_MOVE_LOOP, value, depth); #ifdef DDS_AB_STATS - thrp->ABStats.PrintStats(thrp->fileABstats.GetStream()); + thrp->ABStats.PrintStats(thrp->fileABstats.GetStream()); #endif - return value; + return value; } bool ab_search_0( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - return ab_search_0_ctx(posPoint, target, depth, ctx); + return ab_search_0_ctx(posPoint, target, depth, ctx); } // ctx-enabled implementation static bool ab_search_0_ctx( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - /* posPoint points to the current look-ahead position, + /* posPoint points to the current look-ahead position, target is number of tricks to take for the player, depth is the remaining search length, must be positive, the value of the subtree is returned. This is a specialized AB function for hand_rel_first == 0. */ - ThreadData* thrp = ctx.thread_ptr(); - int trump = thrp->trump; - int hand = posPoint->first[depth]; - int tricks = depth >> 2; + ThreadData* thrp = ctx.thread_ptr(); + int trump = thrp->trump; + int hand = posPoint->first[depth]; + int tricks = depth >> 2; #ifdef DDS_TOP_LEVEL - ctx.search().nodes()++; + ctx.search().nodes()++; #endif - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; - - if (depth >= 20) - { - bool scoreFlag; - if (apply_ab_tt_lookup(posPoint, target, depth, tricks, hand, ctx, scoreFlag)) - return scoreFlag; - } + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; - if (posPoint->tricks_max >= target) - { - AB_COUNT(AB_TARGET_REACHED, true, depth); - return true; - } - else if (posPoint->tricks_max + tricks + 1 < target) - { - AB_COUNT(AB_TARGET_REACHED, false, depth); - return false; - } - else if (depth == 0) /* Maximum depth? */ - { - TIMER_START(TIMER_NO_EVALUATE, depth); - EvalType evalData = evaluate_with_context(posPoint, trump, ctx); - TIMER_END(TIMER_NO_EVALUATE, depth); - - bool value = (evalData.tricks >= target ? true : false); + if (depth >= 20) + { + bool scoreFlag; + if (apply_ab_tt_lookup(posPoint, target, depth, tricks, hand, ctx, scoreFlag)) + return scoreFlag; + } - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = evalData.win_ranks[ss]; + if (posPoint->tricks_max >= target) + { + AB_COUNT(AB_TARGET_REACHED, true, depth); + return true; + } + else if (posPoint->tricks_max + tricks + 1 < target) + { + AB_COUNT(AB_TARGET_REACHED, false, depth); + return false; + } + else if (depth == 0) /* Maximum depth? */ + { + TIMER_START(TIMER_NO_EVALUATE, depth); + EvalType evalData = evaluate_with_context(posPoint, trump, ctx); + TIMER_END(TIMER_NO_EVALUATE, depth); - AB_COUNT(AB_DEPTH_ZERO, value, depth); - return value; - } + bool value = (evalData.tricks >= target ? true : false); - bool res; - TIMER_START(TIMER_NO_QT, depth); - int qtricks = QuickTricks(* posPoint, hand, depth, target, - trump, res, ctx); - TIMER_END(TIMER_NO_QT, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = evalData.win_ranks[ss]; - if (ctx.search().node_type_store(hand) == MAXNODE) - { - if (res) - { - AB_COUNT(AB_QUICKTRICKS, 1, depth); - return (qtricks == 0 ? false : true); + AB_COUNT(AB_DEPTH_ZERO, value, depth); + return value; } - TIMER_START(TIMER_NO_LT, depth); - res = LaterTricksMIN(* posPoint, hand, depth, target, trump, ctx); - TIMER_END(TIMER_NO_LT, depth); + bool res; + TIMER_START(TIMER_NO_QT, depth); + int qtricks = QuickTricks(* posPoint, hand, depth, target, + trump, res, ctx); + TIMER_END(TIMER_NO_QT, depth); - if (! res) + if (ctx.search().node_type_store(hand) == MAXNODE) { - AB_COUNT(AB_LATERTRICKS, true, depth); - return false; + if (res) + { + AB_COUNT(AB_QUICKTRICKS, 1, depth); + return (qtricks == 0 ? false : true); + } + + TIMER_START(TIMER_NO_LT, depth); + res = LaterTricksMIN(* posPoint, hand, depth, target, trump, ctx); + TIMER_END(TIMER_NO_LT, depth); + + if (! res) + { + AB_COUNT(AB_LATERTRICKS, true, depth); + return false; + } } - } - else - { - if (res) + else { - AB_COUNT(AB_QUICKTRICKS, false, depth); - return (qtricks == 0 ? true : false); + if (res) + { + AB_COUNT(AB_QUICKTRICKS, false, depth); + return (qtricks == 0 ? true : false); + } + + TIMER_START(TIMER_NO_LT, depth); + res = LaterTricksMAX(* posPoint, hand, depth, target, trump, ctx); + TIMER_END(TIMER_NO_LT, depth); + + if (res) + { + AB_COUNT(AB_LATERTRICKS, false, depth); + return true; + } } - TIMER_START(TIMER_NO_LT, depth); - res = LaterTricksMAX(* posPoint, hand, depth, target, trump, ctx); - TIMER_END(TIMER_NO_LT, depth); - - if (res) + if (depth < 20) { - AB_COUNT(AB_LATERTRICKS, false, depth); - return true; + bool scoreFlag; + if (apply_ab_tt_lookup(posPoint, target, depth, tricks, hand, ctx, scoreFlag)) + return scoreFlag; } - } - - if (depth < 20) - { - bool scoreFlag; - if (apply_ab_tt_lookup(posPoint, target, depth, tricks, hand, ctx, scoreFlag)) - return scoreFlag; - } - - bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); - bool value = ! success; - - TIMER_START(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - ctx.search().lowest_win(depth, ss) = 0; - - ctx.move_gen().move_gen_0( - tricks, - * posPoint, - ctx.search().best_move(depth), - ctx.search().best_move_tt(depth), - thrp->rel); - - TIMER_END(TIMER_NO_MOVEGEN, depth); - - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; - - while (1) - { - TIMER_START(TIMER_NO_MAKE, depth); - MoveType const * mply = ctx.move_gen().make_next(tricks, 0, - posPoint->win_ranks[depth]); + + bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); + bool value = ! success; + + TIMER_START(TIMER_NO_MOVEGEN, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + ctx.search().lowest_win(depth, ss) = 0; + + ctx.move_gen().move_gen_0( + tricks, + * posPoint, + ctx.search().best_move(depth), + ctx.search().best_move_tt(depth), + thrp->rel); + + TIMER_END(TIMER_NO_MOVEGEN, depth); + + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; + + while (1) + { + TIMER_START(TIMER_NO_MAKE, depth); + MoveType const * mply = ctx.move_gen().make_next(tricks, 0, + posPoint->win_ranks[depth]); #ifdef DDS_AB_STATS - thrp->ABStats.IncrNode(depth); + thrp->ABStats.IncrNode(depth); #endif - TIMER_END(TIMER_NO_MAKE, depth); + TIMER_END(TIMER_NO_MAKE, depth); - if (mply == NULL) - break; + if (mply == NULL) + break; - make_0(posPoint, depth, mply); + make_0(posPoint, depth, mply); - TIMER_START(TIMER_NO_AB, depth - 1); - value = ab_search_1_ctx(posPoint, target, depth - 1, ctx); - TIMER_END(TIMER_NO_AB, depth - 1); + TIMER_START(TIMER_NO_AB, depth - 1); + value = ab_search_1_ctx(posPoint, target, depth - 1, ctx); + TIMER_END(TIMER_NO_AB, depth - 1); - TIMER_START(TIMER_NO_UNDO, depth); - undo_1(posPoint, depth, * mply); - TIMER_END(TIMER_NO_UNDO, depth); + TIMER_START(TIMER_NO_UNDO, depth); + undo_1(posPoint, depth, * mply); + TIMER_END(TIMER_NO_UNDO, depth); - if (value == success) /* A cut-off? */ - { - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = - posPoint->win_ranks[depth - 1][ss]; + if (value == success) /* A cut-off? */ + { + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = + posPoint->win_ranks[depth - 1][ss]; - ctx.search().best_move(depth) = * mply; + ctx.search().best_move(depth) = * mply; #ifdef DDS_MOVES - ctx.move_gen().register_hit(tricks, 0); + ctx.move_gen().register_hit(tricks, 0); #endif - goto ABexit; + goto ABexit; + } + // Accumulate win_ranks from the explored child to inform subsequent moves + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; + + TIMER_START(TIMER_NO_NEXTMOVE, depth); + TIMER_END(TIMER_NO_NEXTMOVE, depth); } - // Accumulate win_ranks from the explored child to inform subsequent moves - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; - - TIMER_START(TIMER_NO_NEXTMOVE, depth); - TIMER_END(TIMER_NO_NEXTMOVE, depth); - } ABexit: - store_ab_tt_result( - posPoint, target, depth, tricks, hand, value, ctx, posPoint->win_ranks[depth]); + store_ab_tt_result( + posPoint, target, depth, tricks, hand, value, ctx, posPoint->win_ranks[depth]); - AB_COUNT(AB_MOVE_LOOP, value, depth); - return value; + AB_COUNT(AB_MOVE_LOOP, value, depth); + return value; } bool ab_search_1( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - return ab_search_1_ctx(posPoint, target, depth, ctx); + return ab_search_1_ctx(posPoint, target, depth, ctx); } static bool ab_search_1_ctx( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - ThreadData* thrp = ctx.thread_ptr(); - int trump = thrp->trump; - int hand = HAND_ID(posPoint->first[depth], 1); - bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); - bool value = ! success; - int tricks = (depth + 3) >> 2; + ThreadData* thrp = ctx.thread_ptr(); + int trump = thrp->trump; + int hand = HAND_ID(posPoint->first[depth], 1); + bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); + bool value = ! success; + int tricks = (depth + 3) >> 2; #ifdef DDS_TOP_LEVEL - ctx.search().nodes()++; + ctx.search().nodes()++; #endif - TIMER_START(TIMER_NO_QT, depth); - int res = QuickTricksSecondHand(* posPoint, hand, depth, target, trump, ctx); - TIMER_END(TIMER_NO_QT, depth); - if (res) - { - AB_COUNT(AB_QUICKTRICKS_2ND, true, depth); - return success; - } + TIMER_START(TIMER_NO_QT, depth); + int res = QuickTricksSecondHand(* posPoint, hand, depth, target, trump, ctx); + TIMER_END(TIMER_NO_QT, depth); + if (res) + { + AB_COUNT(AB_QUICKTRICKS_2ND, true, depth); + return success; + } - TIMER_START(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - ctx.search().lowest_win(depth, ss) = 0; + TIMER_START(TIMER_NO_MOVEGEN, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + ctx.search().lowest_win(depth, ss) = 0; - ctx.move_gen().move_gen_123(tricks, 1, * posPoint); - if (depth == ctx.search().ini_depth()) - ctx.move_gen().purge(tricks, 1, ctx.search().forbidden_moves()); + ctx.move_gen().move_gen_123(tricks, 1, * posPoint); + if (depth == ctx.search().ini_depth()) + ctx.move_gen().purge(tricks, 1, ctx.search().forbidden_moves()); - TIMER_END(TIMER_NO_MOVEGEN, depth); + TIMER_END(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; - while (1) - { - TIMER_START(TIMER_NO_MAKE, depth); - MoveType const * mply = ctx.move_gen().make_next(tricks, 1, posPoint->win_ranks[depth]); + while (1) + { + TIMER_START(TIMER_NO_MAKE, depth); + MoveType const * mply = ctx.move_gen().make_next(tricks, 1, posPoint->win_ranks[depth]); #ifdef DDS_AB_STATS - thrp->ABStats.IncrNode(depth); + thrp->ABStats.IncrNode(depth); #endif - TIMER_END(TIMER_NO_MAKE, depth); + TIMER_END(TIMER_NO_MAKE, depth); - if (mply == NULL) - break; + if (mply == NULL) + break; - make_1(posPoint, depth, mply); + make_1(posPoint, depth, mply); - TIMER_START(TIMER_NO_AB, depth - 1); - value = ab_search_2_ctx(posPoint, target, depth - 1, ctx); - TIMER_END(TIMER_NO_AB, depth - 1); + TIMER_START(TIMER_NO_AB, depth - 1); + value = ab_search_2_ctx(posPoint, target, depth - 1, ctx); + TIMER_END(TIMER_NO_AB, depth - 1); - TIMER_START(TIMER_NO_UNDO, depth); - undo_2(posPoint, depth, * mply); - TIMER_END(TIMER_NO_UNDO, depth); + TIMER_START(TIMER_NO_UNDO, depth); + undo_2(posPoint, depth, * mply); + TIMER_END(TIMER_NO_UNDO, depth); - if (value == success) /* A cut-off? */ - { - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = posPoint->win_ranks[depth - 1][ss]; + if (value == success) /* A cut-off? */ + { + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = posPoint->win_ranks[depth - 1][ss]; - ctx.search().best_move(depth) = * mply; + ctx.search().best_move(depth) = * mply; #ifdef DDS_MOVES - ctx.move_gen().register_hit(tricks, 1); + ctx.move_gen().register_hit(tricks, 1); #endif - goto ABexit; - } + goto ABexit; + } - // Accumulate win_ranks from the explored child to inform subsequent moves - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; + // Accumulate win_ranks from the explored child to inform subsequent moves + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; - TIMER_START(TIMER_NO_NEXTMOVE, depth); - TIMER_END(TIMER_NO_NEXTMOVE, depth); - } + TIMER_START(TIMER_NO_NEXTMOVE, depth); + TIMER_END(TIMER_NO_NEXTMOVE, depth); + } ABexit: - AB_COUNT(AB_MOVE_LOOP, value, depth); - return value; + AB_COUNT(AB_MOVE_LOOP, value, depth); + return value; } bool ab_search_2( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - return ab_search_2_ctx(posPoint, target, depth, ctx); + return ab_search_2_ctx(posPoint, target, depth, ctx); } static bool ab_search_2_ctx( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { #ifdef DDS_AB_STATS - ThreadData* thrp = ctx.thread_ptr(); + ThreadData* thrp = ctx.thread_ptr(); #endif - int hand = HAND_ID(posPoint->first[depth], 2); - bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); - bool value = ! success; - int tricks = (depth + 3) >> 2; + int hand = HAND_ID(posPoint->first[depth], 2); + bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); + bool value = ! success; + int tricks = (depth + 3) >> 2; #ifdef DDS_TOP_LEVEL - ctx.search().nodes()++; + ctx.search().nodes()++; #endif - TIMER_START(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - ctx.search().lowest_win(depth, ss) = 0; + TIMER_START(TIMER_NO_MOVEGEN, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + ctx.search().lowest_win(depth, ss) = 0; - ctx.move_gen().move_gen_123(tricks, 2, * posPoint); - if (depth == ctx.search().ini_depth()) - ctx.move_gen().purge(tricks, 2, ctx.search().forbidden_moves()); + ctx.move_gen().move_gen_123(tricks, 2, * posPoint); + if (depth == ctx.search().ini_depth()) + ctx.move_gen().purge(tricks, 2, ctx.search().forbidden_moves()); - TIMER_END(TIMER_NO_MOVEGEN, depth); + TIMER_END(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; - while (1) - { - TIMER_START(TIMER_NO_MAKE, depth); - MoveType const * mply = ctx.move_gen().make_next(tricks, 2, posPoint->win_ranks[depth]); + while (1) + { + TIMER_START(TIMER_NO_MAKE, depth); + MoveType const * mply = ctx.move_gen().make_next(tricks, 2, posPoint->win_ranks[depth]); - if (mply == NULL) - break; + if (mply == NULL) + break; - make_2(posPoint, depth, mply); + make_2(posPoint, depth, mply); #ifdef DDS_AB_STATS - thrp->ABStats.IncrNode(depth); + thrp->ABStats.IncrNode(depth); #endif - TIMER_END(TIMER_NO_MAKE, depth); + TIMER_END(TIMER_NO_MAKE, depth); - TIMER_START(TIMER_NO_AB, depth - 1); - value = ab_search_3_ctx(posPoint, target, depth - 1, ctx); - TIMER_END(TIMER_NO_AB, depth - 1); + TIMER_START(TIMER_NO_AB, depth - 1); + value = ab_search_3_ctx(posPoint, target, depth - 1, ctx); + TIMER_END(TIMER_NO_AB, depth - 1); - TIMER_START(TIMER_NO_UNDO, depth); - undo_3(posPoint, depth, * mply); - TIMER_END(TIMER_NO_UNDO, depth); + TIMER_START(TIMER_NO_UNDO, depth); + undo_3(posPoint, depth, * mply); + TIMER_END(TIMER_NO_UNDO, depth); - if (value == success) /* A cut-off? */ - { - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = posPoint->win_ranks[depth - 1][ss]; + if (value == success) /* A cut-off? */ + { + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = posPoint->win_ranks[depth - 1][ss]; - ctx.search().best_move(depth) = * mply; + ctx.search().best_move(depth) = * mply; #ifdef DDS_MOVES - ctx.move_gen().register_hit(tricks, 2); + ctx.move_gen().register_hit(tricks, 2); #endif - goto ABexit; - } + goto ABexit; + } - // Accumulate win_ranks from the explored child to inform subsequent moves - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; + // Accumulate win_ranks from the explored child to inform subsequent moves + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss]; - TIMER_START(TIMER_NO_NEXTMOVE, depth); - TIMER_END(TIMER_NO_NEXTMOVE, depth); - } + TIMER_START(TIMER_NO_NEXTMOVE, depth); + TIMER_END(TIMER_NO_NEXTMOVE, depth); + } ABexit: - AB_COUNT(AB_MOVE_LOOP, value, depth); - return value; + AB_COUNT(AB_MOVE_LOOP, value, depth); + return value; } bool ab_search_3( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - return ab_search_3_ctx(posPoint, target, depth, ctx); + return ab_search_3_ctx(posPoint, target, depth, ctx); } static bool ab_search_3_ctx( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) { - /* This is a specialized AB function for hand_rel_first == 3. */ + /* This is a specialized AB function for hand_rel_first == 3. */ - unsigned short int makeWinRank[DDS_SUITS]; + unsigned short int makeWinRank[DDS_SUITS]; #ifdef DDS_AB_STATS - ThreadData* thrp = ctx.thread_ptr(); + ThreadData* thrp = ctx.thread_ptr(); #endif - int hand = HAND_ID(posPoint->first[depth], 3); - bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); - bool value = ! success; + int hand = HAND_ID(posPoint->first[depth], 3); + bool success = (ctx.search().node_type_store(hand) == MAXNODE ? true : false); + bool value = ! success; #ifdef DDS_TOP_LEVEL - ctx.search().nodes()++; + ctx.search().nodes()++; #endif - TIMER_START(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - ctx.search().lowest_win(depth, ss) = 0; - int tricks = (depth + 3) >> 2; + TIMER_START(TIMER_NO_MOVEGEN, depth); + for (int ss = 0; ss < DDS_SUITS; ss++) + ctx.search().lowest_win(depth, ss) = 0; + int tricks = (depth + 3) >> 2; - ctx.move_gen().move_gen_123(tricks, 3, * posPoint); - if (depth == ctx.search().ini_depth()) - ctx.move_gen().purge(tricks, 3, ctx.search().forbidden_moves()); + ctx.move_gen().move_gen_123(tricks, 3, * posPoint); + if (depth == ctx.search().ini_depth()) + ctx.move_gen().purge(tricks, 3, ctx.search().forbidden_moves()); - TIMER_END(TIMER_NO_MOVEGEN, depth); + TIMER_END(TIMER_NO_MOVEGEN, depth); - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = 0; - while (1) - { - TIMER_START(TIMER_NO_MAKE, depth); - MoveType const * mply = ctx.move_gen().make_next(tricks, 3, posPoint->win_ranks[depth]); + while (1) + { + TIMER_START(TIMER_NO_MAKE, depth); + MoveType const * mply = ctx.move_gen().make_next(tricks, 3, posPoint->win_ranks[depth]); #ifdef DDS_AB_STATS - thrp->ABStats.IncrNode(depth); + thrp->ABStats.IncrNode(depth); #endif - TIMER_END(TIMER_NO_MAKE, depth); + TIMER_END(TIMER_NO_MAKE, depth); - if (mply == NULL) - break; + if (mply == NULL) + break; - make_3(posPoint, makeWinRank, depth, mply, ctx); + make_3(posPoint, makeWinRank, depth, mply, ctx); - ctx.search().trick_nodes()++; // As hand_rel_first == 0 + ctx.search().trick_nodes()++; // As hand_rel_first == 0 - if (ctx.search().node_type_store(posPoint->first[depth - 1]) == MAXNODE) - posPoint->tricks_max++; + if (ctx.search().node_type_store(posPoint->first[depth - 1]) == MAXNODE) + posPoint->tricks_max++; - TIMER_START(TIMER_NO_AB, depth - 1); - value = ab_search_0_ctx(posPoint, target, depth - 1, ctx); - TIMER_END(TIMER_NO_AB, depth - 1); + TIMER_START(TIMER_NO_AB, depth - 1); + value = ab_search_0_ctx(posPoint, target, depth - 1, ctx); + TIMER_END(TIMER_NO_AB, depth - 1); - TIMER_START(TIMER_NO_UNDO, depth); - undo_0(posPoint, depth, * mply, ctx); + TIMER_START(TIMER_NO_UNDO, depth); + undo_0(posPoint, depth, * mply, ctx); - if (ctx.search().node_type_store(posPoint->first[depth - 1]) == MAXNODE) - posPoint->tricks_max--; + if (ctx.search().node_type_store(posPoint->first[depth - 1]) == MAXNODE) + posPoint->tricks_max--; - TIMER_END(TIMER_NO_UNDO, depth); + TIMER_END(TIMER_NO_UNDO, depth); - if (value == success) /* A cut-off? */ - { - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] = static_cast( - posPoint->win_ranks[depth - 1][ss] | makeWinRank[ss]); + if (value == success) /* A cut-off? */ + { + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] = static_cast( + posPoint->win_ranks[depth - 1][ss] | makeWinRank[ss]); - ctx.search().best_move(depth) = * mply; + ctx.search().best_move(depth) = * mply; #ifdef DDS_MOVES - ctx.move_gen().register_hit(tricks, 3); + ctx.move_gen().register_hit(tricks, 3); #endif - goto ABexit; + goto ABexit; + } + // Accumulate win_ranks from explored child to inform subsequent moves + for (int ss = 0; ss < DDS_SUITS; ss++) + posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss] | makeWinRank[ss]; + + TIMER_START(TIMER_NO_NEXTMOVE, depth); + TIMER_END(TIMER_NO_NEXTMOVE, depth); } - // Accumulate win_ranks from explored child to inform subsequent moves - for (int ss = 0; ss < DDS_SUITS; ss++) - posPoint->win_ranks[depth][ss] |= posPoint->win_ranks[depth - 1][ss] | makeWinRank[ss]; - - TIMER_START(TIMER_NO_NEXTMOVE, depth); - TIMER_END(TIMER_NO_NEXTMOVE, depth); - } ABexit: - AB_COUNT(AB_MOVE_LOOP, value, depth); - return value; + AB_COUNT(AB_MOVE_LOOP, value, depth); + return value; } void make_0( - Pos * posPoint, - const int depth, - MoveType const * mply) + Pos * posPoint, + const int depth, + MoveType const * mply) { - /* First hand is not changed in next move */ - int h = posPoint->first[depth]; + /* First hand is not changed in next move */ + int h = posPoint->first[depth]; - posPoint->first[depth - 1] = h; - posPoint->move[depth] = * mply; - remove_card(posPoint, h, mply); + posPoint->first[depth - 1] = h; + posPoint->move[depth] = * mply; + remove_card(posPoint, h, mply); } void make_1( - Pos * posPoint, - const int depth, - MoveType const * mply) + Pos * posPoint, + const int depth, + MoveType const * mply) { - /* First hand is not changed in next move */ - int firstHand = posPoint->first[depth]; - posPoint->first[depth - 1] = firstHand; - remove_card(posPoint, HAND_ID(firstHand, 1), mply); + /* First hand is not changed in next move */ + int firstHand = posPoint->first[depth]; + posPoint->first[depth - 1] = firstHand; + remove_card(posPoint, HAND_ID(firstHand, 1), mply); } void make_2( - Pos * posPoint, - const int depth, - MoveType const * mply) + Pos * posPoint, + const int depth, + MoveType const * mply) { - /* First hand is not changed in next move */ - int firstHand = posPoint->first[depth]; - posPoint->first[depth - 1] = firstHand; - remove_card(posPoint, HAND_ID(firstHand, 2), mply); + /* First hand is not changed in next move */ + int firstHand = posPoint->first[depth]; + posPoint->first[depth - 1] = firstHand; + remove_card(posPoint, HAND_ID(firstHand, 2), mply); } void make_3( - Pos * posPoint, - unsigned short trickCards[DDS_SUITS], - const int depth, - MoveType const * mply, - SolverContext& ctx) + Pos * posPoint, + unsigned short trickCards[DDS_SUITS], + const int depth, + MoveType const * mply, + SolverContext& ctx) { - ThreadData* thrp = ctx.thread_ptr(); - int firstHand = posPoint->first[depth]; - - const TrickDataType& data = ctx.move_gen().get_trick_data((depth + 3) >> 2); + ThreadData* thrp = ctx.thread_ptr(); + int firstHand = posPoint->first[depth]; - posPoint->first[depth - 1] = HAND_ID(firstHand, data.rel_winner); - /* Defines who is first in the next move */ + const TrickDataType& data = ctx.move_gen().get_trick_data((depth + 3) >> 2); - int h = HAND_ID(firstHand, 3); - /* Hand pointed to by posPoint->first will lead the next trick */ + posPoint->first[depth - 1] = HAND_ID(firstHand, data.rel_winner); + /* Defines who is first in the next move */ - for (int suit = 0; suit < DDS_SUITS; suit++) - trickCards[suit] = 0; + int h = HAND_ID(firstHand, 3); + /* Hand pointed to by posPoint->first will lead the next trick */ - int ss = data.best_suit; - if (data.play_count[ss] >= 2) - { - // Win by rank when some else played that suit, too. - int rr = data.best_rank; - trickCards[ss] = static_cast - (bit_map_rank[rr] | data.best_sequence); - } + for (int suit = 0; suit < DDS_SUITS; suit++) + trickCards[suit] = 0; - remove_card(posPoint, h, mply); - - // Changes that we may have to undo. - WinnersType * wp = &ctx.search().winners((depth + 3) >> 2); - wp->number = 0; - - for (int st = 0; st < 4; st++) - { - if (data.play_count[st]) + int ss = data.best_suit; + if (data.play_count[ss] >= 2) { - int n = wp->number; - wp->winner[n].suit = st; - wp->winner[n].winnerRank = posPoint->winner[st].rank; - wp->winner[n].winnerHand = posPoint->winner[st].hand; - wp->winner[n].secondRank = posPoint->second_best[st].rank; - wp->winner[n].secondHand = posPoint->second_best[st].hand; - wp->number++; + // Win by rank when some else played that suit, too. + int rr = data.best_rank; + trickCards[ss] = static_cast + (bit_map_rank[rr] | data.best_sequence); + } - int aggr = posPoint->aggr[st]; + remove_card(posPoint, h, mply); - posPoint->winner[st].rank = thrp->rel[aggr].abs_rank[1][st].rank; - posPoint->winner[st].hand = thrp->rel[aggr].abs_rank[1][st].hand; - posPoint->second_best[st].rank = thrp->rel[aggr].abs_rank[2][st].rank; - posPoint->second_best[st].hand = thrp->rel[aggr].abs_rank[2][st].hand; + // Changes that we may have to undo. + WinnersType * wp = &ctx.search().winners((depth + 3) >> 2); + wp->number = 0; + for (int st = 0; st < 4; st++) + { + if (data.play_count[st]) + { + int n = wp->number; + wp->winner[n].suit = st; + wp->winner[n].winnerRank = posPoint->winner[st].rank; + wp->winner[n].winnerHand = posPoint->winner[st].hand; + wp->winner[n].secondRank = posPoint->second_best[st].rank; + wp->winner[n].secondHand = posPoint->second_best[st].hand; + wp->number++; + + int aggr = posPoint->aggr[st]; + + posPoint->winner[st].rank = thrp->rel[aggr].abs_rank[1][st].rank; + posPoint->winner[st].hand = thrp->rel[aggr].abs_rank[1][st].hand; + posPoint->second_best[st].rank = thrp->rel[aggr].abs_rank[2][st].rank; + posPoint->second_best[st].hand = thrp->rel[aggr].abs_rank[2][st].hand; + + } } - } } void undo_0( - Pos * posPoint, - const int depth, - const MoveType& mply, - SolverContext& ctx) + Pos * posPoint, + const int depth, + const MoveType& mply, + SolverContext& ctx) { - restore_card(posPoint, HAND_ID(posPoint->first[depth], 3), mply); - - // Changes that we now undo. - WinnersType const * wp = &ctx.search().winners((depth + 3) >> 2); - - for (int n = 0; n < wp->number; n++) - { - int st = wp->winner[n].suit; - posPoint->winner[st].rank = wp->winner[n].winnerRank; - posPoint->winner[st].hand = wp->winner[n].winnerHand; - posPoint->second_best[st].rank = wp->winner[n].secondRank; - posPoint->second_best[st].hand = wp->winner[n].secondHand; - } + restore_card(posPoint, HAND_ID(posPoint->first[depth], 3), mply); + + // Changes that we now undo. + WinnersType const * wp = &ctx.search().winners((depth + 3) >> 2); + + for (int n = 0; n < wp->number; n++) + { + int st = wp->winner[n].suit; + posPoint->winner[st].rank = wp->winner[n].winnerRank; + posPoint->winner[st].hand = wp->winner[n].winnerHand; + posPoint->second_best[st].rank = wp->winner[n].secondRank; + posPoint->second_best[st].hand = wp->winner[n].secondHand; + } } void undo_1( - Pos * posPoint, - const int depth, - const MoveType& mply) + Pos * posPoint, + const int depth, + const MoveType& mply) { - restore_card(posPoint, posPoint->first[depth], mply); + restore_card(posPoint, posPoint->first[depth], mply); } void undo_2( - Pos * posPoint, - const int depth, - const MoveType& mply) + Pos * posPoint, + const int depth, + const MoveType& mply) { - restore_card(posPoint, HAND_ID(posPoint->first[depth], 1), mply); + restore_card(posPoint, HAND_ID(posPoint->first[depth], 1), mply); } void undo_3( - Pos * posPoint, - const int depth, - const MoveType& mply) + Pos * posPoint, + const int depth, + const MoveType& mply) { - restore_card(posPoint, HAND_ID(posPoint->first[depth], 2), mply); + restore_card(posPoint, HAND_ID(posPoint->first[depth], 2), mply); } EvalType evaluate_with_context( - Pos const * posPoint, - const int trump, - SolverContext& ctx) + Pos const * posPoint, + const int trump, + SolverContext& ctx) { - int s, h, hmax = 0, count = 0, k = 0; - unsigned short rmax = 0; - EvalType eval; + int s, h, hmax = 0, count = 0, k = 0; + unsigned short rmax = 0; + EvalType eval; - int firstHand = posPoint->first[0]; - assert((firstHand >= 0) && (firstHand <= 3)); + int firstHand = posPoint->first[0]; + assert((firstHand >= 0) && (firstHand <= 3)); - for (s = 0; s < DDS_SUITS; s++) - eval.win_ranks[s] = 0; + for (s = 0; s < DDS_SUITS; s++) + eval.win_ranks[s] = 0; - /* Who wins the last trick? */ - if (trump != DDS_NOTRUMP) /* Highest trump card wins */ - { - for (h = 0; h < DDS_HANDS; h++) + /* Who wins the last trick? */ + if (trump != DDS_NOTRUMP) /* Highest trump card wins */ { - if (posPoint->rank_in_suit[h][trump] != 0) - count++; - if (posPoint->rank_in_suit[h][trump] > rmax) - { - hmax = h; - rmax = posPoint->rank_in_suit[h][trump]; - } + for (h = 0; h < DDS_HANDS; h++) + { + if (posPoint->rank_in_suit[h][trump] != 0) + count++; + if (posPoint->rank_in_suit[h][trump] > rmax) + { + hmax = h; + rmax = posPoint->rank_in_suit[h][trump]; + } + } + + if (rmax > 0) /* Trumpcard wins */ + { + if (count >= 2) + eval.win_ranks[trump] = rmax; + + if (ctx.search().node_type_store(hmax) == MAXNODE) + goto maxexit; + else + goto minexit; + } } - if (rmax > 0) /* Trumpcard wins */ - { - if (count >= 2) - eval.win_ranks[trump] = rmax; + /* Who has the highest card in the suit played by 1st hand? */ - if (ctx.search().node_type_store(hmax) == MAXNODE) - goto maxexit; - else - goto minexit; + k = 0; + while (k <= 3) /* Find the card the 1st hand played */ + { + if (posPoint->rank_in_suit[firstHand][k] != 0) /* Is this the card? */ + break; + k++; } - } - /* Who has the highest card in the suit played by 1st hand? */ + assert(k < 4); - k = 0; - while (k <= 3) /* Find the card the 1st hand played */ - { - if (posPoint->rank_in_suit[firstHand][k] != 0) /* Is this the card? */ - break; - k++; - } - - assert(k < 4); - - for (h = 0; h < DDS_HANDS; h++) - { - if (posPoint->rank_in_suit[h][k] != 0) - count++; - if (posPoint->rank_in_suit[h][k] > rmax) + for (h = 0; h < DDS_HANDS; h++) { - hmax = h; - rmax = posPoint->rank_in_suit[h][k]; + if (posPoint->rank_in_suit[h][k] != 0) + count++; + if (posPoint->rank_in_suit[h][k] > rmax) + { + hmax = h; + rmax = posPoint->rank_in_suit[h][k]; + } } - } - if (count >= 2) - eval.win_ranks[k] = rmax; + if (count >= 2) + eval.win_ranks[k] = rmax; - if (ctx.search().node_type_store(hmax) == MAXNODE) - goto maxexit; - else - goto minexit; + if (ctx.search().node_type_store(hmax) == MAXNODE) + goto maxexit; + else + goto minexit; maxexit: - eval.tricks = posPoint->tricks_max + 1; - return eval; + eval.tricks = posPoint->tricks_max + 1; + return eval; minexit: - eval.tricks = posPoint->tricks_max; - return eval; + eval.tricks = posPoint->tricks_max; + return eval; } diff --git a/library/src/ab_stats.cpp b/library/src/ab_stats.cpp index 866d2ee3f..5f332dc04 100644 --- a/library/src/ab_stats.cpp +++ b/library/src/ab_stats.cpp @@ -23,9 +23,9 @@ using namespace std; ABstats::ABstats() { - ABstats::Reset(); - ABstats::ResetCum(); - ABstats::SetNames(); + ABstats::Reset(); + ABstats::ResetCum(); + ABstats::SetNames(); } @@ -36,252 +36,252 @@ ABstats::~ABstats() void ABstats::Reset() { - for (int depth = 0; depth < DDS_MAXDEPTH; depth++) - ABnodes.list[depth] = 0; + for (int depth = 0; depth < DDS_MAXDEPTH; depth++) + ABnodes.list[depth] = 0; - ABnodes.sum = 0; - ABnodes.sumWeighted = 0; + ABnodes.sum = 0; + ABnodes.sumWeighted = 0; - for (int side = 0; side < 2; side++) - { - for (int depth = 0; depth < DDS_MAXDEPTH; depth++) - ABsides[side].list[depth] = 0; + for (int side = 0; side < 2; side++) + { + for (int depth = 0; depth < DDS_MAXDEPTH; depth++) + ABsides[side].list[depth] = 0; - ABsides[side].sum = 0; - ABsides[side].sumWeighted = 0; - } + ABsides[side].sum = 0; + ABsides[side].sumWeighted = 0; + } - for (int place = 0; place < AB_SIZE; place++) - { - for (int depth = 0; depth < DDS_MAXDEPTH; depth++) - ABplaces[place].list[depth] = 0; + for (int place = 0; place < AB_SIZE; place++) + { + for (int depth = 0; depth < DDS_MAXDEPTH; depth++) + ABplaces[place].list[depth] = 0; - ABplaces[place].sum = 0; - ABplaces[place].sumWeighted = 0; - } + ABplaces[place].sum = 0; + ABplaces[place].sumWeighted = 0; + } } void ABstats::ResetCum() { - for (int depth = 0; depth < DDS_MAXDEPTH; depth++) - ABnodesCum.list[depth] = 0; - - ABnodesCum.sumCum = 0; - ABnodesCum.sumCumWeighted = 0; - - for (int side = 0; side < 2; side++) - { - ABsides[side].sumCum = 0; - ABsides[side].sumCumWeighted = 0; - } - - for (int place = 0; place < AB_SIZE; place++) - { - ABplaces[place].sumCum = 0; - ABplaces[place].sumCumWeighted = 0; - } + for (int depth = 0; depth < DDS_MAXDEPTH; depth++) + ABnodesCum.list[depth] = 0; + + ABnodesCum.sumCum = 0; + ABnodesCum.sumCumWeighted = 0; + + for (int side = 0; side < 2; side++) + { + ABsides[side].sumCum = 0; + ABsides[side].sumCumWeighted = 0; + } + + for (int place = 0; place < AB_SIZE; place++) + { + ABplaces[place].sumCum = 0; + ABplaces[place].sumCumWeighted = 0; + } } void ABstats::SetNames() { - name[AB_TARGET_REACHED] = "Target decided"; - name[AB_DEPTH_ZERO] = "depth == 0"; - name[AB_QUICKTRICKS] = "QuickTricks"; - name[AB_QUICKTRICKS_2ND] = "QuickTricks 2nd"; - name[AB_LATERTRICKS] = "LaterTricks"; - name[AB_MAIN_LOOKUP] = "Main lookup"; - name[AB_SIDE_LOOKUP] = "Other lookup"; - name[AB_MOVE_LOOP] = "Move trial"; + name[AB_TARGET_REACHED] = "Target decided"; + name[AB_DEPTH_ZERO] = "depth == 0"; + name[AB_QUICKTRICKS] = "QuickTricks"; + name[AB_QUICKTRICKS_2ND] = "QuickTricks 2nd"; + name[AB_LATERTRICKS] = "LaterTricks"; + name[AB_MAIN_LOOKUP] = "Main lookup"; + name[AB_SIDE_LOOKUP] = "Other lookup"; + name[AB_MOVE_LOOP] = "Move trial"; } void ABstats::IncrPos( - const ABCountType no, - const bool side, - const int depth) + const ABCountType no, + const bool side, + const int depth) { - if (no < 0 || no >= AB_SIZE) - return; + if (no < 0 || no >= AB_SIZE) + return; - ABplaces[no].list[depth]++; - ABplaces[no].sum++; - ABplaces[no].sumWeighted += depth; - ABplaces[no].sumCum++; - ABplaces[no].sumCumWeighted += depth; + ABplaces[no].list[depth]++; + ABplaces[no].sum++; + ABplaces[no].sumWeighted += depth; + ABplaces[no].sumCum++; + ABplaces[no].sumCumWeighted += depth; - const int iside = (side ? 1 : 0); + const int iside = (side ? 1 : 0); - ABsides[iside].list[depth]++; - ABsides[iside].sum++; - ABsides[iside].sumWeighted += depth; - ABsides[iside].sumCum++; - ABsides[iside].sumCumWeighted += depth; + ABsides[iside].list[depth]++; + ABsides[iside].sum++; + ABsides[iside].sumWeighted += depth; + ABsides[iside].sumCum++; + ABsides[iside].sumCumWeighted += depth; } void ABstats::IncrNode(const int depth) { - ABnodes.list[depth]++; - ABnodes.sum++; - ABnodes.sumWeighted += depth; + ABnodes.list[depth]++; + ABnodes.sum++; + ABnodes.sumWeighted += depth; - ABnodesCum.list[depth]++; - ABnodesCum.sumCum++; - ABnodesCum.sumCumWeighted += depth; + ABnodesCum.list[depth]++; + ABnodesCum.sumCum++; + ABnodesCum.sumCumWeighted += depth; } int ABstats::GetNodes() const { - return ABnodes.sum; + return ABnodes.sum; } int ABstats::GetPosCount(const int no) const { - if (no < 0 || no >= AB_SIZE) - return 0; + if (no < 0 || no >= AB_SIZE) + return 0; - return ABplaces[no].sum; + return ABplaces[no].sum; } void ABstats::PrintHeaderPosition(ofstream& fout) const { - fout << "No " << - setw(20) << left << "Return" << - setw(9) << right << "Count" << - setw(6) << "%" << - setw(6) << "d_avg" << - setw(9) << "Cumul" << - setw(6) << "%" << - setw(6) << "d_avg" << "\n"; - - fout << std::string(65, '-') << "\n"; + fout << "No " << + setw(20) << left << "Return" << + setw(9) << right << "Count" << + setw(6) << "%" << + setw(6) << "d_avg" << + setw(9) << "Cumul" << + setw(6) << "%" << + setw(6) << "d_avg" << "\n"; + + fout << std::string(65, '-') << "\n"; } void ABstats::PrintStatsPosition( - ofstream& fout, - const int no, - const string& text, - const ABtracker& abt, - const ABtracker& divisor) const + ofstream& fout, + const int no, + const string& text, + const ABtracker& abt, + const ABtracker& divisor) const { - if (! abt.sumCum) - return; - - fout << setw(2) << (no == -1 ? "" : to_string(no)) << " " << - setw(20) << left << text << - setw(9) << right << abt.sum << - setw(6) << setprecision(1) << fixed << - 100. * abt.sum / static_cast(divisor.sum); - - if (abt.sum) - fout << setw(6) << setprecision(1) << fixed << - abt.sumWeighted / static_cast(abt.sum); - else - fout << setw(6) << ""; - - fout << setw(9) << abt.sumCum << - setw(6) << setprecision(1) << fixed << - 100. * abt.sumCum / static_cast(divisor.sumCum) << - setw(6) << setprecision(1) << fixed << - abt.sumCumWeighted / static_cast(abt.sumCum) << "\n"; + if (! abt.sumCum) + return; + + fout << setw(2) << (no == -1 ? "" : to_string(no)) << " " << + setw(20) << left << text << + setw(9) << right << abt.sum << + setw(6) << setprecision(1) << fixed << + 100. * abt.sum / static_cast(divisor.sum); + + if (abt.sum) + fout << setw(6) << setprecision(1) << fixed << + abt.sumWeighted / static_cast(abt.sum); + else + fout << setw(6) << ""; + + fout << setw(9) << abt.sumCum << + setw(6) << setprecision(1) << fixed << + 100. * abt.sumCum / static_cast(divisor.sumCum) << + setw(6) << setprecision(1) << fixed << + abt.sumCumWeighted / static_cast(abt.sumCum) << "\n"; } void ABstats::PrintHeaderDepth(ofstream& fout) const { - fout << setw(5) << right << "Depth" << - setw(7) << "Nodes" << - setw(7) << "Cumul" << - setw(6) << "Cum%" << - setw(6) << "Cumc%" << - setw(7) << "Branch" << "\n"; - - fout << std::string(38, '-') << "\n"; + fout << setw(5) << right << "Depth" << + setw(7) << "Nodes" << + setw(7) << "Cumul" << + setw(6) << "Cum%" << + setw(6) << "Cumc%" << + setw(7) << "Branch" << "\n"; + + fout << std::string(38, '-') << "\n"; } void ABstats::PrintStatsDepth( - ofstream& fout, - const int depth, - const int cum) const + ofstream& fout, + const int depth, + const int cum) const { - fout << setw(5) << depth << - setw(7) << ABnodes.list[depth] << - setw(7) << ABnodesCum.list[depth] << - setw(6) << setprecision(1) << fixed << - 100. * ABnodesCum.list[depth] / - static_cast(ABnodesCum.sumCum) << - setw(6) << setprecision(1) << fixed << - 100. * cum / static_cast(ABnodesCum.sumCum); - - // "Branching factor" from end of one trick to end of - // the previous trick. - if ((depth % 4 == 1) && - (depth < DDS_MAXDEPTH - 4) && - (ABnodesCum.list[depth + 4] > 0)) - fout << setw(6) << setprecision(2) << fixed << - ABnodesCum.list[depth] / - static_cast(ABnodesCum.list[depth + 4]); - fout << "\n"; + fout << setw(5) << depth << + setw(7) << ABnodes.list[depth] << + setw(7) << ABnodesCum.list[depth] << + setw(6) << setprecision(1) << fixed << + 100. * ABnodesCum.list[depth] / + static_cast(ABnodesCum.sumCum) << + setw(6) << setprecision(1) << fixed << + 100. * cum / static_cast(ABnodesCum.sumCum); + + // "Branching factor" from end of one trick to end of + // the previous trick. + if ((depth % 4 == 1) && + (depth < DDS_MAXDEPTH - 4) && + (ABnodesCum.list[depth + 4] > 0)) + fout << setw(6) << setprecision(2) << fixed << + ABnodesCum.list[depth] / + static_cast(ABnodesCum.list[depth + 4]); + fout << "\n"; } void ABstats::PrintAverageDepth( - ofstream& fout, - const ABtracker& ABsidesSum) const + ofstream& fout, + const ABtracker& ABsidesSum) const { - fout << "\nTotal" << - setw(7) << right << ABnodes.sum << - setw(7) << ABnodesCum.sumCum << "\n"; + fout << "\nTotal" << + setw(7) << right << ABnodes.sum << + setw(7) << ABnodesCum.sumCum << "\n"; + + if (! ABnodesCum.sumCum) + return; - if (! ABnodesCum.sumCum) - return; + fout << setw(5) << left << "Avg" << right; - fout << setw(5) << left << "Avg" << right; + if (ABnodes.sum) + fout << setw(7) << setprecision(1) << fixed << + ABnodes.sumWeighted / static_cast(ABnodes.sum); + else + fout << setw(7) << ""; - if (ABnodes.sum) fout << setw(7) << setprecision(1) << fixed << - ABnodes.sumWeighted / static_cast(ABnodes.sum); - else - fout << setw(7) << ""; - - fout << setw(7) << setprecision(1) << fixed << - ABnodesCum.sumCumWeighted / static_cast(ABnodesCum.sumCum) << - "\n\n"; - - fout << setw(5) << left << "Nodes" << - setw(7) << right << ABnodes.sum << - setw(7) << ABnodesCum.sumCum << "\n"; - - fout << setw(5) << left << "Ends" << - setw(7) << right << ABsidesSum.sum << - setw(7) << ABsidesSum.sumCum << "\n"; - - if (ABsidesSum.sum) - fout << setw(5) << left << "Ratio" << - setw(6) << right << setprecision(0) << fixed << - 100. * ABsidesSum.sum / static_cast(ABnodes.sum) << "%" << - setw(6) << setprecision(0) << fixed << - 100. * ABsidesSum.sumCum / static_cast(ABnodesCum.sumCum) << - "%\n\n"; + ABnodesCum.sumCumWeighted / static_cast(ABnodesCum.sumCum) << + "\n\n"; + + fout << setw(5) << left << "Nodes" << + setw(7) << right << ABnodes.sum << + setw(7) << ABnodesCum.sumCum << "\n"; + + fout << setw(5) << left << "Ends" << + setw(7) << right << ABsidesSum.sum << + setw(7) << ABsidesSum.sumCum << "\n"; + + if (ABsidesSum.sum) + fout << setw(5) << left << "Ratio" << + setw(6) << right << setprecision(0) << fixed << + 100. * ABsidesSum.sum / static_cast(ABnodes.sum) << "%" << + setw(6) << setprecision(0) << fixed << + 100. * ABsidesSum.sumCum / static_cast(ABnodesCum.sumCum) << + "%\n\n"; } void ABstats::PrintHeaderDetail(ofstream& fout) const { - fout << " d" << setw(7) << "Side1" << setw(7) << "Side0"; + fout << " d" << setw(7) << "Side1" << setw(7) << "Side0"; - for (int p = 0; p < AB_SIZE; p++) - fout << setw(6) << p; + for (int p = 0; p < AB_SIZE; p++) + fout << setw(6) << p; - fout << "\n" << std::string(65, '-') << "\n"; + fout << "\n" << std::string(65, '-') << "\n"; } @@ -289,81 +289,81 @@ void ABstats::PrintStatsDetail( ofstream& fout, const int depth) const { - if (ABsides[1].list[depth] == 0 && ABsides[0].list[depth] == 0) - return; + if (ABsides[1].list[depth] == 0 && ABsides[0].list[depth] == 0) + return; - fout << setw(2) << depth << - setw(7) << ABsides[1].list[depth] << - setw(7) << ABsides[0].list[depth]; + fout << setw(2) << depth << + setw(7) << ABsides[1].list[depth] << + setw(7) << ABsides[0].list[depth]; - for (int p = 0; p < AB_SIZE; p++) - fout << setw(6) << ABplaces[p].list[depth]; - fout << "\n"; + for (int p = 0; p < AB_SIZE; p++) + fout << setw(6) << ABplaces[p].list[depth]; + fout << "\n"; } void ABstats::PrintSumDetail(ofstream& fout) const { - fout << std::string(65, '-') << "\n"; + fout << std::string(65, '-') << "\n"; - fout << setw(2) << "S" << - setw(7) << ABsides[1].sum << - setw(7) << ABsides[0].sum; + fout << setw(2) << "S" << + setw(7) << ABsides[1].sum << + setw(7) << ABsides[0].sum; - for (int p = 0; p < AB_SIZE; p++) - fout << setw(6) << ABplaces[p].sum; - fout << "\n\n"; + for (int p = 0; p < AB_SIZE; p++) + fout << setw(6) << ABplaces[p].sum; + fout << "\n\n"; } void ABstats::PrintStats(ofstream& fout) { - ABtracker ABsidesSum; - ABsidesSum.sum = ABsides[1].sum + ABsides[0].sum; - ABsidesSum.sumCum = ABsides[1].sumCum + ABsides[0].sumCum; + ABtracker ABsidesSum; + ABsidesSum.sum = ABsides[1].sum + ABsides[0].sum; + ABsidesSum.sumCum = ABsides[1].sumCum + ABsides[0].sumCum; - if (ABsidesSum.sum) - { - // First table: By side and position. + if (ABsidesSum.sum) + { + // First table: By side and position. - ABstats::PrintHeaderPosition(fout); + ABstats::PrintHeaderPosition(fout); - ABstats::PrintStatsPosition(fout, -1, "Side1", ABsides[1], ABsidesSum); - ABstats::PrintStatsPosition(fout, -1, "Side0", ABsides[0], ABsidesSum); - fout << "\n"; + ABstats::PrintStatsPosition(fout, -1, "Side1", ABsides[1], ABsidesSum); + ABstats::PrintStatsPosition(fout, -1, "Side0", ABsides[0], ABsidesSum); + fout << "\n"; - for (int p = 0; p < AB_SIZE; p++) - ABstats::PrintStatsPosition(fout, p, name[p], ABplaces[p], ABsidesSum); - fout << "\n"; - } + for (int p = 0; p < AB_SIZE; p++) + ABstats::PrintStatsPosition(fout, p, name[p], ABplaces[p], ABsidesSum); + fout << "\n"; + } - ABstats::PrintHeaderDepth(fout); + ABstats::PrintHeaderDepth(fout); - // Second table: By depth. + // Second table: By depth. - int c = 0; - for (int d = DDS_MAXDEPTH - 1; d >= 0; d--) - { - if (ABnodesCum.list[d] == 0) - continue; + int c = 0; + for (int d = DDS_MAXDEPTH - 1; d >= 0; d--) + { + if (ABnodesCum.list[d] == 0) + continue; - c += ABnodesCum.list[d]; - ABstats::PrintStatsDepth(fout, d, c); - } + c += ABnodesCum.list[d]; + ABstats::PrintStatsDepth(fout, d, c); + } - ABstats::PrintAverageDepth(fout, ABsidesSum); + ABstats::PrintAverageDepth(fout, ABsidesSum); #ifdef DDS_AB_DETAILS - // Third table: All the detail. + // Third table: All the detail. - ABstats::PrintHeaderDetail(fout); + ABstats::PrintHeaderDetail(fout); - for (int d = DDS_MAXDEPTH - 1; d >= 0; d--) - ABstats::PrintStatsDetail(fout, d); + for (int d = DDS_MAXDEPTH - 1; d >= 0; d--) + ABstats::PrintStatsDetail(fout, d); - ABstats::PrintSumDetail(fout); + ABstats::PrintSumDetail(fout); #endif } diff --git a/library/src/ab_stats.hpp b/library/src/ab_stats.hpp index 573e50254..d4e28cedf 100644 --- a/library/src/ab_stats.hpp +++ b/library/src/ab_stats.hpp @@ -21,23 +21,23 @@ */ #ifdef DDS_AB_STATS - #define AB_COUNT(a, b, c) thrp->ABStats.IncrPos(a, b, c) + #define AB_COUNT(a, b, c) thrp->ABStats.IncrPos(a, b, c) #else - #define AB_COUNT(a, b, c) + #define AB_COUNT(a, b, c) #endif enum ABCountType { - AB_TARGET_REACHED = 0, - AB_DEPTH_ZERO = 1, - AB_QUICKTRICKS = 2, - AB_QUICKTRICKS_2ND = 3, - AB_LATERTRICKS = 4, - AB_MAIN_LOOKUP = 5, - AB_SIDE_LOOKUP = 6, - AB_MOVE_LOOP = 7, - AB_SIZE = 8 + AB_TARGET_REACHED = 0, + AB_DEPTH_ZERO = 1, + AB_QUICKTRICKS = 2, + AB_QUICKTRICKS_2ND = 3, + AB_LATERTRICKS = 4, + AB_MAIN_LOOKUP = 5, + AB_SIDE_LOOKUP = 6, + AB_MOVE_LOOP = 7, + AB_SIZE = 8 }; constexpr int DDS_MAXDEPTH = 49; @@ -45,11 +45,11 @@ constexpr int DDS_MAXDEPTH = 49; struct ABtracker { - int list[DDS_MAXDEPTH]; - int sum; - int sumWeighted; - int sumCum; - int sumCumWeighted; + int list[DDS_MAXDEPTH]; + int sum; + int sumWeighted; + int sumCum; + int sumCumWeighted; }; @@ -63,80 +63,80 @@ struct ABtracker */ class ABstats { - private: + private: - std::string name[AB_SIZE]; + std::string name[AB_SIZE]; - // A node arises when a new move is generated. - // Not every move leads to an AB termination. - ABtracker ABnodes; - ABtracker ABnodesCum; + // A node arises when a new move is generated. + // Not every move leads to an AB termination. + ABtracker ABnodes; + ABtracker ABnodesCum; - // AB terminations are tracked by side and position. - ABtracker ABsides[2]; - ABtracker ABplaces[AB_SIZE]; + // AB terminations are tracked by side and position. + ABtracker ABsides[2]; + ABtracker ABplaces[AB_SIZE]; - void SetNames(); + void SetNames(); - void PrintHeaderPosition(std::ofstream& fout) const; + void PrintHeaderPosition(std::ofstream& fout) const; - void PrintStatsPosition( - std::ofstream& fout, - const int no, - const std::string& text, - const ABtracker& abt, - const ABtracker& divisor) const; + void PrintStatsPosition( + std::ofstream& fout, + const int no, + const std::string& text, + const ABtracker& abt, + const ABtracker& divisor) const; - void PrintHeaderDepth(std::ofstream& fout) const; + void PrintHeaderDepth(std::ofstream& fout) const; - void PrintStatsDepth( - std::ofstream& fout, - const int depth, - const int cum) const; + void PrintStatsDepth( + std::ofstream& fout, + const int depth, + const int cum) const; - void PrintAverageDepth( - std::ofstream& fout, - const ABtracker& ABsidesSum) const; + void PrintAverageDepth( + std::ofstream& fout, + const ABtracker& ABsidesSum) const; - void PrintHeaderDetail(std::ofstream& fout) const; + void PrintHeaderDetail(std::ofstream& fout) const; - void PrintStatsDetail( - std::ofstream& fout, - const int depth) const; + void PrintStatsDetail( + std::ofstream& fout, + const int depth) const; - void PrintSumDetail(std::ofstream& fout) const; + void PrintSumDetail(std::ofstream& fout) const; - public: + public: - /** + /** * @brief Construct a new ABstats object. * * Initializes the alpha-beta statistics accumulator. */ - ABstats(); + ABstats(); - /** + /** * @brief Destroy the ABstats object and clean up resources. * * Releases all memory and resets the statistics state. */ - ~ABstats(); + ~ABstats(); - void Reset(); + void Reset(); - void ResetCum(); + void ResetCum(); - void IncrPos( - const ABCountType no, - const bool side, - const int depth); + void IncrPos( + const ABCountType no, + const bool side, + const int depth); - void IncrNode(const int depth); + void IncrNode(const int depth); - int GetNodes() const; + int GetNodes() const; - int GetPosCount(int no) const; + int GetPosCount(int no) const; - void PrintStats(std::ofstream& fout); + void PrintStats(std::ofstream& fout); }; diff --git a/library/src/api/PBN.h b/library/src/api/PBN.h index 4ba65cafa..429a94da8 100644 --- a/library/src/api/PBN.h +++ b/library/src/api/PBN.h @@ -23,8 +23,8 @@ * @return 1 if successful, 0 otherwise. */ auto convert_from_pbn( - char const * dealBuff, - unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; + char const * dealBuff, + unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; /** * @brief Convert a PBN-format play trace to binary play trace. @@ -36,5 +36,5 @@ auto convert_from_pbn( * @return 1 if successful, 0 otherwise. */ auto convert_play_from_pbn( - const PlayTracePBN& playPBN, - PlayTraceBin& playBin) -> int; + const PlayTracePBN& playPBN, + PlayTraceBin& playBin) -> int; diff --git a/library/src/api/dds.h b/library/src/api/dds.h index 3160632ca..08ed8df41 100644 --- a/library/src/api/dds.h +++ b/library/src/api/dds.h @@ -11,9 +11,9 @@ // System headers #if defined(DDS_MEMORY_LEAKS) && defined(_MSC_VER) - #define DDS_MEMORY_LEAKS_WIN32 - #define _CRTDBG_MAP_ALLOC - #include + #define DDS_MEMORY_LEAKS_WIN32 + #define _CRTDBG_MAP_ALLOC + #include #endif // Aggregator for the solver's compile-time constants and data model. The diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp index a57f89fdd..b7ca197b4 100644 --- a/library/src/api/dds_api.hpp +++ b/library/src/api/dds_api.hpp @@ -6,77 +6,77 @@ extern "C" { - // Opaque handle type for C#/PInvoke - typedef SolverContext* DDS_SOLVER_CTX; + // Opaque handle type for C#/PInvoke + typedef SolverContext* DDS_SOLVER_CTX; - // Creation - EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default(); + // Creation + EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default(); - EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg); + EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg); - // SolverContext Destruction - EXTERN_C DLLEXPORT void dds_destroy_solvercontext(DDS_SOLVER_CTX ctx); + // SolverContext Destruction + EXTERN_C DLLEXPORT void dds_destroy_solvercontext(DDS_SOLVER_CTX ctx); - // TT Configuration - EXTERN_C DLLEXPORT void dds_configure_tt(DDS_SOLVER_CTX ctx, - TTKind kind, - int defMB, int maxMB); + // TT Configuration + EXTERN_C DLLEXPORT void dds_configure_tt(DDS_SOLVER_CTX ctx, + TTKind kind, + int defMB, int maxMB); - EXTERN_C DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, - int defMB, - int maxMB); + EXTERN_C DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, + int defMB, + int maxMB); - EXTERN_C DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx); + EXTERN_C DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx); - // Resets - EXTERN_C DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx); + // Resets + EXTERN_C DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx); - EXTERN_C DLLEXPORT void dds_reset_best_moves_lite(DDS_SOLVER_CTX ctx); + EXTERN_C DLLEXPORT void dds_reset_best_moves_lite(DDS_SOLVER_CTX ctx); - // Utilities – simple logging passthrough - EXTERN_C DLLEXPORT void dds_log_append(DDS_SOLVER_CTX ctx, - const char* msg); + // Utilities – simple logging passthrough + EXTERN_C DLLEXPORT void dds_log_append(DDS_SOLVER_CTX ctx, + const char* msg); - EXTERN_C DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx); + EXTERN_C DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx); - EXTERN_C DLLEXPORT auto dds_solve_board(DDS_SOLVER_CTX ctx, - const Deal& dl, - int target, - int solutions, - int mode, - FutureTricks* futp) -> int; + EXTERN_C DLLEXPORT auto dds_solve_board(DDS_SOLVER_CTX ctx, + const Deal& dl, + int target, + int solutions, + int mode, + FutureTricks* futp) -> int; - EXTERN_C DLLEXPORT auto dds_solve_board_pbn(DDS_SOLVER_CTX ctx, - const DealPBN& dlpbn, - int target, - int solutions, - int mode, - FutureTricks* futp) -> int; + EXTERN_C DLLEXPORT auto dds_solve_board_pbn(DDS_SOLVER_CTX ctx, + const DealPBN& dlpbn, + int target, + int solutions, + int mode, + FutureTricks* futp) -> int; - EXTERN_C DLLEXPORT auto dds_calc_dd_table( - DDS_SOLVER_CTX ctx, - const DdTableDeal& table_deal, - DdTableResults* table_results) -> int; + EXTERN_C DLLEXPORT auto dds_calc_dd_table( + DDS_SOLVER_CTX ctx, + const DdTableDeal& table_deal, + DdTableResults* table_results) -> int; - EXTERN_C DLLEXPORT auto dds_calc_dd_table_pbn( - DDS_SOLVER_CTX ctx, - const DdTableDealPBN& table_deal, - DdTableResults* table_results) -> int; + EXTERN_C DLLEXPORT auto dds_calc_dd_table_pbn( + DDS_SOLVER_CTX ctx, + const DdTableDealPBN& table_deal, + DdTableResults* table_results) -> int; - EXTERN_C DLLEXPORT auto dds_calc_par( - DDS_SOLVER_CTX ctx, - const DdTableDeal& table_deal, - int vulnerable, - DdTableResults* table_results, - ParResults* par_results) -> int; + EXTERN_C DLLEXPORT auto dds_calc_par( + DDS_SOLVER_CTX ctx, + const DdTableDeal& table_deal, + int vulnerable, + DdTableResults* table_results, + ParResults* par_results) -> int; - EXTERN_C DLLEXPORT auto dds_calc_par_pbn( - DDS_SOLVER_CTX ctx, - const DdTableDealPBN& table_deal_pbn, - int vulnerable, - DdTableResults* table_results, - ParResults* par_results) -> int; + EXTERN_C DLLEXPORT auto dds_calc_par_pbn( + DDS_SOLVER_CTX ctx, + const DdTableDealPBN& table_deal_pbn, + int vulnerable, + DdTableResults* table_results, + ParResults* par_results) -> int; } diff --git a/library/src/api/dds_c_api.cpp b/library/src/api/dds_c_api.cpp index b4c4371eb..75e51d5ab 100644 --- a/library/src/api/dds_c_api.cpp +++ b/library/src/api/dds_c_api.cpp @@ -86,8 +86,8 @@ DLLEXPORT int dds_c_solve_board_pbn(DDS_C_SOLVER_CTX ctx, } DLLEXPORT int dds_c_calc_dd_table(DDS_C_SOLVER_CTX ctx, - const struct DdTableDeal* deal, - struct DdTableResults* results) + const struct DdTableDeal* deal, + struct DdTableResults* results) { if (ctx == nullptr || deal == nullptr || results == nullptr) return RETURN_UNKNOWN_FAULT; @@ -134,7 +134,7 @@ DLLEXPORT int dds_c_calc_par_pbn(DDS_C_SOLVER_CTX ctx, } DLLEXPORT DDS_C_SOLVER_CTX dds_c_create_solvercontext(int tt_kind, - int def_mb, int max_mb) + int def_mb, int max_mb) { try { SolverConfig cfg; @@ -148,8 +148,8 @@ DLLEXPORT DDS_C_SOLVER_CTX dds_c_create_solvercontext(int tt_kind, } DLLEXPORT int dds_c_calc_dd_table_pbn(DDS_C_SOLVER_CTX ctx, - const struct DdTableDealPBN* deal, - struct DdTableResults* results) + const struct DdTableDealPBN* deal, + struct DdTableResults* results) { if (ctx == nullptr || deal == nullptr || results == nullptr) return RETURN_UNKNOWN_FAULT; @@ -163,7 +163,7 @@ DLLEXPORT int dds_c_calc_dd_table_pbn(DDS_C_SOLVER_CTX ctx, } DLLEXPORT void dds_c_configure_tt(DDS_C_SOLVER_CTX ctx, int tt_kind, - int def_mb, int max_mb) + int def_mb, int max_mb) { if (ctx == nullptr) return; @@ -266,8 +266,8 @@ DLLEXPORT int dds_c_par_from_table(const struct DdTableResults* table, } DLLEXPORT int dds_c_sides_par(const struct DdTableResults* table, - struct ParResultsDealer sides_res[2], - int vulnerable) + struct ParResultsDealer sides_res[2], + int vulnerable) { if (table == nullptr || sides_res == nullptr) return RETURN_UNKNOWN_FAULT; @@ -308,8 +308,8 @@ DLLEXPORT int dds_c_dealer_par_bin(const struct DdTableResults* table, } DLLEXPORT int dds_c_sides_par_bin(const struct DdTableResults* table, - struct ParResultsMaster sides_res[2], - int vulnerable) + struct ParResultsMaster sides_res[2], + int vulnerable) { if (table == nullptr || sides_res == nullptr) return RETURN_UNKNOWN_FAULT; @@ -322,7 +322,7 @@ DLLEXPORT int dds_c_sides_par_bin(const struct DdTableResults* table, } DLLEXPORT int dds_c_convert_to_dealer_text_format(const struct ParResultsMaster* par, - char* resp) + char* resp) { if (par == nullptr || resp == nullptr) return RETURN_UNKNOWN_FAULT; diff --git a/library/src/api/dds_c_api.h b/library/src/api/dds_c_api.h index 3c9b16940..d6bb8729a 100644 --- a/library/src/api/dds_c_api.h +++ b/library/src/api/dds_c_api.h @@ -51,8 +51,8 @@ DLLEXPORT int dds_c_solve_board_pbn(DDS_C_SOLVER_CTX ctx, /* Compute the double dummy table for a deal. */ DLLEXPORT int dds_c_calc_dd_table(DDS_C_SOLVER_CTX ctx, - const struct DdTableDeal* deal, - struct DdTableResults* results); + const struct DdTableDeal* deal, + struct DdTableResults* results); /* Compute the par result for a deal (computes the DD table internally). */ DLLEXPORT int dds_c_calc_par(DDS_C_SOLVER_CTX ctx, @@ -76,16 +76,16 @@ DLLEXPORT int dds_c_calc_par_pbn(DDS_C_SOLVER_CTX ctx, 1 = Large, 2 = Pattern (matching enum class TTKind). Returns NULL on failure. */ DLLEXPORT DDS_C_SOLVER_CTX dds_c_create_solvercontext(int tt_kind, - int def_mb, int max_mb); + int def_mb, int max_mb); /* Compute the double dummy table from a PBN-format deal. */ DLLEXPORT int dds_c_calc_dd_table_pbn(DDS_C_SOLVER_CTX ctx, - const struct DdTableDealPBN* deal, - struct DdTableResults* results); + const struct DdTableDealPBN* deal, + struct DdTableResults* results); /* Transposition-table configuration. */ DLLEXPORT void dds_c_configure_tt(DDS_C_SOLVER_CTX ctx, int tt_kind, - int def_mb, int max_mb); + int def_mb, int max_mb); DLLEXPORT void dds_c_resize_tt(DDS_C_SOLVER_CTX ctx, int def_mb, int max_mb); DLLEXPORT void dds_c_clear_tt(DDS_C_SOLVER_CTX ctx); @@ -108,8 +108,8 @@ DLLEXPORT int dds_c_par_from_table(const struct DdTableResults* table, /* Compute par from both the NS and EW dealing sides' viewpoints. */ DLLEXPORT int dds_c_sides_par(const struct DdTableResults* table, - struct ParResultsDealer sides_res[2], - int vulnerable); + struct ParResultsDealer sides_res[2], + int vulnerable); /* Compute par for a specific dealer. */ DLLEXPORT int dds_c_dealer_par(const struct DdTableResults* table, @@ -123,12 +123,12 @@ DLLEXPORT int dds_c_dealer_par_bin(const struct DdTableResults* table, /* Binary (ContractType) variant of dds_c_sides_par. */ DLLEXPORT int dds_c_sides_par_bin(const struct DdTableResults* table, - struct ParResultsMaster sides_res[2], - int vulnerable); + struct ParResultsMaster sides_res[2], + int vulnerable); /* Format a dds_c_dealer_par_bin() result as dealer-oriented text. */ DLLEXPORT int dds_c_convert_to_dealer_text_format(const struct ParResultsMaster* par, - char* resp); + char* resp); /* Format a dds_c_sides_par_bin() result (both sides) as sides-oriented text. par must point to a 2-element array, one entry per side, matching diff --git a/library/src/api/dds_c_data_types.h b/library/src/api/dds_c_data_types.h index cb10c18a8..63461c88c 100644 --- a/library/src/api/dds_c_data_types.h +++ b/library/src/api/dds_c_data_types.h @@ -30,12 +30,12 @@ */ struct FutureTricks { - int nodes; - int cards; - int suit[13]; - int rank[13]; - int equals[13]; - int score[13]; + int nodes; + int cards; + int suit[13]; + int rank[13]; + int equals[13]; + int score[13]; }; /** @@ -49,33 +49,33 @@ struct FutureTricks */ struct Deal { - int trump; - int first; - int currentTrickSuit[3]; - int currentTrickRank[3]; - unsigned int remainCards[DDS_HANDS][DDS_SUITS]; + int trump; + int first; + int currentTrickSuit[3]; + int currentTrickRank[3]; + unsigned int remainCards[DDS_HANDS][DDS_SUITS]; }; struct DdTableDeal { - unsigned int cards[DDS_HANDS][DDS_SUITS]; + unsigned int cards[DDS_HANDS][DDS_SUITS]; }; struct DdTableDealPBN { - char cards[80]; + char cards[80]; }; struct DdTableResults { - int res_table[DDS_STRAINS][DDS_HANDS]; + int res_table[DDS_STRAINS][DDS_HANDS]; }; struct ParResults { - /* index = 0 is NS view and index = 1 + /* index = 0 is NS view and index = 1 is EW view. By 'view' is here meant which side that starts the bidding. */ - char par_score[2][16]; - char par_contracts_string[2][128]; + char par_score[2][16]; + char par_contracts_string[2][128]; }; diff --git a/library/src/api/dds_constants.hpp b/library/src/api/dds_constants.hpp index 2c848d8a5..316dbf4d6 100644 --- a/library/src/api/dds_constants.hpp +++ b/library/src/api/dds_constants.hpp @@ -24,18 +24,18 @@ // --------------------------------------------------------------------------- #if (defined(_WIN32) || defined(__CYGWIN__)) && ! defined(__clang__) - #define DLLEXPORT __declspec(dllexport) - #define STDCALL __stdcall + #define DLLEXPORT __declspec(dllexport) + #define STDCALL __stdcall #else - #define DLLEXPORT - #define STDCALL + #define DLLEXPORT + #define STDCALL #endif #ifdef __cplusplus - #define EXTERN_C extern "C" + #define EXTERN_C extern "C" #else - #define EXTERN_C - #include // make "bool" available + #define EXTERN_C + #include // make "bool" available #endif // --------------------------------------------------------------------------- @@ -89,7 +89,7 @@ constexpr const char TEXT_ZERO_CARDS[] = "Zero cards"; // SolveBoard() constexpr int RETURN_TARGET_TOO_HIGH = -3; constexpr const char TEXT_TARGET_TOO_HIGH[] = - "Target exceeds number of tricks"; + "Target exceeds number of tricks"; // SolveBoard() constexpr int RETURN_DUPLICATE_CARDS = -4; @@ -98,22 +98,22 @@ constexpr const char TEXT_DUPLICATE_CARDS[] = "Cards duplicated"; // SolveBoard() constexpr int RETURN_TARGET_WRONG_LO = -5; constexpr const char TEXT_TARGET_WRONG_LO[] = - "Target is less than -1"; + "Target is less than -1"; // SolveBoard() constexpr int RETURN_TARGET_WRONG_HI = -7; constexpr const char TEXT_TARGET_WRONG_HI[] = - "Target is higher than 13"; + "Target is higher than 13"; // SolveBoard() constexpr int RETURN_SOLNS_WRONG_LO = -8; constexpr const char TEXT_SOLNS_WRONG_LO[] = - "Solutions parameter is less than 1"; + "Solutions parameter is less than 1"; // SolveBoard() constexpr int RETURN_SOLNS_WRONG_HI = -9; constexpr const char TEXT_SOLNS_WRONG_HI[] = - "Solutions parameter is higher than 3"; + "Solutions parameter is higher than 3"; // SolveBoard(), self-explanatory. constexpr int RETURN_TOO_MANY_CARDS = -10; @@ -122,32 +122,32 @@ constexpr const char TEXT_TOO_MANY_CARDS[] = "Too many cards"; // SolveBoard() constexpr int RETURN_SUIT_OR_RANK = -12; constexpr const char TEXT_SUIT_OR_RANK[] = - "currentTrickSuit or currentTrickRank has wrong data"; + "currentTrickSuit or currentTrickRank has wrong data"; // SolveBoard constexpr int RETURN_PLAYED_CARD = -13; constexpr const char TEXT_PLAYED_CARD[] = - "Played card also remains in a hand"; + "Played card also remains in a hand"; // SolveBoard() constexpr int RETURN_CARD_COUNT = -14; constexpr const char TEXT_CARD_COUNT[] = - "Wrong number of remaining cards in a hand"; + "Wrong number of remaining cards in a hand"; // SolveBoard() constexpr int RETURN_THREAD_INDEX = -15; constexpr const char TEXT_THREAD_INDEX[] = - "Thread index is not 0 .. maximum"; + "Thread index is not 0 .. maximum"; // SolveBoard() constexpr int RETURN_MODE_WRONG_LO = -16; constexpr const char TEXT_MODE_WRONG_LO[] = - "Mode parameter is less than 0"; + "Mode parameter is less than 0"; // SolveBoard() constexpr int RETURN_MODE_WRONG_HI = -17; constexpr const char TEXT_MODE_WRONG_HI[] = - "Mode parameter is higher than 2"; + "Mode parameter is higher than 2"; // SolveBoard() constexpr int RETURN_TRUMP_WRONG = -18; @@ -171,33 +171,33 @@ constexpr const char TEXT_PBN_FAULT[] = "PBN string error"; // SolveBoard() and AnalysePlay*() constexpr int RETURN_TOO_MANY_BOARDS = -101; constexpr const char TEXT_TOO_MANY_BOARDS[] = - "Too many Boards requested"; + "Too many Boards requested"; // Returned from multi-threading functions. constexpr int RETURN_THREAD_CREATE = -102; constexpr const char TEXT_THREAD_CREATE[] = - "Could not create threads"; + "Could not create threads"; // Returned from multi-threading functions when something went // wrong while waiting for all threads to complete. constexpr int RETURN_THREAD_WAIT = -103; constexpr const char TEXT_THREAD_WAIT[] = - "Something failed waiting for thread to end"; + "Something failed waiting for thread to end"; // Tried to set a multi-threading system that is not present in DLL. constexpr int RETURN_THREAD_MISSING = -104; constexpr const char TEXT_THREAD_MISSING[] = - "Multi-threading system not present"; + "Multi-threading system not present"; // CalcAllTables*() constexpr int RETURN_NO_SUIT = -201; constexpr const char TEXT_NO_SUIT[] = - "Denomination filter vector has no entries"; + "Denomination filter vector has no entries"; // CalcAllTables*() constexpr int RETURN_TOO_MANY_TABLES = -202; constexpr const char TEXT_TOO_MANY_TABLES[] = - "Too many DD tables requested"; + "Too many DD tables requested"; // SolveAllChunks*() constexpr int RETURN_CHUNK_SIZE = -301; @@ -206,7 +206,7 @@ constexpr const char TEXT_CHUNK_SIZE[] = "Chunk size is less than 1"; // Par(), SidesPar(), SidesParBin(), DealerPar(), DealerParBin() constexpr int RETURN_PAR_TABLE_FAULT = -401; constexpr const char TEXT_PAR_TABLE_FAULT[] = - "Missing double dummy table, or an entry outside the range 0 to 13"; + "Missing double dummy table, or an entry outside the range 0 to 13"; // --------------------------------------------------------------------------- // Solver tuning constants diff --git a/library/src/api/dds_data_types.hpp b/library/src/api/dds_data_types.hpp index 7309e266d..532807ba0 100644 --- a/library/src/api/dds_data_types.hpp +++ b/library/src/api/dds_data_types.hpp @@ -25,7 +25,7 @@ #include #include #include // card-representation lookup tables (lho/rho/partner, - // bit_map_rank, card_rank/suit/hand) + // bit_map_rank, card_rank/suit/hand) // =========================================================================== // Legacy plain-old-data structures (not part of the pure-C ABI shim) @@ -42,11 +42,11 @@ */ struct DealPBN { - int trump; - int first; - int currentTrickSuit[3]; - int currentTrickRank[3]; - char remainCards[80]; + int trump; + int first; + int currentTrickSuit[3]; + int currentTrickRank[3]; + char remainCards[80]; }; @@ -61,11 +61,11 @@ struct DealPBN */ struct Boards { - int no_of_boards; - struct Deal deals[MAXNOOFBOARDS]; - int target[MAXNOOFBOARDS]; - int solutions[MAXNOOFBOARDS]; - int mode[MAXNOOFBOARDS]; + int no_of_boards; + struct Deal deals[MAXNOOFBOARDS]; + int target[MAXNOOFBOARDS]; + int solutions[MAXNOOFBOARDS]; + int mode[MAXNOOFBOARDS]; }; /** @@ -78,11 +78,11 @@ struct Boards */ struct BoardsPBN { - int no_of_boards; ///< Number of boards to solve - struct DealPBN deals[MAXNOOFBOARDS]; ///< Array of deals in PBN format - int target[MAXNOOFBOARDS]; ///< Target tricks for each board - int solutions[MAXNOOFBOARDS]; ///< Solution mode for each board - int mode[MAXNOOFBOARDS]; ///< Solve mode for each board + int no_of_boards; ///< Number of boards to solve + struct DealPBN deals[MAXNOOFBOARDS]; ///< Array of deals in PBN format + int target[MAXNOOFBOARDS]; ///< Target tricks for each board + int solutions[MAXNOOFBOARDS]; ///< Solution mode for each board + int mode[MAXNOOFBOARDS]; ///< Solve mode for each board }; /** @@ -95,153 +95,153 @@ struct BoardsPBN */ struct SolvedBoards { - int no_of_boards; ///< Number of solved boards - struct FutureTricks solved_board[MAXNOOFBOARDS]; ///< Array of solutions + int no_of_boards; ///< Number of solved boards + struct FutureTricks solved_board[MAXNOOFBOARDS]; ///< Array of solutions }; struct DdTableDeals { - int no_of_tables; - struct DdTableDeal deals[MAXNOOFTABLES * DDS_STRAINS]; + int no_of_tables; + struct DdTableDeal deals[MAXNOOFTABLES * DDS_STRAINS]; }; struct DdTableDealsPBN { - int no_of_tables; - struct DdTableDealPBN deals[MAXNOOFTABLES * DDS_STRAINS]; + int no_of_tables; + struct DdTableDealPBN deals[MAXNOOFTABLES * DDS_STRAINS]; }; struct DdTablesRes { - int no_of_boards; - struct DdTableResults results[MAXNOOFTABLES * DDS_STRAINS]; + int no_of_boards; + struct DdTableResults results[MAXNOOFTABLES * DDS_STRAINS]; }; struct AllParResults { - struct ParResults par_results[MAXNOOFTABLES]; + struct ParResults par_results[MAXNOOFTABLES]; }; struct ParResultsDealer { - /* number: Number of contracts yielding the par score. + /* number: Number of contracts yielding the par score. score: Par score for the specified dealer hand. contracts: Par contract text strings. The first contract is in contracts[0], the last one in contracts[number-1]. The detailed text format is given in the DLL interface document. - */ - int number; - int score; - char contracts[10][10]; + */ + int number; + int score; + char contracts[10][10]; }; struct ContractType { - int under_tricks; /* 0 = make 1-13 = sacrifice */ - int over_tricks; /* 0-3, e.g. 1 for 4S + 1. */ - int level; /* 1-7 */ - int denom; /* 0 = No Trumps, 1 = trump Spades, 2 = trump Hearts, - 3 = trump Diamonds, 4 = trump Clubs */ - int seats; /* One of the cases N, E, W, S, NS, EW; + int under_tricks; /* 0 = make 1-13 = sacrifice */ + int over_tricks; /* 0-3, e.g. 1 for 4S + 1. */ + int level; /* 1-7 */ + int denom; /* 0 = No Trumps, 1 = trump Spades, 2 = trump Hearts, + 3 = trump Diamonds, 4 = trump Clubs */ + int seats; /* One of the cases N, E, W, S, NS, EW; 0 = N 1 = E, 2 = S, 3 = W, 4 = NS, 5 = EW */ }; struct ParResultsMaster { - int score; /* Sign according to the NS view */ - int number; /* Number of contracts giving the par score */ - struct ContractType contracts[10]; /* Par contracts */ + int score; /* Sign according to the NS view */ + int number; /* Number of contracts giving the par score */ + struct ContractType contracts[10]; /* Par contracts */ }; struct ParTextResults { - char par_text[2][128]; /* Short text for par information, e.g. - Par -110: EW 2S EW 2D+1 */ - bool equal; /* true in the normal case when it does not matter who - starts the bidding. Otherwise, false. */ + char par_text[2][128]; /* Short text for par information, e.g. + Par -110: EW 2S EW 2D+1 */ + bool equal; /* true in the normal case when it does not matter who + starts the bidding. Otherwise, false. */ }; struct PlayTraceBin { - int number; - int suit[52]; - int rank[52]; + int number; + int suit[52]; + int rank[52]; }; struct PlayTracePBN { - int number; - char cards[106]; + int number; + char cards[106]; }; struct SolvedPlay { - int number; - int tricks[53]; + int number; + int tricks[53]; }; struct PlayTracesBin { - int no_of_boards; - struct PlayTraceBin plays[MAXNOOFBOARDS]; + int no_of_boards; + struct PlayTraceBin plays[MAXNOOFBOARDS]; }; struct PlayTracesPBN { - int no_of_boards; - struct PlayTracePBN plays[MAXNOOFBOARDS]; + int no_of_boards; + struct PlayTracePBN plays[MAXNOOFBOARDS]; }; struct SolvedPlays { - int no_of_boards; - struct SolvedPlay solved[MAXNOOFBOARDS]; + int no_of_boards; + struct SolvedPlay solved[MAXNOOFBOARDS]; }; struct DDSInfo { - // Version 2.8.0 has 2, 8, 0 and a string of 2.8.0 - int major, minor, patch; - char version_string[10]; - - // Currently 0 = unknown, 1 = Windows, 2 = Cygwin, 3 = Linux, 4 = Apple - int system; - - // We know 32 and 64-bit systems. - int numBits; - - // Currently 0 = unknown, 1 = Microsoft Visual C++, 2 = mingw, - // 3 = GNU g++, 4 = clang - int compiler; - - // Currently 0 = none, 1 = DllMain, 2 = Unix-style - int constructor; - - int numCores; - - // Currently - // 0 = none, - // 1 = Windows (native), - // 2 = OpenMP, - // 3 = GCD, - // 4 = Boost, - // 5 = STL, - // 6 = TBB, - // 7 = STLIMPL (for_each), experimental only - // 8 = PPLIMPL (for_each), experimental only - int threading; - - // The actual number of threads configured - int noOfThreads; - - // This will break if there are > 128 threads... - // The string is of the form LLLSSS meaning 3 large TT memories - // and 3 small ones. - char threadSizes[128]; - - char systemString[1024]; + // Version 2.8.0 has 2, 8, 0 and a string of 2.8.0 + int major, minor, patch; + char version_string[10]; + + // Currently 0 = unknown, 1 = Windows, 2 = Cygwin, 3 = Linux, 4 = Apple + int system; + + // We know 32 and 64-bit systems. + int numBits; + + // Currently 0 = unknown, 1 = Microsoft Visual C++, 2 = mingw, + // 3 = GNU g++, 4 = clang + int compiler; + + // Currently 0 = none, 1 = DllMain, 2 = Unix-style + int constructor; + + int numCores; + + // Currently + // 0 = none, + // 1 = Windows (native), + // 2 = OpenMP, + // 3 = GCD, + // 4 = Boost, + // 5 = STL, + // 6 = TBB, + // 7 = STLIMPL (for_each), experimental only + // 8 = PPLIMPL (for_each), experimental only + int threading; + + // The actual number of threads configured + int noOfThreads; + + // This will break if there are > 128 threads... + // The string is of the form LLLSSS meaning 3 large TT memories + // and 3 small ones. + char threadSizes[128]; + + char systemString[1024]; }; @@ -257,10 +257,10 @@ struct DDSInfo */ struct MoveType { - int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) - int rank; ///< Rank of the card (2-14: 2 through Ace) - int sequence; ///< Whether this move is the first in a sequence - int weight; ///< Weight used for sorting during move generation + int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) + int rank; ///< Rank of the card (2-14: 2 through Ace) + int sequence; ///< Whether this move is the first in a sequence + int weight; ///< Weight used for sorting during move generation }; /** @@ -271,9 +271,9 @@ struct MoveType */ struct MovePlyType { - MoveType move[14]; ///< Array of possible moves (max 13 cards + sentinel) - int current; ///< Index of current move being considered - int last; ///< Index of last valid move in array + MoveType move[14]; ///< Array of possible moves (max 13 cards + sentinel) + int current; ///< Index of current move being considered + int last; ///< Index of last valid move in array }; /** @@ -283,8 +283,8 @@ struct MovePlyType */ struct HighCardType { - int rank; ///< Rank of the high card (2-14) - int hand; ///< Hand holding the card (0-3: N, E, S, W) + int rank; ///< Rank of the high card (2-14) + int hand; ///< Hand holding the card (0-3: N, E, S, W) }; /** @@ -296,18 +296,18 @@ struct HighCardType */ struct Pos { - unsigned short int rank_in_suit[DDS_HANDS][DDS_SUITS]; ///< Bitmask of ranks held by each hand in each suit - unsigned short int aggr[DDS_SUITS]; ///< Aggregate bitmask of all cards in each suit - unsigned char length[DDS_HANDS][DDS_SUITS]; ///< Number of cards each hand holds in each suit - int hand_dist[DDS_HANDS]; ///< Total number of cards held by each hand - - unsigned short int win_ranks[50][DDS_SUITS]; ///< Cards that win by rank at each depth - int first[50]; ///< Hand that leads the trick for each ply - MoveType move[50]; ///< Presently winning move at each ply - int hand_rel_first; ///< Current hand, relative to first hand - int tricks_max; ///< Aggregated tricks won by maximizing side - HighCardType winner[DDS_SUITS]; ///< Winning rank of trick in each suit - HighCardType second_best[DDS_SUITS]; ///< Second best rank in each suit + unsigned short int rank_in_suit[DDS_HANDS][DDS_SUITS]; ///< Bitmask of ranks held by each hand in each suit + unsigned short int aggr[DDS_SUITS]; ///< Aggregate bitmask of all cards in each suit + unsigned char length[DDS_HANDS][DDS_SUITS]; ///< Number of cards each hand holds in each suit + int hand_dist[DDS_HANDS]; ///< Total number of cards held by each hand + + unsigned short int win_ranks[50][DDS_SUITS]; ///< Cards that win by rank at each depth + int first[50]; ///< Hand that leads the trick for each ply + MoveType move[50]; ///< Presently winning move at each ply + int hand_rel_first; ///< Current hand, relative to first hand + int tricks_max; ///< Aggregated tricks won by maximizing side + HighCardType winner[DDS_SUITS]; ///< Winning rank of trick in each suit + HighCardType second_best[DDS_SUITS]; ///< Second best rank in each suit }; /** @@ -318,12 +318,12 @@ struct Pos */ struct TrickDataType { - int play_count[DDS_SUITS]; ///< Number of cards played in each suit - int best_rank; ///< Rank of best card played so far - int best_suit; ///< Suit of best card played so far - int best_sequence; ///< Sequence of best card - int rel_winner; ///< Relative position of current trick winner - int next_lead_hand; ///< Hand that will lead next trick + int play_count[DDS_SUITS]; ///< Number of cards played in each suit + int best_rank; ///< Rank of best card played so far + int best_suit; ///< Suit of best card played so far + int best_sequence; ///< Sequence of best card + int rel_winner; ///< Relative position of current trick winner + int next_lead_hand; ///< Hand that will lead next trick }; /** @@ -334,8 +334,8 @@ struct TrickDataType */ struct EvalType { - int tricks; ///< Number of tricks that can be won from this position - unsigned short int win_ranks[DDS_SUITS]; ///< Bitmask of winning ranks in each suit + int tricks; ///< Number of tricks that can be won from this position + unsigned short int win_ranks[DDS_SUITS]; ///< Bitmask of winning ranks in each suit }; /** @@ -345,8 +345,8 @@ struct EvalType */ struct Card { - int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) - int rank; ///< Rank of the card (2-14: 2 through Ace) + int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) + int rank; ///< Rank of the card (2-14: 2 through Ace) }; /** @@ -357,9 +357,9 @@ struct Card */ struct ExtCard { - int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) - int rank; ///< Rank of the card (2-14: 2 through Ace) - int sequence; ///< Sequence identifier for equivalent cards + int suit; ///< Suit of the card (0-3: spades, hearts, diamonds, clubs) + int rank; ///< Rank of the card (2-14: 2 through Ace) + int sequence; ///< Sequence identifier for equivalent cards }; /** @@ -370,8 +370,8 @@ struct ExtCard */ struct AbsRankType // 2 bytes { - char rank; ///< Rank of the card (2-14) - signed char hand; ///< Hand holding the card (0-3: N, E, S, W) + char rank; ///< Rank of the card (2-14) + signed char hand; ///< Hand holding the card (0-3: N, E, S, W) }; /** @@ -382,7 +382,7 @@ struct AbsRankType // 2 bytes */ struct RelRanksType // 120 bytes { - AbsRankType abs_rank[15][DDS_SUITS]; ///< Rank information indexed by position and suit + AbsRankType abs_rank[15][DDS_SUITS]; ///< Rank information indexed by position and suit }; /** @@ -393,10 +393,10 @@ struct RelRanksType // 120 bytes */ struct ParamType { - int no_of_boards; ///< Number of boards to solve - Boards const * bop; ///< Pointer to input boards - SolvedBoards * solvedp; ///< Pointer to output solutions - int error; ///< Error code from operation + int no_of_boards; ///< Number of boards to solve + Boards const * bop; ///< Pointer to input boards + SolvedBoards * solvedp; ///< Pointer to output solutions + int error; ///< Error code from operation }; /** @@ -407,8 +407,8 @@ struct ParamType */ enum class RunMode { - DDS_RUN_SOLVE = 0, ///< Solve mode: find optimal play - DDS_RUN_CALC = 1, ///< Calculate mode: compute all outcomes - DDS_RUN_TRACE = 2, ///< Trace mode: analyze specific play sequence - DDS_RUN_SIZE = 3 ///< Size sentinel (not a valid mode) + DDS_RUN_SOLVE = 0, ///< Solve mode: find optimal play + DDS_RUN_CALC = 1, ///< Calculate mode: compute all outcomes + DDS_RUN_TRACE = 2, ///< Trace mode: analyze specific play sequence + DDS_RUN_SIZE = 3 ///< Size sentinel (not a valid mode) }; diff --git a/library/src/api/dll.h b/library/src/api/dll.h index 1a23f9985..0dc033db7 100644 --- a/library/src/api/dll.h +++ b/library/src/api/dll.h @@ -49,7 +49,7 @@ EXTERN_C DLLEXPORT auto STDCALL InitializeStaticMemory() -> void; * compatibility. It simply forwards to InitializeStaticMemory(). */ EXTERN_C DLLEXPORT auto STDCALL SetMaxThreads( - int userThreads) -> void; + int userThreads) -> void; /** * @brief Set the threading backend used by the solver. @@ -65,7 +65,7 @@ EXTERN_C DLLEXPORT auto STDCALL SetMaxThreads( * instead, create one SolverContext instance per thread. */ EXTERN_C DLLEXPORT auto STDCALL SetThreading( - int code) -> int; + int code) -> int; /** * @brief Set memory and thread resources for the solver. @@ -81,8 +81,8 @@ EXTERN_C DLLEXPORT auto STDCALL SetThreading( * which provides per-instance configuration through SolverConfig. */ EXTERN_C DLLEXPORT auto STDCALL SetResources( - int maxMemoryMB, - int maxThreads) -> void; + int maxMemoryMB, + int maxThreads) -> void; /** * @brief Free memory used by the solver. @@ -118,12 +118,12 @@ EXTERN_C DLLEXPORT auto STDCALL FreeMemory() -> void; * relying on the internal thread-indexed memory pools. */ EXTERN_C DLLEXPORT auto STDCALL SolveBoard( - struct Deal dl, - int target, - int solutions, - int mode, - struct FutureTricks * futp, - int threadIndex) -> int; + struct Deal dl, + int target, + int solutions, + int mode, + struct FutureTricks * futp, + int threadIndex) -> int; /** * @brief Solve a single bridge Deal in PBN format using double dummy analysis. @@ -146,12 +146,12 @@ EXTERN_C DLLEXPORT auto STDCALL SolveBoard( * across calls instead of being reallocated internally on each call. */ EXTERN_C DLLEXPORT auto STDCALL SolveBoardPBN( - struct DealPBN dlpbn, - int target, - int solutions, - int mode, - struct FutureTricks * futp, - int thrId) -> int; + struct DealPBN dlpbn, + int target, + int solutions, + int mode, + struct FutureTricks * futp, + int thrId) -> int; /** * @brief Calculate the double dummy table for a given Deal. @@ -170,8 +170,8 @@ EXTERN_C DLLEXPORT auto STDCALL SolveBoardPBN( * across calls instead of being reallocated internally on each call. */ EXTERN_C DLLEXPORT auto STDCALL CalcDDtable( - struct DdTableDeal tableDeal, - struct DdTableResults * tablep) -> int; + struct DdTableDeal tableDeal, + struct DdTableResults * tablep) -> int; /** * @brief CalcDDtable with an explicit worker-thread cap. @@ -187,9 +187,9 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtable( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL CalcDDtableN( - struct DdTableDeal tableDeal, - struct DdTableResults * tablep, - int maxThreads) -> int; + struct DdTableDeal tableDeal, + struct DdTableResults * tablep, + int maxThreads) -> int; /** * @brief Calculate the double dummy table for a PBN Deal. @@ -208,8 +208,8 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtableN( * across calls instead of being reallocated internally on each call. */ EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBN( - struct DdTableDealPBN tableDealPBN, - struct DdTableResults * tablep) -> int; + struct DdTableDealPBN tableDealPBN, + struct DdTableResults * tablep) -> int; /** * @brief CalcDDtablePBN with an explicit worker-thread cap. @@ -225,9 +225,9 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBN( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBNN( - struct DdTableDealPBN tableDealPBN, - struct DdTableResults * tablep, - int maxThreads) -> int; + struct DdTableDealPBN tableDealPBN, + struct DdTableResults * tablep, + int maxThreads) -> int; /** * @brief Calculate double dummy tables for multiple deals. @@ -240,11 +240,11 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBNN( * @return 1 on success, error code otherwise */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTables( - struct DdTableDeals const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTablesRes * resp, - struct AllParResults * presp) -> int; + struct DdTableDeals const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTablesRes * resp, + struct AllParResults * presp) -> int; /** * @brief CalcAllTables with an explicit worker-thread cap. @@ -253,12 +253,12 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTables( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesN( - struct DdTableDeals const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTablesRes * resp, - struct AllParResults * presp, - int maxThreads) -> int; + struct DdTableDeals const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTablesRes * resp, + struct AllParResults * presp, + int maxThreads) -> int; /** * @brief Calculate double dummy tables for multiple PBN deals. @@ -271,11 +271,11 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesN( * @return 1 on success, error code otherwise */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBN( - struct DdTableDealsPBN const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTablesRes * resp, - struct AllParResults * presp) -> int; + struct DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTablesRes * resp, + struct AllParResults * presp) -> int; /** * @brief CalcAllTablesPBN with an explicit worker-thread cap. @@ -284,12 +284,12 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBN( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNN( - struct DdTableDealsPBN const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTablesRes * resp, - struct AllParResults * presp, - int maxThreads) -> int; + struct DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTablesRes * resp, + struct AllParResults * presp, + int maxThreads) -> int; /** * @brief Unbounded CalcAllTables: any number of deals, one parallel board job. @@ -308,25 +308,25 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNN( * @param maxThreads Worker cap; <= 0 means auto */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesX( - int numDeals, - struct DdTableDeal const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTableResults * results, - struct ParResults * par, - int maxThreads) -> int; + int numDeals, + struct DdTableDeal const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTableResults * results, + struct ParResults * par, + int maxThreads) -> int; /** * @brief PBN variant of CalcAllTablesX. */ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNX( - int numDeals, - struct DdTableDealPBN const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - struct DdTableResults * results, - struct ParResults * par, - int maxThreads) -> int; + int numDeals, + struct DdTableDealPBN const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + struct DdTableResults * results, + struct ParResults * par, + int maxThreads) -> int; /** * @brief Solve multiple bridge deals in PBN format. @@ -336,8 +336,8 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNX( * @return 1 on success, error code otherwise */ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoards( - struct BoardsPBN const * bop, - struct SolvedBoards * solvedp) -> int; + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp) -> int; /** * @brief SolveAllBoards with an explicit worker-thread cap. @@ -346,13 +346,13 @@ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoards( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsN( - struct BoardsPBN const * bop, - struct SolvedBoards * solvedp, - int maxThreads) -> int; + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp, + int maxThreads) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBin( - struct Boards const * bop, - struct SolvedBoards * solvedp) -> int; + struct Boards const * bop, + struct SolvedBoards * solvedp) -> int; /** * @brief SolveAllBoardsBin with an explicit worker-thread cap. @@ -361,37 +361,37 @@ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBin( * (hardware_concurrency) default. */ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBinN( - struct Boards const * bop, - struct SolvedBoards * solvedp, - int maxThreads) -> int; + struct Boards const * bop, + struct SolvedBoards * solvedp, + int maxThreads) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsSeq( - struct BoardsPBN const * bop, - struct SolvedBoards * solvedp) -> int; + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBinSeq( - struct Boards const * bop, - struct SolvedBoards * solvedp) -> int; + struct Boards const * bop, + struct SolvedBoards * solvedp) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllChunks( - struct BoardsPBN const * bop, - struct SolvedBoards * solvedp, - int chunkSize) -> int; + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp, + int chunkSize) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllChunksBin( - struct Boards const * bop, - struct SolvedBoards * solvedp, - int chunkSize) -> int; + struct Boards const * bop, + struct SolvedBoards * solvedp, + int chunkSize) -> int; EXTERN_C DLLEXPORT auto STDCALL SolveAllChunksPBN( - struct BoardsPBN const * bop, - struct SolvedBoards * solvedp, - int chunkSize) -> int; + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp, + int chunkSize) -> int; EXTERN_C DLLEXPORT auto STDCALL Par( - struct DdTableResults const * tablep, - struct ParResults * presp, - int vulnerable) -> int; + struct DdTableResults const * tablep, + struct ParResults * presp, + int vulnerable) -> int; /** * @brief Calculate the double dummy table and par result for a given Deal. @@ -412,10 +412,10 @@ EXTERN_C DLLEXPORT auto STDCALL Par( * across calls instead of being reallocated internally on each call. */ EXTERN_C DLLEXPORT auto STDCALL CalcPar( - struct DdTableDeal tableDeal, - int vulnerable, - struct DdTableResults * tablep, - struct ParResults * presp) -> int; + struct DdTableDeal tableDeal, + int vulnerable, + struct DdTableResults * tablep, + struct ParResults * presp) -> int; /** * @brief Calculate the double dummy table and par result for a PBN Deal. @@ -436,68 +436,68 @@ EXTERN_C DLLEXPORT auto STDCALL CalcPar( * across calls instead of being reallocated internally on each call. */ EXTERN_C DLLEXPORT auto STDCALL CalcParPBN( - struct DdTableDealPBN tableDealPBN, - struct DdTableResults * tablep, - int vulnerable, - struct ParResults * presp) -> int; + struct DdTableDealPBN tableDealPBN, + struct DdTableResults * tablep, + int vulnerable, + struct ParResults * presp) -> int; EXTERN_C DLLEXPORT auto STDCALL SidesPar( - struct DdTableResults const * tablep, - struct ParResultsDealer sidesRes[2], - int vulnerable) -> int; + struct DdTableResults const * tablep, + struct ParResultsDealer sidesRes[2], + int vulnerable) -> int; EXTERN_C DLLEXPORT auto STDCALL DealerPar( - struct DdTableResults const * tablep, - struct ParResultsDealer * presp, - int dealer, - int vulnerable) -> int; + struct DdTableResults const * tablep, + struct ParResultsDealer * presp, + int dealer, + int vulnerable) -> int; EXTERN_C DLLEXPORT auto STDCALL DealerParBin( - struct DdTableResults const * tablep, - struct ParResultsMaster * presp, - int dealer, - int vulnerable) -> int; + struct DdTableResults const * tablep, + struct ParResultsMaster * presp, + int dealer, + int vulnerable) -> int; EXTERN_C DLLEXPORT auto STDCALL SidesParBin( - struct DdTableResults const * tablep, - struct ParResultsMaster sidesRes[2], - int vulnerable) -> int; + struct DdTableResults const * tablep, + struct ParResultsMaster sidesRes[2], + int vulnerable) -> int; EXTERN_C DLLEXPORT auto STDCALL ConvertToDealerTextFormat( - struct ParResultsMaster const * pres, - char * resp) -> int; + struct ParResultsMaster const * pres, + char * resp) -> int; EXTERN_C DLLEXPORT auto STDCALL ConvertToSidesTextFormat( - struct ParResultsMaster const * pres, - struct ParTextResults * resp) -> int; + struct ParResultsMaster const * pres, + struct ParTextResults * resp) -> int; EXTERN_C DLLEXPORT auto STDCALL AnalysePlayBin( - struct Deal dl, - struct PlayTraceBin play, - struct SolvedPlay * solved, - int thrId) -> int; + struct Deal dl, + struct PlayTraceBin play, + struct SolvedPlay * solved, + int thrId) -> int; EXTERN_C DLLEXPORT auto STDCALL AnalysePlayPBN( - struct DealPBN dlPBN, - struct PlayTracePBN playPBN, - struct SolvedPlay * solvedp, - int thrId) -> int; + struct DealPBN dlPBN, + struct PlayTracePBN playPBN, + struct SolvedPlay * solvedp, + int thrId) -> int; EXTERN_C DLLEXPORT auto STDCALL AnalyseAllPlaysBin( - struct Boards const * bop, - struct PlayTracesBin const * plp, - struct SolvedPlays * solvedp, - int chunkSize) -> int; + struct Boards const * bop, + struct PlayTracesBin const * plp, + struct SolvedPlays * solvedp, + int chunkSize) -> int; EXTERN_C DLLEXPORT auto STDCALL AnalyseAllPlaysPBN( - struct BoardsPBN const * bopPBN, - struct PlayTracesPBN const * plpPBN, - struct SolvedPlays * solvedp, - int chunkSize) -> int; + struct BoardsPBN const * bopPBN, + struct PlayTracesPBN const * plpPBN, + struct SolvedPlays * solvedp, + int chunkSize) -> int; EXTERN_C DLLEXPORT auto STDCALL GetDDSInfo( - struct DDSInfo * info) -> void; + struct DDSInfo * info) -> void; EXTERN_C DLLEXPORT auto STDCALL ErrorMessage( - int code, - char line[80]) -> void; + int code, + char line[80]) -> void; diff --git a/library/src/api/portab.h b/library/src/api/portab.h index 0f917f574..c3ebf02fd 100644 --- a/library/src/api/portab.h +++ b/library/src/api/portab.h @@ -12,46 +12,46 @@ #if defined(_WIN32) - #if defined(__MINGW32__) && !defined(WINVER) - #define WINVER 0x500 - #endif + #if defined(__MINGW32__) && !defined(WINVER) + #define WINVER 0x500 + #endif - #include - #include + #include + #include - #define USES_DLLMAIN - /* DLL uses DllMain() for initialization */ + #define USES_DLLMAIN + /* DLL uses DllMain() for initialization */ - #if defined (_MSC_VER) - #include - #endif + #if defined (_MSC_VER) + #include + #endif #elif defined (__CYGWIN__) - #include - #include + #include + #include - #define USES_DLLMAIN + #define USES_DLLMAIN #elif defined (__linux) - #include - #if !defined(DDS_NO_STATIC_INIT) - #define USES_CONSTRUCTOR - /* DLL uses a constructor function for initialization */ - #endif + #include + #if !defined(DDS_NO_STATIC_INIT) + #define USES_CONSTRUCTOR + /* DLL uses a constructor function for initialization */ + #endif - typedef long long __int64; + typedef long long __int64; #elif defined (__APPLE__) - #include + #include - #define USES_CONSTRUCTOR + #define USES_CONSTRUCTOR - typedef long long int64; + typedef long long int64; #endif #if (! defined DDS_THREADS_WIN32) && \ - (! defined DDS_THREADS_OPENMP) && \ - (! defined DDS_THREADS_NONE) - #define DDS_THREADS_NONE + (! defined DDS_THREADS_OPENMP) && \ + (! defined DDS_THREADS_NONE) + #define DDS_THREADS_NONE #endif diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 479cc43b3..a118da441 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -34,32 +34,32 @@ extern Memory memory; extern Scheduler scheduler; auto calc_all_boards_n( - Boards * bop, - SolvedBoards * solvedp, - int max_threads = 0, - bool difficulty_sort = true) -> int; + Boards * bop, + SolvedBoards * solvedp, + int max_threads = 0, + bool difficulty_sort = true) -> int; // Match SolveBoard's remaining-trick count from remainCards alone. auto remaining_tricks_from_holdings( - unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> int + unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> int { - int card_count = 0; - for (int h = 0; h < DDS_HANDS; h++) - { - for (int s = 0; s < DDS_SUITS; s++) - card_count += count_table[cards[h][s] >> 2]; - } - - if (card_count % 4) - return ((card_count - 4) >> 2) + 2; - return ((card_count - 4) >> 2) + 1; + int card_count = 0; + for (int h = 0; h < DDS_HANDS; h++) + { + for (int s = 0; s < DDS_SUITS; s++) + card_count += count_table[cards[h][s] >> 2]; + } + + if (card_count % 4) + return ((card_count - 4) >> 2) + 2; + return ((card_count - 4) >> 2) + 1; } auto declarer_tricks_from_leader_score( - int remaining_tricks, - int leader_side_score) -> int + int remaining_tricks, + int leader_side_score) -> int { - return remaining_tricks - leader_side_score; + return remaining_tricks - leader_side_score; } namespace @@ -69,430 +69,430 @@ namespace constexpr int kFullDealRemainingTricks = 13; auto is_full_thirteen_trick_deal( - unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> bool + unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> bool { - return remaining_tricks_from_holdings(cards) == kFullDealRemainingTricks; + return remaining_tricks_from_holdings(cards) == kFullDealRemainingTricks; } } // namespace auto calc_single_common_internal( - SolverContext& ctx, - Boards const& bds, - SolvedBoards& solved, - const int bno) -> int + SolverContext& ctx, + Boards const& bds, + SolvedBoards& solved, + const int bno) -> int { - FutureTricks fut{}; - Deal deal = bds.deals[bno]; // Make a local copy - deal.first = 0; + FutureTricks fut{}; + Deal deal = bds.deals[bno]; // Make a local copy + deal.first = 0; + + int res = solve_board( + ctx, + deal, + bds.target[bno], + bds.solutions[bno], + bds.mode[bno], + &fut); + + // SH: I'm making a terrible use of the fut structure here. + + if (res == 1) + solved.solved_board[bno].score[0] = fut.score[0]; + else + return res; - int res = solve_board( + // Reuse the same SolverContext (including ThreadData and TransTable) + // for subsequent same-board solves to ensure all declarers on the same + // board share the same transposition table state, which is important + // for calculation consistency and fixes a previous consistency bug. + const bool reuse_same_board = + is_full_thirteen_trick_deal(deal.remainCards); + for (int k = 1; k < DDS_HANDS; k++) + { + deal.first = k; + if (reuse_same_board) + { + // Fast path for full deals: null-window reuse with a partner/opponent hint. + const int hint = + (k == 2 ? fut.score[0] : kFullDealRemainingTricks - fut.score[0]); + res = solve_same_board(ctx, deal, &fut, hint); + } + else + { + // Partial deals: solve_same_board hints/reuse are incorrect; full solve. + res = solve_board( ctx, deal, bds.target[bno], bds.solutions[bno], bds.mode[bno], &fut); + } - // SH: I'm making a terrible use of the fut structure here. - - if (res == 1) - solved.solved_board[bno].score[0] = fut.score[0]; - else - return res; - - // Reuse the same SolverContext (including ThreadData and TransTable) - // for subsequent same-board solves to ensure all declarers on the same - // board share the same transposition table state, which is important - // for calculation consistency and fixes a previous consistency bug. - const bool reuse_same_board = - is_full_thirteen_trick_deal(deal.remainCards); - for (int k = 1; k < DDS_HANDS; k++) - { - deal.first = k; - if (reuse_same_board) - { - // Fast path for full deals: null-window reuse with a partner/opponent hint. - const int hint = - (k == 2 ? fut.score[0] : kFullDealRemainingTricks - fut.score[0]); - res = solve_same_board(ctx, deal, &fut, hint); + if (res == 1) + solved.solved_board[bno].score[k] = fut.score[0]; + else + return res; } - else - { - // Partial deals: solve_same_board hints/reuse are incorrect; full solve. - res = solve_board( - ctx, - deal, - bds.target[bno], - bds.solutions[bno], - bds.mode[bno], - &fut); - } - - if (res == 1) - solved.solved_board[bno].score[k] = fut.score[0]; - else - return res; - } - return 1; + return 1; } auto calc_all_boards_n( - SolverContext& ctx, - Boards * bop, - SolvedBoards * solvedp) -> int + SolverContext& ctx, + Boards * bop, + SolvedBoards * solvedp) -> int { - if (bop->no_of_boards > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; + if (bop->no_of_boards > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; - for (int k = 0; k < MAXNOOFBOARDS; k++) - solvedp->solved_board[k].cards = 0; + for (int k = 0; k < MAXNOOFBOARDS; k++) + solvedp->solved_board[k].cards = 0; - START_BLOCK_TIMER; + START_BLOCK_TIMER; - for (int bno = 0; bno < bop->no_of_boards; bno++) { - const int err = calc_single_common_internal(ctx, *bop, *solvedp, bno); - if (err != 1) - return err; - } + for (int bno = 0; bno < bop->no_of_boards; bno++) { + const int err = calc_single_common_internal(ctx, *bop, *solvedp, bno); + if (err != 1) + return err; + } - END_BLOCK_TIMER; + END_BLOCK_TIMER; - solvedp->no_of_boards = bop->no_of_boards; + solvedp->no_of_boards = bop->no_of_boards; #ifdef DDS_SCHEDULER - scheduler.PrintTiming(); + scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } // Legacy overload: parallel across boards, one SolverContext per worker. auto calc_all_boards_n( - Boards * bop, - SolvedBoards * solvedp, - int max_threads, - bool difficulty_sort) -> int + Boards * bop, + SolvedBoards * solvedp, + int max_threads, + bool difficulty_sort) -> int { - const int n = bop->no_of_boards; - if (n > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; + const int n = bop->no_of_boards; + if (n > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; - for (int k = 0; k < MAXNOOFBOARDS; k++) - solvedp->solved_board[k].cards = 0; + for (int k = 0; k < MAXNOOFBOARDS; k++) + solvedp->solved_board[k].cards = 0; - START_BLOCK_TIMER; + START_BLOCK_TIMER; - const int nthreads = resolve_worker_count(max_threads, n); + const int nthreads = resolve_worker_count(max_threads, n); - int err = RETURN_NO_FAULT; - if (nthreads <= 1) - { - SolverContext& ctx = dds::internal::worker_solver_context(); - for (int bno = 0; bno < n; ++bno) + int err = RETURN_NO_FAULT; + if (nthreads <= 1) { - err = calc_single_common_internal(ctx, *bop, *solvedp, bno); - if (err != RETURN_NO_FAULT) - break; + SolverContext& ctx = dds::internal::worker_solver_context(); + for (int bno = 0; bno < n; ++bno) + { + err = calc_single_common_internal(ctx, *bop, *solvedp, bno); + if (err != RETURN_NO_FAULT) + break; + } } - } - else - { - // Dispatch hardest boards first to shorten the parallel tail. This only - // helps across distinct deals (batch calc); for a single deal every board - // shares one fanout, so the sort is skipped (it would be a no-op anyway). - std::vector order; - if (difficulty_sort) + else { - std::vector fanout(static_cast(n)); - for (int i = 0; i < n; i++) - fanout[static_cast(i)] = - dds::internal::deal_fanout(bop->deals[i]); - order.resize(static_cast(n)); - std::iota(order.begin(), order.end(), 0); - std::stable_sort(order.begin(), order.end(), - [&](const int a, const int b) { - return fanout[static_cast(a)] > fanout[static_cast(b)]; - }); - } + // Dispatch hardest boards first to shorten the parallel tail. This only + // helps across distinct deals (batch calc); for a single deal every board + // shares one fanout, so the sort is skipped (it would be a no-op anyway). + std::vector order; + if (difficulty_sort) + { + std::vector fanout(static_cast(n)); + for (int i = 0; i < n; i++) + fanout[static_cast(i)] = + dds::internal::deal_fanout(bop->deals[i]); + order.resize(static_cast(n)); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), + [&](const int a, const int b) { + return fanout[static_cast(a)] > fanout[static_cast(b)]; + }); + } - err = parallel_all_boards_n(n, nthreads, - [&](const int worker_id, const int bno) -> int { - (void)worker_id; - // Persistent per-thread context: pool worker threads survive across - // batches, so the TT allocated here is reused for the whole run. - return calc_single_common_internal( - dds::internal::worker_solver_context(), *bop, *solvedp, bno); - }, - order.empty() ? nullptr : &order); - } + err = parallel_all_boards_n(n, nthreads, + [&](const int worker_id, const int bno) -> int { + (void)worker_id; + // Persistent per-thread context: pool worker threads survive across + // batches, so the TT allocated here is reused for the whole run. + return calc_single_common_internal( + dds::internal::worker_solver_context(), *bop, *solvedp, bno); + }, + order.empty() ? nullptr : &order); + } - END_BLOCK_TIMER; + END_BLOCK_TIMER; - if (err != RETURN_NO_FAULT) - return err; + if (err != RETURN_NO_FAULT) + return err; - solvedp->no_of_boards = n; + solvedp->no_of_boards = n; #ifdef DDS_SCHEDULER - scheduler.PrintTiming(); + scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL CalcDDtableN( - DdTableDeal tableDeal, - DdTableResults * tablep, - int maxThreads) + DdTableDeal tableDeal, + DdTableResults * tablep, + int maxThreads) { - if (int const check = table_deal_checks(tableDeal); check != RETURN_NO_FAULT) - return check; - - Deal dl; - Boards bo; - SolvedBoards solved; - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = tableDeal.cards[h][s]; - - for (int k = 0; k <= 2; k++) - { - dl.currentTrickRank[k] = 0; - dl.currentTrickSuit[k] = 0; - } - - int ind = 0; - bo.no_of_boards = DDS_STRAINS; - - for (int tr = DDS_STRAINS-1; tr >= 0; tr--) - { - dl.trump = tr; - bo.deals[ind] = dl; - bo.target[ind] = -1; - bo.solutions[ind] = 1; - bo.mode[ind] = 1; - ind++; - } - - // Single deal: all boards share one deal, so hardest-first sorting is a no-op. - int res = calc_all_boards_n(&bo, &solved, maxThreads, /*difficulty_sort=*/false); - if (res != 1) - return res; + if (int const check = table_deal_checks(tableDeal); check != RETURN_NO_FAULT) + return check; - const int tricks = remaining_tricks_from_holdings(tableDeal.cards); - for (int index = 0; index < DDS_STRAINS; index++) - { - int strain = bo.deals[index].trump; + Deal dl; + Boards bo; + SolvedBoards solved; - // SH: I'm making a terrible use of the fut structure here. + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = tableDeal.cards[h][s]; + + for (int k = 0; k <= 2; k++) + { + dl.currentTrickRank[k] = 0; + dl.currentTrickSuit[k] = 0; + } + + int ind = 0; + bo.no_of_boards = DDS_STRAINS; - for (int first = 0; first < DDS_HANDS; first++) + for (int tr = DDS_STRAINS-1; tr >= 0; tr--) { - tablep->res_table[strain][ rho[first] ] = - declarer_tricks_from_leader_score( - tricks, solved.solved_board[index].score[first]); + dl.trump = tr; + bo.deals[ind] = dl; + bo.target[ind] = -1; + bo.solutions[ind] = 1; + bo.mode[ind] = 1; + ind++; } - } - return RETURN_NO_FAULT; + + // Single deal: all boards share one deal, so hardest-first sorting is a no-op. + int res = calc_all_boards_n(&bo, &solved, maxThreads, /*difficulty_sort=*/false); + if (res != 1) + return res; + + const int tricks = remaining_tricks_from_holdings(tableDeal.cards); + for (int index = 0; index < DDS_STRAINS; index++) + { + int strain = bo.deals[index].trump; + + // SH: I'm making a terrible use of the fut structure here. + + for (int first = 0; first < DDS_HANDS; first++) + { + tablep->res_table[strain][ rho[first] ] = + declarer_tricks_from_leader_score( + tricks, solved.solved_board[index].score[first]); + } + } + return RETURN_NO_FAULT; } int STDCALL CalcDDtable( - DdTableDeal tableDeal, - DdTableResults * tablep) + DdTableDeal tableDeal, + DdTableResults * tablep) { - return CalcDDtableN(tableDeal, tablep, 0); + return CalcDDtableN(tableDeal, tablep, 0); } int STDCALL CalcAllTablesN( - DdTableDeals const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTablesRes * resp, - AllParResults * presp, - int maxThreads) + DdTableDeals const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTablesRes * resp, + AllParResults * presp, + int maxThreads) { - /* mode = 0: par calculation, vulnerability None + /* mode = 0: par calculation, vulnerability None mode = 1: par calculation, vulnerability All mode = 2: par calculation, vulnerability NS mode = 3: par calculation, vulnerability EW mode = -1: no par calculation */ - // dealsp->deals is a fixed MAXNOOFTABLES * DDS_STRAINS array, and the - // capacity check below multiplies by count. Bound no_of_tables first, so - // that multiply cannot overflow signed int and wrap past the check, and so - // the per-deal loops cannot read past the array. - if (dealsp == nullptr) - return RETURN_UNKNOWN_FAULT; - - if (dealsp->no_of_tables < 0 || - dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) - return RETURN_TOO_MANY_TABLES; - - Boards bo; - SolvedBoards solved; - int count = 0; - bool okey = false; - - for (int k = 0; k < DDS_STRAINS; k++) - { - if (!trumpFilter[k]) - { - okey = true; - count++; - } - } + // dealsp->deals is a fixed MAXNOOFTABLES * DDS_STRAINS array, and the + // capacity check below multiplies by count. Bound no_of_tables first, so + // that multiply cannot overflow signed int and wrap past the check, and so + // the per-deal loops cannot read past the array. + if (dealsp == nullptr) + return RETURN_UNKNOWN_FAULT; - if (!okey) - return RETURN_NO_SUIT; + if (dealsp->no_of_tables < 0 || + dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) + return RETURN_TOO_MANY_TABLES; - if (count * dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) - return RETURN_TOO_MANY_TABLES; + Boards bo; + SolvedBoards solved; + int count = 0; + bool okey = false; - for (int m = 0; m < dealsp->no_of_tables; m++) - { - int const check = table_deal_checks(dealsp->deals[m]); - if (check != RETURN_NO_FAULT) - return check; - } + for (int k = 0; k < DDS_STRAINS; k++) + { + if (!trumpFilter[k]) + { + okey = true; + count++; + } + } - int ind = 0; - resp->no_of_boards = 0; + if (!okey) + return RETURN_NO_SUIT; - // With no deals the loop below writes no boards, and bo is an uninitialized - // local -- solving a board from it reads indeterminate values. Return early, - // matching CalcAllTablesX(). - if (dealsp->no_of_tables == 0) - return RETURN_NO_FAULT; + if (count * dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) + return RETURN_TOO_MANY_TABLES; - for (int m = 0; m < dealsp->no_of_tables; m++) - { - for (int tr = DDS_STRAINS-1; tr >= 0; tr--) + for (int m = 0; m < dealsp->no_of_tables; m++) { - if (trumpFilter[tr]) - continue; - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - bo.deals[ind].remainCards[h][s] = - dealsp->deals[m].cards[h][s]; + int const check = table_deal_checks(dealsp->deals[m]); + if (check != RETURN_NO_FAULT) + return check; + } - bo.deals[ind].trump = tr; + int ind = 0; + resp->no_of_boards = 0; - for (int k = 0; k <= 2; k++) - { - bo.deals[ind].currentTrickRank[k] = 0; - bo.deals[ind].currentTrickSuit[k] = 0; - } + // With no deals the loop below writes no boards, and bo is an uninitialized + // local -- solving a board from it reads indeterminate values. Return early, + // matching CalcAllTablesX(). + if (dealsp->no_of_tables == 0) + return RETURN_NO_FAULT; - bo.target[ind] = -1; - bo.solutions[ind] = 1; - bo.mode[ind] = 1; - ind++; + for (int m = 0; m < dealsp->no_of_tables; m++) + { + for (int tr = DDS_STRAINS-1; tr >= 0; tr--) + { + if (trumpFilter[tr]) + continue; + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + bo.deals[ind].remainCards[h][s] = + dealsp->deals[m].cards[h][s]; + + bo.deals[ind].trump = tr; + + for (int k = 0; k <= 2; k++) + { + bo.deals[ind].currentTrickRank[k] = 0; + bo.deals[ind].currentTrickSuit[k] = 0; + } + + bo.target[ind] = -1; + bo.solutions[ind] = 1; + bo.mode[ind] = 1; + ind++; + } } - } - // ind counts the boards actually written; deriving the count from a - // last-index variable initialized to 0 claimed one board even when none - // had been filled in. - bo.no_of_boards = ind; + // ind counts the boards actually written; deriving the count from a + // last-index variable initialized to 0 claimed one board even when none + // had been filled in. + bo.no_of_boards = ind; - int res = calc_all_boards_n(&bo, &solved, maxThreads); - if (res != 1) - return res; + int res = calc_all_boards_n(&bo, &solved, maxThreads); + if (res != 1) + return res; - resp->no_of_boards += 4 * solved.no_of_boards; + resp->no_of_boards += 4 * solved.no_of_boards; - for (int m = 0; m < dealsp->no_of_tables; m++) - { - const int tricks = remaining_tricks_from_holdings(dealsp->deals[m].cards); - for (int strainIndex = 0; strainIndex < count; strainIndex++) + for (int m = 0; m < dealsp->no_of_tables; m++) { - int index = m * count + strainIndex; - int strain = bo.deals[index].trump; + const int tricks = remaining_tricks_from_holdings(dealsp->deals[m].cards); + for (int strainIndex = 0; strainIndex < count; strainIndex++) + { + int index = m * count + strainIndex; + int strain = bo.deals[index].trump; - // SH: I'm making a terrible use of the fut structure here. + // SH: I'm making a terrible use of the fut structure here. - for (int first = 0; first < DDS_HANDS; first++) - { - resp->results[m].res_table[strain][ rho[first] ] = - declarer_tricks_from_leader_score( - tricks, solved.solved_board[index].score[first]); - } + for (int first = 0; first < DDS_HANDS; first++) + { + resp->results[m].res_table[strain][ rho[first] ] = + declarer_tricks_from_leader_score( + tricks, solved.solved_board[index].score[first]); + } + } } - } - if ((mode > -1) && (mode < 4) && (count == 5)) - { - /* Calculate par */ - for (int k = 0; k < dealsp->no_of_tables; k++) + if ((mode > -1) && (mode < 4) && (count == 5)) { - res = Par(&(resp->results[k]), &(presp->par_results[k]), mode); - /* vulnerable 0: None 1: Both 2: NS 3: EW */ - if (res != 1) - return res; + /* Calculate par */ + for (int k = 0; k < dealsp->no_of_tables; k++) + { + res = Par(&(resp->results[k]), &(presp->par_results[k]), mode); + /* vulnerable 0: None 1: Both 2: NS 3: EW */ + if (res != 1) + return res; + } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL CalcAllTables( - DdTableDeals const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTablesRes * resp, - AllParResults * presp) + DdTableDeals const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTablesRes * resp, + AllParResults * presp) { - return CalcAllTablesN(dealsp, mode, trumpFilter, resp, presp, 0); + return CalcAllTablesN(dealsp, mode, trumpFilter, resp, presp, 0); } int STDCALL CalcAllTablesPBNN( - DdTableDealsPBN const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTablesRes * resp, - AllParResults * presp, - int maxThreads) + DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTablesRes * resp, + AllParResults * presp, + int maxThreads) { - // dls.deals and dealsp->deals both hold MAXNOOFTABLES * DDS_STRAINS - // entries. Bound the count before the conversion loop: unchecked, this - // wrote past the fixed-size local. - if (dealsp == nullptr) - return RETURN_UNKNOWN_FAULT; + // dls.deals and dealsp->deals both hold MAXNOOFTABLES * DDS_STRAINS + // entries. Bound the count before the conversion loop: unchecked, this + // wrote past the fixed-size local. + if (dealsp == nullptr) + return RETURN_UNKNOWN_FAULT; - if (dealsp->no_of_tables < 0 || - dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) - return RETURN_TOO_MANY_TABLES; + if (dealsp->no_of_tables < 0 || + dealsp->no_of_tables > MAXNOOFTABLES * DDS_STRAINS) + return RETURN_TOO_MANY_TABLES; - DdTableDeals dls; - for (int k = 0; k < dealsp->no_of_tables; k++) - if (convert_from_pbn(dealsp->deals[k].cards, dls.deals[k].cards) != 1) - return RETURN_PBN_FAULT; + DdTableDeals dls; + for (int k = 0; k < dealsp->no_of_tables; k++) + if (convert_from_pbn(dealsp->deals[k].cards, dls.deals[k].cards) != 1) + return RETURN_PBN_FAULT; - dls.no_of_tables = dealsp->no_of_tables; + dls.no_of_tables = dealsp->no_of_tables; - int res = CalcAllTablesN(&dls, mode, trumpFilter, resp, presp, maxThreads); - return res; + int res = CalcAllTablesN(&dls, mode, trumpFilter, resp, presp, maxThreads); + return res; } int STDCALL CalcAllTablesPBN( - DdTableDealsPBN const * dealsp, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTablesRes * resp, - AllParResults * presp) + DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTablesRes * resp, + AllParResults * presp) { - return CalcAllTablesPBNN(dealsp, mode, trumpFilter, resp, presp, 0); + return CalcAllTablesPBNN(dealsp, mode, trumpFilter, resp, presp, 0); } @@ -501,41 +501,41 @@ namespace // Solve one strain board (all four declarers) into scores[DDS_HANDS]. auto calc_single_deal_scores( - SolverContext& ctx, - Deal deal, - const int target, - const int solutions, - const int mode, - int scores[DDS_HANDS]) -> int + SolverContext& ctx, + Deal deal, + const int target, + const int solutions, + const int mode, + int scores[DDS_HANDS]) -> int { - FutureTricks fut{}; - deal.first = 0; - int res = solve_board(ctx, deal, target, solutions, mode, &fut); - if (res != RETURN_NO_FAULT) - return res; - scores[0] = fut.score[0]; - - const bool reuse_same_board = - is_full_thirteen_trick_deal(deal.remainCards); - for (int k = 1; k < DDS_HANDS; k++) - { - deal.first = k; - if (reuse_same_board) - { - const int hint = - (k == 2 ? fut.score[0] : kFullDealRemainingTricks - fut.score[0]); - res = solve_same_board(ctx, deal, &fut, hint); - } - else + FutureTricks fut{}; + deal.first = 0; + int res = solve_board(ctx, deal, target, solutions, mode, &fut); + if (res != RETURN_NO_FAULT) + return res; + scores[0] = fut.score[0]; + + const bool reuse_same_board = + is_full_thirteen_trick_deal(deal.remainCards); + for (int k = 1; k < DDS_HANDS; k++) { - // Partial deals: solve_same_board is wrong; use a full solve per leader. - res = solve_board(ctx, deal, target, solutions, mode, &fut); + deal.first = k; + if (reuse_same_board) + { + const int hint = + (k == 2 ? fut.score[0] : kFullDealRemainingTricks - fut.score[0]); + res = solve_same_board(ctx, deal, &fut, hint); + } + else + { + // Partial deals: solve_same_board is wrong; use a full solve per leader. + res = solve_board(ctx, deal, target, solutions, mode, &fut); + } + if (res != RETURN_NO_FAULT) + return res; + scores[k] = fut.score[0]; } - if (res != RETURN_NO_FAULT) - return res; - scores[k] = fut.score[0]; - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } } // namespace @@ -551,297 +551,297 @@ namespace variant, a memory-exhaustion path. Also reports how many strains survive the filter, which the caller needs anyway. */ auto batch_count_preflight( - const int numDeals, - int const trumpFilter[DDS_STRAINS], - int& included) -> int + const int numDeals, + int const trumpFilter[DDS_STRAINS], + int& included) -> int { - included = 0; - for (int k = 0; k < DDS_STRAINS; k++) - if (!trumpFilter[k]) - included++; + included = 0; + for (int k = 0; k < DDS_STRAINS; k++) + if (!trumpFilter[k]) + included++; - if (included == 0) - return RETURN_NO_SUIT; + if (included == 0) + return RETURN_NO_SUIT; - if (numDeals > std::numeric_limits::max() / included) - return RETURN_TOO_MANY_TABLES; + if (numDeals > std::numeric_limits::max() / included) + return RETURN_TOO_MANY_TABLES; - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } } // namespace int STDCALL CalcAllTablesX( - int numDeals, - DdTableDeal const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads) + int numDeals, + DdTableDeal const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads) { - return calc_all_tables_x( - numDeals, deals, mode, trumpFilter, results, par, maxThreads, nullptr); + return calc_all_tables_x( + numDeals, deals, mode, trumpFilter, results, par, maxThreads, nullptr); } auto calc_all_tables_x( - int numDeals, - DdTableDeal const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads, - std::vector * strain_times_us) -> int + int numDeals, + DdTableDeal const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads, + std::vector * strain_times_us) -> int { - // C ABI: exceptions must not unwind into a foreign caller (UB). Heap - // allocations below (and parallel_all_boards_n) may throw; map any throw - // to RETURN_UNKNOWN_FAULT. - try - { - if (numDeals < 0) - return RETURN_TOO_MANY_TABLES; - if (numDeals == 0) + // C ABI: exceptions must not unwind into a foreign caller (UB). Heap + // allocations below (and parallel_all_boards_n) may throw; map any throw + // to RETURN_UNKNOWN_FAULT. + try { - if (strain_times_us != nullptr) - strain_times_us->clear(); - return RETURN_NO_FAULT; - } - if (deals == nullptr || results == nullptr || trumpFilter == nullptr) - return RETURN_UNKNOWN_FAULT; - - int included = 0; - if (int const check = batch_count_preflight(numDeals, trumpFilter, included); - check != RETURN_NO_FAULT) - return check; - - const bool want_par = (mode > -1) && (mode < 4) && (included == DDS_STRAINS); - if (want_par && par == nullptr) - return RETURN_UNKNOWN_FAULT; + if (numDeals < 0) + return RETURN_TOO_MANY_TABLES; + if (numDeals == 0) + { + if (strain_times_us != nullptr) + strain_times_us->clear(); + return RETURN_NO_FAULT; + } + if (deals == nullptr || results == nullptr || trumpFilter == nullptr) + return RETURN_UNKNOWN_FAULT; - // This path builds its board list directly rather than going through - // CalcDDtableN, so it needs the same deal validation. - for (int m = 0; m < numDeals; m++) - { - int const check = table_deal_checks(deals[m]); - if (check != RETURN_NO_FAULT) - return check; - } + int included = 0; + if (int const check = batch_count_preflight(numDeals, trumpFilter, included); + check != RETURN_NO_FAULT) + return check; - // Expand every deal×included-strain into one board list and solve in a - // single parallel_all_boards_n job (heap-backed). This is the ddss-style - // large-batch shape; legacy CalcAllTablesN remains capped at MAXNOOFTABLES. - // Overflow already ruled out by batch_count_preflight() above. - const int nboards = numDeals * included; - std::vector boards(static_cast(nboards)); - std::vector> scores(static_cast(nboards)); - std::vector local_strain_times; - if (strain_times_us != nullptr) - local_strain_times.assign(static_cast(nboards), 0); + const bool want_par = (mode > -1) && (mode < 4) && (included == DDS_STRAINS); + if (want_par && par == nullptr) + return RETURN_UNKNOWN_FAULT; - int ind = 0; - for (int m = 0; m < numDeals; m++) - { - for (int tr = DDS_STRAINS - 1; tr >= 0; tr--) - { - if (trumpFilter[tr]) - continue; - - Deal& dl = boards[static_cast(ind)]; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = deals[m].cards[h][s]; - dl.trump = tr; - dl.first = 0; - for (int k = 0; k <= 2; k++) + // This path builds its board list directly rather than going through + // CalcDDtableN, so it needs the same deal validation. + for (int m = 0; m < numDeals; m++) { - dl.currentTrickRank[k] = 0; - dl.currentTrickSuit[k] = 0; + int const check = table_deal_checks(deals[m]); + if (check != RETURN_NO_FAULT) + return check; } - ind++; - } - } - const int nthreads = resolve_worker_count(maxThreads, nboards); + // Expand every deal×included-strain into one board list and solve in a + // single parallel_all_boards_n job (heap-backed). This is the ddss-style + // large-batch shape; legacy CalcAllTablesN remains capped at MAXNOOFTABLES. + // Overflow already ruled out by batch_count_preflight() above. + const int nboards = numDeals * included; + std::vector boards(static_cast(nboards)); + std::vector> scores(static_cast(nboards)); + std::vector local_strain_times; + if (strain_times_us != nullptr) + local_strain_times.assign(static_cast(nboards), 0); + + int ind = 0; + for (int m = 0; m < numDeals; m++) + { + for (int tr = DDS_STRAINS - 1; tr >= 0; tr--) + { + if (trumpFilter[tr]) + continue; + + Deal& dl = boards[static_cast(ind)]; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = deals[m].cards[h][s]; + dl.trump = tr; + dl.first = 0; + for (int k = 0; k <= 2; k++) + { + dl.currentTrickRank[k] = 0; + dl.currentTrickSuit[k] = 0; + } + ind++; + } + } - // Hardest-first dispatch only helps with multiple workers (same idea as - // calc_all_boards_n). Skip fanout+sort on the single-thread path so large - // single-worker batches avoid O(n log n) overhead. - std::vector order; - if (nthreads > 1) - { - order.resize(static_cast(nboards)); - std::iota(order.begin(), order.end(), 0); - std::vector fanout(static_cast(nboards)); - for (int i = 0; i < nboards; i++) - fanout[static_cast(i)] = - dds::internal::deal_fanout(boards[static_cast(i)]); - std::stable_sort(order.begin(), order.end(), - [&](const int a, const int b) { - return fanout[static_cast(a)] > fanout[static_cast(b)]; - }); - } + const int nthreads = resolve_worker_count(maxThreads, nboards); - const int err = parallel_all_boards_n(nboards, nthreads, - [&](const int worker_id, const int bno) -> int { - (void)worker_id; - if (strain_times_us == nullptr) + // Hardest-first dispatch only helps with multiple workers (same idea as + // calc_all_boards_n). Skip fanout+sort on the single-thread path so large + // single-worker batches avoid O(n log n) overhead. + std::vector order; + if (nthreads > 1) { - return calc_single_deal_scores( - dds::internal::worker_solver_context(), - boards[static_cast(bno)], - -1, 1, 1, - scores[static_cast(bno)].data()); + order.resize(static_cast(nboards)); + std::iota(order.begin(), order.end(), 0); + std::vector fanout(static_cast(nboards)); + for (int i = 0; i < nboards; i++) + fanout[static_cast(i)] = + dds::internal::deal_fanout(boards[static_cast(i)]); + std::stable_sort(order.begin(), order.end(), + [&](const int a, const int b) { + return fanout[static_cast(a)] > fanout[static_cast(b)]; + }); } - const auto t0 = std::chrono::steady_clock::now(); - const int res = calc_single_deal_scores( - dds::internal::worker_solver_context(), - boards[static_cast(bno)], - -1, 1, 1, - scores[static_cast(bno)].data()); - const auto dur = std::chrono::duration_cast( - std::chrono::steady_clock::now() - t0).count(); - local_strain_times[static_cast(bno)] = - saturate_board_time_us(dur); - return res; - }, - order.empty() ? nullptr : &order); - if (err != RETURN_NO_FAULT) - return err; - - if (strain_times_us != nullptr) - *strain_times_us = std::move(local_strain_times); + const int err = parallel_all_boards_n(nboards, nthreads, + [&](const int worker_id, const int bno) -> int { + (void)worker_id; + if (strain_times_us == nullptr) + { + return calc_single_deal_scores( + dds::internal::worker_solver_context(), + boards[static_cast(bno)], + -1, 1, 1, + scores[static_cast(bno)].data()); + } + + const auto t0 = std::chrono::steady_clock::now(); + const int res = calc_single_deal_scores( + dds::internal::worker_solver_context(), + boards[static_cast(bno)], + -1, 1, 1, + scores[static_cast(bno)].data()); + const auto dur = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + local_strain_times[static_cast(bno)] = + saturate_board_time_us(dur); + return res; + }, + order.empty() ? nullptr : &order); + if (err != RETURN_NO_FAULT) + return err; + + if (strain_times_us != nullptr) + *strain_times_us = std::move(local_strain_times); + + for (int m = 0; m < numDeals; m++) + { + const int tricks = remaining_tricks_from_holdings(deals[m].cards); + for (int strainIndex = 0; strainIndex < included; strainIndex++) + { + const int index = m * included + strainIndex; + const int strain = boards[static_cast(index)].trump; + for (int first = 0; first < DDS_HANDS; first++) + { + results[m].res_table[strain][rho[first]] = + declarer_tricks_from_leader_score( + tricks, + scores[static_cast(index)][static_cast(first)]); + } + } + } - for (int m = 0; m < numDeals; m++) - { - const int tricks = remaining_tricks_from_holdings(deals[m].cards); - for (int strainIndex = 0; strainIndex < included; strainIndex++) - { - const int index = m * included + strainIndex; - const int strain = boards[static_cast(index)].trump; - for (int first = 0; first < DDS_HANDS; first++) + if (want_par) { - results[m].res_table[strain][rho[first]] = - declarer_tricks_from_leader_score( - tricks, - scores[static_cast(index)][static_cast(first)]); + for (int k = 0; k < numDeals; k++) + { + const int res = Par(&results[k], &par[k], mode); + if (res != RETURN_NO_FAULT) + return res; + } } - } - } - if (want_par) + return RETURN_NO_FAULT; + } + catch (...) { - for (int k = 0; k < numDeals; k++) - { - const int res = Par(&results[k], &par[k], mode); - if (res != RETURN_NO_FAULT) - return res; - } + return RETURN_UNKNOWN_FAULT; } - - return RETURN_NO_FAULT; - } - catch (...) - { - return RETURN_UNKNOWN_FAULT; - } } int STDCALL CalcAllTablesPBNX( - int numDeals, - DdTableDealPBN const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads) + int numDeals, + DdTableDealPBN const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads) { - return calc_all_tables_pbn_x( - numDeals, deals, mode, trumpFilter, results, par, maxThreads, nullptr); + return calc_all_tables_pbn_x( + numDeals, deals, mode, trumpFilter, results, par, maxThreads, nullptr); } auto calc_all_tables_pbn_x( - int numDeals, - DdTableDealPBN const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads, - std::vector * strain_times_us) -> int + int numDeals, + DdTableDealPBN const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads, + std::vector * strain_times_us) -> int { - // C ABI: same catch-all contract as CalcAllTablesX / dds_c_api.cpp. - try - { - if (numDeals < 0) - return RETURN_TOO_MANY_TABLES; - if (numDeals == 0) + // C ABI: same catch-all contract as CalcAllTablesX / dds_c_api.cpp. + try { - if (strain_times_us != nullptr) - strain_times_us->clear(); - return RETURN_NO_FAULT; + if (numDeals < 0) + return RETURN_TOO_MANY_TABLES; + if (numDeals == 0) + { + if (strain_times_us != nullptr) + strain_times_us->clear(); + return RETURN_NO_FAULT; + } + if (deals == nullptr || results == nullptr || trumpFilter == nullptr) + return RETURN_UNKNOWN_FAULT; + + // Share CalcAllTablesX's preflight so a count that cannot succeed is + // rejected before this allocates and converts numDeals records. + int included = 0; + if (int const check = batch_count_preflight(numDeals, trumpFilter, included); + check != RETURN_NO_FAULT) + return check; + + std::vector binary(static_cast(numDeals)); + for (int i = 0; i < numDeals; ++i) + { + if (convert_from_pbn(deals[i].cards, binary[static_cast(i)].cards) != 1) + return RETURN_PBN_FAULT; + } + + return calc_all_tables_x( + numDeals, binary.data(), mode, trumpFilter, results, par, maxThreads, + strain_times_us); } - if (deals == nullptr || results == nullptr || trumpFilter == nullptr) - return RETURN_UNKNOWN_FAULT; - - // Share CalcAllTablesX's preflight so a count that cannot succeed is - // rejected before this allocates and converts numDeals records. - int included = 0; - if (int const check = batch_count_preflight(numDeals, trumpFilter, included); - check != RETURN_NO_FAULT) - return check; - - std::vector binary(static_cast(numDeals)); - for (int i = 0; i < numDeals; ++i) + catch (...) { - if (convert_from_pbn(deals[i].cards, binary[static_cast(i)].cards) != 1) - return RETURN_PBN_FAULT; + return RETURN_UNKNOWN_FAULT; } - - return calc_all_tables_x( - numDeals, binary.data(), mode, trumpFilter, results, par, maxThreads, - strain_times_us); - } - catch (...) - { - return RETURN_UNKNOWN_FAULT; - } } int STDCALL CalcDDtablePBNN( - DdTableDealPBN tableDealPBN, - DdTableResults * tablep, - int maxThreads) + DdTableDealPBN tableDealPBN, + DdTableResults * tablep, + int maxThreads) { - DdTableDeal tableDeal; - if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) - return RETURN_PBN_FAULT; + DdTableDeal tableDeal; + if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) + return RETURN_PBN_FAULT; - int res = CalcDDtableN(tableDeal, tablep, maxThreads); - return res; + int res = CalcDDtableN(tableDeal, tablep, maxThreads); + return res; } int STDCALL CalcDDtablePBN( - DdTableDealPBN tableDealPBN, - DdTableResults * tablep) + DdTableDealPBN tableDealPBN, + DdTableResults * tablep) { - return CalcDDtablePBNN(tableDealPBN, tablep, 0); + return CalcDDtablePBNN(tableDealPBN, tablep, 0); } void detect_calc_duplicates( - const Boards& bds, - std::vector& uniques, - std::vector& crossrefs) + const Boards& bds, + std::vector& uniques, + std::vector& crossrefs) { - // Could save a little bit of time with a dedicated checker that - // only looks at the cards. - return detect_solve_duplicates(bds, uniques, crossrefs); + // Could save a little bit of time with a dedicated checker that + // only looks at the cards. + return detect_solve_duplicates(bds, uniques, crossrefs); } diff --git a/library/src/calc_tables.hpp b/library/src/calc_tables.hpp index 1df00e640..117e525a9 100644 --- a/library/src/calc_tables.hpp +++ b/library/src/calc_tables.hpp @@ -21,10 +21,10 @@ * @return 1 on success, error code otherwise */ auto calc_single_common_internal( - SolverContext& ctx, - Boards const& bds, - SolvedBoards& solved, - const int bno) -> int; + SolverContext& ctx, + Boards const& bds, + SolvedBoards& solved, + const int bno) -> int; /** * @brief Calculate all boards with explicit solver context. @@ -37,9 +37,9 @@ auto calc_single_common_internal( * @return Error code */ auto calc_all_boards_n( - SolverContext& ctx, - Boards * bop, - SolvedBoards * solvedp) -> int; + SolverContext& ctx, + Boards * bop, + SolvedBoards * solvedp) -> int; /** * @brief Detect duplicate board calculations and build cross-reference maps. @@ -51,17 +51,17 @@ auto calc_all_boards_n( * @param crossrefs Output vector mapping each board to its unique representative. */ auto detect_calc_duplicates( - const Boards& bds, - std::vector& uniques, - std::vector& crossrefs) -> void; + const Boards& bds, + std::vector& uniques, + std::vector& crossrefs) -> void; // Match SolveBoard's remaining-trick count from remainCards alone. auto remaining_tricks_from_holdings( - unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> int; + unsigned int const cards[DDS_HANDS][DDS_SUITS]) -> int; auto declarer_tricks_from_leader_score( - int remaining_tricks, - int leader_side_score) -> int; + int remaining_tricks, + int leader_side_score) -> int; /** * @brief Unbounded CalcAllTables with optional per-strain-board timings. @@ -73,24 +73,24 @@ auto declarer_tricks_from_leader_score( * stale timings. */ auto calc_all_tables_x( - int numDeals, - DdTableDeal const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads, - std::vector * strain_times_us = nullptr) -> int; + int numDeals, + DdTableDeal const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads, + std::vector * strain_times_us = nullptr) -> int; /** * @brief PBN variant of calc_all_tables_x. */ auto calc_all_tables_pbn_x( - int numDeals, - DdTableDealPBN const * deals, - int mode, - int const trumpFilter[DDS_STRAINS], - DdTableResults * results, - ParResults * par, - int maxThreads, - std::vector * strain_times_us = nullptr) -> int; + int numDeals, + DdTableDealPBN const * deals, + int mode, + int const trumpFilter[DDS_STRAINS], + DdTableResults * results, + ParResults * par, + int maxThreads, + std::vector * strain_times_us = nullptr) -> int; diff --git a/library/src/dds.cpp b/library/src/dds.cpp index f9049d4f9..d17a58b6d 100644 --- a/library/src/dds.cpp +++ b/library/src/dds.cpp @@ -11,44 +11,44 @@ #include #if defined(_WIN32) || defined(USES_DLLMAIN) - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #include + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include #endif #ifdef _MANAGED - #pragma managed(push, off) + #pragma managed(push, off) #endif #if defined(_WIN32) || defined(USES_DLLMAIN) extern "C" BOOL APIENTRY DllMain( - [[maybe_unused]] HMODULE hModule, - DWORD ul_reason_for_call, - [[maybe_unused]] LPVOID lpReserved); + [[maybe_unused]] HMODULE hModule, + DWORD ul_reason_for_call, + [[maybe_unused]] LPVOID lpReserved); extern "C" BOOL APIENTRY DllMain( - HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved) + HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) { - if (ul_reason_for_call == DLL_PROCESS_ATTACH) - InitializeStaticMemory(); - else if (ul_reason_for_call == DLL_PROCESS_DETACH) - { - FreeMemory(); + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + InitializeStaticMemory(); + else if (ul_reason_for_call == DLL_PROCESS_DETACH) + { + FreeMemory(); #ifdef DDS_MEMORY_LEAKS_WIN32 - _CrtDumpMemoryLeaks(); + _CrtDumpMemoryLeaks(); #endif - } + } - hModule; - lpReserved; + hModule; + lpReserved; - return 1; + return 1; } #elif (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) || defined(__MAC_OS_X_VERSION_MAX_ALLOWED)) @@ -63,7 +63,7 @@ void DDSInitialize(), DDSFinalize(); */ void DDSInitialize(void) { - InitializeStaticMemory(); + InitializeStaticMemory(); } @@ -72,7 +72,7 @@ void DDSInitialize(void) */ void DDSFinalize(void) { - FreeMemory(); + FreeMemory(); } @@ -86,13 +86,13 @@ void DDSFinalize(void) */ static void __attribute__ ((constructor)) libInit(void) { - DDSInitialize(); + DDSInitialize(); } static void __attribute__ ((destructor)) libFini(void) { - DDSFinalize(); + DDSFinalize(); } #elif defined(USES_CONSTRUCTOR) @@ -104,12 +104,12 @@ static void __attribute__ ((destructor)) libFini(void) */ static void __attribute__ ((constructor)) libInit(void) { - InitializeStaticMemory(); + InitializeStaticMemory(); } #endif #ifdef _MANAGED - #pragma managed(pop) + #pragma managed(pop) #endif diff --git a/library/src/dds_api.cpp b/library/src/dds_api.cpp index cd5705568..752740729 100644 --- a/library/src/dds_api.cpp +++ b/library/src/dds_api.cpp @@ -12,102 +12,102 @@ // Creation DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default() { - SolverConfig cfg{}; - return new SolverContext(cfg); + SolverConfig cfg{}; + return new SolverContext(cfg); } DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg) { - return new SolverContext(cfg); + return new SolverContext(cfg); } // SolverContext Destruction DLLEXPORT void dds_destroy_solvercontext(DDS_SOLVER_CTX ctx) { - delete ctx; + delete ctx; } // TT Configuration DLLEXPORT void dds_configure_tt(DDS_SOLVER_CTX ctx, TTKind kind, int defMB, int maxMB) { - ctx->configure_tt(kind, defMB, maxMB); + ctx->configure_tt(kind, defMB, maxMB); } DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, int defMB, int maxMB) { - ctx->resize_tt(defMB, maxMB); + ctx->resize_tt(defMB, maxMB); } DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx) { - ctx->clear_tt(); + ctx->clear_tt(); } // Resets DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx) { - ctx->reset_for_solve(); + ctx->reset_for_solve(); } DLLEXPORT void dds_reset_best_moves_lite(DDS_SOLVER_CTX ctx) { - ctx->reset_best_moves_lite(); + ctx->reset_best_moves_lite(); } // Utilities – simple logging passthrough DLLEXPORT void dds_log_append(DDS_SOLVER_CTX ctx, const char* msg) { - ctx->utilities().log_append(std::string(msg ? msg : "")); + ctx->utilities().log_append(std::string(msg ? msg : "")); } DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx) { - ctx->utilities().log_clear(); + ctx->utilities().log_clear(); } DLLEXPORT auto dds_solve_board(DDS_SOLVER_CTX ctx, const Deal& dl, int target, int solutions, int mode, FutureTricks* futp) -> int { - return SolveBoard(*ctx, - dl, - target, - solutions, - mode, - futp); + return SolveBoard(*ctx, + dl, + target, + solutions, + mode, + futp); } DLLEXPORT auto dds_solve_board_pbn(DDS_SOLVER_CTX ctx, const DealPBN& dlpbn, int target, int solutions, int mode, FutureTricks* futp) -> int { - return solve_board_pbn(*ctx, dlpbn, target, solutions, mode, futp); + return solve_board_pbn(*ctx, dlpbn, target, solutions, mode, futp); } DLLEXPORT auto dds_calc_dd_table(DDS_SOLVER_CTX ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int { - return calc_dd_table(*ctx, table_deal, table_results); + return calc_dd_table(*ctx, table_deal, table_results); } DLLEXPORT auto dds_calc_dd_table_pbn(DDS_SOLVER_CTX ctx, const DdTableDealPBN& table_deal, DdTableResults* table_results) -> int { - return calc_dd_table_pbn(*ctx, table_deal, table_results); + return calc_dd_table_pbn(*ctx, table_deal, table_results); } DLLEXPORT auto dds_calc_par( - DDS_SOLVER_CTX ctx, - const DdTableDeal& table_deal, - int vulnerable, - DdTableResults* table_results, - ParResults* par_results) -> int + DDS_SOLVER_CTX ctx, + const DdTableDeal& table_deal, + int vulnerable, + DdTableResults* table_results, + ParResults* par_results) -> int { - return calc_par(*ctx, table_deal, vulnerable, table_results, par_results); + return calc_par(*ctx, table_deal, vulnerable, table_results, par_results); } DLLEXPORT auto dds_calc_par_pbn( - DDS_SOLVER_CTX ctx, - const DdTableDealPBN& table_deal_pbn, - int vulnerable, - DdTableResults* table_results, - ParResults* par_results) -> int + DDS_SOLVER_CTX ctx, + const DdTableDealPBN& table_deal_pbn, + int vulnerable, + DdTableResults* table_results, + ParResults* par_results) -> int { - return calc_par_pbn(*ctx, table_deal_pbn, vulnerable, table_results, par_results); + return calc_par_pbn(*ctx, table_deal_pbn, vulnerable, table_results, par_results); } diff --git a/library/src/dealer_par.cpp b/library/src/dealer_par.cpp index 81773237e..5fdd980e6 100644 --- a/library/src/dealer_par.cpp +++ b/library/src/dealer_par.cpp @@ -20,14 +20,14 @@ using namespace std; /* First index: 0 nonvul, 1 vul. Second index: tricks down */ const int DOUBLED_SCORES[2][14] = { - { - 0 , 100, 300, 500, 800, 1100, 1400, 1700, - 2000, 2300, 2600, 2900, 3200, 3500 - }, - { - 0 , 200, 500, 800, 1100, 1400, 1700, 2000, - 2300, 2600, 2900, 3200, 3500, 3800 - } + { + 0 , 100, 300, 500, 800, 1100, 1400, 1700, + 2000, 2300, 2600, 2900, 3200, 3500 + }, + { + 0 , 200, 500, 800, 1100, 1400, 1700, 2000, + 2300, 2600, 2900, 3200, 3500, 3800 + } }; /* First index is contract number, @@ -36,14 +36,14 @@ const int DOUBLED_SCORES[2][14] = const int SCORES[36][2] = { - { 0, 0}, - { 70, 70}, { 70, 70}, { 80, 80}, { 80, 80}, { 90, 90}, - { 90, 90}, { 90, 90}, { 110, 110}, { 110, 110}, { 120, 120}, - { 110, 110}, { 110, 110}, { 140, 140}, { 140, 140}, { 400, 600}, - { 130, 130}, { 130, 130}, { 420, 620}, { 420, 620}, { 430, 630}, - { 400, 600}, { 400, 600}, { 450, 650}, { 450, 650}, { 460, 660}, - { 920, 1370}, { 920, 1370}, { 980, 1430}, { 980, 1430}, { 990, 1440}, - {1440, 2140}, {1440, 2140}, {1510, 2210}, {1510, 2210}, {1520, 2220} + { 0, 0}, + { 70, 70}, { 70, 70}, { 80, 80}, { 80, 80}, { 90, 90}, + { 90, 90}, { 90, 90}, { 110, 110}, { 110, 110}, { 120, 120}, + { 110, 110}, { 110, 110}, { 140, 140}, { 140, 140}, { 400, 600}, + { 130, 130}, { 130, 130}, { 420, 620}, { 420, 620}, { 430, 630}, + { 400, 600}, { 400, 600}, { 450, 650}, { 450, 650}, { 460, 660}, + { 920, 1370}, { 920, 1370}, { 980, 1430}, { 980, 1430}, { 990, 1440}, + {1440, 2140}, {1440, 2140}, {1510, 2210}, {1510, 2210}, {1520, 2220} }; /* Second index is contract number, 0 .. 35. @@ -51,34 +51,34 @@ const int SCORES[36][2] = const int DOWN_TARGET[36][4] = { - {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, - {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {2, 1, 3, 2}, - {1, 0, 1, 0}, {1, 0, 1, 0}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, - {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, - {4, 3, 5, 4}, {4, 3, 5, 4}, {4, 3, 6, 5}, {4, 3, 6, 5}, {4, 3, 6, 5}, - {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7} + {0, 0, 0, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, + {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 1, 0}, {2, 1, 3, 2}, + {1, 0, 1, 0}, {1, 0, 1, 0}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, + {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, {2, 1, 3, 2}, + {4, 3, 5, 4}, {4, 3, 5, 4}, {4, 3, 6, 5}, {4, 3, 6, 5}, {4, 3, 6, 5}, + {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7}, {6, 5, 8, 7} }; const int FLOOR_CONTRACT[36] = { 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 15, 1, 2, 18, 19, 15, - 21, 22, 18, 19, 15, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35 + 21, 22, 18, 19, 15, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35 }; const vector NUMBER_TO_CONTRACT = { - "0", - "1C", "1D", "1H", "1S", "1N", - "2C", "2D", "2H", "2S", "2N", - "3C", "3D", "3H", "3S", "3N", - "4C", "4D", "4H", "4S", "4N", - "5C", "5D", "5H", "5S", "5N", - "6C", "6D", "6H", "6S", "6N", - "7C", "7D", "7H", "7S", "7N" + "0", + "1C", "1D", "1H", "1S", "1N", + "2C", "2D", "2H", "2S", "2N", + "3C", "3D", "3H", "3S", "3N", + "4C", "4D", "4H", "4S", "4N", + "5C", "5D", "5H", "5S", "5N", + "6C", "6D", "6H", "6S", "6N", + "7C", "7D", "7H", "7S", "7N" }; const vector NUMBER_TO_PLAYER = { "N", "E", "S", "W" }; @@ -97,20 +97,20 @@ const int DENOM_ORDER[5] = { 3, 2, 1, 0, 4 }; struct data_type { - int primacy; - int highest_making_no; - int dearest_making_no; - int dearest_score; - int vul_no; + int primacy; + int highest_making_no; + int dearest_making_no; + int dearest_score; + int vul_no; }; struct list_type { - int score; - int dno; - int no; - int tricks; - int down; + int score; + int dno; + int no; + int tricks; + int down; }; @@ -118,51 +118,51 @@ constexpr int BIGNUM = 9999; void survey_scores( - const DdTableResults& table, - const int dealer, - const int vul_by_side[2], - data_type& data, - int& num_candidates, - list_type list[2][DDS_STRAINS]); + const DdTableResults& table, + const int dealer, + const int vul_by_side[2], + data_type& data, + int& num_candidates, + list_type list[2][DDS_STRAINS]); void best_sacrifice( - const DdTableResults& table, - const int side, - const int no, - const int dno, - const int dealer, - const list_type list[2][5], - int sacr[5][5], - int& best_down); + const DdTableResults& table, + const int side, + const int no, + const int dno, + const int dealer, + const list_type list[2][5], + int sacr[5][5], + int& best_down); void sacrifices_as_text( - const DdTableResults& table, - const int side, - const int dealer, - const int best_down, - const int no_decl, - const int dno, - const list_type list[2][5], - const int sacr[5][5], - char results[10][10], - int& res_no); + const DdTableResults& table, + const int side, + const int dealer, + const int best_down, + const int no_decl, + const int dno, + const list_type list[2][5], + const int sacr[5][5], + char results[10][10], + int& res_no); void reduce_contract( - int& no, - const int down, - int& plus); + int& no, + const int down, + int& plus); string contract_as_text( - const DdTableResults& table, - const int side, - const int no, - const int dno, - const int down); + const DdTableResults& table, + const int side, + const int no, + const int dno, + const int down); string sacrifice_as_text( - const int no, - const int pno, - const int down); + const int no, + const int pno, + const int down); @@ -179,452 +179,452 @@ string sacrifice_as_text( * @return 1 on success, error code otherwise */ int STDCALL DealerPar( - DdTableResults const * tablep, - ParResultsDealer * presp, - int dealer, - int vulnerable) + DdTableResults const * tablep, + ParResultsDealer * presp, + int dealer, + int vulnerable) { - /* dealer 0: North 1: East 2: South 3: West */ - /* vulnerable 0: None 1: Both 2: NS 3: EW */ + /* dealer 0: North 1: East 2: South 3: West */ + /* vulnerable 0: None 1: Both 2: NS 3: EW */ - if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) - return check; + if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) + return check; - /* Both parameters reach array subscripts: vulnerable indexes VUL_LOOKUP + /* Both parameters reach array subscripts: vulnerable indexes VUL_LOOKUP below, and dealer propagates into the par tables via pno_list[]. */ - if (int const check = par_vulnerable_checks(vulnerable); - check != RETURN_NO_FAULT) - return check; + if (int const check = par_vulnerable_checks(vulnerable); + check != RETURN_NO_FAULT) + return check; - if (dealer < 0 || dealer > 3) - return RETURN_UNKNOWN_FAULT; + if (dealer < 0 || dealer > 3) + return RETURN_UNKNOWN_FAULT; - int const * vul_by_side = VUL_LOOKUP[vulnerable]; - data_type data; - list_type list[2][DDS_STRAINS]; + int const * vul_by_side = VUL_LOOKUP[vulnerable]; + data_type data; + list_type list[2][DDS_STRAINS]; - /* First we find the side entitled to a plus score (primacy) + /* First we find the side entitled to a plus score (primacy) and some statistics for each constructively bid (undoubled) contract that might be the par score. */ - int num_cand; - survey_scores(* tablep, dealer, vul_by_side, data, num_cand, list); - int side = data.primacy; + int num_cand; + survey_scores(* tablep, dealer, vul_by_side, data, num_cand, list); + int side = data.primacy; - if (side == -1) - { - presp->number = 1; - strcpy(presp->contracts[0], "pass"); - return RETURN_NO_FAULT; - } - - /* Go through the contracts, starting from the highest one. */ - list_type * lists = list[side]; - int vul_no = data.vul_no; - int best_plus = 0; - int down = 0; - int sac_found = 0; - - int type[DDS_STRAINS], sac_gap[DDS_STRAINS]; - int best_down = 0; - int sacr[DDS_STRAINS][DDS_STRAINS] = - { {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0} - }; - - for (int n = 0; n < num_cand; n++) - { - int no = lists[n].no; - int dno = lists[n].dno; - int target = DOWN_TARGET[no][vul_no]; - - best_sacrifice(* tablep, side, no, dno, dealer, list, sacr, down); - - if (down <= target) - { - if (down > best_down) best_down = down; - if (sac_found) - { - /* Declarer will never get a higher sacrifice by bidding - less, so we can stop looking for sacrifices. But it - can't be a worthwhile contract to bid, either. */ - type[n] = -1; - } - else - { - sac_found = 1; - type[n] = 0; - lists[n].down = down; - } - } - else + if (side == -1) { - if (lists[n].score > best_plus) - best_plus = lists[n].score; - type[n] = 1; - sac_gap[n] = target - down; + presp->number = 1; + strcpy(presp->contracts[0], "pass"); + return RETURN_NO_FAULT; } - } - int res_no = 0; - int vul_def = vul_by_side[1 - side]; - int sac = DOUBLED_SCORES[vul_def][best_down]; + /* Go through the contracts, starting from the highest one. */ + list_type * lists = list[side]; + int vul_no = data.vul_no; + int best_plus = 0; + int down = 0; + int sac_found = 0; - if (! sac_found || best_plus > sac) - { - /* The primacy side bids. */ - presp->score = (side == 0 ? best_plus : -best_plus); + int type[DDS_STRAINS], sac_gap[DDS_STRAINS]; + int best_down = 0; + int sacr[DDS_STRAINS][DDS_STRAINS] = + { {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0} + }; for (int n = 0; n < num_cand; n++) { - if (type[n] != 1 || lists[n].score != best_plus) continue; - int no = lists[n].no, plus; - reduce_contract(no, sac_gap[n], plus); + int no = lists[n].no; + int dno = lists[n].dno; + int target = DOWN_TARGET[no][vul_no]; + + best_sacrifice(* tablep, side, no, dno, dealer, list, sacr, down); - strcpy(presp->contracts[res_no], - contract_as_text(* tablep, side, no, lists[n].dno, plus).c_str()); - res_no++; + if (down <= target) + { + if (down > best_down) best_down = down; + if (sac_found) + { + /* Declarer will never get a higher sacrifice by bidding + less, so we can stop looking for sacrifices. But it + can't be a worthwhile contract to bid, either. */ + type[n] = -1; + } + else + { + sac_found = 1; + type[n] = 0; + lists[n].down = down; + } + } + else + { + if (lists[n].score > best_plus) + best_plus = lists[n].score; + type[n] = 1; + sac_gap[n] = target - down; + } } - } - else - { - /* The primacy side collects the penalty. */ - int sac_vul = vul_by_side[1 - side]; - int sac_score = DOUBLED_SCORES[sac_vul][best_down]; - presp->score = (side == 0 ? sac_score : -sac_score); - for (int n = 0; n < num_cand; n++) + int res_no = 0; + int vul_def = vul_by_side[1 - side]; + int sac = DOUBLED_SCORES[vul_def][best_down]; + + if (! sac_found || best_plus > sac) { - if (type[n] != 0 || lists[n].down != best_down) continue; - sacrifices_as_text(* tablep, side, dealer, best_down, + /* The primacy side bids. */ + presp->score = (side == 0 ? best_plus : -best_plus); + + for (int n = 0; n < num_cand; n++) + { + if (type[n] != 1 || lists[n].score != best_plus) continue; + int no = lists[n].no, plus; + reduce_contract(no, sac_gap[n], plus); + + strcpy(presp->contracts[res_no], + contract_as_text(* tablep, side, no, lists[n].dno, plus).c_str()); + res_no++; + } + } + else + { + /* The primacy side collects the penalty. */ + int sac_vul = vul_by_side[1 - side]; + int sac_score = DOUBLED_SCORES[sac_vul][best_down]; + presp->score = (side == 0 ? sac_score : -sac_score); + + for (int n = 0; n < num_cand; n++) + { + if (type[n] != 0 || lists[n].down != best_down) continue; + sacrifices_as_text(* tablep, side, dealer, best_down, lists[n].no, lists[n].dno, list, sacr, presp->contracts, res_no); + } } - } - presp->number = res_no; - return RETURN_NO_FAULT; + presp->number = res_no; + return RETURN_NO_FAULT; } void survey_scores( - const DdTableResults& table, - const int dealer, - const int vul_by_side[2], - data_type& data, - int& num_candidates, - list_type list[2][DDS_STRAINS]) + const DdTableResults& table, + const int dealer, + const int vul_by_side[2], + data_type& data, + int& num_candidates, + list_type list[2][DDS_STRAINS]) { - /* - When this is done, data has added the following entries: - * primacy (0 or 1) is the side entitled to a plus score. - If the Deal should be passed out, it is -1, and nothing - else is set. - * highest_making_no is a contract number (for that side) - * dearest_making_no is a contract number (for that side) - * dearest_score is the best score if there is no sacrifice - * vul_no is an index for a table, seen from the primacy - - list[side][dno] has added the following entries: - * score - * dno is the denomination number - * no is a contract number - * tricks is the number of tricks embedded in the contract - For the primacy side, the list is sorted in descending - order of the contract number (no). - */ - - data_type stats[2]; - - for (int side = 0; side <= 1; side++) - { - int highest_making_no = 0; - int dearest_making_no = 0; - int dearest_score = 0; - - for (int dno = 0; dno < DDS_STRAINS; dno++) + /* + When this is done, data has added the following entries: + * primacy (0 or 1) is the side entitled to a plus score. + If the Deal should be passed out, it is -1, and nothing + else is set. + * highest_making_no is a contract number (for that side) + * dearest_making_no is a contract number (for that side) + * dearest_score is the best score if there is no sacrifice + * vul_no is an index for a table, seen from the primacy + + list[side][dno] has added the following entries: + * score + * dno is the denomination number + * no is a contract number + * tricks is the number of tricks embedded in the contract + For the primacy side, the list is sorted in descending + order of the contract number (no). + */ + + data_type stats[2]; + + for (int side = 0; side <= 1; side++) { - list_type * slist = &list[side][dno]; - int const * t = table.res_table[ DENOM_ORDER[dno] ]; - const int a = t[side]; - const int b = t[side + 2]; - const int best = (a > b ? a : b); - - const int no = 5 * (best - 7) + dno + 1; - slist->no = no; /* May be negative! */ - - if (best < 7) - { - slist->score = 0; - continue; - } - - const int score = SCORES[no][ vul_by_side[side] ]; - slist->score = score; - slist->dno = dno; - slist->tricks = best; - - if (score > dearest_score) - { - dearest_score = score; - dearest_making_no = no; - } - else if (score == dearest_score && no < dearest_making_no) - { - /* The lowest such, e.g. 3NT and 5C. */ - dearest_making_no = no; - } - - if (no > highest_making_no) - { - highest_making_no = no; - } + int highest_making_no = 0; + int dearest_making_no = 0; + int dearest_score = 0; + + for (int dno = 0; dno < DDS_STRAINS; dno++) + { + list_type * slist = &list[side][dno]; + int const * t = table.res_table[ DENOM_ORDER[dno] ]; + const int a = t[side]; + const int b = t[side + 2]; + const int best = (a > b ? a : b); + + const int no = 5 * (best - 7) + dno + 1; + slist->no = no; /* May be negative! */ + + if (best < 7) + { + slist->score = 0; + continue; + } + + const int score = SCORES[no][ vul_by_side[side] ]; + slist->score = score; + slist->dno = dno; + slist->tricks = best; + + if (score > dearest_score) + { + dearest_score = score; + dearest_making_no = no; + } + else if (score == dearest_score && no < dearest_making_no) + { + /* The lowest such, e.g. 3NT and 5C. */ + dearest_making_no = no; + } + + if (no > highest_making_no) + { + highest_making_no = no; + } + } + data_type& sside = stats[side]; + sside.highest_making_no = highest_making_no; + sside.dearest_making_no = dearest_making_no; + sside.dearest_score = dearest_score; } - data_type& sside = stats[side]; - sside.highest_making_no = highest_making_no; - sside.dearest_making_no = dearest_making_no; - sside.dearest_score = dearest_score; - } - - int primacy = 0; - const int s0 = stats[0].highest_making_no; - const int s1 = stats[1].highest_making_no; - if (s0 > s1) - { - primacy = 0; - } - else if (s0 < s1) - { - primacy = 1; - } - else if (s0 == 0) - { - data.primacy = -1; - return; - } - else - { - /* Special case, depends who can bid it first. */ - const int dno = (s0 - 1) % 5; - const int t_max = list[0][dno].tricks; - int const * t = table.res_table[ DENOM_ORDER[dno] ]; - for (int pno = dealer; pno <= dealer + 3; pno++) + int primacy = 0; + const int s0 = stats[0].highest_making_no; + const int s1 = stats[1].highest_making_no; + if (s0 > s1) + { + primacy = 0; + } + else if (s0 < s1) + { + primacy = 1; + } + else if (s0 == 0) + { + data.primacy = -1; + return; + } + else { - if (t[pno % 4] != t_max) - continue; - primacy = pno % 2; - break; + /* Special case, depends who can bid it first. */ + const int dno = (s0 - 1) % 5; + const int t_max = list[0][dno].tricks; + int const * t = table.res_table[ DENOM_ORDER[dno] ]; + + for (int pno = dealer; pno <= dealer + 3; pno++) + { + if (t[pno % 4] != t_max) + continue; + primacy = pno % 2; + break; + } } - } - data_type * sside = &stats[primacy]; + data_type * sside = &stats[primacy]; - const int dm_no = sside->dearest_making_no; - data.primacy = primacy; - data.highest_making_no = sside->highest_making_no; - data.dearest_making_no = dm_no; - data.dearest_score = sside->dearest_score; + const int dm_no = sside->dearest_making_no; + data.primacy = primacy; + data.highest_making_no = sside->highest_making_no; + data.dearest_making_no = dm_no; + data.dearest_score = sside->dearest_score; - const int vul_primacy = vul_by_side[primacy]; - const int vul_other = vul_by_side[1 - primacy]; - data.vul_no = VUL_TO_NO[vul_primacy][vul_other]; + const int vul_primacy = vul_by_side[primacy]; + const int vul_other = vul_by_side[1 - primacy]; + data.vul_no = VUL_TO_NO[vul_primacy][vul_other]; - /* Sort the scores in descending order of contract number, + /* Sort the scores in descending order of contract number, i.e. first by score and second by contract number in case the score is the same. Primitive bubble sort... */ - int n = DDS_STRAINS; - do - { - int new_n = 0; - for (int i = 1; i < n; i++) + int n = DDS_STRAINS; + do { - if (list[primacy][i - 1].no > list[primacy][i].no) - continue; + int new_n = 0; + for (int i = 1; i < n; i++) + { + if (list[primacy][i - 1].no > list[primacy][i].no) + continue; - list_type temp = list[primacy][i - 1]; - list[primacy][i - 1] = list[primacy][i]; - list[primacy][i] = temp; + list_type temp = list[primacy][i - 1]; + list[primacy][i - 1] = list[primacy][i]; + list[primacy][i] = temp; - new_n = i; + new_n = i; + } + n = new_n; + } + while (n > 0); + + num_candidates = DDS_STRAINS; + for (n = 0; n < DDS_STRAINS; n++) + { + if (list[primacy][n].no < dm_no) + num_candidates--; } - n = new_n; - } - while (n > 0); - - num_candidates = DDS_STRAINS; - for (n = 0; n < DDS_STRAINS; n++) - { - if (list[primacy][n].no < dm_no) - num_candidates--; - } } void best_sacrifice( - const DdTableResults& table, - const int side, - const int no, - const int dno, - const int dealer, - const list_type list[2][DDS_STRAINS], - int sacr_table[DDS_STRAINS][DDS_STRAINS], - int& best_down) + const DdTableResults& table, + const int side, + const int no, + const int dno, + const int dealer, + const list_type list[2][DDS_STRAINS], + int sacr_table[DDS_STRAINS][DDS_STRAINS], + int& best_down) { - const int other = 1 - side; - list_type const * sacr_list = list[other]; - best_down = BIGNUM; + const int other = 1 - side; + list_type const * sacr_list = list[other]; + best_down = BIGNUM; - for (int eno = 0; eno <= 4; eno++) - { - const list_type sacr = sacr_list[eno]; - int down = BIGNUM; - - if (eno == dno) + for (int eno = 0; eno <= 4; eno++) { - const int t_max = static_cast((no + 34) / 5); - int const * t = table.res_table[ DENOM_ORDER[dno] ]; - int incr_flag = 0; - for (int pno = dealer; pno <= dealer + 3; pno++) - { - const int diff = t_max - t[pno % 4]; - const int s = pno % 2; - if (s == side) + const list_type sacr = sacr_list[eno]; + int down = BIGNUM; + + if (eno == dno) { - if (diff == 0) - incr_flag = 1; + const int t_max = static_cast((no + 34) / 5); + int const * t = table.res_table[ DENOM_ORDER[dno] ]; + int incr_flag = 0; + for (int pno = dealer; pno <= dealer + 3; pno++) + { + const int diff = t_max - t[pno % 4]; + const int s = pno % 2; + if (s == side) + { + if (diff == 0) + incr_flag = 1; + } + else + { + const int local = diff + incr_flag; + if (local < down) + down = local; + } + } + if (sacr.no + 5 * down > 35) + down = BIGNUM; } else { - const int local = diff + incr_flag; - if (local < down) - down = local; + down = static_cast((no - sacr.no + 4) / 5); + if (sacr.no + 5 * down > 35) down = BIGNUM; } - } - if (sacr.no + 5 * down > 35) - down = BIGNUM; - } - else - { - down = static_cast((no - sacr.no + 4) / 5); - if (sacr.no + 5 * down > 35) down = BIGNUM; + sacr_table[dno][eno] = down; + if (down < best_down) + best_down = down; } - sacr_table[dno][eno] = down; - if (down < best_down) - best_down = down; - } } void sacrifices_as_text( - const DdTableResults& table, - const int side, - const int dealer, - const int best_down, - const int no_decl, - const int dno, - const list_type list[2][DDS_STRAINS], - const int sacr[DDS_STRAINS][DDS_STRAINS], - char results[10][10], - int& res_no) + const DdTableResults& table, + const int side, + const int dealer, + const int best_down, + const int no_decl, + const int dno, + const list_type list[2][DDS_STRAINS], + const int sacr[DDS_STRAINS][DDS_STRAINS], + char results[10][10], + int& res_no) { - const int other = 1 - side; - list_type const * sacr_list = list[other]; + const int other = 1 - side; + list_type const * sacr_list = list[other]; - for (int eno = 0; eno <= 4; eno++) - { - int down = sacr[dno][eno]; - if (down != best_down) continue; - - if (eno != dno) - { - const int no_sac = sacr_list[eno].no + 5 * best_down; - strcpy(results[res_no], - contract_as_text(table, other, no_sac, eno, -best_down).c_str()); - res_no++; - continue; - } - - const int t_max = static_cast((no_decl + 34) / 5); - int const * t = table.res_table[ DENOM_ORDER[dno] ]; - int incr_flag = 0; - int p_hit = 0; - int pno_list[2], sac_list[2]; - for (int pno = dealer; pno <= dealer + 3; pno++) + for (int eno = 0; eno <= 4; eno++) { - int pno_mod = pno % 4; - int diff = t_max - t[pno_mod]; - int s = pno % 2; - if (s == side) - { - if (diff == 0) incr_flag = 1; - } - else - { - down = diff + incr_flag; + int down = sacr[dno][eno]; if (down != best_down) continue; - pno_list[p_hit] = pno_mod; - sac_list[p_hit] = no_decl + 5 * incr_flag; - p_hit++; - } - } - const int ns0 = sac_list[0]; - if (p_hit == 1) - { - strcpy(results[res_no], - sacrifice_as_text(ns0, pno_list[0], best_down).c_str()); - res_no++; - continue; - } + if (eno != dno) + { + const int no_sac = sacr_list[eno].no + 5 * best_down; + strcpy(results[res_no], + contract_as_text(table, other, no_sac, eno, -best_down).c_str()); + res_no++; + continue; + } - const int ns1 = sac_list[1]; - if (ns0 == ns1) - { - /* Both players */ - strcpy(results[res_no], - contract_as_text(table, other, ns0, eno, -best_down).c_str()); - res_no++; - continue; - } + const int t_max = static_cast((no_decl + 34) / 5); + int const * t = table.res_table[ DENOM_ORDER[dno] ]; + int incr_flag = 0; + int p_hit = 0; + int pno_list[2], sac_list[2]; + for (int pno = dealer; pno <= dealer + 3; pno++) + { + int pno_mod = pno % 4; + int diff = t_max - t[pno_mod]; + int s = pno % 2; + if (s == side) + { + if (diff == 0) incr_flag = 1; + } + else + { + down = diff + incr_flag; + if (down != best_down) continue; + pno_list[p_hit] = pno_mod; + sac_list[p_hit] = no_decl + 5 * incr_flag; + p_hit++; + } + } - const int p = (ns0 < ns1 ? 0 : 1); - strcpy(results[res_no], - sacrifice_as_text(sac_list[p], pno_list[p], best_down).c_str()); - res_no++; - } + const int ns0 = sac_list[0]; + if (p_hit == 1) + { + strcpy(results[res_no], + sacrifice_as_text(ns0, pno_list[0], best_down).c_str()); + res_no++; + continue; + } + + const int ns1 = sac_list[1]; + if (ns0 == ns1) + { + /* Both players */ + strcpy(results[res_no], + contract_as_text(table, other, ns0, eno, -best_down).c_str()); + res_no++; + continue; + } + + const int p = (ns0 < ns1 ? 0 : 1); + strcpy(results[res_no], + sacrifice_as_text(sac_list[p], pno_list[p], best_down).c_str()); + res_no++; + } } void reduce_contract( - int& no, - const int sac_gap, - int& plus) + int& no, + const int sac_gap, + int& plus) { - /* Could be that we found 4C just making, but it would be + /* Could be that we found 4C just making, but it would be enough to bid 2C +2. But we don't want to bid so low that we lose a game or slam bonus. */ - if (sac_gap >= -1) - { - /* No scope to reduce. */ - plus = 0; - return; - } + if (sac_gap >= -1) + { + /* No scope to reduce. */ + plus = 0; + return; + } - /* This is the lowest contract that we could reduce to. */ - int flr = FLOOR_CONTRACT[no]; + /* This is the lowest contract that we could reduce to. */ + int flr = FLOOR_CONTRACT[no]; - /* As such, declarer could reduce the contract by down+1 levels + /* As such, declarer could reduce the contract by down+1 levels (where down is negative) and still the opponent's sacrifice would not turn profitable. But for non-vulnerable partials, this can go wrong: 1M+1 and 2M= both pay +90, but 3m*-2 is a bad sacrifice against 2M=, while 2m*-1 would be a good sacrifice against 1M+1. */ - int no_sac_level = no + 5 * (sac_gap + 1); - int new_no = (no_sac_level > flr ? no_sac_level : flr); - plus = (no - new_no) / 5; - no = new_no; + int no_sac_level = no + 5 * (sac_gap + 1); + int new_no = (no_sac_level > flr ? no_sac_level : flr); + plus = (no - new_no) / 5; + no = new_no; } @@ -636,46 +636,46 @@ void reduce_contract( string contract_text(const int no) { - if (no < 0 || static_cast(no) >= NUMBER_TO_CONTRACT.size()) - return "?"; - return NUMBER_TO_CONTRACT[static_cast(no)]; + if (no < 0 || static_cast(no) >= NUMBER_TO_CONTRACT.size()) + return "?"; + return NUMBER_TO_CONTRACT[static_cast(no)]; } string player_text(const int pno) { - if (pno < 0 || static_cast(pno) >= NUMBER_TO_PLAYER.size()) - return "?"; - return NUMBER_TO_PLAYER[static_cast(pno)]; + if (pno < 0 || static_cast(pno) >= NUMBER_TO_PLAYER.size()) + return "?"; + return NUMBER_TO_PLAYER[static_cast(pno)]; } string contract_as_text( - const DdTableResults& table, - const int side, - const int no, - const int dno, - const int delta) + const DdTableResults& table, + const int side, + const int no, + const int dno, + const int delta) { - int const * t = table.res_table[ DENOM_ORDER[dno] ]; - const int ta = t[side]; - const int tb = t[side + 2]; - const int t_max = (ta > tb ? ta : tb); - - return contract_text(no) + - (delta < 0 ? "*-" : "-") + - (ta == t_max ? player_text(side) : "") + - (tb == t_max ? player_text(side + 2) : "") + - (delta > 0 ? "+" : "") + - (delta == 0 ? "" : to_string(delta)); + int const * t = table.res_table[ DENOM_ORDER[dno] ]; + const int ta = t[side]; + const int tb = t[side + 2]; + const int t_max = (ta > tb ? ta : tb); + + return contract_text(no) + + (delta < 0 ? "*-" : "-") + + (ta == t_max ? player_text(side) : "") + + (tb == t_max ? player_text(side + 2) : "") + + (delta > 0 ? "+" : "") + + (delta == 0 ? "" : to_string(delta)); } string sacrifice_as_text( - const int no, - const int pno, - const int down) + const int no, + const int pno, + const int down) { - return contract_text(no) + "-" + - player_text(pno) + "-" + - to_string(down); + return contract_text(no) + "-" + + player_text(pno) + "-" + + to_string(down); } diff --git a/library/src/dump.cpp b/library/src/dump.cpp index b9cff22c8..257dd1308 100644 --- a/library/src/dump.cpp +++ b/library/src/dump.cpp @@ -24,16 +24,16 @@ std::string PrintSuit(const unsigned short suitCode); std::string PrintSuit( - const unsigned short suitCode, - const char leastWin); + const unsigned short suitCode, + const char leastWin); std::string PrintDeal( - const unsigned short ranks[][DDS_SUITS], - const int spacing); + const unsigned short ranks[][DDS_SUITS], + const int spacing); std::string RankToDiagrams( - const unsigned short ranks[DDS_HANDS][DDS_SUITS], - const NodeCards& node); + const unsigned short ranks[DDS_HANDS][DDS_SUITS], + const NodeCards& node); std::string WinnersToText(const unsigned short win_ranks[]); @@ -42,232 +42,232 @@ std::string NodeToText(const NodeCards& node); std::string FullNodeToText(const NodeCards& node); std::string PosToText( - const Pos& tpos, - const int target, - const int depth); + const Pos& tpos, + const int target, + const int depth); std::string TopMove( - const bool val, - const MoveType& bestMove); + const bool val, + const MoveType& bestMove); std::string DumpTopHeader( - const std::shared_ptr& thrp, - const int tricks, - const int lower, - const int upper, - const int printMode); + const std::shared_ptr& thrp, + const int tricks, + const int lower, + const int upper, + const int printMode); std::string PrintSuit(const unsigned short suitCode) { - if (! suitCode) - return "--"; - - std::string st; - for (int r = 14; r >= 2; r--) - if ((suitCode & bit_map_rank[r])) - st += static_cast(card_rank[r]); - return st; + if (! suitCode) + return "--"; + + std::string st; + for (int r = 14; r >= 2; r--) + if ((suitCode & bit_map_rank[r])) + st += static_cast(card_rank[r]); + return st; } std::string PrintSuit( - const unsigned short suitCode, - const char leastWin) + const unsigned short suitCode, + const char leastWin) { - if (! suitCode) - return "--"; + if (! suitCode) + return "--"; - std::string st; - for (int r = 14; r >= 2; r--) - { - if ((suitCode & bit_map_rank[r])) + std::string st; + for (int r = 14; r >= 2; r--) { - if (r >= 15 - leastWin) - st += static_cast(card_rank[r]); - else - st += "x"; + if ((suitCode & bit_map_rank[r])) + { + if (r >= 15 - leastWin) + st += static_cast(card_rank[r]); + else + st += "x"; + } } - } - return st; + return st; } std::string PrintDeal( - const unsigned short ranks[][DDS_SUITS], - const int spacing) + const unsigned short ranks[][DDS_SUITS], + const int spacing) { - std::stringstream ss; - for (int s = 0; s < DDS_SUITS; s++) - { - ss << std::setw(spacing) << "" << - card_suit[s] << " " << - PrintSuit(ranks[0][s]) << "\n"; - } - - for (int s = 0; s < DDS_SUITS; s++) - { - ss << card_suit[s] << " " << - std::setw(2*spacing - 2) << std::left << PrintSuit(ranks[3][s]) << - card_suit[s] << " " << - PrintSuit(ranks[1][s]) << "\n"; - } - - for (int s = 0; s < DDS_SUITS; s++) - { - ss << std::setw(spacing) << "" << - card_suit[s] << " " << - PrintSuit(ranks[2][s]) << "\n"; - } - - return ss.str() + "\n"; + std::stringstream ss; + for (int s = 0; s < DDS_SUITS; s++) + { + ss << std::setw(spacing) << "" << + card_suit[s] << " " << + PrintSuit(ranks[0][s]) << "\n"; + } + + for (int s = 0; s < DDS_SUITS; s++) + { + ss << card_suit[s] << " " << + std::setw(2*spacing - 2) << std::left << PrintSuit(ranks[3][s]) << + card_suit[s] << " " << + PrintSuit(ranks[1][s]) << "\n"; + } + + for (int s = 0; s < DDS_SUITS; s++) + { + ss << std::setw(spacing) << "" << + card_suit[s] << " " << + PrintSuit(ranks[2][s]) << "\n"; + } + + return ss.str() + "\n"; } std::string RankToDiagrams( - const unsigned short ranks[DDS_HANDS][DDS_SUITS], - const NodeCards& node) + const unsigned short ranks[DDS_HANDS][DDS_SUITS], + const NodeCards& node) { - std::stringstream ss; - for (int s = 0; s < DDS_SUITS; s++) - { - ss << std::setw(12) << std::left << - (s == 0 ? "Sought" : "") << - card_suit[s] << " " << std::setw(20) << PrintSuit(ranks[0][s]) << "| " << - std::setw(12) << (s == 0 ? "Found" : "") << - card_suit[s] << " " << - PrintSuit(ranks[0][s], node.least_win[s]) << "\n"; - } - - for (int s = 0; s < DDS_SUITS; s++) - { - ss << - card_suit[s] << " " << std::setw(22) << std::left << PrintSuit(ranks[3][s]) << - card_suit[s] << " " << std::setw(8) << PrintSuit(ranks[1][s]) << "| " << - card_suit[s] << " " << - std::setw(22) << PrintSuit(ranks[3][s], node.least_win[s]) << - card_suit[s] << " " << - PrintSuit(ranks[1][s], node.least_win[s]) << "\n"; - } - - for (int s = 0; s < DDS_SUITS; s++) - { - ss << std::setw(12) << std::left << "" << - card_suit[s] << " " << std::setw(20) << PrintSuit(ranks[0][s]) << "| " << - std::setw(12) << "" << card_suit[s] << " " << - PrintSuit(ranks[0][s], node.least_win[s]) << "\n"; - } - return ss.str(); + std::stringstream ss; + for (int s = 0; s < DDS_SUITS; s++) + { + ss << std::setw(12) << std::left << + (s == 0 ? "Sought" : "") << + card_suit[s] << " " << std::setw(20) << PrintSuit(ranks[0][s]) << "| " << + std::setw(12) << (s == 0 ? "Found" : "") << + card_suit[s] << " " << + PrintSuit(ranks[0][s], node.least_win[s]) << "\n"; + } + + for (int s = 0; s < DDS_SUITS; s++) + { + ss << + card_suit[s] << " " << std::setw(22) << std::left << PrintSuit(ranks[3][s]) << + card_suit[s] << " " << std::setw(8) << PrintSuit(ranks[1][s]) << "| " << + card_suit[s] << " " << + std::setw(22) << PrintSuit(ranks[3][s], node.least_win[s]) << + card_suit[s] << " " << + PrintSuit(ranks[1][s], node.least_win[s]) << "\n"; + } + + for (int s = 0; s < DDS_SUITS; s++) + { + ss << std::setw(12) << std::left << "" << + card_suit[s] << " " << std::setw(20) << PrintSuit(ranks[0][s]) << "| " << + std::setw(12) << "" << card_suit[s] << " " << + PrintSuit(ranks[0][s], node.least_win[s]) << "\n"; + } + return ss.str(); } std::string WinnersToText(const unsigned short ourWinRanks[]) { - std::stringstream ss; - for (int s = 0; s < DDS_SUITS; s++) - ss << card_suit[s] << " " << PrintSuit(ourWinRanks[s]) << "\n"; + std::stringstream ss; + for (int s = 0; s < DDS_SUITS; s++) + ss << card_suit[s] << " " << PrintSuit(ourWinRanks[s]) << "\n"; - return ss.str(); + return ss.str(); } std::string NodeToText(const NodeCards& node) { - std::stringstream ss; - ss << std::setw(16) << std::left << "Address" << - static_cast(&node) << "\n"; + std::stringstream ss; + ss << std::setw(16) << std::left << "Address" << + static_cast(&node) << "\n"; - ss << std::setw(16) << std::left << "Bounds" << - static_cast(node.lower_bound) << " to " << - static_cast(node.upper_bound) << " tricks\n"; + ss << std::setw(16) << std::left << "Bounds" << + static_cast(node.lower_bound) << " to " << + static_cast(node.upper_bound) << " tricks\n"; - ss << std::setw(16) << std::left << "Best move" << - card_suit[ static_cast(node.best_move_suit) ] << - card_rank[ static_cast(node.best_move_rank) ] << "\n"; + ss << std::setw(16) << std::left << "Best move" << + card_suit[ static_cast(node.best_move_suit) ] << + card_rank[ static_cast(node.best_move_rank) ] << "\n"; - return ss.str(); + return ss.str(); } std::string FullNodeToText(const NodeCards& node) { - std::stringstream ss; - std::vector v(DDS_SUITS); - for (unsigned i = 0; i < DDS_SUITS; i++) - v[i] = 15 - static_cast(node.least_win[i]); - - ss << std::setw(16) << std::left << "Lowest used" << - card_suit[0] << card_rank[v[0]] << ", " << - card_suit[1] << card_rank[v[1]] << ", " << - card_suit[2] << card_rank[v[2]] << ", " << - card_suit[3] << card_rank[v[3]] << "\n"; - - return NodeToText(node) + ss.str(); + std::stringstream ss; + std::vector v(DDS_SUITS); + for (unsigned i = 0; i < DDS_SUITS; i++) + v[i] = 15 - static_cast(node.least_win[i]); + + ss << std::setw(16) << std::left << "Lowest used" << + card_suit[0] << card_rank[v[0]] << ", " << + card_suit[1] << card_rank[v[1]] << ", " << + card_suit[2] << card_rank[v[2]] << ", " << + card_suit[3] << card_rank[v[3]] << "\n"; + + return NodeToText(node) + ss.str(); } std::string PosToText( - const Pos& tpos, - const int target, - const int depth) + const Pos& tpos, + const int target, + const int depth) { - std::stringstream ss; - ss << std::setw(16) << std::left << "Target" << target << "\n"; - ss << std::setw(16) << "Depth" << depth << "\n"; - ss << std::setw(16) << "tricks_max" << tpos.tricks_max << "\n"; - ss << std::setw(16) << "First hand" << card_hand[tpos.first[depth]] << "\n"; - ss << std::setw(16) << "Next first" << card_hand[tpos.first[depth - 1]] << "\n"; - return ss.str(); + std::stringstream ss; + ss << std::setw(16) << std::left << "Target" << target << "\n"; + ss << std::setw(16) << "Depth" << depth << "\n"; + ss << std::setw(16) << "tricks_max" << tpos.tricks_max << "\n"; + ss << std::setw(16) << "First hand" << card_hand[tpos.first[depth]] << "\n"; + ss << std::setw(16) << "Next first" << card_hand[tpos.first[depth - 1]] << "\n"; + return ss.str(); } std::string DumpTopHeader( - const std::shared_ptr& thrp, - const int tricks, - const int lower, - const int upper, - const int printMode) + const std::shared_ptr& thrp, + const int tricks, + const int lower, + const int upper, + const int printMode) { - // Use facade to read search-state safely (caller provides shared_ptr) - SolverContext ctx{ thrp }; - std::string stext; - if (printMode == 0) - { - // Trying just one target. - stext = "Single target " + std::to_string(tricks) + ", " + "achieved"; - } - else if (printMode == 1) - { - // Looking for best score. - stext = "Loop target " + std::to_string(tricks) + ", " + - "bounds " + std::to_string(lower) + " .. " + std::to_string(upper) + ", " + - TopMove(thrp->val, ctx.search().best_move(ctx.search().ini_depth())) + ""; - } - else if (printMode == 2) - { - // Looking for other moves with best score. - stext = "Loop for cards with score " + std::to_string(tricks) + ", " + - TopMove(thrp->val, ctx.search().best_move(ctx.search().ini_depth())); - } - return stext + "\n" + std::string(stext.size(), '-') + "\n"; + // Use facade to read search-state safely (caller provides shared_ptr) + SolverContext ctx{ thrp }; + std::string stext; + if (printMode == 0) + { + // Trying just one target. + stext = "Single target " + std::to_string(tricks) + ", " + "achieved"; + } + else if (printMode == 1) + { + // Looking for best score. + stext = "Loop target " + std::to_string(tricks) + ", " + + "bounds " + std::to_string(lower) + " .. " + std::to_string(upper) + ", " + + TopMove(thrp->val, ctx.search().best_move(ctx.search().ini_depth())) + ""; + } + else if (printMode == 2) + { + // Looking for other moves with best score. + stext = "Loop for cards with score " + std::to_string(tricks) + ", " + + TopMove(thrp->val, ctx.search().best_move(ctx.search().ini_depth())); + } + return stext + "\n" + std::string(stext.size(), '-') + "\n"; } std::string TopMove( - const bool val, - const MoveType& bestMove) + const bool val, + const MoveType& bestMove) { - if (val) - { - std::stringstream ss; - ss << "achieved with move " << - card_suit[ bestMove.suit ] << - card_rank[ bestMove.rank ]; - return ss.str(); - } - else - return "failed"; + if (val) + { + std::stringstream ss; + ss << "achieved with move " << + card_suit[ bestMove.suit ] << + card_rank[ bestMove.rank ]; + return ss.str(); + } + else + return "failed"; } @@ -286,28 +286,28 @@ namespace { auto suit_text(const int suit, const int strain_count) -> std::string { - if (suit < 0 || suit >= strain_count) - return "?(" + std::to_string(suit) + ")"; - return std::string(1, static_cast(card_suit[suit])); + if (suit < 0 || suit >= strain_count) + return "?(" + std::to_string(suit) + ")"; + return std::string(1, static_cast(card_suit[suit])); } /// Trump: 0..3 plus DDS_NOTRUMP. auto trump_text(const int trump) -> std::string { - return suit_text(trump, DDS_STRAINS); + return suit_text(trump, DDS_STRAINS); } /// Suit led in the current trick: no-trump is not a legal value. auto trick_suit_text(const int suit) -> std::string { - return suit_text(suit, DDS_SUITS); + return suit_text(suit, DDS_SUITS); } auto hand_text(const int hand) -> std::string { - if (hand < 0 || hand >= DDS_HANDS) - return "?(" + std::to_string(hand) + ")"; - return std::string(1, static_cast(card_hand[hand])); + if (hand < 0 || hand >= DDS_HANDS) + return "?(" + std::to_string(hand) + ")"; + return std::string(1, static_cast(card_hand[hand])); } /* card_rank[] has 16 entries, but indices 0, 1 and 15 hold the sentinels 'x' @@ -317,134 +317,134 @@ auto hand_text(const int hand) -> std::string auto rank_text(const int rank) -> std::string { - constexpr int min_rank = 2; // deuce - constexpr int max_rank = 14; // ace - if (rank < min_rank || rank > max_rank) - return "?(" + std::to_string(rank) + ")"; - return std::string(1, static_cast(card_rank[rank])); + constexpr int min_rank = 2; // deuce + constexpr int max_rank = 14; // ace + if (rank < min_rank || rank > max_rank) + return "?(" + std::to_string(rank) + ")"; + return std::string(1, static_cast(card_rank[rank])); } } // namespace int DumpInput( - const int errCode, - const Deal& dl, - const int target, - const int solutions, - const int mode) + const int errCode, + const Deal& dl, + const int target, + const int solutions, + const int mode) { #ifndef DDS_NO_DUMP_ON_ERROR - std::ofstream fout; - fout.open("dump.txt"); - - fout << "Error code=" << errCode << "\n\n"; - fout << "Deal data:\n"; - fout << "trump="; - - if (dl.trump == DDS_NOTRUMP) - fout << "N\n"; - else - fout << trump_text(dl.trump) << "\n"; - fout << "first=" << hand_text(dl.first) << "\n"; - - unsigned short ranks[4][4]; - - for (int k = 0; k <= 2; k++) - if (dl.currentTrickRank[k] != 0) - { - fout << "index=" << k << - " currentTrickSuit=" << trick_suit_text(dl.currentTrickSuit[k]) << - " currentTrickRank= " << rank_text(dl.currentTrickRank[k]) << "\n"; - } - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - { - fout << "index1=" << h << " index2=" << s << - " remainCards=" << dl.remainCards[h][s] << "\n"; - ranks[h][s] = static_cast - (dl.remainCards[h][s] >> 2); - } - - fout << "\ntarget=" << target << "\n"; - fout << "solutions=" << solutions << "\n"; - fout << "mode=" << mode << "\n\n\n"; - fout << PrintDeal(ranks, 8); - - fout.close(); + std::ofstream fout; + fout.open("dump.txt"); + + fout << "Error code=" << errCode << "\n\n"; + fout << "Deal data:\n"; + fout << "trump="; + + if (dl.trump == DDS_NOTRUMP) + fout << "N\n"; + else + fout << trump_text(dl.trump) << "\n"; + fout << "first=" << hand_text(dl.first) << "\n"; + + unsigned short ranks[4][4]; + + for (int k = 0; k <= 2; k++) + if (dl.currentTrickRank[k] != 0) + { + fout << "index=" << k << + " currentTrickSuit=" << trick_suit_text(dl.currentTrickSuit[k]) << + " currentTrickRank= " << rank_text(dl.currentTrickRank[k]) << "\n"; + } + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + { + fout << "index1=" << h << " index2=" << s << + " remainCards=" << dl.remainCards[h][s] << "\n"; + ranks[h][s] = static_cast + (dl.remainCards[h][s] >> 2); + } + + fout << "\ntarget=" << target << "\n"; + fout << "solutions=" << solutions << "\n"; + fout << "mode=" << mode << "\n\n\n"; + fout << PrintDeal(ranks, 8); + + fout.close(); #endif - return 0; + return 0; } void DumpTopLevel( - std::ofstream& fout, - const std::shared_ptr& thrp, - const int tricks, - const int lower, - const int upper, - const int printMode) + std::ofstream& fout, + const std::shared_ptr& thrp, + const int tricks, + const int lower, + const int upper, + const int printMode) { - const Pos& tpos = thrp->lookAheadPos; - SolverContext ctx{ thrp }; - - fout << DumpTopHeader(thrp, tricks, lower, upper, printMode) << "\n"; - fout << PrintDeal(tpos.rank_in_suit, 16); - fout << WinnersToText(tpos.win_ranks[ctx.search().ini_depth()]) << "\n"; - fout << ctx.search().nodes() << " AB nodes, " << - ctx.search().trick_nodes() << " trick nodes\n\n"; + const Pos& tpos = thrp->lookAheadPos; + SolverContext ctx{ thrp }; + + fout << DumpTopHeader(thrp, tricks, lower, upper, printMode) << "\n"; + fout << PrintDeal(tpos.rank_in_suit, 16); + fout << WinnersToText(tpos.win_ranks[ctx.search().ini_depth()]) << "\n"; + fout << ctx.search().nodes() << " AB nodes, " << + ctx.search().trick_nodes() << " trick nodes\n\n"; } #ifdef DDS_AB_HITS void DumpRetrieved( - std::ofstream& fout, - const Pos& tpos, - const NodeCards& node, - const int target, - const int depth) + std::ofstream& fout, + const Pos& tpos, + const NodeCards& node, + const int target, + const int depth) { - fout << "Retrieved entry\n"; - fout << std::string(15, '-') << "\n"; - fout << PosToText(tpos, target, depth) << "\n"; - fout << FullNodeToText(node) << "\n"; - fout << RankToDiagrams(tpos.rank_in_suit, node) << "\n"; + fout << "Retrieved entry\n"; + fout << std::string(15, '-') << "\n"; + fout << PosToText(tpos, target, depth) << "\n"; + fout << FullNodeToText(node) << "\n"; + fout << RankToDiagrams(tpos.rank_in_suit, node) << "\n"; } void DumpStored( - std::ofstream& fout, - const Pos& tpos, - const Moves& moves, - const NodeCards& node, - const int target, - const int depth) + std::ofstream& fout, + const Pos& tpos, + const Moves& moves, + const NodeCards& node, + const int target, + const int depth) { - fout << "Stored entry\n"; - fout << std::string(12, '-') << "\n"; - fout << PosToText(tpos, target, depth) << "\n"; - fout << NodeToText(node); - fout << moves.TrickToText((depth >> 2) + 1) << "\n"; - fout << PrintDeal(tpos.rank_in_suit, 16); + fout << "Stored entry\n"; + fout << std::string(12, '-') << "\n"; + fout << PosToText(tpos, target, depth) << "\n"; + fout << NodeToText(node); + fout << moves.TrickToText((depth >> 2) + 1) << "\n"; + fout << PrintDeal(tpos.rank_in_suit, 16); } void DumpStored( - std::ofstream& fout, - const Pos& tpos, - SolverContext& ctx, - const NodeCards& node, - const int target, - const int depth) + std::ofstream& fout, + const Pos& tpos, + SolverContext& ctx, + const NodeCards& node, + const int target, + const int depth) { - fout << "Stored entry\n"; - fout << std::string(12, '-') << "\n"; - fout << PosToText(tpos, target, depth) << "\n"; - fout << NodeToText(node); - fout << ctx.move_gen().trick_to_text((depth >> 2) + 1) << "\n"; - fout << PrintDeal(tpos.rank_in_suit, 16); + fout << "Stored entry\n"; + fout << std::string(12, '-') << "\n"; + fout << PosToText(tpos, target, depth) << "\n"; + fout << NodeToText(node); + fout << ctx.move_gen().trick_to_text((depth >> 2) + 1) << "\n"; + fout << PrintDeal(tpos.rank_in_suit, 16); } #endif // DDS_AB_HITS diff --git a/library/src/dump.hpp b/library/src/dump.hpp index f75779fb2..f2d82168c 100644 --- a/library/src/dump.hpp +++ b/library/src/dump.hpp @@ -14,41 +14,41 @@ #include int DumpInput( - const int errCode, - const Deal& dl, - const int target, - const int solutions, - const int mode); + const int errCode, + const Deal& dl, + const int target, + const int solutions, + const int mode); void DumpTopLevel( - std::ofstream& fout, - const std::shared_ptr& thrp, - const int tricks, - const int lower, - const int upper, - const int printMode); + std::ofstream& fout, + const std::shared_ptr& thrp, + const int tricks, + const int lower, + const int upper, + const int printMode); void DumpRetrieved( - std::ofstream& fout, - const Pos& tpos, - const NodeCards& node, - const int target, - const int depth); + std::ofstream& fout, + const Pos& tpos, + const NodeCards& node, + const int target, + const int depth); void DumpStored( - std::ofstream& fout, - const Pos& tpos, - const Moves& moves, - const NodeCards& node, - const int target, - const int depth); + std::ofstream& fout, + const Pos& tpos, + const Moves& moves, + const NodeCards& node, + const int target, + const int depth); // Convenience overload to avoid direct Moves exposure at call sites void DumpStored( - std::ofstream& fout, - const Pos& tpos, - SolverContext& ctx, - const NodeCards& node, - const int target, - const int depth); + std::ofstream& fout, + const Pos& tpos, + SolverContext& ctx, + const NodeCards& node, + const int target, + const int depth); diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 7a028aed3..78bcb82d9 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -6,29 +6,29 @@ // re-derived from the context here. void call_heuristic(HeuristicContext& context, const WeightCase weight_case) { - switch (weight_case) { - case WeightCase::Nt0: weight_alloc_nt0(context); break; - case WeightCase::Trump0: weight_alloc_trump0(context); break; - case WeightCase::NtNotVoid1: weight_alloc_nt_notvoid1(context); break; - case WeightCase::TrumpNotVoid1: weight_alloc_trump_notvoid1(context); break; - case WeightCase::NtVoid1: weight_alloc_nt_void1(context); break; - case WeightCase::TrumpVoid1: weight_alloc_trump_void1(context); break; - case WeightCase::NtNotVoid2: weight_alloc_nt_notvoid2(context); break; - case WeightCase::TrumpNotVoid2: weight_alloc_trump_notvoid2(context); break; - case WeightCase::NtVoid2: weight_alloc_nt_void2(context); break; - case WeightCase::TrumpVoid2: weight_alloc_trump_void2(context); break; - case WeightCase::CombinedNotVoid3: - case WeightCase::CombinedNotVoid3Trump: - weight_alloc_combined_notvoid3(context); - break; - case WeightCase::NtVoid3: weight_alloc_nt_void3(context); break; - case WeightCase::TrumpVoid3: weight_alloc_trump_void3(context); break; - default: - // Should not happen; fall back to NT leading weights so moves get - // deterministic ordering instead of stale/uninitialized weights. - weight_alloc_nt0(context); - break; - } + switch (weight_case) { + case WeightCase::Nt0: weight_alloc_nt0(context); break; + case WeightCase::Trump0: weight_alloc_trump0(context); break; + case WeightCase::NtNotVoid1: weight_alloc_nt_notvoid1(context); break; + case WeightCase::TrumpNotVoid1: weight_alloc_trump_notvoid1(context); break; + case WeightCase::NtVoid1: weight_alloc_nt_void1(context); break; + case WeightCase::TrumpVoid1: weight_alloc_trump_void1(context); break; + case WeightCase::NtNotVoid2: weight_alloc_nt_notvoid2(context); break; + case WeightCase::TrumpNotVoid2: weight_alloc_trump_notvoid2(context); break; + case WeightCase::NtVoid2: weight_alloc_nt_void2(context); break; + case WeightCase::TrumpVoid2: weight_alloc_trump_void2(context); break; + case WeightCase::CombinedNotVoid3: + case WeightCase::CombinedNotVoid3Trump: + weight_alloc_combined_notvoid3(context); + break; + case WeightCase::NtVoid3: weight_alloc_nt_void3(context); break; + case WeightCase::TrumpVoid3: weight_alloc_trump_void3(context); break; + default: + // Should not happen; fall back to NT leading weights so moves get + // deterministic ordering instead of stale/uninitialized weights. + weight_alloc_nt0(context); + break; + } } // The following functions are extracted from Moves.cpp and refactored to be @@ -37,1366 +37,1366 @@ void call_heuristic(HeuristicContext& context, const WeightCase weight_case) void weight_alloc_trump0(HeuristicContext& context) { - const unsigned short suitCount = context.tpos.length[context.lead_hand][context.suit]; - const unsigned short suit_count_lh = context.tpos.length[lho[context.lead_hand]][context.suit]; - const unsigned short suit_count_rh = context.tpos.length[rho[context.lead_hand]][context.suit]; - const unsigned short aggr = context.tpos.aggr[context.suit]; + const unsigned short suitCount = context.tpos.length[context.lead_hand][context.suit]; + const unsigned short suit_count_lh = context.tpos.length[lho[context.lead_hand]][context.suit]; + const unsigned short suit_count_rh = context.tpos.length[rho[context.lead_hand]][context.suit]; + const unsigned short aggr = context.tpos.aggr[context.suit]; - // Why? - int countLH = (suit_count_lh == 0 ? context.curr_trick + 1 : suit_count_lh) << 2; - int countRH = (suit_count_rh == 0 ? context.curr_trick + 1 : suit_count_rh) << 2; + // Why? + int countLH = (suit_count_lh == 0 ? context.curr_trick + 1 : suit_count_lh) << 2; + int countRH = (suit_count_rh == 0 ? context.curr_trick + 1 : suit_count_rh) << 2; - int suit_weight_d = - (((countLH + countRH) << 5) / 13); + int suit_weight_d = - (((countLH + countRH) << 5) / 13); - for (int k = context.last_num_moves; k < context.num_moves; k++) - { - int suit_bonus = 0; - bool win_move = false; + for (int k = context.last_num_moves; k < context.num_moves; k++) + { + int suit_bonus = 0; + bool win_move = false; - int r_rank = rel_rank[aggr][context.mply[k].rank]; + int r_rank = rel_rank[aggr][context.mply[k].rank]; - /* Discourage suit if LHO or RHO can ruff. */ - if ((context.suit != context.trump) && - (((context.tpos.rank_in_suit[lho[context.lead_hand]][context.suit] == 0) && - (context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump] != 0)) || + /* Discourage suit if LHO or RHO can ruff. */ + if ((context.suit != context.trump) && + (((context.tpos.rank_in_suit[lho[context.lead_hand]][context.suit] == 0) && + (context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump] != 0)) || ((context.tpos.rank_in_suit[rho[context.lead_hand]][context.suit] == 0) && - (context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump] != 0)))) - suit_bonus = -12; - - /* Encourage suit if partner can ruff. */ - if ((context.suit != context.trump) && - (context.tpos.length[partner[context.lead_hand]][context.suit] == 0) && - (context.tpos.length[partner[context.lead_hand]][context.trump] > 0) && - (suit_count_rh > 0)) - suit_bonus += 17; - - /* Discourage suit if RHO has high card. */ - if ((context.tpos.winner[context.suit].hand == rho[context.lead_hand]) || - (context.tpos.second_best[context.suit].hand == rho[context.lead_hand])) - { - if (suit_count_rh != 1) - suit_bonus += -12; - } + (context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump] != 0)))) + suit_bonus = -12; + + /* Encourage suit if partner can ruff. */ + if ((context.suit != context.trump) && + (context.tpos.length[partner[context.lead_hand]][context.suit] == 0) && + (context.tpos.length[partner[context.lead_hand]][context.trump] > 0) && + (suit_count_rh > 0)) + suit_bonus += 17; + + /* Discourage suit if RHO has high card. */ + if ((context.tpos.winner[context.suit].hand == rho[context.lead_hand]) || + (context.tpos.second_best[context.suit].hand == rho[context.lead_hand])) + { + if (suit_count_rh != 1) + suit_bonus += -12; + } - /* Try suit if LHO has winning card and partner second best. + /* Try suit if LHO has winning card and partner second best. Exception: partner has singleton. */ - else if ((context.tpos.winner[context.suit].hand == lho[context.lead_hand]) && + else if ((context.tpos.winner[context.suit].hand == lho[context.lead_hand]) && (context.tpos.second_best[context.suit].hand == partner[context.lead_hand])) - { - /* This case was suggested by Joel Bradmetz. */ - if (context.tpos.length[partner[context.lead_hand]][context.suit] != 1) - suit_bonus += 27; - } + { + /* This case was suggested by Joel Bradmetz. */ + if (context.tpos.length[partner[context.lead_hand]][context.suit] != 1) + suit_bonus += 27; + } - /* Encourage play of suit where partner wins and + /* Encourage play of suit where partner wins and returns the suit for a ruff. */ - if ((context.suit != context.trump) && (suitCount == 1) && - (context.tpos.length[context.lead_hand][context.trump] > 0) && - (context.tpos.length[partner[context.lead_hand]][context.suit] > 1) && - (context.tpos.winner[context.suit].hand == partner[context.lead_hand])) - suit_bonus += 19; + if ((context.suit != context.trump) && (suitCount == 1) && + (context.tpos.length[context.lead_hand][context.trump] > 0) && + (context.tpos.length[partner[context.lead_hand]][context.suit] > 1) && + (context.tpos.winner[context.suit].hand == partner[context.lead_hand])) + suit_bonus += 19; - /* Discourage a suit selection where the search tree appears larger + /* Discourage a suit selection where the search tree appears larger than for the altenative suits: the search is estimated to be small when the added number of alternative cards to play for the opponents is small. */ - int suit_weight_delta = suit_bonus + suit_weight_d; + int suit_weight_delta = suit_bonus + suit_weight_d; - if (context.tpos.winner[context.suit].rank == context.mply[k].rank) - { - if ((context.suit != context.trump)) - { - if ((context.tpos.length[partner[context.lead_hand]][context.suit] != 0) || - (context.tpos.length[partner[context.lead_hand]][context.trump] == 0)) + if (context.tpos.winner[context.suit].rank == context.mply[k].rank) { - if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || + if ((context.suit != context.trump)) + { + if ((context.tpos.length[partner[context.lead_hand]][context.suit] != 0) || + (context.tpos.length[partner[context.lead_hand]][context.trump] == 0)) + { + if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || (context.tpos.length[lho[context.lead_hand]][context.trump] == 0)) && - ((context.tpos.length[rho[context.lead_hand]][context.suit] != 0) || + ((context.tpos.length[rho[context.lead_hand]][context.suit] != 0) || (context.tpos.length[rho[context.lead_hand]][context.trump] == 0))) - win_move = true; - } - else if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || - (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > + win_move = true; + } + else if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || + (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump])) && ((context.tpos.length[rho[context.lead_hand]][context.suit] != 0) || - (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > + (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump]))) - win_move = true; - } - else - win_move = true; - } - else if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.suit] > + win_move = true; + } + else + win_move = true; + } + else if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.suit] > (context.tpos.rank_in_suit[lho[context.lead_hand]][context.suit] | - context.tpos.rank_in_suit[rho[context.lead_hand]][context.suit])) - { - if (context.suit != context.trump) - { - if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || + context.tpos.rank_in_suit[rho[context.lead_hand]][context.suit])) + { + if (context.suit != context.trump) + { + if (((context.tpos.length[lho[context.lead_hand]][context.suit] != 0) || (context.tpos.length[lho[context.lead_hand]][context.trump] == 0)) && - ((context.tpos.length[rho[context.lead_hand]][context.suit] != 0) || + ((context.tpos.length[rho[context.lead_hand]][context.suit] != 0) || (context.tpos.length[rho[context.lead_hand]][context.trump] == 0))) - win_move = true; - } - else - win_move = true; - } - else if (context.suit != context.trump) - { - if ((context.tpos.length[partner[context.lead_hand]][context.suit] == 0) && - (context.tpos.length[partner[context.lead_hand]][context.trump] != 0)) - { - if ((context.tpos.length[lho[context.lead_hand]][context.suit] == 0) && - (context.tpos.length[lho[context.lead_hand]][context.trump] != 0) && - (context.tpos.length[rho[context.lead_hand]][context.suit] == 0) && - (context.tpos.length[rho[context.lead_hand]][context.trump] != 0)) + win_move = true; + } + else + win_move = true; + } + else if (context.suit != context.trump) { - if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > - (context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump] | + if ((context.tpos.length[partner[context.lead_hand]][context.suit] == 0) && + (context.tpos.length[partner[context.lead_hand]][context.trump] != 0)) + { + if ((context.tpos.length[lho[context.lead_hand]][context.suit] == 0) && + (context.tpos.length[lho[context.lead_hand]][context.trump] != 0) && + (context.tpos.length[rho[context.lead_hand]][context.suit] == 0) && + (context.tpos.length[rho[context.lead_hand]][context.trump] != 0)) + { + if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] > + (context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump] | context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump])) - win_move = true; - } - else if ((context.tpos.length[lho[context.lead_hand]][context.suit] == 0) && + win_move = true; + } + else if ((context.tpos.length[lho[context.lead_hand]][context.suit] == 0) && (context.tpos.length[lho[context.lead_hand]][context.trump] != 0)) - { - if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] - > context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump]) - win_move = true; - } - else if ((context.tpos.length[rho[context.lead_hand]][context.suit] == 0) && + { + if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] + > context.tpos.rank_in_suit[lho[context.lead_hand]][context.trump]) + win_move = true; + } + else if ((context.tpos.length[rho[context.lead_hand]][context.suit] == 0) && (context.tpos.length[rho[context.lead_hand]][context.trump] != 0)) - { - if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] - > context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump]) - win_move = true; + { + if (context.tpos.rank_in_suit[partner[context.lead_hand]][context.trump] + > context.tpos.rank_in_suit[rho[context.lead_hand]][context.trump]) + win_move = true; + } + else + win_move = true; + } } - else - win_move = true; - } - } - if (win_move) - { - /* Encourage ruffing LHO or RHO singleton, highest card. */ - if (((suit_count_lh == 1) && + if (win_move) + { + /* Encourage ruffing LHO or RHO singleton, highest card. */ + if (((suit_count_lh == 1) && (context.tpos.winner[context.suit].hand == lho[context.lead_hand])) - || ((suit_count_rh == 1) && - (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) - context.mply[k].weight = suit_weight_delta + 35 + r_rank; - - /* Lead hand has the highest card. */ - - else if (context.tpos.winner[context.suit].hand == context.lead_hand) - { - /* Also, partner has second highest card. */ - if (context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) - context.mply[k].weight = suit_weight_delta + 48 + r_rank; - else if (context.tpos.winner[context.suit].rank == context.mply[k].rank) - /* If the current card to play is the highest card. */ - context.mply[k].weight = suit_weight_delta + 31; - else - context.mply[k].weight = suit_weight_delta - 3 + r_rank; - } - else if (context.tpos.winner[context.suit].hand == partner[context.lead_hand]) - { - /* If partner has highest card */ - if (context.tpos.second_best[context.suit].hand == context.lead_hand) - context.mply[k].weight = suit_weight_delta + 42 + r_rank; - else - context.mply[k].weight = suit_weight_delta + 28 + r_rank; - } - /* Encourage playing second highest rank if hand also has + || ((suit_count_rh == 1) && + (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) + context.mply[k].weight = suit_weight_delta + 35 + r_rank; + + /* Lead hand has the highest card. */ + + else if (context.tpos.winner[context.suit].hand == context.lead_hand) + { + /* Also, partner has second highest card. */ + if (context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) + context.mply[k].weight = suit_weight_delta + 48 + r_rank; + else if (context.tpos.winner[context.suit].rank == context.mply[k].rank) + /* If the current card to play is the highest card. */ + context.mply[k].weight = suit_weight_delta + 31; + else + context.mply[k].weight = suit_weight_delta - 3 + r_rank; + } + else if (context.tpos.winner[context.suit].hand == partner[context.lead_hand]) + { + /* If partner has highest card */ + if (context.tpos.second_best[context.suit].hand == context.lead_hand) + context.mply[k].weight = suit_weight_delta + 42 + r_rank; + else + context.mply[k].weight = suit_weight_delta + 28 + r_rank; + } + /* Encourage playing second highest rank if hand also has third highest rank. */ - else if ((context.mply[k].sequence) && + else if ((context.mply[k].sequence) && (context.mply[k].rank == context.tpos.second_best[context.suit].rank)) - context.mply[k].weight = suit_weight_delta + 40; - else if (context.mply[k].sequence) - context.mply[k].weight = suit_weight_delta + 22 + r_rank; - else - context.mply[k].weight = suit_weight_delta + 11 + r_rank; + context.mply[k].weight = suit_weight_delta + 40; + else if (context.mply[k].sequence) + context.mply[k].weight = suit_weight_delta + 22 + r_rank; + else + context.mply[k].weight = suit_weight_delta + 11 + r_rank; - /* playing cards that previously caused search cutoff + /* playing cards that previously caused search cutoff or was stored as the best move in a transposition table entry match. */ - // Only use best_move/best_move_tt if they're valid (non-empty) - if ((context.best_move.rank > 0) && - (context.best_move.suit == context.suit) && - (context.best_move.rank == context.mply[k].rank)) - context.mply[k].weight += 55; - else if ((context.best_move_tt.rank > 0) && + // Only use best_move/best_move_tt if they're valid (non-empty) + if ((context.best_move.rank > 0) && + (context.best_move.suit == context.suit) && + (context.best_move.rank == context.mply[k].rank)) + context.mply[k].weight += 55; + else if ((context.best_move_tt.rank > 0) && (context.best_move_tt.suit == context.suit) && (context.best_move_tt.rank == context.mply[k].rank)) - context.mply[k].weight += 18; - } - else - { - /* Encourage playing the suit if the hand together with partner + context.mply[k].weight += 18; + } + else + { + /* Encourage playing the suit if the hand together with partner have both the 2nd highest and the 3rd highest cards such that the side of the hand has the highest card in the next round playing this suit. */ - int thirdBestHand = context.thrp_rel[aggr].abs_rank[3][context.suit].hand; + int thirdBestHand = context.thrp_rel[aggr].abs_rank[3][context.suit].hand; - if ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && - (partner[context.lead_hand] == thirdBestHand)) - suit_weight_delta += 20; - else if (((context.tpos.second_best[context.suit].hand == context.lead_hand) && - (partner[context.lead_hand] == thirdBestHand) && - (context.tpos.length[partner[context.lead_hand]][context.suit] > 1)) || + if ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && + (partner[context.lead_hand] == thirdBestHand)) + suit_weight_delta += 20; + else if (((context.tpos.second_best[context.suit].hand == context.lead_hand) && + (partner[context.lead_hand] == thirdBestHand) && + (context.tpos.length[partner[context.lead_hand]][context.suit] > 1)) || ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && - (context.lead_hand == thirdBestHand) && - (context.tpos.length[partner[context.lead_hand]][context.suit] > 1))) - suit_weight_delta += 13; + (context.lead_hand == thirdBestHand) && + (context.tpos.length[partner[context.lead_hand]][context.suit] > 1))) + suit_weight_delta += 13; - /* Higher weight if LHO or RHO has the highest (winning) card as + /* Higher weight if LHO or RHO has the highest (winning) card as a singleton. */ - if (((suit_count_lh == 1) && + if (((suit_count_lh == 1) && (context.tpos.winner[context.suit].hand == lho[context.lead_hand])) - || ((suit_count_rh == 1) && - (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) - context.mply[k].weight = suit_weight_delta + r_rank + 2; - else if (context.tpos.winner[context.suit].hand == context.lead_hand) - { - if (context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) - /* Opponents win by ruffing */ - context.mply[k].weight = suit_weight_delta + 33 + r_rank; - else if (context.tpos.winner[context.suit].rank == context.mply[k].rank) - /* Opponents win by ruffing */ - context.mply[k].weight = suit_weight_delta + 38; - else - context.mply[k].weight = suit_weight_delta - 14 + r_rank; - } - else if (context.tpos.winner[context.suit].hand == partner[context.lead_hand]) - { - /* Opponents win by ruffing */ - context.mply[k].weight = suit_weight_delta + 34 + r_rank; - } - /* Encourage playing second highest rank if hand also has + || ((suit_count_rh == 1) && + (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) + context.mply[k].weight = suit_weight_delta + r_rank + 2; + else if (context.tpos.winner[context.suit].hand == context.lead_hand) + { + if (context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) + /* Opponents win by ruffing */ + context.mply[k].weight = suit_weight_delta + 33 + r_rank; + else if (context.tpos.winner[context.suit].rank == context.mply[k].rank) + /* Opponents win by ruffing */ + context.mply[k].weight = suit_weight_delta + 38; + else + context.mply[k].weight = suit_weight_delta - 14 + r_rank; + } + else if (context.tpos.winner[context.suit].hand == partner[context.lead_hand]) + { + /* Opponents win by ruffing */ + context.mply[k].weight = suit_weight_delta + 34 + r_rank; + } + /* Encourage playing second highest rank if hand also has third highest rank. */ - else if ((context.mply[k].sequence) && + else if ((context.mply[k].sequence) && (context.mply[k].rank == context.tpos.second_best[context.suit].rank)) - context.mply[k].weight = suit_weight_delta + 35; - else - context.mply[k].weight = suit_weight_delta + 17 - (context.mply[k].rank); + context.mply[k].weight = suit_weight_delta + 35; + else + context.mply[k].weight = suit_weight_delta + 17 - (context.mply[k].rank); - /* Encourage playing cards that previously caused search cutoff + /* Encourage playing cards that previously caused search cutoff or was stored as the best move in a transposition table entry match. */ - if ((context.best_move.rank > 0) && - (context.best_move.suit == context.suit) && - (context.best_move.rank == context.mply[k].rank)) - context.mply[k].weight += 18; + if ((context.best_move.rank > 0) && + (context.best_move.suit == context.suit) && + (context.best_move.rank == context.mply[k].rank)) + context.mply[k].weight += 18; + } } - } } // Placeholder for the rest of the functions to be moved void weight_alloc_nt0(HeuristicContext& context) { - int aggr = context.tpos.aggr[context.suit]; + int aggr = context.tpos.aggr[context.suit]; - /* Discourage a suit selection where the search tree appears larger + /* Discourage a suit selection where the search tree appears larger than for the alternative suits: the search is estimated to be small when the added number of alternative cards to play for the opponents is small. */ - unsigned short suit_count_lh = context.tpos.length[lho[context.lead_hand]][context.suit]; - unsigned short suit_count_rh = context.tpos.length[rho[context.lead_hand]][context.suit]; + unsigned short suit_count_lh = context.tpos.length[lho[context.lead_hand]][context.suit]; + unsigned short suit_count_rh = context.tpos.length[rho[context.lead_hand]][context.suit]; - // Why? - int countLH = (suit_count_lh == 0 ? context.curr_trick + 1 : suit_count_lh) << 2; - int countRH = (suit_count_rh == 0 ? context.curr_trick + 1 : suit_count_rh) << 2; + // Why? + int countLH = (suit_count_lh == 0 ? context.curr_trick + 1 : suit_count_lh) << 2; + int countRH = (suit_count_rh == 0 ? context.curr_trick + 1 : suit_count_rh) << 2; - int suit_weight_d = - (((countLH + countRH) << 5) / 19); - if (context.tpos.length[partner[context.lead_hand]][context.suit] == 0) - suit_weight_d += -9; + int suit_weight_d = - (((countLH + countRH) << 5) / 19); + if (context.tpos.length[partner[context.lead_hand]][context.suit] == 0) + suit_weight_d += -9; - for (int k = context.last_num_moves; k < context.num_moves; k++) - { - int suit_weight_delta = suit_weight_d; - int r_rank = rel_rank[aggr][context.mply[k].rank]; + for (int k = context.last_num_moves; k < context.num_moves; k++) + { + int suit_weight_delta = suit_weight_d; + int r_rank = rel_rank[aggr][context.mply[k].rank]; - if (context.tpos.winner[context.suit].rank == context.mply[k].rank || - (context.tpos.rank_in_suit[partner[context.lead_hand]][context.suit] > + if (context.tpos.winner[context.suit].rank == context.mply[k].rank || + (context.tpos.rank_in_suit[partner[context.lead_hand]][context.suit] > (context.tpos.rank_in_suit[lho[context.lead_hand]][context.suit] | - context.tpos.rank_in_suit[rho[context.lead_hand]][context.suit]))) - { - // Can win trick, ourselves or partner. - // FIX: No distinction? - /* Discourage suit if RHO has second best card. + context.tpos.rank_in_suit[rho[context.lead_hand]][context.suit]))) + { + // Can win trick, ourselves or partner. + // FIX: No distinction? + /* Discourage suit if RHO has second best card. Exception: RHO has singleton. */ - if (context.tpos.second_best[context.suit].hand == rho[context.lead_hand]) - { - if (suit_count_rh != 1) - suit_weight_delta += -1; - } - /* Encourage playing suit if LHO has second highest rank. */ - else if (context.tpos.second_best[context.suit].hand == lho[context.lead_hand]) - { - if (suit_count_lh != 1) - suit_weight_delta += 22; - else - suit_weight_delta += 16; - } - - /* Higher weight if also second best rank is present on + if (context.tpos.second_best[context.suit].hand == rho[context.lead_hand]) + { + if (suit_count_rh != 1) + suit_weight_delta += -1; + } + /* Encourage playing suit if LHO has second highest rank. */ + else if (context.tpos.second_best[context.suit].hand == lho[context.lead_hand]) + { + if (suit_count_lh != 1) + suit_weight_delta += 22; + else + suit_weight_delta += 16; + } + + /* Higher weight if also second best rank is present on current side to play, or if second best is a singleton at LHO or RHO. */ - if (((context.tpos.second_best[context.suit].hand != lho[context.lead_hand]) + if (((context.tpos.second_best[context.suit].hand != lho[context.lead_hand]) || (suit_count_lh == 1)) && - ((context.tpos.second_best[context.suit].hand != rho[context.lead_hand]) + ((context.tpos.second_best[context.suit].hand != rho[context.lead_hand]) || (suit_count_rh == 1))) - context.mply[k].weight = suit_weight_delta + 45 + r_rank; - else - context.mply[k].weight = suit_weight_delta + 18 + r_rank; + context.mply[k].weight = suit_weight_delta + 45 + r_rank; + else + context.mply[k].weight = suit_weight_delta + 18 + r_rank; - /* Encourage playing cards that previously caused search cutoff + /* Encourage playing cards that previously caused search cutoff or was stored as the best move in a transposition table entry match. */ - if ((context.best_move.rank > 0) && - (context.best_move.suit == context.suit) && - (context.best_move.rank == context.mply[k].rank)) - context.mply[k].weight += 126; - else if ((context.best_move_tt.rank > 0) && + if ((context.best_move.rank > 0) && + (context.best_move.suit == context.suit) && + (context.best_move.rank == context.mply[k].rank)) + context.mply[k].weight += 126; + else if ((context.best_move_tt.rank > 0) && (context.best_move_tt.suit == context.suit) && (context.best_move_tt.rank == context.mply[k].rank)) - context.mply[k].weight += 32; - } - else - { - /* Discourage suit if RHO has winning or second best card. + context.mply[k].weight += 32; + } + else + { + /* Discourage suit if RHO has winning or second best card. Exception: RHO has singleton. */ - if ((context.tpos.winner[context.suit].hand == rho[context.lead_hand]) || - (context.tpos.second_best[context.suit].hand == rho[context.lead_hand])) - { - if (suit_count_rh != 1) - suit_weight_delta += -10; - } + if ((context.tpos.winner[context.suit].hand == rho[context.lead_hand]) || + (context.tpos.second_best[context.suit].hand == rho[context.lead_hand])) + { + if (suit_count_rh != 1) + suit_weight_delta += -10; + } - /* Try suit if LHO has winning card and partner second best. + /* Try suit if LHO has winning card and partner second best. Exception: partner has singleton. */ - else if ((context.tpos.winner[context.suit].hand == lho[context.lead_hand]) && + else if ((context.tpos.winner[context.suit].hand == lho[context.lead_hand]) && (context.tpos.second_best[context.suit].hand == partner[context.lead_hand])) - { - /* This case was suggested by Joel Bradmetz. */ - if (context.tpos.length[partner[context.lead_hand]][context.suit] != 1) - suit_weight_delta += 31; - } + { + /* This case was suggested by Joel Bradmetz. */ + if (context.tpos.length[partner[context.lead_hand]][context.suit] != 1) + suit_weight_delta += 31; + } - /* Encourage playing the suit if the hand together with partner + /* Encourage playing the suit if the hand together with partner have both the 2nd highest and the 3rd highest cards such that the side of the hand has the highest card in the next round playing this suit. */ - int thirdBestHand = context.thrp_rel[aggr].abs_rank[3][context.suit].hand; + int thirdBestHand = context.thrp_rel[aggr].abs_rank[3][context.suit].hand; - if ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && - (partner[context.lead_hand] == thirdBestHand)) - suit_weight_delta += 35; - else if (((context.tpos.second_best[context.suit].hand == context.lead_hand) && - (partner[context.lead_hand] == thirdBestHand) && - (context.tpos.length[partner[context.lead_hand]][context.suit] > 1)) || + if ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && + (partner[context.lead_hand] == thirdBestHand)) + suit_weight_delta += 35; + else if (((context.tpos.second_best[context.suit].hand == context.lead_hand) && + (partner[context.lead_hand] == thirdBestHand) && + (context.tpos.length[partner[context.lead_hand]][context.suit] > 1)) || ((context.tpos.second_best[context.suit].hand == partner[context.lead_hand]) && - (context.lead_hand == thirdBestHand) && - (context.tpos.length[partner[context.lead_hand]][context.suit] > 1))) - suit_weight_delta += 25; + (context.lead_hand == thirdBestHand) && + (context.tpos.length[partner[context.lead_hand]][context.suit] > 1))) + suit_weight_delta += 25; - /* Higher weight if LHO or RHO has the highest (winning) card + /* Higher weight if LHO or RHO has the highest (winning) card as a singleton. */ - if (((suit_count_lh == 1) && + if (((suit_count_lh == 1) && (context.tpos.winner[context.suit].hand == lho[context.lead_hand])) - || ((suit_count_rh == 1) && - (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) - context.mply[k].weight = suit_weight_delta + 28 + r_rank; - else if (context.tpos.winner[context.suit].hand == context.lead_hand) - context.mply[k].weight = suit_weight_delta - 17 + r_rank; - else if (! context.mply[k].sequence) - context.mply[k].weight = suit_weight_delta + 12 + r_rank; - else if (context.mply[k].rank == context.tpos.second_best[context.suit].rank) - context.mply[k].weight = suit_weight_delta + 48; - else - context.mply[k].weight = suit_weight_delta + 29 - r_rank; - - /* Encourage playing cards that previously caused search cutoff + || ((suit_count_rh == 1) && + (context.tpos.winner[context.suit].hand == rho[context.lead_hand]))) + context.mply[k].weight = suit_weight_delta + 28 + r_rank; + else if (context.tpos.winner[context.suit].hand == context.lead_hand) + context.mply[k].weight = suit_weight_delta - 17 + r_rank; + else if (! context.mply[k].sequence) + context.mply[k].weight = suit_weight_delta + 12 + r_rank; + else if (context.mply[k].rank == context.tpos.second_best[context.suit].rank) + context.mply[k].weight = suit_weight_delta + 48; + else + context.mply[k].weight = suit_weight_delta + 29 - r_rank; + + /* Encourage playing cards that previously caused search cutoff or was stored as the best move in a transposition table entry match. */ - if ((context.best_move.rank > 0) && - (context.best_move.suit == context.suit) && - (context.best_move.rank == context.mply[k].rank)) - context.mply[k].weight += 47; - else if ((context.best_move_tt.rank > 0) && + if ((context.best_move.rank > 0) && + (context.best_move.suit == context.suit) && + (context.best_move.rank == context.mply[k].rank)) + context.mply[k].weight += 47; + else if ((context.best_move_tt.rank > 0) && (context.best_move_tt.suit == context.suit) && (context.best_move_tt.rank == context.mply[k].rank)) - context.mply[k].weight += 19; + context.mply[k].weight += 19; + } } - } } void weight_alloc_trump_notvoid1(HeuristicContext& ctx) { - const Pos& tpos = ctx.tpos; - const int trump = ctx.trump; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - // trackp not needed here; use context snapshots for trick state. + const Pos& tpos = ctx.tpos; + const int trump = ctx.trump; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + // trackp not needed here; use context snapshots for trick state. - const int max3rd = highest_rank[ + const int max3rd = highest_rank[ tpos.rank_in_suit[partner[lead_hand]][lead_suit]]; - const int maxpd = highest_rank[ + const int maxpd = highest_rank[ tpos.rank_in_suit[rho[lead_hand] ][lead_suit]]; - const int min3rd = lowest_rank [ + const int min3rd = lowest_rank [ tpos.rank_in_suit[partner[lead_hand]][lead_suit]]; - const int minpd = lowest_rank [ + const int minpd = lowest_rank [ tpos.rank_in_suit[rho[lead_hand] ][lead_suit]]; - for (int k = 0; k < num_moves; k++) - { - bool win_move = false; /* If true, current move can win trick. */ - int r_rank = rel_rank[ tpos.aggr[lead_suit] ][mply[k].rank]; - - if (lead_suit == trump) + for (int k = 0; k < num_moves; k++) { - if (maxpd > ctx.lead0_rank && maxpd > max3rd) - win_move = true; - else if (mply[k].rank > ctx.lead0_rank && + bool win_move = false; /* If true, current move can win trick. */ + int r_rank = rel_rank[ tpos.aggr[lead_suit] ][mply[k].rank]; + + if (lead_suit == trump) + { + if (maxpd > ctx.lead0_rank && maxpd > max3rd) + win_move = true; + else if (mply[k].rank > ctx.lead0_rank && mply[k].rank > max3rd) - win_move = true; - } - else - { - if (mply[k].rank > ctx.lead0_rank && mply[k].rank > max3rd) - { - if ((max3rd != 0) || - (tpos.length[partner[lead_hand]][trump] == 0)) - win_move = true; - else if ((maxpd == 0) + win_move = true; + } + else + { + if (mply[k].rank > ctx.lead0_rank && mply[k].rank > max3rd) + { + if ((max3rd != 0) || + (tpos.length[partner[lead_hand]][trump] == 0)) + win_move = true; + else if ((maxpd == 0) && (tpos.length[rho[lead_hand]][trump] != 0) && (tpos.rank_in_suit[rho[lead_hand]][trump] > tpos.rank_in_suit[partner[lead_hand]][trump])) - win_move = true; - } - else if (maxpd > ctx.lead0_rank && maxpd > max3rd) - { - if ((max3rd != 0) || - (tpos.length[partner[lead_hand]][trump] == 0)) - win_move = true; - } - else if (ctx.lead0_rank > maxpd && + win_move = true; + } + else if (maxpd > ctx.lead0_rank && maxpd > max3rd) + { + if ((max3rd != 0) || + (tpos.length[partner[lead_hand]][trump] == 0)) + win_move = true; + } + else if (ctx.lead0_rank > maxpd && ctx.lead0_rank > max3rd && ctx.lead0_rank > mply[k].rank) - { - if ((maxpd == 0) && (tpos.length[rho[lead_hand]][trump] != 0)) - { - if ((max3rd != 0) || - (tpos.length[partner[lead_hand]][trump] == 0)) - win_move = true; - else if (tpos.rank_in_suit[rho[lead_hand]][trump] + { + if ((maxpd == 0) && (tpos.length[rho[lead_hand]][trump] != 0)) + { + if ((max3rd != 0) || + (tpos.length[partner[lead_hand]][trump] == 0)) + win_move = true; + else if (tpos.rank_in_suit[rho[lead_hand]][trump] > tpos.rank_in_suit[partner[lead_hand]][trump]) - win_move = true; + win_move = true; + } + } + else if (maxpd == 0 && tpos.length[rho[lead_hand]][trump] != 0) + /* winnerHand is partner to first */ + win_move = true; } - } - else if (maxpd == 0 && tpos.length[rho[lead_hand]][trump] != 0) - /* winnerHand is partner to first */ - win_move = true; - } - if (win_move) - { - if (min3rd > mply[k].rank) - // Partner must be winning -- we can't. - mply[k].weight = 40 + r_rank; - else if ((maxpd > ctx.lead0_rank) && + if (win_move) + { + if (min3rd > mply[k].rank) + // Partner must be winning -- we can't. + mply[k].weight = 40 + r_rank; + else if ((maxpd > ctx.lead0_rank) && (tpos.rank_in_suit[lead_hand][lead_suit] > - tpos.rank_in_suit[rho[lead_hand]][lead_suit])) - mply[k].weight = 41 + r_rank; + tpos.rank_in_suit[rho[lead_hand]][lead_suit])) + mply[k].weight = 41 + r_rank; - /* If rho has a card in the leading suit that + /* If rho has a card in the leading suit that is higher than the trick leading card but lower than the highest rank of the leading hand, then lho playing the lowest card will be the cheapest win */ - // FIX: Don't follow + // FIX: Don't follow - else if (mply[k].rank > ctx.lead0_rank) - { - if (mply[k].rank < maxpd) - mply[k].weight = 78 - (mply[k].rank); - /* If played card is lower than any of the cards of + else if (mply[k].rank > ctx.lead0_rank) + { + if (mply[k].rank < maxpd) + mply[k].weight = 78 - (mply[k].rank); + /* If played card is lower than any of the cards of rho, it will be the cheapest win */ - else if (mply[k].rank > max3rd) - mply[k].weight = 73 - (mply[k].rank); - /* If played card is higher than any cards at partner + else if (mply[k].rank > max3rd) + mply[k].weight = 73 - (mply[k].rank); + /* If played card is higher than any cards at partner of the leading hand, rho can play low, under the condition that he has a lower card than lho played */ - else if (mply[k].sequence) // May establish a winner - mply[k].weight = 62 - (mply[k].rank); + else if (mply[k].sequence) // May establish a winner + mply[k].weight = 62 - (mply[k].rank); + else + mply[k].weight = 49 - (mply[k].rank); + } + else if (maxpd > 0) + mply[k].weight = 47 - (mply[k].rank); + else + mply[k].weight = 40 - (mply[k].rank); + } + else if (mply[k].rank < min3rd || mply[k].rank < minpd) + // Will be beaten anyway. + mply[k].weight = -9 + r_rank; + else if (mply[k].rank < ctx.lead0_rank) + // Already beaten. + mply[k].weight = -16 + r_rank; + else if (mply[k].sequence) + // May establish a winner + mply[k].weight = 22 - (mply[k].rank); else - mply[k].weight = 49 - (mply[k].rank); - } - else if (maxpd > 0) - mply[k].weight = 47 - (mply[k].rank); - else - mply[k].weight = 40 - (mply[k].rank); + mply[k].weight = 10 - (mply[k].rank); } - else if (mply[k].rank < min3rd || mply[k].rank < minpd) - // Will be beaten anyway. - mply[k].weight = -9 + r_rank; - else if (mply[k].rank < ctx.lead0_rank) - // Already beaten. - mply[k].weight = -16 + r_rank; - else if (mply[k].sequence) - // May establish a winner - mply[k].weight = 22 - (mply[k].rank); - else - mply[k].weight = 10 - (mply[k].rank); - } } void weight_alloc_nt_notvoid1(HeuristicContext& ctx) { - // Faithful port of Moves::weight_alloc_nt_notvoid1(const Pos& tpos) - const Pos& tpos = ctx.tpos; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - // trackp not needed; using snapshot lead0_rank. - - const int partner_lh = partner[lead_hand]; - const int rho_lh = rho[lead_hand]; - - // Original logic from Moves::weight_alloc_nt_notvoid1 - const int max3rd = highest_rank[ - tpos.rank_in_suit[partner_lh][lead_suit]]; - const int maxpd = highest_rank[ - tpos.rank_in_suit[rho_lh][lead_suit] ]; - - if (maxpd > ctx.lead0_rank && maxpd > max3rd) - { - // Partner can beat both opponents. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - } - else - { - int min3rd = lowest_rank [ + // Faithful port of Moves::weight_alloc_nt_notvoid1(const Pos& tpos) + const Pos& tpos = ctx.tpos; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + // trackp not needed; using snapshot lead0_rank. + + const int partner_lh = partner[lead_hand]; + const int rho_lh = rho[lead_hand]; + + // Original logic from Moves::weight_alloc_nt_notvoid1 + const int max3rd = highest_rank[ + tpos.rank_in_suit[partner_lh][lead_suit]]; + const int maxpd = highest_rank[ + tpos.rank_in_suit[rho_lh][lead_suit] ]; + + if (maxpd > ctx.lead0_rank && maxpd > max3rd) + { + // Partner can beat both opponents. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + } + else + { + int min3rd = lowest_rank [ tpos.rank_in_suit[partner_lh][lead_suit]]; - int minpd = lowest_rank [ + int minpd = lowest_rank [ tpos.rank_in_suit[rho_lh][lead_suit] ]; - for (int k = 0; k < num_moves; k++) - { - int r_rank = rel_rank[ tpos.aggr[lead_suit] ][mply[k].rank]; + for (int k = 0; k < num_moves; k++) + { + int r_rank = rel_rank[ tpos.aggr[lead_suit] ][mply[k].rank]; - if (mply[k].rank > ctx.lead0_rank && mply[k].rank > max3rd) - // We can beat both opponents. - mply[k].weight = 81 - mply[k].rank; + if (mply[k].rank > ctx.lead0_rank && mply[k].rank > max3rd) + // We can beat both opponents. + mply[k].weight = 81 - mply[k].rank; - else if ((min3rd > mply[k].rank) || (minpd > mply[k].rank)) - // Card can make no difference, so play very low. - mply[k].weight = -3 + r_rank; + else if ((min3rd > mply[k].rank) || (minpd > mply[k].rank)) + // Card can make no difference, so play very low. + mply[k].weight = -3 + r_rank; - else if (mply[k].rank < ctx.lead0_rank) - // Can't beat the card led. - mply[k].weight = -11 + r_rank; + else if (mply[k].rank < ctx.lead0_rank) + // Can't beat the card led. + mply[k].weight = -11 + r_rank; - else if (mply[k].sequence) - // Some willingness to split. - mply[k].weight = 10 + r_rank; + else if (mply[k].sequence) + // Some willingness to split. + mply[k].weight = 10 + r_rank; - else - mply[k].weight = 13 - mply[k].rank; + else + mply[k].weight = 13 - mply[k].rank; + } } - } } void weight_alloc_trump_void1(HeuristicContext& ctx) { - const Pos& tpos = ctx.tpos; - const int trump = ctx.trump; - const int suit = ctx.suit; - const int curr_hand = ctx.curr_hand; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - // trackp not needed here; use context snapshots for trick state. - - const int partner_lh = partner[lead_hand]; - const int rho_lh = rho[lead_hand]; - - unsigned short suitCount = tpos.length[curr_hand][suit]; - int suitAdd; - - if (lead_suit == trump) // We pitch - { - if (tpos.rank_in_suit[rho_lh][lead_suit] > - (tpos.rank_in_suit[partner_lh][lead_suit] | - bit_map_rank[ctx.lead0_rank])) - // RHO can win. - suitAdd = (suitCount << 6) / 44; - else - { - // Don't pitch from Kx. - suitAdd = (suitCount << 6) / 36; - if ((suitCount == 2) && - (tpos.second_best[suit].hand == curr_hand)) - suitAdd += -4; - } + const Pos& tpos = ctx.tpos; + const int trump = ctx.trump; + const int suit = ctx.suit; + const int curr_hand = ctx.curr_hand; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + // trackp not needed here; use context snapshots for trick state. + + const int partner_lh = partner[lead_hand]; + const int rho_lh = rho[lead_hand]; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + suitAdd; - } - else if (suit != trump) - { - // We discard on a side suit. + unsigned short suitCount = tpos.length[curr_hand][suit]; + int suitAdd; - if (tpos.length[partner_lh][lead_suit] != 0) + if (lead_suit == trump) // We pitch { - // 3rd hand will follow. - if (tpos.rank_in_suit[rho_lh][lead_suit] > - (tpos.rank_in_suit[partner_lh][lead_suit] | + if (tpos.rank_in_suit[rho_lh][lead_suit] > + (tpos.rank_in_suit[partner_lh][lead_suit] | + bit_map_rank[ctx.lead0_rank])) + // RHO can win. + suitAdd = (suitCount << 6) / 44; + else + { + // Don't pitch from Kx. + suitAdd = (suitCount << 6) / 36; + if ((suitCount == 2) && + (tpos.second_best[suit].hand == curr_hand)) + suitAdd += -4; + } + + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + suitAdd; + } + else if (suit != trump) + { + // We discard on a side suit. + + if (tpos.length[partner_lh][lead_suit] != 0) + { + // 3rd hand will follow. + if (tpos.rank_in_suit[rho_lh][lead_suit] > + (tpos.rank_in_suit[partner_lh][lead_suit] | bit_map_rank[ctx.lead0_rank])) - // RHO can win. - suitAdd = 60 + (suitCount << 6) / 44; - else if ((tpos.length[rho_lh][lead_suit] == 0) + // RHO can win. + suitAdd = 60 + (suitCount << 6) / 44; + else if ((tpos.length[rho_lh][lead_suit] == 0) && (tpos.length[rho_lh][trump] != 0)) - // Partner can ruff. - suitAdd = 60 + (suitCount << 6) / 44; - else - { - // FIX: No reason to differentiate here? - suitAdd = -2 + (suitCount << 6) / 36; - // Don't pitch from Kx. - if ((suitCount == 2) && - (tpos.second_best[suit].hand == curr_hand)) - suitAdd += -4; - } - } - else if ((tpos.length[rho_lh][lead_suit] == 0) + // Partner can ruff. + suitAdd = 60 + (suitCount << 6) / 44; + else + { + // FIX: No reason to differentiate here? + suitAdd = -2 + (suitCount << 6) / 36; + // Don't pitch from Kx. + if ((suitCount == 2) && + (tpos.second_best[suit].hand == curr_hand)) + suitAdd += -4; + } + } + else if ((tpos.length[rho_lh][lead_suit] == 0) && (tpos.rank_in_suit[rho_lh][trump] > tpos.rank_in_suit[partner_lh][trump])) - // Partner can overruff 3rd hand. - suitAdd = 60 + (suitCount << 6) / 44; - else if ((tpos.length[partner_lh][trump] == 0) + // Partner can overruff 3rd hand. + suitAdd = 60 + (suitCount << 6) / 44; + else if ((tpos.length[partner_lh][trump] == 0) && (tpos.rank_in_suit[rho_lh][lead_suit] > bit_map_rank[ctx.lead0_rank])) - // 3rd hand has no trumps, and partner has suit winner. - suitAdd = 60 + (suitCount << 6) / 44; - else + // 3rd hand has no trumps, and partner has suit winner. + suitAdd = 60 + (suitCount << 6) / 44; + else + { + // FIX: No reason to differentiate here? + suitAdd = -2 + (suitCount << 6) / 36; + // Don't pitch from Kx. + if ((suitCount == 2) && + (tpos.second_best[suit].hand == curr_hand)) + suitAdd += -4; + } + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + suitAdd; + } + else if (tpos.length[partner_lh][lead_suit] != 0) { - // FIX: No reason to differentiate here? - suitAdd = -2 + (suitCount << 6) / 36; - // Don't pitch from Kx. - if ((suitCount == 2) && - (tpos.second_best[suit].hand == curr_hand)) - suitAdd += -4; + // 3rd hand follows suit while we ruff. + // Could be ruffing partner's winner! + suitAdd = (suitCount << 6) / 44; + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 24 - (mply[k].rank) + suitAdd; } - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + suitAdd; - } - else if (tpos.length[partner_lh][lead_suit] != 0) - { - // 3rd hand follows suit while we ruff. - // Could be ruffing partner's winner! - suitAdd = (suitCount << 6) / 44; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 24 - (mply[k].rank) + suitAdd; - } - else if ((tpos.length[rho_lh][lead_suit] == 0) + else if ((tpos.length[rho_lh][lead_suit] == 0) && (tpos.length[rho_lh][trump] != 0) && (tpos.rank_in_suit[rho_lh][trump] > - tpos.rank_in_suit[partner_lh][trump])) - { - // Everybody is void, and partner can overruff. - suitAdd = (suitCount << 6) / 44; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 24 - (mply[k].rank) + suitAdd; - } - else - { - for (int k = last_num_moves; k < num_moves; k++) + tpos.rank_in_suit[partner_lh][trump])) { - if (bit_map_rank[mply[k].rank] > - tpos.rank_in_suit[partner_lh][trump]) - { - // We can ruff, 3rd hand is void but can't overruff. + // Everybody is void, and partner can overruff. suitAdd = (suitCount << 6) / 44; - mply[k].weight = 24 - (mply[k].rank) + suitAdd; - } - else - { - // We're getting overruffed. Make trick costly for opponents. - suitAdd = (suitCount << 6) / 36; - // Don't ruff from Kx. - if ((suitCount == 2) && - (tpos.second_best[suit].hand == curr_hand)) - suitAdd += -4; - mply[k].weight = 15 - (mply[k].rank) + suitAdd; - } + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 24 - (mply[k].rank) + suitAdd; + } + else + { + for (int k = last_num_moves; k < num_moves; k++) + { + if (bit_map_rank[mply[k].rank] > + tpos.rank_in_suit[partner_lh][trump]) + { + // We can ruff, 3rd hand is void but can't overruff. + suitAdd = (suitCount << 6) / 44; + mply[k].weight = 24 - (mply[k].rank) + suitAdd; + } + else + { + // We're getting overruffed. Make trick costly for opponents. + suitAdd = (suitCount << 6) / 36; + // Don't ruff from Kx. + if ((suitCount == 2) && + (tpos.second_best[suit].hand == curr_hand)) + suitAdd += -4; + mply[k].weight = 15 - (mply[k].rank) + suitAdd; + } + } } - } } void weight_alloc_nt_void1(HeuristicContext& ctx) { - const Pos& tpos = ctx.tpos; - const int suit = ctx.suit; - const int curr_hand = ctx.curr_hand; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - const int partner_lh = partner[lead_hand]; - const int rho_lh = rho[lead_hand]; - - // FIX: - // Why the different penalties depending on partner? - - if (tpos.rank_in_suit[rho_lh][lead_suit] > - (tpos.rank_in_suit[partner_lh][lead_suit] | - bit_map_rank[ctx.lead0_rank])) - { - // Partner can win. - unsigned short suitCount = tpos.length[curr_hand][suit]; - int suitAdd = (suitCount << 6) / 23; - // Discourage pitch from Kx or A stiff. - if (suitCount == 2 && tpos.second_best[suit].hand == curr_hand) - suitAdd += -2; - else if (suitCount == 1 && tpos.winner[suit].hand == curr_hand) - suitAdd += -3; + const Pos& tpos = ctx.tpos; + const int suit = ctx.suit; + const int curr_hand = ctx.curr_hand; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + suitAdd; - } - else - { - unsigned short suitCount = tpos.length[curr_hand][suit]; - int suitAdd = (suitCount << 6) / 33; + const int partner_lh = partner[lead_hand]; + const int rho_lh = rho[lead_hand]; - // Discourage pitch from Kx. - if ((suitCount == 2) && - (tpos.second_best[suit].hand == curr_hand)) - suitAdd += -6; + // FIX: + // Why the different penalties depending on partner? - /* Discourage suit discard of highest card. */ - else if ((suitCount == 1) && + if (tpos.rank_in_suit[rho_lh][lead_suit] > + (tpos.rank_in_suit[partner_lh][lead_suit] | + bit_map_rank[ctx.lead0_rank])) + { + // Partner can win. + unsigned short suitCount = tpos.length[curr_hand][suit]; + int suitAdd = (suitCount << 6) / 23; + // Discourage pitch from Kx or A stiff. + if (suitCount == 2 && tpos.second_best[suit].hand == curr_hand) + suitAdd += -2; + else if (suitCount == 1 && tpos.winner[suit].hand == curr_hand) + suitAdd += -3; + + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + suitAdd; + } + else + { + unsigned short suitCount = tpos.length[curr_hand][suit]; + int suitAdd = (suitCount << 6) / 33; + + // Discourage pitch from Kx. + if ((suitCount == 2) && + (tpos.second_best[suit].hand == curr_hand)) + suitAdd += -6; + + /* Discourage suit discard of highest card. */ + else if ((suitCount == 1) && (tpos.winner[suit].hand == curr_hand)) - suitAdd += -8; + suitAdd += -8; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + suitAdd; - } + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + suitAdd; + } } // Helper functions for level 2+ weight allocation int rank_forces_ace(const HeuristicContext& ctx, const int cards4th) { - // Figure out how high we have to play to force out the top. - const MoveGroupType& mp = group_data[cards4th]; + // Figure out how high we have to play to force out the top. + const MoveGroupType& mp = group_data[cards4th]; - int g = mp.last_group_; - int removed = static_cast(ctx.removed_ranks[ctx.lead_suit]); + int g = mp.last_group_; + int removed = static_cast(ctx.removed_ranks[ctx.lead_suit]); - while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) - g--; + while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) + g--; - if (g <= 0) - return -1; + if (g <= 0) + return -1; + + // RHO's second-highest rank. + int secondRHO = (g == 0 ? 0 : mp.rank_[g-1]); - // RHO's second-highest rank. - int secondRHO = (g == 0 ? 0 : mp.rank_[g-1]); - - if (secondRHO > ctx.move1_rank) - { - // Try to force out the top as cheaply as possible. - int k = 0; - while (k < ctx.num_moves && ctx.mply[k].rank > secondRHO) - k++; - - if (k) - return k - 1; - } - else if (ctx.high1 == 1) - { - // Try to beat 2nd hand as cheaply as possible. - int k = 0; - while (k < ctx.num_moves && ctx.mply[k].rank > ctx.move1_rank) - k++; - - if (k) - return k - 1; - } - - return -1; + if (secondRHO > ctx.move1_rank) + { + // Try to force out the top as cheaply as possible. + int k = 0; + while (k < ctx.num_moves && ctx.mply[k].rank > secondRHO) + k++; + + if (k) + return k - 1; + } + else if (ctx.high1 == 1) + { + // Try to beat 2nd hand as cheaply as possible. + int k = 0; + while (k < ctx.num_moves && ctx.mply[k].rank > ctx.move1_rank) + k++; + + if (k) + return k - 1; + } + + return -1; } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters): Intentional ordering; mirrors legacy helper usage void get_top_number(const HeuristicContext& ctx, const int ris, const int prank, int& top_number, int& mno) { - top_number = -10; + top_number = -10; - // Find the lowest move that still overtakes partner's card. - mno = 0; - while (mno < ctx.num_moves - 1 && ctx.mply[1 + mno].rank > prank) - mno++; + // Find the lowest move that still overtakes partner's card. + mno = 0; + while (mno < ctx.num_moves - 1 && ctx.mply[1 + mno].rank > prank) + mno++; - const MoveGroupType& mp = group_data[ris]; - int g = mp.last_group_; + const MoveGroupType& mp = group_data[ris]; + int g = mp.last_group_; - // Remove partner's card as well. - int removed = static_cast(ctx.removed_ranks[ctx.lead_suit] | + // Remove partner's card as well. + int removed = static_cast(ctx.removed_ranks[ctx.lead_suit] | bit_map_rank[prank]); - // Empty suit: last_group_ == -1; no sequence to measure. - if (g < 0) - { - top_number = -1; - return; - } + // Empty suit: last_group_ == -1; no sequence to measure. + if (g < 0) + { + top_number = -1; + return; + } - int fullseq = mp.fullseq_[g]; + int fullseq = mp.fullseq_[g]; - while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) - fullseq |= mp.fullseq_[--g]; + while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) + fullseq |= mp.fullseq_[--g]; - top_number = count_table[fullseq] - 1; + top_number = count_table[fullseq] - 1; } void weight_alloc_trump_notvoid2(HeuristicContext& ctx) { - const Pos& tpos = ctx.tpos; - const int trump = ctx.trump; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - const int rho_lh = rho[lead_hand]; - const int cards4th = tpos.rank_in_suit[rho_lh][lead_suit]; - const int max4th = highest_rank[cards4th]; - const int min4th = lowest_rank[cards4th]; - const int max3rd = mply[0].rank; - - if (lead_suit == trump) - { - if (ctx.high1 == 0 && ctx.lead0_rank > max4th) - { - // Partner has already beat his LHO and will beat his RHO. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } - else if (max3rd < min4th || max3rd < ctx.move1_rank) - { - // Our cards are too low to matter. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } - else if (max3rd > max4th) + const Pos& tpos = ctx.tpos; + const int trump = ctx.trump; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + const int rho_lh = rho[lead_hand]; + const int cards4th = tpos.rank_in_suit[rho_lh][lead_suit]; + const int max4th = highest_rank[cards4th]; + const int min4th = lowest_rank[cards4th]; + const int max3rd = mply[0].rank; + + if (lead_suit == trump) { - // We can win the trick. - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > max4th && - mply[k].rank > ctx.move1_rank) - mply[k].weight = 58 - mply[k].rank; + if (ctx.high1 == 0 && ctx.lead0_rank > max4th) + { + // Partner has already beat his LHO and will beat his RHO. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } + else if (max3rd < min4th || max3rd < ctx.move1_rank) + { + // Our cards are too low to matter. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } + else if (max3rd > max4th) + { + // We can win the trick. + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > max4th && + mply[k].rank > ctx.move1_rank) + mply[k].weight = 58 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } + } else - mply[k].weight = -mply[k].rank; - } - } - else - { - // Figure out how high we have to play to force out the top. - int kBonus = rank_forces_ace(ctx, cards4th); + { + // Figure out how high we have to play to force out the top. + int kBonus = rank_forces_ace(ctx, cards4th); - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; - if (kBonus != -1) // Force out ace - mply[kBonus].weight += 20; - return; + if (kBonus != -1) // Force out ace + mply[kBonus].weight += 20; + return; + } } - } - else if (ctx.move1_suit == trump) - { - // 2nd hand ruffs, and we must follow suit. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } - - // So now lead_suit != trump and second hand didn't ruff. - else if (ctx.high1 == 0) - { - // Partner is winning so far. - if (max4th == 0) + else if (ctx.move1_suit == trump) { - // 4th hand is either ruffing or not -- play low. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; + // 2nd hand ruffs, and we must follow suit. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; } - // So 4th hand follows. - else if (ctx.lead0_rank > max4th) + // So now lead_suit != trump and second hand didn't ruff. + else if (ctx.high1 == 0) { - // Partner is already winning. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } + // Partner is winning so far. + if (max4th == 0) + { + // 4th hand is either ruffing or not -- play low. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } - else if (max3rd < min4th || max3rd < ctx.move1_rank) - { - // Our cards are too low to matter. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } + // So 4th hand follows. + else if (ctx.lead0_rank > max4th) + { + // Partner is already winning. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } - // So 4th hand can beat partner in the suit. - else if (max3rd > max4th) - { - // We can win the trick. - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > max4th) - mply[k].weight = 58 - mply[k].rank; + else if (max3rd < min4th || max3rd < ctx.move1_rank) + { + // Our cards are too low to matter. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } + + // So 4th hand can beat partner in the suit. + else if (max3rd > max4th) + { + // We can win the trick. + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > max4th) + mply[k].weight = 58 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } + } else - mply[k].weight = -mply[k].rank; - } + { + // We can't win the trick. + // Figure out how high we have to play to force out the top. + int kBonus = rank_forces_ace(ctx, cards4th); + + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move1_rank && + mply[k].rank > max4th) // We will win + mply[k].weight = 60 - mply[k].rank; + + else + mply[k].weight = -mply[k].rank; + } + + if (kBonus != -1) // Force out ace + mply[kBonus].weight += 20; + } } else { - // We can't win the trick. - // Figure out how high we have to play to force out the top. - int kBonus = rank_forces_ace(ctx, cards4th); - - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > ctx.move1_rank && - mply[k].rank > max4th) // We will win - mply[k].weight = 60 - mply[k].rank; - - else - mply[k].weight = -mply[k].rank; - } + // 2nd hand is winning so far. 4th hand is either ruffing + // or not -- play high enough to beat 2nd hand. + if (max4th == 0) + { + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move1_rank) + mply[k].weight = 20 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } + return; + } - if (kBonus != -1) // Force out ace - mply[kBonus].weight += 20; - } - } - else - { - // 2nd hand is winning so far. 4th hand is either ruffing - // or not -- play high enough to beat 2nd hand. - if (max4th == 0) - { - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > ctx.move1_rank) - mply[k].weight = 20 - mply[k].rank; - else - mply[k].weight = -mply[k].rank; - } - return; - } + // Our cards are too low to matter. + else if (max3rd < min4th || max3rd < ctx.move1_rank) + { + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; + } - // Our cards are too low to matter. - else if (max3rd < min4th || max3rd < ctx.move1_rank) - { - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } + // We can win the trick. + else if (max3rd > max4th) + { + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move1_rank && + mply[k].rank > max4th) + mply[k].weight = 58 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } + return; + } - // We can win the trick. - else if (max3rd > max4th) - { - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > ctx.move1_rank && - mply[k].rank > max4th) - mply[k].weight = 58 - mply[k].rank; - else - mply[k].weight = -mply[k].rank; - } - return; - } + // Figure out how high we have to play to force out the top. + int kBonus = rank_forces_ace(ctx, cards4th); - // Figure out how high we have to play to force out the top. - int kBonus = rank_forces_ace(ctx, cards4th); + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move1_rank && + mply[k].rank > max4th) // We will win + mply[k].weight = 60 - mply[k].rank; - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > ctx.move1_rank && - mply[k].rank > max4th) // We will win - mply[k].weight = 60 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } - else - mply[k].weight = -mply[k].rank; + if (kBonus != -1) // Force out ace + mply[kBonus].weight += 20; } - - if (kBonus != -1) // Force out ace - mply[kBonus].weight += 20; - } } void weight_alloc_nt_notvoid2(HeuristicContext& ctx) { - // One of the main remaining issues here is cashing out long - // suits. Examples: - // AKJ opposite Q, overtake. - // KQx opposite Jxxxx, don't block on the ace. - // KJTx opposite 9 with Qx in dummy, do win the T. - - const Pos& tpos = ctx.tpos; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int curr_hand = ctx.curr_hand; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - const int rho_lh = rho[lead_hand]; - const int lho_lh = lho[lead_hand]; - const int partner_lh = partner[lead_hand]; - - const int cards4th = tpos.rank_in_suit[rho_lh][lead_suit]; - const int max4th = highest_rank[cards4th]; - const int min4th = lowest_rank[cards4th]; - const int max3rd = mply[0].rank; - - if (ctx.high1 == 0 && ctx.lead0_rank > max4th) - { - // Partner has already beat his LHO and will beat his RHO. - // Generally we play low and let partner win. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; + // One of the main remaining issues here is cashing out long + // suits. Examples: + // AKJ opposite Q, overtake. + // KQx opposite Jxxxx, don't block on the ace. + // KJTx opposite 9 with Qx in dummy, do win the T. + + const Pos& tpos = ctx.tpos; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int curr_hand = ctx.curr_hand; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + const int rho_lh = rho[lead_hand]; + const int lho_lh = lho[lead_hand]; + const int partner_lh = partner[lead_hand]; + + const int cards4th = tpos.rank_in_suit[rho_lh][lead_suit]; + const int max4th = highest_rank[cards4th]; + const int min4th = lowest_rank[cards4th]; + const int max3rd = mply[0].rank; + + if (ctx.high1 == 0 && ctx.lead0_rank > max4th) + { + // Partner has already beat his LHO and will beat his RHO. + // Generally we play low and let partner win. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; - // This doesn't help much, not sure why. It does work. + // This doesn't help much, not sure why. It does work. - // if (0 && tpos.length[lead_hand][lead_suit] == 0 && - if (tpos.length[lead_hand][lead_suit] == 0 && - tpos.winner[lead_suit].hand == curr_hand) + // if (0 && tpos.length[lead_hand][lead_suit] == 0 && + if (tpos.length[lead_hand][lead_suit] == 0 && + tpos.winner[lead_suit].hand == curr_hand) + { + // Partner has a singleton, and we have the ace. + // Maybe we should overtake to run the suit. + int oppLen = tpos.length[rho_lh][lead_suit] - 1; + int lhoLen = tpos.length[lho_lh][lead_suit]; + if (lhoLen > oppLen) + oppLen = lhoLen; + + int top_number, mno; + get_top_number(ctx, tpos.rank_in_suit[partner_lh][lead_suit], + ctx.lead0_rank, top_number, mno); + + if (oppLen <= top_number) + mply[mno].weight += 20; + } + return; + } + else if (max3rd < min4th || max3rd < ctx.move1_rank) { - // Partner has a singleton, and we have the ace. - // Maybe we should overtake to run the suit. - int oppLen = tpos.length[rho_lh][lead_suit] - 1; - int lhoLen = tpos.length[lho_lh][lead_suit]; - if (lhoLen > oppLen) - oppLen = lhoLen; - - int top_number, mno; - get_top_number(ctx, tpos.rank_in_suit[partner_lh][lead_suit], - ctx.lead0_rank, top_number, mno); - - if (oppLen <= top_number) - mply[mno].weight += 20; + // Our cards are too low to matter. + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + return; } - return; - } - else if (max3rd < min4th || max3rd < ctx.move1_rank) - { - // Our cards are too low to matter. - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - return; - } - int kBonus = -1; - if (max4th > max3rd && max4th > ctx.move1_rank) - kBonus = rank_forces_ace(ctx, cards4th); + int kBonus = -1; + if (max4th > max3rd && max4th > ctx.move1_rank) + kBonus = rank_forces_ace(ctx, cards4th); - for (int k = 0; k < num_moves; k++) - { - if (mply[k].rank > ctx.move1_rank && - mply[k].rank > max4th) // We will win - mply[k].weight = 60 - mply[k].rank; + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move1_rank && + mply[k].rank > max4th) // We will win + mply[k].weight = 60 - mply[k].rank; - else - mply[k].weight = -mply[k].rank; - } + else + mply[k].weight = -mply[k].rank; + } - if (kBonus != -1) // Force out ace - mply[kBonus].weight += 20; + if (kBonus != -1) // Force out ace + mply[kBonus].weight += 20; } void weight_alloc_trump_void2(HeuristicContext& ctx) { - // Compared to "v2.8": - // Moved a test for partner's win out of the k loop. - - const Pos& tpos = ctx.tpos; - const int trump = ctx.trump; - const int suit = ctx.suit; - const int lead_hand = ctx.lead_hand; - const int lead_suit = ctx.lead_suit; - const int curr_hand = ctx.curr_hand; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - const int rho_lh = rho[lead_hand]; - - int suitAdd; - const unsigned short suitCount = tpos.length[curr_hand][suit]; - const int max4th = highest_rank[tpos.rank_in_suit[rho_lh][lead_suit]]; - - if (lead_suit == trump || suit != trump) - { - // Discard small from a long suit. - suitAdd = (suitCount << 6) / 40; - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + suitAdd; - return; - } + // Compared to "v2.8": + // Moved a test for partner's win out of the k loop. + + const Pos& tpos = ctx.tpos; + const int trump = ctx.trump; + const int suit = ctx.suit; + const int lead_hand = ctx.lead_hand; + const int lead_suit = ctx.lead_suit; + const int curr_hand = ctx.curr_hand; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + const int rho_lh = rho[lead_hand]; + + int suitAdd; + const unsigned short suitCount = tpos.length[curr_hand][suit]; + const int max4th = highest_rank[tpos.rank_in_suit[rho_lh][lead_suit]]; + + if (lead_suit == trump || suit != trump) + { + // Discard small from a long suit. + suitAdd = (suitCount << 6) / 40; + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + suitAdd; + return; + } - else if (ctx.high1 == 0 && ctx.lead0_rank > max4th && + else if (ctx.high1 == 0 && ctx.lead0_rank > max4th && (max4th != 0 || tpos.length[rho_lh][trump] == 0)) - { - // Partner already beat 2nd and 4th hands. - // Don't overruff partner's sure winner. - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank - 50; - return; - } - - // So now we're ruffing and partner is not already sure to win. - - for (int k = last_num_moves; k < num_moves; k++) - { - if (ctx.move1_suit == trump && - mply[k].rank < ctx.move1_rank) { - // Don't underruff. - int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; - suitAdd = (suitCount << 6) / 40; - mply[k].weight = -32 + r_rank + suitAdd; + // Partner already beat 2nd and 4th hands. + // Don't overruff partner's sure winner. + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank - 50; + return; } - else if (ctx.high1 == 0) + // So now we're ruffing and partner is not already sure to win. + + for (int k = last_num_moves; k < num_moves; k++) { - // We ruff partner's winner over 2nd hand. - if (max4th != 0) - { - if (tpos.second_best[lead_suit].hand == lead_hand) + if (ctx.move1_suit == trump && + mply[k].rank < ctx.move1_rank) { - // We'd like to know whether partner has KQ or just K, - // but that information takes a bit of diggging. It's - // easier just not to ruff the king. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 36 - mply[k].rank + suitAdd; + // Don't underruff. + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; + suitAdd = (suitCount << 6) / 40; + mply[k].weight = -32 + r_rank + suitAdd; } - else + + else if (ctx.high1 == 0) { - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 48 - mply[k].rank + suitAdd; - } - } - else if (bit_map_rank[mply[k].rank] > + // We ruff partner's winner over 2nd hand. + if (max4th != 0) + { + if (tpos.second_best[lead_suit].hand == lead_hand) + { + // We'd like to know whether partner has KQ or just K, + // but that information takes a bit of diggging. It's + // easier just not to ruff the king. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 36 - mply[k].rank + suitAdd; + } + else + { + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 48 - mply[k].rank + suitAdd; + } + } + else if (bit_map_rank[mply[k].rank] > tpos.rank_in_suit[rho_lh][trump]) - { - // We ruff higher than 4th hand. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 48 - mply[k].rank + suitAdd; - } - else - { - // Force out a higher trump in 4th hand. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = -12 - mply[k].rank + suitAdd; - } - } + { + // We ruff higher than 4th hand. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 48 - mply[k].rank + suitAdd; + } + else + { + // Force out a higher trump in 4th hand. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = -12 - mply[k].rank + suitAdd; + } + } - // 2nd hand was winning before we ruffed. - else if (max4th != 0) - { - // Just ruff low. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 72 - mply[k].rank + suitAdd; - } + // 2nd hand was winning before we ruffed. + else if (max4th != 0) + { + // Just ruff low. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 72 - mply[k].rank + suitAdd; + } - else if (bit_map_rank[mply[k].rank] > + else if (bit_map_rank[mply[k].rank] > tpos.rank_in_suit[rho_lh][trump]) - { - // Ruff higher than 4th hand can. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 48 - mply[k].rank + suitAdd; - } + { + // Ruff higher than 4th hand can. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 48 - mply[k].rank + suitAdd; + } - else - { - // Force out a higher trump in 4th hand. - suitAdd = (suitCount << 6) / 50; - mply[k].weight = 36 - mply[k].rank + suitAdd; + else + { + // Force out a higher trump in 4th hand. + suitAdd = (suitCount << 6) / 50; + mply[k].weight = 36 - mply[k].rank + suitAdd; + } } - } } void weight_alloc_nt_void2(HeuristicContext& ctx) { - // Compared to "v2.8": - // Took only the second branch. The first branch (partner - // has beat his LHO and will beat his RHO) was a bit different, - // for no reason that I could see. This is the same or a tiny - // bit better. - - const Pos& tpos = ctx.tpos; - const int suit = ctx.suit; - const int curr_hand = ctx.curr_hand; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - const unsigned short suitCount = tpos.length[curr_hand][suit]; - int suitAdd = (suitCount << 6) / 24; - - // Try not to pitch from Kx or stiff ace. - if (suitCount == 2 && tpos.second_best[suit].hand == curr_hand) - suitAdd -= 4; - if (suitCount == 1 && tpos.winner[suit].hand == curr_hand) - suitAdd -= 4; - - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -(mply[k].rank) + suitAdd; + // Compared to "v2.8": + // Took only the second branch. The first branch (partner + // has beat his LHO and will beat his RHO) was a bit different, + // for no reason that I could see. This is the same or a tiny + // bit better. + + const Pos& tpos = ctx.tpos; + const int suit = ctx.suit; + const int curr_hand = ctx.curr_hand; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + const unsigned short suitCount = tpos.length[curr_hand][suit]; + int suitAdd = (suitCount << 6) / 24; + + // Try not to pitch from Kx or stiff ace. + if (suitCount == 2 && tpos.second_best[suit].hand == curr_hand) + suitAdd -= 4; + if (suitCount == 1 && tpos.winner[suit].hand == curr_hand) + suitAdd -= 4; + + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -(mply[k].rank) + suitAdd; } void weight_alloc_combined_notvoid3(HeuristicContext& ctx) { - // We're always following suit. - // This function is very good, but occasionally it is better - // to beat partner's card in order to cash out a suit in NT. + // We're always following suit. + // This function is very good, but occasionally it is better + // to beat partner's card in order to cash out a suit in NT. - const int trump = ctx.trump; - const int lead_suit = ctx.lead_suit; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; + const int trump = ctx.trump; + const int lead_suit = ctx.lead_suit; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; - if (ctx.high2 == 1 || - (lead_suit != trump && ctx.move2_suit == trump)) - { - // Partner is winning the trick so far, or an opponent - // has ruffed while we must follow. Play low. - - for (int k = 0; k < num_moves; k++) - mply[k].weight = -mply[k].rank; - } - else - { - // We're losing so far, and either trumps were led or - // trumps don't matter in this trick. + if (ctx.high2 == 1 || + (lead_suit != trump && ctx.move2_suit == trump)) + { + // Partner is winning the trick so far, or an opponent + // has ruffed while we must follow. Play low. - for (int k = 0; k < num_moves; k++) + for (int k = 0; k < num_moves; k++) + mply[k].weight = -mply[k].rank; + } + else { - if (mply[k].rank > ctx.move2_rank) - // Win as cheaply as possible. - mply[k].weight = 30 - mply[k].rank; - else - mply[k].weight = -mply[k].rank; + // We're losing so far, and either trumps were led or + // trumps don't matter in this trick. + + for (int k = 0; k < num_moves; k++) + { + if (mply[k].rank > ctx.move2_rank) + // Win as cheaply as possible. + mply[k].weight = 30 - mply[k].rank; + else + mply[k].weight = -mply[k].rank; + } } - } } void weight_alloc_trump_void3(HeuristicContext& ctx) { - // Compared to "v2.8": - // val removed for trump plays (doesn't really matter, though). - - // To consider: - // r_rank vs rank - - const Pos& tpos = ctx.tpos; - const int trump = ctx.trump; - const int suit = ctx.suit; - const int lead_suit = ctx.lead_suit; - const int curr_hand = ctx.curr_hand; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - // Don't pitch from Kx or stiff ace. - const int mylen = tpos.length[curr_hand][suit]; - int val = (mylen << 6) / 24; - if ((mylen == 2) && (tpos.second_best[suit].hand == curr_hand)) - val -= 2; - - if (lead_suit == trump) - { - // We're not following suit, so no hope. - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = -mply[k].rank + val; - } - else if (ctx.high2 == 1) // Partner is winning so far - { - if (suit == trump) // Don't ruff - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 2 - mply[k].rank + val; - - else // Discard from a long suit - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 25 - mply[k].rank + val; - } - else if (ctx.move2_suit == trump) // They've ruffed - { - if (suit == trump) + // Compared to "v2.8": + // val removed for trump plays (doesn't really matter, though). + + // To consider: + // r_rank vs rank + + const Pos& tpos = ctx.tpos; + const int trump = ctx.trump; + const int suit = ctx.suit; + const int lead_suit = ctx.lead_suit; + const int curr_hand = ctx.curr_hand; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + // Don't pitch from Kx or stiff ace. + const int mylen = tpos.length[curr_hand][suit]; + int val = (mylen << 6) / 24; + if ((mylen == 2) && (tpos.second_best[suit].hand == curr_hand)) + val -= 2; + + if (lead_suit == trump) { - for (int k = last_num_moves; k < num_moves; k++) - { - int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; - if (mply[k].rank > ctx.move2_rank) - mply[k].weight = 33 + r_rank; // Overruff - else - mply[k].weight = -13 + r_rank; // Underruff - } + // We're not following suit, so no hope. + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = -mply[k].rank + val; } - else // We discard - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 14 - (mply[k].rank) + val; - } - else if (suit == trump) // We ruff and win - { - for (int k = last_num_moves; k < num_moves; k++) + else if (ctx.high2 == 1) // Partner is winning so far { - int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; - mply[k].weight = 33 + r_rank; + if (suit == trump) // Don't ruff + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 2 - mply[k].rank + val; + + else // Discard from a long suit + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 25 - mply[k].rank + val; + } + else if (ctx.move2_suit == trump) // They've ruffed + { + if (suit == trump) + { + for (int k = last_num_moves; k < num_moves; k++) + { + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; + if (mply[k].rank > ctx.move2_rank) + mply[k].weight = 33 + r_rank; // Overruff + else + mply[k].weight = -13 + r_rank; // Underruff + } + } + else // We discard + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 14 - (mply[k].rank) + val; + } + else if (suit == trump) // We ruff and win + { + for (int k = last_num_moves; k < num_moves; k++) + { + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; + mply[k].weight = 33 + r_rank; + } + } + else // We discard and lose + { + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = 14 - mply[k].rank + val; } - } - else // We discard and lose - { - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = 14 - mply[k].rank + val; - } } void weight_alloc_nt_void3(HeuristicContext& ctx) { - const Pos& tpos = ctx.tpos; - const int suit = ctx.suit; - const int curr_hand = ctx.curr_hand; - const int last_num_moves = ctx.last_num_moves; - const int num_moves = ctx.num_moves; - MoveType* mply = ctx.mply; - - int mylen = tpos.length[curr_hand][suit]; - int val = (mylen << 6) / 27; - // Try not to pitch from Kx, or to pitch a singleton winner. - if ((mylen == 2) && (tpos.second_best[suit].hand == curr_hand)) - val -= 6; - else if ((mylen == 1) && (tpos.winner[suit].hand == curr_hand)) - val -= 8; - - for (int k = last_num_moves; k < num_moves; k++) - mply[k].weight = - mply[k].rank + val; + const Pos& tpos = ctx.tpos; + const int suit = ctx.suit; + const int curr_hand = ctx.curr_hand; + const int last_num_moves = ctx.last_num_moves; + const int num_moves = ctx.num_moves; + MoveType* mply = ctx.mply; + + int mylen = tpos.length[curr_hand][suit]; + int val = (mylen << 6) / 27; + // Try not to pitch from Kx, or to pitch a singleton winner. + if ((mylen == 2) && (tpos.second_best[suit].hand == curr_hand)) + val -= 6; + else if ((mylen == 1) && (tpos.winner[suit].hand == curr_hand)) + val -= 8; + + for (int k = last_num_moves; k < num_moves; k++) + mply[k].weight = - mply[k].rank + val; } diff --git a/library/src/heuristic_sorting/heuristic_sorting.hpp b/library/src/heuristic_sorting/heuristic_sorting.hpp index 53fce940b..9a5024a7c 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.hpp +++ b/library/src/heuristic_sorting/heuristic_sorting.hpp @@ -8,15 +8,15 @@ /// by heuristic sorting routines to order candidate moves effectively. struct TrackType { - int lead_hand; - int lead_suit; - int play_suits[DDS_HANDS]; - int play_ranks[DDS_HANDS]; - TrickDataType trick_data; - ExtCard move[DDS_HANDS]; - int high[DDS_HANDS]; - int lowest_win[DDS_HANDS][DDS_SUITS]; - int removed_ranks[DDS_SUITS]; + int lead_hand; + int lead_suit; + int play_suits[DDS_HANDS]; + int play_ranks[DDS_HANDS]; + TrickDataType trick_data; + ExtCard move[DDS_HANDS]; + int high[DDS_HANDS]; + int lowest_win[DDS_HANDS][DDS_SUITS]; + int removed_ranks[DDS_SUITS]; }; /// @brief Context information for heuristic move sorting and weighting. @@ -26,40 +26,40 @@ struct TrackType /// snapshots to minimize repeated lookups during hot path execution. struct HeuristicContext { - // Core position and move generation data - const Pos& tpos; - const MoveType& best_move; - const MoveType& best_move_tt; - const RelRanksType* thrp_rel; - MoveType* mply; - int num_moves; - int last_num_moves; - const int trump; - // For MoveGen0, the suit being considered. Mutable so callers can build - // the context once per move generation and update it per suit iteration. - int suit; - const TrackType* trackp; - const int curr_trick; - const int curr_hand; - const int lead_hand; - const int lead_suit; // For MoveGen123 - // Snapshot of per-suit removed ranks for the current trick. This is - // populated by the caller to avoid relying on the underlying Moves::trackp - // mutation and to localize mutable heuristic buffers inside the context. - int removed_ranks[DDS_SUITS] = {0}; - // Tiny trick-view snapshots to reduce dependence on trackp for hot helpers. - // Only the fields required by rank_forces_ace are copied for now. - int move1_rank = 0; // trackp->move[1].rank - int high1 = 0; // trackp->high[1] - int move1_suit = 0; // trackp->move[1].suit (for some helpers) + // Core position and move generation data + const Pos& tpos; + const MoveType& best_move; + const MoveType& best_move_tt; + const RelRanksType* thrp_rel; + MoveType* mply; + int num_moves; + int last_num_moves; + const int trump; + // For MoveGen0, the suit being considered. Mutable so callers can build + // the context once per move generation and update it per suit iteration. + int suit; + const TrackType* trackp; + const int curr_trick; + const int curr_hand; + const int lead_hand; + const int lead_suit; // For MoveGen123 + // Snapshot of per-suit removed ranks for the current trick. This is + // populated by the caller to avoid relying on the underlying Moves::trackp + // mutation and to localize mutable heuristic buffers inside the context. + int removed_ranks[DDS_SUITS] = {0}; + // Tiny trick-view snapshots to reduce dependence on trackp for hot helpers. + // Only the fields required by rank_forces_ace are copied for now. + int move1_rank = 0; // trackp->move[1].rank + int high1 = 0; // trackp->high[1] + int move1_suit = 0; // trackp->move[1].suit (for some helpers) - // Third-hand snapshots for weight_alloc_combined_notvoid3 and weight_alloc_trump_void3 helpers. - int move2_rank = 0; // trackp->move[2].rank - int move2_suit = 0; // trackp->move[2].suit - int high2 = 0; // trackp->high[2] + // Third-hand snapshots for weight_alloc_combined_notvoid3 and weight_alloc_trump_void3 helpers. + int move2_rank = 0; // trackp->move[2].rank + int move2_suit = 0; // trackp->move[2].suit + int high2 = 0; // trackp->high[2] - // Leader's card snapshot for targeted helpers. - int lead0_rank = 0; // trackp->move[0].rank + // Leader's card snapshot for targeted helpers. + int lead0_rank = 0; // trackp->move[0].rank }; /// @brief Which weight_alloc_* helper to run for the current move list. @@ -69,20 +69,20 @@ struct HeuristicContext /// it from the context. Numeric values match the historical findex encoding /// used by Moves::MoveGen0 / MoveGen123 (and DDS_MOVES RegisterList). enum class WeightCase : int { - Nt0 = 0, ///< Leading hand, no trump winner available - Trump0 = 1, ///< Leading hand, trump winner available - NtNotVoid1 = 4, ///< 2nd hand, can follow, no trump winner - TrumpNotVoid1 = 5, ///< 2nd hand, can follow, trump winner - NtVoid1 = 6, ///< 2nd hand, void in lead suit, no trump winner - TrumpVoid1 = 7, ///< 2nd hand, void in lead suit, trump winner - NtNotVoid2 = 8, ///< 3rd hand, can follow, no trump winner - TrumpNotVoid2 = 9, ///< 3rd hand, can follow, trump winner - NtVoid2 = 10, ///< 3rd hand, void in lead suit, no trump winner - TrumpVoid2 = 11, ///< 3rd hand, void in lead suit, trump winner - CombinedNotVoid3 = 12, ///< 4th hand, can follow, no trump winner - CombinedNotVoid3Trump = 13,///< 4th hand, can follow, trump winner - NtVoid3 = 14, ///< 4th hand, void in lead suit, no trump winner - TrumpVoid3 = 15, ///< 4th hand, void in lead suit, trump winner + Nt0 = 0, ///< Leading hand, no trump winner available + Trump0 = 1, ///< Leading hand, trump winner available + NtNotVoid1 = 4, ///< 2nd hand, can follow, no trump winner + TrumpNotVoid1 = 5, ///< 2nd hand, can follow, trump winner + NtVoid1 = 6, ///< 2nd hand, void in lead suit, no trump winner + TrumpVoid1 = 7, ///< 2nd hand, void in lead suit, trump winner + NtNotVoid2 = 8, ///< 3rd hand, can follow, no trump winner + TrumpNotVoid2 = 9, ///< 3rd hand, can follow, trump winner + NtVoid2 = 10, ///< 3rd hand, void in lead suit, no trump winner + TrumpVoid2 = 11, ///< 3rd hand, void in lead suit, trump winner + CombinedNotVoid3 = 12, ///< 4th hand, can follow, no trump winner + CombinedNotVoid3Trump = 13,///< 4th hand, can follow, trump winner + NtVoid3 = 14, ///< 4th hand, void in lead suit, no trump winner + TrumpVoid3 = 15, ///< 4th hand, void in lead suit, trump winner }; /// @brief Apply heuristic sorting using a precomputed weight case. diff --git a/library/src/init.cpp b/library/src/init.cpp index 73881c026..6b0acf929 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -56,7 +56,7 @@ int _initialized = 0; */ void STDCALL InitializeStaticMemory() { - SetResources(0, 0); + SetResources(0, 0); } @@ -68,10 +68,10 @@ void STDCALL InitializeStaticMemory() * Public API documentation is maintained in the API headers. */ void STDCALL SetMaxThreads( - int userThreads) + int userThreads) { - (void) userThreads; - InitializeStaticMemory(); + (void) userThreads; + InitializeStaticMemory(); } @@ -81,93 +81,93 @@ void STDCALL SetMaxThreads( * Public API documentation is maintained in the API headers. */ void STDCALL SetResources( - int maxMemoryMB, - int maxThreadsIn) + int maxMemoryMB, + int maxThreadsIn) { - // Figure out system resources. - int ncores; - unsigned long long kilobytesFree; - sysdep.get_hardware(ncores, kilobytesFree); - - // Memory usage will be limited to the lower of: - // - maxMemoryMB + 30% (if given; statistically this works out) - // - 70% of free memory - // - 1800 MB if we're on a 32-bit system. - - const int memMaxGivenMB = (maxMemoryMB == 0 ? 1000000 : - static_cast(1.3 * maxMemoryMB)); - const int memMaxFreeMB = static_cast(0.70 * kilobytesFree / 1024); - const int memMax32bMB = (sizeof(void *) == 4 ? 1800 : 1000000); - - int memMaxMB = min(memMaxGivenMB, memMaxFreeMB); - memMaxMB = min(memMaxMB, memMax32bMB); - - // Internal parallel execution has been removed. - // Legacy API calls execute sequentially, so use a single internal thread. - if (maxThreadsIn > 1) { - std::fprintf( - stderr, - "DDS warning: SetResources maxThreadsIn=%d requested, but internal batch threading is disabled; using 1 thread.\n", - maxThreadsIn); - } - (void) maxThreadsIn; - (void) ncores; - const int thrMax = 1; - - // For simplicity we won't vary the amount of memory per thread - // in the small and large versions. - - int noOfThreads, noOfLargeThreads, noOfSmallThreads; - if (thrMax * THREADMEM_LARGE_MAX_MB <= memMaxMB) - { - // We have enough memory for the maximum number of large threads. - noOfThreads = thrMax; - noOfLargeThreads = thrMax; - noOfSmallThreads = 0; - } - else if (thrMax * THREADMEM_SMALL_MAX_MB > memMaxMB) - { - // We don't even have enough memory for only small threads. - // We'll limit the number of threads. - noOfThreads = static_cast(memMaxMB / - static_cast(THREADMEM_SMALL_MAX_MB)); - noOfLargeThreads = 0; - noOfSmallThreads = noOfThreads; - } - else - { - // We'll have a mixture with as many large threads as possible. - const double d = static_cast( - THREADMEM_LARGE_MAX_MB - THREADMEM_SMALL_MAX_MB); - - noOfThreads = thrMax; - noOfLargeThreads = static_cast( - (memMaxMB - thrMax * THREADMEM_SMALL_MAX_MB) / d); - noOfSmallThreads = thrMax - noOfLargeThreads; - } - - sysdep.register_params(noOfThreads, memMaxMB); - - scheduler.RegisterThreads(noOfThreads); - - // Clear the thread memory and fill it up again. - memory.Resize(0, DDS_TT_SMALL, 0, 0); - if (noOfLargeThreads > 0) - memory.Resize(static_cast(noOfLargeThreads), - DDS_TT_LARGE, THREADMEM_LARGE_DEF_MB, THREADMEM_LARGE_MAX_MB); - if (noOfSmallThreads > 0) - memory.Resize(static_cast(noOfThreads), - DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - - ThreadMgr::instance().Reset(noOfThreads); - - InitDebugFiles(); - - if (! _initialized) - { - _initialized = 1; - init_lookup_tables(); - } + // Figure out system resources. + int ncores; + unsigned long long kilobytesFree; + sysdep.get_hardware(ncores, kilobytesFree); + + // Memory usage will be limited to the lower of: + // - maxMemoryMB + 30% (if given; statistically this works out) + // - 70% of free memory + // - 1800 MB if we're on a 32-bit system. + + const int memMaxGivenMB = (maxMemoryMB == 0 ? 1000000 : + static_cast(1.3 * maxMemoryMB)); + const int memMaxFreeMB = static_cast(0.70 * kilobytesFree / 1024); + const int memMax32bMB = (sizeof(void *) == 4 ? 1800 : 1000000); + + int memMaxMB = min(memMaxGivenMB, memMaxFreeMB); + memMaxMB = min(memMaxMB, memMax32bMB); + + // Internal parallel execution has been removed. + // Legacy API calls execute sequentially, so use a single internal thread. + if (maxThreadsIn > 1) { + std::fprintf( + stderr, + "DDS warning: SetResources maxThreadsIn=%d requested, but internal batch threading is disabled; using 1 thread.\n", + maxThreadsIn); + } + (void) maxThreadsIn; + (void) ncores; + const int thrMax = 1; + + // For simplicity we won't vary the amount of memory per thread + // in the small and large versions. + + int noOfThreads, noOfLargeThreads, noOfSmallThreads; + if (thrMax * THREADMEM_LARGE_MAX_MB <= memMaxMB) + { + // We have enough memory for the maximum number of large threads. + noOfThreads = thrMax; + noOfLargeThreads = thrMax; + noOfSmallThreads = 0; + } + else if (thrMax * THREADMEM_SMALL_MAX_MB > memMaxMB) + { + // We don't even have enough memory for only small threads. + // We'll limit the number of threads. + noOfThreads = static_cast(memMaxMB / + static_cast(THREADMEM_SMALL_MAX_MB)); + noOfLargeThreads = 0; + noOfSmallThreads = noOfThreads; + } + else + { + // We'll have a mixture with as many large threads as possible. + const double d = static_cast( + THREADMEM_LARGE_MAX_MB - THREADMEM_SMALL_MAX_MB); + + noOfThreads = thrMax; + noOfLargeThreads = static_cast( + (memMaxMB - thrMax * THREADMEM_SMALL_MAX_MB) / d); + noOfSmallThreads = thrMax - noOfLargeThreads; + } + + sysdep.register_params(noOfThreads, memMaxMB); + + scheduler.RegisterThreads(noOfThreads); + + // Clear the thread memory and fill it up again. + memory.Resize(0, DDS_TT_SMALL, 0, 0); + if (noOfLargeThreads > 0) + memory.Resize(static_cast(noOfLargeThreads), + DDS_TT_LARGE, THREADMEM_LARGE_DEF_MB, THREADMEM_LARGE_MAX_MB); + if (noOfSmallThreads > 0) + memory.Resize(static_cast(noOfThreads), + DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + + ThreadMgr::instance().Reset(noOfThreads); + + InitDebugFiles(); + + if (! _initialized) + { + _initialized = 1; + init_lookup_tables(); + } } @@ -177,9 +177,9 @@ void STDCALL SetResources( * Public API documentation is maintained in the API headers. */ int STDCALL SetThreading( - int code) + int code) { - return sysdep.prefer_threading(static_cast(code)); + return sysdep.prefer_threading(static_cast(code)); } @@ -189,221 +189,221 @@ void InitDebugFiles() void SetDeal( - const std::shared_ptr& thrp) + const std::shared_ptr& thrp) { - /* Initialization of the rel structure is inspired by + /* Initialization of the rel structure is inspired by a solution given by Thomas Andrews */ - for (int s = 0; s < DDS_SUITS; s++) - { - thrp->lookAheadPos.aggr[s] = 0; - for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + { + thrp->lookAheadPos.aggr[s] = 0; + for (int h = 0; h < DDS_HANDS; h++) + { + thrp->lookAheadPos.rank_in_suit[h][s] = thrp->suit[h][s]; + thrp->lookAheadPos.aggr[s] |= thrp->suit[h][s]; + } + } + + for (int s = 0; s < DDS_SUITS; s++) { - thrp->lookAheadPos.rank_in_suit[h][s] = thrp->suit[h][s]; - thrp->lookAheadPos.aggr[s] |= thrp->suit[h][s]; + for (int h = 0; h < DDS_HANDS; h++) + thrp->lookAheadPos.length[h][s] = static_cast( + count_table[thrp->lookAheadPos.rank_in_suit[h][s]]); } - } - for (int s = 0; s < DDS_SUITS; s++) - { + // Clubs are implicit, for a given trick number. for (int h = 0; h < DDS_HANDS; h++) - thrp->lookAheadPos.length[h][s] = static_cast( - count_table[thrp->lookAheadPos.rank_in_suit[h][s]]); - } - - // Clubs are implicit, for a given trick number. - for (int h = 0; h < DDS_HANDS; h++) - { - thrp->lookAheadPos.hand_dist[h] = - static_cast( - (thrp->lookAheadPos.length[h][0] << 8) | - (thrp->lookAheadPos.length[h][1] << 4) | - (thrp->lookAheadPos.length[h][2] )); - } + { + thrp->lookAheadPos.hand_dist[h] = + static_cast( + (thrp->lookAheadPos.length[h][0] << 8) | + (thrp->lookAheadPos.length[h][1] << 4) | + (thrp->lookAheadPos.length[h][2] )); + } } void SetDealTables( - SolverContext& ctx) + SolverContext& ctx) { - auto thrp = ctx.thread(); - unsigned int topBitRank = 1; - unsigned int topBitNo = 2; - - // Initialization of the rel structure is inspired by - // a solution given by Thomas Andrews. - - // rel[aggr].abs_rank[absolute rank][suit].hand is the hand - // (N = 0, E = 1 etc.) which holds the absolute rank in - // the suit characterized by aggr. - // rel[aggr].abs_rank[absolute rank][suit].rank is the - // relative rank of that card. - - for (int s = 0; s < DDS_SUITS; s++) - { - for (int ord = 1; ord <= 13; ord++) - { - thrp->rel[0].abs_rank[ord][s].hand = -1; - thrp->rel[0].abs_rank[ord][s].rank = 0; - } - } + auto thrp = ctx.thread(); + unsigned int topBitRank = 1; + unsigned int topBitNo = 2; + + // Initialization of the rel structure is inspired by + // a solution given by Thomas Andrews. - // handLookup[suit][absolute rank] is the hand (N = 0 etc.) - // holding the absolute rank in suit. + // rel[aggr].abs_rank[absolute rank][suit].hand is the hand + // (N = 0, E = 1 etc.) which holds the absolute rank in + // the suit characterized by aggr. + // rel[aggr].abs_rank[absolute rank][suit].rank is the + // relative rank of that card. - int handLookup[DDS_SUITS][15]; - for (int s = 0; s < DDS_SUITS; s++) - { - for (int r = 14; r >= 2; r--) + for (int s = 0; s < DDS_SUITS; s++) { - handLookup[s][r] = 0; - for (int h = 0; h < DDS_HANDS; h++) - { - if (thrp->suit[h][s] & bit_map_rank[r]) + for (int ord = 1; ord <= 13; ord++) { - handLookup[s][r] = h; - break; + thrp->rel[0].abs_rank[ord][s].hand = -1; + thrp->rel[0].abs_rank[ord][s].rank = 0; } - } } - } - { - ctx.trans_table()->init(handLookup); - } + // handLookup[suit][absolute rank] is the hand (N = 0 etc.) + // holding the absolute rank in suit. - RelRanksType * relp; - for (unsigned int aggr = 1; aggr < 8192; aggr++) - { - if (aggr >= (topBitRank << 1)) + int handLookup[DDS_SUITS][15]; + for (int s = 0; s < DDS_SUITS; s++) { - /* Next top bit */ - topBitRank <<= 1; - topBitNo++; + for (int r = 14; r >= 2; r--) + { + handLookup[s][r] = 0; + for (int h = 0; h < DDS_HANDS; h++) + { + if (thrp->suit[h][s] & bit_map_rank[r]) + { + handLookup[s][r] = h; + break; + } + } + } } - thrp->rel[aggr] = thrp->rel[aggr ^ topBitRank]; - relp = &thrp->rel[aggr]; - - int weight = count_table[aggr]; - for (int c = weight; c >= 2; c--) { - for (int s = 0; s < DDS_SUITS; s++) - { - relp->abs_rank[c][s].hand = relp->abs_rank[c - 1][s].hand; - relp->abs_rank[c][s].rank = relp->abs_rank[c - 1][s].rank; - } + ctx.trans_table()->init(handLookup); } - for (int s = 0; s < DDS_SUITS; s++) + + RelRanksType * relp; + for (unsigned int aggr = 1; aggr < 8192; aggr++) { - relp->abs_rank[1][s].hand = - static_cast(handLookup[s][topBitNo]); - relp->abs_rank[1][s].rank = static_cast(topBitNo); + if (aggr >= (topBitRank << 1)) + { + /* Next top bit */ + topBitRank <<= 1; + topBitNo++; + } + + thrp->rel[aggr] = thrp->rel[aggr ^ topBitRank]; + relp = &thrp->rel[aggr]; + + int weight = count_table[aggr]; + for (int c = weight; c >= 2; c--) + { + for (int s = 0; s < DDS_SUITS; s++) + { + relp->abs_rank[c][s].hand = relp->abs_rank[c - 1][s].hand; + relp->abs_rank[c][s].rank = relp->abs_rank[c - 1][s].rank; + } + } + for (int s = 0; s < DDS_SUITS; s++) + { + relp->abs_rank[1][s].hand = + static_cast(handLookup[s][topBitNo]); + relp->abs_rank[1][s].rank = static_cast(topBitNo); + } } - } } void InitWinners( - const Deal& dl, - Pos& posPoint, - const std::shared_ptr& thrp) + const Deal& dl, + Pos& posPoint, + const std::shared_ptr& thrp) { - int hand, suit, rank; - unsigned short int startMovesBitMap[DDS_HANDS][DDS_SUITS]; + int hand, suit, rank; + unsigned short int startMovesBitMap[DDS_HANDS][DDS_SUITS]; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - startMovesBitMap[h][s] = 0; - - for (int k = 0; k < posPoint.hand_rel_first; k++) - { - hand = HAND_ID(dl.first, k); - suit = dl.currentTrickSuit[k]; - rank = dl.currentTrickRank[k]; - startMovesBitMap[hand][suit] |= bit_map_rank[rank]; - } - - int aggr; - for (int s = 0; s < DDS_SUITS; s++) - { - aggr = 0; for (int h = 0; h < DDS_HANDS; h++) - aggr |= startMovesBitMap[h][s] | thrp->suit[h][s]; + for (int s = 0; s < DDS_SUITS; s++) + startMovesBitMap[h][s] = 0; - posPoint.winner[s].rank = thrp->rel[aggr].abs_rank[1][s].rank; - posPoint.winner[s].hand = thrp->rel[aggr].abs_rank[1][s].hand; - posPoint.second_best[s].rank = thrp->rel[aggr].abs_rank[2][s].rank; - posPoint.second_best[s].hand = thrp->rel[aggr].abs_rank[2][s].hand; - } + for (int k = 0; k < posPoint.hand_rel_first; k++) + { + hand = HAND_ID(dl.first, k); + suit = dl.currentTrickSuit[k]; + rank = dl.currentTrickRank[k]; + startMovesBitMap[hand][suit] |= bit_map_rank[rank]; + } + + int aggr; + for (int s = 0; s < DDS_SUITS; s++) + { + aggr = 0; + for (int h = 0; h < DDS_HANDS; h++) + aggr |= startMovesBitMap[h][s] | thrp->suit[h][s]; + + posPoint.winner[s].rank = thrp->rel[aggr].abs_rank[1][s].rank; + posPoint.winner[s].hand = thrp->rel[aggr].abs_rank[1][s].hand; + posPoint.second_best[s].rank = thrp->rel[aggr].abs_rank[2][s].rank; + posPoint.second_best[s].hand = thrp->rel[aggr].abs_rank[2][s].hand; + } } void STDCALL GetDDSInfo(DDSInfo * info) { - stringstream ss; - ss << "DDS DLL\n-------\n"; - - const string strSystem = sysdep.get_system(info->system); - ss << left << setw(13) << "System" << - setw(20) << right << strSystem << "\n"; - - const string strBits = sysdep.get_bits(info->numBits); - ss << left << setw(13) << "Word size" << - setw(20) << right << strBits << "\n"; - - const string strCompiler =sysdep.get_compiler(info->compiler); - ss << left << setw(13) << "Compiler" << - setw(20) << right << strCompiler << "\n"; - - const string strConstructor = sysdep.get_constructor(info->constructor); - ss << left << setw(13) << "Constructor" << - setw(20) << right << strConstructor << "\n"; - - const string strVersion = sysdep.get_version(info->major, - info->minor, info->patch); - ss << left << setw(13) << "Version" << - setw(20) << right << strVersion << "\n"; - strcpy(info->version_string, strVersion.c_str()); - - ss << left << setw(17) << "Memory max (MB)" << - setw(16) << right << sysdep.get_memory_max() << "\n"; - - const string stm = to_string(THREADMEM_SMALL_DEF_MB) + "-" + - to_string(THREADMEM_SMALL_MAX_MB) + " / " + - to_string(THREADMEM_LARGE_DEF_MB) + "-" + - to_string(THREADMEM_LARGE_MAX_MB); - ss << left << setw(17) << "Threads (MB)" << - setw(16) << right << stm << "\n"; - - info->numCores = sysdep.get_cores(); - ss << left << setw(17) << "Number of cores" << - setw(16) << right << info->numCores << "\n"; - - info->noOfThreads = sysdep.get_num_threads(); - ss << left << setw(17) << "Number of threads" << - setw(16) << right << sysdep.get_num_threads() << "\n"; - - int l = 0, s = 0; - for (unsigned i = 0; i < static_cast(info->noOfThreads); i++) - { - if (memory.ThreadSize(i) == "S") - s++; - else - l++; - } + stringstream ss; + ss << "DDS DLL\n-------\n"; + + const string strSystem = sysdep.get_system(info->system); + ss << left << setw(13) << "System" << + setw(20) << right << strSystem << "\n"; + + const string strBits = sysdep.get_bits(info->numBits); + ss << left << setw(13) << "Word size" << + setw(20) << right << strBits << "\n"; + + const string strCompiler =sysdep.get_compiler(info->compiler); + ss << left << setw(13) << "Compiler" << + setw(20) << right << strCompiler << "\n"; + + const string strConstructor = sysdep.get_constructor(info->constructor); + ss << left << setw(13) << "Constructor" << + setw(20) << right << strConstructor << "\n"; + + const string strVersion = sysdep.get_version(info->major, + info->minor, info->patch); + ss << left << setw(13) << "Version" << + setw(20) << right << strVersion << "\n"; + strcpy(info->version_string, strVersion.c_str()); + + ss << left << setw(17) << "Memory max (MB)" << + setw(16) << right << sysdep.get_memory_max() << "\n"; + + const string stm = to_string(THREADMEM_SMALL_DEF_MB) + "-" + + to_string(THREADMEM_SMALL_MAX_MB) + " / " + + to_string(THREADMEM_LARGE_DEF_MB) + "-" + + to_string(THREADMEM_LARGE_MAX_MB); + ss << left << setw(17) << "Threads (MB)" << + setw(16) << right << stm << "\n"; + + info->numCores = sysdep.get_cores(); + ss << left << setw(17) << "Number of cores" << + setw(16) << right << info->numCores << "\n"; + + info->noOfThreads = sysdep.get_num_threads(); + ss << left << setw(17) << "Number of threads" << + setw(16) << right << sysdep.get_num_threads() << "\n"; + + int l = 0, s = 0; + for (unsigned i = 0; i < static_cast(info->noOfThreads); i++) + { + if (memory.ThreadSize(i) == "S") + s++; + else + l++; + } - const string strThrSizes = to_string(s) + " S, " + to_string(l) + " L"; - strcpy(info->threadSizes, strThrSizes.c_str()); - ss << left << setw(13) << "Thread sizes" << - setw(20) << right << strThrSizes << "\n"; + const string strThrSizes = to_string(s) + " S, " + to_string(l) + " L"; + strcpy(info->threadSizes, strThrSizes.c_str()); + ss << left << setw(13) << "Thread sizes" << + setw(20) << right << strThrSizes << "\n"; - const string strThreading = sysdep.get_threading(info->threading); - ss << left << setw(9) << "Threading" << - setw(24) << right << strThreading << "\n"; + const string strThreading = sysdep.get_threading(info->threading); + ss << left << setw(9) << "Threading" << + setw(24) << right << strThreading << "\n"; - const string st = ss.str(); - strcpy(info->systemString, st.c_str()); + const string st = ss.str(); + strcpy(info->systemString, st.c_str()); } @@ -412,101 +412,101 @@ void STDCALL GetDDSInfo(DDSInfo * info) */ void STDCALL FreeMemory() { - for (unsigned thrId = 0; thrId < memory.NumThreads(); thrId++) - memory.ReturnThread(thrId); + for (unsigned thrId = 0; thrId < memory.NumThreads(); thrId++) + memory.ReturnThread(thrId); } void STDCALL ErrorMessage(int code, char line[80]) { - switch (code) - { - case RETURN_NO_FAULT: - strcpy(line, TEXT_NO_FAULT); - break; - case RETURN_UNKNOWN_FAULT: - strcpy(line, TEXT_UNKNOWN_FAULT); - break; - case RETURN_ZERO_CARDS: - strcpy(line, TEXT_ZERO_CARDS); - break; - case RETURN_TARGET_TOO_HIGH: - strcpy(line, TEXT_TARGET_TOO_HIGH); - break; - case RETURN_DUPLICATE_CARDS: - strcpy(line, TEXT_DUPLICATE_CARDS); - break; - case RETURN_TARGET_WRONG_LO: - strcpy(line, TEXT_TARGET_WRONG_LO); - break; - case RETURN_TARGET_WRONG_HI: - strcpy(line, TEXT_TARGET_WRONG_HI); - break; - case RETURN_SOLNS_WRONG_LO: - strcpy(line, TEXT_SOLNS_WRONG_LO); - break; - case RETURN_SOLNS_WRONG_HI: - strcpy(line, TEXT_SOLNS_WRONG_HI); - break; - case RETURN_TOO_MANY_CARDS: - strcpy(line, TEXT_TOO_MANY_CARDS); - break; - case RETURN_SUIT_OR_RANK: - strcpy(line, TEXT_SUIT_OR_RANK); - break; - case RETURN_PLAYED_CARD: - strcpy(line, TEXT_PLAYED_CARD); - break; - case RETURN_CARD_COUNT: - strcpy(line, TEXT_CARD_COUNT); - break; - case RETURN_THREAD_INDEX: - strcpy(line, TEXT_THREAD_INDEX); - break; - case RETURN_MODE_WRONG_LO: - strcpy(line, TEXT_MODE_WRONG_LO); - break; - case RETURN_MODE_WRONG_HI: - strcpy(line, TEXT_MODE_WRONG_HI); - break; - case RETURN_TRUMP_WRONG: - strcpy(line, TEXT_TRUMP_WRONG); - break; - case RETURN_FIRST_WRONG: - strcpy(line, TEXT_FIRST_WRONG); - break; - case RETURN_PLAY_FAULT: - strcpy(line, TEXT_PLAY_FAULT); - break; - case RETURN_PBN_FAULT: - strcpy(line, TEXT_PBN_FAULT); - break; - case RETURN_TOO_MANY_BOARDS: - strcpy(line, TEXT_TOO_MANY_BOARDS); - break; - case RETURN_THREAD_CREATE: - strcpy(line, TEXT_THREAD_CREATE); - break; - case RETURN_THREAD_WAIT: - strcpy(line, TEXT_THREAD_WAIT); - break; - case RETURN_THREAD_MISSING: - strcpy(line, TEXT_THREAD_MISSING); - break; - case RETURN_NO_SUIT: - strcpy(line, TEXT_NO_SUIT); - break; - case RETURN_TOO_MANY_TABLES: - strcpy(line, TEXT_TOO_MANY_TABLES); - break; - case RETURN_CHUNK_SIZE: - strcpy(line, TEXT_CHUNK_SIZE); - break; - case RETURN_PAR_TABLE_FAULT: - strcpy(line, TEXT_PAR_TABLE_FAULT); - break; - default: - strcpy(line, "Not a DDS error code"); - break; - } + switch (code) + { + case RETURN_NO_FAULT: + strcpy(line, TEXT_NO_FAULT); + break; + case RETURN_UNKNOWN_FAULT: + strcpy(line, TEXT_UNKNOWN_FAULT); + break; + case RETURN_ZERO_CARDS: + strcpy(line, TEXT_ZERO_CARDS); + break; + case RETURN_TARGET_TOO_HIGH: + strcpy(line, TEXT_TARGET_TOO_HIGH); + break; + case RETURN_DUPLICATE_CARDS: + strcpy(line, TEXT_DUPLICATE_CARDS); + break; + case RETURN_TARGET_WRONG_LO: + strcpy(line, TEXT_TARGET_WRONG_LO); + break; + case RETURN_TARGET_WRONG_HI: + strcpy(line, TEXT_TARGET_WRONG_HI); + break; + case RETURN_SOLNS_WRONG_LO: + strcpy(line, TEXT_SOLNS_WRONG_LO); + break; + case RETURN_SOLNS_WRONG_HI: + strcpy(line, TEXT_SOLNS_WRONG_HI); + break; + case RETURN_TOO_MANY_CARDS: + strcpy(line, TEXT_TOO_MANY_CARDS); + break; + case RETURN_SUIT_OR_RANK: + strcpy(line, TEXT_SUIT_OR_RANK); + break; + case RETURN_PLAYED_CARD: + strcpy(line, TEXT_PLAYED_CARD); + break; + case RETURN_CARD_COUNT: + strcpy(line, TEXT_CARD_COUNT); + break; + case RETURN_THREAD_INDEX: + strcpy(line, TEXT_THREAD_INDEX); + break; + case RETURN_MODE_WRONG_LO: + strcpy(line, TEXT_MODE_WRONG_LO); + break; + case RETURN_MODE_WRONG_HI: + strcpy(line, TEXT_MODE_WRONG_HI); + break; + case RETURN_TRUMP_WRONG: + strcpy(line, TEXT_TRUMP_WRONG); + break; + case RETURN_FIRST_WRONG: + strcpy(line, TEXT_FIRST_WRONG); + break; + case RETURN_PLAY_FAULT: + strcpy(line, TEXT_PLAY_FAULT); + break; + case RETURN_PBN_FAULT: + strcpy(line, TEXT_PBN_FAULT); + break; + case RETURN_TOO_MANY_BOARDS: + strcpy(line, TEXT_TOO_MANY_BOARDS); + break; + case RETURN_THREAD_CREATE: + strcpy(line, TEXT_THREAD_CREATE); + break; + case RETURN_THREAD_WAIT: + strcpy(line, TEXT_THREAD_WAIT); + break; + case RETURN_THREAD_MISSING: + strcpy(line, TEXT_THREAD_MISSING); + break; + case RETURN_NO_SUIT: + strcpy(line, TEXT_NO_SUIT); + break; + case RETURN_TOO_MANY_TABLES: + strcpy(line, TEXT_TOO_MANY_TABLES); + break; + case RETURN_CHUNK_SIZE: + strcpy(line, TEXT_CHUNK_SIZE); + break; + case RETURN_PAR_TABLE_FAULT: + strcpy(line, TEXT_PAR_TABLE_FAULT); + break; + default: + strcpy(line, "Not a DDS error code"); + break; + } } diff --git a/library/src/init.hpp b/library/src/init.hpp index 0cda60511..fe542165b 100644 --- a/library/src/init.hpp +++ b/library/src/init.hpp @@ -20,6 +20,6 @@ void SetDeal(const std::shared_ptr& thrp); void SetDealTables(SolverContext& ctx); void InitWinners( - const Deal& dl, - Pos& posPoint, - const std::shared_ptr& thrp); + const Deal& dl, + Pos& posPoint, + const std::shared_ptr& thrp); diff --git a/library/src/later_tricks.cpp b/library/src/later_tricks.cpp index 6c1b7b52c..2b2f20aad 100644 --- a/library/src/later_tricks.cpp +++ b/library/src/later_tricks.cpp @@ -29,160 +29,160 @@ * @return true if target can be reached, false otherwise */ bool LaterTricksMIN( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx) + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx) { - - const bool depth_ok = (depth >= 0 && depth < 50); - if ((trump == DDS_NOTRUMP) || (tpos.winner[trump].rank == 0)) - { - int sum = 0; - for (int ss = 0; ss < DDS_SUITS; ss++) + + const bool depth_ok = (depth >= 0 && depth < 50); + if ((trump == DDS_NOTRUMP) || (tpos.winner[trump].rank == 0)) { - int hh = tpos.winner[ss].hand; - if (hh != -1) - { - if (static_cast(hh) < static_cast(DDS_HANDS) && - ctx.search().node_type_store(hh) == MAXNODE) - sum += std::max(tpos.length[hh][ss], + int sum = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) + { + int hh = tpos.winner[ss].hand; + if (hh != -1) + { + if (static_cast(hh) < static_cast(DDS_HANDS) && + ctx.search().node_type_store(hh) == MAXNODE) + sum += std::max(tpos.length[hh][ss], tpos.length[partner[hh]][ss]); - } - } + } + } - if ((tpos.tricks_max + sum < target) && (sum > 0)) - { - if ((tpos.tricks_max + (depth >> 2) >= target)) - return true; + if ((tpos.tricks_max + sum < target) && (sum > 0)) + { + if ((tpos.tricks_max + (depth >> 2) >= target)) + return true; - for (int ss = 0; ss < DDS_SUITS; ss++) - { - int win_hand = tpos.winner[ss].hand; + for (int ss = 0; ss < DDS_SUITS; ss++) + { + int win_hand = tpos.winner[ss].hand; - if (win_hand == -1) { - if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (win_hand == -1) { + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + } + else if (static_cast(win_hand) >= static_cast(DDS_HANDS)) { + // Invalid hand index; avoid using partner/lho/rho with OOB index. + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + continue; + } + else if (ctx.search().node_type_store(win_hand) == MINNODE) + { + if ((tpos.rank_in_suit[partner[win_hand]][ss] == 0) && + (tpos.rank_in_suit[lho[win_hand]][ss] == 0) && + (tpos.rank_in_suit[rho[win_hand]][ss] == 0)) + { if (depth_ok) tpos.win_ranks[depth][ss] = 0; } + else + { if (depth_ok) tpos.win_ranks[depth][ss] = bit_map_rank[tpos.winner[ss].rank]; } + } + else { + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + } + } + return false; } - else if (static_cast(win_hand) >= static_cast(DDS_HANDS)) { - // Invalid hand index; avoid using partner/lho/rho with OOB index. - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - continue; + } + else if (ctx.search().node_type_store(tpos.winner[trump].hand) == MINNODE) + { + if ((tpos.length[hand][trump] == 0) && + (tpos.length[partner[hand]][trump] == 0)) + { + if (((tpos.tricks_max + (depth >> 2) + 1 - + std::max(tpos.length[lho[hand]][trump], + tpos.length[rho[hand]][trump])) < target)) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + return false; + } } - else if (ctx.search().node_type_store(win_hand) == MINNODE) + else if ((tpos.tricks_max + (depth >> 2)) < target) { - if ((tpos.rank_in_suit[partner[win_hand]][ss] == 0) && - (tpos.rank_in_suit[lho[win_hand]][ss] == 0) && - (tpos.rank_in_suit[rho[win_hand]][ss] == 0)) - { if (depth_ok) tpos.win_ranks[depth][ss] = 0; } - else - { if (depth_ok) tpos.win_ranks[depth][ss] = bit_map_rank[tpos.winner[ss].rank]; } + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = + bit_map_rank[tpos.winner[trump].rank]; + return false; } - else { - if (depth_ok) tpos.win_ranks[depth][ss] = 0; + else if (tpos.tricks_max + (depth >> 2) == target) + { + int hh = tpos.second_best[trump].hand; + if (hh == -1) + return true; + + if (static_cast(hh) >= static_cast(DDS_HANDS)) + return true; + + int r2 = tpos.second_best[trump].rank; + if ((ctx.search().node_type_store(hh) == MINNODE) && (r2 != 0)) + { + if (tpos.length[hh][trump] > 1 || + tpos.length[partner[hh]][trump] > 1) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[r2]; + return false; + } + } } - } - return false; - } - } - else if (ctx.search().node_type_store(tpos.winner[trump].hand) == MINNODE) - { - if ((tpos.length[hand][trump] == 0) && - (tpos.length[partner[hand]][trump] == 0)) - { - if (((tpos.tricks_max + (depth >> 2) + 1 - - std::max(tpos.length[lho[hand]][trump], - tpos.length[rho[hand]][trump])) < target)) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - return false; - } - } - else if ((tpos.tricks_max + (depth >> 2)) < target) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = - bit_map_rank[tpos.winner[trump].rank]; - return false; } - else if (tpos.tricks_max + (depth >> 2) == target) + else // Not NT { - int hh = tpos.second_best[trump].hand; - if (hh == -1) - return true; + int hh = tpos.second_best[trump].hand; + if (hh == -1) + return true; + if (static_cast(hh) >= static_cast(DDS_HANDS)) + return true; - if (static_cast(hh) >= static_cast(DDS_HANDS)) - return true; + if ((ctx.search().node_type_store(hh) != MINNODE) || + (tpos.length[hh][trump] <= 1)) + return true; - int r2 = tpos.second_best[trump].rank; - if ((ctx.search().node_type_store(hh) == MINNODE) && (r2 != 0)) - { - if (tpos.length[hh][trump] > 1 || - tpos.length[partner[hh]][trump] > 1) + if (tpos.winner[trump].hand == rho[hh]) { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[r2]; - return false; + if (((tpos.tricks_max + (depth >> 2)) < target)) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = + bit_map_rank[tpos.second_best[trump].rank]; + return false; + } } - } - } - } - else // Not NT - { - int hh = tpos.second_best[trump].hand; - if (hh == -1) - return true; - if (static_cast(hh) >= static_cast(DDS_HANDS)) - return true; - - if ((ctx.search().node_type_store(hh) != MINNODE) || - (tpos.length[hh][trump] <= 1)) - return true; - - if (tpos.winner[trump].hand == rho[hh]) - { - if (((tpos.tricks_max + (depth >> 2)) < target)) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = - bit_map_rank[tpos.second_best[trump].rank]; - return false; - } - } - else - { - unsigned short aggr = tpos.aggr[trump]; - // Defensive check: rel[] is sized 8192 in ThreadData. If aggr - // is out of bounds we avoid a crash and return a conservative result. - if (aggr >= 8192u) - { - fprintf(stderr, "LaterTricksMIN: invalid aggr=%u (depth=%d)", aggr, depth); - return true; // conservative fallback - } - int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; - if (h == -1) - return true; + else + { + unsigned short aggr = tpos.aggr[trump]; + // Defensive check: rel[] is sized 8192 in ThreadData. If aggr + // is out of bounds we avoid a crash and return a conservative result. + if (aggr >= 8192u) + { + fprintf(stderr, "LaterTricksMIN: invalid aggr=%u (depth=%d)", aggr, depth); + return true; // conservative fallback + } + int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; + if (h == -1) + return true; - if (static_cast(h) >= static_cast(DDS_HANDS)) - return true; + if (static_cast(h) >= static_cast(DDS_HANDS)) + return true; - if ((ctx.search().node_type_store(h) == MINNODE) && - ((tpos.tricks_max + (depth >> 2)) < target)) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[ - static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; - return false; - } + if ((ctx.search().node_type_store(h) == MINNODE) && + ((tpos.tricks_max + (depth >> 2)) < target)) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[ + static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; + return false; + } + } } - } - return true; + return true; } @@ -201,162 +201,162 @@ bool LaterTricksMIN( * @return true if target can be reached, false otherwise */ bool LaterTricksMAX( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx) + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx) { - - const bool depth_ok = (depth >= 0 && depth < 50); - if ((trump == DDS_NOTRUMP) || (tpos.winner[trump].rank == 0)) - { - int sum = 0; - for (int ss = 0; ss < DDS_SUITS; ss++) - { - int hh = tpos.winner[ss].hand; - if (hh != -1) - { - if (static_cast(hh) < static_cast(DDS_HANDS) && - ctx.search().node_type_store(hh) == MINNODE) - sum += std::max(tpos.length[hh][ss], - tpos.length[partner[hh]][ss]); - } - } - if ((tpos.tricks_max + (depth >> 2) + 1 - sum >= target) && - (sum > 0)) + const bool depth_ok = (depth >= 0 && depth < 50); + if ((trump == DDS_NOTRUMP) || (tpos.winner[trump].rank == 0)) { - if ((tpos.tricks_max + 1 < target)) - return false; - - for (int ss = 0; ss < DDS_SUITS; ss++) - { - int win_hand = tpos.winner[ss].hand; - if (win_hand == -1) { - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - } - else if (static_cast(win_hand) >= static_cast(DDS_HANDS)) { - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - continue; - } - else if (ctx.search().node_type_store(win_hand) == MAXNODE) + int sum = 0; + for (int ss = 0; ss < DDS_SUITS; ss++) { - if ((tpos.rank_in_suit[partner[win_hand]][ss] == 0) && - (tpos.rank_in_suit[lho[win_hand]][ss] == 0) && - (tpos.rank_in_suit[rho[win_hand]][ss] == 0)) - { if (depth_ok) tpos.win_ranks[depth][ss] = 0; } - else - { if (depth_ok) tpos.win_ranks[depth][ss] = - bit_map_rank[tpos.winner[ss].rank]; } + int hh = tpos.winner[ss].hand; + if (hh != -1) + { + if (static_cast(hh) < static_cast(DDS_HANDS) && + ctx.search().node_type_store(hh) == MINNODE) + sum += std::max(tpos.length[hh][ss], + tpos.length[partner[hh]][ss]); + } } - else { - if (depth_ok) tpos.win_ranks[depth][ss] = 0; + + if ((tpos.tricks_max + (depth >> 2) + 1 - sum >= target) && + (sum > 0)) + { + if ((tpos.tricks_max + 1 < target)) + return false; + + for (int ss = 0; ss < DDS_SUITS; ss++) + { + int win_hand = tpos.winner[ss].hand; + if (win_hand == -1) { + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + } + else if (static_cast(win_hand) >= static_cast(DDS_HANDS)) { + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + continue; + } + else if (ctx.search().node_type_store(win_hand) == MAXNODE) + { + if ((tpos.rank_in_suit[partner[win_hand]][ss] == 0) && + (tpos.rank_in_suit[lho[win_hand]][ss] == 0) && + (tpos.rank_in_suit[rho[win_hand]][ss] == 0)) + { if (depth_ok) tpos.win_ranks[depth][ss] = 0; } + else + { if (depth_ok) tpos.win_ranks[depth][ss] = + bit_map_rank[tpos.winner[ss].rank]; } + } + else { + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + } + } + return true; } - } - return true; } - } - else if (ctx.search().node_type_store(tpos.winner[trump].hand) == MAXNODE) - { - if ((tpos.length[hand][trump] == 0) && - (tpos.length[partner[hand]][trump] == 0)) + else if (ctx.search().node_type_store(tpos.winner[trump].hand) == MAXNODE) { - int maxlen = std::max(tpos.length[lho[hand]][trump], + if ((tpos.length[hand][trump] == 0) && + (tpos.length[partner[hand]][trump] == 0)) + { + int maxlen = std::max(tpos.length[lho[hand]][trump], tpos.length[rho[hand]][trump]); - if ((tpos.tricks_max + maxlen) >= target) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - return true; - } - } - else if ((tpos.tricks_max + 1) >= target) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = - bit_map_rank[tpos.winner[trump].rank]; - return true; - } - else - { - int hh = tpos.second_best[trump].hand; - if (hh == -1) - return false; + if ((tpos.tricks_max + maxlen) >= target) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + return true; + } + } + else if ((tpos.tricks_max + 1) >= target) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = + bit_map_rank[tpos.winner[trump].rank]; + return true; + } + else + { + int hh = tpos.second_best[trump].hand; + if (hh == -1) + return false; - if (static_cast(hh) >= static_cast(DDS_HANDS)) - return false; + if (static_cast(hh) >= static_cast(DDS_HANDS)) + return false; - if ((ctx.search().node_type_store(hh) == MAXNODE) && - (tpos.second_best[trump].rank != 0)) - { - if (((tpos.length[hh][trump] > 1) || + if ((ctx.search().node_type_store(hh) == MAXNODE) && + (tpos.second_best[trump].rank != 0)) + { + if (((tpos.length[hh][trump] > 1) || (tpos.length[partner[hh]][trump] > 1)) && - ((tpos.tricks_max + 2) >= target)) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = - bit_map_rank[tpos.second_best[trump].rank]; - return true; + ((tpos.tricks_max + 2) >= target)) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = + bit_map_rank[tpos.second_best[trump].rank]; + return true; + } + } } - } } - } - else // trump != DDS_NOTRUMP) - { - int hh = tpos.second_best[trump].hand; - if (hh == -1) - return false; - if (static_cast(hh) >= static_cast(DDS_HANDS)) - return false; + else // trump != DDS_NOTRUMP) + { + int hh = tpos.second_best[trump].hand; + if (hh == -1) + return false; + if (static_cast(hh) >= static_cast(DDS_HANDS)) + return false; - if ((ctx.search().node_type_store(hh) != MAXNODE) || - (tpos.length[hh][trump] <= 1)) - return false; + if ((ctx.search().node_type_store(hh) != MAXNODE) || + (tpos.length[hh][trump] <= 1)) + return false; - if (tpos.winner[trump].hand == rho[hh]) - { - if ((tpos.tricks_max + 1) >= target) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = - bit_map_rank[tpos.second_best[trump].rank] ; - return true; - } - } - else - { - unsigned short aggr = tpos.aggr[trump]; - // Defensive check mirroring LaterTricksMIN: ThreadData::rel has 8192 entries. - if (aggr >= 8192u) - { - fprintf(stderr, "LaterTricksMAX: invalid aggr=%u (depth=%d)\n", aggr, depth); - return false; // conservative fallback for MAX - } - int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; - if (h == -1) - return false; + if (tpos.winner[trump].hand == rho[hh]) + { + if ((tpos.tricks_max + 1) >= target) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = + bit_map_rank[tpos.second_best[trump].rank] ; + return true; + } + } + else + { + unsigned short aggr = tpos.aggr[trump]; + // Defensive check mirroring LaterTricksMIN: ThreadData::rel has 8192 entries. + if (aggr >= 8192u) + { + fprintf(stderr, "LaterTricksMAX: invalid aggr=%u (depth=%d)\n", aggr, depth); + return false; // conservative fallback for MAX + } + int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; + if (h == -1) + return false; - if (static_cast(h) >= static_cast(DDS_HANDS)) - return false; + if (static_cast(h) >= static_cast(DDS_HANDS)) + return false; - if ((ctx.search().node_type_store(h) == MAXNODE) && - ((tpos.tricks_max + 1) >= target)) - { - for (int ss = 0; ss < DDS_SUITS; ss++) - if (depth_ok) tpos.win_ranks[depth][ss] = 0; - if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[ - static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; - return true; - } + if ((ctx.search().node_type_store(h) == MAXNODE) && + ((tpos.tricks_max + 1) >= target)) + { + for (int ss = 0; ss < DDS_SUITS; ss++) + if (depth_ok) tpos.win_ranks[depth][ss] = 0; + if (depth_ok) tpos.win_ranks[depth][trump] = bit_map_rank[ + static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; + return true; + } + } } - } - return false; + return false; } diff --git a/library/src/later_tricks.hpp b/library/src/later_tricks.hpp index 506d03cc8..241b3af9d 100644 --- a/library/src/later_tricks.hpp +++ b/library/src/later_tricks.hpp @@ -14,17 +14,17 @@ bool LaterTricksMIN( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx); + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx); bool LaterTricksMAX( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx); + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx); diff --git a/library/src/lookup_tables/lookup_tables.cpp b/library/src/lookup_tables/lookup_tables.cpp index 2eba329f9..ba18cb669 100644 --- a/library/src/lookup_tables/lookup_tables.cpp +++ b/library/src/lookup_tables/lookup_tables.cpp @@ -15,202 +15,202 @@ namespace { - // Underlying storage (internal linkage) - static int highest_rank_storage[8192]; - static int lowest_rank_storage[8192]; - static int count_table_storage[8192]; - static char rel_rank_storage[8192][15]; - static unsigned short win_ranks_storage[8192][14]; - static MoveGroupType group_data_storage[8192]; - - static std::once_flag lookup_tables_init_flag; + // Underlying storage (internal linkage) + static int highest_rank_storage[8192]; + static int lowest_rank_storage[8192]; + static int count_table_storage[8192]; + static char rel_rank_storage[8192][15]; + static unsigned short win_ranks_storage[8192][14]; + static MoveGroupType group_data_storage[8192]; + + static std::once_flag lookup_tables_init_flag; } static auto init_lookup_tables_impl() -> void { - // highestRank[aggregate] is the highest absolute rank in the - // suit represented by aggr. The absolute rank is 2 .. 14. - // Similarly for lowestRank. - highest_rank_storage[0] = 0; - lowest_rank_storage[0] = 0; - for (int aggregate = 1; aggregate < 8192; aggregate++) - { - for (int rank = 14; rank >= 2; rank--) + // highestRank[aggregate] is the highest absolute rank in the + // suit represented by aggr. The absolute rank is 2 .. 14. + // Similarly for lowestRank. + highest_rank_storage[0] = 0; + lowest_rank_storage[0] = 0; + for (int aggregate = 1; aggregate < 8192; aggregate++) { - if (aggregate & bit_map_rank[rank]) - { - highest_rank_storage[aggregate] = rank; - break; - } - } - for (int rank = 2; rank <= 14; rank++) - { - if (aggregate & bit_map_rank[rank]) - { - lowest_rank_storage[aggregate] = rank; - break; - } + for (int rank = 14; rank >= 2; rank--) + { + if (aggregate & bit_map_rank[rank]) + { + highest_rank_storage[aggregate] = rank; + break; + } + } + for (int rank = 2; rank <= 14; rank++) + { + if (aggregate & bit_map_rank[rank]) + { + lowest_rank_storage[aggregate] = rank; + break; + } + } } - } - // The use of the counttable to give the number of bits set to - // one in an integer follows an implementation by Thomas Andrews. + // The use of the counttable to give the number of bits set to + // one in an integer follows an implementation by Thomas Andrews. - // counttable[aggregate] is the number of '1' bits (binary weight) - // in aggr. - for (int aggregate = 0; aggregate < 8192; aggregate++) - { - count_table_storage[aggregate] = 0; - for (int rank = 0; rank < 13; rank++) - { - if (aggregate & (1 << rank)) - { - count_table_storage[aggregate]++; - } - } - } - - // relRank[aggregate][absolute rank] is the relative rank of - // that absolute rank in the suit represented by aggr. - // The relative rank is 2 .. 14. - memset(rel_rank_storage[0], 0, 15); - for (int aggregate = 1; aggregate < 8192; aggregate++) - { - char ordinal = 0; - for (int rank = 14; rank >= 2; rank--) + // counttable[aggregate] is the number of '1' bits (binary weight) + // in aggr. + for (int aggregate = 0; aggregate < 8192; aggregate++) { - if (aggregate & bit_map_rank[rank]) - { - ordinal++; - rel_rank_storage[aggregate][rank] = ordinal; - } + count_table_storage[aggregate] = 0; + for (int rank = 0; rank < 13; rank++) + { + if (aggregate & (1 << rank)) + { + count_table_storage[aggregate]++; + } + } } - } - - // win_ranks[aggregate][least_win] is the absolute suit represented - // by aggr, but limited to its top "leastWin" bits. - for (int aggregate = 0; aggregate < 8192; aggregate++) - { - win_ranks_storage[aggregate][0] = 0; - for (int least_win = 1; least_win < 14; least_win++) + + // relRank[aggregate][absolute rank] is the relative rank of + // that absolute rank in the suit represented by aggr. + // The relative rank is 2 .. 14. + memset(rel_rank_storage[0], 0, 15); + for (int aggregate = 1; aggregate < 8192; aggregate++) { - int result = 0; - int next_bit_position = 1; - for (int rank = 14; rank >= 2; rank--) - { - if (aggregate & bit_map_rank[rank]) + char ordinal = 0; + for (int rank = 14; rank >= 2; rank--) { - if (next_bit_position <= least_win) - { - result |= bit_map_rank[rank]; - next_bit_position++; - } - else - break; + if (aggregate & bit_map_rank[rank]) + { + ordinal++; + rel_rank_storage[aggregate][rank] = ordinal; + } } - } - win_ranks_storage[aggregate][least_win] = static_cast(result); } - } - - // groupData[ris] is a representation of the suit (ris is - // "rank in suit") in terms of runs of adjacent bits. - // 1 1100 1101 0110 - // has 4 runs, so last_group_ is 3, and the entries are - // 0: 4 and 0x0002, gap 0x0000 (lowest gap unused, though) - // 1: 6 and 0x0000, gap 0x0008 - // 2: 9 and 0x0040, gap 0x0020 - // 3: 14 and 0x0c00, gap 0x0300 - - static const int topside[15] = - { - 0x0000, 0x0000, 0x0000, 0x0001, // 2, 3, - 0x0003, 0x0007, 0x000f, 0x001f, // 4, 5, 6, 7, - 0x003f, 0x007f, 0x00ff, 0x01ff, // 8, 9, T, J, - 0x03ff, 0x07ff, 0x0fff // Q, K, A - }; - - static const int botside[15] = - { - 0xffff, 0xffff, 0x1ffe, 0x1ffc, // 2, 3, - 0x1ff8, 0x1ff0, 0x1fe0, 0x1fc0, // 4, 5, 6, 7, - 0x1f80, 0x1f00, 0x1e00, 0x1c00, // 8, 9, T, J, - 0x1800, 0x1000, 0x0000 // Q, K, A - }; - - // So the bit vector in the gap between a top card of K - // and a bottom card of T is - // topside[K] = 0x07ff & - // botside[T] = 0x1e00 - // which is 0x0600, the binary code for QJ. - - group_data_storage[0].last_group_ = -1; - - group_data_storage[1].last_group_ = 0; - group_data_storage[1].rank_[0] = 2; - group_data_storage[1].sequence_[0] = 0; - group_data_storage[1].fullseq_[0] = 1; - group_data_storage[1].gap_[0] = 0; - - int topBitRank = 1; - int nextBitRank = 0; - int topBitNo = 2; - int g; - - for (int ris = 2; ris < 8192; ris++) - { - if (ris >= (topBitRank << 1)) + + // win_ranks[aggregate][least_win] is the absolute suit represented + // by aggr, but limited to its top "leastWin" bits. + for (int aggregate = 0; aggregate < 8192; aggregate++) { - // Next top bit - nextBitRank = topBitRank; - topBitRank <<= 1; - topBitNo++; + win_ranks_storage[aggregate][0] = 0; + for (int least_win = 1; least_win < 14; least_win++) + { + int result = 0; + int next_bit_position = 1; + for (int rank = 14; rank >= 2; rank--) + { + if (aggregate & bit_map_rank[rank]) + { + if (next_bit_position <= least_win) + { + result |= bit_map_rank[rank]; + next_bit_position++; + } + else + break; + } + } + win_ranks_storage[aggregate][least_win] = static_cast(result); + } } - group_data_storage[ris] = group_data_storage[ris ^ topBitRank]; + // groupData[ris] is a representation of the suit (ris is + // "rank in suit") in terms of runs of adjacent bits. + // 1 1100 1101 0110 + // has 4 runs, so last_group_ is 3, and the entries are + // 0: 4 and 0x0002, gap 0x0000 (lowest gap unused, though) + // 1: 6 and 0x0000, gap 0x0008 + // 2: 9 and 0x0040, gap 0x0020 + // 3: 14 and 0x0c00, gap 0x0300 - if (ris & nextBitRank) // 11... Extend group + static const int topside[15] = { - g = group_data_storage[ris].last_group_; - group_data_storage[ris].rank_[g]++; - group_data_storage[ris].sequence_[g] |= nextBitRank; - group_data_storage[ris].fullseq_[g] |= topBitRank; - } - else // 10... New group + 0x0000, 0x0000, 0x0000, 0x0001, // 2, 3, + 0x0003, 0x0007, 0x000f, 0x001f, // 4, 5, 6, 7, + 0x003f, 0x007f, 0x00ff, 0x01ff, // 8, 9, T, J, + 0x03ff, 0x07ff, 0x0fff // Q, K, A + }; + + static const int botside[15] = { - g = ++group_data_storage[ris].last_group_; - group_data_storage[ris].rank_[g] = topBitNo; - group_data_storage[ris].sequence_[g] = 0; - group_data_storage[ris].fullseq_[g] = topBitRank; - // gap_[g] is the gap between group g and the previous group (g-1). - // When opening the first group from an empty suit (last_group_ was -1), - // g == 0 and there is no previous group; gap_[0] is unused (see - // MoveGroupType) and stays 0, matching the explicit ris==1 seed above. - if (g == 0) - group_data_storage[ris].gap_[g] = 0; - else - group_data_storage[ris].gap_[g] = - topside[topBitNo] & botside[ group_data_storage[ris].rank_[g - 1] ]; + 0xffff, 0xffff, 0x1ffe, 0x1ffc, // 2, 3, + 0x1ff8, 0x1ff0, 0x1fe0, 0x1fc0, // 4, 5, 6, 7, + 0x1f80, 0x1f00, 0x1e00, 0x1c00, // 8, 9, T, J, + 0x1800, 0x1000, 0x0000 // Q, K, A + }; + + // So the bit vector in the gap between a top card of K + // and a bottom card of T is + // topside[K] = 0x07ff & + // botside[T] = 0x1e00 + // which is 0x0600, the binary code for QJ. + + group_data_storage[0].last_group_ = -1; + + group_data_storage[1].last_group_ = 0; + group_data_storage[1].rank_[0] = 2; + group_data_storage[1].sequence_[0] = 0; + group_data_storage[1].fullseq_[0] = 1; + group_data_storage[1].gap_[0] = 0; + + int topBitRank = 1; + int nextBitRank = 0; + int topBitNo = 2; + int g; + + for (int ris = 2; ris < 8192; ris++) + { + if (ris >= (topBitRank << 1)) + { + // Next top bit + nextBitRank = topBitRank; + topBitRank <<= 1; + topBitNo++; + } + + group_data_storage[ris] = group_data_storage[ris ^ topBitRank]; + + if (ris & nextBitRank) // 11... Extend group + { + g = group_data_storage[ris].last_group_; + group_data_storage[ris].rank_[g]++; + group_data_storage[ris].sequence_[g] |= nextBitRank; + group_data_storage[ris].fullseq_[g] |= topBitRank; + } + else // 10... New group + { + g = ++group_data_storage[ris].last_group_; + group_data_storage[ris].rank_[g] = topBitNo; + group_data_storage[ris].sequence_[g] = 0; + group_data_storage[ris].fullseq_[g] = topBitRank; + // gap_[g] is the gap between group g and the previous group (g-1). + // When opening the first group from an empty suit (last_group_ was -1), + // g == 0 and there is no previous group; gap_[0] is unused (see + // MoveGroupType) and stays 0, matching the explicit ris==1 seed above. + if (g == 0) + group_data_storage[ris].gap_[g] = 0; + else + group_data_storage[ris].gap_[g] = + topside[topBitNo] & botside[ group_data_storage[ris].rank_[g - 1] ]; + } } - } } auto init_lookup_tables() -> void { - std::call_once(lookup_tables_init_flag, init_lookup_tables_impl); + std::call_once(lookup_tables_init_flag, init_lookup_tables_impl); } // Eager initialization at program start (TU load) to avoid any cost on first use. namespace { - struct DdsLutInitGuard - { - DdsLutInitGuard() noexcept + struct DdsLutInitGuard { - init_lookup_tables(); - } - }; - static const DdsLutInitGuard dds_lut_init_guard; + DdsLutInitGuard() noexcept + { + init_lookup_tables(); + } + }; + static const DdsLutInitGuard dds_lut_init_guard; } // Bind const references to internal storage for zero-overhead access diff --git a/library/src/lookup_tables/lookup_tables.hpp b/library/src/lookup_tables/lookup_tables.hpp index 1e302056d..d459c79d6 100644 --- a/library/src/lookup_tables/lookup_tables.hpp +++ b/library/src/lookup_tables/lookup_tables.hpp @@ -27,32 +27,32 @@ */ struct MoveGroupType { - /** + /** * @brief Index of the last valid group (run) in this representation. * * Valid range: -1 (empty suit) to 6 (maximum 7 groups). * Groups are indexed from 0 to last_group_ (inclusive). */ - int last_group_; + int last_group_; - /** + /** * @brief For each group g, the absolute rank (2..14) of the top card. * * Rank encoding: 2=deuce, ..., 10=ten, 11=Jack, 12=Queen, 13=King, 14=Ace. * Only indices 0..last_group_ contain valid data. */ - int rank_[7]; + int rank_[7]; - /** + /** * @brief For each group g, bitmask of the sequence excluding the top card. * * This represents the "tail" of the run below the top card. * Example: For AKQ, top=Ace(0x1000), sequence=0x0C00 (K=0x0800 | Q=0x0400). * Only indices 0..last_group_ contain valid data. */ - int sequence_[7]; + int sequence_[7]; - /** + /** * @brief For each group g, bitmask of the full sequence including top card. * * This is the complete run including the top card: @@ -62,16 +62,16 @@ struct MoveGroupType * Example: For AKQ, fullseq=0x1C00 (A=0x1000 | K=0x0800 | Q=0x0400). * Only indices 0..last_group_ contain valid data. */ - int fullseq_[7]; + int fullseq_[7]; - /** + /** * @brief For each group g (g>=1), bitmask of the gap between group g and g-1. * * Represents the missing ranks between two consecutive runs. * gap[0] is not used (no gap before first group). * Only indices 1..last_group_ contain valid gap data. */ - int gap_[7]; + int gap_[7]; }; /** diff --git a/library/src/moves/moves.cpp b/library/src/moves/moves.cpp index b63a2c908..c299483f8 100644 --- a/library/src/moves/moves.cpp +++ b/library/src/moves/moves.cpp @@ -56,76 +56,76 @@ namespace // unless trump is a suit in [0, DDS_SUITS). auto trump_winner_bit(const Pos& tpos, const int trump) -> int { - return ((trump != DDS_NOTRUMP) && - (trump >= 0 && trump < DDS_SUITS) && - (tpos.winner[trump].rank != 0)) + return ((trump != DDS_NOTRUMP) && + (trump >= 0 && trump < DDS_SUITS) && + (tpos.winner[trump].rank != 0)) ? 1 : 0; } // Encode following-hand WeightCase: 4 * hand_rel + trump_winner + (void ? 2 : 0). auto weight_case_follow(const int hand_rel, const int trump_winner, - const bool is_void) -> WeightCase + const bool is_void) -> WeightCase { - return static_cast( - 4 * hand_rel + trump_winner + (is_void ? 2 : 0)); + return static_cast( + 4 * hand_rel + trump_winner + (is_void ? 2 : 0)); } } // namespace Moves::Moves() { - // Initialize non-owning pointers to nullptr for safety - trackp = nullptr; - mply = nullptr; - - // Scalars snapshotted by make_heuristic_context must not be indeterminate. - leadHand = 0; - currHand = 0; - leadSuit = 0; - currTrick = 0; - trump = DDS_NOTRUMP; - suit = 0; - numMoves = 0; - lastNumMoves = 0; - - funcName[static_cast(MgType::NT0)] = "NT0"; - funcName[static_cast(MgType::TRUMP0)] = "Trump0"; - funcName[static_cast(MgType::NT_VOID1)] = "NT_Void1"; - funcName[static_cast(MgType::TRUMP_VOID1)] = "Trump_Void1"; - funcName[static_cast(MgType::NT_NOTVOID1)] = "NT_Notvoid1"; - funcName[static_cast(MgType::TRUMP_NOTVOID1)] = "Trump_Notvoid1"; - funcName[static_cast(MgType::NT_VOID2)] = "NT_Void2"; - funcName[static_cast(MgType::TRUMP_VOID2)] = "Trump_Void2"; - funcName[static_cast(MgType::NT_NOTVOID2)] = "NT_Notvoid2"; - funcName[static_cast(MgType::TRUMP_NOTVOID2)] = "Trump_Notvoid2"; - funcName[static_cast(MgType::NT_VOID3)] = "NT_Void3"; - funcName[static_cast(MgType::TRUMP_VOID3)] = "Trump_Void3"; - funcName[static_cast(MgType::COMB_NOTVOID3)] = "Comb_Notvoid3"; - - for (int t = 0; t < 13; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - lastCall[t][h] = MgType::SIZE; - - moveList[t][h] = {}; - - trickTable[t][h].count = 0; - trickSuitTable[t][h].count = 0; - - trickDetailTable[t][h].nfuncs = 0; - trickDetailSuitTable[t][h].nfuncs = 0; - for (int i = 0; i < static_cast(MgType::SIZE); i++) { - trickDetailTable[t][h].list[i].count = 0; - trickDetailSuitTable[t][h].list[i].count = 0; - } + // Initialize non-owning pointers to nullptr for safety + trackp = nullptr; + mply = nullptr; + + // Scalars snapshotted by make_heuristic_context must not be indeterminate. + leadHand = 0; + currHand = 0; + leadSuit = 0; + currTrick = 0; + trump = DDS_NOTRUMP; + suit = 0; + numMoves = 0; + lastNumMoves = 0; + + funcName[static_cast(MgType::NT0)] = "NT0"; + funcName[static_cast(MgType::TRUMP0)] = "Trump0"; + funcName[static_cast(MgType::NT_VOID1)] = "NT_Void1"; + funcName[static_cast(MgType::TRUMP_VOID1)] = "Trump_Void1"; + funcName[static_cast(MgType::NT_NOTVOID1)] = "NT_Notvoid1"; + funcName[static_cast(MgType::TRUMP_NOTVOID1)] = "Trump_Notvoid1"; + funcName[static_cast(MgType::NT_VOID2)] = "NT_Void2"; + funcName[static_cast(MgType::TRUMP_VOID2)] = "Trump_Void2"; + funcName[static_cast(MgType::NT_NOTVOID2)] = "NT_Notvoid2"; + funcName[static_cast(MgType::TRUMP_NOTVOID2)] = "Trump_Notvoid2"; + funcName[static_cast(MgType::NT_VOID3)] = "NT_Void3"; + funcName[static_cast(MgType::TRUMP_VOID3)] = "Trump_Void3"; + funcName[static_cast(MgType::COMB_NOTVOID3)] = "Comb_Notvoid3"; + + for (int t = 0; t < 13; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + lastCall[t][h] = MgType::SIZE; + + moveList[t][h] = {}; + + trickTable[t][h].count = 0; + trickSuitTable[t][h].count = 0; + + trickDetailTable[t][h].nfuncs = 0; + trickDetailSuitTable[t][h].nfuncs = 0; + for (int i = 0; i < static_cast(MgType::SIZE); i++) { + trickDetailTable[t][h].list[i].count = 0; + trickDetailSuitTable[t][h].list[i].count = 0; + } + } + } + + trickFuncTable.nfuncs = 0; + trickFuncSuitTable.nfuncs = 0; + for (int i = 0; i < static_cast(MgType::SIZE); i++) { + trickFuncTable.list[i].count = 0; + trickFuncSuitTable.list[i].count = 0; } - } - - trickFuncTable.nfuncs = 0; - trickFuncSuitTable.nfuncs = 0; - for (int i = 0; i < static_cast(MgType::SIZE); i++) { - trickFuncTable.list[i].count = 0; - trickFuncSuitTable.list[i].count = 0; - } } Moves::~Moves() {} @@ -144,37 +144,37 @@ auto Moves::Init(const int tricks, const int relStartHand, const int initialRanks[], const int initialSuits[], const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], const int our_trump, const int our_lead_hand) -> void { - currTrick = tricks; - trump = our_trump; + currTrick = tricks; + trump = our_trump; - if (relStartHand == 0) - track[tricks].lead_hand = our_lead_hand; + if (relStartHand == 0) + track[tricks].lead_hand = our_lead_hand; - for (int m = 0; m < 13; m++) { - for (int h = 0; h < DDS_HANDS; h++) { - moveList[m][h].current = 0; - moveList[m][h].last = 0; + for (int m = 0; m < 13; m++) { + for (int h = 0; h < DDS_HANDS; h++) { + moveList[m][h].current = 0; + moveList[m][h].last = 0; + } } - } - // 0x1ffff would be enough, but this is for compatibility. - for (int s = 0; s < DDS_SUITS; s++) - track[tricks].removed_ranks[s] = 0xffff; - - for (int h = 0; h < DDS_HANDS; h++) + // 0x1ffff would be enough, but this is for compatibility. for (int s = 0; s < DDS_SUITS; s++) - track[tricks].removed_ranks[s] ^= rank_in_suit[h][s]; + track[tricks].removed_ranks[s] = 0xffff; + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + track[tricks].removed_ranks[s] ^= rank_in_suit[h][s]; - for (int n = 0; n < relStartHand; n++) { - int s = initialSuits[n]; - int r = initialRanks[n]; + for (int n = 0; n < relStartHand; n++) { + int s = initialSuits[n]; + int r = initialRanks[n]; - track[tricks].removed_ranks[s] ^= bit_map_rank[r]; - } + track[tricks].removed_ranks[s] ^= bit_map_rank[r]; + } } auto Moves::Reinit(const int tricks, const int ourLeadHand) -> void { - track[tricks].lead_hand = ourLeadHand; + track[tricks].lead_hand = ourLeadHand; } /** @@ -193,464 +193,464 @@ auto Moves::Reinit(const int tricks, const int ourLeadHand) -> void { auto Moves::MoveGen0(const int tricks, const Pos &tpos, const MoveType &bestMove, const MoveType &bestMoveTT, const RelRanksType thrp_rel[]) -> int { - trackp = &track[tricks]; - leadHand = trackp->lead_hand; - currHand = leadHand; - currTrick = tricks; - - const MoveGroupType *mp; - int removed, g, rank, seq; - - MovePlyType &list = moveList[tricks][0]; - mply = list.move; - for (int s = 0; s < DDS_SUITS; s++) - trackp->lowest_win[0][s] = 0; - // Reset fields snapshotted by make_heuristic_context before the hoist; - // suit/lastNumMoves are overwritten again before each call_heuristic. - numMoves = 0; - lastNumMoves = 0; - suit = 0; - leadSuit = 0; - - // Leading-hand weight case, known here once instead of re-derived per - // suit inside the heuristic dispatcher. - const WeightCase lead_case = trump_winner_bit(tpos, trump) + trackp = &track[tricks]; + leadHand = trackp->lead_hand; + currHand = leadHand; + currTrick = tricks; + + const MoveGroupType *mp; + int removed, g, rank, seq; + + MovePlyType &list = moveList[tricks][0]; + mply = list.move; + for (int s = 0; s < DDS_SUITS; s++) + trackp->lowest_win[0][s] = 0; + // Reset fields snapshotted by make_heuristic_context before the hoist; + // suit/lastNumMoves are overwritten again before each call_heuristic. + numMoves = 0; + lastNumMoves = 0; + suit = 0; + leadSuit = 0; + + // Leading-hand weight case, known here once instead of re-derived per + // suit inside the heuristic dispatcher. + const WeightCase lead_case = trump_winner_bit(tpos, trump) ? WeightCase::Trump0 : WeightCase::Nt0; - HeuristicContext hctx = - make_heuristic_context(tpos, bestMove, bestMoveTT, thrp_rel, + HeuristicContext hctx = + make_heuristic_context(tpos, bestMove, bestMoveTT, thrp_rel, track[tricks]); - for (suit = 0; suit < DDS_SUITS; suit++) { - unsigned short ris = tpos.rank_in_suit[leadHand][suit]; - if (ris == 0) - continue; - - lastNumMoves = numMoves; - mp = &group_data[ris]; - g = mp->last_group_; - removed = trackp->removed_ranks[suit]; - - // Generate moves for this suit by iterating through card groups. - // Merge consecutive groups when gaps are filled by removed cards. - while (g >= 0) { - rank = mp->rank_[g]; - seq = mp->sequence_[g]; - - // If all cards in the gap above this group have been played, - // merge this group with the one below it (equivalent plays). - while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) - seq |= mp->fullseq_[--g]; - - mply[numMoves].sequence = seq; - mply[numMoves].suit = suit; - mply[numMoves].rank = rank; - - numMoves++; - g--; + for (suit = 0; suit < DDS_SUITS; suit++) { + unsigned short ris = tpos.rank_in_suit[leadHand][suit]; + if (ris == 0) + continue; + + lastNumMoves = numMoves; + mp = &group_data[ris]; + g = mp->last_group_; + removed = trackp->removed_ranks[suit]; + + // Generate moves for this suit by iterating through card groups. + // Merge consecutive groups when gaps are filled by removed cards. + while (g >= 0) { + rank = mp->rank_[g]; + seq = mp->sequence_[g]; + + // If all cards in the gap above this group have been played, + // merge this group with the one below it (equivalent plays). + while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) + seq |= mp->fullseq_[--g]; + + mply[numMoves].sequence = seq; + mply[numMoves].suit = suit; + mply[numMoves].rank = rank; + + numMoves++; + g--; + } + + hctx.suit = suit; + hctx.last_num_moves = lastNumMoves; + hctx.num_moves = numMoves; + ::call_heuristic(hctx, lead_case); } - hctx.suit = suit; - hctx.last_num_moves = lastNumMoves; - hctx.num_moves = numMoves; - ::call_heuristic(hctx, lead_case); - } - #ifdef DDS_MOVES - if (lead_case == WeightCase::Trump0) - MG_REGISTER(MgType::TRUMP0, 0); - else - MG_REGISTER(MgType::NT0, 0); + if (lead_case == WeightCase::Trump0) + MG_REGISTER(MgType::TRUMP0, 0); + else + MG_REGISTER(MgType::NT0, 0); #endif - list.current = 0; - list.last = numMoves - 1; + list.current = 0; + list.last = numMoves - 1; - if (numMoves != 1) - Moves::MergeSort(); - return numMoves; + if (numMoves != 1) + Moves::MergeSort(); + return numMoves; } auto Moves::MoveGen123(const int tricks, const int handRel, const Pos &tpos) - -> int { - trackp = &track[tricks]; - leadHand = trackp->lead_hand; - currHand = HAND_ID(leadHand, handRel); - currTrick = tricks; - leadSuit = track[tricks].lead_suit; - - const MoveGroupType *mp; - int removed, g, rank, seq; - - MovePlyType &list = moveList[tricks][handRel]; - mply = list.move; - - for (int s = 0; s < DDS_SUITS; s++) - trackp->lowest_win[handRel][s] = 0; - // Reset fields snapshotted by make_heuristic_context before either hoist. - // Follow-suit uses suit == leadSuit; the void path overwrites suit per - // iteration before call_heuristic. - numMoves = 0; - lastNumMoves = 0; - suit = leadSuit; - - WeightCase weight_case; - const int trump_winner = trump_winner_bit(tpos, trump); - - // Empty best-move placeholders must outlive the hoisted context, which - // holds references to them. - const MoveType empty_move{}; - - unsigned short ris = tpos.rank_in_suit[currHand][leadSuit]; - - if (ris != 0) { - mp = &group_data[ris]; - g = mp->last_group_; - removed = trackp->removed_ranks[leadSuit]; - - while (g >= 0) { - rank = mp->rank_[g]; - seq = mp->sequence_[g]; - - while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) - seq |= mp->fullseq_[--g]; - - mply[numMoves].sequence = seq; - mply[numMoves].suit = leadSuit; - mply[numMoves].rank = rank; - - numMoves++; - g--; - } + -> int { + trackp = &track[tricks]; + leadHand = trackp->lead_hand; + currHand = HAND_ID(leadHand, handRel); + currTrick = tricks; + leadSuit = track[tricks].lead_suit; - weight_case = weight_case_follow(handRel, trump_winner, /*is_void=*/false); + const MoveGroupType *mp; + int removed, g, rank, seq; + + MovePlyType &list = moveList[tricks][handRel]; + mply = list.move; + + for (int s = 0; s < DDS_SUITS; s++) + trackp->lowest_win[handRel][s] = 0; + // Reset fields snapshotted by make_heuristic_context before either hoist. + // Follow-suit uses suit == leadSuit; the void path overwrites suit per + // iteration before call_heuristic. + numMoves = 0; + lastNumMoves = 0; + suit = leadSuit; + + WeightCase weight_case; + const int trump_winner = trump_winner_bit(tpos, trump); + + // Empty best-move placeholders must outlive the hoisted context, which + // holds references to them. + const MoveType empty_move{}; + + unsigned short ris = tpos.rank_in_suit[currHand][leadSuit]; + + if (ris != 0) { + mp = &group_data[ris]; + g = mp->last_group_; + removed = trackp->removed_ranks[leadSuit]; + + while (g >= 0) { + rank = mp->rank_[g]; + seq = mp->sequence_[g]; + + while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) + seq |= mp->fullseq_[--g]; + + mply[numMoves].sequence = seq; + mply[numMoves].suit = leadSuit; + mply[numMoves].rank = rank; + + numMoves++; + g--; + } + + weight_case = weight_case_follow(handRel, trump_winner, /*is_void=*/false); #ifdef DDS_MOVES - MG_REGISTER(RegisterList[static_cast(weight_case)], handRel); + MG_REGISTER(RegisterList[static_cast(weight_case)], handRel); #endif - list.current = 0; - list.last = numMoves - 1; - if (numMoves == 1) - return numMoves; + list.current = 0; + list.last = numMoves - 1; + if (numMoves == 1) + return numMoves; - HeuristicContext hctx = - make_heuristic_context(tpos, empty_move, empty_move, nullptr, + HeuristicContext hctx = + make_heuristic_context(tpos, empty_move, empty_move, nullptr, track[tricks]); - ::call_heuristic(hctx, weight_case); + ::call_heuristic(hctx, weight_case); - Moves::MergeSort(); - return numMoves; - } + Moves::MergeSort(); + return numMoves; + } - weight_case = weight_case_follow(handRel, trump_winner, /*is_void=*/true); + weight_case = weight_case_follow(handRel, trump_winner, /*is_void=*/true); #ifdef DDS_MOVES - MG_REGISTER(RegisterList[static_cast(weight_case)], handRel); + MG_REGISTER(RegisterList[static_cast(weight_case)], handRel); #endif - HeuristicContext hctx = - make_heuristic_context(tpos, empty_move, empty_move, nullptr, + HeuristicContext hctx = + make_heuristic_context(tpos, empty_move, empty_move, nullptr, track[tricks]); - for (suit = 0; suit < DDS_SUITS; suit++) { - ris = tpos.rank_in_suit[currHand][suit]; - if (ris == 0) - continue; + for (suit = 0; suit < DDS_SUITS; suit++) { + ris = tpos.rank_in_suit[currHand][suit]; + if (ris == 0) + continue; - lastNumMoves = numMoves; - mp = &group_data[ris]; - g = mp->last_group_; - removed = trackp->removed_ranks[suit]; + lastNumMoves = numMoves; + mp = &group_data[ris]; + g = mp->last_group_; + removed = trackp->removed_ranks[suit]; - while (g >= 0) { - rank = mp->rank_[g]; - seq = mp->sequence_[g]; + while (g >= 0) { + rank = mp->rank_[g]; + seq = mp->sequence_[g]; - while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) - seq |= mp->fullseq_[--g]; + while (g >= 1 && ((mp->gap_[g] & removed) == mp->gap_[g])) + seq |= mp->fullseq_[--g]; - mply[numMoves].sequence = seq; - mply[numMoves].suit = suit; - mply[numMoves].rank = rank; + mply[numMoves].sequence = seq; + mply[numMoves].suit = suit; + mply[numMoves].rank = rank; - numMoves++; - g--; - } + numMoves++; + g--; + } - hctx.suit = suit; - hctx.last_num_moves = lastNumMoves; - hctx.num_moves = numMoves; - ::call_heuristic(hctx, weight_case); - } + hctx.suit = suit; + hctx.last_num_moves = lastNumMoves; + hctx.num_moves = numMoves; + ::call_heuristic(hctx, weight_case); + } - list.current = 0; - list.last = numMoves - 1; - if (numMoves != 1) - Moves::MergeSort(); - return numMoves; + list.current = 0; + list.last = numMoves - 1; + if (numMoves != 1) + Moves::MergeSort(); + return numMoves; } auto Moves::GetTopNumber(const int ris, const int prank, int &topNumber, int &mno) const -> void { - // Determine how many winning moves exist when partner has played prank. - // topNumber indicates the number of cards that can win, mno is the index - // in the move list of the lowest winning card. - topNumber = -10; + // Determine how many winning moves exist when partner has played prank. + // topNumber indicates the number of cards that can win, mno is the index + // in the move list of the lowest winning card. + topNumber = -10; - // Find the lowest move that still overtakes partner's card. - mno = 0; - while (mno < numMoves - 1 && mply[1 + mno].rank > prank) - mno++; + // Find the lowest move that still overtakes partner's card. + mno = 0; + while (mno < numMoves - 1 && mply[1 + mno].rank > prank) + mno++; - const MoveGroupType &mp = group_data[ris]; - int g = mp.last_group_; + const MoveGroupType &mp = group_data[ris]; + int g = mp.last_group_; - // Include partner's card as removed to count only moves that beat it. - const int removed = - static_cast(trackp->removed_ranks[leadSuit] | bit_map_rank[prank]); + // Include partner's card as removed to count only moves that beat it. + const int removed = + static_cast(trackp->removed_ranks[leadSuit] | bit_map_rank[prank]); - int fullseq = mp.fullseq_[g]; + int fullseq = mp.fullseq_[g]; - // Merge groups as in move generation, accounting for gaps filled - // by removed cards (including partner's card). - while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) - fullseq |= mp.fullseq_[--g]; + // Merge groups as in move generation, accounting for gaps filled + // by removed cards (including partner's card). + while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) + fullseq |= mp.fullseq_[--g]; - topNumber = count_table[fullseq] - 1; + topNumber = count_table[fullseq] - 1; } inline auto Moves::WinningMove(const MoveType &mvp1, const ExtCard &mvp2, const int ourTrump) const -> bool { - /* Return true if move 1 wins over move 2, with the assumption that - move 2 is the presently winning card of the trick */ - - if (mvp1.suit == mvp2.suit) { - if (mvp1.rank > mvp2.rank) - return true; + /* Return true if move 1 wins over move 2, with the assumption that + move 2 is the presently winning card of the trick */ + + if (mvp1.suit == mvp2.suit) { + if (mvp1.rank > mvp2.rank) + return true; + else + return false; + } else if (mvp1.suit == ourTrump) + return true; else - return false; - } else if (mvp1.suit == ourTrump) - return true; - else - return false; + return false; } auto Moves::GetLength(const int trick, const int relHand) const -> int { - return moveList[trick][relHand].last + 1; + return moveList[trick][relHand].last + 1; } auto Moves::apply_move_to_track(const MoveType &move, const int relHand, - const int trick) -> void { - assert(trick >= 0 && trick < 13 && "apply_move_to_track: trick out of range"); - assert(relHand >= 0 && relHand < DDS_HANDS); - if (relHand == 3) - assert(trick > 0 && "apply_move_to_track: trick must be > 0 when relHand==3"); - trackp = &track[trick]; - if (relHand == 0) { - trackp->move[0].suit = move.suit; - trackp->move[0].rank = move.rank; - trackp->move[0].sequence = move.sequence; - trackp->high[0] = 0; - trackp->lead_suit = move.suit; - } else if (move.suit == trackp->move[relHand - 1].suit) { - if (move.rank > trackp->move[relHand - 1].rank) { - trackp->move[relHand].suit = move.suit; - trackp->move[relHand].rank = move.rank; - trackp->move[relHand].sequence = move.sequence; - trackp->high[relHand] = relHand; + const int trick) -> void { + assert(trick >= 0 && trick < 13 && "apply_move_to_track: trick out of range"); + assert(relHand >= 0 && relHand < DDS_HANDS); + if (relHand == 3) + assert(trick > 0 && "apply_move_to_track: trick must be > 0 when relHand==3"); + trackp = &track[trick]; + if (relHand == 0) { + trackp->move[0].suit = move.suit; + trackp->move[0].rank = move.rank; + trackp->move[0].sequence = move.sequence; + trackp->high[0] = 0; + trackp->lead_suit = move.suit; + } else if (move.suit == trackp->move[relHand - 1].suit) { + if (move.rank > trackp->move[relHand - 1].rank) { + trackp->move[relHand].suit = move.suit; + trackp->move[relHand].rank = move.rank; + trackp->move[relHand].sequence = move.sequence; + trackp->high[relHand] = relHand; + } else { + trackp->move[relHand] = trackp->move[relHand - 1]; + trackp->high[relHand] = trackp->high[relHand - 1]; + } + } else if (move.suit == trump) { + trackp->move[relHand].suit = move.suit; + trackp->move[relHand].rank = move.rank; + trackp->move[relHand].sequence = move.sequence; + trackp->high[relHand] = relHand; } else { - trackp->move[relHand] = trackp->move[relHand - 1]; - trackp->high[relHand] = trackp->high[relHand - 1]; + trackp->move[relHand] = trackp->move[relHand - 1]; + trackp->high[relHand] = trackp->high[relHand - 1]; } - } else if (move.suit == trump) { - trackp->move[relHand].suit = move.suit; - trackp->move[relHand].rank = move.rank; - trackp->move[relHand].sequence = move.sequence; - trackp->high[relHand] = relHand; - } else { - trackp->move[relHand] = trackp->move[relHand - 1]; - trackp->high[relHand] = trackp->high[relHand - 1]; - } - trackp->play_suits[relHand] = move.suit; - trackp->play_ranks[relHand] = move.rank; - if (relHand == 3) { - TrackType &newt = track[trick - 1]; - newt.lead_hand = (trackp->lead_hand + trackp->high[3]) % 4; - int r, s; - for (s = 0; s < DDS_SUITS; s++) - newt.removed_ranks[s] = trackp->removed_ranks[s]; - for (int h = 0; h < DDS_HANDS; h++) { - r = trackp->play_ranks[h]; - s = trackp->play_suits[h]; - newt.removed_ranks[s] |= bit_map_rank[r]; + trackp->play_suits[relHand] = move.suit; + trackp->play_ranks[relHand] = move.rank; + if (relHand == 3) { + TrackType &newt = track[trick - 1]; + newt.lead_hand = (trackp->lead_hand + trackp->high[3]) % 4; + int r, s; + for (s = 0; s < DDS_SUITS; s++) + newt.removed_ranks[s] = trackp->removed_ranks[s]; + for (int h = 0; h < DDS_HANDS; h++) { + r = trackp->play_ranks[h]; + s = trackp->play_suits[h]; + newt.removed_ranks[s] |= bit_map_rank[r]; + } } - } } auto Moves::MakeSpecific(const MoveType &ourMply, const int trick, - const int relHand) -> void { - apply_move_to_track(ourMply, relHand, trick); + const int relHand) -> void { + apply_move_to_track(ourMply, relHand, trick); } auto Moves::MakeNext(const int trick, const int relHand, - const unsigned short ourWinRanks[DDS_SUITS]) - -> MoveType const * { - int *lwp = track[trick].lowest_win[relHand]; - MovePlyType &list = moveList[trick][relHand]; - - MoveType *currp = nullptr, *prevp; - - bool found = false; - if (list.last == -1) - return nullptr; - else if (list.current == 0) { - currp = &list.move[0]; - found = true; - } else { - prevp = &list.move[list.current - 1]; - if (lwp[prevp->suit] == 0) { - int low = lowest_rank[ourWinRanks[prevp->suit]]; - if (low == 0) - low = 15; - if (prevp->rank < low) - lwp[prevp->suit] = low; - } - - while (list.current <= list.last && !found) { - currp = &list.move[list.current]; - if (currp->rank >= lwp[currp->suit]) + const unsigned short ourWinRanks[DDS_SUITS]) + -> MoveType const * { + int *lwp = track[trick].lowest_win[relHand]; + MovePlyType &list = moveList[trick][relHand]; + + MoveType *currp = nullptr, *prevp; + + bool found = false; + if (list.last == -1) + return nullptr; + else if (list.current == 0) { + currp = &list.move[0]; found = true; - else - list.current++; + } else { + prevp = &list.move[list.current - 1]; + if (lwp[prevp->suit] == 0) { + int low = lowest_rank[ourWinRanks[prevp->suit]]; + if (low == 0) + low = 15; + if (prevp->rank < low) + lwp[prevp->suit] = low; + } + + while (list.current <= list.last && !found) { + currp = &list.move[list.current]; + if (currp->rank >= lwp[currp->suit]) + found = true; + else + list.current++; + } + + if (!found) + return nullptr; } - if (!found) - return nullptr; - } - - apply_move_to_track(*currp, relHand, trick); + apply_move_to_track(*currp, relHand, trick); - list.current++; - return currp; + list.current++; + return currp; } auto Moves::MakeNextSimple(const int trick, const int relHand) - -> MoveType const * { - MovePlyType &list = moveList[trick][relHand]; - if (list.current > list.last) - return nullptr; + -> MoveType const * { + MovePlyType &list = moveList[trick][relHand]; + if (list.current > list.last) + return nullptr; - const MoveType &curr = list.move[list.current]; + const MoveType &curr = list.move[list.current]; - apply_move_to_track(curr, relHand, trick); + apply_move_to_track(curr, relHand, trick); - list.current++; - return &curr; + list.current++; + return &curr; } auto Moves::Step(const int tricks, const int relHand) -> void { - moveList[tricks][relHand].current++; + moveList[tricks][relHand].current++; } auto Moves::Rewind(const int tricks, const int relHand) -> void { - moveList[tricks][relHand].current = 0; + moveList[tricks][relHand].current = 0; } auto Moves::Purge(const int trick, const int ourLeadHand, - const MoveType forbiddenMoves[]) -> void { - MovePlyType &ourMply = moveList[trick][ourLeadHand]; - - for (int k = 1; k <= 13; k++) { - int s = forbiddenMoves[k].suit; - int rank = forbiddenMoves[k].rank; - if (rank == 0) - continue; - - for (int r = 0; r <= ourMply.last; r++) { - if (s == ourMply.move[r].suit && rank == ourMply.move[r].rank) { - /* For the forbidden move r: */ - for (int n = r; n <= ourMply.last; n++) - ourMply.move[n] = ourMply.move[n + 1]; - ourMply.last--; - } + const MoveType forbiddenMoves[]) -> void { + MovePlyType &ourMply = moveList[trick][ourLeadHand]; + + for (int k = 1; k <= 13; k++) { + int s = forbiddenMoves[k].suit; + int rank = forbiddenMoves[k].rank; + if (rank == 0) + continue; + + for (int r = 0; r <= ourMply.last; r++) { + if (s == ourMply.move[r].suit && rank == ourMply.move[r].rank) { + /* For the forbidden move r: */ + for (int n = r; n <= ourMply.last; n++) + ourMply.move[n] = ourMply.move[n + 1]; + ourMply.last--; + } + } } - } } auto Moves::Reward(const int tricks, const int relHand) -> void { - moveList[tricks][relHand] - .move[moveList[tricks][relHand].current - 1] - .weight += 100; + moveList[tricks][relHand] + .move[moveList[tricks][relHand].current - 1] + .weight += 100; } auto Moves::GetTrickData(const int tricks) -> const TrickDataType & { - TrickDataType &data = track[tricks].trick_data; - for (int s = 0; s < DDS_SUITS; s++) - data.play_count[s] = 0; - for (int relh = 0; relh < DDS_HANDS; relh++) - data.play_count[trackp->play_suits[relh]]++; + TrickDataType &data = track[tricks].trick_data; + for (int s = 0; s < DDS_SUITS; s++) + data.play_count[s] = 0; + for (int relh = 0; relh < DDS_HANDS; relh++) + data.play_count[trackp->play_suits[relh]]++; #ifndef NDEBUG - int sum = 0; - for (int s = 0; s < DDS_SUITS; s++) - sum += data.play_count[s]; + int sum = 0; + for (int s = 0; s < DDS_SUITS; s++) + sum += data.play_count[s]; - // Internal invariant: exactly 4 cards must be played per trick - assert(sum == 4 && "GetTrickData: play_count sum must equal 4"); + // Internal invariant: exactly 4 cards must be played per trick + assert(sum == 4 && "GetTrickData: play_count sum must equal 4"); #endif - data.best_rank = trackp->move[3].rank; - data.best_suit = trackp->move[3].suit; - data.best_sequence = trackp->move[3].sequence; - data.rel_winner = trackp->high[3]; - return data; + data.best_rank = trackp->move[3].rank; + data.best_suit = trackp->move[3].suit; + data.best_sequence = trackp->move[3].sequence; + data.rel_winner = trackp->high[3]; + return data; } auto Moves::Sort(const int tricks, const int relHand) -> void { - numMoves = moveList[tricks][relHand].last + 1; - mply = moveList[tricks][relHand].move; - Moves::MergeSort(); + numMoves = moveList[tricks][relHand].last + 1; + mply = moveList[tricks][relHand].move; + Moves::MergeSort(); } #define CMP_SWAP(i, j) \ - if (mply[i].weight < mply[j].weight) { \ - tmp = mply[i]; \ - mply[i] = mply[j]; \ - mply[j] = tmp; \ - } + if (mply[i].weight < mply[j].weight) { \ + tmp = mply[i]; \ + mply[i] = mply[j]; \ + mply[j] = tmp; \ + } auto Moves::make_heuristic_context(const Pos &tpos, const MoveType &best_move, const MoveType &best_move_tt, const RelRanksType thrp_rel[], const TrackType &tr) const - -> HeuristicContext { - HeuristicContext context{ - tpos, best_move, best_move_tt, thrp_rel, mply, numMoves, lastNumMoves, - trump, suit, &tr, currTrick, currHand, leadHand, leadSuit}; - - // Snapshot removed_ranks and only those trick-card fields that are defined - // for the current relative hand. Earlier slots may be unpopulated (MoveGen0 - // / hand_rel 1–2), so leave the rest at HeuristicContext's zero defaults. - for (int s = 0; s < DDS_SUITS; ++s) - context.removed_ranks[s] = tr.removed_ranks[s]; - - const int hand_rel = - (currHand == leadHand) ? 0 : (currHand + 4 - leadHand) % 4; - if (hand_rel >= 1) - context.lead0_rank = tr.move[0].rank; - if (hand_rel >= 2) - { - context.move1_rank = tr.move[1].rank; - context.move1_suit = tr.move[1].suit; - context.high1 = tr.high[1]; - } - if (hand_rel >= 3) - { - context.move2_rank = tr.move[2].rank; - context.move2_suit = tr.move[2].suit; - context.high2 = tr.high[2]; - } - return context; + -> HeuristicContext { + HeuristicContext context{ + tpos, best_move, best_move_tt, thrp_rel, mply, numMoves, lastNumMoves, + trump, suit, &tr, currTrick, currHand, leadHand, leadSuit}; + + // Snapshot removed_ranks and only those trick-card fields that are defined + // for the current relative hand. Earlier slots may be unpopulated (MoveGen0 + // / hand_rel 1–2), so leave the rest at HeuristicContext's zero defaults. + for (int s = 0; s < DDS_SUITS; ++s) + context.removed_ranks[s] = tr.removed_ranks[s]; + + const int hand_rel = + (currHand == leadHand) ? 0 : (currHand + 4 - leadHand) % 4; + if (hand_rel >= 1) + context.lead0_rank = tr.move[0].rank; + if (hand_rel >= 2) + { + context.move1_rank = tr.move[1].rank; + context.move1_suit = tr.move[1].suit; + context.high1 = tr.high[1]; + } + if (hand_rel >= 3) + { + context.move2_rank = tr.move[2].rank; + context.move2_suit = tr.move[2].suit; + context.high2 = tr.high[2]; + } + return context; } /** @@ -660,281 +660,281 @@ auto Moves::make_heuristic_context(const Pos &tpos, const MoveType &best_move, * insertion sort for other sizes. */ auto Moves::MergeSort() -> void { - const int len = numMoves; - MoveType tmp; - - switch (len) { - case 12: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(4, 5); - CMP_SWAP(6, 7); - CMP_SWAP(8, 9); - CMP_SWAP(10, 11); - - CMP_SWAP(1, 3); - CMP_SWAP(5, 7); - CMP_SWAP(9, 11); - - CMP_SWAP(0, 2); - CMP_SWAP(4, 6); - CMP_SWAP(8, 10); - - CMP_SWAP(1, 2); - CMP_SWAP(5, 6); - CMP_SWAP(9, 10); - - CMP_SWAP(1, 5); - CMP_SWAP(6, 10); - CMP_SWAP(5, 9); - CMP_SWAP(2, 6); - CMP_SWAP(1, 5); - CMP_SWAP(6, 10); - CMP_SWAP(0, 4); - CMP_SWAP(7, 11); - CMP_SWAP(3, 7); - CMP_SWAP(4, 8); - CMP_SWAP(0, 4); - CMP_SWAP(7, 11); - CMP_SWAP(1, 4); - CMP_SWAP(7, 10); - CMP_SWAP(3, 8); - CMP_SWAP(2, 3); - CMP_SWAP(8, 9); - CMP_SWAP(2, 4); - CMP_SWAP(7, 9); - CMP_SWAP(3, 5); - CMP_SWAP(6, 8); - CMP_SWAP(3, 4); - CMP_SWAP(5, 6); - CMP_SWAP(7, 8); - break; - case 11: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(4, 5); - CMP_SWAP(6, 7); - CMP_SWAP(8, 9); - - CMP_SWAP(1, 3); - CMP_SWAP(5, 7); - CMP_SWAP(0, 2); - CMP_SWAP(4, 6); - CMP_SWAP(8, 10); - CMP_SWAP(1, 2); - CMP_SWAP(5, 6); - CMP_SWAP(9, 10); - CMP_SWAP(1, 5); - CMP_SWAP(6, 10); - CMP_SWAP(5, 9); - CMP_SWAP(2, 6); - CMP_SWAP(1, 5); - CMP_SWAP(6, 10); - CMP_SWAP(0, 4); - CMP_SWAP(3, 7); - CMP_SWAP(4, 8); - CMP_SWAP(0, 4); - CMP_SWAP(1, 4); - CMP_SWAP(7, 10); - CMP_SWAP(3, 8); - CMP_SWAP(2, 3); - CMP_SWAP(8, 9); - CMP_SWAP(2, 4); - CMP_SWAP(7, 9); - CMP_SWAP(3, 5); - CMP_SWAP(6, 8); - CMP_SWAP(3, 4); - CMP_SWAP(5, 6); - CMP_SWAP(7, 8); - break; - case 10: - CMP_SWAP(1, 8); - CMP_SWAP(0, 4); - CMP_SWAP(5, 9); - CMP_SWAP(2, 6); - CMP_SWAP(3, 7); - CMP_SWAP(0, 3); - CMP_SWAP(6, 9); - CMP_SWAP(2, 5); - CMP_SWAP(0, 1); - CMP_SWAP(3, 6); - CMP_SWAP(8, 9); - CMP_SWAP(4, 7); - CMP_SWAP(0, 2); - CMP_SWAP(4, 8); - CMP_SWAP(1, 5); - CMP_SWAP(7, 9); - - CMP_SWAP(1, 2); - CMP_SWAP(3, 4); - CMP_SWAP(5, 6); - CMP_SWAP(7, 8); - - CMP_SWAP(1, 3); - CMP_SWAP(6, 8); - CMP_SWAP(2, 4); - CMP_SWAP(5, 7); - CMP_SWAP(2, 3); - CMP_SWAP(6, 7); - CMP_SWAP(3, 5); - CMP_SWAP(4, 6); - CMP_SWAP(4, 5); - break; - case 9: - CMP_SWAP(0, 1); - CMP_SWAP(3, 4); - CMP_SWAP(6, 7); - CMP_SWAP(1, 2); - CMP_SWAP(4, 5); - CMP_SWAP(7, 8); - CMP_SWAP(0, 1); - CMP_SWAP(3, 4); - CMP_SWAP(6, 7); - CMP_SWAP(0, 3); - CMP_SWAP(3, 6); - CMP_SWAP(0, 3); - CMP_SWAP(1, 4); - CMP_SWAP(4, 7); - CMP_SWAP(1, 4); - CMP_SWAP(2, 5); - CMP_SWAP(5, 8); - CMP_SWAP(2, 5); - CMP_SWAP(1, 3); - CMP_SWAP(5, 7); - CMP_SWAP(2, 6); - CMP_SWAP(4, 6); - CMP_SWAP(2, 4); - CMP_SWAP(2, 3); - CMP_SWAP(5, 6); - break; - case 8: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(4, 5); - CMP_SWAP(6, 7); - - CMP_SWAP(0, 2); - CMP_SWAP(4, 6); - CMP_SWAP(1, 3); - CMP_SWAP(5, 7); - - CMP_SWAP(1, 2); - CMP_SWAP(5, 6); - CMP_SWAP(0, 4); - CMP_SWAP(1, 5); - - CMP_SWAP(2, 6); - CMP_SWAP(3, 7); - CMP_SWAP(2, 4); - CMP_SWAP(3, 5); - - CMP_SWAP(1, 2); - CMP_SWAP(3, 4); - CMP_SWAP(5, 6); - break; - case 7: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(4, 5); - CMP_SWAP(0, 2); - CMP_SWAP(4, 6); - CMP_SWAP(1, 3); - CMP_SWAP(1, 2); - CMP_SWAP(5, 6); - CMP_SWAP(0, 4); - CMP_SWAP(1, 5); - CMP_SWAP(2, 6); - CMP_SWAP(2, 4); - CMP_SWAP(3, 5); - CMP_SWAP(1, 2); - CMP_SWAP(3, 4); - CMP_SWAP(5, 6); - break; - case 6: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(4, 5); - CMP_SWAP(0, 2); - CMP_SWAP(1, 3); - CMP_SWAP(1, 2); - CMP_SWAP(0, 4); - CMP_SWAP(1, 5); - CMP_SWAP(2, 4); - CMP_SWAP(3, 5); - CMP_SWAP(1, 2); - CMP_SWAP(3, 4); - break; - case 5: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(0, 2); - CMP_SWAP(1, 3); - CMP_SWAP(1, 2); - CMP_SWAP(0, 4); - CMP_SWAP(2, 4); - CMP_SWAP(1, 2); - CMP_SWAP(3, 4); - break; - case 4: - CMP_SWAP(0, 1); - CMP_SWAP(2, 3); - CMP_SWAP(0, 2); - CMP_SWAP(1, 3); - CMP_SWAP(1, 2); - break; - case 3: - CMP_SWAP(0, 1); - CMP_SWAP(0, 2); - CMP_SWAP(1, 2); - break; - case 2: - CMP_SWAP(0, 1); - break; - default: { - for (int i = 1; i < len; i++) { - tmp = mply[i]; - int j = i; - for (; j && tmp.weight > mply[j - 1].weight; --j) - mply[j] = mply[j - 1]; - mply[j] = tmp; + const int len = numMoves; + MoveType tmp; + + switch (len) { + case 12: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(4, 5); + CMP_SWAP(6, 7); + CMP_SWAP(8, 9); + CMP_SWAP(10, 11); + + CMP_SWAP(1, 3); + CMP_SWAP(5, 7); + CMP_SWAP(9, 11); + + CMP_SWAP(0, 2); + CMP_SWAP(4, 6); + CMP_SWAP(8, 10); + + CMP_SWAP(1, 2); + CMP_SWAP(5, 6); + CMP_SWAP(9, 10); + + CMP_SWAP(1, 5); + CMP_SWAP(6, 10); + CMP_SWAP(5, 9); + CMP_SWAP(2, 6); + CMP_SWAP(1, 5); + CMP_SWAP(6, 10); + CMP_SWAP(0, 4); + CMP_SWAP(7, 11); + CMP_SWAP(3, 7); + CMP_SWAP(4, 8); + CMP_SWAP(0, 4); + CMP_SWAP(7, 11); + CMP_SWAP(1, 4); + CMP_SWAP(7, 10); + CMP_SWAP(3, 8); + CMP_SWAP(2, 3); + CMP_SWAP(8, 9); + CMP_SWAP(2, 4); + CMP_SWAP(7, 9); + CMP_SWAP(3, 5); + CMP_SWAP(6, 8); + CMP_SWAP(3, 4); + CMP_SWAP(5, 6); + CMP_SWAP(7, 8); + break; + case 11: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(4, 5); + CMP_SWAP(6, 7); + CMP_SWAP(8, 9); + + CMP_SWAP(1, 3); + CMP_SWAP(5, 7); + CMP_SWAP(0, 2); + CMP_SWAP(4, 6); + CMP_SWAP(8, 10); + CMP_SWAP(1, 2); + CMP_SWAP(5, 6); + CMP_SWAP(9, 10); + CMP_SWAP(1, 5); + CMP_SWAP(6, 10); + CMP_SWAP(5, 9); + CMP_SWAP(2, 6); + CMP_SWAP(1, 5); + CMP_SWAP(6, 10); + CMP_SWAP(0, 4); + CMP_SWAP(3, 7); + CMP_SWAP(4, 8); + CMP_SWAP(0, 4); + CMP_SWAP(1, 4); + CMP_SWAP(7, 10); + CMP_SWAP(3, 8); + CMP_SWAP(2, 3); + CMP_SWAP(8, 9); + CMP_SWAP(2, 4); + CMP_SWAP(7, 9); + CMP_SWAP(3, 5); + CMP_SWAP(6, 8); + CMP_SWAP(3, 4); + CMP_SWAP(5, 6); + CMP_SWAP(7, 8); + break; + case 10: + CMP_SWAP(1, 8); + CMP_SWAP(0, 4); + CMP_SWAP(5, 9); + CMP_SWAP(2, 6); + CMP_SWAP(3, 7); + CMP_SWAP(0, 3); + CMP_SWAP(6, 9); + CMP_SWAP(2, 5); + CMP_SWAP(0, 1); + CMP_SWAP(3, 6); + CMP_SWAP(8, 9); + CMP_SWAP(4, 7); + CMP_SWAP(0, 2); + CMP_SWAP(4, 8); + CMP_SWAP(1, 5); + CMP_SWAP(7, 9); + + CMP_SWAP(1, 2); + CMP_SWAP(3, 4); + CMP_SWAP(5, 6); + CMP_SWAP(7, 8); + + CMP_SWAP(1, 3); + CMP_SWAP(6, 8); + CMP_SWAP(2, 4); + CMP_SWAP(5, 7); + CMP_SWAP(2, 3); + CMP_SWAP(6, 7); + CMP_SWAP(3, 5); + CMP_SWAP(4, 6); + CMP_SWAP(4, 5); + break; + case 9: + CMP_SWAP(0, 1); + CMP_SWAP(3, 4); + CMP_SWAP(6, 7); + CMP_SWAP(1, 2); + CMP_SWAP(4, 5); + CMP_SWAP(7, 8); + CMP_SWAP(0, 1); + CMP_SWAP(3, 4); + CMP_SWAP(6, 7); + CMP_SWAP(0, 3); + CMP_SWAP(3, 6); + CMP_SWAP(0, 3); + CMP_SWAP(1, 4); + CMP_SWAP(4, 7); + CMP_SWAP(1, 4); + CMP_SWAP(2, 5); + CMP_SWAP(5, 8); + CMP_SWAP(2, 5); + CMP_SWAP(1, 3); + CMP_SWAP(5, 7); + CMP_SWAP(2, 6); + CMP_SWAP(4, 6); + CMP_SWAP(2, 4); + CMP_SWAP(2, 3); + CMP_SWAP(5, 6); + break; + case 8: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(4, 5); + CMP_SWAP(6, 7); + + CMP_SWAP(0, 2); + CMP_SWAP(4, 6); + CMP_SWAP(1, 3); + CMP_SWAP(5, 7); + + CMP_SWAP(1, 2); + CMP_SWAP(5, 6); + CMP_SWAP(0, 4); + CMP_SWAP(1, 5); + + CMP_SWAP(2, 6); + CMP_SWAP(3, 7); + CMP_SWAP(2, 4); + CMP_SWAP(3, 5); + + CMP_SWAP(1, 2); + CMP_SWAP(3, 4); + CMP_SWAP(5, 6); + break; + case 7: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(4, 5); + CMP_SWAP(0, 2); + CMP_SWAP(4, 6); + CMP_SWAP(1, 3); + CMP_SWAP(1, 2); + CMP_SWAP(5, 6); + CMP_SWAP(0, 4); + CMP_SWAP(1, 5); + CMP_SWAP(2, 6); + CMP_SWAP(2, 4); + CMP_SWAP(3, 5); + CMP_SWAP(1, 2); + CMP_SWAP(3, 4); + CMP_SWAP(5, 6); + break; + case 6: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(4, 5); + CMP_SWAP(0, 2); + CMP_SWAP(1, 3); + CMP_SWAP(1, 2); + CMP_SWAP(0, 4); + CMP_SWAP(1, 5); + CMP_SWAP(2, 4); + CMP_SWAP(3, 5); + CMP_SWAP(1, 2); + CMP_SWAP(3, 4); + break; + case 5: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(0, 2); + CMP_SWAP(1, 3); + CMP_SWAP(1, 2); + CMP_SWAP(0, 4); + CMP_SWAP(2, 4); + CMP_SWAP(1, 2); + CMP_SWAP(3, 4); + break; + case 4: + CMP_SWAP(0, 1); + CMP_SWAP(2, 3); + CMP_SWAP(0, 2); + CMP_SWAP(1, 3); + CMP_SWAP(1, 2); + break; + case 3: + CMP_SWAP(0, 1); + CMP_SWAP(0, 2); + CMP_SWAP(1, 2); + break; + case 2: + CMP_SWAP(0, 1); + break; + default: { + for (int i = 1; i < len; i++) { + tmp = mply[i]; + int j = i; + for (; j && tmp.weight > mply[j - 1].weight; --j) + mply[j] = mply[j - 1]; + mply[j] = tmp; + } } - } - } - return; + } + return; } auto Moves::PrintMove(const MovePlyType &ourMply) const -> string { - stringstream ss; + stringstream ss; - ss << "current " << ourMply.current << ", last " << ourMply.last << "\n"; - ss << " i suit sequence rank wgt\n"; - for (int i = 0; i <= ourMply.last; i++) { - ss << setw(2) << right << i << setw(3) << card_suit[ourMply.move[i].suit] + ss << "current " << ourMply.current << ", last " << ourMply.last << "\n"; + ss << " i suit sequence rank wgt\n"; + for (int i = 0; i <= ourMply.last; i++) { + ss << setw(2) << right << i << setw(3) << card_suit[ourMply.move[i].suit] << setw(9) << hex << ourMply.move[i].sequence << setw(3) << card_rank[ourMply.move[i].rank] << setw(3) << ourMply.move[i].weight << "\n"; - } - return ss.str(); + } + return ss.str(); } auto Moves::PrintMoves(const int trick, const int relHand) const -> string { - const MovePlyType &list = moveList[trick][relHand]; + const MovePlyType &list = moveList[trick][relHand]; - const string st = "trick " + to_string(trick) + " relHand " + - to_string(relHand) + " last " + to_string(list.last) + - " current " + to_string(list.current) + "\n"; + const string st = "trick " + to_string(trick) + " relHand " + + to_string(relHand) + " last " + to_string(list.last) + + " current " + to_string(list.current) + "\n"; - return st + Moves::PrintMove(list); + return st + Moves::PrintMove(list); } auto Moves::TrickToText(const int trick) const -> string { - const MovePlyType &listp0 = moveList[trick][0]; - const MovePlyType &listp1 = moveList[trick][1]; - const MovePlyType &listp2 = moveList[trick][2]; - const MovePlyType &listp3 = moveList[trick][3]; + const MovePlyType &listp0 = moveList[trick][0]; + const MovePlyType &listp1 = moveList[trick][1]; + const MovePlyType &listp2 = moveList[trick][2]; + const MovePlyType &listp3 = moveList[trick][3]; - stringstream ss; - ss << setw(16) << left << "Last trick" << card_hand[track[trick].lead_hand] + stringstream ss; + ss << setw(16) << left << "Last trick" << card_hand[track[trick].lead_hand] << ": " << card_suit[listp0.move[listp0.current].suit] << card_rank[listp0.move[listp0.current].rank] << " - " << card_suit[listp1.move[listp1.current].suit] @@ -944,196 +944,196 @@ auto Moves::TrickToText(const int trick) const -> string { << card_suit[listp3.move[listp3.current].suit] << card_rank[listp3.move[listp3.current].rank] << "\n"; - return ss.str(); + return ss.str(); } auto Moves::UpdateStatsEntry(moveStatsType &stat, const int findex, const int hit, const int len) const -> void { - bool found = false; - int fno = 0; - for (int i = 0; i < stat.nfuncs; i++) { - if (stat.list[i].findex == findex) { - found = true; - fno = i; - break; + bool found = false; + int fno = 0; + for (int i = 0; i < stat.nfuncs; i++) { + if (stat.list[i].findex == findex) { + found = true; + fno = i; + break; + } } - } - - moveStatType *funp; - if (found) { - funp = &stat.list[fno]; - funp->count++; - funp->sumHits += hit; - funp->sumLengths += len; - } else { - // Internal invariant: nfuncs must not exceed array size - assert(stat.nfuncs < static_cast(MgType::SIZE) && + + moveStatType *funp; + if (found) { + funp = &stat.list[fno]; + funp->count++; + funp->sumHits += hit; + funp->sumLengths += len; + } else { + // Internal invariant: nfuncs must not exceed array size + assert(stat.nfuncs < static_cast(MgType::SIZE) && "UpdateStatsEntry: nfuncs overflow"); - funp = &stat.list[stat.nfuncs++]; + funp = &stat.list[stat.nfuncs++]; - funp->count++; - funp->findex = findex; - funp->sumHits += hit; - funp->sumLengths += len; - } + funp->count++; + funp->findex = findex; + funp->sumHits += hit; + funp->sumLengths += len; + } } auto Moves::RegisterHit(const int trick, const int relHand) -> void { - const MovePlyType &list = moveList[trick][relHand]; + const MovePlyType &list = moveList[trick][relHand]; - const int findex = static_cast(lastCall[trick][relHand]); - const int len = list.last + 1; + const int findex = static_cast(lastCall[trick][relHand]); + const int len = list.last + 1; - // Internal invariant: lastCall must be initialized before RegisterHit - assert(findex != -1 && "RegisterHit: lastCall not initialized"); + // Internal invariant: lastCall must be initialized before RegisterHit + assert(findex != -1 && "RegisterHit: lastCall not initialized"); - const int curr = list.current; - // Internal invariant: current must be within valid range [1, len] - assert(curr >= 1 && curr <= len && "RegisterHit: current out of bounds"); + const int curr = list.current; + // Internal invariant: current must be within valid range [1, len] + assert(curr >= 1 && curr <= len && "RegisterHit: current out of bounds"); - const int moveSuit = list.move[curr - 1].suit; - int numSuit = 0; - int numSeen = 0; + const int moveSuit = list.move[curr - 1].suit; + int numSuit = 0; + int numSeen = 0; - for (int i = 0; i < len; i++) { - if (list.move[i].suit == moveSuit) { - numSuit++; - if (i == curr - 1) - numSeen = numSuit; + for (int i = 0; i < len; i++) { + if (list.move[i].suit == moveSuit) { + numSuit++; + if (i == curr - 1) + numSeen = numSuit; + } } - } - // Now we know enough to update the statistics tables. + // Now we know enough to update the statistics tables. - trickTable[trick][relHand].count++; - trickTable[trick][relHand].sumHits += curr; - trickTable[trick][relHand].sumLengths += len; + trickTable[trick][relHand].count++; + trickTable[trick][relHand].sumHits += curr; + trickTable[trick][relHand].sumLengths += len; - trickSuitTable[trick][relHand].count++; - trickSuitTable[trick][relHand].sumHits += numSeen; - trickSuitTable[trick][relHand].sumLengths += numSuit; + trickSuitTable[trick][relHand].count++; + trickSuitTable[trick][relHand].sumHits += numSeen; + trickSuitTable[trick][relHand].sumLengths += numSuit; - Moves::UpdateStatsEntry(trickDetailTable[trick][relHand], findex, curr, len); + Moves::UpdateStatsEntry(trickDetailTable[trick][relHand], findex, curr, len); - Moves::UpdateStatsEntry(trickDetailSuitTable[trick][relHand], findex, numSeen, - numSuit); + Moves::UpdateStatsEntry(trickDetailSuitTable[trick][relHand], findex, numSeen, + numSuit); - Moves::UpdateStatsEntry(trickFuncTable, findex, curr, len); + Moves::UpdateStatsEntry(trickFuncTable, findex, curr, len); - Moves::UpdateStatsEntry(trickFuncSuitTable, findex, numSeen, numSuit); + Moves::UpdateStatsEntry(trickFuncSuitTable, findex, numSeen, numSuit); } auto Moves::AverageString(const moveStatType &stat) const -> string { - stringstream ss; - if (stat.count == 0) - ss << setw(5) << right << "--" << setw(5) << "--"; - else { - ss << setw(5) << setprecision(2) << fixed + stringstream ss; + if (stat.count == 0) + ss << setw(5) << right << "--" << setw(5) << "--"; + else { + ss << setw(5) << setprecision(2) << fixed << stat.sumHits / static_cast(stat.count) << setw(5) << setprecision(1) << fixed << 100. * stat.sumHits / static_cast(stat.sumLengths); - } + } - return ss.str(); + return ss.str(); } auto Moves::FullAverageString(const moveStatType &stat) const -> string { - stringstream ss; - if (stat.count == 0) { - ss << setw(6) << right << "--" << setw(6) << "--" << setw(5) << "--" + stringstream ss; + if (stat.count == 0) { + ss << setw(6) << right << "--" << setw(6) << "--" << setw(5) << "--" << setw(9) << "--" << setw(5) << "--"; - } else { - double avg = stat.sumHits / static_cast(stat.count); + } else { + double avg = stat.sumHits / static_cast(stat.count); - ss << setw(5) << setprecision(3) << fixed << avg << setw(6) + ss << setw(5) << setprecision(3) << fixed << avg << setw(6) << setprecision(2) << fixed << stat.sumLengths / static_cast(stat.count) << setw(5) << setprecision(1) << fixed << 100. * stat.sumHits / static_cast(stat.sumLengths) << setw(9) << stat.count << setprecision(0) << fixed << (avg * avg * avg - 1) * stat.count; - } + } - return ss.str(); + return ss.str(); } auto Moves::PrintTrickTable(const moveStatType tablep[][DDS_HANDS]) const - -> string { - stringstream ss; + -> string { + stringstream ss; - ss << setw(5) << "Trick" << setw(12) << "Hand 0" << setw(12) << "Hand 1" + ss << setw(5) << "Trick" << setw(12) << "Hand 0" << setw(12) << "Hand 1" << setw(12) << "Hand 2" << setw(12) << "Hand 3" << "\n"; - ss << setw(6) << "" << setw(6) << "Avg" << setw(5) << "%" << setw(6) << "Avg" + ss << setw(6) << "" << setw(6) << "Avg" << setw(5) << "%" << setw(6) << "Avg" << setw(5) << "%" << setw(6) << "Avg" << setw(5) << "%" << setw(6) << "Avg" << setw(5) << "%" << "\n"; - for (int t = 12; t >= 0; t--) { - ss << setw(5) << right << t << setw(12) + for (int t = 12; t >= 0; t--) { + ss << setw(5) << right << t << setw(12) << Moves::AverageString(tablep[t][0]) << setw(12) << Moves::AverageString(tablep[t][1]) << setw(12) << Moves::AverageString(tablep[t][2]) << setw(12) << Moves::AverageString(tablep[t][3]) << "\n"; - } - return ss.str(); + } + return ss.str(); } auto Moves::PrintFunctionTable(const moveStatsType &stat) const -> string { - if (stat.nfuncs == 0) - return ""; + if (stat.nfuncs == 0) + return ""; - stringstream ss; - ss << setw(15) << left << "Function" << setw(6) << "Avg" << setw(6) << "Len" + stringstream ss; + ss << setw(15) << left << "Function" << setw(6) << "Avg" << setw(6) << "Len" << setw(5) << "%" << setw(9) << "Count" << setw(9) << "Imp" << "\n"; - for (int fr = 0; fr < static_cast(MgType::SIZE); fr++) { - for (int f = 0; f < stat.nfuncs; f++) { - if (stat.list[f].findex != fr) - continue; + for (int fr = 0; fr < static_cast(MgType::SIZE); fr++) { + for (int f = 0; f < stat.nfuncs; f++) { + if (stat.list[f].findex != fr) + continue; - ss << setw(15) << left << funcName[fr] + ss << setw(15) << left << funcName[fr] << Moves::FullAverageString(stat.list[f]) << "\n"; + } } - } - return ss.str(); + return ss.str(); } auto Moves::PrintTrickStats(ofstream &fout) const -> void { - fout << "Overall statistics\n\n"; - fout << Moves::PrintTrickTable(trickTable); + fout << "Overall statistics\n\n"; + fout << Moves::PrintTrickTable(trickTable); - fout << "\n\nStatistics for winning suit\n\n"; - fout << Moves::PrintTrickTable(trickSuitTable) << "\n\n"; + fout << "\n\nStatistics for winning suit\n\n"; + fout << Moves::PrintTrickTable(trickSuitTable) << "\n\n"; } auto Moves::PrintTrickDetails(ofstream &fout) const -> void { - fout << "Trick detail statistics\n\n"; + fout << "Trick detail statistics\n\n"; - for (int t = 12; t >= 0; t--) { - for (int h = 0; h < DDS_HANDS; h++) { - fout << "Trick " << t << ", relative hand " << h << "\n"; - fout << Moves::PrintFunctionTable(trickDetailTable[t][h]) << "\n"; + for (int t = 12; t >= 0; t--) { + for (int h = 0; h < DDS_HANDS; h++) { + fout << "Trick " << t << ", relative hand " << h << "\n"; + fout << Moves::PrintFunctionTable(trickDetailTable[t][h]) << "\n"; + } } - } - fout << "Suit detail statistics\n\n"; + fout << "Suit detail statistics\n\n"; - for (int t = 12; t >= 0; t--) { - for (int h = 0; h < DDS_HANDS; h++) { - fout << "Trick " << t << ", relative hand " << h << "\n"; - fout << Moves::PrintFunctionTable(trickDetailSuitTable[t][h]) << "\n"; + for (int t = 12; t >= 0; t--) { + for (int h = 0; h < DDS_HANDS; h++) { + fout << "Trick " << t << ", relative hand " << h << "\n"; + fout << Moves::PrintFunctionTable(trickDetailSuitTable[t][h]) << "\n"; + } } - } - fout << "\n\n"; + fout << "\n\n"; } auto Moves::PrintFunctionStats(ofstream &fout) const -> void { - fout << "Function statistics\n\n"; - fout << Moves::PrintFunctionTable(trickFuncTable); + fout << "Function statistics\n\n"; + fout << Moves::PrintFunctionTable(trickFuncTable); - fout << "\n\nFunction statistics for winning suit\n\n"; - fout << Moves::PrintFunctionTable(trickFuncSuitTable); - fout << "\n\n"; + fout << "\n\nFunction statistics for winning suit\n\n"; + fout << Moves::PrintFunctionTable(trickFuncSuitTable); + fout << "\n\n"; } diff --git a/library/src/moves/moves.hpp b/library/src/moves/moves.hpp index 196310cd2..e4caed836 100644 --- a/library/src/moves/moves.hpp +++ b/library/src/moves/moves.hpp @@ -22,34 +22,34 @@ * is being generated. Values are used as indices into statistics tables. */ enum class MgType { - /** Notrump at trick 0. */ - NT0 = 0, - /** Trump contract at trick 0. */ - TRUMP0 = 1, - /** Notrump, void in one hand. */ - NT_VOID1 = 2, - /** Trump, void in one hand. */ - TRUMP_VOID1 = 3, - /** Notrump, no void in one hand. */ - NT_NOTVOID1 = 4, - /** Trump, no void in one hand. */ - TRUMP_NOTVOID1 = 5, - /** Notrump, void in two hands. */ - NT_VOID2 = 6, - /** Trump, void in two hands. */ - TRUMP_VOID2 = 7, - /** Notrump, no void in two hands. */ - NT_NOTVOID2 = 8, - /** Trump, no void in two hands. */ - TRUMP_NOTVOID2 = 9, - /** Notrump, void in three hands. */ - NT_VOID3 = 10, - /** Trump, void in three hands. */ - TRUMP_VOID3 = 11, - /** Combined void/not-void tracking for three hands. */ - COMB_NOTVOID3 = 12, - /** Number of categories. */ - SIZE = 13 + /** Notrump at trick 0. */ + NT0 = 0, + /** Trump contract at trick 0. */ + TRUMP0 = 1, + /** Notrump, void in one hand. */ + NT_VOID1 = 2, + /** Trump, void in one hand. */ + TRUMP_VOID1 = 3, + /** Notrump, no void in one hand. */ + NT_NOTVOID1 = 4, + /** Trump, no void in one hand. */ + TRUMP_NOTVOID1 = 5, + /** Notrump, void in two hands. */ + NT_VOID2 = 6, + /** Trump, void in two hands. */ + TRUMP_VOID2 = 7, + /** Notrump, no void in two hands. */ + NT_NOTVOID2 = 8, + /** Trump, no void in two hands. */ + TRUMP_NOTVOID2 = 9, + /** Notrump, void in three hands. */ + NT_VOID3 = 10, + /** Trump, void in three hands. */ + TRUMP_VOID3 = 11, + /** Combined void/not-void tracking for three hands. */ + COMB_NOTVOID3 = 12, + /** Number of categories. */ + SIZE = 13 }; /** @@ -84,73 +84,73 @@ enum class MgType { */ class Moves { public: - /** @brief Lead hand index for the current trick. */ - int leadHand; - /** @brief Lead suit for the current trick. */ - int leadSuit; - /** @brief Current hand index being processed. */ - int currHand; - /** @brief Current trick number. */ - int currTrick; - /** @brief Trump suit or DDS_NOTRUMP. */ - int trump; - /** @brief Suit currently being generated. */ - int suit; - /** @brief Number of moves currently generated. */ - int numMoves; - /** @brief Previous move count used by heuristic. */ - int lastNumMoves; - - /** @brief Per-trick tracking state. */ - TrackType track[13]; - /** @brief Pointer to active track entry (non-owning, points into track array). */ - TrackType *trackp; - - /** @brief Move lists indexed by trick and relative hand. */ - MovePlyType moveList[13][DDS_HANDS]; - - /** @brief Pointer to current move list storage (non-owning, points into moveList). */ - MoveType *mply; - - /** @brief Last heuristic category per trick and hand. */ - MgType lastCall[13][DDS_HANDS]; - - /** @brief Human-readable names for MgType categories. */ - std::string funcName[static_cast(MgType::SIZE)]; - - /** @brief Aggregate statistics for a single function category. */ - struct moveStatType { - int count; - int findex; - int sumHits; - int sumLengths; - }; - - /** @brief Collection of statistics for all function categories. */ - struct moveStatsType { - int nfuncs; - moveStatType list[static_cast(MgType::SIZE)]; - }; - - /** @brief Trick-level statistics for all hands. */ - moveStatType trickTable[13][DDS_HANDS]; - - /** @brief Trick-level statistics for winning suit only. */ - moveStatType trickSuitTable[13][DDS_HANDS]; - - /** @brief Detailed per-function stats by trick and hand. */ - moveStatsType trickDetailTable[13][DDS_HANDS]; - - /** @brief Detailed per-function stats by trick/hand for winning suit. */ - moveStatsType trickDetailSuitTable[13][DDS_HANDS]; - - /** @brief Aggregated function stats across all tricks. */ - moveStatsType trickFuncTable; - - /** @brief Aggregated function stats for winning suit. */ - moveStatsType trickFuncSuitTable; - - /** + /** @brief Lead hand index for the current trick. */ + int leadHand; + /** @brief Lead suit for the current trick. */ + int leadSuit; + /** @brief Current hand index being processed. */ + int currHand; + /** @brief Current trick number. */ + int currTrick; + /** @brief Trump suit or DDS_NOTRUMP. */ + int trump; + /** @brief Suit currently being generated. */ + int suit; + /** @brief Number of moves currently generated. */ + int numMoves; + /** @brief Previous move count used by heuristic. */ + int lastNumMoves; + + /** @brief Per-trick tracking state. */ + TrackType track[13]; + /** @brief Pointer to active track entry (non-owning, points into track array). */ + TrackType *trackp; + + /** @brief Move lists indexed by trick and relative hand. */ + MovePlyType moveList[13][DDS_HANDS]; + + /** @brief Pointer to current move list storage (non-owning, points into moveList). */ + MoveType *mply; + + /** @brief Last heuristic category per trick and hand. */ + MgType lastCall[13][DDS_HANDS]; + + /** @brief Human-readable names for MgType categories. */ + std::string funcName[static_cast(MgType::SIZE)]; + + /** @brief Aggregate statistics for a single function category. */ + struct moveStatType { + int count; + int findex; + int sumHits; + int sumLengths; + }; + + /** @brief Collection of statistics for all function categories. */ + struct moveStatsType { + int nfuncs; + moveStatType list[static_cast(MgType::SIZE)]; + }; + + /** @brief Trick-level statistics for all hands. */ + moveStatType trickTable[13][DDS_HANDS]; + + /** @brief Trick-level statistics for winning suit only. */ + moveStatType trickSuitTable[13][DDS_HANDS]; + + /** @brief Detailed per-function stats by trick and hand. */ + moveStatsType trickDetailTable[13][DDS_HANDS]; + + /** @brief Detailed per-function stats by trick/hand for winning suit. */ + moveStatsType trickDetailSuitTable[13][DDS_HANDS]; + + /** @brief Aggregated function stats across all tricks. */ + moveStatsType trickFuncTable; + + /** @brief Aggregated function stats for winning suit. */ + moveStatsType trickFuncSuitTable; + + /** * @brief Compute top number of winning moves for a given rank. * * @param ris Rank-in-suit bitmask @@ -158,10 +158,10 @@ class Moves { * @param topNumber Output: top move number * @param mno Output: move index */ - auto GetTopNumber(const int ris, const int prank, int &topNumber, - int &mno) const -> void; + auto GetTopNumber(const int ris, const int prank, int &topNumber, + int &mno) const -> void; - /** + /** * @brief Determine whether one move wins over another. * * @param mvp1 Candidate move @@ -169,21 +169,21 @@ class Moves { * @param trump Trump suit * @return True if mvp1 wins against mvp2 */ - inline auto WinningMove(const MoveType &mvp1, const ExtCard &mvp2, - const int trump) const -> bool; + inline auto WinningMove(const MoveType &mvp1, const ExtCard &mvp2, + const int trump) const -> bool; - /** + /** * @brief Render a move list as a printable string. * * @param mply Move list * @return Formatted string for debugging/logging */ - auto PrintMove(const MovePlyType &mply) const -> std::string; + auto PrintMove(const MovePlyType &mply) const -> std::string; - /** @brief Sort current move list by weight. */ - auto MergeSort() -> void; + /** @brief Sort current move list by weight. */ + auto MergeSort() -> void; - /** + /** * @brief Build a HeuristicContext snapshot from current move-gen state. * * Built once per move-generation call; the caller updates suit and move @@ -196,15 +196,15 @@ class Moves { * @param tr Bound trick track (must outlive the context). Passed explicitly * so callers cannot accidentally dereference a null Moves::trackp. */ - auto make_heuristic_context(const Pos &tpos, const MoveType &best_move, - const MoveType &best_move_tt, - const RelRanksType thrp_rel[], - const TrackType &tr) const - -> HeuristicContext; + auto make_heuristic_context(const Pos &tpos, const MoveType &best_move, + const MoveType &best_move_tt, + const RelRanksType thrp_rel[], + const TrackType &tr) const + -> HeuristicContext; - // (logging accessors removed) + // (logging accessors removed) - /** + /** * @brief Update statistics for a single function category. * * @param stat Statistics table to update @@ -212,47 +212,47 @@ class Moves { * @param hit Hit position * @param len List length */ - auto UpdateStatsEntry(moveStatsType &stat, const int findex, const int hit, - const int len) const -> void; + auto UpdateStatsEntry(moveStatsType &stat, const int findex, const int hit, + const int len) const -> void; - /** @brief Format average stats for a single category. */ - auto AverageString(const moveStatType &statp) const -> std::string; + /** @brief Format average stats for a single category. */ + auto AverageString(const moveStatType &statp) const -> std::string; - /** @brief Format detailed average stats for a single category. */ - auto FullAverageString(const moveStatType &statp) const -> std::string; + /** @brief Format detailed average stats for a single category. */ + auto FullAverageString(const moveStatType &statp) const -> std::string; - /** + /** * @brief Format trick-level statistics as a table. * * @param tablep Table of statistics * @return Formatted text table */ - auto PrintTrickTable(const moveStatType tablep[][DDS_HANDS]) const - -> std::string; + auto PrintTrickTable(const moveStatType tablep[][DDS_HANDS]) const + -> std::string; - /** + /** * @brief Format function-level statistics as a table. * * @param tablep Statistics collection * @return Formatted text table */ - auto PrintFunctionTable(const moveStatsType &tablep) const -> std::string; + auto PrintFunctionTable(const moveStatsType &tablep) const -> std::string; - /** + /** * @brief Construct a new Moves object. * * Initializes move tracking structures and prepares for move generation. */ - Moves(); + Moves(); - /** + /** * @brief Destroy the Moves object and clean up resources. * * Releases all memory and performs cleanup of move tracking state. */ - ~Moves(); + ~Moves(); - /** + /** * @brief Initialize move generation for a new deal state. * * @param tricks Current trick index @@ -263,20 +263,20 @@ class Moves { * @param our_trump Trump suit * @param our_lead_hand Absolute lead hand */ - auto Init(const int tricks, const int relStartHand, const int initialRanks[], - const int initialSuits[], - const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], - const int our_trump, const int our_lead_hand) -> void; + auto Init(const int tricks, const int relStartHand, const int initialRanks[], + const int initialSuits[], + const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], + const int our_trump, const int our_lead_hand) -> void; - /** + /** * @brief Reset tracking state for a new lead hand. * * @param tricks Current trick index * @param leadHand Absolute lead hand */ - auto Reinit(const int tricks, const int leadHand) -> void; + auto Reinit(const int tricks, const int leadHand) -> void; - /** + /** * @brief Generate moves for first hand of the trick. * * @param tricks Current trick index @@ -286,11 +286,11 @@ class Moves { * @param thrp_rel Relative ranks per hand * @return Number of generated moves */ - auto MoveGen0(const int tricks, const Pos &tpos, const MoveType &bestMove, - const MoveType &bestMoveTT, const RelRanksType thrp_rel[]) - -> int; + auto MoveGen0(const int tricks, const Pos &tpos, const MoveType &bestMove, + const MoveType &bestMoveTT, const RelRanksType thrp_rel[]) + -> int; - /** + /** * @brief Generate moves for second/third/fourth hand of the trick. * * @param tricks Current trick index @@ -298,28 +298,28 @@ class Moves { * @param tpos Current position * @return Number of generated moves */ - auto MoveGen123(const int tricks, const int relHand, const Pos &tpos) -> int; + auto MoveGen123(const int tricks, const int relHand, const Pos &tpos) -> int; - /** + /** * @brief Get number of moves available for trick/hand. * * @param trick Trick index * @param relHand Relative hand index * @return Move count */ - auto GetLength(const int trick, const int relHand) const -> int; + auto GetLength(const int trick, const int relHand) const -> int; - /** + /** * @brief Apply a specific move to tracking state. * * @param mply Move to apply * @param trick Trick index * @param relHand Relative hand index */ - auto MakeSpecific(const MoveType &mply, const int trick, const int relHand) - -> void; + auto MakeSpecific(const MoveType &mply, const int trick, const int relHand) + -> void; - /** + /** * @brief Choose next move according to win constraints. * * @param trick Trick index @@ -327,18 +327,18 @@ class Moves { * @param win_ranks Minimum winning rank per suit * @return Pointer to chosen move, or nullptr if no valid move found */ - auto MakeNext(const int trick, const int relHand, - const unsigned short win_ranks[DDS_SUITS]) -> MoveType const *; + auto MakeNext(const int trick, const int relHand, + const unsigned short win_ranks[DDS_SUITS]) -> MoveType const *; - /** + /** * @brief Choose next move without win constraints. * * @param trick Trick index * @param relHand Relative hand index * @return Pointer to chosen move, or nullptr if list exhausted */ - auto MakeNextSimple(const int trick, const int relHand) -> MoveType const *; - /** + auto MakeNextSimple(const int trick, const int relHand) -> MoveType const *; + /** * @brief Update TrackType state to reflect a played move. * * Sets trackp to &track[trick] internally before updating state. @@ -347,102 +347,102 @@ class Moves { * @param relHand Relative hand index within the current trick (0..3) * @param trick Trick index (0..12); must be > 0 when relHand==3 (updates track[trick-1]) */ - auto apply_move_to_track(const MoveType &move, const int relHand, + auto apply_move_to_track(const MoveType &move, const int relHand, const int trick) -> void; - /** + /** * @brief Advance to next move in list. * * @param tricks Current trick index * @param relHand Relative hand index */ - auto Step(const int tricks, const int relHand) -> void; + auto Step(const int tricks, const int relHand) -> void; - /** + /** * @brief Reset move index to start of list. * * @param tricks Current trick index * @param relHand Relative hand index */ - auto Rewind(const int tricks, const int relHand) -> void; + auto Rewind(const int tricks, const int relHand) -> void; - /** + /** * @brief Remove forbidden moves from a list. * * @param tricks Current trick index * @param relHand Relative hand index * @param forbiddenMoves Move list to exclude */ - auto Purge(const int tricks, const int relHand, + auto Purge(const int tricks, const int relHand, const MoveType forbiddenMoves[]) -> void; - /** + /** * @brief Reward the last chosen move with extra weight. * * @param trick Trick index * @param relHand Relative hand index */ - auto Reward(const int trick, const int relHand) -> void; + auto Reward(const int trick, const int relHand) -> void; - /** + /** * @brief Collect summary data for the current trick. * * @param tricks Current trick index * @return Trick data snapshot */ - auto GetTrickData(const int tricks) -> const TrickDataType &; + auto GetTrickData(const int tricks) -> const TrickDataType &; - /** + /** * @brief Sort moves by heuristic weight. * * @param tricks Current trick index * @param relHand Relative hand index */ - auto Sort(const int tricks, const int relHand) -> void; + auto Sort(const int tricks, const int relHand) -> void; - /** + /** * @brief Render moves for a trick/hand as a printable string. * * @param trick Trick index * @param relHand Relative hand index * @return Formatted string */ - auto PrintMoves(const int trick, const int relHand) const -> std::string; + auto PrintMoves(const int trick, const int relHand) const -> std::string; - /** + /** * @brief Register the chosen move in statistics tables. * * @param tricks Current trick index * @param relHand Relative hand index */ - auto RegisterHit(const int tricks, const int relHand) -> void; + auto RegisterHit(const int tricks, const int relHand) -> void; - /** + /** * @brief Render the last trick as a string. * * @param trick Trick index * @return Formatted string */ - auto TrickToText(const int trick) const -> std::string; + auto TrickToText(const int trick) const -> std::string; - /** + /** * @brief Print summary trick statistics to stream. * * @param fout Output stream */ - auto PrintTrickStats(std::ofstream &fout) const -> void; + auto PrintTrickStats(std::ofstream &fout) const -> void; - /** + /** * @brief Print detailed trick statistics to stream. * * @param fout Output stream */ - auto PrintTrickDetails(std::ofstream &fout) const -> void; + auto PrintTrickDetails(std::ofstream &fout) const -> void; - /** + /** * @brief Print aggregated function statistics to stream. * * @param fout Output stream */ - auto PrintFunctionStats(std::ofstream &fout) const -> void; + auto PrintFunctionStats(std::ofstream &fout) const -> void; }; diff --git a/library/src/par.cpp b/library/src/par.cpp index 878c32eb5..e0a38cdbc 100644 --- a/library/src/par.cpp +++ b/library/src/par.cpp @@ -19,78 +19,78 @@ using namespace std; struct par_suits_type { - int suit; - int tricks; - int score; + int suit; + int tricks; + int score; }; struct best_par_type { - int par_denom; - int par_tricks; + int par_denom; + int par_tricks; }; struct parContr2Type { - char contracts[10]; - int denom; + char contracts[10]; + int denom; }; /* index 1: 0=NT, 1=Major, 2=Minor index 2: contract level 1-7 */ const int max_low[3][8] = { - {0, 0, 1, 0, 1, 2, 0, 0}, - {0, 0, 1, 2, 0, 1, 0, 0}, - {0, 0, 1, 2, 3, 0, 0, 0} + {0, 0, 1, 0, 1, 2, 0, 0}, + {0, 0, 1, 2, 0, 1, 0, 0}, + {0, 0, 1, 2, 3, 0, 0, 0} }; int STDCALL CalcParPBN( - DdTableDealPBN tableDealPBN, - DdTableResults * tablep, - int vulnerable, - ParResults * presp) + DdTableDealPBN tableDealPBN, + DdTableResults * tablep, + int vulnerable, + ParResults * presp) { - int res; - DdTableDeal tableDeal; - int STDCALL CalcPar(DdTableDeal tableDeal, int vulnerable, - DdTableResults * tablep, ParResults * presp); + int res; + DdTableDeal tableDeal; + int STDCALL CalcPar(DdTableDeal tableDeal, int vulnerable, + DdTableResults * tablep, ParResults * presp); - if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) - return RETURN_PBN_FAULT; + if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) + return RETURN_PBN_FAULT; - res = CalcPar(tableDeal, vulnerable, tablep, presp); + res = CalcPar(tableDeal, vulnerable, tablep, presp); - return res; + return res; } int rawscore( - int denom, - int tricks, - int isvul); + int denom, + int tricks, + int isvul); void SideSeats( - int dr, - int i, - int t1, - int t2, - int order, - ParResultsMaster sidesRes[2]); + int dr, + int i, + int t1, + int t2, + int order, + ParResultsMaster sidesRes[2]); void CalcOverTricks( - int i, - int max_lower, - int tricks, - int order, - ParResultsMaster sidesRes[2]); + int i, + int max_lower, + int tricks, + int order, + ParResultsMaster sidesRes[2]); int CalcMultiContracts( - int max_lower, - int tricks); + int max_lower, + int tricks); int VulnerDefSide( - int side, - int vulnerable); + int side, + int vulnerable); /** @@ -105,760 +105,760 @@ int VulnerDefSide( * @return 1 on success, error code otherwise */ int STDCALL Par( - DdTableResults const * tablep, - ParResults * presp, - int vulnerable) + DdTableResults const * tablep, + ParResults * presp, + int vulnerable) { - /* vulnerable 0: None 1: Both 2: NS 3: EW */ - - /* The code for calculation of par score / contracts is based upon the - perl code written by Matthew Kidd ACBLmerge. He has kindly given me - permission to include a C++ adaptation in DDS. */ + /* vulnerable 0: None 1: Both 2: NS 3: EW */ - /* The Par function computes the par result and contracts. */ + /* The code for calculation of par score / contracts is based upon the + perl code written by Matthew Kidd ACBLmerge. He has kindly given me + permission to include a C++ adaptation in DDS. */ - ParResultsMaster sidesRes[2]; - int res, k; - char temp[8], buff[3]; - int denom_conv[5] = { 4, 0, 1, 2, 3 }; - char contr_sep[2] = { ',', '\0' }; - char seats[6][4] = { - { "N " }, { "E " }, { "S " }, { "W " }, { "NS " }, { "EW " } }; + /* The Par function computes the par result and contracts. */ - res = SidesParBin(tablep, sidesRes, vulnerable); + ParResultsMaster sidesRes[2]; + int res, k; + char temp[8], buff[3]; + int denom_conv[5] = { 4, 0, 1, 2, 3 }; + char contr_sep[2] = { ',', '\0' }; + char seats[6][4] = { + { "N " }, { "E " }, { "S " }, { "W " }, { "NS " }, { "EW " } }; - if (res != RETURN_NO_FAULT) - return res; + res = SidesParBin(tablep, sidesRes, vulnerable); - presp->par_score[0][0] = 'N'; - presp->par_score[0][1] = 'S'; - presp->par_score[0][2] = ' '; - presp->par_score[0][3] = '\0'; - presp->par_score[1][0] = 'E'; - presp->par_score[1][1] = 'W'; - presp->par_score[1][2] = ' '; - presp->par_score[1][3] = '\0'; - - snprintf(temp, 8, "%d", sidesRes[0].score); - strcat(presp->par_score[0], temp); - snprintf(temp, 8, "%d", sidesRes[1].score); - strcat(presp->par_score[1], temp); - - presp->par_contracts_string[0][0] = 'N'; - presp->par_contracts_string[0][1] = 'S'; - presp->par_contracts_string[0][2] = ':'; - presp->par_contracts_string[0][3] = '\0'; - presp->par_contracts_string[1][0] = 'E'; - presp->par_contracts_string[1][1] = 'W'; - presp->par_contracts_string[1][2] = ':'; - presp->par_contracts_string[1][3] = '\0'; - - if (sidesRes[0].score == 0) - { - /* Neither side can make anything.*/ - - return RETURN_NO_FAULT; - } - - for (int i = 0; i <= 1; i++) - { - if (sidesRes[i].contracts[0].under_tricks > 0) + if (res != RETURN_NO_FAULT) + return res; + + presp->par_score[0][0] = 'N'; + presp->par_score[0][1] = 'S'; + presp->par_score[0][2] = ' '; + presp->par_score[0][3] = '\0'; + presp->par_score[1][0] = 'E'; + presp->par_score[1][1] = 'W'; + presp->par_score[1][2] = ' '; + presp->par_score[1][3] = '\0'; + + snprintf(temp, 8, "%d", sidesRes[0].score); + strcat(presp->par_score[0], temp); + snprintf(temp, 8, "%d", sidesRes[1].score); + strcat(presp->par_score[1], temp); + + presp->par_contracts_string[0][0] = 'N'; + presp->par_contracts_string[0][1] = 'S'; + presp->par_contracts_string[0][2] = ':'; + presp->par_contracts_string[0][3] = '\0'; + presp->par_contracts_string[1][0] = 'E'; + presp->par_contracts_string[1][1] = 'W'; + presp->par_contracts_string[1][2] = ':'; + presp->par_contracts_string[1][3] = '\0'; + + if (sidesRes[0].score == 0) { - /* Sacrifice*/ - - for (k = 0; k < sidesRes[i].number; k++) - { - - strcat(presp->par_contracts_string[i], - seats[sidesRes[i].contracts[k].seats]); - snprintf(temp, 8, "%d", sidesRes[i].contracts[k].level); - buff[0] = static_cast( - card_suit[denom_conv[sidesRes[i].contracts[k].denom]]); - buff[1] = 'x'; - buff[2] = '\0'; - strcat(temp, buff); - strcat(presp->par_contracts_string[i], temp); - if (k != (sidesRes[i].number - 1)) - strcat(presp->par_contracts_string[i], contr_sep); - } + /* Neither side can make anything.*/ + + return RETURN_NO_FAULT; } - else + + for (int i = 0; i <= 1; i++) { - /* Make */ + if (sidesRes[i].contracts[0].under_tricks > 0) + { + /* Sacrifice*/ + + for (k = 0; k < sidesRes[i].number; k++) + { + + strcat(presp->par_contracts_string[i], + seats[sidesRes[i].contracts[k].seats]); + snprintf(temp, 8, "%d", sidesRes[i].contracts[k].level); + buff[0] = static_cast( + card_suit[denom_conv[sidesRes[i].contracts[k].denom]]); + buff[1] = 'x'; + buff[2] = '\0'; + strcat(temp, buff); + strcat(presp->par_contracts_string[i], temp); + if (k != (sidesRes[i].number - 1)) + strcat(presp->par_contracts_string[i], contr_sep); + } + } + else + { + /* Make */ - for (k = 0; k < sidesRes[i].number; k++) - { + for (k = 0; k < sidesRes[i].number; k++) + { - strcat(presp->par_contracts_string[i], - seats[sidesRes[i].contracts[k].seats]); + strcat(presp->par_contracts_string[i], + seats[sidesRes[i].contracts[k].seats]); - int n = CalcMultiContracts(sidesRes[i].contracts[k].over_tricks, - sidesRes[i].contracts[k].over_tricks - + sidesRes[i].contracts[k].level + 6); + int n = CalcMultiContracts(sidesRes[i].contracts[k].over_tricks, + sidesRes[i].contracts[k].over_tricks + + sidesRes[i].contracts[k].level + 6); - snprintf(temp, 8, "%d", n); - buff[0] = static_cast( - card_suit[denom_conv[sidesRes[i].contracts[k].denom]]); - buff[1] = '\0'; - strcat(temp, buff); - strcat(presp->par_contracts_string[i], temp); - if (k != (sidesRes[i].number - 1)) - strcat(presp->par_contracts_string[i], contr_sep); - } + snprintf(temp, 8, "%d", n); + buff[0] = static_cast( + card_suit[denom_conv[sidesRes[i].contracts[k].denom]]); + buff[1] = '\0'; + strcat(temp, buff); + strcat(presp->par_contracts_string[i], temp); + if (k != (sidesRes[i].number - 1)) + strcat(presp->par_contracts_string[i], contr_sep); + } + } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } #ifndef DEALER_PAR_ENGINE_ONLY int STDCALL SidesParBin( - DdTableResults const * tablep, - ParResultsMaster sidesRes[2], - int vulnerable) + DdTableResults const * tablep, + ParResultsMaster sidesRes[2], + int vulnerable) { - /* vulnerable 0: None 1: Both 2: NS 3: EW */ - - /* The code for calculation of par score / contracts is based upon the - perl code written by Matthew Kidd ACBLmerge. He has kindly given me - permission to include a C++ adaptation in DDS. */ - - /* The Par function computes the par result and contracts. */ - - if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) - return check; - - if (int const check = par_vulnerable_checks(vulnerable); - check != RETURN_NO_FAULT) - return check; - - int denom_conv[5] = { 4, 0, 1, 2, 3 }; - /* Preallocate for efficiency. These hold result from last direction - (N-S or E-W) examined. */ - int j, k, m, isvul; - int current_side, both_sides_once_flag, denom_max = 0, max_lower; - int new_score_flag, sc1, sc2, sc3; - int prev_par_denom = 0, prev_par_tricks = 0; - int denom_filter[5] = { 0, 0, 0, 0, 0 }; - int no_filtered[2] = { 0, 0 }; - int no_of_denom[2]; - int best_par_score[2]; - int best_par_sacut[2]; - best_par_type best_par[5][2]; /* 1st index order number. */ - - int ut = 0, t1, t2, tt, score, dr, tu, tu_max, t3[5], t4[5], n; - par_suits_type par_suits[5]; - - int par_denom[2] = { -1, -1 }; /* 0-4 = NT,S,H,D,C */ - int par_tricks[2] = { 6, 6 }; /* Initial "contract" beats 0 NT */ - int par_score[2] = { 0, 0 }; - int par_sacut[2] = { 0, 0 }; /* Undertricks for sacrifice (0 if not sac) */ - - - /* Find best par result for N-S (i==0) or E-W (i==1). These will - nearly always be the same, but when we have a "hot" situation - they will not be. */ - - for (int i = 0; i <= 1; i++) - { - /* Start with the with the offensive side (current_side = 0) and - alternate between sides seeking the to improve the result for the - current side.*/ - - no_filtered[i] = 0; - for (m = 0; m <= 4; m++) - denom_filter[m] = 0; - - current_side = 0; - both_sides_once_flag = 0; - while (1) - { + /* vulnerable 0: None 1: Both 2: NS 3: EW */ - /* Find best contract for current side that beats current contract. - Choose highest contract if results are equal. */ + /* The code for calculation of par score / contracts is based upon the + perl code written by Matthew Kidd ACBLmerge. He has kindly given me + permission to include a C++ adaptation in DDS. */ - k = (i + current_side) % 2; + /* The Par function computes the par result and contracts. */ - isvul = ((vulnerable == 1) || - (k ? (vulnerable == 3) : (vulnerable == 2))); + if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) + return check; - new_score_flag = 0; - prev_par_denom = par_denom[i]; - prev_par_tricks = par_tricks[i]; + if (int const check = par_vulnerable_checks(vulnerable); + check != RETURN_NO_FAULT) + return check; - /* Calculate tricks and score values and - store them for each denomination in structure par_suits[5]. */ + int denom_conv[5] = { 4, 0, 1, 2, 3 }; + /* Preallocate for efficiency. These hold result from last direction + (N-S or E-W) examined. */ + int j, k, m, isvul; + int current_side, both_sides_once_flag, denom_max = 0, max_lower; + int new_score_flag, sc1, sc2, sc3; + int prev_par_denom = 0, prev_par_tricks = 0; + int denom_filter[5] = { 0, 0, 0, 0, 0 }; + int no_filtered[2] = { 0, 0 }; + int no_of_denom[2]; + int best_par_score[2]; + int best_par_sacut[2]; + best_par_type best_par[5][2]; /* 1st index order number. */ - n = 0; - for (j = 0; j <= 4; j++) - { - if (denom_filter[j] == 0) - { - /* Current denomination is not filtered out. */ - t1 = k ? tablep->res_table[denom_conv[j]][1] : - tablep->res_table[denom_conv[j]][0]; - t2 = k ? tablep->res_table[denom_conv[j]][3] : - tablep->res_table[denom_conv[j]][2]; - tt = max(t1, t2); - /* tt is the maximum number of tricks current side can take in - denomination.*/ - - par_suits[n].suit = j; - par_suits[n].tricks = tt; - - if ((tt > par_tricks[i]) || ((tt == par_tricks[i]) && - (j < par_denom[i]))) - par_suits[n].score = rawscore(j, tt, isvul); - else - par_suits[n].score = rawscore(-1, prev_par_tricks - tt, isvul); - n++; - } - } + int ut = 0, t1, t2, tt, score, dr, tu, tu_max, t3[5], t4[5], n; + par_suits_type par_suits[5]; - /* Sort the items in the par_suits structure with decreasing order - of the values on the scores. */ + int par_denom[2] = { -1, -1 }; /* 0-4 = NT,S,H,D,C */ + int par_tricks[2] = { 6, 6 }; /* Initial "contract" beats 0 NT */ + int par_score[2] = { 0, 0 }; + int par_sacut[2] = { 0, 0 }; /* Undertricks for sacrifice (0 if not sac) */ - for (int s = 1; s < n; s++) - { - par_suits_type tmp = par_suits[s]; - int r = s; - for (; r && tmp.score > par_suits[r - 1].score; --r) - par_suits[r] = par_suits[r - 1]; - par_suits[r] = tmp; - } - /* Do the iteration as before but now in the order of the sorted - denominations. */ + /* Find best par result for N-S (i==0) or E-W (i==1). These will + nearly always be the same, but when we have a "hot" situation + they will not be. */ - for (m = 0; m < n; m++) - { - j = par_suits[m].suit; - tt = par_suits[m].tricks; - - if ((tt > par_tricks[i]) || ((tt == par_tricks[i]) && - (j < par_denom[i]))) - { - /* Can bid higher and make contract.*/ - score = rawscore(j, tt, isvul); - } - else - { - /* Bidding higher in this denomination will not beat previous - denomination and may be a sacrifice. */ - ut = prev_par_tricks - tt; - if (j >= prev_par_denom) - { - /* Sacrifices higher than 7N are not permitted (but long ago - the official rules did not prohibit bidding higher than 7N!) */ - if (prev_par_tricks == 13) - continue; - /* It will be necessary to bid one level higher, resulting in - one more undertrick. */ - ut++; - } - /* Not a sacrifice (due to par_tricks > prev_par_tricks) */ - if (ut <= 0) - continue; - /* Compute sacrifice.*/ - score = rawscore(-1, ut, isvul); - } + for (int i = 0; i <= 1; i++) + { + /* Start with the with the offensive side (current_side = 0) and + alternate between sides seeking the to improve the result for the + current side.*/ - if (current_side == 1) - score = -score; + no_filtered[i] = 0; + for (m = 0; m <= 4; m++) + denom_filter[m] = 0; - if (((current_side == 0) && (score > par_score[i])) || - ((current_side == 1) && (score < par_score[i]))) + current_side = 0; + both_sides_once_flag = 0; + while (1) { - new_score_flag = 1; - par_score[i] = score; - par_denom[i] = j; - - if (((current_side == 0) && (score > 0)) || - ((current_side == 1) && (score < 0))) - { - /* New par score from a making contract. - Can immediately update since score at same level in higher - ranking suit is always >= score in lower ranking suit and - better than any sacrifice. */ - - par_tricks[i] = tt; - par_sacut[i] = 0; - } - else - { - par_tricks[i] = tt + ut; - par_sacut[i] = ut; - } - } - } - - if (!new_score_flag && both_sides_once_flag) - { - if (no_filtered[i] == 0) - { - best_par_score[i] = par_score[i]; - if (best_par_score[i] == 0) - break; - best_par_sacut[i] = par_sacut[i]; - no_of_denom[i] = 0; - } - else if (best_par_score[i] != par_score[i]) - break; - if (no_filtered[i] >= 5) - break; - denom_filter[par_denom[i]] = 1; - no_filtered[i]++; - best_par[no_of_denom[i]][i].par_denom = par_denom[i]; - best_par[no_of_denom[i]][i].par_tricks = par_tricks[i]; - no_of_denom[i]++; - both_sides_once_flag = 0; - current_side = 0; - par_denom[i] = -1; - par_tricks[i] = 6; - par_score[i] = 0; - par_sacut[i] = 0; - } - else - { - both_sides_once_flag = 1; - current_side = 1 - current_side; - } - } - } - - /* Output: "best par score" */ - sidesRes[0].score = best_par_score[0]; - sidesRes[1].score = best_par_score[1]; - - if (best_par_score[0] == 0) - { - /* Neither side can make anything.*/ - sidesRes[0].contracts[0].denom = 0; - sidesRes[0].contracts[0].level = 0; - sidesRes[0].contracts[0].over_tricks = 0; - sidesRes[0].contracts[0].under_tricks = 0; - sidesRes[0].contracts[0].seats = 0; - sidesRes[0].number = 1; - sidesRes[1].contracts[0].denom = 0; - sidesRes[1].contracts[0].level = 0; - sidesRes[1].contracts[0].over_tricks = 0; - sidesRes[1].contracts[0].under_tricks = 0; - sidesRes[1].contracts[0].seats = 0; - sidesRes[1].number = 1; - return RETURN_NO_FAULT; - } + /* Find best contract for current side that beats current contract. + Choose highest contract if results are equal. */ - for (int i = 0; i <= 1; i++) - { - sidesRes[i].number = no_of_denom[i]; - sidesRes[i].score = best_par_score[i]; + k = (i + current_side) % 2; - if (best_par_sacut[i] > 0) - { - /* Sacrifice */ - dr = (best_par_score[i] > 0) ? 0 : 1; - /* Sort the items in the best_par structure with increasing order - of the values on denom. */ - - for (int s = 1; s < no_of_denom[i]; s++) - { - best_par_type tmp = best_par[s][i]; - int r = s; - for (; r && tmp.par_denom < best_par[r - 1][i].par_denom; --r) - best_par[r][i] = best_par[r - 1][i]; - best_par[r][i] = tmp; - } + isvul = ((vulnerable == 1) || + (k ? (vulnerable == 3) : (vulnerable == 2))); - for (m = 0; m < no_of_denom[i]; m++) - { + new_score_flag = 0; + prev_par_denom = par_denom[i]; + prev_par_tricks = par_tricks[i]; - j = best_par[m][i].par_denom; + /* Calculate tricks and score values and + store them for each denomination in structure par_suits[5]. */ - t1 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][0] : - tablep->res_table[denom_conv[j]][1]; - t2 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][2] : - tablep->res_table[denom_conv[j]][3]; - tt = (t1 > t2) ? t1 : t2; + n = 0; + for (j = 0; j <= 4; j++) + { + if (denom_filter[j] == 0) + { + /* Current denomination is not filtered out. */ + t1 = k ? tablep->res_table[denom_conv[j]][1] : + tablep->res_table[denom_conv[j]][0]; + t2 = k ? tablep->res_table[denom_conv[j]][3] : + tablep->res_table[denom_conv[j]][2]; + tt = max(t1, t2); + /* tt is the maximum number of tricks current side can take in + denomination.*/ - SideSeats(dr, i, t1, t2, m, sidesRes); - sidesRes[i].contracts[m].denom = j; - sidesRes[i].contracts[m].level = best_par[m][i].par_tricks - 6; - sidesRes[i].contracts[m].over_tricks = 0; - sidesRes[i].contracts[m].under_tricks = best_par_sacut[i]; + par_suits[n].suit = j; + par_suits[n].tricks = tt; - } - } - else - { - /* Par contract is a makeable contract.*/ - - dr = (best_par_score[i] < 0) ? 0 : 1; - - tu_max = 0; - for (m = 0; m <= 4; m++) - { - t3[m] = ((dr + i) % 2 == 0) ? tablep->res_table[denom_conv[m]][0] : - tablep->res_table[denom_conv[m]][1]; - t4[m] = ((dr + i) % 2 == 0) ? tablep->res_table[denom_conv[m]][2] : - tablep->res_table[denom_conv[m]][3]; - tu = (t3[m] > t4[m]) ? t3[m] : t4[m]; - if (tu > tu_max) - { - tu_max = tu; - denom_max = m; - /* Lowest if several denominations have max tricks. */ + if ((tt > par_tricks[i]) || ((tt == par_tricks[i]) && + (j < par_denom[i]))) + par_suits[n].score = rawscore(j, tt, isvul); + else + par_suits[n].score = rawscore(-1, prev_par_tricks - tt, isvul); + n++; + } + } + + /* Sort the items in the par_suits structure with decreasing order + of the values on the scores. */ + + for (int s = 1; s < n; s++) + { + par_suits_type tmp = par_suits[s]; + int r = s; + for (; r && tmp.score > par_suits[r - 1].score; --r) + par_suits[r] = par_suits[r - 1]; + par_suits[r] = tmp; + } + + /* Do the iteration as before but now in the order of the sorted + denominations. */ + + for (m = 0; m < n; m++) + { + j = par_suits[m].suit; + tt = par_suits[m].tricks; + + if ((tt > par_tricks[i]) || ((tt == par_tricks[i]) && + (j < par_denom[i]))) + { + /* Can bid higher and make contract.*/ + score = rawscore(j, tt, isvul); + } + else + { + /* Bidding higher in this denomination will not beat previous + denomination and may be a sacrifice. */ + ut = prev_par_tricks - tt; + if (j >= prev_par_denom) + { + /* Sacrifices higher than 7N are not permitted (but long ago + the official rules did not prohibit bidding higher than 7N!) */ + if (prev_par_tricks == 13) + continue; + /* It will be necessary to bid one level higher, resulting in + one more undertrick. */ + ut++; + } + /* Not a sacrifice (due to par_tricks > prev_par_tricks) */ + if (ut <= 0) + continue; + /* Compute sacrifice.*/ + score = rawscore(-1, ut, isvul); + } + + if (current_side == 1) + score = -score; + + if (((current_side == 0) && (score > par_score[i])) || + ((current_side == 1) && (score < par_score[i]))) + { + new_score_flag = 1; + par_score[i] = score; + par_denom[i] = j; + + if (((current_side == 0) && (score > 0)) || + ((current_side == 1) && (score < 0))) + { + /* New par score from a making contract. + Can immediately update since score at same level in higher + ranking suit is always >= score in lower ranking suit and + better than any sacrifice. */ + + par_tricks[i] = tt; + par_sacut[i] = 0; + } + else + { + par_tricks[i] = tt + ut; + par_sacut[i] = ut; + } + } + } + + + if (!new_score_flag && both_sides_once_flag) + { + if (no_filtered[i] == 0) + { + best_par_score[i] = par_score[i]; + if (best_par_score[i] == 0) + break; + best_par_sacut[i] = par_sacut[i]; + no_of_denom[i] = 0; + } + else if (best_par_score[i] != par_score[i]) + break; + if (no_filtered[i] >= 5) + break; + denom_filter[par_denom[i]] = 1; + no_filtered[i]++; + best_par[no_of_denom[i]][i].par_denom = par_denom[i]; + best_par[no_of_denom[i]][i].par_tricks = par_tricks[i]; + no_of_denom[i]++; + both_sides_once_flag = 0; + current_side = 0; + par_denom[i] = -1; + par_tricks[i] = 6; + par_score[i] = 0; + par_sacut[i] = 0; + } + else + { + both_sides_once_flag = 1; + current_side = 1 - current_side; + } } - } - - for (m = 0; m < no_of_denom[i]; m++) - { - j = best_par[m][i].par_denom; - - t1 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][0] : - tablep->res_table[denom_conv[j]][1]; - t2 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][2] : - tablep->res_table[denom_conv[j]][3]; - tt = (t1 > t2) ? t1 : t2; + } - SideSeats(dr, i, t1, t2, m, sidesRes); + /* Output: "best par score" */ + sidesRes[0].score = best_par_score[0]; + sidesRes[1].score = best_par_score[1]; - if (denom_max < j) - max_lower = best_par[m][i].par_tricks - tu_max - 1; - else - max_lower = best_par[m][i].par_tricks - tu_max; - - /* max_lower is the maximal contract lowering, otherwise - opponent contract is higher. It is already known that par_score - is high enough to make opponent sacrifices futile. - To find the actual contract lowering allowed, it must be - checked that the lowered contract still gets the score bonus - points that is present in par score.*/ - - sc2 = (best_par_score[i] >= 0 ? - best_par_score[i] : -best_par_score[i]); - /* Score for making the tentative lower par contract. */ - while (max_lower > 0) - { - if (denom_max < j) - sc1 = -rawscore(-1, - best_par[m][i].par_tricks - max_lower - tu_max, - VulnerDefSide(best_par_score[0] > 0, vulnerable)); - else - sc1 = -rawscore(-1, - best_par[m][i].par_tricks - max_lower - tu_max + 1, - VulnerDefSide(best_par_score[0] > 0, vulnerable)); - /* Score for undertricks needed to beat the tentative - lower par contract.*/ - - if (sc2 < sc1) - break; - else - max_lower--; - - /* Tentative lower par contract must be 1 trick higher, - since the cost for the sacrifice is too small. */ - } + if (best_par_score[0] == 0) + { + /* Neither side can make anything.*/ + sidesRes[0].contracts[0].denom = 0; + sidesRes[0].contracts[0].level = 0; + sidesRes[0].contracts[0].over_tricks = 0; + sidesRes[0].contracts[0].under_tricks = 0; + sidesRes[0].contracts[0].seats = 0; + sidesRes[0].number = 1; + sidesRes[1].contracts[0].denom = 0; + sidesRes[1].contracts[0].level = 0; + sidesRes[1].contracts[0].over_tricks = 0; + sidesRes[1].contracts[0].under_tricks = 0; + sidesRes[1].contracts[0].seats = 0; + sidesRes[1].number = 1; + return RETURN_NO_FAULT; + } - int opp_tricks = max(t3[j], t4[j]); + for (int i = 0; i <= 1; i++) + { + sidesRes[i].number = no_of_denom[i]; + sidesRes[i].score = best_par_score[i]; - while (max_lower > 0) + if (best_par_sacut[i] > 0) { - sc3 = -rawscore(-1, - best_par[m][i].par_tricks - max_lower - opp_tricks, - VulnerDefSide(best_par_score[0] > 0, vulnerable)); - - /* If opponents to side with par score start the bidding - and has a sacrifice in the par denom on the same trick level - as implied by current max_lower, then max_lower must be - decremented. */ - - if ((sc2 > sc3) && (best_par_score[i] < 0)) - /* Opposite side with best par score starts the bidding. */ - max_lower--; - else - break; + /* Sacrifice */ + dr = (best_par_score[i] > 0) ? 0 : 1; + /* Sort the items in the best_par structure with increasing order + of the values on denom. */ + + for (int s = 1; s < no_of_denom[i]; s++) + { + best_par_type tmp = best_par[s][i]; + int r = s; + for (; r && tmp.par_denom < best_par[r - 1][i].par_denom; --r) + best_par[r][i] = best_par[r - 1][i]; + best_par[r][i] = tmp; + } + + for (m = 0; m < no_of_denom[i]; m++) + { + + j = best_par[m][i].par_denom; + + t1 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][0] : + tablep->res_table[denom_conv[j]][1]; + t2 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][2] : + tablep->res_table[denom_conv[j]][3]; + tt = (t1 > t2) ? t1 : t2; + + SideSeats(dr, i, t1, t2, m, sidesRes); + sidesRes[i].contracts[m].denom = j; + sidesRes[i].contracts[m].level = best_par[m][i].par_tricks - 6; + sidesRes[i].contracts[m].over_tricks = 0; + sidesRes[i].contracts[m].under_tricks = best_par_sacut[i]; + + } } - - switch (j) + else { - case 0: - k = 0; - break; - case 1: - case 2: - k = 1; - break; - case 3: - case 4: - k = 2; - break; - default: - return RETURN_UNKNOWN_FAULT; - // j not in (0..4) + /* Par contract is a makeable contract.*/ + + dr = (best_par_score[i] < 0) ? 0 : 1; + + tu_max = 0; + for (m = 0; m <= 4; m++) + { + t3[m] = ((dr + i) % 2 == 0) ? tablep->res_table[denom_conv[m]][0] : + tablep->res_table[denom_conv[m]][1]; + t4[m] = ((dr + i) % 2 == 0) ? tablep->res_table[denom_conv[m]][2] : + tablep->res_table[denom_conv[m]][3]; + tu = (t3[m] > t4[m]) ? t3[m] : t4[m]; + if (tu > tu_max) + { + tu_max = tu; + denom_max = m; + /* Lowest if several denominations have max tricks. */ + } + } + + for (m = 0; m < no_of_denom[i]; m++) + { + j = best_par[m][i].par_denom; + + t1 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][0] : + tablep->res_table[denom_conv[j]][1]; + t2 = ((dr + i) % 2) ? tablep->res_table[denom_conv[j]][2] : + tablep->res_table[denom_conv[j]][3]; + tt = (t1 > t2) ? t1 : t2; + + SideSeats(dr, i, t1, t2, m, sidesRes); + + if (denom_max < j) + max_lower = best_par[m][i].par_tricks - tu_max - 1; + else + max_lower = best_par[m][i].par_tricks - tu_max; + + /* max_lower is the maximal contract lowering, otherwise + opponent contract is higher. It is already known that par_score + is high enough to make opponent sacrifices futile. + To find the actual contract lowering allowed, it must be + checked that the lowered contract still gets the score bonus + points that is present in par score.*/ + + sc2 = (best_par_score[i] >= 0 ? + best_par_score[i] : -best_par_score[i]); + /* Score for making the tentative lower par contract. */ + while (max_lower > 0) + { + if (denom_max < j) + sc1 = -rawscore(-1, + best_par[m][i].par_tricks - max_lower - tu_max, + VulnerDefSide(best_par_score[0] > 0, vulnerable)); + else + sc1 = -rawscore(-1, + best_par[m][i].par_tricks - max_lower - tu_max + 1, + VulnerDefSide(best_par_score[0] > 0, vulnerable)); + /* Score for undertricks needed to beat the tentative + lower par contract.*/ + + if (sc2 < sc1) + break; + else + max_lower--; + + /* Tentative lower par contract must be 1 trick higher, + since the cost for the sacrifice is too small. */ + } + + int opp_tricks = max(t3[j], t4[j]); + + while (max_lower > 0) + { + sc3 = -rawscore(-1, + best_par[m][i].par_tricks - max_lower - opp_tricks, + VulnerDefSide(best_par_score[0] > 0, vulnerable)); + + /* If opponents to side with par score start the bidding + and has a sacrifice in the par denom on the same trick level + as implied by current max_lower, then max_lower must be + decremented. */ + + if ((sc2 > sc3) && (best_par_score[i] < 0)) + /* Opposite side with best par score starts the bidding. */ + max_lower--; + else + break; + } + + switch (j) + { + case 0: + k = 0; + break; + case 1: + case 2: + k = 1; + break; + case 3: + case 4: + k = 2; + break; + default: + return RETURN_UNKNOWN_FAULT; + // j not in (0..4) + } + + max_lower = min(max_low[k][best_par[m][i].par_tricks - 6], + max_lower); + + sidesRes[i].contracts[m].denom = j; + sidesRes[i].contracts[m].under_tricks = 0; + + CalcOverTricks(i, max_lower, best_par[m][i].par_tricks, + m, sidesRes); + + sidesRes[i].contracts[m].level = best_par[m][i].par_tricks - 6 - + sidesRes[i].contracts[m].over_tricks; + + } } - - max_lower = min(max_low[k][best_par[m][i].par_tricks - 6], - max_lower); - - sidesRes[i].contracts[m].denom = j; - sidesRes[i].contracts[m].under_tricks = 0; - - CalcOverTricks(i, max_lower, best_par[m][i].par_tricks, - m, sidesRes); - - sidesRes[i].contracts[m].level = best_par[m][i].par_tricks - 6 - - sidesRes[i].contracts[m].over_tricks; - - } } - } - /* Filter out par contracts where the other side has a higher par - contract. This can happen when par scores differ for the two sides. */ + /* Filter out par contracts where the other side has a higher par + contract. This can happen when par scores differ for the two sides. */ - int opp_side[2]; + int opp_side[2]; - int denom_to_remove[2][5] = { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; + int denom_to_remove[2][5] = { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; - int dom_denom[2] = { -1, -1 }; /* Dominating denom */ + int dom_denom[2] = { -1, -1 }; /* Dominating denom */ - int dom_level[2] = { -1, -1 }; /* Dominating level */ + int dom_level[2] = { -1, -1 }; /* Dominating level */ - for (int i = 0; i < 2; i++) - { - k = 0; - opp_side[i] = (i == 0) ? 1 : 0; - - while (k < sidesRes[opp_side[i]].number) + for (int i = 0; i < 2; i++) { - j = sidesRes[opp_side[i]].contracts[k].denom; - int ss = sidesRes[opp_side[i]].contracts[k].level + + k = 0; + opp_side[i] = (i == 0) ? 1 : 0; + + while (k < sidesRes[opp_side[i]].number) + { + j = sidesRes[opp_side[i]].contracts[k].denom; + int ss = sidesRes[opp_side[i]].contracts[k].level + sidesRes[opp_side[i]].contracts[k].over_tricks; - if (ss > dom_level[opp_side[i]] || + if (ss > dom_level[opp_side[i]] || (ss == dom_level[opp_side[i]] && j < dom_denom[opp_side[i]])) - { - if (((i == 0) && + { + if (((i == 0) && ((sidesRes[opp_side[i]].contracts[k].seats % 2) != 0)) || ((i == 1) && ((sidesRes[opp_side[i]].contracts[k].seats % 2) == 0))) - { - dom_denom[opp_side[i]] = j; - dom_level[opp_side[i]] = - sidesRes[opp_side[i]].contracts[k].level + - sidesRes[opp_side[i]].contracts[k].over_tricks; + { + dom_denom[opp_side[i]] = j; + dom_level[opp_side[i]] = + sidesRes[opp_side[i]].contracts[k].level + + sidesRes[opp_side[i]].contracts[k].over_tricks; + } + } + k++; } - } - k++; } - } - if ((dom_denom[0] != -1) && (dom_denom[1] != -1)) - { - - /* Remove par contracts that can be dominated by the other side. */ - - for (int i = 0; i < 2; i++) + if ((dom_denom[0] != -1) && (dom_denom[1] != -1)) { - opp_side[i] = (i == 0) ? 1 : 0; - for (k = 0; k < sidesRes[i].number; k++) - { - j = sidesRes[i].contracts[k].denom; + /* Remove par contracts that can be dominated by the other side. */ - if (((sidesRes[i].contracts[k].level + - sidesRes[i].contracts[k].over_tricks) < - dom_level[opp_side[i]]) || - (((sidesRes[i].contracts[k].level + - sidesRes[i].contracts[k].over_tricks) == dom_level[opp_side[i]]) - && (dom_denom[opp_side[i]] < sidesRes[i].contracts[k].denom))) - denom_to_remove[i][j] = 1; - } + for (int i = 0; i < 2; i++) + { + opp_side[i] = (i == 0) ? 1 : 0; - int mm = 0; + for (k = 0; k < sidesRes[i].number; k++) + { + j = sidesRes[i].contracts[k].denom; - for (k = 0; k < sidesRes[i].number; k++) - { - j = sidesRes[i].contracts[k].denom; - if (denom_to_remove[i][j] != 1) - { - sidesRes[i].contracts[mm] = sidesRes[i].contracts[k]; - mm++; + if (((sidesRes[i].contracts[k].level + + sidesRes[i].contracts[k].over_tricks) < + dom_level[opp_side[i]]) || + (((sidesRes[i].contracts[k].level + + sidesRes[i].contracts[k].over_tricks) == dom_level[opp_side[i]]) + && (dom_denom[opp_side[i]] < sidesRes[i].contracts[k].denom))) + denom_to_remove[i][j] = 1; + } + + int mm = 0; + + for (k = 0; k < sidesRes[i].number; k++) + { + j = sidesRes[i].contracts[k].denom; + if (denom_to_remove[i][j] != 1) + { + sidesRes[i].contracts[mm] = sidesRes[i].contracts[k]; + mm++; + } + } + sidesRes[i].number = mm; } - } - sidesRes[i].number = mm; - } - } + } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } #else int STDCALL SidesParBin( - DdTableResults const * tablep, - parResultsMaster sidesRes[2], - int vulnerable) + DdTableResults const * tablep, + parResultsMaster sidesRes[2], + int vulnerable) { - if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) - return check; + if (int const check = par_table_checks(tablep); check != RETURN_NO_FAULT) + return check; - if (int const check = par_vulnerable_checks(vulnerable); - check != RETURN_NO_FAULT) - return check; + if (int const check = par_vulnerable_checks(vulnerable); + check != RETURN_NO_FAULT) + return check; - int res, h, hbest[2], i, k, m, index; - parResultsMaster parRes2[4]; - int cross_index[4][5] = { - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 } }; + int res, h, hbest[2], i, k, m, index; + parResultsMaster parRes2[4]; + int cross_index[4][5] = { + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }; - for (h = 0; h <= 3; h++) - { + for (h = 0; h <= 3; h++) + { - res = DealerParBin(tablep, &parRes2[h], h, vulnerable); + res = DealerParBin(tablep, &parRes2[h], h, vulnerable); - if (res != RETURN_NO_FAULT) - return res; + if (res != RETURN_NO_FAULT) + return res; - if (parRes2[h].score == 0) - { - sidesRes[0].number = 1; - sidesRes[0].score = 0; - sidesRes[1].number = 1; - sidesRes[1].score = 0; + if (parRes2[h].score == 0) + { + sidesRes[0].number = 1; + sidesRes[0].score = 0; + sidesRes[1].number = 1; + sidesRes[1].score = 0; - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; + } } - } - - for (h = 0; h <= 3; h++) - { - for (k = 0; k < parRes2[h].number; k++) - /* Corresponding index and denom */ - cross_index[h][parRes2[h].contracts[k].denom] = k; - } - for (i = 0; i <= 1; i++) - /* Sides 0 and 1 */ - { - if (parRes2[2 + i].score != parRes2[i].score) + for (h = 0; h <= 3; h++) { - if (i == 0) - { - if (parRes2[2 + i].score > parRes2[i].score) - hbest[i] = 2 + i; - else - hbest[i] = i; - } - else - { - if (parRes2[2 + i].score < parRes2[i].score) - hbest[i] = 2 + i; - else - hbest[i] = i; - } - - sidesRes[i].number = parRes2[hbest[i]].number; - if (i == 0) - sidesRes[i].score = parRes2[hbest[i]].score; - else - sidesRes[i].score = -parRes2[hbest[i]].score; - - - for (k = 0; k < sidesRes[i].number; k++) - sidesRes[i].contracts[k] = parRes2[hbest[i]].contracts[k]; + for (k = 0; k < parRes2[h].number; k++) + /* Corresponding index and denom */ + cross_index[h][parRes2[h].contracts[k].denom] = k; } - else - { - if (i == 0) - sidesRes[i].score = parRes2[0].score; - else - sidesRes[i].score = -parRes2[1].score; - - index = 0; - for (m = 0; m < 5; m++) - { - /* Iterate through denoms */ - - if ((cross_index[2 + i][m] == -1) && (cross_index[i][m] == -1)) - { - continue; - } - else if (cross_index[2 + i][m] == -1) - { - if (((i == 0) && ((parRes2[i].contracts[index].seats % 2) != 0)) || - ((i == 1) && ((parRes2[i].contracts[index].seats % 2) == 0))) - continue; - sidesRes[i].contracts[index] = - parRes2[i].contracts[cross_index[i][m]]; - index++; - } - else if (cross_index[i][m] == -1) - { - if (((i == 0) && ((parRes2[i].contracts[index].seats % 2) != 0)) || - ((i == 1) && ((parRes2[i].contracts[index].seats % 2) == 0))) - continue; - sidesRes[i].contracts[index] = - parRes2[2 + i].contracts[cross_index[2 + i][m]]; - index++; - } - else if (parRes2[2 + i].contracts[cross_index[2 + i][m]].level == - parRes2[i].contracts[cross_index[i][m]].level) + for (i = 0; i <= 1; i++) + /* Sides 0 and 1 */ + { + if (parRes2[2 + i].score != parRes2[i].score) { - sidesRes[i].contracts[index] = - parRes2[i].contracts[cross_index[i][m]]; - index++; + if (i == 0) + { + if (parRes2[2 + i].score > parRes2[i].score) + hbest[i] = 2 + i; + else + hbest[i] = i; + } + else + { + if (parRes2[2 + i].score < parRes2[i].score) + hbest[i] = 2 + i; + else + hbest[i] = i; + } + + sidesRes[i].number = parRes2[hbest[i]].number; + if (i == 0) + sidesRes[i].score = parRes2[hbest[i]].score; + else + sidesRes[i].score = -parRes2[hbest[i]].score; + + + for (k = 0; k < sidesRes[i].number; k++) + sidesRes[i].contracts[k] = parRes2[hbest[i]].contracts[k]; } - else if (((i == 0) && (parRes2[i].score > 0) || - (i == 1) && (parRes2[i].score < 0)) && - (parRes2[2 + i].contracts[cross_index[2 + i][m]].level > - parRes2[i].contracts[cross_index[i][m]].level)) + else { - sidesRes[i].contracts[index] = - parRes2[i].contracts[cross_index[i][m]]; - index++; - } - else if (((i == 0) && (parRes2[i].score < 0) || (i == 1) && - (parRes2[i].score > 0)) && + if (i == 0) + sidesRes[i].score = parRes2[0].score; + else + sidesRes[i].score = -parRes2[1].score; + + index = 0; + + for (m = 0; m < 5; m++) + { + /* Iterate through denoms */ + + if ((cross_index[2 + i][m] == -1) && (cross_index[i][m] == -1)) + { + continue; + } + else if (cross_index[2 + i][m] == -1) + { + if (((i == 0) && ((parRes2[i].contracts[index].seats % 2) != 0)) || + ((i == 1) && ((parRes2[i].contracts[index].seats % 2) == 0))) + continue; + sidesRes[i].contracts[index] = + parRes2[i].contracts[cross_index[i][m]]; + index++; + } + else if (cross_index[i][m] == -1) + { + if (((i == 0) && ((parRes2[i].contracts[index].seats % 2) != 0)) || + ((i == 1) && ((parRes2[i].contracts[index].seats % 2) == 0))) + continue; + sidesRes[i].contracts[index] = + parRes2[2 + i].contracts[cross_index[2 + i][m]]; + index++; + } + else if (parRes2[2 + i].contracts[cross_index[2 + i][m]].level == + parRes2[i].contracts[cross_index[i][m]].level) + { + sidesRes[i].contracts[index] = + parRes2[i].contracts[cross_index[i][m]]; + index++; + } + else if (((i == 0) && (parRes2[i].score > 0) || + (i == 1) && (parRes2[i].score < 0)) && + (parRes2[2 + i].contracts[cross_index[2 + i][m]].level > + parRes2[i].contracts[cross_index[i][m]].level)) + { + sidesRes[i].contracts[index] = + parRes2[i].contracts[cross_index[i][m]]; + index++; + } + else if (((i == 0) && (parRes2[i].score < 0) || (i == 1) && + (parRes2[i].score > 0)) && (parRes2[2 + i].contracts[cross_index[2 + i][m]].level < parRes2[i].contracts[cross_index[i][m]].level)) - { - sidesRes[i].contracts[index] = - parRes2[i].contracts[cross_index[i][m]]; - index++; + { + sidesRes[i].contracts[index] = + parRes2[i].contracts[cross_index[i][m]]; + index++; + } + else + { + sidesRes[i].contracts[index] = + parRes2[2 + i].contracts[cross_index[2 + i][m]]; + index++; + } + } + sidesRes[i].number = index; } - else - { - sidesRes[i].contracts[index] = - parRes2[2 + i].contracts[cross_index[2 + i][m]]; - index++; - } - } - sidesRes[i].number = index; } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } #endif int rawscore(int denom, int tricks, int isvul) { - int game_bonus, level, score; + int game_bonus, level, score; - /* Computes score for undoubled contract or a doubled contract with + /* Computes score for undoubled contract or a doubled contract with for a given number of undertricks. These are the only possibilities for a par contract (aside from a passed out hand). @@ -868,730 +868,730 @@ int rawscore(int denom, int tricks, int isvul) undertricks. isvul - True if vulnerable */ - if (denom == -1) - { - if (isvul) - return -300 * tricks + 100; - if (tricks <= 3) - return -200 * tricks + 100; - return -300 * tricks + 400; - } - else - { - level = tricks - 6; - game_bonus = 0; - if (denom == 0) + if (denom == -1) { - score = 10 + 30 * level; - if (level >= 3) - game_bonus = 1; - } - else if ((denom == 1) || (denom == 2)) - { - score = 30 * level; - if (level >= 4) - game_bonus = 1; + if (isvul) + return -300 * tricks + 100; + if (tricks <= 3) + return -200 * tricks + 100; + return -300 * tricks + 400; } else { - score = 20 * level; - if (level >= 5) - game_bonus = 1; - } - if (game_bonus) - { - score += (isvul ? 500 : 300); - } - else - score += 50; + level = tricks - 6; + game_bonus = 0; + if (denom == 0) + { + score = 10 + 30 * level; + if (level >= 3) + game_bonus = 1; + } + else if ((denom == 1) || (denom == 2)) + { + score = 30 * level; + if (level >= 4) + game_bonus = 1; + } + else + { + score = 20 * level; + if (level >= 5) + game_bonus = 1; + } + if (game_bonus) + { + score += (isvul ? 500 : 300); + } + else + score += 50; - if (level == 6) - { - score += (isvul ? 750 : 500); - } - else if (level == 7) - { - score += (isvul ? 1500 : 1000); + if (level == 6) + { + score += (isvul ? 750 : 500); + } + else if (level == 7) + { + score += (isvul ? 1500 : 1000); + } } - } - return score; + return score; } void SideSeats( - int dr, - int i, - int t1, - int t2, - int order, - ParResultsMaster sidesRes[2]) + int dr, + int i, + int t1, + int t2, + int order, + ParResultsMaster sidesRes[2]) { - if ((dr + i) % 2 ) - { - if (t1 == t2) - { - sidesRes[i].contracts[order].seats = 4; - } - else if (t1 > t2) - { - sidesRes[i].contracts[order].seats = 0; - } - else + if ((dr + i) % 2 ) { - sidesRes[i].contracts[order].seats = 2; - } - } - else - { - if (t1 == t2) - { - sidesRes[i].contracts[order].seats = 5; - } - else if (t1 > t2) - { - sidesRes[i].contracts[order].seats = 1; + if (t1 == t2) + { + sidesRes[i].contracts[order].seats = 4; + } + else if (t1 > t2) + { + sidesRes[i].contracts[order].seats = 0; + } + else + { + sidesRes[i].contracts[order].seats = 2; + } } else { - sidesRes[i].contracts[order].seats = 3; + if (t1 == t2) + { + sidesRes[i].contracts[order].seats = 5; + } + else if (t1 > t2) + { + sidesRes[i].contracts[order].seats = 1; + } + else + { + sidesRes[i].contracts[order].seats = 3; + } } - } - return; + return; } void CalcOverTricks( - int i, - int max_lower, - int tricks, - int order, - ParResultsMaster sidesRes[2]) + int i, + int max_lower, + int tricks, + int order, + ParResultsMaster sidesRes[2]) { - switch (tricks - 6) - { - case 5: - if (max_lower == 3) - { - sidesRes[i].contracts[order].over_tricks = 3; - } - else if (max_lower == 2) - { - sidesRes[i].contracts[order].over_tricks = 2; - } - else if (max_lower == 1) - { - sidesRes[i].contracts[order].over_tricks = 1; - } - else - { - sidesRes[i].contracts[order].over_tricks = 0; - } - break; - case 4: - if (max_lower == 3) - { - sidesRes[i].contracts[order].over_tricks = 3; - } - else if (max_lower == 2) - { - sidesRes[i].contracts[order].over_tricks = 2; - } - else if (max_lower == 1) - { - sidesRes[i].contracts[order].over_tricks = 1; - } - else - { - sidesRes[i].contracts[order].over_tricks = 0; - } - break; - case 3: - if (max_lower == 2) - { - sidesRes[i].contracts[order].over_tricks = 2; - } - else if (max_lower == 1) - { - sidesRes[i].contracts[order].over_tricks = 1; - } - else - { - sidesRes[i].contracts[order].over_tricks = 0; - } - break; - case 2: - if (max_lower == 1) - { - sidesRes[i].contracts[order].over_tricks = 1; - } - else - { - sidesRes[i].contracts[order].over_tricks = 0; - } - break; - default: - sidesRes[i].contracts[order].over_tricks = 0; - } - return; + switch (tricks - 6) + { + case 5: + if (max_lower == 3) + { + sidesRes[i].contracts[order].over_tricks = 3; + } + else if (max_lower == 2) + { + sidesRes[i].contracts[order].over_tricks = 2; + } + else if (max_lower == 1) + { + sidesRes[i].contracts[order].over_tricks = 1; + } + else + { + sidesRes[i].contracts[order].over_tricks = 0; + } + break; + case 4: + if (max_lower == 3) + { + sidesRes[i].contracts[order].over_tricks = 3; + } + else if (max_lower == 2) + { + sidesRes[i].contracts[order].over_tricks = 2; + } + else if (max_lower == 1) + { + sidesRes[i].contracts[order].over_tricks = 1; + } + else + { + sidesRes[i].contracts[order].over_tricks = 0; + } + break; + case 3: + if (max_lower == 2) + { + sidesRes[i].contracts[order].over_tricks = 2; + } + else if (max_lower == 1) + { + sidesRes[i].contracts[order].over_tricks = 1; + } + else + { + sidesRes[i].contracts[order].over_tricks = 0; + } + break; + case 2: + if (max_lower == 1) + { + sidesRes[i].contracts[order].over_tricks = 1; + } + else + { + sidesRes[i].contracts[order].over_tricks = 0; + } + break; + default: + sidesRes[i].contracts[order].over_tricks = 0; + } + return; } int VulnerDefSide(int side, int vulnerable) { - if (vulnerable == 0) - return 0; - else if (vulnerable == 1) - return 1; - else if (side) - { - /* N/S makes par contract. */ - if (vulnerable == 2) - return 0; - else - return 1; - } - else - { - if (vulnerable == 3) - return 0; + if (vulnerable == 0) + return 0; + else if (vulnerable == 1) + return 1; + else if (side) + { + /* N/S makes par contract. */ + if (vulnerable == 2) + return 0; + else + return 1; + } else - return 1; - } + { + if (vulnerable == 3) + return 0; + else + return 1; + } } int CalcMultiContracts(int max_lower, int tricks) { - int n; - - switch (tricks - 6) - { - case 5: - if (max_lower == 3) - { - n = 2345; - } - else if (max_lower == 2) - { - n = 345; - } - else if (max_lower == 1) - { - n = 45; - } - else - { - n = 5; - } - break; - case 4: - if (max_lower == 3) - { - n = 1234; - } - else if (max_lower == 2) - { - n = 234; - } - else if (max_lower == 1) - { - n = 34; - } - else - { - n = 4; - } - break; - case 3: - if (max_lower == 2) - { - n = 123; - } - else if (max_lower == 1) - { - n = 23; - } - else - { - n = 3; - } - break; - case 2: - if (max_lower == 1) - { - n = 12; - } - else - { - n = 2; - } - break; - default: - n = tricks - 6; - } - return n; + int n; + + switch (tricks - 6) + { + case 5: + if (max_lower == 3) + { + n = 2345; + } + else if (max_lower == 2) + { + n = 345; + } + else if (max_lower == 1) + { + n = 45; + } + else + { + n = 5; + } + break; + case 4: + if (max_lower == 3) + { + n = 1234; + } + else if (max_lower == 2) + { + n = 234; + } + else if (max_lower == 1) + { + n = 34; + } + else + { + n = 4; + } + break; + case 3: + if (max_lower == 2) + { + n = 123; + } + else if (max_lower == 1) + { + n = 23; + } + else + { + n = 3; + } + break; + case 2: + if (max_lower == 1) + { + n = 12; + } + else + { + n = 2; + } + break; + default: + n = tricks - 6; + } + return n; } int STDCALL CalcPar( - DdTableDeal tableDeal, - int vulnerable, - DdTableResults * tablep, - ParResults * presp) + DdTableDeal tableDeal, + int vulnerable, + DdTableResults * tablep, + ParResults * presp) { - int res; + int res; - res = CalcDDtable(tableDeal, tablep); + res = CalcDDtable(tableDeal, tablep); - if (res != 1) - return res; + if (res != 1) + return res; - res = Par(tablep, presp, vulnerable); + res = Par(tablep, presp, vulnerable); - return res; + return res; } int STDCALL DealerParBin( - DdTableResults const * tablep, - ParResultsMaster * presp, - int dealer, - int vulnerable) + DdTableResults const * tablep, + ParResultsMaster * presp, + int dealer, + int vulnerable) { - /* dealer 0: North 1: East 2: South 3: West */ - /* vulnerable 0: None 1: Both 2: NS 3: EW */ + /* dealer 0: North 1: East 2: South 3: West */ + /* vulnerable 0: None 1: Both 2: NS 3: EW */ - ParResultsDealer parResDealer; - parContr2Type parContr2[10]; - int k, delta; + ParResultsDealer parResDealer; + parContr2Type parContr2[10]; + int k, delta; - int res = DealerPar(tablep, &parResDealer, dealer, vulnerable); + int res = DealerPar(tablep, &parResDealer, dealer, vulnerable); - if (res != RETURN_NO_FAULT) - { - return res; - } - - if (parResDealer.contracts[0][0] == 'p') - { - /*Passed out, i.e. no par contract can be found.*/ - presp->number = 1; - presp->score = 0; - return RETURN_NO_FAULT; - } - - - for (k = 0; k < parResDealer.number; k++) - { - - for (int u = 0; u < 10; u++) - parContr2[k].contracts[u] = parResDealer.contracts[k][u]; - - if (parResDealer.contracts[k][1] == 'N') - parContr2[k].denom = 0; - else if (parResDealer.contracts[k][1] == 'S') - parContr2[k].denom = 1; - else if (parResDealer.contracts[k][1] == 'H') - parContr2[k].denom = 2; - else if (parResDealer.contracts[k][1] == 'D') - parContr2[k].denom = 3; - else if (parResDealer.contracts[k][1] == 'C') - parContr2[k].denom = 4; - } - - for (int s = 1; s < parResDealer.number; s++) - { - parContr2Type tmp = parContr2[s]; - int r = s; - for (; r && tmp.denom < parContr2[r - 1].denom; --r) - parContr2[r] = parContr2[r - 1]; - parContr2[r] = tmp; - } - - presp->score = parResDealer.score; - presp->number = parResDealer.number; - - for (k = 0; k < parResDealer.number; k++) - { - delta = 1; - - presp->contracts[k].level = int(parContr2[k].contracts[0] - '0'); - - switch (parContr2[k].contracts[1]) + if (res != RETURN_NO_FAULT) { - case 'N': - presp->contracts[k].denom = 0; - break; - case 'S': - presp->contracts[k].denom = 1; - break; - case 'H': - presp->contracts[k].denom = 2; - break; - case 'D': - presp->contracts[k].denom = 3; - break; - case 'C': - presp->contracts[k].denom = 4; - break; - default: - return RETURN_UNKNOWN_FAULT; - // denomination not in (NSHDC) + return res; } - if (strstr(parContr2[k].contracts, "NS")) - presp->contracts[k].seats = 4; - else if (strstr(parContr2[k].contracts, "EW")) - presp->contracts[k].seats = 5; - else if (strstr(parContr2[k].contracts, "-N")) - { - presp->contracts[k].seats = 0; - delta = 0; - } - else if (strstr(parContr2[k].contracts, "-E")) + if (parResDealer.contracts[0][0] == 'p') { - presp->contracts[k].seats = 1; - delta = 0; + /*Passed out, i.e. no par contract can be found.*/ + presp->number = 1; + presp->score = 0; + return RETURN_NO_FAULT; } - else if (strstr(parContr2[k].contracts, "-S")) - { - presp->contracts[k].seats = 2; - delta = 0; - } - else if (strstr(parContr2[k].contracts, "-W")) + + + for (k = 0; k < parResDealer.number; k++) { - presp->contracts[k].seats = 3; - delta = 0; + + for (int u = 0; u < 10; u++) + parContr2[k].contracts[u] = parResDealer.contracts[k][u]; + + if (parResDealer.contracts[k][1] == 'N') + parContr2[k].denom = 0; + else if (parResDealer.contracts[k][1] == 'S') + parContr2[k].denom = 1; + else if (parResDealer.contracts[k][1] == 'H') + parContr2[k].denom = 2; + else if (parResDealer.contracts[k][1] == 'D') + parContr2[k].denom = 3; + else if (parResDealer.contracts[k][1] == 'C') + parContr2[k].denom = 4; } - if (parResDealer.contracts[0][2] == '*') + for (int s = 1; s < parResDealer.number; s++) { - /* Sacrifice */ - presp->contracts[k].under_tricks = - static_cast(parContr2[k].contracts[6 + delta] - '0'); - presp->contracts[k].over_tricks = 0; + parContr2Type tmp = parContr2[s]; + int r = s; + for (; r && tmp.denom < parContr2[r - 1].denom; --r) + parContr2[r] = parContr2[r - 1]; + parContr2[r] = tmp; } - else - /* Make */ + presp->score = parResDealer.score; + presp->number = parResDealer.number; + + for (k = 0; k < parResDealer.number; k++) { - if (strchr(parContr2[k].contracts, '+')) - presp->contracts[k].over_tricks = - static_cast(parContr2[k].contracts[5 + delta] - '0'); - else - presp->contracts[k].over_tricks = 0; - presp->contracts[k].under_tricks = 0; + delta = 1; + + presp->contracts[k].level = int(parContr2[k].contracts[0] - '0'); + + switch (parContr2[k].contracts[1]) + { + case 'N': + presp->contracts[k].denom = 0; + break; + case 'S': + presp->contracts[k].denom = 1; + break; + case 'H': + presp->contracts[k].denom = 2; + break; + case 'D': + presp->contracts[k].denom = 3; + break; + case 'C': + presp->contracts[k].denom = 4; + break; + default: + return RETURN_UNKNOWN_FAULT; + // denomination not in (NSHDC) + } + + if (strstr(parContr2[k].contracts, "NS")) + presp->contracts[k].seats = 4; + else if (strstr(parContr2[k].contracts, "EW")) + presp->contracts[k].seats = 5; + else if (strstr(parContr2[k].contracts, "-N")) + { + presp->contracts[k].seats = 0; + delta = 0; + } + else if (strstr(parContr2[k].contracts, "-E")) + { + presp->contracts[k].seats = 1; + delta = 0; + } + else if (strstr(parContr2[k].contracts, "-S")) + { + presp->contracts[k].seats = 2; + delta = 0; + } + else if (strstr(parContr2[k].contracts, "-W")) + { + presp->contracts[k].seats = 3; + delta = 0; + } + + if (parResDealer.contracts[0][2] == '*') + { + /* Sacrifice */ + presp->contracts[k].under_tricks = + static_cast(parContr2[k].contracts[6 + delta] - '0'); + presp->contracts[k].over_tricks = 0; + } + else + /* Make */ + + { + if (strchr(parContr2[k].contracts, '+')) + presp->contracts[k].over_tricks = + static_cast(parContr2[k].contracts[5 + delta] - '0'); + else + presp->contracts[k].over_tricks = 0; + presp->contracts[k].under_tricks = 0; + } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL SidesPar( - DdTableResults const * tablep, - ParResultsDealer sidesRes[2], - int vulnerable) + DdTableResults const * tablep, + ParResultsDealer sidesRes[2], + int vulnerable) { - ParResultsMaster parm[2]; - int res, i, k; - constexpr size_t buf_size = 13; - char buff[buf_size]; + ParResultsMaster parm[2]; + int res, i, k; + constexpr size_t buf_size = 13; + char buff[buf_size]; - res = SidesParBin(tablep, parm, vulnerable); + res = SidesParBin(tablep, parm, vulnerable); - if (res != RETURN_NO_FAULT) - return res; - - /*Convert the bin results to DealerPar format. */ - for (i = 0; i <= 1; i++) - { - sidesRes[i].score = parm[i].score; - sidesRes[i].number = parm[i].number; + if (res != RETURN_NO_FAULT) + return res; - for (k = 0; k < sidesRes[i].number; k++) + /*Convert the bin results to DealerPar format. */ + for (i = 0; i <= 1; i++) { - snprintf(sidesRes[i].contracts[k], 10, "%d", parm[i].contracts[k].level); - switch (parm[i].contracts[k].denom) - { - case 0: - strcat(sidesRes[i].contracts[k], "N"); - break; - case 1: - strcat(sidesRes[i].contracts[k], "S"); - break; - case 2: - strcat(sidesRes[i].contracts[k], "H"); - break; - case 3: - strcat(sidesRes[i].contracts[k], "D"); - break; - case 4: - strcat(sidesRes[i].contracts[k], "C"); - break; - default: - // Cannot happen, but keeps gcc happy. - break; - } - if (parm[i].contracts[k].under_tricks > 0) - /* Sacrifice */ - strcat(sidesRes[i].contracts[k], "*"); - switch (parm[i].contracts[k].seats) - { - case 0: - strcat(sidesRes[i].contracts[k], "-N"); - break; - case 1: - strcat(sidesRes[i].contracts[k], "-E"); - break; - case 2: - strcat(sidesRes[i].contracts[k], "-S"); - break; - case 3: - strcat(sidesRes[i].contracts[k], "-W"); - break; - case 4: - strcat(sidesRes[i].contracts[k], "-NS"); - break; - case 5: - strcat(sidesRes[i].contracts[k], "-EW"); - break; - default: - // Cannot happen, but keeps gcc happy. - break; - } - if (parm[i].contracts[k].under_tricks > 0) - { - /* Sacrifice */ - snprintf(buff, buf_size, "-%d", parm[i].contracts[k].under_tricks); - strcat(sidesRes[i].contracts[k], buff); - } - else if (parm[i].contracts[k].over_tricks > 0) - { - /* Make */ - snprintf(buff, buf_size, "+%d", parm[i].contracts[k].over_tricks); - strcat(sidesRes[i].contracts[k], buff); - } + sidesRes[i].score = parm[i].score; + sidesRes[i].number = parm[i].number; + + for (k = 0; k < sidesRes[i].number; k++) + { + snprintf(sidesRes[i].contracts[k], 10, "%d", parm[i].contracts[k].level); + switch (parm[i].contracts[k].denom) + { + case 0: + strcat(sidesRes[i].contracts[k], "N"); + break; + case 1: + strcat(sidesRes[i].contracts[k], "S"); + break; + case 2: + strcat(sidesRes[i].contracts[k], "H"); + break; + case 3: + strcat(sidesRes[i].contracts[k], "D"); + break; + case 4: + strcat(sidesRes[i].contracts[k], "C"); + break; + default: + // Cannot happen, but keeps gcc happy. + break; + } + if (parm[i].contracts[k].under_tricks > 0) + /* Sacrifice */ + strcat(sidesRes[i].contracts[k], "*"); + switch (parm[i].contracts[k].seats) + { + case 0: + strcat(sidesRes[i].contracts[k], "-N"); + break; + case 1: + strcat(sidesRes[i].contracts[k], "-E"); + break; + case 2: + strcat(sidesRes[i].contracts[k], "-S"); + break; + case 3: + strcat(sidesRes[i].contracts[k], "-W"); + break; + case 4: + strcat(sidesRes[i].contracts[k], "-NS"); + break; + case 5: + strcat(sidesRes[i].contracts[k], "-EW"); + break; + default: + // Cannot happen, but keeps gcc happy. + break; + } + if (parm[i].contracts[k].under_tricks > 0) + { + /* Sacrifice */ + snprintf(buff, buf_size, "-%d", parm[i].contracts[k].under_tricks); + strcat(sidesRes[i].contracts[k], buff); + } + else if (parm[i].contracts[k].over_tricks > 0) + { + /* Make */ + snprintf(buff, buf_size, "+%d", parm[i].contracts[k].over_tricks); + strcat(sidesRes[i].contracts[k], buff); + } + } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL ConvertToDealerTextFormat( - ParResultsMaster const * pres, - char * resp) + ParResultsMaster const * pres, + char * resp) { - int k, i; - char buff[20]; + int k, i; + char buff[20]; - snprintf(resp, 20, "Par %d: ", pres->score); + snprintf(resp, 20, "Par %d: ", pres->score); - for (k = 0; k < pres->number; k++) - { + for (k = 0; k < pres->number; k++) + { - if (k != 0) - strcat(resp, " "); + if (k != 0) + strcat(resp, " "); - switch (pres->contracts[k].seats) - { - case 0: - strcat(resp, "N "); - break; - case 1: - strcat(resp, "E "); - break; - case 2: - strcat(resp, "S "); - break; - case 3: - strcat(resp, "W "); - break; - case 4: - strcat(resp, "NS "); - break; - case 5: - strcat(resp, "EW "); - break; - default: - return RETURN_UNKNOWN_FAULT; - // Seats not in (N,W,S,W,NS,EW) - } + switch (pres->contracts[k].seats) + { + case 0: + strcat(resp, "N "); + break; + case 1: + strcat(resp, "E "); + break; + case 2: + strcat(resp, "S "); + break; + case 3: + strcat(resp, "W "); + break; + case 4: + strcat(resp, "NS "); + break; + case 5: + strcat(resp, "EW "); + break; + default: + return RETURN_UNKNOWN_FAULT; + // Seats not in (N,W,S,W,NS,EW) + } - for (i = 0; i < 10; i++) - buff[i] = '\0'; - snprintf(buff, 4, "%d", pres->contracts[k].level); - strcat(resp, buff); + for (i = 0; i < 10; i++) + buff[i] = '\0'; + snprintf(buff, 4, "%d", pres->contracts[k].level); + strcat(resp, buff); - switch (pres->contracts[k].denom) - { - case 0: - strcat(resp, "N"); - break; - case 1: - strcat(resp, "S"); - break; - case 2: - strcat(resp, "H"); - break; - case 3: - strcat(resp, "D"); - break; - case 4: - strcat(resp, "C"); - break; - default: - return RETURN_UNKNOWN_FAULT; - // denom not in /N,S,H,D,C) - } + switch (pres->contracts[k].denom) + { + case 0: + strcat(resp, "N"); + break; + case 1: + strcat(resp, "S"); + break; + case 2: + strcat(resp, "H"); + break; + case 3: + strcat(resp, "D"); + break; + case 4: + strcat(resp, "C"); + break; + default: + return RETURN_UNKNOWN_FAULT; + // denom not in /N,S,H,D,C) + } - if (pres->contracts[k].under_tricks > 0) - { - strcat(resp, "x-"); - for (i = 0; i < 10; i++) - buff[i] = '\0'; - snprintf(buff, 10, "%d", pres->contracts[k].under_tricks); - strcat(resp, buff); - } - else if (pres->contracts[k].over_tricks > 0) - { - strcat(resp, "+"); - for (i = 0; i < 10; i++) - buff[i] = '\0'; - snprintf(buff, 10, "%d", pres->contracts[k].over_tricks); - strcat(resp, buff); + if (pres->contracts[k].under_tricks > 0) + { + strcat(resp, "x-"); + for (i = 0; i < 10; i++) + buff[i] = '\0'; + snprintf(buff, 10, "%d", pres->contracts[k].under_tricks); + strcat(resp, buff); + } + else if (pres->contracts[k].over_tricks > 0) + { + strcat(resp, "+"); + for (i = 0; i < 10; i++) + buff[i] = '\0'; + snprintf(buff, 10, "%d", pres->contracts[k].over_tricks); + strcat(resp, buff); + } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL ConvertToSidesTextFormat( - ParResultsMaster const * pres, - ParTextResults * resp) + ParResultsMaster const * pres, + ParTextResults * resp) { - int k, i, j; - char buff[20]; + int k, i, j; + char buff[20]; - for (i = 0; i < 2; i++) - for (k = 0; k < 128; k++) - resp->par_text[i][k] = '\0'; + for (i = 0; i < 2; i++) + for (k = 0; k < 128; k++) + resp->par_text[i][k] = '\0'; - if (pres->score == 0) - { - snprintf(resp->par_text[0], 7, "Par 0"); - return RETURN_NO_FAULT; - } + if (pres->score == 0) + { + snprintf(resp->par_text[0], 7, "Par 0"); + return RETURN_NO_FAULT; + } - for (i = 0; i < 2; i++) - { + for (i = 0; i < 2; i++) + { - snprintf(resp->par_text[i], 10, "Par %d: ", (pres + i)->score); + snprintf(resp->par_text[i], 10, "Par %d: ", (pres + i)->score); - for (k = 0; k < (pres + i)->number; k++) - { + for (k = 0; k < (pres + i)->number; k++) + { - if (k != 0) - strcat(resp->par_text[i], " "); - - switch ((pres + i)->contracts[k].seats) - { - case 0: - strcat(resp->par_text[i], "N "); - break; - case 1: - strcat(resp->par_text[i], "E "); - break; - case 2: - strcat(resp->par_text[i], "S "); - break; - case 3: - strcat(resp->par_text[i], "W "); - break; - case 4: - strcat(resp->par_text[i], "NS "); - break; - case 5: - strcat(resp->par_text[i], "EW "); - break; - default: - return RETURN_UNKNOWN_FAULT; - // Seats not in (N,W,S,W,NS,EW) - } - - for (j = 0; j < 10; j++) - buff[j] = '\0'; - snprintf(buff, 10, "%d", (pres + i)->contracts[k].level); - strcat(resp->par_text[i], buff); - - switch ((pres + i)->contracts[k].denom) - { - case 0: - strcat(resp->par_text[i], "NT"); - break; - case 1: - strcat(resp->par_text[i], "S"); - break; - case 2: - strcat(resp->par_text[i], "H"); - break; - case 3: - strcat(resp->par_text[i], "D"); - break; - case 4: - strcat(resp->par_text[i], "C"); - break; - default: - return RETURN_UNKNOWN_FAULT; - // denom not in (N,S,H,D,C) - } - - if ((pres + i)->contracts[k].under_tricks > 0) - { - strcat(resp->par_text[i], "x-"); - for (j = 0; j < 10; j++) - buff[j] = '\0'; - snprintf(buff, 10, "%d", (pres + i)->contracts[k].under_tricks); - strcat(resp->par_text[i], buff); - } - else if ((pres + i)->contracts[k].over_tricks > 0) - { - strcat(resp->par_text[i], "+"); - for (j = 0; j < 10; j++) - buff[j] = '\0'; - snprintf(buff, 10, "%d", (pres + i)->contracts[k].over_tricks); - strcat(resp->par_text[i], buff); - } - } + if (k != 0) + strcat(resp->par_text[i], " "); + + switch ((pres + i)->contracts[k].seats) + { + case 0: + strcat(resp->par_text[i], "N "); + break; + case 1: + strcat(resp->par_text[i], "E "); + break; + case 2: + strcat(resp->par_text[i], "S "); + break; + case 3: + strcat(resp->par_text[i], "W "); + break; + case 4: + strcat(resp->par_text[i], "NS "); + break; + case 5: + strcat(resp->par_text[i], "EW "); + break; + default: + return RETURN_UNKNOWN_FAULT; + // Seats not in (N,W,S,W,NS,EW) + } + + for (j = 0; j < 10; j++) + buff[j] = '\0'; + snprintf(buff, 10, "%d", (pres + i)->contracts[k].level); + strcat(resp->par_text[i], buff); + + switch ((pres + i)->contracts[k].denom) + { + case 0: + strcat(resp->par_text[i], "NT"); + break; + case 1: + strcat(resp->par_text[i], "S"); + break; + case 2: + strcat(resp->par_text[i], "H"); + break; + case 3: + strcat(resp->par_text[i], "D"); + break; + case 4: + strcat(resp->par_text[i], "C"); + break; + default: + return RETURN_UNKNOWN_FAULT; + // denom not in (N,S,H,D,C) + } + + if ((pres + i)->contracts[k].under_tricks > 0) + { + strcat(resp->par_text[i], "x-"); + for (j = 0; j < 10; j++) + buff[j] = '\0'; + snprintf(buff, 10, "%d", (pres + i)->contracts[k].under_tricks); + strcat(resp->par_text[i], buff); + } + else if ((pres + i)->contracts[k].over_tricks > 0) + { + strcat(resp->par_text[i], "+"); + for (j = 0; j < 10; j++) + buff[j] = '\0'; + snprintf(buff, 10, "%d", (pres + i)->contracts[k].over_tricks); + strcat(resp->par_text[i], buff); + } + } - if (i == 0) - { - if ((pres->score != -(pres + 1)->score) || (pres->number != (pres + 1)->number)) - { - resp->equal = false; - } - else - { - resp->equal = true; - for (k = 0; k < pres->number; k++) + if (i == 0) { - if ((pres->contracts[k].denom != (pres + 1)->contracts[k].denom) || - (pres->contracts[k].level != (pres + 1)->contracts[k].level) || - (pres->contracts[k].over_tricks != (pres + 1)->contracts[k].over_tricks) || - (pres->contracts[k].seats != (pres + 1)->contracts[k].seats) || - (pres->contracts[k].under_tricks != (pres + 1)->contracts[k].under_tricks)) - { - resp->equal = false; - break; - } + if ((pres->score != -(pres + 1)->score) || (pres->number != (pres + 1)->number)) + { + resp->equal = false; + } + else + { + resp->equal = true; + for (k = 0; k < pres->number; k++) + { + if ((pres->contracts[k].denom != (pres + 1)->contracts[k].denom) || + (pres->contracts[k].level != (pres + 1)->contracts[k].level) || + (pres->contracts[k].over_tricks != (pres + 1)->contracts[k].over_tricks) || + (pres->contracts[k].seats != (pres + 1)->contracts[k].seats) || + (pres->contracts[k].under_tricks != (pres + 1)->contracts[k].under_tricks)) + { + resp->equal = false; + break; + } + } + } } - } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } diff --git a/library/src/par_validate.hpp b/library/src/par_validate.hpp index 1c11bc9c7..f84aa4ba4 100644 --- a/library/src/par_validate.hpp +++ b/library/src/par_validate.hpp @@ -29,15 +29,15 @@ */ inline auto par_table_checks(DdTableResults const * tablep) -> int { - if (tablep == nullptr) - return RETURN_PAR_TABLE_FAULT; - - for (int d = 0; d < DDS_STRAINS; d++) - for (int h = 0; h < DDS_HANDS; h++) - if (tablep->res_table[d][h] < 0 || tablep->res_table[d][h] > 13) + if (tablep == nullptr) return RETURN_PAR_TABLE_FAULT; - return RETURN_NO_FAULT; + for (int d = 0; d < DDS_STRAINS; d++) + for (int h = 0; h < DDS_HANDS; h++) + if (tablep->res_table[d][h] < 0 || tablep->res_table[d][h] > 13) + return RETURN_PAR_TABLE_FAULT; + + return RETURN_NO_FAULT; } @@ -56,8 +56,8 @@ inline auto par_table_checks(DdTableResults const * tablep) -> int */ inline auto par_vulnerable_checks(int const vulnerable) -> int { - if (vulnerable < 0 || vulnerable > 3) - return RETURN_UNKNOWN_FAULT; + if (vulnerable < 0 || vulnerable > 3) + return RETURN_UNKNOWN_FAULT; - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } diff --git a/library/src/pbn.cpp b/library/src/pbn.cpp index 9e5b1955a..3360fb28c 100644 --- a/library/src/pbn.cpp +++ b/library/src/pbn.cpp @@ -17,208 +17,208 @@ auto is_compass_letter(const char c) -> bool; auto convert_from_pbn( - char const * dealBuff, - unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int + char const * dealBuff, + unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int { - if (remainCards == nullptr) - return 0; + if (remainCards == nullptr) + return 0; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - remainCards[h][s] = 0; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + remainCards[h][s] = 0; - if (dealBuff == nullptr) - return 0; + if (dealBuff == nullptr) + return 0; - int bp = 0; - while ((bp < 3) && (dealBuff[bp] != '\0') && !is_compass_letter(dealBuff[bp])) - bp++; + int bp = 0; + while ((bp < 3) && (dealBuff[bp] != '\0') && !is_compass_letter(dealBuff[bp])) + bp++; - if ((bp >= 3) || (dealBuff[bp] == '\0') || (dealBuff[bp + 1] != ':')) - return 0; - - int first; - if ((dealBuff[bp] == 'N') || (dealBuff[bp] == 'n')) - first = 0; - else if ((dealBuff[bp] == 'E') || (dealBuff[bp] == 'e')) - first = 1; - else if ((dealBuff[bp] == 'S') || (dealBuff[bp] == 's')) - first = 2; - else - first = 3; - - bp++; - bp++; - - int hand_rel_first = 0; - int suitInHand = 0; - int card, hand; - - while ((bp < PbnBufferSize) && (dealBuff[bp] != '\0')) - { - card = is_card(dealBuff[bp]); - if (card) - { - if (hand_rel_first >= DDS_HANDS || suitInHand >= DDS_SUITS) + if ((bp >= 3) || (dealBuff[bp] == '\0') || (dealBuff[bp + 1] != ':')) return 0; - switch (first) - { - case 0: - hand = hand_rel_first; - break; - case 1: - if (hand_rel_first == 0) - hand = 1; - else if (hand_rel_first == 3) - hand = 0; - else - hand = hand_rel_first + 1; - break; - case 2: - if (hand_rel_first == 0) - hand = 2; - else if (hand_rel_first == 1) - hand = 3; - else - hand = hand_rel_first - 2; - break; - default: - if (hand_rel_first == 0) - hand = 3; - else - hand = hand_rel_first - 1; - } + int first; + if ((dealBuff[bp] == 'N') || (dealBuff[bp] == 'n')) + first = 0; + else if ((dealBuff[bp] == 'E') || (dealBuff[bp] == 'e')) + first = 1; + else if ((dealBuff[bp] == 'S') || (dealBuff[bp] == 's')) + first = 2; + else + first = 3; - if (hand < 0 || hand >= DDS_HANDS) - return 0; + bp++; + bp++; - remainCards[hand][suitInHand] |= - static_cast((bit_map_rank[card] << 2)); + int hand_rel_first = 0; + int suitInHand = 0; + int card, hand; - } - else if (dealBuff[bp] == '.') + while ((bp < PbnBufferSize) && (dealBuff[bp] != '\0')) { - if (suitInHand >= DDS_SUITS - 1) - return 0; - suitInHand++; - } - else if (dealBuff[bp] == ' ') - { - if (hand_rel_first >= DDS_HANDS - 1) - return 0; - hand_rel_first++; - suitInHand = 0; + card = is_card(dealBuff[bp]); + if (card) + { + if (hand_rel_first >= DDS_HANDS || suitInHand >= DDS_SUITS) + return 0; + + switch (first) + { + case 0: + hand = hand_rel_first; + break; + case 1: + if (hand_rel_first == 0) + hand = 1; + else if (hand_rel_first == 3) + hand = 0; + else + hand = hand_rel_first + 1; + break; + case 2: + if (hand_rel_first == 0) + hand = 2; + else if (hand_rel_first == 1) + hand = 3; + else + hand = hand_rel_first - 2; + break; + default: + if (hand_rel_first == 0) + hand = 3; + else + hand = hand_rel_first - 1; + } + + if (hand < 0 || hand >= DDS_HANDS) + return 0; + + remainCards[hand][suitInHand] |= + static_cast((bit_map_rank[card] << 2)); + + } + else if (dealBuff[bp] == '.') + { + if (suitInHand >= DDS_SUITS - 1) + return 0; + suitInHand++; + } + else if (dealBuff[bp] == ' ') + { + if (hand_rel_first >= DDS_HANDS - 1) + return 0; + hand_rel_first++; + suitInHand = 0; + } + else if (is_compass_letter(dealBuff[bp])) + return 0; + bp++; } - else if (is_compass_letter(dealBuff[bp])) - return 0; - bp++; - } - if (bp >= PbnBufferSize) - return 0; + if (bp >= PbnBufferSize) + return 0; - if (hand_rel_first != DDS_HANDS - 1) - return 0; + if (hand_rel_first != DDS_HANDS - 1) + return 0; - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto is_compass_letter(const char c) -> bool { - switch (c) - { - case 'N': - case 'n': - case 'E': - case 'e': - case 'S': - case 's': - case 'W': - case 'w': - return true; - default: - return false; - } + switch (c) + { + case 'N': + case 'n': + case 'E': + case 'e': + case 'S': + case 's': + case 'W': + case 'w': + return true; + default: + return false; + } } auto is_card(const char cardChar) -> int { - switch (cardChar) - { - case '2': - return 2; - case '3': - return 3; - case '4': - return 4; - case '5': - return 5; - case '6': - return 6; - case '7': - return 7; - case '8': - return 8; - case '9': - return 9; - case 'T': - case 't': - return 10; - case 'J': - case 'j': - return 11; - case 'Q': - case 'q': - return 12; - case 'K': - case 'k': - return 13; - case 'A': - case 'a': - return 14; - default: - return 0; - } + switch (cardChar) + { + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'T': + case 't': + return 10; + case 'J': + case 'j': + return 11; + case 'Q': + case 'q': + return 12; + case 'K': + case 'k': + return 13; + case 'A': + case 'a': + return 14; + default: + return 0; + } } auto convert_play_from_pbn( - const PlayTracePBN& playPBN, - PlayTraceBin& playBin) -> int + const PlayTracePBN& playPBN, + PlayTraceBin& playBin) -> int { - const int n = playPBN.number; - - if (n < 0 || n > 52) - return RETURN_PLAY_FAULT; - - playBin.number = n; - - for (int i = 0; i < 2 * n; i += 2) - { - char suit = playPBN.cards[i]; - int s; - - if (suit == 's' || suit == 'S') - s = 0; - else if (suit == 'h' || suit == 'H') - s = 1; - else if (suit == 'd' || suit == 'D') - s = 2; - else if (suit == 'c' || suit == 'C') - s = 3; - else - return RETURN_PLAY_FAULT; - playBin.suit[i >> 1] = s; + const int n = playPBN.number; - int rank = is_card(playPBN.cards[i+1]); - if (rank == 0) - return RETURN_PLAY_FAULT; + if (n < 0 || n > 52) + return RETURN_PLAY_FAULT; - playBin.rank[i >> 1] = rank; - } - return RETURN_NO_FAULT; + playBin.number = n; + + for (int i = 0; i < 2 * n; i += 2) + { + char suit = playPBN.cards[i]; + int s; + + if (suit == 's' || suit == 'S') + s = 0; + else if (suit == 'h' || suit == 'H') + s = 1; + else if (suit == 'd' || suit == 'D') + s = 2; + else if (suit == 'c' || suit == 'C') + s = 3; + else + return RETURN_PLAY_FAULT; + playBin.suit[i >> 1] = s; + + int rank = is_card(playPBN.cards[i+1]); + if (rank == 0) + return RETURN_PLAY_FAULT; + + playBin.rank[i >> 1] = rank; + } + return RETURN_NO_FAULT; } diff --git a/library/src/pbn.hpp b/library/src/pbn.hpp index 4ba65cafa..429a94da8 100644 --- a/library/src/pbn.hpp +++ b/library/src/pbn.hpp @@ -23,8 +23,8 @@ * @return 1 if successful, 0 otherwise. */ auto convert_from_pbn( - char const * dealBuff, - unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; + char const * dealBuff, + unsigned int remainCards[DDS_HANDS][DDS_SUITS]) -> int; /** * @brief Convert a PBN-format play trace to binary play trace. @@ -36,5 +36,5 @@ auto convert_from_pbn( * @return 1 if successful, 0 otherwise. */ auto convert_play_from_pbn( - const PlayTracePBN& playPBN, - PlayTraceBin& playBin) -> int; + const PlayTracePBN& playPBN, + PlayTraceBin& playBin) -> int; diff --git a/library/src/play_analyser.cpp b/library/src/play_analyser.cpp index 8848a2edf..fe15f0ab5 100644 --- a/library/src/play_analyser.cpp +++ b/library/src/play_analyser.cpp @@ -23,8 +23,8 @@ using namespace std; #define DEBUG 0 #if DEBUG - #include - ofstream fout; + #include + ofstream fout; #endif extern Scheduler scheduler; @@ -43,200 +43,200 @@ extern Scheduler scheduler; * @return 1 on success, error code otherwise */ int STDCALL AnalysePlayBin( - Deal dl, - PlayTraceBin play, - SolvedPlay * solvedp, - [[maybe_unused]] int thrId) + Deal dl, + PlayTraceBin play, + SolvedPlay * solvedp, + [[maybe_unused]] int thrId) { - // Create an owned context for this analysis. The same context (and its - // transposition table) is reused for the initial solve and every subsequent - // analyse_later_board call, so the hint-bounded incremental searches see a - // warm TT -- see the analogous calc_dd_table fix (commit 27030ba). - SolverContext outer_ctx; - - MoveType move; - FutureTricks fut; - - int ret = solve_board_internal(outer_ctx, dl, -1, 1, 1, &fut); - if (ret != RETURN_NO_FAULT) - return ret; - SolverContext& ctx = outer_ctx; - const int ini_depth = ctx.search().ini_depth(); - const int numTricks = ((ini_depth + 3) >> 2) + 1; - const int numCardsPlayed = ((48 - ini_depth) % 4) + 1; - - int last_trick = (play.number + 3) / 4; - int last_card = ((play.number + 3) % 4) + 1; - if (last_trick >= numTricks) - { - last_trick = numTricks-1; - last_card = 4; - } - solvedp->number = 0; - - solvedp->tricks[0] = (numCardsPlayed % 2 == 1 ? - numTricks - fut.score[0] : fut.score[0]); - int hint = solvedp->tricks[0]; - int hintDir; - - int running_remainder = numTricks; - int running_declarer = 0; - int running_player = dl.first; - int running_side = 1; /* defenders */ - int start_side = running_player % 2; + // Create an owned context for this analysis. The same context (and its + // transposition table) is reused for the initial solve and every subsequent + // analyse_later_board call, so the hint-bounded incremental searches see a + // warm TT -- see the analogous calc_dd_table fix (commit 27030ba). + SolverContext outer_ctx; + + MoveType move; + FutureTricks fut; + + int ret = solve_board_internal(outer_ctx, dl, -1, 1, 1, &fut); + if (ret != RETURN_NO_FAULT) + return ret; + SolverContext& ctx = outer_ctx; + const int ini_depth = ctx.search().ini_depth(); + const int numTricks = ((ini_depth + 3) >> 2) + 1; + const int numCardsPlayed = ((48 - ini_depth) % 4) + 1; + + int last_trick = (play.number + 3) / 4; + int last_card = ((play.number + 3) % 4) + 1; + if (last_trick >= numTricks) + { + last_trick = numTricks-1; + last_card = 4; + } + solvedp->number = 0; + + solvedp->tricks[0] = (numCardsPlayed % 2 == 1 ? + numTricks - fut.score[0] : fut.score[0]); + int hint = solvedp->tricks[0]; + int hintDir; + + int running_remainder = numTricks; + int running_declarer = 0; + int running_player = dl.first; + int running_side = 1; /* defenders */ + int start_side = running_player % 2; #if DEBUG - int solved_declarer = solvedp->tricks[0]; - int initial_par = solved_declarer; - fout.open("trace.txt", ofstream::out | ofstream::app); - fout << "Initial solve: " << initial_par << "\n"; - fout << "no " << play.number << ", Last trick " << last_trick << - ", last card " << last_card << "\n"; - fout << setw(5) << "trick" << setw(6) << "card" << - setw(6) << "rest" << setw(9) << "declarer" << - setw(7) << "player" << setw(7) << "side" << - setw(6) << "soln0" << setw(6) << "soln1" << setw(6) << "diff" << "\n"; + int solved_declarer = solvedp->tricks[0]; + int initial_par = solved_declarer; + fout.open("trace.txt", ofstream::out | ofstream::app); + fout << "Initial solve: " << initial_par << "\n"; + fout << "no " << play.number << ", Last trick " << last_trick << + ", last card " << last_card << "\n"; + fout << setw(5) << "trick" << setw(6) << "card" << + setw(6) << "rest" << setw(9) << "declarer" << + setw(7) << "player" << setw(7) << "side" << + setw(6) << "soln0" << setw(6) << "soln1" << setw(6) << "diff" << "\n"; #endif - for (int trick = 1; trick <= last_trick; trick++) - { - int best_card = 0, best_suit = 0, best_player = 0, trump_played = 0; - int lc = (trick == last_trick ? last_card : 4); + for (int trick = 1; trick <= last_trick; trick++) + { + int best_card = 0, best_suit = 0, best_player = 0, trump_played = 0; + int lc = (trick == last_trick ? last_card : 4); - bool haveCurrent = (numCardsPlayed > 1 && trick == 1); - int offset = 4 * (trick - 1) - (numCardsPlayed - 1); + bool haveCurrent = (numCardsPlayed > 1 && trick == 1); + int offset = 4 * (trick - 1) - (numCardsPlayed - 1); - for (int card = 1; card <= lc; card++) - { - int suit, rr; - bool usingCurrent = (haveCurrent && card < numCardsPlayed); - if (usingCurrent) - { - suit = dl.currentTrickSuit[card - 1]; - rr = dl.currentTrickRank[card - 1]; - } - else - { - suit = play.suit[offset + card - 1]; - rr = play.rank[offset + card - 1]; - } - unsigned hold = static_cast(bit_map_rank[rr] << 2); - - move.suit = suit; - move.rank = rr; - move.sequence = rr; - - /* Keep track of the winner of the trick so far */ - if (card == 1) - { - best_card = rr; - best_suit = suit; - best_player = dl.first; - trump_played = (suit == dl.trump ? 1 : 0); - } - else if (suit == dl.trump) - { - if (! trump_played || rr > best_card) - { - best_card = rr; - best_suit = suit; - best_player = running_player; - trump_played = 1; - } - } - else if (! trump_played && suit == best_suit && rr > best_card) - { - best_card = rr; - best_player = running_player; - } - - if ((dl.remainCards[running_player][suit] & hold) == 0) - { - if (! usingCurrent) + for (int card = 1; card <= lc; card++) { + int suit, rr; + bool usingCurrent = (haveCurrent && card < numCardsPlayed); + if (usingCurrent) + { + suit = dl.currentTrickSuit[card - 1]; + rr = dl.currentTrickRank[card - 1]; + } + else + { + suit = play.suit[offset + card - 1]; + rr = play.rank[offset + card - 1]; + } + unsigned hold = static_cast(bit_map_rank[rr] << 2); + + move.suit = suit; + move.rank = rr; + move.sequence = rr; + + /* Keep track of the winner of the trick so far */ + if (card == 1) + { + best_card = rr; + best_suit = suit; + best_player = dl.first; + trump_played = (suit == dl.trump ? 1 : 0); + } + else if (suit == dl.trump) + { + if (! trump_played || rr > best_card) + { + best_card = rr; + best_suit = suit; + best_player = running_player; + trump_played = 1; + } + } + else if (! trump_played && suit == best_suit && rr > best_card) + { + best_card = rr; + best_player = running_player; + } + + if ((dl.remainCards[running_player][suit] & hold) == 0) + { + if (! usingCurrent) + { #if DEBUG - fout << "ERR Trick " << trick << " card " << card << - " pl " << running_player << ": suit " << suit << - " hold " << hold << "\n"; - fout.close(); + fout << "ERR Trick " << trick << " card " << card << + " pl " << running_player << ": suit " << suit << + " hold " << hold << "\n"; + fout.close(); #endif - return RETURN_PLAY_FAULT; - } - } - else - dl.remainCards[running_player][suit] ^= hold; + return RETURN_PLAY_FAULT; + } + } + else + dl.remainCards[running_player][suit] ^= hold; #if DEBUG - int resp_player = running_player; + int resp_player = running_player; #endif - if (card == 4) - { - running_declarer += (best_player % 2 == start_side ? 0 : 1); - running_remainder--; - - if ((dl.first + best_player) % 2 == 0) - { - hintDir = 0; // Same side leads again; lower bound - hint = running_remainder - fut.score[0]; - } - else - { - hintDir = 1; // Other ("our") side wins trick; upper bound - hint = fut.score[0] - 1; - } - - dl.first = best_player; - running_side = (dl.first % 2 == start_side ? 1 : 0); - running_player = dl.first; - } - else - { - running_player = (running_player + 1) % 4; - running_side = 1 - running_side; - hint = running_remainder - fut.score[0]; - hintDir = 0; - } - - if (usingCurrent) - continue; - - if ((ret = analyse_later_board(ctx, dl.first, &move, hint, - hintDir, &fut)) - != RETURN_NO_FAULT) - { + if (card == 4) + { + running_declarer += (best_player % 2 == start_side ? 0 : 1); + running_remainder--; + + if ((dl.first + best_player) % 2 == 0) + { + hintDir = 0; // Same side leads again; lower bound + hint = running_remainder - fut.score[0]; + } + else + { + hintDir = 1; // Other ("our") side wins trick; upper bound + hint = fut.score[0] - 1; + } + + dl.first = best_player; + running_side = (dl.first % 2 == start_side ? 1 : 0); + running_player = dl.first; + } + else + { + running_player = (running_player + 1) % 4; + running_side = 1 - running_side; + hint = running_remainder - fut.score[0]; + hintDir = 0; + } + + if (usingCurrent) + continue; + + if ((ret = analyse_later_board(ctx, dl.first, &move, hint, + hintDir, &fut)) + != RETURN_NO_FAULT) + { #if DEBUG - fout << "SolveBoard failed, ret " << ret << "\n"; - fout.close(); + fout << "SolveBoard failed, ret " << ret << "\n"; + fout.close(); #endif - return ret; - } + return ret; + } - int new_solved_decl = running_declarer + (running_side ? - running_remainder - fut.score[0] : fut.score[0]); + int new_solved_decl = running_declarer + (running_side ? + running_remainder - fut.score[0] : fut.score[0]); - solvedp->tricks[offset + card] = new_solved_decl; + solvedp->tricks[offset + card] = new_solved_decl; #if DEBUG - fout << setw(5) << trick << - setw(6) << card << - setw(6) << running_remainder << - setw(9) << running_declarer << - setw(7) << card_hand[resp_player] << - setw(7) << running_side << - setw(6) << solved_declarer << - setw(6) << new_solved_decl << - setw(6) << new_solved_decl - solved_declarer << "\n"; - solved_declarer = new_solved_decl; + fout << setw(5) << trick << + setw(6) << card << + setw(6) << running_remainder << + setw(9) << running_declarer << + setw(7) << card_hand[resp_player] << + setw(7) << running_side << + setw(6) << solved_declarer << + setw(6) << new_solved_decl << + setw(6) << new_solved_decl - solved_declarer << "\n"; + solved_declarer = new_solved_decl; #endif - + + } } - } - solvedp->number = 4 * last_trick + last_card - 3 - (numCardsPlayed - 1); + solvedp->number = 4 * last_trick + last_card - 3 - (numCardsPlayed - 1); #if DEBUG - fout.close(); + fout.close(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } @@ -253,133 +253,133 @@ int STDCALL AnalysePlayBin( * @return 1 on success, error code otherwise */ int STDCALL AnalysePlayPBN( - DealPBN dlPBN, - PlayTracePBN playPBN, - SolvedPlay * solvedp, - int thrId) + DealPBN dlPBN, + PlayTracePBN playPBN, + SolvedPlay * solvedp, + int thrId) { - Deal dl; - PlayTraceBin play; + Deal dl; + PlayTraceBin play; - if (convert_from_pbn(dlPBN.remainCards, dl.remainCards) != - RETURN_NO_FAULT) - return RETURN_PBN_FAULT; + if (convert_from_pbn(dlPBN.remainCards, dl.remainCards) != + RETURN_NO_FAULT) + return RETURN_PBN_FAULT; - dl.first = dlPBN.first; - dl.trump = dlPBN.trump; - for (int i = 0; i <= 2; i++) - { - dl.currentTrickSuit[i] = dlPBN.currentTrickSuit[i]; - dl.currentTrickRank[i] = dlPBN.currentTrickRank[i]; - } + dl.first = dlPBN.first; + dl.trump = dlPBN.trump; + for (int i = 0; i <= 2; i++) + { + dl.currentTrickSuit[i] = dlPBN.currentTrickSuit[i]; + dl.currentTrickRank[i] = dlPBN.currentTrickRank[i]; + } - if (convert_play_from_pbn(playPBN, play) != RETURN_NO_FAULT) - return RETURN_PLAY_FAULT; + if (convert_play_from_pbn(playPBN, play) != RETURN_NO_FAULT) + return RETURN_PLAY_FAULT; - return AnalysePlayBin(dl, play, solvedp, thrId); + return AnalysePlayBin(dl, play, solvedp, thrId); } int STDCALL AnalyseAllPlaysBin( - Boards const * bop, - PlayTracesBin const * plp, - SolvedPlays * solvedp, - [[maybe_unused]] int chunkSize) + Boards const * bop, + PlayTracesBin const * plp, + SolvedPlays * solvedp, + [[maybe_unused]] int chunkSize) { - if (bop->no_of_boards > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; + if (bop->no_of_boards > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; - if (bop->no_of_boards != plp->no_of_boards) - return RETURN_UNKNOWN_FAULT; + if (bop->no_of_boards != plp->no_of_boards) + return RETURN_UNKNOWN_FAULT; - scheduler.RegisterRun(RunMode::DDS_RUN_TRACE, * bop, * plp); + scheduler.RegisterRun(RunMode::DDS_RUN_TRACE, * bop, * plp); - START_BLOCK_TIMER; + START_BLOCK_TIMER; - for (int bno = 0; bno < bop->no_of_boards; bno++) { - SolvedPlay solved; - const int res = AnalysePlayBin(bop->deals[bno], plp->plays[bno], &solved, 0); - if (res == 1) - solvedp->solved[bno] = solved; - else - return res; - } + for (int bno = 0; bno < bop->no_of_boards; bno++) { + SolvedPlay solved; + const int res = AnalysePlayBin(bop->deals[bno], plp->plays[bno], &solved, 0); + if (res == 1) + solvedp->solved[bno] = solved; + else + return res; + } - END_BLOCK_TIMER; + END_BLOCK_TIMER; - solvedp->no_of_boards = bop->no_of_boards; + solvedp->no_of_boards = bop->no_of_boards; #ifdef DDS_SCHEDULER - scheduler.PrintTiming(); + scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } int STDCALL AnalyseAllPlaysPBN( - BoardsPBN const * bopPBN, - PlayTracesPBN const * plpPBN, - SolvedPlays * solvedp, - int chunkSize) + BoardsPBN const * bopPBN, + PlayTracesPBN const * plpPBN, + SolvedPlays * solvedp, + int chunkSize) { - Boards bd; - PlayTracesBin pl; + Boards bd; + PlayTracesBin pl; - bd.no_of_boards = bopPBN->no_of_boards; - if (bd.no_of_boards > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; + bd.no_of_boards = bopPBN->no_of_boards; + if (bd.no_of_boards > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; - for (int k = 0; k < bopPBN->no_of_boards; k++) - { - Deal& dl = bd.deals[k]; - DealPBN const & dlp = bopPBN->deals[k]; + for (int k = 0; k < bopPBN->no_of_boards; k++) + { + Deal& dl = bd.deals[k]; + DealPBN const & dlp = bopPBN->deals[k]; - if (convert_from_pbn(dlp.remainCards, + if (convert_from_pbn(dlp.remainCards, dl.remainCards) != RETURN_NO_FAULT) - return RETURN_PBN_FAULT; + return RETURN_PBN_FAULT; - dl.trump = dlp.trump; - dl.first = dlp.first; + dl.trump = dlp.trump; + dl.first = dlp.first; - for (int i = 0; i <= 2; i++) - { - dl.currentTrickSuit[i] = dlp.currentTrickSuit[i]; - dl.currentTrickRank[i] = dlp.currentTrickRank[i]; + for (int i = 0; i <= 2; i++) + { + dl.currentTrickSuit[i] = dlp.currentTrickSuit[i]; + dl.currentTrickRank[i] = dlp.currentTrickRank[i]; + } } - } - pl.no_of_boards = plpPBN->no_of_boards; + pl.no_of_boards = plpPBN->no_of_boards; - for (int k = 0; k < plpPBN->no_of_boards; k++) - { - if (convert_play_from_pbn(plpPBN->plays[k], pl.plays[k]) != - RETURN_NO_FAULT) - return RETURN_PLAY_FAULT; - } + for (int k = 0; k < plpPBN->no_of_boards; k++) + { + if (convert_play_from_pbn(plpPBN->plays[k], pl.plays[k]) != + RETURN_NO_FAULT) + return RETURN_PLAY_FAULT; + } - chunkSize = 1; - return AnalyseAllPlaysBin(&bd, &pl, solvedp, chunkSize); + chunkSize = 1; + return AnalyseAllPlaysBin(&bd, &pl, solvedp, chunkSize); } void detect_play_duplicates( - const Boards& bds, - vector& uniques, - vector& crossrefs) + const Boards& bds, + vector& uniques, + vector& crossrefs) { - // This dummy function is there for consistency in System.cpp. - // In practice there is not much point in deteting play repeats, - // as it is highly unlikely that the play went identically at - // two tables. - - uniques.resize(static_cast(bds.no_of_boards)); - crossrefs.resize(static_cast(bds.no_of_boards)); - for (unsigned i = 0; i < uniques.size(); i++) - { - uniques[i] = static_cast(i); - crossrefs[i] = -1; - } + // This dummy function is there for consistency in System.cpp. + // In practice there is not much point in deteting play repeats, + // as it is highly unlikely that the play went identically at + // two tables. + + uniques.resize(static_cast(bds.no_of_boards)); + crossrefs.resize(static_cast(bds.no_of_boards)); + for (unsigned i = 0; i < uniques.size(); i++) + { + uniques[i] = static_cast(i); + crossrefs[i] = -1; + } } diff --git a/library/src/play_analyser.hpp b/library/src/play_analyser.hpp index a649d0756..57286a6b8 100644 --- a/library/src/play_analyser.hpp +++ b/library/src/play_analyser.hpp @@ -15,6 +15,6 @@ void detect_play_duplicates( - const Boards& bds, - std::vector& uniques, - std::vector& crossrefs); + const Boards& bds, + std::vector& uniques, + std::vector& crossrefs); diff --git a/library/src/quick_tricks.cpp b/library/src/quick_tricks.cpp index c5f17f96d..b0820625d 100644 --- a/library/src/quick_tricks.cpp +++ b/library/src/quick_tricks.cpp @@ -16,104 +16,104 @@ auto next_quick_trick_suit(int suit, int trump) -> int { - if ((trump != DDS_NOTRUMP) && (suit == trump)) - { - if (trump == 0) - return 1; - return 0; - } - - suit++; - if ((trump != DDS_NOTRUMP) && (suit == trump)) + if ((trump != DDS_NOTRUMP) && (suit == trump)) + { + if (trump == 0) + return 1; + return 0; + } + suit++; - return suit; + if ((trump != DDS_NOTRUMP) && (suit == trump)) + suit++; + return suit; } int QtricksLeadHandNT( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - int& lhoTrumpRanks, - int& rhoTrumpRanks, - const bool commPartner, - const int commSuit, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int trump, - int& res); + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + int& lhoTrumpRanks, + int& rhoTrumpRanks, + const bool commPartner, + const int commSuit, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int trump, + int& res); int QtricksLeadHandTrump( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int lhoTrumpRanks, - const int rhoTrumpRanks, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - int& res); + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int lhoTrumpRanks, + const int rhoTrumpRanks, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + int& res); int QuickTricksPartnerHand( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int lhoTrumpRanks, - const int rhoTrumpRanks, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int commSuit, - const int commRank, - int& res, - SolverContext& ctx); + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int lhoTrumpRanks, + const int rhoTrumpRanks, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int commSuit, + const int commRank, + int& res, + SolverContext& ctx); int QuickTricksPartnerHandTrump( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int lhoTrumpRanks, - const int rhoTrumpRanks, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int commSuit, - const int commRank, - int& res, - SolverContext& ctx); + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int lhoTrumpRanks, + const int rhoTrumpRanks, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int commSuit, + const int commRank, + int& res, + SolverContext& ctx); int QuickTricksPartnerHandNT( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int commSuit, - const int commRank, - int& res, - SolverContext& ctx); + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int commSuit, + const int commRank, + int& res, + SolverContext& ctx); /** @@ -132,1027 +132,1027 @@ int QuickTricksPartnerHandNT( * @return Number of quick tricks found */ int QuickTricks( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - bool& result, - SolverContext& ctx) + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + bool& result, + SolverContext& ctx) { - int suit, commRank = 0, commSuit = -1; - int res; - int lhoTrumpRanks = 0, rhoTrumpRanks = 0; - int cutoff, lowestQtricks = 0; - - result = true; - int qtricks = 0; - - if (ctx.search().node_type_store(hand) == MAXNODE) - cutoff = target - tpos.tricks_max; - else - cutoff = tpos.tricks_max - target + (depth >> 2) + 2; - - bool commPartner = false; - const unsigned short (* ris)[DDS_SUITS] = tpos.rank_in_suit; - const unsigned char (* len)[DDS_SUITS] = tpos.length; - HighCardType const * winner = tpos.winner; - - for (int s = 0; s < DDS_SUITS; s++) - { - if ((trump != DDS_NOTRUMP) && (trump != s)) + int suit, commRank = 0, commSuit = -1; + int res; + int lhoTrumpRanks = 0, rhoTrumpRanks = 0; + int cutoff, lowestQtricks = 0; + + result = true; + int qtricks = 0; + + if (ctx.search().node_type_store(hand) == MAXNODE) + cutoff = target - tpos.tricks_max; + else + cutoff = tpos.tricks_max - target + (depth >> 2) + 2; + + bool commPartner = false; + const unsigned short (* ris)[DDS_SUITS] = tpos.rank_in_suit; + const unsigned char (* len)[DDS_SUITS] = tpos.length; + HighCardType const * winner = tpos.winner; + + for (int s = 0; s < DDS_SUITS; s++) { - /* Trump game, and we lead a non-trump suit */ - if (winner[s].hand == partner[hand]) - { - /* Partner has winning card */ - if (ris[hand][s] != 0 && /* Own hand has card */ - (((ris[lho[hand]][s] != 0) || /* LHO not void */ - (ris[lho[hand]][trump] == 0)) && /* LHO no trump */ - ((ris[rho[hand]][s] != 0) || /* RHO not void */ - (ris[rho[hand]][trump] == 0)))) /* RHO no trump */ + if ((trump != DDS_NOTRUMP) && (trump != s)) { - commPartner = true; - commSuit = s; - commRank = winner[s].rank; - break; - } - } - else if ((tpos.second_best[s].hand == partner[hand]) && + /* Trump game, and we lead a non-trump suit */ + if (winner[s].hand == partner[hand]) + { + /* Partner has winning card */ + if (ris[hand][s] != 0 && /* Own hand has card */ + (((ris[lho[hand]][s] != 0) || /* LHO not void */ + (ris[lho[hand]][trump] == 0)) && /* LHO no trump */ + ((ris[rho[hand]][s] != 0) || /* RHO not void */ + (ris[rho[hand]][trump] == 0)))) /* RHO no trump */ + { + commPartner = true; + commSuit = s; + commRank = winner[s].rank; + break; + } + } + else if ((tpos.second_best[s].hand == partner[hand]) && (winner[s].hand == hand) && (len[hand][s] >= 2) && (len[partner[hand]][s] >= 2)) - { - /* Can cross to partner's card: Type Kx opposite Ax */ - if (((ris[lho[hand]][s] != 0) || /* LHO not void */ + { + /* Can cross to partner's card: Type Kx opposite Ax */ + if (((ris[lho[hand]][s] != 0) || /* LHO not void */ (ris[lho[hand]][trump] == 0)) /* LHO no trump */ - && ((ris[rho[hand]][s] != 0) || /* RHO not void */ - (ris[rho[hand]][trump] == 0))) /* RHO no trump */ - { - commPartner = true; - commSuit = s; - commRank = tpos.second_best[s].rank; - break; + && ((ris[rho[hand]][s] != 0) || /* RHO not void */ + (ris[rho[hand]][trump] == 0))) /* RHO no trump */ + { + commPartner = true; + commSuit = s; + commRank = tpos.second_best[s].rank; + break; + } + } } - } - } - else if (trump == DDS_NOTRUMP) - { - if (winner[s].hand == partner[hand]) - { - /* Partner has winning card in NT */ - if (ris[hand][s] != 0) /* Own hand has card */ + else if (trump == DDS_NOTRUMP) { - commPartner = true; - commSuit = s; - commRank = winner[s].rank; - break; - } - } - else if ((tpos.second_best[s].hand == partner[hand]) && + if (winner[s].hand == partner[hand]) + { + /* Partner has winning card in NT */ + if (ris[hand][s] != 0) /* Own hand has card */ + { + commPartner = true; + commSuit = s; + commRank = winner[s].rank; + break; + } + } + else if ((tpos.second_best[s].hand == partner[hand]) && (winner[s].hand == hand) && (len[hand][s] >= 2) && (len[partner[hand]][s] >= 2)) - { - /* Can cross to partner's card: Type Kx opposite Ax */ - commPartner = true; - commSuit = s; - commRank = tpos.second_best[s].rank; - break; - } - } - } - - if ((trump != DDS_NOTRUMP) && (!commPartner) && - (ris[hand][trump] != 0) && - (winner[trump].hand == partner[hand])) - { - /* Communication in trump suit */ - commPartner = true; - commSuit = trump; - commRank = winner[trump].rank; - } - - if (trump != DDS_NOTRUMP) - { - suit = trump; - lhoTrumpRanks = len[lho[hand]][trump]; - rhoTrumpRanks = len[rho[hand]][trump]; - } - else - suit = 0; - - do - { - int countOwn = len[hand][suit]; - int countLho = len[lho[hand]][suit]; - int countRho = len[rho[hand]][suit]; - int countPart = len[partner[hand]][suit]; - int opps = countLho | countRho; - - if (!opps && (countPart == 0)) [[unlikely]] - { - if (countOwn == 0) [[unlikely]] - { - /* Continue with next suit. */ - suit = next_quick_trick_suit(suit, trump); - continue; - } - - /* Long tricks when only leading hand have cards in the suit. */ - if ((trump != DDS_NOTRUMP) && (trump != suit)) - { - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) [[unlikely]] - { - qtricks += countOwn; - if (qtricks >= cutoff) - return qtricks; - suit = next_quick_trick_suit(suit, trump); - continue; - } - else [[likely]] - { - suit = next_quick_trick_suit(suit, trump); - continue; + { + /* Can cross to partner's card: Type Kx opposite Ax */ + commPartner = true; + commSuit = s; + commRank = tpos.second_best[s].rank; + break; + } } - } - else [[likely]] - { - qtricks += countOwn; - if (qtricks >= cutoff) - return qtricks; + } - suit = next_quick_trick_suit(suit, trump); - continue; - } + if ((trump != DDS_NOTRUMP) && (!commPartner) && + (ris[hand][trump] != 0) && + (winner[trump].hand == partner[hand])) + { + /* Communication in trump suit */ + commPartner = true; + commSuit = trump; + commRank = winner[trump].rank; } - else [[likely]] + + if (trump != DDS_NOTRUMP) { - if (!opps && (trump != DDS_NOTRUMP) && (suit == trump)) [[unlikely]] - { - /* The partner but not the opponents have cards in - the trump suit. */ + suit = trump; + lhoTrumpRanks = len[lho[hand]][trump]; + rhoTrumpRanks = len[rho[hand]][trump]; + } + else + suit = 0; - int sum = std::max(countOwn, countPart); - for (int s = 0; s < DDS_SUITS; s++) - { - if ((sum > 0) && - (s != trump) && - (countOwn >= countPart) && - (len[hand][s] > 0) && - (len[partner[hand]][s] == 0)) - { - sum++; - break; - } - } - /* If the additional trick by ruffing causes a cutoff. - (qtricks not incremented.) */ - if (sum >= cutoff) - return sum; - } - else if (!opps) [[unlikely]] - { - /* The partner but not the opponents have cards in the suit. */ - int sum = std::min(countOwn, countPart); - if (trump == DDS_NOTRUMP) - { - if (sum >= cutoff) - return sum; - } - else if ((suit != trump) && - (lhoTrumpRanks == 0) && - (rhoTrumpRanks == 0)) - { - if (sum >= cutoff) - return sum; - } - } + do + { + int countOwn = len[hand][suit]; + int countLho = len[lho[hand]][suit]; + int countRho = len[rho[hand]][suit]; + int countPart = len[partner[hand]][suit]; + int opps = countLho | countRho; - if (commPartner) - { - if (!opps && (countOwn == 0)) [[unlikely]] + if (!opps && (countPart == 0)) [[unlikely]] { - if ((trump != DDS_NOTRUMP) && (trump != suit)) - { - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) [[unlikely]] + if (countOwn == 0) [[unlikely]] { - qtricks += countPart; - tpos.win_ranks[depth][commSuit] |= - bit_map_rank[commRank]; - - if (qtricks >= cutoff) - return qtricks; + /* Continue with next suit. */ + suit = next_quick_trick_suit(suit, trump); + continue; + } - suit = next_quick_trick_suit(suit, trump); - continue; + /* Long tricks when only leading hand have cards in the suit. */ + if ((trump != DDS_NOTRUMP) && (trump != suit)) + { + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) [[unlikely]] + { + qtricks += countOwn; + if (qtricks >= cutoff) + return qtricks; + suit = next_quick_trick_suit(suit, trump); + continue; + } + else [[likely]] + { + suit = next_quick_trick_suit(suit, trump); + continue; + } } else [[likely]] { - suit = next_quick_trick_suit(suit, trump); - continue; - } - } - else [[likely]] - { - qtricks += countPart; - tpos.win_ranks[depth][commSuit] |= - bit_map_rank[commRank]; - - if (qtricks >= cutoff) - return qtricks; + qtricks += countOwn; + if (qtricks >= cutoff) + return qtricks; - suit = next_quick_trick_suit(suit, trump); - continue; - } + suit = next_quick_trick_suit(suit, trump); + continue; + } } else [[likely]] { - if (!opps && (trump != DDS_NOTRUMP) && (suit == trump)) [[unlikely]] - { - int sum = std::max(countOwn, countPart); - for (int s = 0; s < DDS_SUITS; s++) + if (!opps && (trump != DDS_NOTRUMP) && (suit == trump)) [[unlikely]] { - if ((sum > 0) && - (s != trump) && - (countOwn <= countPart) && - (len[partner[hand]][s] > 0) && - (len[hand][s] == 0)) - { - sum++; - break; - } + /* The partner but not the opponents have cards in + the trump suit. */ + + int sum = std::max(countOwn, countPart); + for (int s = 0; s < DDS_SUITS; s++) + { + if ((sum > 0) && + (s != trump) && + (countOwn >= countPart) && + (len[hand][s] > 0) && + (len[partner[hand]][s] == 0)) + { + sum++; + break; + } + } + /* If the additional trick by ruffing causes a cutoff. + (qtricks not incremented.) */ + if (sum >= cutoff) + return sum; } - if (sum >= cutoff) + else if (!opps) [[unlikely]] { - tpos.win_ranks[depth][commSuit] |= - bit_map_rank[commRank]; - return sum; + /* The partner but not the opponents have cards in the suit. */ + int sum = std::min(countOwn, countPart); + if (trump == DDS_NOTRUMP) + { + if (sum >= cutoff) + return sum; + } + else if ((suit != trump) && + (lhoTrumpRanks == 0) && + (rhoTrumpRanks == 0)) + { + if (sum >= cutoff) + return sum; + } } - } - else if (!opps) [[unlikely]] - { - int sum = std::min(countOwn, countPart); - if (trump == DDS_NOTRUMP) + + if (commPartner) { - if (sum >= cutoff) - return sum; - } - else if ((suit != trump) && + if (!opps && (countOwn == 0)) [[unlikely]] + { + if ((trump != DDS_NOTRUMP) && (trump != suit)) + { + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) [[unlikely]] + { + qtricks += countPart; + tpos.win_ranks[depth][commSuit] |= + bit_map_rank[commRank]; + + if (qtricks >= cutoff) + return qtricks; + + suit = next_quick_trick_suit(suit, trump); + continue; + } + else [[likely]] + { + suit = next_quick_trick_suit(suit, trump); + continue; + } + } + else [[likely]] + { + qtricks += countPart; + tpos.win_ranks[depth][commSuit] |= + bit_map_rank[commRank]; + + if (qtricks >= cutoff) + return qtricks; + + suit = next_quick_trick_suit(suit, trump); + continue; + } + } + else [[likely]] + { + if (!opps && (trump != DDS_NOTRUMP) && (suit == trump)) [[unlikely]] + { + int sum = std::max(countOwn, countPart); + for (int s = 0; s < DDS_SUITS; s++) + { + if ((sum > 0) && + (s != trump) && + (countOwn <= countPart) && + (len[partner[hand]][s] > 0) && + (len[hand][s] == 0)) + { + sum++; + break; + } + } + if (sum >= cutoff) + { + tpos.win_ranks[depth][commSuit] |= + bit_map_rank[commRank]; + return sum; + } + } + else if (!opps) [[unlikely]] + { + int sum = std::min(countOwn, countPart); + if (trump == DDS_NOTRUMP) + { + if (sum >= cutoff) + return sum; + } + else if ((suit != trump) && (lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) - { - if (sum >= cutoff) - return sum; + { + if (sum >= cutoff) + return sum; + } + } + } } - } } - } - } - - if (winner[suit].rank == 0) [[unlikely]] - { - suit = next_quick_trick_suit(suit, trump); - continue; - } - if (winner[suit].hand == hand) - { - if ((trump != DDS_NOTRUMP) && (trump != suit)) - { - qtricks = QtricksLeadHandTrump(hand, tpos, cutoff, depth, - countLho, countRho, lhoTrumpRanks, rhoTrumpRanks, - countOwn, countPart, suit, qtricks, res); - - if (res == 1) - return qtricks; - else if (res == 2) + if (winner[suit].rank == 0) [[unlikely]] { - suit = next_quick_trick_suit(suit, trump); - continue; - } - } - else - { - qtricks = QtricksLeadHandNT(hand, tpos, cutoff, depth, - countLho, countRho, lhoTrumpRanks, rhoTrumpRanks, - commPartner, commSuit, countOwn, countPart, - suit, qtricks, trump, res); - - if (res == 1) - return qtricks; - else if (res == 2) - { - suit = next_quick_trick_suit(suit, trump); - continue; + suit = next_quick_trick_suit(suit, trump); + continue; } - } - } - /* It was not possible to take a quick trick by own winning - card in the suit */ - else - { - /* Partner winning card? */ - if (winner[suit].hand == partner[hand]) - { - /* Winner found at partner*/ - if (commPartner) + if (winner[suit].hand == hand) { - /* There is communication with the partner */ - if ((trump != DDS_NOTRUMP) && (trump != suit)) - { - qtricks = QuickTricksPartnerHandTrump(hand, tpos, - cutoff, depth, countLho, countRho, - lhoTrumpRanks, rhoTrumpRanks, countOwn, - countPart, suit, qtricks, commSuit, commRank, res, ctx); - - if (res == 1) - return qtricks; - else if (res == 2) + if ((trump != DDS_NOTRUMP) && (trump != suit)) { - suit = next_quick_trick_suit(suit, trump); - continue; + qtricks = QtricksLeadHandTrump(hand, tpos, cutoff, depth, + countLho, countRho, lhoTrumpRanks, rhoTrumpRanks, + countOwn, countPart, suit, qtricks, res); + + if (res == 1) + return qtricks; + else if (res == 2) + { + suit = next_quick_trick_suit(suit, trump); + continue; + } } - } - else - { - qtricks = QuickTricksPartnerHandNT(hand, tpos, cutoff, - depth, countLho, countRho, countOwn, countPart, - suit, qtricks, commSuit, commRank, res, ctx); - - if (res == 1) - return qtricks; - else if (res == 2) + else { - suit = next_quick_trick_suit(suit, trump); - continue; + qtricks = QtricksLeadHandNT(hand, tpos, cutoff, depth, + countLho, countRho, lhoTrumpRanks, rhoTrumpRanks, + commPartner, commSuit, countOwn, countPart, + suit, qtricks, trump, res); + + if (res == 1) + return qtricks; + else if (res == 2) + { + suit = next_quick_trick_suit(suit, trump); + continue; + } } - } } - } - } - if ((trump != DDS_NOTRUMP) && (suit != trump) && - (countOwn > 0) && (lowestQtricks == 0) && - ((qtricks == 0) || - ((winner[suit].hand != hand) && - (winner[suit].hand != partner[hand]) && - (winner[trump].hand != hand) && - (winner[trump].hand != partner[hand])))) - { - if ((countPart == 0) && (len[partner[hand]][trump] > 0)) - { - if (((countRho > 0) || (len[rho[hand]][trump] == 0)) && - ((countLho > 0) || (len[lho[hand]][trump] == 0))) - { - lowestQtricks = 1; - if (1 >= cutoff) - return 1; - suit = next_quick_trick_suit(suit, trump); - continue; - } - else if ((countRho == 0) && (countLho == 0)) - { - if ((ris[lho[hand]][trump] | - ris[rho[hand]][trump]) < - ris[partner[hand]][trump]) - { - lowestQtricks = 1; - int rr = highest_rank[ris[partner[hand]][trump]]; - if (rr != 0) - { - tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; - if (1 >= cutoff) - return 1; - } - } - suit = next_quick_trick_suit(suit, trump); - continue; - } - else if (countLho == 0) + /* It was not possible to take a quick trick by own winning + card in the suit */ + else { - if (ris[lho[hand]][trump] < - ris[partner[hand]][trump]) - { - lowestQtricks = 1; - for (int rr = 14; rr >= 2; rr--) + /* Partner winning card? */ + if (winner[suit].hand == partner[hand]) { - if ((ris[partner[hand]][trump] & bit_map_rank[rr]) != 0) - { - tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; - break; - } + /* Winner found at partner*/ + if (commPartner) + { + /* There is communication with the partner */ + if ((trump != DDS_NOTRUMP) && (trump != suit)) + { + qtricks = QuickTricksPartnerHandTrump(hand, tpos, + cutoff, depth, countLho, countRho, + lhoTrumpRanks, rhoTrumpRanks, countOwn, + countPart, suit, qtricks, commSuit, commRank, res, ctx); + + if (res == 1) + return qtricks; + else if (res == 2) + { + suit = next_quick_trick_suit(suit, trump); + continue; + } + } + else + { + qtricks = QuickTricksPartnerHandNT(hand, tpos, cutoff, + depth, countLho, countRho, countOwn, countPart, + suit, qtricks, commSuit, commRank, res, ctx); + + if (res == 1) + return qtricks; + else if (res == 2) + { + suit = next_quick_trick_suit(suit, trump); + continue; + } + } + } } - if (1 >= cutoff) - return 1; - } - suit = next_quick_trick_suit(suit, trump); - continue; } - else if (countRho == 0) + if ((trump != DDS_NOTRUMP) && (suit != trump) && + (countOwn > 0) && (lowestQtricks == 0) && + ((qtricks == 0) || + ((winner[suit].hand != hand) && + (winner[suit].hand != partner[hand]) && + (winner[trump].hand != hand) && + (winner[trump].hand != partner[hand])))) { - if (ris[rho[hand]][trump] < - ris[partner[hand]][trump]) - { - lowestQtricks = 1; - for (int rr = 14; rr >= 2; rr--) + if ((countPart == 0) && (len[partner[hand]][trump] > 0)) { - if ((ris[partner[hand]][trump] & bit_map_rank[rr]) != 0) - { - tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; - break; - } + if (((countRho > 0) || (len[rho[hand]][trump] == 0)) && + ((countLho > 0) || (len[lho[hand]][trump] == 0))) + { + lowestQtricks = 1; + if (1 >= cutoff) + return 1; + suit = next_quick_trick_suit(suit, trump); + continue; + } + else if ((countRho == 0) && (countLho == 0)) + { + if ((ris[lho[hand]][trump] | + ris[rho[hand]][trump]) < + ris[partner[hand]][trump]) + { + lowestQtricks = 1; + + int rr = highest_rank[ris[partner[hand]][trump]]; + if (rr != 0) + { + tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; + if (1 >= cutoff) + return 1; + } + } + suit = next_quick_trick_suit(suit, trump); + continue; + } + else if (countLho == 0) + { + if (ris[lho[hand]][trump] < + ris[partner[hand]][trump]) + { + lowestQtricks = 1; + for (int rr = 14; rr >= 2; rr--) + { + if ((ris[partner[hand]][trump] & bit_map_rank[rr]) != 0) + { + tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; + break; + } + } + if (1 >= cutoff) + return 1; + } + suit = next_quick_trick_suit(suit, trump); + continue; + } + else if (countRho == 0) + { + if (ris[rho[hand]][trump] < + ris[partner[hand]][trump]) + { + lowestQtricks = 1; + for (int rr = 14; rr >= 2; rr--) + { + if ((ris[partner[hand]][trump] & bit_map_rank[rr]) != 0) + { + tpos.win_ranks[depth][trump] |= bit_map_rank[rr]; + break; + } + } + if (1 >= cutoff) + return 1; + } + suit = next_quick_trick_suit(suit, trump); + continue; + } } - if (1 >= cutoff) - return 1; - } - suit = next_quick_trick_suit(suit, trump); - continue; } - } - } - if (qtricks >= cutoff) - return qtricks; + if (qtricks >= cutoff) + return qtricks; - suit = next_quick_trick_suit(suit, trump); - } - while (suit <= 3); + suit = next_quick_trick_suit(suit, trump); + } + while (suit <= 3); - if (qtricks == 0) - { - if ((trump == DDS_NOTRUMP) || (winner[trump].hand == -1)) + if (qtricks == 0) { - for (int ss = 0; ss < DDS_SUITS; ss++) - { - if (winner[ss].hand == -1) - continue; - if (len[hand][ss] > 0) + if ((trump == DDS_NOTRUMP) || (winner[trump].hand == -1)) { - tpos.win_ranks[depth][ss] = bit_map_rank[winner[ss].rank]; - } - } + for (int ss = 0; ss < DDS_SUITS; ss++) + { + if (winner[ss].hand == -1) + continue; + if (len[hand][ss] > 0) + { + tpos.win_ranks[depth][ss] = bit_map_rank[winner[ss].rank]; + } + } - if (ctx.search().node_type_store(hand) != MAXNODE) - cutoff = target - tpos.tricks_max; - else - { - cutoff = tpos.tricks_max - target + (depth >> 2) + 2; - } + if (ctx.search().node_type_store(hand) != MAXNODE) + cutoff = target - tpos.tricks_max; + else + { + cutoff = tpos.tricks_max - target + (depth >> 2) + 2; + } - if (1 >= cutoff) - return 0; + if (1 >= cutoff) + return 0; + } } - } - result = false; - return qtricks; + result = false; + return qtricks; } int QtricksLeadHandTrump( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int lhoTrumpRanks, - const int rhoTrumpRanks, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - int& res) + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int lhoTrumpRanks, + const int rhoTrumpRanks, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + int& res) { - /* res=0 Continue with same suit. + /* res=0 Continue with same suit. res=1 Cutoff. res=2 Continue with next suit. */ - res = 1; - int qt = qtricks; - if (((countLho != 0) || + res = 1; + int qt = qtricks; + if (((countLho != 0) || (lhoTrumpRanks == 0)) && - ((countRho != 0) || (rhoTrumpRanks == 0))) - { - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.winner[suit].rank]; - qt++; - if (qt >= cutoff) - return qt; - - if ((countLho <= 1) && - (countRho <= 1) && - (countPart <= 1) && - (lhoTrumpRanks == 0) && - (rhoTrumpRanks == 0)) + ((countRho != 0) || (rhoTrumpRanks == 0))) { - qt += countOwn - 1; - if (qt >= cutoff) - return qt; - res = 2; - return qt; + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.winner[suit].rank]; + qt++; + if (qt >= cutoff) + return qt; + + if ((countLho <= 1) && + (countRho <= 1) && + (countPart <= 1) && + (lhoTrumpRanks == 0) && + (rhoTrumpRanks == 0)) + { + qt += countOwn - 1; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } } - } - if (tpos.second_best[suit].hand == hand) - { - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + if (tpos.second_best[suit].hand == hand) { - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 2) && (countRho <= 2) && (countPart <= 2)) - { - qt += countOwn - 2; - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + { + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && (countRho <= 2) && (countPart <= 2)) + { + qt += countOwn - 2; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } + } } - } - else if ((tpos.second_best[suit].hand == partner[hand]) + else if ((tpos.second_best[suit].hand == partner[hand]) && (countOwn > 1) && (countPart > 1)) - { - /* Second best at partner and suit length of own - hand and partner > 1 */ - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) { - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 2) && - (countRho <= 2) && - ((countPart <= 2) || (countOwn <= 2))) - { - qt += std::max(countOwn - 2, countPart - 2); - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } + /* Second best at partner and suit length of own + hand and partner > 1 */ + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + { + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && + (countRho <= 2) && + ((countPart <= 2) || (countOwn <= 2))) + { + qt += std::max(countOwn - 2, countPart - 2); + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } + } } - } - res = 0; - return qt; + res = 0; + return qt; } int QtricksLeadHandNT( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - int& lhoTrumpRanks, - int& rhoTrumpRanks, - const bool commPartner, - const int commSuit, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int trump, - int& res) + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + int& lhoTrumpRanks, + int& rhoTrumpRanks, + const bool commPartner, + const int commSuit, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int trump, + int& res) { - /* res=0 Continue with same suit. + /* res=0 Continue with same suit. res=1 Cutoff. res=2 Continue with next suit. */ - res = 1; - int qt = qtricks; - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.winner[suit].rank]; - - qt++; - if (qt >= cutoff) - return qt; - if ((trump == suit) && ((!commPartner) || (suit != commSuit))) - { - lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); - rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); - } - - if ((countLho <= 1) && (countRho <= 1) && (countPart <= 1)) - { - qt += countOwn - 1; - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } - - if (tpos.second_best[suit].hand == hand) - { + res = 1; + int qt = qtricks; tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; + bit_map_rank[tpos.winner[suit].rank]; + qt++; if (qt >= cutoff) - return qt; + return qt; if ((trump == suit) && ((!commPartner) || (suit != commSuit))) { - lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); - rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); + lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); + rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); } - if ((countLho <= 2) && (countRho <= 2) && (countPart <= 2)) + + if ((countLho <= 1) && (countRho <= 1) && (countPart <= 1)) { - qt += countOwn - 2; - if (qt >= cutoff) + qt += countOwn - 1; + if (qt >= cutoff) + return qt; + res = 2; return qt; - res = 2; - return qt; } - } - else if ((tpos.second_best[suit].hand == partner[hand]) - && (countOwn > 1) && (countPart > 1)) - { - /* Second best at partner and suit length of own - hand and partner > 1 */ - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; - qt++; - if (qt >= cutoff) - return qt; - if ((trump == suit) && ((!commPartner) || (suit != commSuit))) + + if (tpos.second_best[suit].hand == hand) { - lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); - rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + qt++; + if (qt >= cutoff) + return qt; + if ((trump == suit) && ((!commPartner) || (suit != commSuit))) + { + lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); + rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); + } + if ((countLho <= 2) && (countRho <= 2) && (countPart <= 2)) + { + qt += countOwn - 2; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } } - if ((countLho <= 2) && - (countRho <= 2) && - ((countPart <= 2) || (countOwn <= 2))) + else if ((tpos.second_best[suit].hand == partner[hand]) + && (countOwn > 1) && (countPart > 1)) { - qt += std::max(countOwn - 2, countPart - 2); - if (qt >= cutoff) - return qt; - res = 2; - return qt; + /* Second best at partner and suit length of own + hand and partner > 1 */ + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + qt++; + if (qt >= cutoff) + return qt; + if ((trump == suit) && ((!commPartner) || (suit != commSuit))) + { + lhoTrumpRanks = std::max(0, lhoTrumpRanks - 1); + rhoTrumpRanks = std::max(0, rhoTrumpRanks - 1); + } + if ((countLho <= 2) && + (countRho <= 2) && + ((countPart <= 2) || (countOwn <= 2))) + { + qt += std::max(countOwn - 2, countPart - 2); + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } } - } - res = 0; - return qt; + res = 0; + return qt; } int QuickTricksPartnerHandTrump( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int lhoTrumpRanks, - const int rhoTrumpRanks, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int commSuit, - const int commRank, - int& res, - SolverContext& ctx) + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int lhoTrumpRanks, + const int rhoTrumpRanks, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int commSuit, + const int commRank, + int& res, + SolverContext& ctx) { - /* res=0 Continue with same suit. + /* res=0 Continue with same suit. res=1 Cutoff. res=2 Continue with next suit. */ - res = 1; - int qt = qtricks; - if (((countLho != 0) || (lhoTrumpRanks == 0)) && - ((countRho != 0) || (rhoTrumpRanks == 0))) - { - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.winner[suit].rank]; + res = 1; + int qt = qtricks; + if (((countLho != 0) || (lhoTrumpRanks == 0)) && + ((countRho != 0) || (rhoTrumpRanks == 0))) + { + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.winner[suit].rank]; - tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; - qt++; /* A trick can be taken */ - if (qt >= cutoff) - return qt; - if ((countLho <= 1) && - (countRho <= 1) && - (countOwn <= 1) && - (lhoTrumpRanks == 0) && - (rhoTrumpRanks == 0)) - { - qt += countPart - 1; - if (qt >= cutoff) - return qt; - res = 2; - return qt; + qt++; /* A trick can be taken */ + if (qt >= cutoff) + return qt; + if ((countLho <= 1) && + (countRho <= 1) && + (countOwn <= 1) && + (lhoTrumpRanks == 0) && + (rhoTrumpRanks == 0)) + { + qt += countPart - 1; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } } - } - if (tpos.second_best[suit].hand == partner[hand]) - { - /* Second best found in partners hand */ - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + if (tpos.second_best[suit].hand == partner[hand]) { - /* Opponents have no trump */ - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; - - tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 2) && (countRho <= 2) && (countOwn <= 2)) - { - qt += countPart - 2; - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } + /* Second best found in partners hand */ + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + { + /* Opponents have no trump */ + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + + tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && (countRho <= 2) && (countOwn <= 2)) + { + qt += countPart - 2; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } + } } - } - else if ((tpos.second_best[suit].hand == hand) && + else if ((tpos.second_best[suit].hand == hand) && (countPart > 1) && (countOwn > 1)) - { - /* Second best found in own hand and suit lengths of own hand - and partner > 1*/ - - if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) { - /* Opponents have no trump */ - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; - - tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + /* Second best found in own hand and suit lengths of own hand + and partner > 1*/ - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 2) && - (countRho <= 2) && - ((countOwn <= 2) || (countPart <= 2))) - { - qt += std::max(countPart - 2, countOwn - 2); - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } + if ((lhoTrumpRanks == 0) && (rhoTrumpRanks == 0)) + { + /* Opponents have no trump */ + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + + tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && + (countRho <= 2) && + ((countOwn <= 2) || (countPart <= 2))) + { + qt += std::max(countPart - 2, countOwn - 2); + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } + } } - } - else if ((suit == commSuit) && + else if ((suit == commSuit) && (tpos.second_best[suit].hand == lho[hand]) && ((countLho >= 2) || (lhoTrumpRanks == 0)) && ((countRho >= 2) || (rhoTrumpRanks == 0))) - { - unsigned short ranks = 0; - for (int h = 0; h < DDS_HANDS; h++) - ranks |= tpos.rank_in_suit[h][suit]; - - if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { - tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; - - tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + unsigned short ranks = 0; + for (int h = 0; h < DDS_HANDS; h++) + ranks |= tpos.rank_in_suit[h][suit]; - qt++; - if (qt >= cutoff) - return qt; - if ((countOwn <= 2) && - (countLho <= 2) && - (countRho <= 2) && - (lhoTrumpRanks == 0) && - (rhoTrumpRanks == 0)) - { - qt += countPart - 2; - if (qt >= cutoff) - return qt; - } + if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) + { + tpos.win_ranks[depth][suit] |= bit_map_rank[ + static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; + + tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + + qt++; + if (qt >= cutoff) + return qt; + if ((countOwn <= 2) && + (countLho <= 2) && + (countRho <= 2) && + (lhoTrumpRanks == 0) && + (rhoTrumpRanks == 0)) + { + qt += countPart - 2; + if (qt >= cutoff) + return qt; + } + } } - } - res = 0; - return qt; + res = 0; + return qt; } int QuickTricksPartnerHandNT( - const int hand, - Pos& tpos, - const int cutoff, - const int depth, - const int countLho, - const int countRho, - const int countOwn, - const int countPart, - const int suit, - const int qtricks, - const int commSuit, - const int commRank, - int& res, - SolverContext& ctx) + const int hand, + Pos& tpos, + const int cutoff, + const int depth, + const int countLho, + const int countRho, + const int countOwn, + const int countPart, + const int suit, + const int qtricks, + const int commSuit, + const int commRank, + int& res, + SolverContext& ctx) { - res = 1; - int qt = qtricks; - - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.winner[suit].rank]; - - tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; + res = 1; + int qt = qtricks; - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 1) && (countRho <= 1) && (countOwn <= 1)) - { - qt += countPart - 1; - if (qt >= cutoff) - return qt; - res = 2; - return qt; - } - - if (tpos.second_best[suit].hand == partner[hand]) - { - /* Second best found in partners hand */ tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; + bit_map_rank[tpos.winner[suit].rank]; + + tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; qt++; if (qt >= cutoff) - return qt; - if ((countLho <= 2) && (countRho <= 2) && (countOwn <= 2)) + return qt; + if ((countLho <= 1) && (countRho <= 1) && (countOwn <= 1)) { - qt += countPart - 2; - if (qt >= cutoff) + qt += countPart - 1; + if (qt >= cutoff) + return qt; + res = 2; return qt; - res = 2; - return qt; } - } - else if ((tpos.second_best[suit].hand == hand) + + if (tpos.second_best[suit].hand == partner[hand]) + { + /* Second best found in partners hand */ + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; + + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && (countRho <= 2) && (countOwn <= 2)) + { + qt += countPart - 2; + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } + } + else if ((tpos.second_best[suit].hand == hand) && (countPart > 1) && (countOwn > 1)) - { - /* Second best found in own hand and own and + { + /* Second best found in own hand and own and partner's suit length > 1 */ - tpos.win_ranks[depth][suit] |= - bit_map_rank[tpos.second_best[suit].rank]; + tpos.win_ranks[depth][suit] |= + bit_map_rank[tpos.second_best[suit].rank]; - qt++; - if (qt >= cutoff) - return qt; - if ((countLho <= 2) && - (countRho <= 2) && - ((countOwn <= 2) || (countPart <= 2))) - { - qt += std::max(countPart - 2, countOwn - 2); - if (qt >= cutoff) - return qt; - res = 2; - return qt; + qt++; + if (qt >= cutoff) + return qt; + if ((countLho <= 2) && + (countRho <= 2) && + ((countOwn <= 2) || (countPart <= 2))) + { + qt += std::max(countPart - 2, countOwn - 2); + if (qt >= cutoff) + return qt; + res = 2; + return qt; + } } - } - else if ((suit == commSuit) && + else if ((suit == commSuit) && (tpos.second_best[suit].hand == lho[hand])) - { - unsigned short ranks = 0; - for (int h = 0; h < DDS_HANDS; h++) - ranks |= tpos.rank_in_suit[h][suit]; - - if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { - tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; - qt++; - if (qt >= cutoff) - return qt; - if ((countOwn <= 2) && (countLho <= 2) && (countRho <= 2)) - { - qt += countPart - 2; - if (qt >= cutoff) - return qt; - } + unsigned short ranks = 0; + for (int h = 0; h < DDS_HANDS; h++) + ranks |= tpos.rank_in_suit[h][suit]; + + if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) + { + tpos.win_ranks[depth][suit] |= bit_map_rank[ + static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; + qt++; + if (qt >= cutoff) + return qt; + if ((countOwn <= 2) && (countLho <= 2) && (countRho <= 2)) + { + qt += countPart - 2; + if (qt >= cutoff) + return qt; + } + } } - } - res = 0; - return qt; + res = 0; + return qt; } bool QuickTricksSecondHand( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx) + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx) { - if (depth == ctx.search().ini_depth()) - return false; + if (depth == ctx.search().ini_depth()) + return false; - int ss = tpos.move[depth + 1].suit; - unsigned short (*ris)[DDS_SUITS] = tpos.rank_in_suit; - unsigned short ranks = static_cast + int ss = tpos.move[depth + 1].suit; + unsigned short (*ris)[DDS_SUITS] = tpos.rank_in_suit; + unsigned short ranks = static_cast (ris[hand][ss] | ris[partner[hand]][ss]); - for (int s = 0; s < DDS_SUITS; s++) - tpos.win_ranks[depth][s] = 0; + for (int s = 0; s < DDS_SUITS; s++) + tpos.win_ranks[depth][s] = 0; - if ((trump != DDS_NOTRUMP) && (ss != trump) && - (((ris[hand][ss] == 0) && (ris[hand][trump] != 0)) || - ((ris[partner[hand]][ss] == 0) && - (ris[partner[hand]][trump] != 0)))) - { - if ((ris[lho[hand]][ss] == 0) && - (ris[lho[hand]][trump] != 0)) - return false; - - /* Own side can ruff, their side can't. */ - } - - else if (ranks > (bit_map_rank[tpos.move[depth + 1].rank] | - ris[lho[hand]][ss])) - { if ((trump != DDS_NOTRUMP) && (ss != trump) && - (ris[lho[hand]][trump] != 0) && - (ris[lho[hand]][ss] == 0)) - return false; - - /* Own side has highest card in suit, which LHO can't ruff. */ - - int rr = highest_rank[ranks]; - tpos.win_ranks[depth][ss] = bit_map_rank[rr]; - } - else - { - /* No easy way to win current trick for own side. */ - return false; - } - - int qtricks = 1; + (((ris[hand][ss] == 0) && (ris[hand][trump] != 0)) || + ((ris[partner[hand]][ss] == 0) && + (ris[partner[hand]][trump] != 0)))) + { + if ((ris[lho[hand]][ss] == 0) && + (ris[lho[hand]][trump] != 0)) + return false; - int cutoff; - if (ctx.search().node_type_store(hand) == MAXNODE) - cutoff = target - tpos.tricks_max; - else - cutoff = tpos.tricks_max - target + (depth >> 2) + 3; + /* Own side can ruff, their side can't. */ + } - if (qtricks >= cutoff) - return true; + else if (ranks > (bit_map_rank[tpos.move[depth + 1].rank] | + ris[lho[hand]][ss])) + { + if ((trump != DDS_NOTRUMP) && (ss != trump) && + (ris[lho[hand]][trump] != 0) && + (ris[lho[hand]][ss] == 0)) + return false; - if (trump != DDS_NOTRUMP) - return false; + /* Own side has highest card in suit, which LHO can't ruff. */ - /* In NT, second winner (by rank) in same suit. */ + int rr = highest_rank[ranks]; + tpos.win_ranks[depth][ss] = bit_map_rank[rr]; + } + else + { + /* No easy way to win current trick for own side. */ + return false; + } - int hh; - if (ris[hand][ss] > ris[partner[hand]][ss]) - hh = hand; /* Hand to lead next trick */ - else - hh = partner[hand]; + int qtricks = 1; - if ((tpos.winner[ss].hand == hh) && - (tpos.second_best[ss].rank != 0) && - (tpos.second_best[ss].hand == hh)) - { - qtricks++; - tpos.win_ranks[depth][ss] |= - bit_map_rank[tpos.second_best[ss].rank]; + int cutoff; + if (ctx.search().node_type_store(hand) == MAXNODE) + cutoff = target - tpos.tricks_max; + else + cutoff = tpos.tricks_max - target + (depth >> 2) + 3; if (qtricks >= cutoff) - return true; - } + return true; + + if (trump != DDS_NOTRUMP) + return false; - for (int s = 0; s < DDS_SUITS; s++) - { - if ((s == ss) || (tpos.length[hh][s] == 0)) - continue; + /* In NT, second winner (by rank) in same suit. */ - if ((tpos.length[lho[hh]][s] == 0) && - (tpos.length[rho[hh]][s] == 0) && - (tpos.length[partner[hh]][s] == 0)) + int hh; + if (ris[hand][ss] > ris[partner[hand]][ss]) + hh = hand; /* Hand to lead next trick */ + else + hh = partner[hand]; + + if ((tpos.winner[ss].hand == hh) && + (tpos.second_best[ss].rank != 0) && + (tpos.second_best[ss].hand == hh)) { - /* Long other suit which nobody else holds. */ - qtricks += count_table[ris[hh][s]]; - if (qtricks >= cutoff) - return true; + qtricks++; + tpos.win_ranks[depth][ss] |= + bit_map_rank[tpos.second_best[ss].rank]; + + if (qtricks >= cutoff) + return true; } - else if ((tpos.winner[s].rank != 0) && - (tpos.winner[s].hand == hh)) + + for (int s = 0; s < DDS_SUITS; s++) { - /* Top winners in other suits. */ - qtricks++; - tpos.win_ranks[depth][s] |= - bit_map_rank[tpos.winner[s].rank]; + if ((s == ss) || (tpos.length[hh][s] == 0)) + continue; - if (qtricks >= cutoff) - return true; + if ((tpos.length[lho[hh]][s] == 0) && + (tpos.length[rho[hh]][s] == 0) && + (tpos.length[partner[hh]][s] == 0)) + { + /* Long other suit which nobody else holds. */ + qtricks += count_table[ris[hh][s]]; + if (qtricks >= cutoff) + return true; + } + else if ((tpos.winner[s].rank != 0) && + (tpos.winner[s].hand == hh)) + { + /* Top winners in other suits. */ + qtricks++; + tpos.win_ranks[depth][s] |= + bit_map_rank[tpos.winner[s].rank]; + + if (qtricks >= cutoff) + return true; + } } - } - return false; + return false; } diff --git a/library/src/quick_tricks.hpp b/library/src/quick_tricks.hpp index 5b28eafee..6bc495952 100644 --- a/library/src/quick_tricks.hpp +++ b/library/src/quick_tricks.hpp @@ -20,18 +20,18 @@ auto next_quick_trick_suit(int suit, int trump) -> int; int QuickTricks( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - bool& result, - SolverContext& ctx); + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + bool& result, + SolverContext& ctx); bool QuickTricksSecondHand( - Pos& tpos, - const int hand, - const int depth, - const int target, - const int trump, - SolverContext& ctx); + Pos& tpos, + const int hand, + const int depth, + const int target, + const int trump, + SolverContext& ctx); diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index 0f29b0af8..27b223b21 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -25,104 +25,104 @@ extern Scheduler scheduler; auto same_board( - const Boards& bds, - const unsigned index1, - const unsigned index2) -> bool; + const Boards& bds, + const unsigned index1, + const unsigned index2) -> bool; static auto boards_from_pbn( - BoardsPBN const& bop, - Boards& bo) -> int + BoardsPBN const& bop, + Boards& bo) -> int { - bo.no_of_boards = bop.no_of_boards; - if (bo.no_of_boards > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; - - for (int k = 0; k < bop.no_of_boards; k++) - { - bo.mode[k] = bop.mode[k]; - bo.solutions[k] = bop.solutions[k]; - bo.target[k] = bop.target[k]; - bo.deals[k].first = bop.deals[k].first; - bo.deals[k].trump = bop.deals[k].trump; - - for (int i = 0; i <= 2; i++) + bo.no_of_boards = bop.no_of_boards; + if (bo.no_of_boards > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; + + for (int k = 0; k < bop.no_of_boards; k++) { - bo.deals[k].currentTrickSuit[i] = bop.deals[k].currentTrickSuit[i]; - bo.deals[k].currentTrickRank[i] = bop.deals[k].currentTrickRank[i]; + bo.mode[k] = bop.mode[k]; + bo.solutions[k] = bop.solutions[k]; + bo.target[k] = bop.target[k]; + bo.deals[k].first = bop.deals[k].first; + bo.deals[k].trump = bop.deals[k].trump; + + for (int i = 0; i <= 2; i++) + { + bo.deals[k].currentTrickSuit[i] = bop.deals[k].currentTrickSuit[i]; + bo.deals[k].currentTrickRank[i] = bop.deals[k].currentTrickRank[i]; + } + + if (convert_from_pbn(bop.deals[k].remainCards, bo.deals[k].remainCards) + != RETURN_NO_FAULT) + return RETURN_PBN_FAULT; } - if (convert_from_pbn(bop.deals[k].remainCards, bo.deals[k].remainCards) - != RETURN_NO_FAULT) - return RETURN_PBN_FAULT; - } - - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto solve_all_boards_n( - Boards const& bds, - SolvedBoards& solved, - int max_threads) -> int + Boards const& bds, + SolvedBoards& solved, + int max_threads) -> int { - const int n = bds.no_of_boards; - if (n > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; + const int n = bds.no_of_boards; + if (n > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; - for (int k = 0; k < MAXNOOFBOARDS; k++) - solved.solved_board[k].cards = 0; + for (int k = 0; k < MAXNOOFBOARDS; k++) + solved.solved_board[k].cards = 0; - scheduler.RegisterRun(RunMode::DDS_RUN_SOLVE, bds); + scheduler.RegisterRun(RunMode::DDS_RUN_SOLVE, bds); - START_BLOCK_TIMER; + START_BLOCK_TIMER; - const int err = parallel_all_boards_n(n, max_threads, - [&](const int worker_id, const int bno) -> int { - (void)worker_id; + const int err = parallel_all_boards_n(n, max_threads, + [&](const int worker_id, const int bno) -> int { + (void)worker_id; - FutureTricks fut; - const auto t0 = std::chrono::steady_clock::now(); - // Persistent per-thread context: reuses the worker's TT across boards - // and consecutive batch calls instead of allocating one per board. - const int res = solve_board( - dds::internal::worker_solver_context(), - bds.deals[bno], bds.target[bno], bds.solutions[bno], - bds.mode[bno], &fut); - auto dur = std::chrono::duration_cast( - std::chrono::steady_clock::now() - t0).count(); - scheduler.SetBoardTime(bno, dur); + FutureTricks fut; + const auto t0 = std::chrono::steady_clock::now(); + // Persistent per-thread context: reuses the worker's TT across boards + // and consecutive batch calls instead of allocating one per board. + const int res = solve_board( + dds::internal::worker_solver_context(), + bds.deals[bno], bds.target[bno], bds.solutions[bno], + bds.mode[bno], &fut); + auto dur = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + scheduler.SetBoardTime(bno, dur); - if (res == RETURN_NO_FAULT) - solved.solved_board[bno] = fut; - return res; - }); + if (res == RETURN_NO_FAULT) + solved.solved_board[bno] = fut; + return res; + }); - END_BLOCK_TIMER; + END_BLOCK_TIMER; - if (err != RETURN_NO_FAULT) - return err; + if (err != RETURN_NO_FAULT) + return err; - solved.no_of_boards = n; + solved.no_of_boards = n; #ifdef DDS_SCHEDULER - scheduler.PrintTiming(); + scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto solve_all_boards_pbn_n( - BoardsPBN const& bop, - SolvedBoards& solved, - const int max_threads) -> int + BoardsPBN const& bop, + SolvedBoards& solved, + const int max_threads) -> int { - Boards bo; - const int rc = boards_from_pbn(bop, bo); - if (rc != RETURN_NO_FAULT) - return rc; - return solve_all_boards_n(bo, solved, max_threads); + Boards bo; + const int rc = boards_from_pbn(bop, bo); + if (rc != RETURN_NO_FAULT) + return rc; + return solve_all_boards_n(bo, solved, max_threads); } @@ -132,27 +132,27 @@ auto solve_all_boards_pbn_n( * Public API documentation is maintained in the API headers. */ int STDCALL SolveBoardPBN( - DealPBN dlpbn, - int target, - int solutions, - int mode, - FutureTricks * futp, - int thrId) + DealPBN dlpbn, + int target, + int solutions, + int mode, + FutureTricks * futp, + int thrId) { - Deal dl; - if (convert_from_pbn(dlpbn.remainCards, dl.remainCards) != RETURN_NO_FAULT) - return RETURN_PBN_FAULT; - - for (int k = 0; k <= 2; k++) - { - dl.currentTrickRank[k] = dlpbn.currentTrickRank[k]; - dl.currentTrickSuit[k] = dlpbn.currentTrickSuit[k]; - } - dl.first = dlpbn.first; - dl.trump = dlpbn.trump; - - int res = SolveBoard(dl, target, solutions, mode, futp, thrId); - return res; + Deal dl; + if (convert_from_pbn(dlpbn.remainCards, dl.remainCards) != RETURN_NO_FAULT) + return RETURN_PBN_FAULT; + + for (int k = 0; k <= 2; k++) + { + dl.currentTrickRank[k] = dlpbn.currentTrickRank[k]; + dl.currentTrickSuit[k] = dlpbn.currentTrickSuit[k]; + } + dl.first = dlpbn.first; + dl.trump = dlpbn.trump; + + int res = SolveBoard(dl, target, solutions, mode, futp, thrId); + return res; } @@ -166,209 +166,209 @@ int STDCALL SolveBoardPBN( * @return 1 on success, error code otherwise */ int STDCALL SolveAllBoardsN( - BoardsPBN const * bop, - SolvedBoards * solvedp, - int maxThreads) + BoardsPBN const * bop, + SolvedBoards * solvedp, + int maxThreads) { - return solve_all_boards_pbn_n(* bop, * solvedp, maxThreads); + return solve_all_boards_pbn_n(* bop, * solvedp, maxThreads); } int STDCALL SolveAllBoards( - BoardsPBN const * bop, - SolvedBoards * solvedp) + BoardsPBN const * bop, + SolvedBoards * solvedp) { - return SolveAllBoardsN(bop, solvedp, 0); + return SolveAllBoardsN(bop, solvedp, 0); } int STDCALL SolveAllBoardsBinN( - Boards const * bop, - SolvedBoards * solvedp, - int maxThreads) + Boards const * bop, + SolvedBoards * solvedp, + int maxThreads) { - return solve_all_boards_n(* bop, * solvedp, maxThreads); + return solve_all_boards_n(* bop, * solvedp, maxThreads); } int STDCALL SolveAllBoardsBin( - Boards const * bop, - SolvedBoards * solvedp) + Boards const * bop, + SolvedBoards * solvedp) { - return SolveAllBoardsBinN(bop, solvedp, 0); + return SolveAllBoardsBinN(bop, solvedp, 0); } int STDCALL SolveAllBoardsSeq( - BoardsPBN const * bop, - SolvedBoards * solvedp) + BoardsPBN const * bop, + SolvedBoards * solvedp) { - Boards bo; - const int rc = boards_from_pbn(*bop, bo); - if (rc != RETURN_NO_FAULT) - return rc; - return solve_all_boards_n_seq(bo, * solvedp); + Boards bo; + const int rc = boards_from_pbn(*bop, bo); + if (rc != RETURN_NO_FAULT) + return rc; + return solve_all_boards_n_seq(bo, * solvedp); } int STDCALL SolveAllBoardsBinSeq( - Boards const * bop, - SolvedBoards * solvedp) + Boards const * bop, + SolvedBoards * solvedp) { - return solve_all_boards_n_seq(* bop, * solvedp); + return solve_all_boards_n_seq(* bop, * solvedp); } int STDCALL SolveAllChunksPBN( - BoardsPBN const * bop, - SolvedBoards * solvedp, - int chunkSize) + BoardsPBN const * bop, + SolvedBoards * solvedp, + int chunkSize) { - // Historical aliases. Don't use -- they may go away. - if (chunkSize < 1) - return RETURN_CHUNK_SIZE; + // Historical aliases. Don't use -- they may go away. + if (chunkSize < 1) + return RETURN_CHUNK_SIZE; - return SolveAllBoards(bop, solvedp); + return SolveAllBoards(bop, solvedp); } int STDCALL SolveAllChunks( - BoardsPBN const * bop, - SolvedBoards * solvedp, - int chunkSize) + BoardsPBN const * bop, + SolvedBoards * solvedp, + int chunkSize) { - // Historical aliases. Don't use -- they may go away. - if (chunkSize < 1) - return RETURN_CHUNK_SIZE; + // Historical aliases. Don't use -- they may go away. + if (chunkSize < 1) + return RETURN_CHUNK_SIZE; - return SolveAllBoards(bop, solvedp); + return SolveAllBoards(bop, solvedp); } int STDCALL SolveAllChunksBin( - Boards const * bop, - SolvedBoards * solvedp, - int chunkSize) + Boards const * bop, + SolvedBoards * solvedp, + int chunkSize) { - // Historical aliases. Don't use -- they may go away. - if (chunkSize < 1) - return RETURN_CHUNK_SIZE; + // Historical aliases. Don't use -- they may go away. + if (chunkSize < 1) + return RETURN_CHUNK_SIZE; - return solve_all_boards_n(* bop, * solvedp); + return solve_all_boards_n(* bop, * solvedp); } auto solve_all_boards_n_seq( - Boards const& bds, - SolvedBoards& solved) -> int + Boards const& bds, + SolvedBoards& solved) -> int { - const int n = bds.no_of_boards; - if (n > MAXNOOFBOARDS) - return RETURN_TOO_MANY_BOARDS; - - for (int k = 0; k < MAXNOOFBOARDS; k++) - solved.solved_board[k].cards = 0; - - scheduler.RegisterRun(RunMode::DDS_RUN_SOLVE, bds); - - int error = 0; - - START_BLOCK_TIMER; - for (int bno = 0; bno < n && error == 0; bno++) { - FutureTricks fut; - const auto t0 = std::chrono::steady_clock::now(); - const int res = solve_board( - dds::internal::worker_solver_context(), - bds.deals[bno], bds.target[bno], bds.solutions[bno], - bds.mode[bno], &fut); - auto dur = std::chrono::duration_cast( - std::chrono::steady_clock::now() - t0).count(); - scheduler.SetBoardTime(bno, dur); - - if (res == 1) - solved.solved_board[bno] = fut; - else - error = res; - } - END_BLOCK_TIMER; + const int n = bds.no_of_boards; + if (n > MAXNOOFBOARDS) + return RETURN_TOO_MANY_BOARDS; + + for (int k = 0; k < MAXNOOFBOARDS; k++) + solved.solved_board[k].cards = 0; + + scheduler.RegisterRun(RunMode::DDS_RUN_SOLVE, bds); + + int error = 0; + + START_BLOCK_TIMER; + for (int bno = 0; bno < n && error == 0; bno++) { + FutureTricks fut; + const auto t0 = std::chrono::steady_clock::now(); + const int res = solve_board( + dds::internal::worker_solver_context(), + bds.deals[bno], bds.target[bno], bds.solutions[bno], + bds.mode[bno], &fut); + auto dur = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + scheduler.SetBoardTime(bno, dur); + + if (res == 1) + solved.solved_board[bno] = fut; + else + error = res; + } + END_BLOCK_TIMER; - if (error != 0) - return error; + if (error != 0) + return error; - solved.no_of_boards = n; + solved.no_of_boards = n; #ifdef DDS_SCHEDULER - scheduler.PrintTiming(); + scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto detect_solve_duplicates( - const Boards& bds, - std::vector& uniques, - std::vector& crossrefs) -> void + const Boards& bds, + std::vector& uniques, + std::vector& crossrefs) -> void { - const unsigned nu = static_cast(bds.no_of_boards); + const unsigned nu = static_cast(bds.no_of_boards); - uniques.clear(); - crossrefs.resize(nu); + uniques.clear(); + crossrefs.resize(nu); - for (unsigned i = 0; i < nu; i++) - crossrefs[i] = -1; + for (unsigned i = 0; i < nu; i++) + crossrefs[i] = -1; - for (unsigned i = 0; i < nu; i++) - { - if (crossrefs[i] != -1) - continue; + for (unsigned i = 0; i < nu; i++) + { + if (crossrefs[i] != -1) + continue; - uniques.push_back(static_cast(i)); + uniques.push_back(static_cast(i)); - for (unsigned index = i+1; index < nu; index++) - { - if (same_board(bds, i, index)) - crossrefs[index] = static_cast(i); + for (unsigned index = i+1; index < nu; index++) + { + if (same_board(bds, i, index)) + crossrefs[index] = static_cast(i); + } } - } } auto same_board( - const Boards& bds, - const unsigned index1, - const unsigned index2) -> bool + const Boards& bds, + const unsigned index1, + const unsigned index2) -> bool { - for (int h = 0; h < DDS_HANDS; h++) - { - for (int s = 0; s < DDS_SUITS; s++) + for (int h = 0; h < DDS_HANDS; h++) { - if (bds.deals[index1].remainCards[h][s] != - bds.deals[index2].remainCards[h][s]) + for (int s = 0; s < DDS_SUITS; s++) + { + if (bds.deals[index1].remainCards[h][s] != + bds.deals[index2].remainCards[h][s]) + return false; + } + } + + if (bds.mode[index1] != bds.mode[index2]) + return false; + if (bds.solutions[index1] != bds.solutions[index2]) return false; + if (bds.target[index1] != bds.target[index2]) + return false; + if (bds.deals[index1].first != bds.deals[index2].first) + return false; + if (bds.deals[index1].trump != bds.deals[index2].trump) + return false; + + for (int k = 0; k < 3; k++) + { + if (bds.deals[index1].currentTrickSuit[k] != + bds.deals[index2].currentTrickSuit[k]) + return false; + if (bds.deals[index1].currentTrickRank[k] != + bds.deals[index2].currentTrickRank[k]) + return false; } - } - - if (bds.mode[index1] != bds.mode[index2]) - return false; - if (bds.solutions[index1] != bds.solutions[index2]) - return false; - if (bds.target[index1] != bds.target[index2]) - return false; - if (bds.deals[index1].first != bds.deals[index2].first) - return false; - if (bds.deals[index1].trump != bds.deals[index2].trump) - return false; - - for (int k = 0; k < 3; k++) - { - if (bds.deals[index1].currentTrickSuit[k] != - bds.deals[index2].currentTrickSuit[k]) - return false; - if (bds.deals[index1].currentTrickRank[k] != - bds.deals[index2].currentTrickRank[k]) - return false; - } - return true; + return true; } diff --git a/library/src/solve_board.hpp b/library/src/solve_board.hpp index cb8011d81..b2684710c 100644 --- a/library/src/solve_board.hpp +++ b/library/src/solve_board.hpp @@ -15,20 +15,20 @@ auto solve_all_boards_n( - Boards const& bds, - SolvedBoards& solved, - int max_threads = 0) -> int; + Boards const& bds, + SolvedBoards& solved, + int max_threads = 0) -> int; auto solve_all_boards_pbn_n( - BoardsPBN const& bop, - SolvedBoards& solved, - int max_threads = 0) -> int; + BoardsPBN const& bop, + SolvedBoards& solved, + int max_threads = 0) -> int; auto solve_all_boards_n_seq( - Boards const& bds, - SolvedBoards& solved) -> int; + Boards const& bds, + SolvedBoards& solved) -> int; auto detect_solve_duplicates( - const Boards& bds, - std::vector& uniques, - std::vector& crossrefs) -> void; + const Boards& bds, + std::vector& uniques, + std::vector& crossrefs) -> void; diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index 893e0fc5f..03ddb1dce 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -23,40 +23,40 @@ namespace { /// Optional DDS_TT_KIND=small|large|pattern override of the configured kind. auto tt_kind_from_environment(TTKind configured) -> TTKind { - const char* s = std::getenv("DDS_TT_KIND"); - if (s == nullptr) return configured; - const std::string value(s); - if (value == "small") return TTKind::Small; - if (value == "large") return TTKind::Large; - if (value == "pattern") return TTKind::Pattern; - return configured; + const char* s = std::getenv("DDS_TT_KIND"); + if (s == nullptr) return configured; + const std::string value(s); + if (value == "small") return TTKind::Small; + if (value == "large") return TTKind::Large; + if (value == "pattern") return TTKind::Pattern; + return configured; } auto tt_kind_of(const TransTable* tt) -> TTKind { - if (dynamic_cast(tt) != nullptr) return TTKind::Small; - if (dynamic_cast(tt) != nullptr) return TTKind::Pattern; - return TTKind::Large; + if (dynamic_cast(tt) != nullptr) return TTKind::Small; + if (dynamic_cast(tt) != nullptr) return TTKind::Pattern; + return TTKind::Large; } auto tt_kind_letter(TTKind kind) -> char { - switch (kind) { - case TTKind::Small: return 'S'; - case TTKind::Pattern: return 'P'; - case TTKind::Large: break; - } - return 'L'; + switch (kind) { + case TTKind::Small: return 'S'; + case TTKind::Pattern: return 'P'; + case TTKind::Large: break; + } + return 'L'; } auto make_trans_table(TTKind kind) -> std::unique_ptr { - switch (kind) { - case TTKind::Small: return std::make_unique(); - case TTKind::Pattern: return std::make_unique(); - case TTKind::Large: break; - } - return std::make_unique(); + switch (kind) { + case TTKind::Small: return std::make_unique(); + case TTKind::Pattern: return std::make_unique(); + case TTKind::Large: break; + } + return std::make_unique(); } /// Replaces non-positive limits with the built-in THREADMEM_* values, one at @@ -65,18 +65,18 @@ auto make_trans_table(TTKind kind) -> std::unique_ptr /// maximum-only configuration keeps its cap. auto fill_unset_limits(const TTKind kind, int& defMB, int& maxMB) -> void { - const int builtin_def = kind == TTKind::Small ? THREADMEM_SMALL_DEF_MB : THREADMEM_LARGE_DEF_MB; - const int builtin_max = kind == TTKind::Small ? THREADMEM_SMALL_MAX_MB : THREADMEM_LARGE_MAX_MB; - if (maxMB <= 0) maxMB = builtin_max; - if (defMB <= 0) defMB = std::min(builtin_def, maxMB); + const int builtin_def = kind == TTKind::Small ? THREADMEM_SMALL_DEF_MB : THREADMEM_LARGE_DEF_MB; + const int builtin_max = kind == TTKind::Small ? THREADMEM_SMALL_MAX_MB : THREADMEM_LARGE_MAX_MB; + if (maxMB <= 0) maxMB = builtin_max; + if (defMB <= 0) defMB = std::min(builtin_def, maxMB); } #if defined(DDS_TOP_LEVEL) || defined(DDS_AB_STATS) || defined(DDS_AB_HITS) || \ - defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) + defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) std::string next_debug_file_suffix() { - static std::atomic serial{0}; - return std::to_string(serial.fetch_add(1, std::memory_order_relaxed)) + + static std::atomic serial{0}; + return std::to_string(serial.fetch_add(1, std::memory_order_relaxed)) + DDS_DEBUG_SUFFIX; } #endif @@ -85,111 +85,111 @@ std::string next_debug_file_suffix() void SolverContext::bind_thread_data() { - // Ensure persistent facades like SearchContext see the bound ThreadData. - search_.set_thread(thr_); - search_.set_owner(this); - if (!thr_) return; + // Ensure persistent facades like SearchContext see the bound ThreadData. + search_.set_thread(thr_); + search_.set_owner(this); + if (!thr_) return; - if (owns_thread_data_) { + if (owns_thread_data_) { #if defined(DDS_TOP_LEVEL) || defined(DDS_AB_STATS) || defined(DDS_AB_HITS) || \ - defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) - thr_->init_debug_files(next_debug_file_suffix()); + defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) + thr_->init_debug_files(next_debug_file_suffix()); #endif - } + } } // Owned-ThreadData constructor: allocate ThreadData as a member of the // SolverContext so callers can create a context at the top of the stack // and pass it down without a separate per-thread lookup. SolverContext::SolverContext(SolverConfig cfg) - : cfg_(cfg), owns_thread_data_(true) + : cfg_(cfg), owns_thread_data_(true) { - // Create an owned ThreadData instance and keep it in thr_. - thr_ = std::make_shared(); - bind_thread_data(); + // Create an owned ThreadData instance and keep it in thr_. + thr_ = std::make_shared(); + bind_thread_data(); } auto SolverContext::trans_table() const -> TransTable* { - // Delegate to per-context SearchContext member (lazy creation inside). - return const_cast(this)->search_.trans_table(); + // Delegate to per-context SearchContext member (lazy creation inside). + return const_cast(this)->search_.trans_table(); } // Trivial accessors and disposal helpers are now inline in the header. // The lazy TT creator (large body) remains out-of-line. auto SolverContext::SearchContext::trans_table() -> TransTable* { - if (tt_) return tt_.get(); - // Require owner (for config and utilities). If missing, fall back - // to the SolverConfig default with built-in memory limits. - TTKind kind = tt_kind_from_environment(owner_ ? owner_->config().tt_kind_ : SolverConfig{}.tt_kind_); - int defMB = (owner_ ? owner_->config().tt_mem_default_mb_ : 0); - int maxMB = (owner_ ? owner_->config().tt_mem_maximum_mb_ : 0); - fill_unset_limits(kind, defMB, maxMB); - // Optional environment overrides - if (const char* s = std::getenv("DDS_TT_DEFAULT_MB")) { - int v = std::atoi(s); - if (v > 0) defMB = v; - } - if (const char* s = std::getenv("DDS_TT_LIMIT_MB")) { - int v = std::atoi(s); - if (v > 0) maxMB = std::min(maxMB, v); - } - if (maxMB < defMB) maxMB = defMB; - - tt_ = make_trans_table(kind); - - tt_->set_memory_default(defMB); - tt_->set_memory_maximum(maxMB); - tt_->make_tt(); + if (tt_) return tt_.get(); + // Require owner (for config and utilities). If missing, fall back + // to the SolverConfig default with built-in memory limits. + TTKind kind = tt_kind_from_environment(owner_ ? owner_->config().tt_kind_ : SolverConfig{}.tt_kind_); + int defMB = (owner_ ? owner_->config().tt_mem_default_mb_ : 0); + int maxMB = (owner_ ? owner_->config().tt_mem_maximum_mb_ : 0); + fill_unset_limits(kind, defMB, maxMB); + // Optional environment overrides + if (const char* s = std::getenv("DDS_TT_DEFAULT_MB")) { + int v = std::atoi(s); + if (v > 0) defMB = v; + } + if (const char* s = std::getenv("DDS_TT_LIMIT_MB")) { + int v = std::atoi(s); + if (v > 0) maxMB = std::min(maxMB, v); + } + if (maxMB < defMB) maxMB = defMB; + + tt_ = make_trans_table(kind); + + tt_->set_memory_default(defMB); + tt_->set_memory_maximum(maxMB); + tt_->make_tt(); #ifdef DDS_UTILITIES_LOG - { - const char kch = tt_kind_letter(kind); - char buf[96]; - std::snprintf(buf, sizeof(buf), "tt:create|%c|%d|%d", kch, defMB, maxMB); - if (owner_) owner_->utilities().log_append(std::string(buf)); - } + { + const char kch = tt_kind_letter(kind); + char buf[96]; + std::snprintf(buf, sizeof(buf), "tt:create|%c|%d|%d", kch, defMB, maxMB); + if (owner_) owner_->utilities().log_append(std::string(buf)); + } #endif #ifdef DDS_UTILITIES_STATS - if (owner_) owner_->utilities().util().stats().tt_creates++; + if (owner_) owner_->utilities().util().stats().tt_creates++; #endif - // Optional one-time debug print per creation - if (const char* dbg = std::getenv("DDS_DEBUG_TT_CREATE")) { - if (*dbg) { - std::cerr << "[DDS] TT create: kind=" - << tt_kind_letter(kind) - << " defMB=" << defMB - << " maxMB=" << maxMB - << std::endl; + // Optional one-time debug print per creation + if (const char* dbg = std::getenv("DDS_DEBUG_TT_CREATE")) { + if (*dbg) { + std::cerr << "[DDS] TT create: kind=" + << tt_kind_letter(kind) + << " defMB=" << defMB + << " maxMB=" << maxMB + << std::endl; + } } - } - return tt_.get(); + return tt_.get(); } auto SolverContext::maybe_trans_table() const -> TransTable* { - return search_.maybe_trans_table(); + return search_.maybe_trans_table(); } auto SolverContext::dispose_trans_table() const -> void { #ifdef DDS_UTILITIES_LOG - // Append a tiny debug entry indicating TT disposal. - utilities().log_append("tt:dispose"); + // Append a tiny debug entry indicating TT disposal. + utilities().log_append("tt:dispose"); #endif #ifdef DDS_UTILITIES_STATS - utilities().util().stats().tt_disposes++; + utilities().util().stats().tt_disposes++; #endif - // Dispose the member-owned TT (if any) - const_cast(this)->search_.dispose_trans_table(); - // A replacement table has not seen the current deal. Forget the deal the - // thread remembers so the next solve treats it as new and runs - // SetDealTables(), which init()s the table, even for the same cards. - if (thr_) std::memset(thr_->suit, 0, sizeof(thr_->suit)); + // Dispose the member-owned TT (if any) + const_cast(this)->search_.dispose_trans_table(); + // A replacement table has not seen the current deal. Forget the deal the + // thread remembers so the next solve treats it as new and runs + // SetDealTables(), which init()s the table, even for the same cards. + if (thr_) std::memset(thr_->suit, 0, sizeof(thr_->suit)); } // Defaulted destructor defined out-of-line so destruction of the @@ -197,138 +197,138 @@ auto SolverContext::dispose_trans_table() const -> void // complete type. SolverContext::~SolverContext() { - if (!thr_) - return; + if (!thr_) + return; - // SearchContext holds its own shared_ptr; release it before testing whether - // this context is the last owner of ThreadData. - search_.set_thread({}); - if (thr_.use_count() == 1) - thr_->close_debug_files(); + // SearchContext holds its own shared_ptr; release it before testing whether + // this context is the last owner of ThreadData. + search_.set_thread({}); + if (thr_.use_count() == 1) + thr_->close_debug_files(); } auto SolverContext::reset_for_solve() const -> void { #ifdef DDS_UTILITIES_LOG - { - char buf[32]; - std::snprintf(buf, sizeof(buf), "ctx:reset_for_solve"); - utilities().log_append(std::string(buf)); - } + { + char buf[32]; + std::snprintf(buf, sizeof(buf), "ctx:reset_for_solve"); + utilities().log_append(std::string(buf)); + } #endif - if (auto* tt = search_.maybe_trans_table()) - tt->reset_memory(ResetReason::FreeMemory); - if (!thr_) return; - // Reset a subset of search state to a clean slate. - thr_->nodes = 0; - thr_->trickNodes = 0; - thr_->analysisFlag = false; - for (int d = 0; d < 50; ++d) { - thr_->bestMove[d].suit = 0; - thr_->bestMove[d].rank = 0; - thr_->bestMoveTT[d].suit = 0; - thr_->bestMoveTT[d].rank = 0; - for (int s = 0; s < DDS_SUITS; ++s) { - thr_->lowestWin[d][s] = 0; + if (auto* tt = search_.maybe_trans_table()) + tt->reset_memory(ResetReason::FreeMemory); + if (!thr_) return; + // Reset a subset of search state to a clean slate. + thr_->nodes = 0; + thr_->trickNodes = 0; + thr_->analysisFlag = false; + for (int d = 0; d < 50; ++d) { + thr_->bestMove[d].suit = 0; + thr_->bestMove[d].rank = 0; + thr_->bestMoveTT[d].suit = 0; + thr_->bestMoveTT[d].rank = 0; + for (int s = 0; s < DDS_SUITS; ++s) { + thr_->lowestWin[d][s] = 0; + } + } + for (int t = 0; t < 13; ++t) { + thr_->winners[t].number = 0; + } + for (int k = 0; k <= 13; ++k) { + thr_->forbiddenMoves[k].rank = 0; + thr_->forbiddenMoves[k].suit = 0; } - } - for (int t = 0; t < 13; ++t) { - thr_->winners[t].number = 0; - } - for (int k = 0; k <= 13; ++k) { - thr_->forbiddenMoves[k].rank = 0; - thr_->forbiddenMoves[k].suit = 0; - } } auto SolverContext::clear_tt() const -> void { #ifdef DDS_UTILITIES_LOG - utilities().log_append("tt:clear"); + utilities().log_append("tt:clear"); #endif - // Dispose the instance rather than calling return_all_memory() on it. Both - // free the pools — the TT destructor returns all memory — but returning the - // memory while keeping the object leaves a husk whose pool pointers dangle, - // and SearchContext::trans_table() hands that husk straight back because it - // only checks whether tt_ is non-null. The next lookup then reads freed - // memory (ASan: heap-use-after-free in TransTable{L,S}::lookup). - // - // Disposing instead makes the documented "recreates lazily on demand" - // behavior real: tt_ becomes null, so the next trans_table() rebuilds from - // the owner's config. Nothing is lost, because the kind and memory limits - // live in SolverContext::cfg_, not in the TT instance. - dispose_trans_table(); + // Dispose the instance rather than calling return_all_memory() on it. Both + // free the pools — the TT destructor returns all memory — but returning the + // memory while keeping the object leaves a husk whose pool pointers dangle, + // and SearchContext::trans_table() hands that husk straight back because it + // only checks whether tt_ is non-null. The next lookup then reads freed + // memory (ASan: heap-use-after-free in TransTable{L,S}::lookup). + // + // Disposing instead makes the documented "recreates lazily on demand" + // behavior real: tt_ becomes null, so the next trans_table() rebuilds from + // the owner's config. Nothing is lost, because the kind and memory limits + // live in SolverContext::cfg_, not in the TT instance. + dispose_trans_table(); } auto SolverContext::resize_tt(int defMB, int maxMB) const -> void { #ifdef DDS_UTILITIES_LOG - { - char buf[64]; - std::snprintf(buf, sizeof(buf), "tt:resize|%d|%d", defMB, maxMB); - utilities().log_append(std::string(buf)); - } + { + char buf[64]; + std::snprintf(buf, sizeof(buf), "tt:resize|%d|%d", defMB, maxMB); + utilities().log_append(std::string(buf)); + } #endif - if (auto* tt = search_.maybe_trans_table()) - { - if (maxMB < defMB) maxMB = defMB; - tt->set_memory_default(defMB); - tt->set_memory_maximum(maxMB); - } + if (auto* tt = search_.maybe_trans_table()) + { + if (maxMB < defMB) maxMB = defMB; + tt->set_memory_default(defMB); + tt->set_memory_maximum(maxMB); + } } auto SolverContext::configure_tt(TTKind kind, int defMB, int maxMB) -> void { - // Unset limits resolve exactly as they would on lazy creation, so that an - // in-place resize never hands a live table a zero maximum. - fill_unset_limits(tt_kind_from_environment(kind), defMB, maxMB); - // Apply environment limit if present to preserve existing behavior. - if (const char* s = std::getenv("DDS_TT_LIMIT_MB")) { - int v = std::atoi(s); - if (v > 0) maxMB = std::min(maxMB, v); - } - if (maxMB < defMB) maxMB = defMB; - - // Persist configuration for future TT creations. - cfg_.tt_kind_ = kind; - cfg_.tt_mem_default_mb_ = defMB; - cfg_.tt_mem_maximum_mb_ = maxMB; - - auto* tt = search_.maybe_trans_table(); - if (!tt) return; // Nothing to apply now; will take effect on lazy creation. - - // If the effective kind (environment override included, as at creation) - // changes, dispose and recreate now to ensure effect is applied. - if (tt_kind_of(tt) != tt_kind_from_environment(kind)) { - dispose_trans_table(); - // Force immediate creation with new config to keep behavior explicit. - (void)trans_table(); - return; - } + // Unset limits resolve exactly as they would on lazy creation, so that an + // in-place resize never hands a live table a zero maximum. + fill_unset_limits(tt_kind_from_environment(kind), defMB, maxMB); + // Apply environment limit if present to preserve existing behavior. + if (const char* s = std::getenv("DDS_TT_LIMIT_MB")) { + int v = std::atoi(s); + if (v > 0) maxMB = std::min(maxMB, v); + } + if (maxMB < defMB) maxMB = defMB; + + // Persist configuration for future TT creations. + cfg_.tt_kind_ = kind; + cfg_.tt_mem_default_mb_ = defMB; + cfg_.tt_mem_maximum_mb_ = maxMB; + + auto* tt = search_.maybe_trans_table(); + if (!tt) return; // Nothing to apply now; will take effect on lazy creation. + + // If the effective kind (environment override included, as at creation) + // changes, dispose and recreate now to ensure effect is applied. + if (tt_kind_of(tt) != tt_kind_from_environment(kind)) { + dispose_trans_table(); + // Force immediate creation with new config to keep behavior explicit. + (void)trans_table(); + return; + } - // Same kind: resize in-place. - resize_tt(defMB, maxMB); + // Same kind: resize in-place. + resize_tt(defMB, maxMB); } // Lightweight reset matching legacy ResetBestMoves semantics. auto SolverContext::reset_best_moves_lite() const -> void { #ifdef DDS_UTILITIES_LOG - utilities().log_append("ctx:reset_best_moves_lite"); + utilities().log_append("ctx:reset_best_moves_lite"); #endif - if (!thr_) return; - for (int d = 0; d <= 49; ++d) - { - thr_->bestMove[d].rank = 0; - thr_->bestMoveTT[d].rank = 0; - } - // Keep memUsed in sync as the legacy code did - if (auto* tt = search_.maybe_trans_table()) - thr_->memUsed = tt->memory_in_use() + ThreadMemoryUsed(); - else - thr_->memUsed = ThreadMemoryUsed(); + if (!thr_) return; + for (int d = 0; d <= 49; ++d) + { + thr_->bestMove[d].rank = 0; + thr_->bestMoveTT[d].rank = 0; + } + // Keep memUsed in sync as the legacy code did + if (auto* tt = search_.maybe_trans_table()) + thr_->memUsed = tt->memory_in_use() + ThreadMemoryUsed(); + else + thr_->memUsed = ThreadMemoryUsed(); #ifdef DDS_AB_STATS - thr_->ABStats.Reset(); + thr_->ABStats.Reset(); #endif } @@ -342,156 +342,156 @@ std::atomic g_worker_contexts_created{0}; struct CountedWorkerContext { - CountedWorkerContext() - { - g_worker_contexts_created.fetch_add(1, std::memory_order_relaxed); - } + CountedWorkerContext() + { + g_worker_contexts_created.fetch_add(1, std::memory_order_relaxed); + } - SolverContext ctx; + SolverContext ctx; }; } // namespace auto worker_solver_context() -> SolverContext& { - thread_local CountedWorkerContext holder; - return holder.ctx; + thread_local CountedWorkerContext holder; + return holder.ctx; } auto worker_solver_contexts_created() -> std::uint64_t { - return g_worker_contexts_created.load(std::memory_order_relaxed); + return g_worker_contexts_created.load(std::memory_order_relaxed); } } // namespace dds::internal auto ThreadMemoryUsed() -> double { - // Fixed per-thread lookup-table memory (RelRanksType) included in memUsed - // reporting; legacy SolverIF uses the same accounting. - double memUsed = - 8192 * sizeof(RelRanksType) - / static_cast(1024.); + // Fixed per-thread lookup-table memory (RelRanksType) included in memUsed + // reporting; legacy SolverIF uses the same accounting. + double memUsed = + 8192 * sizeof(RelRanksType) + / static_cast(1024.); - return memUsed; + return memUsed; } // --- MoveGenContext out-of-line definitions --- // No TLS allocator shim required: move generation now runs without a global allocator hook. auto SolverContext::MoveGenContext::move_gen_0( - const int tricks, - const Pos& tpos, - const MoveType& bestMove, - const MoveType& bestMoveTT, - const RelRanksType thrp_rel[]) -> int + const int tricks, + const Pos& tpos, + const MoveType& bestMove, + const MoveType& bestMoveTT, + const RelRanksType thrp_rel[]) -> int { - auto rc = thr_->moves.MoveGen0(tricks, tpos, bestMove, bestMoveTT, thrp_rel); - return rc; + auto rc = thr_->moves.MoveGen0(tricks, tpos, bestMove, bestMoveTT, thrp_rel); + return rc; } auto SolverContext::MoveGenContext::move_gen_123( - const int tricks, - const int relHand, - const Pos& tpos) -> int + const int tricks, + const int relHand, + const Pos& tpos) -> int { - auto rc = thr_->moves.MoveGen123(tricks, relHand, tpos); - return rc; + auto rc = thr_->moves.MoveGen123(tricks, relHand, tpos); + return rc; } auto SolverContext::MoveGenContext::purge( - const int tricks, - const int relHand, - const MoveType forbiddenMoves[]) -> void + const int tricks, + const int relHand, + const MoveType forbiddenMoves[]) -> void { - thr_->moves.Purge(tricks, relHand, forbiddenMoves); + thr_->moves.Purge(tricks, relHand, forbiddenMoves); } auto SolverContext::MoveGenContext::make_next( - const int trick, - const int relHand, - const unsigned short win_ranks[]) -> const MoveType* + const int trick, + const int relHand, + const unsigned short win_ranks[]) -> const MoveType* { - return thr_->moves.MakeNext(trick, relHand, win_ranks); + return thr_->moves.MakeNext(trick, relHand, win_ranks); } auto SolverContext::MoveGenContext::make_next_simple( - const int trick, - const int relHand) -> const MoveType* + const int trick, + const int relHand) -> const MoveType* { - return thr_->moves.MakeNextSimple(trick, relHand); + return thr_->moves.MakeNextSimple(trick, relHand); } auto SolverContext::MoveGenContext::get_length( - const int trick, - const int relHand) const -> int + const int trick, + const int relHand) const -> int { - return thr_->moves.GetLength(trick, relHand); + return thr_->moves.GetLength(trick, relHand); } auto SolverContext::MoveGenContext::rewind( - const int tricks, - const int relHand) -> void + const int tricks, + const int relHand) -> void { - thr_->moves.Rewind(tricks, relHand); + thr_->moves.Rewind(tricks, relHand); } auto SolverContext::MoveGenContext::register_hit( - const int tricks, - const int relHand) -> void + const int tricks, + const int relHand) -> void { - thr_->moves.RegisterHit(tricks, relHand); + thr_->moves.RegisterHit(tricks, relHand); } auto SolverContext::MoveGenContext::get_trick_data(const int tricks) -> const TrickDataType& { - return thr_->moves.GetTrickData(tricks); + return thr_->moves.GetTrickData(tricks); } auto SolverContext::MoveGenContext::make_specific( - const MoveType& mply, - const int trick, - const int relHand) -> void + const MoveType& mply, + const int trick, + const int relHand) -> void { - thr_->moves.MakeSpecific(mply, trick, relHand); + thr_->moves.MakeSpecific(mply, trick, relHand); } auto SolverContext::MoveGenContext::trick_to_text(const int trick) const -> std::string { - return thr_->moves.TrickToText(trick); + return thr_->moves.TrickToText(trick); } auto SolverContext::MoveGenContext::reinit( - const int tricks, - const int leadHand) -> void + const int tricks, + const int leadHand) -> void { - thr_->moves.Reinit(tricks, leadHand); + thr_->moves.Reinit(tricks, leadHand); } auto SolverContext::MoveGenContext::init( - const int tricks, - const int relStartHand, - const int initialRanks[], - const int initialSuits[], - const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], - const int trump, - const int leadHand) -> void -{ - thr_->moves.Init(tricks, relStartHand, initialRanks, initialSuits, + const int tricks, + const int relStartHand, + const int initialRanks[], + const int initialSuits[], + const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], + const int trump, + const int leadHand) -> void +{ + thr_->moves.Init(tricks, relStartHand, initialRanks, initialSuits, rank_in_suit, trump, leadHand); } auto SolverContext::MoveGenContext::print_trick_stats(std::ofstream& fout) const -> void { - thr_->moves.PrintTrickStats(fout); + thr_->moves.PrintTrickStats(fout); } auto SolverContext::MoveGenContext::print_function_stats(std::ofstream& fout) const -> void { - thr_->moves.PrintFunctionStats(fout); + thr_->moves.PrintFunctionStats(fout); } auto SolverContext::MoveGenContext::print_trick_details(std::ofstream& fout) const -> void { - thr_->moves.PrintTrickDetails(fout); + thr_->moves.PrintTrickDetails(fout); } diff --git a/library/src/solver_context/solver_context.hpp b/library/src/solver_context/solver_context.hpp index cbe3942ae..02934642d 100644 --- a/library/src/solver_context/solver_context.hpp +++ b/library/src/solver_context/solver_context.hpp @@ -36,9 +36,9 @@ enum class TTKind { Small = 0, Large = 1, Pattern = 2 }; */ struct SolverConfig { - TTKind tt_kind_ = TTKind::Pattern; - int tt_mem_default_mb_ = 0; - int tt_mem_maximum_mb_ = 0; + TTKind tt_kind_ = TTKind::Pattern; + int tt_mem_default_mb_ = 0; + int tt_mem_maximum_mb_ = 0; }; /** @@ -53,174 +53,174 @@ struct SolverConfig class SolverContext { public: - // Wrap existing ThreadData (helper/sub-context). Does not initialize debug - // files; ~SolverContext() closes them only when this context is the last - // shared_ptr holder of that ThreadData. - explicit SolverContext(std::shared_ptr thread, SolverConfig cfg = {}) - : thr_(std::move(thread)), cfg_(cfg) - { - bind_thread_data(); - } - - // NOTE: constructors that accepted raw ThreadData* were removed as part - // of the ownership migration. Callers should pass a - // std::shared_ptr (non-owning wrappers can be created with - // std::shared_ptr(ptr, [](ThreadData*){})). - - // Construct a context that owns its ThreadData instance. This is the - // preferred mode for the new instance-scoped API: callers can create a - // SolverContext at the top of the call-stack and pass it downwards. - explicit SolverContext(SolverConfig cfg = {}); - - ~SolverContext(); - - /** + // Wrap existing ThreadData (helper/sub-context). Does not initialize debug + // files; ~SolverContext() closes them only when this context is the last + // shared_ptr holder of that ThreadData. + explicit SolverContext(std::shared_ptr thread, SolverConfig cfg = {}) + : thr_(std::move(thread)), cfg_(cfg) + { + bind_thread_data(); + } + + // NOTE: constructors that accepted raw ThreadData* were removed as part + // of the ownership migration. Callers should pass a + // std::shared_ptr (non-owning wrappers can be created with + // std::shared_ptr(ptr, [](ThreadData*){})). + + // Construct a context that owns its ThreadData instance. This is the + // preferred mode for the new instance-scoped API: callers can create a + // SolverContext at the top of the call-stack and pass it downwards. + explicit SolverContext(SolverConfig cfg = {}); + + ~SolverContext(); + + /** * @brief Access the underlying ThreadData shared pointer. * * @return Shared ownership of the ThreadData used by this context. */ - auto thread() const -> std::shared_ptr - { - return thr_; - } + auto thread() const -> std::shared_ptr + { + return thr_; + } - /** + /** * @brief Non-owning raw access to the underlying ThreadData. * * Avoids the atomic reference-count traffic of copying the shared_ptr in * hot search paths. The pointer is valid for the lifetime of the context. */ - auto thread_ptr() const -> ThreadData* - { - return thr_.get(); - } + auto thread_ptr() const -> ThreadData* + { + return thr_.get(); + } - /** + /** * @brief Access the current configuration snapshot. * * @return Const reference to the configuration stored in this context. */ - auto config() const -> const SolverConfig& - { - return cfg_; - } - - // --- Utilities facade --- - class UtilitiesContext - { - public: - explicit UtilitiesContext(::dds::Utilities* util) - : util_(util) - { - } - - auto util() -> ::dds::Utilities& - { - return *util_; - } - - auto util() const -> const ::dds::Utilities& - { - return *util_; - } - - auto log_append(const std::string& s) -> void - { - util_->log_append(s); - } - - auto log_buffer() const -> const std::vector& + auto config() const -> const SolverConfig& { - return util_->log_buffer(); + return cfg_; } - auto log_clear() -> void + // --- Utilities facade --- + class UtilitiesContext { - util_->log_clear(); - } - - private: - ::dds::Utilities* util_ = nullptr; - }; - - /** + public: + explicit UtilitiesContext(::dds::Utilities* util) + : util_(util) + { + } + + auto util() -> ::dds::Utilities& + { + return *util_; + } + + auto util() const -> const ::dds::Utilities& + { + return *util_; + } + + auto log_append(const std::string& s) -> void + { + util_->log_append(s); + } + + auto log_buffer() const -> const std::vector& + { + return util_->log_buffer(); + } + + auto log_clear() -> void + { + util_->log_clear(); + } + + private: + ::dds::Utilities* util_ = nullptr; + }; + + /** * @brief Access utilities facade for logging and stats. */ - /** + /** * @brief Access utilities facade for mutable contexts. */ - auto utilities() -> UtilitiesContext - { - return UtilitiesContext(&utils_); - } + auto utilities() -> UtilitiesContext + { + return UtilitiesContext(&utils_); + } - /** + /** * @brief Access utilities facade for const contexts. * @note Returns a const-only wrapper to preserve const-correctness. */ - auto utilities() const -> UtilitiesContext - { - return UtilitiesContext(const_cast(&utils_)); - } - - // Developer note — TT lifecycle (instance-scoped) - // - // - Ownership: Each SolverContext::SearchContext owns its TransTable (TT) - // via a std::unique_ptr created lazily on first access. There is no - // global TT registry and no ThreadData-owned TT. - // - Configuration: The effective TT kind and memory sizes are determined by - // the SolverContext's SolverConfig (tt_kind_, tt_mem_default_mb_, tt_mem_maximum_mb_), - // with optional environment overrides: - // DDS_TT_DEFAULT_MB — overrides default MB if > 0 - // DDS_TT_LIMIT_MB — caps maximum MB if > 0 - // Call configure_tt(...) at runtime to persist a new configuration and apply - // it to an existing TT (resize in place) or recreate if the kind changes. - // - Reset semantics: - // reset_for_solve() — clears a subset of search state and calls - // tt->reset_memory(FreeMemory) when a TT exists; - // preserves the TT allocation for reuse. - // reset_best_moves_lite() — clears only best-move ranks and updates memUsed. - // clear_tt() — disposes the TT instance; preserves future - // config and recreates lazily on demand. - // dispose_trans_table() — destroys the owned TT immediately. - // - Diagnostics: When built with DDS_UTILITIES_LOG / DDS_UTILITIES_STATS, TT - // lifecycle events append compact log entries and bump small counters. - - // Returns the owned transposition table instance (creates if null) - /** + auto utilities() const -> UtilitiesContext + { + return UtilitiesContext(const_cast(&utils_)); + } + + // Developer note — TT lifecycle (instance-scoped) + // + // - Ownership: Each SolverContext::SearchContext owns its TransTable (TT) + // via a std::unique_ptr created lazily on first access. There is no + // global TT registry and no ThreadData-owned TT. + // - Configuration: The effective TT kind and memory sizes are determined by + // the SolverContext's SolverConfig (tt_kind_, tt_mem_default_mb_, tt_mem_maximum_mb_), + // with optional environment overrides: + // DDS_TT_DEFAULT_MB — overrides default MB if > 0 + // DDS_TT_LIMIT_MB — caps maximum MB if > 0 + // Call configure_tt(...) at runtime to persist a new configuration and apply + // it to an existing TT (resize in place) or recreate if the kind changes. + // - Reset semantics: + // reset_for_solve() — clears a subset of search state and calls + // tt->reset_memory(FreeMemory) when a TT exists; + // preserves the TT allocation for reuse. + // reset_best_moves_lite() — clears only best-move ranks and updates memUsed. + // clear_tt() — disposes the TT instance; preserves future + // config and recreates lazily on demand. + // dispose_trans_table() — destroys the owned TT immediately. + // - Diagnostics: When built with DDS_UTILITIES_LOG / DDS_UTILITIES_STATS, TT + // lifecycle events append compact log entries and bump small counters. + + // Returns the owned transposition table instance (creates if null) + /** * @brief Get or create the transposition table. * * @return Pointer to the owned TT instance. */ - auto trans_table() const -> TransTable*; - // Returns the TT instance if it exists, or nullptr - /** + auto trans_table() const -> TransTable*; + // Returns the TT instance if it exists, or nullptr + /** * @brief Get the transposition table if already created. * * @return Pointer to the TT instance or nullptr. */ - auto maybe_trans_table() const -> TransTable*; + auto maybe_trans_table() const -> TransTable*; - // Dispose and erase the TT instance associated with this thread, if any. - /** + // Dispose and erase the TT instance associated with this thread, if any. + /** * @brief Dispose the owned transposition table immediately. */ - auto dispose_trans_table() const -> void; + auto dispose_trans_table() const -> void; - // Lightweight facades used by tests and call sites; no-ops if no TT exists. - /** + // Lightweight facades used by tests and call sites; no-ops if no TT exists. + /** * @brief Reset search state for a new solve. * * Calls TT reset with ResetReason::FreeMemory when applicable. */ - auto reset_for_solve() const -> void; // Calls reset_memory(ResetReason::FreeMemory) - // Lightweight per-iteration reset matching legacy ResetBestMoves semantics. - // Only clears bestMove[*].rank and bestMoveTT[*].rank, updates memUsed and ABStats. - /** + auto reset_for_solve() const -> void; // Calls reset_memory(ResetReason::FreeMemory) + // Lightweight per-iteration reset matching legacy ResetBestMoves semantics. + // Only clears bestMove[*].rank and bestMoveTT[*].rank, updates memUsed and ABStats. + /** * @brief Lightweight reset used inside search iterations. */ - auto reset_best_moves_lite() const -> void; - /** + auto reset_best_moves_lite() const -> void; + /** * @brief Return all TT memory to the system. * * Disposes the TT instance; the configured kind and memory limits persist on @@ -228,237 +228,237 @@ class SolverContext * memory-less instance alive instead would leave dangling pool pointers for * the next lookup to read. */ - auto clear_tt() const -> void; - /** + auto clear_tt() const -> void; + /** * @brief Resize TT memory defaults and limits in-place if TT exists. */ - auto resize_tt(int defMB, int maxMB) const -> void; // Updates sizes if TT exists - // Explicit runtime configuration of TT kind and memory limits. Applies to - // existing TT (resize or recreate) and persists for future creations. - /** + auto resize_tt(int defMB, int maxMB) const -> void; // Updates sizes if TT exists + // Explicit runtime configuration of TT kind and memory limits. Applies to + // existing TT (resize or recreate) and persists for future creations. + /** * @brief Configure TT kind and memory limits. */ - auto configure_tt(TTKind kind, int defMB, int maxMB) -> void; + auto configure_tt(TTKind kind, int defMB, int maxMB) -> void; - // --- Search state facade --- - /** + // --- Search state facade --- + /** * @brief Facade for per-solve search state. */ - class SearchContext - { - public: - SearchContext() = default; - - explicit SearchContext(std::shared_ptr thr) - : thr_(std::move(thr)) - { - } - - // Returns the owned transposition table instance (creates if null) - auto trans_table() -> TransTable*; - // Returns the TT instance if it exists, or nullptr - auto maybe_trans_table() const -> TransTable* { return tt_.get(); } - // Dispose and erase the TT instance owned by this context, if any. - auto dispose_trans_table() -> void { tt_.reset(); } - // Trivial accessors defined in the header so call sites in hot inner - // loops (notably ab_search.cpp) get inlined direct field accesses - // instead of cross-TU function calls. The previous out-of-line - // definitions in solver_context.cpp added ~20% to total ab_search - // self-time on Linux/x86_64. - auto analysis_flag() -> bool& { return thr_->analysisFlag; } - auto analysis_flag() const -> bool { return thr_->analysisFlag; } - auto lowest_win(int depth, int suit) -> unsigned short& { return thr_->lowestWin[depth][suit]; } - auto lowest_win(int depth, int suit) const -> const unsigned short& { return thr_->lowestWin[depth][suit]; } - auto best_move(int depth) -> MoveType& { return thr_->bestMove[depth]; } - auto best_move(int depth) const -> const MoveType& { return thr_->bestMove[depth]; } - auto best_move_tt(int depth) -> MoveType& { return thr_->bestMoveTT[depth]; } - auto best_move_tt(int depth) const -> const MoveType& { return thr_->bestMoveTT[depth]; } - auto winners(int trickIndex) -> WinnersType& { return thr_->winners[trickIndex]; } - auto winners(int trickIndex) const -> const WinnersType& { return thr_->winners[trickIndex]; } - // Node type store for each hand (MAXNODE/MINNODE) - auto node_type_store(int hand) -> int& { return thr_->nodeTypeStore[hand]; } - auto node_type_store(int hand) const -> const int& { return thr_->nodeTypeStore[hand]; } - // Access to forbidden moves buffer used by Moves::Purge and solver loops - auto forbidden_moves() -> MoveType* { return thr_->forbiddenMoves; } - auto forbidden_moves() const -> const MoveType* { return thr_->forbiddenMoves; } - auto forbidden_move(int index) -> MoveType& { return thr_->forbiddenMoves[index]; } - auto forbidden_move(int index) const -> const MoveType& { return thr_->forbiddenMoves[index]; } - auto clear_forbidden_moves() -> void { - for (int k = 0; k <= 13; ++k) { - thr_->forbiddenMoves[k].rank = 0; - thr_->forbiddenMoves[k].suit = 0; - } - } - auto nodes() -> int& { return thr_->nodes; } - auto nodes() const -> const int& { return thr_->nodes; } - auto trick_nodes() -> int& { return thr_->trickNodes; } - auto trick_nodes() const -> const int& { return thr_->trickNodes; } - auto ini_depth() -> int& { return thr_->iniDepth; } - auto ini_depth() const -> int { return thr_->iniDepth; } - - public: - // Allow SolverContext to bind or rebind the underlying ThreadData - // after construction (useful when SolverContext owns the ThreadData - // and sets it up after default construction). - auto set_thread(const std::shared_ptr& thr) -> void + class SearchContext { - thr_ = thr; - } - - // Bind the owning SolverContext instance for access to config/utilities/arena - auto set_owner(SolverContext* owner) -> void - { - owner_ = owner; - } - - private: - std::shared_ptr thr_; - // Instance-owned transposition table, created lazily on first access. - std::unique_ptr tt_; - // Back-reference to the owning SolverContext (for config and utilities). - SolverContext* owner_ = nullptr; - }; - - // Expose a persistent SearchContext owned by the SolverContext. - /** + public: + SearchContext() = default; + + explicit SearchContext(std::shared_ptr thr) + : thr_(std::move(thr)) + { + } + + // Returns the owned transposition table instance (creates if null) + auto trans_table() -> TransTable*; + // Returns the TT instance if it exists, or nullptr + auto maybe_trans_table() const -> TransTable* { return tt_.get(); } + // Dispose and erase the TT instance owned by this context, if any. + auto dispose_trans_table() -> void { tt_.reset(); } + // Trivial accessors defined in the header so call sites in hot inner + // loops (notably ab_search.cpp) get inlined direct field accesses + // instead of cross-TU function calls. The previous out-of-line + // definitions in solver_context.cpp added ~20% to total ab_search + // self-time on Linux/x86_64. + auto analysis_flag() -> bool& { return thr_->analysisFlag; } + auto analysis_flag() const -> bool { return thr_->analysisFlag; } + auto lowest_win(int depth, int suit) -> unsigned short& { return thr_->lowestWin[depth][suit]; } + auto lowest_win(int depth, int suit) const -> const unsigned short& { return thr_->lowestWin[depth][suit]; } + auto best_move(int depth) -> MoveType& { return thr_->bestMove[depth]; } + auto best_move(int depth) const -> const MoveType& { return thr_->bestMove[depth]; } + auto best_move_tt(int depth) -> MoveType& { return thr_->bestMoveTT[depth]; } + auto best_move_tt(int depth) const -> const MoveType& { return thr_->bestMoveTT[depth]; } + auto winners(int trickIndex) -> WinnersType& { return thr_->winners[trickIndex]; } + auto winners(int trickIndex) const -> const WinnersType& { return thr_->winners[trickIndex]; } + // Node type store for each hand (MAXNODE/MINNODE) + auto node_type_store(int hand) -> int& { return thr_->nodeTypeStore[hand]; } + auto node_type_store(int hand) const -> const int& { return thr_->nodeTypeStore[hand]; } + // Access to forbidden moves buffer used by Moves::Purge and solver loops + auto forbidden_moves() -> MoveType* { return thr_->forbiddenMoves; } + auto forbidden_moves() const -> const MoveType* { return thr_->forbiddenMoves; } + auto forbidden_move(int index) -> MoveType& { return thr_->forbiddenMoves[index]; } + auto forbidden_move(int index) const -> const MoveType& { return thr_->forbiddenMoves[index]; } + auto clear_forbidden_moves() -> void { + for (int k = 0; k <= 13; ++k) { + thr_->forbiddenMoves[k].rank = 0; + thr_->forbiddenMoves[k].suit = 0; + } + } + auto nodes() -> int& { return thr_->nodes; } + auto nodes() const -> const int& { return thr_->nodes; } + auto trick_nodes() -> int& { return thr_->trickNodes; } + auto trick_nodes() const -> const int& { return thr_->trickNodes; } + auto ini_depth() -> int& { return thr_->iniDepth; } + auto ini_depth() const -> int { return thr_->iniDepth; } + + public: + // Allow SolverContext to bind or rebind the underlying ThreadData + // after construction (useful when SolverContext owns the ThreadData + // and sets it up after default construction). + auto set_thread(const std::shared_ptr& thr) -> void + { + thr_ = thr; + } + + // Bind the owning SolverContext instance for access to config/utilities/arena + auto set_owner(SolverContext* owner) -> void + { + owner_ = owner; + } + + private: + std::shared_ptr thr_; + // Instance-owned transposition table, created lazily on first access. + std::unique_ptr tt_; + // Back-reference to the owning SolverContext (for config and utilities). + SolverContext* owner_ = nullptr; + }; + + // Expose a persistent SearchContext owned by the SolverContext. + /** * @brief Access the persistent search-state facade. */ - auto search() -> SearchContext& - { - return search_; - } + auto search() -> SearchContext& + { + return search_; + } - /** + /** * @brief Access the persistent search-state facade (const). */ - auto search() const -> const SearchContext& - { - return search_; - } + auto search() const -> const SearchContext& + { + return search_; + } - // --- Move generation facade --- - /** + // --- Move generation facade --- + /** * @brief Facade for move generation utilities bound to ThreadData. */ - class MoveGenContext - { - public: - // Non-owning. `thr` must outlive this MoveGenContext; in practice the - // ThreadData is owned by the enclosing SolverContext's `thr_` - // shared_ptr, so a raw pointer here is safe and lets `SolverContext - // ::move_gen()` return a value-typed facade without an atomic - // shared_ptr refcount bump on every call (~22 calls per ab_search - // invocation, hot path). - explicit MoveGenContext(ThreadData* thr) - : thr_(thr) + class MoveGenContext { - } - - auto move_gen_0( - const int tricks, - const Pos& tpos, - const MoveType& bestMove, - const MoveType& bestMoveTT, - const RelRanksType thrp_rel[]) -> int; - - auto move_gen_123( - const int tricks, - const int relHand, - const Pos& tpos) -> int; - - auto purge( - const int tricks, - const int relHand, - const MoveType forbiddenMoves[]) -> void; - - auto make_next( - const int trick, - const int relHand, - const unsigned short win_ranks[]) -> const MoveType*; - - // Simpler variant without win_ranks used in several SolverIF paths - auto make_next_simple( - const int trick, - const int relHand) -> const MoveType*; - - auto get_length( - const int trick, - const int relHand) const -> int; - - auto rewind( - const int tricks, - const int relHand) -> void; - - auto register_hit( - const int tricks, - const int relHand) -> void; - - // Reinitialize move generation for a new lead hand at a given trick - auto reinit( - const int tricks, - const int leadHand) -> void; - - // Initialize move generation state for a given trick and starting hand - auto init( - const int tricks, - const int relStartHand, - const int initialRanks[], - const int initialSuits[], - const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], - const int trump, - const int leadHand) -> void; - - // Diagnostics (no behavior change; passthrough to Moves) - // Note: Emission is controlled by DDS_MOVES / DDS_MOVES_DETAILS. - auto print_trick_stats(std::ofstream& fout) const -> void; - auto print_trick_details(std::ofstream& fout) const -> void; - auto print_function_stats(std::ofstream& fout) const -> void; - - // Read-only access to per-trick generated metadata - auto get_trick_data(const int tricks) -> const TrickDataType&; + public: + // Non-owning. `thr` must outlive this MoveGenContext; in practice the + // ThreadData is owned by the enclosing SolverContext's `thr_` + // shared_ptr, so a raw pointer here is safe and lets `SolverContext + // ::move_gen()` return a value-typed facade without an atomic + // shared_ptr refcount bump on every call (~22 calls per ab_search + // invocation, hot path). + explicit MoveGenContext(ThreadData* thr) + : thr_(thr) + { + } + + auto move_gen_0( + const int tricks, + const Pos& tpos, + const MoveType& bestMove, + const MoveType& bestMoveTT, + const RelRanksType thrp_rel[]) -> int; + + auto move_gen_123( + const int tricks, + const int relHand, + const Pos& tpos) -> int; + + auto purge( + const int tricks, + const int relHand, + const MoveType forbiddenMoves[]) -> void; + + auto make_next( + const int trick, + const int relHand, + const unsigned short win_ranks[]) -> const MoveType*; + + // Simpler variant without win_ranks used in several SolverIF paths + auto make_next_simple( + const int trick, + const int relHand) -> const MoveType*; + + auto get_length( + const int trick, + const int relHand) const -> int; + + auto rewind( + const int tricks, + const int relHand) -> void; + + auto register_hit( + const int tricks, + const int relHand) -> void; + + // Reinitialize move generation for a new lead hand at a given trick + auto reinit( + const int tricks, + const int leadHand) -> void; + + // Initialize move generation state for a given trick and starting hand + auto init( + const int tricks, + const int relStartHand, + const int initialRanks[], + const int initialSuits[], + const unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS], + const int trump, + const int leadHand) -> void; + + // Diagnostics (no behavior change; passthrough to Moves) + // Note: Emission is controlled by DDS_MOVES / DDS_MOVES_DETAILS. + auto print_trick_stats(std::ofstream& fout) const -> void; + auto print_trick_details(std::ofstream& fout) const -> void; + auto print_function_stats(std::ofstream& fout) const -> void; + + // Read-only access to per-trick generated metadata + auto get_trick_data(const int tricks) -> const TrickDataType&; // Read-only textual dump helper - auto trick_to_text(const int trick) const -> std::string; + auto trick_to_text(const int trick) const -> std::string; - // Specify a particular move at a trick/hand position - auto make_specific( - const MoveType& mply, - const int trick, - const int relHand) -> void; + // Specify a particular move at a trick/hand position + auto make_specific( + const MoveType& mply, + const int trick, + const int relHand) -> void; - private: - ThreadData* thr_ = nullptr; - }; + private: + ThreadData* thr_ = nullptr; + }; - /** + /** * @brief Access move generation facade. */ - auto move_gen() const -> MoveGenContext - { - return MoveGenContext(thr_.get()); - } + auto move_gen() const -> MoveGenContext + { + return MoveGenContext(thr_.get()); + } private: - // Shared ownership of per-context ThreadData. Callers can construct - // a context with an externally-owned std::shared_ptr or - // let the context create/own one via the default constructor. - std::shared_ptr thr_; - // Persistent facade objects bound to this context. `search_` is - // initialized after `thr_` is set in constructors. - SearchContext search_; - SolverConfig cfg_{}; - mutable ::dds::Utilities utils_{}; - // Arena removed. - // NOTE: `owned_thr_` removed; `thr_` now represents the shared ownership - // (if any) for this context. - // Transposition table is now owned per SearchContext and created lazily. - // - // See the developer note above for details on TT lifecycle and resets. - - // True when this context created thr_ via SolverContext(SolverConfig). - bool owns_thread_data_ = false; - - void bind_thread_data(); + // Shared ownership of per-context ThreadData. Callers can construct + // a context with an externally-owned std::shared_ptr or + // let the context create/own one via the default constructor. + std::shared_ptr thr_; + // Persistent facade objects bound to this context. `search_` is + // initialized after `thr_` is set in constructors. + SearchContext search_; + SolverConfig cfg_{}; + mutable ::dds::Utilities utils_{}; + // Arena removed. + // NOTE: `owned_thr_` removed; `thr_` now represents the shared ownership + // (if any) for this context. + // Transposition table is now owned per SearchContext and created lazily. + // + // See the developer note above for details on TT lifecycle and resets. + + // True when this context created thr_ via SolverContext(SolverConfig). + bool owns_thread_data_ = false; + + void bind_thread_data(); }; auto ThreadMemoryUsed() -> double; diff --git a/library/src/solver_context_adapter.cpp b/library/src/solver_context_adapter.cpp index 7440599fc..20c4325f1 100644 --- a/library/src/solver_context_adapter.cpp +++ b/library/src/solver_context_adapter.cpp @@ -3,48 +3,48 @@ #include auto solve_board( - SolverContext& ctx, - const Deal& dl, - int target, - int solutions, - int mode, - FutureTricks* futp) -> int + SolverContext& ctx, + const Deal& dl, + int target, + int solutions, + int mode, + FutureTricks* futp) -> int { - // Use ThreadData-attached TT so all contexts created in lower layers - // observe the same table. No ownership adoption to avoid duplication. - return solve_board_internal(ctx, dl, target, solutions, mode, futp); + // Use ThreadData-attached TT so all contexts created in lower layers + // observe the same table. No ownership adoption to avoid duplication. + return solve_board_internal(ctx, dl, target, solutions, mode, futp); } auto SolveBoard( - SolverContext& ctx, - const Deal& dl, - int target, - int solutions, - int mode, - FutureTricks* futp) -> int + SolverContext& ctx, + const Deal& dl, + int target, + int solutions, + int mode, + FutureTricks* futp) -> int { - return solve_board(ctx, dl, target, solutions, mode, futp); + return solve_board(ctx, dl, target, solutions, mode, futp); } auto solve_board_pbn( - SolverContext& ctx, - const DealPBN& dlpbn, - int target, - int solutions, - int mode, - FutureTricks* futp) -> int + SolverContext& ctx, + const DealPBN& dlpbn, + int target, + int solutions, + int mode, + FutureTricks* futp) -> int { - Deal dl; - if (convert_from_pbn(dlpbn.remainCards, dl.remainCards) != RETURN_NO_FAULT) - return RETURN_PBN_FAULT; + Deal dl; + if (convert_from_pbn(dlpbn.remainCards, dl.remainCards) != RETURN_NO_FAULT) + return RETURN_PBN_FAULT; - for (int k = 0; k <= 2; k++) - { - dl.currentTrickRank[k] = dlpbn.currentTrickRank[k]; - dl.currentTrickSuit[k] = dlpbn.currentTrickSuit[k]; - } - dl.first = dlpbn.first; - dl.trump = dlpbn.trump; + for (int k = 0; k <= 2; k++) + { + dl.currentTrickRank[k] = dlpbn.currentTrickRank[k]; + dl.currentTrickSuit[k] = dlpbn.currentTrickSuit[k]; + } + dl.first = dlpbn.first; + dl.trump = dlpbn.trump; - return solve_board(ctx, dl, target, solutions, mode, futp); + return solve_board(ctx, dl, target, solutions, mode, futp); } diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index 0c0bf5da8..70fb3403c 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -29,429 +29,429 @@ extern Scheduler scheduler; auto board_range_checks( - const Deal& dl, - const int target, - const int solutions, - const int mode) -> int; + const Deal& dl, + const int target, + const int solutions, + const int mode) -> int; auto board_value_checks( - SolverContext& ctx, - const Deal& dl, - const int target, - const int solutions, - const int mode) -> int; + SolverContext& ctx, + const Deal& dl, + const int target, + const int solutions, + const int mode) -> int; auto last_trick_winner( - const Deal& dl, - const std::shared_ptr& thrp, - const int handToPlay, - const int hand_rel_first, - int& leadRank, - int& leadSuit, - int& leadSideWins) -> void; + const Deal& dl, + const std::shared_ptr& thrp, + const int handToPlay, + const int hand_rel_first, + int& leadRank, + int& leadSuit, + int& leadSideWins) -> void; bool (* AB_ptr_list[DDS_HANDS])( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) - = { ab_search, ab_search_1, ab_search_2, ab_search_3 }; + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) + = { ab_search, ab_search_1, ab_search_2, ab_search_3 }; bool (* AB_ptr_trace_list[DDS_HANDS])( - Pos * posPoint, - const int target, - const int depth, - SolverContext& ctx) - = { ab_search_0, ab_search_1, ab_search_2, ab_search_3 }; + Pos * posPoint, + const int target, + const int depth, + SolverContext& ctx) + = { ab_search_0, ab_search_1, ab_search_2, ab_search_3 }; void (* Make_ptr_list[3])( - Pos * posPoint, - const int depth, - MoveType const * mply) - = { make_0, make_1, make_2 }; + Pos * posPoint, + const int depth, + MoveType const * mply) + = { make_0, make_1, make_2 }; int STDCALL SolveBoard( - Deal dl, - int target, - int solutions, - int mode, - FutureTricks * futp, - [[maybe_unused]] int thrId) + Deal dl, + int target, + int solutions, + int mode, + FutureTricks * futp, + [[maybe_unused]] int thrId) { - SolverContext outer_ctx; - return solve_board(outer_ctx, dl, target, solutions, mode, futp); + SolverContext outer_ctx; + return solve_board(outer_ctx, dl, target, solutions, mode, futp); } auto solve_board_internal( - SolverContext& ctx, - const Deal& dl, - const int target, - const int solutions, - const int mode, - FutureTricks * futp) -> int + SolverContext& ctx, + const Deal& dl, + const int target, + const int solutions, + const int mode, + FutureTricks * futp) -> int { - // ---------------------------------------------------------- - // Formal parameter checks. - // ---------------------------------------------------------- - - int ret = board_range_checks(dl, target, solutions, mode); - if (ret != RETURN_NO_FAULT) - return ret; - - // ---------------------------------------------------------- - // Count and classify Deal. - // ---------------------------------------------------------- - - auto thrp = ctx.thread(); - bool newDeal = false; - bool newTrump = false; - unsigned diffDeal = 0; - unsigned aggDeal = 0; - bool similarDeal; - int cardCount = 0; - int ind, forb, noMoves; - - for (int h = 0; h < DDS_HANDS; h++) - { - for (int s = 0; s < DDS_SUITS; s++) + // ---------------------------------------------------------- + // Formal parameter checks. + // ---------------------------------------------------------- + + int ret = board_range_checks(dl, target, solutions, mode); + if (ret != RETURN_NO_FAULT) + return ret; + + // ---------------------------------------------------------- + // Count and classify Deal. + // ---------------------------------------------------------- + + auto thrp = ctx.thread(); + bool newDeal = false; + bool newTrump = false; + unsigned diffDeal = 0; + unsigned aggDeal = 0; + bool similarDeal; + int cardCount = 0; + int ind, forb, noMoves; + + for (int h = 0; h < DDS_HANDS; h++) { - unsigned int c = dl.remainCards[h][s] >> 2; + for (int s = 0; s < DDS_SUITS; s++) + { + unsigned int c = dl.remainCards[h][s] >> 2; - cardCount += count_table[c]; - diffDeal += (c ^ (thrp->suit[h][s])); - aggDeal += c; + cardCount += count_table[c]; + diffDeal += (c ^ (thrp->suit[h][s])); + aggDeal += c; - if (thrp->suit[h][s] != c) - { - thrp->suit[h][s] = static_cast(c); - newDeal = true; - } + if (thrp->suit[h][s] != c) + { + thrp->suit[h][s] = static_cast(c); + newDeal = true; + } + } } - } - if (newDeal) - { - if (diffDeal == 0) - similarDeal = true; - else if ((aggDeal / diffDeal) > SIMILARDEALLIMIT) - similarDeal = true; + if (newDeal) + { + if (diffDeal == 0) + similarDeal = true; + else if ((aggDeal / diffDeal) > SIMILARDEALLIMIT) + similarDeal = true; + else + similarDeal = false; + } else - similarDeal = false; - } - else - similarDeal = false; + similarDeal = false; - if (dl.trump != thrp->trump) - newTrump = true; + if (dl.trump != thrp->trump) + newTrump = true; - // ---------------------------------------------------------- - // Generic initialization. - // ---------------------------------------------------------- + // ---------------------------------------------------------- + // Generic initialization. + // ---------------------------------------------------------- + + thrp->trump = dl.trump; + ctx.search().ini_depth() = cardCount - 4; + int ini_depth = ctx.search().ini_depth(); + int trick = (ini_depth + 3) >> 2; + int hand_rel_first = (48 - ini_depth) % 4; + int handToPlay = HAND_ID(dl.first, hand_rel_first); + ctx.search().trick_nodes() = 0; - thrp->trump = dl.trump; - ctx.search().ini_depth() = cardCount - 4; - int ini_depth = ctx.search().ini_depth(); - int trick = (ini_depth + 3) >> 2; - int hand_rel_first = (48 - ini_depth) % 4; - int handToPlay = HAND_ID(dl.first, hand_rel_first); - ctx.search().trick_nodes() = 0; + thrp->lookAheadPos.hand_rel_first = hand_rel_first; + thrp->lookAheadPos.tricks_max = 0; - thrp->lookAheadPos.hand_rel_first = hand_rel_first; - thrp->lookAheadPos.tricks_max = 0; + MoveType mv = {0, 0, 0, 0}; - MoveType mv = {0, 0, 0, 0}; + ctx.search().clear_forbidden_moves(); - ctx.search().clear_forbidden_moves(); - - // ---------------------------------------------------------- - // Consistency checks. - // ---------------------------------------------------------- + // ---------------------------------------------------------- + // Consistency checks. + // ---------------------------------------------------------- - ret = board_value_checks(ctx, dl, target, solutions, mode); - if (ret != RETURN_NO_FAULT) - return ret; + ret = board_value_checks(ctx, dl, target, solutions, mode); + if (ret != RETURN_NO_FAULT) + return ret; - // Reset per-solve TT stats here so the SOLVER_DONE report reflects - // this board even when the last-trick early exit below is taken. - thrp->tt_lookup_count = 0; - thrp->tt_hit_count = 0; - if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); + // Reset per-solve TT stats here so the SOLVER_DONE report reflects + // this board even when the last-trick early exit below is taken. + thrp->tt_lookup_count = 0; + thrp->tt_hit_count = 0; + if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); - // ---------------------------------------------------------- - // Last trick, easy to solve. - // ---------------------------------------------------------- + // ---------------------------------------------------------- + // Last trick, easy to solve. + // ---------------------------------------------------------- - if (cardCount <= 4) - { - int leadRank, leadSuit, leadSideWins; + if (cardCount <= 4) + { + int leadRank, leadSuit, leadSideWins; - last_trick_winner(dl, thrp, handToPlay, hand_rel_first, - leadRank, leadSuit, leadSideWins); + last_trick_winner(dl, thrp, handToPlay, hand_rel_first, + leadRank, leadSuit, leadSideWins); - futp->nodes = 0; - futp->cards = 1; - futp->suit[0] = leadSuit; - futp->rank[0] = leadRank; - futp->equals[0] = 0; - futp->score[0] = (target == 0 && solutions < 3 ? 0 : leadSideWins); + futp->nodes = 0; + futp->cards = 1; + futp->suit[0] = leadSuit; + futp->rank[0] = leadRank; + futp->equals[0] = 0; + futp->score[0] = (target == 0 && solutions < 3 ? 0 : leadSideWins); - goto SOLVER_DONE; - } + goto SOLVER_DONE; + } - // Validated cardCount > 4 ⇒ ini_depth >= 1; safe to index first[]. - thrp->lookAheadPos.first[ini_depth] = dl.first; + // Validated cardCount > 4 ⇒ ini_depth >= 1; safe to index first[]. + thrp->lookAheadPos.first[ini_depth] = dl.first; - // ---------------------------------------------------------- - // More detailed initialization. - // ---------------------------------------------------------- + // ---------------------------------------------------------- + // More detailed initialization. + // ---------------------------------------------------------- - { - if ((mode != 2) && - (((newDeal) && (! similarDeal)) || + { + if ((mode != 2) && + (((newDeal) && (! similarDeal)) || newTrump || (ctx.search().nodes() > SIMILARMAXWINNODES))) + { + ResetReason reason = ResetReason::Unknown; + if (ctx.search().nodes() > SIMILARMAXWINNODES) + reason = ResetReason::TooManyNodes; + else if (newDeal && ! similarDeal) + reason = ResetReason::NewDeal; + else if (newTrump) + reason = ResetReason::NewTrump; + + ctx.trans_table()->reset_memory(reason); + } + } + + if (newDeal) { - ResetReason reason = ResetReason::Unknown; - if (ctx.search().nodes() > SIMILARMAXWINNODES) - reason = ResetReason::TooManyNodes; - else if (newDeal && ! similarDeal) - reason = ResetReason::NewDeal; - else if (newTrump) - reason = ResetReason::NewTrump; - - ctx.trans_table()->reset_memory(reason); - } - } - - if (newDeal) - { - SetDeal(thrp); - SetDealTables(ctx); - } - else if (ctx.search().analysis_flag()) - { SetDeal(thrp); - } - ctx.search().analysis_flag() = false; - - if (handToPlay == 0 || handToPlay == 2) - { - ctx.search().node_type_store(0) = MAXNODE; - ctx.search().node_type_store(1) = MINNODE; - ctx.search().node_type_store(2) = MAXNODE; - ctx.search().node_type_store(3) = MINNODE; - } - else - { - ctx.search().node_type_store(0) = MINNODE; - ctx.search().node_type_store(1) = MAXNODE; - ctx.search().node_type_store(2) = MINNODE; - ctx.search().node_type_store(3) = MAXNODE; - } - - for (int k = 0; k < hand_rel_first; k++) - { - mv.rank = dl.currentTrickRank[k]; - mv.suit = dl.currentTrickSuit[k]; - mv.sequence = 0; - - ctx.move_gen().init( - trick, - k, - dl.currentTrickRank, - dl.currentTrickSuit, - thrp->lookAheadPos.rank_in_suit, - thrp->trump, - thrp->lookAheadPos.first[ini_depth]); + SetDealTables(ctx); + } + else if (ctx.search().analysis_flag()) + { + SetDeal(thrp); + } + ctx.search().analysis_flag() = false; - if (k == 0) + if (handToPlay == 0 || handToPlay == 2) { - ctx.move_gen().move_gen_0( - trick, - thrp->lookAheadPos, - ctx.search().best_move(ini_depth), - ctx.search().best_move_tt(ini_depth), - thrp->rel); + ctx.search().node_type_store(0) = MAXNODE; + ctx.search().node_type_store(1) = MINNODE; + ctx.search().node_type_store(2) = MAXNODE; + ctx.search().node_type_store(3) = MINNODE; } else - ctx.move_gen().move_gen_123( - trick, - k, - thrp->lookAheadPos); + { + ctx.search().node_type_store(0) = MINNODE; + ctx.search().node_type_store(1) = MAXNODE; + ctx.search().node_type_store(2) = MINNODE; + ctx.search().node_type_store(3) = MAXNODE; + } - thrp->lookAheadPos.move[ini_depth + hand_rel_first - k] = mv; - ctx.move_gen().make_specific(mv, trick, k); - } + for (int k = 0; k < hand_rel_first; k++) + { + mv.rank = dl.currentTrickRank[k]; + mv.suit = dl.currentTrickSuit[k]; + mv.sequence = 0; + + ctx.move_gen().init( + trick, + k, + dl.currentTrickRank, + dl.currentTrickSuit, + thrp->lookAheadPos.rank_in_suit, + thrp->trump, + thrp->lookAheadPos.first[ini_depth]); + + if (k == 0) + { + ctx.move_gen().move_gen_0( + trick, + thrp->lookAheadPos, + ctx.search().best_move(ini_depth), + ctx.search().best_move_tt(ini_depth), + thrp->rel); + } + else + ctx.move_gen().move_gen_123( + trick, + k, + thrp->lookAheadPos); + + thrp->lookAheadPos.move[ini_depth + hand_rel_first - k] = mv; + ctx.move_gen().make_specific(mv, trick, k); + } - InitWinners(dl, thrp->lookAheadPos, thrp); + InitWinners(dl, thrp->lookAheadPos, thrp); #ifdef DDS_AB_STATS - thrp->ABStats.Reset(); - thrp->ABStats.ResetCum(); + thrp->ABStats.Reset(); + thrp->ABStats.ResetCum(); #endif #ifdef DDS_TOP_LEVEL - { - ctx.search().nodes() = 0; - } + { + ctx.search().nodes() = 0; + } #endif - ctx.move_gen().init( - trick, - hand_rel_first, - dl.currentTrickRank, - dl.currentTrickSuit, - thrp->lookAheadPos.rank_in_suit, - thrp->trump, - thrp->lookAheadPos.first[ini_depth]); - - if (hand_rel_first == 0) - { - ctx.move_gen().move_gen_0( - trick, - thrp->lookAheadPos, - ctx.search().best_move(ini_depth), - ctx.search().best_move_tt(ini_depth), - thrp->rel); - } - else - ctx.move_gen().move_gen_123( - trick, - hand_rel_first, - thrp->lookAheadPos); - - noMoves = ctx.move_gen().get_length(trick, hand_rel_first); - - // ---------------------------------------------------------- - // mode == 0: Check whether there is only one possible move - // ---------------------------------------------------------- - - if (mode == 0 && noMoves == 1 && solutions != 3) - { - MoveType const * mp = ctx.move_gen().make_next_simple(trick, hand_rel_first); - - futp->nodes = 0; - futp->cards = 1; + ctx.move_gen().init( + trick, + hand_rel_first, + dl.currentTrickRank, + dl.currentTrickSuit, + thrp->lookAheadPos.rank_in_suit, + thrp->trump, + thrp->lookAheadPos.first[ini_depth]); + + if (hand_rel_first == 0) + { + ctx.move_gen().move_gen_0( + trick, + thrp->lookAheadPos, + ctx.search().best_move(ini_depth), + ctx.search().best_move_tt(ini_depth), + thrp->rel); + } + else + ctx.move_gen().move_gen_123( + trick, + hand_rel_first, + thrp->lookAheadPos); + + noMoves = ctx.move_gen().get_length(trick, hand_rel_first); - futp->suit[0] = mp->suit; - futp->rank[0] = mp->rank; - futp->equals[0] = mp->sequence << 2; - futp->score[0] = -2; + // ---------------------------------------------------------- + // mode == 0: Check whether there is only one possible move + // ---------------------------------------------------------- - goto SOLVER_DONE; - } + if (mode == 0 && noMoves == 1 && solutions != 3) + { + MoveType const * mp = ctx.move_gen().make_next_simple(trick, hand_rel_first); - // ---------------------------------------------------------- - // solutions == 3: Target and mode don't matter; all cards - // ---------------------------------------------------------- + futp->nodes = 0; + futp->cards = 1; - if (solutions == 3) - { - // 7 for hand 0 and 2, 6 for hand 1 and 3 - int guess = 7 - (handToPlay & 0x1); - int upperbound = 13; - int lowerbound = 0; - futp->cards = noMoves; + futp->suit[0] = mp->suit; + futp->rank[0] = mp->rank; + futp->equals[0] = mp->sequence << 2; + futp->score[0] = -2; + + goto SOLVER_DONE; + } - for (int mno = 0; mno < noMoves; mno++) + // ---------------------------------------------------------- + // solutions == 3: Target and mode don't matter; all cards + // ---------------------------------------------------------- + + if (solutions == 3) { - do - { - ctx.reset_best_moves_lite(); + // 7 for hand 0 and 2, 6 for hand 1 and 3 + int guess = 7 - (handToPlay & 0x1); + int upperbound = 13; + int lowerbound = 0; + futp->cards = noMoves; - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = (* AB_ptr_list[hand_rel_first])( - &thrp->lookAheadPos, - guess, - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + for (int mno = 0; mno < noMoves; mno++) + { + do + { + ctx.reset_best_moves_lite(); + + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = (* AB_ptr_list[hand_rel_first])( + &thrp->lookAheadPos, + guess, + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, guess, lowerbound, upperbound, 1); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, guess, lowerbound, upperbound, 1); #endif - if (thrp->val) - { - mv = ctx.search().best_move(ini_depth); - lowerbound = guess++; + if (thrp->val) + { + mv = ctx.search().best_move(ini_depth); + lowerbound = guess++; + } + else + upperbound = --guess; + } + while (lowerbound < upperbound); + + if (lowerbound) + { + ctx.search().best_move(ini_depth) = mv; + + futp->suit[mno] = mv.suit; + futp->rank[mno] = mv.rank; + futp->equals[mno] = mv.sequence << 2; + futp->score[mno] = lowerbound; + + ctx.search().forbidden_move(mno + 1).suit = mv.suit; + ctx.search().forbidden_move(mno + 1).rank = mv.rank; + + guess = lowerbound; + lowerbound = 0; + } + else + { + int noLeft = ctx.move_gen().get_length(trick, hand_rel_first); + + ctx.move_gen().rewind(trick, hand_rel_first); + for (int j = 0; j < noLeft; j++) + { + MoveType const * mp = + ctx.move_gen().make_next_simple(trick, hand_rel_first); + + futp->suit[mno + j] = mp->suit; + futp->rank[mno + j] = mp->rank; + futp->equals[mno + j] = mp->sequence << 2; + futp->score[mno + j] = 0; + } + + break; + } } - else - upperbound = --guess; - } - while (lowerbound < upperbound); - - if (lowerbound) - { - ctx.search().best_move(ini_depth) = mv; - - futp->suit[mno] = mv.suit; - futp->rank[mno] = mv.rank; - futp->equals[mno] = mv.sequence << 2; - futp->score[mno] = lowerbound; + goto SOLVER_STATS; + } - ctx.search().forbidden_move(mno + 1).suit = mv.suit; - ctx.search().forbidden_move(mno + 1).rank = mv.rank; + // ---------------------------------------------------------- + // target == 0: Only cards required, no scoring + // ---------------------------------------------------------- - guess = lowerbound; - lowerbound = 0; - } - else - { - int noLeft = ctx.move_gen().get_length(trick, hand_rel_first); + else if (target == 0) + { + futp->nodes = 0; + futp->cards = (solutions == 1 ? 1 : noMoves); - ctx.move_gen().rewind(trick, hand_rel_first); - for (int j = 0; j < noLeft; j++) + for (int mno = 0; mno < noMoves; mno++) { - MoveType const * mp = - ctx.move_gen().make_next_simple(trick, hand_rel_first); + MoveType const * mp = + ctx.move_gen().make_next_simple(trick, hand_rel_first); - futp->suit[mno + j] = mp->suit; - futp->rank[mno + j] = mp->rank; - futp->equals[mno + j] = mp->sequence << 2; - futp->score[mno + j] = 0; + futp->suit[mno] = mp->suit; + futp->rank[mno] = mp->rank; + futp->equals[mno] = mp->sequence << 2; + futp->score[mno] = 0; } - break; - } + goto SOLVER_DONE; } - goto SOLVER_STATS; - } - - // ---------------------------------------------------------- - // target == 0: Only cards required, no scoring - // ---------------------------------------------------------- - else if (target == 0) - { - futp->nodes = 0; - futp->cards = (solutions == 1 ? 1 : noMoves); + // ---------------------------------------------------------- + // target == -1: Find optimum score and 1 or more cards + // ---------------------------------------------------------- - for (int mno = 0; mno < noMoves; mno++) + else if (target == -1) { - MoveType const * mp = - ctx.move_gen().make_next_simple(trick, hand_rel_first); - - futp->suit[mno] = mp->suit; - futp->rank[mno] = mp->rank; - futp->equals[mno] = mp->sequence << 2; - futp->score[mno] = 0; - } - - goto SOLVER_DONE; - } - - // ---------------------------------------------------------- - // target == -1: Find optimum score and 1 or more cards - // ---------------------------------------------------------- - - else if (target == -1) - { - /* + /* * Reset semantics * ---------------- * - reset_for_solve(): Heavy, per-solve reset (frees TT memory as needed and @@ -464,881 +464,881 @@ auto solve_board_internal( * bestMoveTT[*].rank, updates memUsed and ABStats. Use this inside the * do/while and other iterative loops below to preserve historical results. */ - // 7 for hand 0 and 2, 6 for hand 1 and 3 - int guess = 7 - (handToPlay & 0x1); - int upperbound = 13; - int lowerbound = 0; - do - { - ctx.reset_best_moves_lite(); + // 7 for hand 0 and 2, 6 for hand 1 and 3 + int guess = 7 - (handToPlay & 0x1); + int upperbound = 13; + int lowerbound = 0; + do + { + ctx.reset_best_moves_lite(); - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = (* AB_ptr_list[hand_rel_first])(&thrp->lookAheadPos, - guess, - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = (* AB_ptr_list[hand_rel_first])(&thrp->lookAheadPos, + guess, + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, guess, lowerbound, upperbound, 1); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, guess, lowerbound, upperbound, 1); #endif - if (thrp->val) - { - mv = ctx.search().best_move(ini_depth); - lowerbound = guess++; - } - else - upperbound = --guess; + if (thrp->val) + { + mv = ctx.search().best_move(ini_depth); + lowerbound = guess++; + } + else + upperbound = --guess; - } - while (lowerbound < upperbound); - - - ctx.search().best_move(ini_depth) = mv; - - if (lowerbound == 0) - { - // ALL the other moves must also have payoff 0. - - if (solutions == 1) // We only need one of them - futp->cards = 1; - else // solutions == 2, so return all cards - futp->cards = noMoves; + } + while (lowerbound < upperbound); - ctx.move_gen().rewind(trick, hand_rel_first); - for (int i = 0; i < noMoves; i++) - { - MoveType const * mp = - ctx.move_gen().make_next_simple(trick, hand_rel_first); - futp->score[i] = 0; - futp->suit[i] = mp->suit; - futp->rank[i] = mp->rank; - futp->equals[i] = mp->sequence << 2; - } + ctx.search().best_move(ini_depth) = mv; - goto SOLVER_STATS; - } - else // payoff > 0 - { - futp->cards = 1; - futp->score[0] = lowerbound; - futp->suit[0] = mv.suit; - futp->rank[0] = mv.rank; - futp->equals[0] = mv.sequence << 2; - - if (solutions != 2) - goto SOLVER_STATS; + if (lowerbound == 0) + { + // ALL the other moves must also have payoff 0. + + if (solutions == 1) // We only need one of them + futp->cards = 1; + else // solutions == 2, so return all cards + futp->cards = noMoves; + + ctx.move_gen().rewind(trick, hand_rel_first); + for (int i = 0; i < noMoves; i++) + { + MoveType const * mp = + ctx.move_gen().make_next_simple(trick, hand_rel_first); + + futp->score[i] = 0; + futp->suit[i] = mp->suit; + futp->rank[i] = mp->rank; + futp->equals[i] = mp->sequence << 2; + } + + goto SOLVER_STATS; + } + else // payoff > 0 + { + futp->cards = 1; + futp->score[0] = lowerbound; + futp->suit[0] = mv.suit; + futp->rank[0] = mv.rank; + futp->equals[0] = mv.sequence << 2; + + if (solutions != 2) + goto SOLVER_STATS; + } } - } - // ---------------------------------------------------------- - // target >= 1: Find optimum card(s) achieving user's target - // ---------------------------------------------------------- + // ---------------------------------------------------------- + // target >= 1: Find optimum card(s) achieving user's target + // ---------------------------------------------------------- - else - { - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = (* AB_ptr_list[hand_rel_first])( - &thrp->lookAheadPos, - target, - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + else + { + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = (* AB_ptr_list[hand_rel_first])( + &thrp->lookAheadPos, + target, + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, target, -1, -1, 0); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, target, -1, -1, 0); #endif - if (! thrp->val) - { - // No move. If target was 1, then we are sure that in - // fact no tricks can be won. If target was > 1, then - // it is still possible that no tricks can't be won. - // We don't know. So that's arguably a small bug. - futp->cards = 0; - futp->score[0] = (target > 1 ? -1 : 0); - - goto SOLVER_STATS; + if (! thrp->val) + { + // No move. If target was 1, then we are sure that in + // fact no tricks can be won. If target was > 1, then + // it is still possible that no tricks can't be won. + // We don't know. So that's arguably a small bug. + futp->cards = 0; + futp->score[0] = (target > 1 ? -1 : 0); + + goto SOLVER_STATS; + } + else + { + futp->cards = 1; + futp->suit[0] = ctx.search().best_move(ini_depth).suit; + futp->rank[0] = ctx.search().best_move(ini_depth).rank; + futp->equals[0] = ctx.search().best_move(ini_depth).sequence << 2; + futp->score[0] = target; + + if (solutions != 2) + goto SOLVER_STATS; + } } - else - { - futp->cards = 1; - futp->suit[0] = ctx.search().best_move(ini_depth).suit; - futp->rank[0] = ctx.search().best_move(ini_depth).rank; - futp->equals[0] = ctx.search().best_move(ini_depth).sequence << 2; - futp->score[0] = target; - if (solutions != 2) - goto SOLVER_STATS; - } - } + // ---------------------------------------------------------- + // solution == 2 && payoff > 0: Find other cards with score. + // This applies both to target == -1 and target >= 1. + // ---------------------------------------------------------- - // ---------------------------------------------------------- - // solution == 2 && payoff > 0: Find other cards with score. - // This applies both to target == -1 and target >= 1. - // ---------------------------------------------------------- + forb = 1; + ind = 1; - forb = 1; - ind = 1; + while (ind < noMoves) + { + // Moves up to and including bestMove are now forbidden. - while (ind < noMoves) - { - // Moves up to and including bestMove are now forbidden. + ctx.move_gen().rewind(trick, hand_rel_first); + int num = ctx.move_gen().get_length(trick, hand_rel_first); - ctx.move_gen().rewind(trick, hand_rel_first); - int num = ctx.move_gen().get_length(trick, hand_rel_first); + for (int k = 0; k < num; k++) + { + MoveType const * mp = + ctx.move_gen().make_next_simple(trick, hand_rel_first); - for (int k = 0; k < num; k++) - { - MoveType const * mp = - ctx.move_gen().make_next_simple(trick, hand_rel_first); - - ctx.search().forbidden_move(forb) = * mp; - forb++; + ctx.search().forbidden_move(forb) = * mp; + forb++; - if ((ctx.search().best_move(ini_depth).suit == mp->suit) && - (ctx.search().best_move(ini_depth).rank == mp->rank)) - break; - } + if ((ctx.search().best_move(ini_depth).suit == mp->suit) && + (ctx.search().best_move(ini_depth).rank == mp->rank)) + break; + } - /* No per-iteration full reset here; preserve original behavior */ + /* No per-iteration full reset here; preserve original behavior */ - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = (* AB_ptr_list[hand_rel_first])( - &thrp->lookAheadPos, - futp->score[0], - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = (* AB_ptr_list[hand_rel_first])( + &thrp->lookAheadPos, + futp->score[0], + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, target, -1, -1, 2); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, target, -1, -1, 2); #endif - if (! thrp->val) - break; + if (! thrp->val) + break; - futp->cards = ind + 1; - futp->suit[ind] = ctx.search().best_move(ini_depth).suit; - futp->rank[ind] = ctx.search().best_move(ini_depth).rank; - futp->equals[ind] = ctx.search().best_move(ini_depth).sequence << 2; - - futp->score[ind] = futp->score[0]; - ind++; - } + futp->cards = ind + 1; + futp->suit[ind] = ctx.search().best_move(ini_depth).suit; + futp->rank[ind] = ctx.search().best_move(ini_depth).rank; + futp->equals[ind] = ctx.search().best_move(ini_depth).sequence << 2; + + futp->score[ind] = futp->score[0]; + ind++; + } SOLVER_STATS: - { - ctx.search().clear_forbidden_moves(); - } + { + ctx.search().clear_forbidden_moves(); + } #ifdef DDS_TIMING - thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); + thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); #endif #ifdef DDS_TT_STATS - // These are for the large TT -- empty if not. - // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); - - { - ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); - } - - // These are for the small TT -- empty if not. - { - ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); - } + // These are for the large TT -- empty if not. + // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); + + { + ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); + } + + // These are for the small TT -- empty if not. + { + ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); + } #endif // Diagnostics are routed via the SolverContext MoveGen facade. #ifdef DDS_MOVES - ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); #ifdef DDS_MOVES_DETAILS - ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); #endif - ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); #endif SOLVER_DONE: - { - thrp->memUsed = ctx.trans_table()->memory_in_use() + ThreadMemoryUsed(); - } - { - futp->nodes = ctx.search().trick_nodes(); - } - - // Print TT stats if requested - if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { - ThreadData* thrp_ptr = ctx.thread_ptr(); - if (thrp_ptr) { - double hit_rate = thrp_ptr->tt_lookup_count > 0 ? - 100.0 * (double)thrp_ptr->tt_hit_count / - (double)thrp_ptr->tt_lookup_count : 0.0; - std::fprintf(stderr, + { + thrp->memUsed = ctx.trans_table()->memory_in_use() + ThreadMemoryUsed(); + } + { + futp->nodes = ctx.search().trick_nodes(); + } + + // Print TT stats if requested + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { + ThreadData* thrp_ptr = ctx.thread_ptr(); + if (thrp_ptr) { + double hit_rate = thrp_ptr->tt_lookup_count > 0 ? + 100.0 * (double)thrp_ptr->tt_hit_count / + (double)thrp_ptr->tt_lookup_count : 0.0; + std::fprintf(stderr, "DDS_TT_STATS: lookups=%" PRIu64 " hits=%" PRIu64 " hit_rate=%.2f%%\n", thrp_ptr->tt_lookup_count, thrp_ptr->tt_hit_count, hit_rate); - if (auto* tt = ctx.trans_table()) { - int adds, overwrites, harvests; - tt->get_op_stats(adds, overwrites, harvests); - double ow_rate = adds > 0 ? - 100.0 * (double)overwrites / (double)adds : 0.0; - std::fprintf(stderr, + if (auto* tt = ctx.trans_table()) { + int adds, overwrites, harvests; + tt->get_op_stats(adds, overwrites, harvests); + double ow_rate = adds > 0 ? + 100.0 * (double)overwrites / (double)adds : 0.0; + std::fprintf(stderr, "DDS_TT_STATS: adds=%d overwrites=%d overwrite_rate=%.2f%% harvests=%d\n", adds, overwrites, ow_rate, harvests); - } + } + } } - } #ifdef DDS_MEMORY_LEAKS_WIN32 - _CrtDumpMemoryLeaks(); + _CrtDumpMemoryLeaks(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto solve_same_board( - SolverContext& ctx, - const Deal& dl, - FutureTricks * futp, - const int hint) -> int + SolverContext& ctx, + const Deal& dl, + FutureTricks * futp, + const int hint) -> int { - // Specialized function for SolveChunkDDtable for repeat solves. - // No further parameter checks! This function makes heavy reuse - // of parameters that are already stored in various places. It - // corresponds to: - // target == -1, solutions == 1, mode == 2. - // The function only needs to return fut.score[0]. - - auto thrp = ctx.thread(); - int ini_depth = ctx.search().ini_depth(); - int trick = (ini_depth + 3) >> 2; - { - ctx.search().trick_nodes() = 0; - } - - thrp->lookAheadPos.first[ini_depth] = dl.first; - - { - if (dl.first == 0 || dl.first == 2) + // Specialized function for SolveChunkDDtable for repeat solves. + // No further parameter checks! This function makes heavy reuse + // of parameters that are already stored in various places. It + // corresponds to: + // target == -1, solutions == 1, mode == 2. + // The function only needs to return fut.score[0]. + + auto thrp = ctx.thread(); + int ini_depth = ctx.search().ini_depth(); + int trick = (ini_depth + 3) >> 2; { - ctx.search().node_type_store(0) = MAXNODE; - ctx.search().node_type_store(1) = MINNODE; - ctx.search().node_type_store(2) = MAXNODE; - ctx.search().node_type_store(3) = MINNODE; + ctx.search().trick_nodes() = 0; } - else + + thrp->lookAheadPos.first[ini_depth] = dl.first; + { - ctx.search().node_type_store(0) = MINNODE; - ctx.search().node_type_store(1) = MAXNODE; - ctx.search().node_type_store(2) = MINNODE; - ctx.search().node_type_store(3) = MAXNODE; + if (dl.first == 0 || dl.first == 2) + { + ctx.search().node_type_store(0) = MAXNODE; + ctx.search().node_type_store(1) = MINNODE; + ctx.search().node_type_store(2) = MAXNODE; + ctx.search().node_type_store(3) = MINNODE; + } + else + { + ctx.search().node_type_store(0) = MINNODE; + ctx.search().node_type_store(1) = MAXNODE; + ctx.search().node_type_store(2) = MINNODE; + ctx.search().node_type_store(3) = MAXNODE; + } } - } #ifdef DDS_AB_STATS - thrp->ABStats.Reset(); - thrp->ABStats.ResetCum(); + thrp->ABStats.Reset(); + thrp->ABStats.ResetCum(); #endif - // Reset per-solve TT stats (keeps warm TT, resets counters only) - thrp->tt_lookup_count = 0; - thrp->tt_hit_count = 0; - if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); + // Reset per-solve TT stats (keeps warm TT, resets counters only) + thrp->tt_lookup_count = 0; + thrp->tt_hit_count = 0; + if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); #ifdef DDS_TOP_LEVEL - { - ctx.search().nodes() = 0; - } + { + ctx.search().nodes() = 0; + } #endif - ctx.move_gen().reinit(trick, dl.first); - - // ini_depth == cardCount - 4 (see solve_board). Bound the null-window - // search by remaining tricks so partial deals cannot report scores > 13 - // leftovers from a full-hand upper bound. - const int card_count = ini_depth + 4; - const int remaining_tricks = (card_count % 4) - ? ((card_count - 4) >> 2) + 2 - : ((card_count - 4) >> 2) + 1; - - int guess = hint; - if (guess < 0) - guess = 0; - if (guess > remaining_tricks) - guess = remaining_tricks; - int lowerbound = 0; - int upperbound = remaining_tricks; - - do - { - /* No per-iteration full reset here; preserve original behavior */ - - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = ab_search( - &thrp->lookAheadPos, - guess, - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + ctx.move_gen().reinit(trick, dl.first); + + // ini_depth == cardCount - 4 (see solve_board). Bound the null-window + // search by remaining tricks so partial deals cannot report scores > 13 + // leftovers from a full-hand upper bound. + const int card_count = ini_depth + 4; + const int remaining_tricks = (card_count % 4) + ? ((card_count - 4) >> 2) + 2 + : ((card_count - 4) >> 2) + 1; + + int guess = hint; + if (guess < 0) + guess = 0; + if (guess > remaining_tricks) + guess = remaining_tricks; + int lowerbound = 0; + int upperbound = remaining_tricks; + + do + { + /* No per-iteration full reset here; preserve original behavior */ + + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = ab_search( + &thrp->lookAheadPos, + guess, + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, guess, lowerbound, upperbound, 1); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, guess, lowerbound, upperbound, 1); #endif - if (thrp->val) - lowerbound = guess++; - else - upperbound = --guess; - } - while (lowerbound < upperbound); + if (thrp->val) + lowerbound = guess++; + else + upperbound = --guess; + } + while (lowerbound < upperbound); - futp->cards = 1; - futp->score[0] = lowerbound; + futp->cards = 1; + futp->score[0] = lowerbound; - thrp->memUsed = ctx.trans_table()->memory_in_use() + - ThreadMemoryUsed(); + thrp->memUsed = ctx.trans_table()->memory_in_use() + + ThreadMemoryUsed(); #ifdef DDS_TIMING - thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); + thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); #endif #ifdef DDS_TT_STATS - // These are for the large TT -- empty if not. - // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); - - { - ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); - } - - // These are for the small TT -- empty if not. - { - ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); - } + // These are for the large TT -- empty if not. + // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); + + { + ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); + } + + // These are for the small TT -- empty if not. + { + ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); + } #endif #ifdef DDS_MOVES - ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); #ifdef DDS_MOVES_DETAILS - ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); #endif - ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); #endif - { - futp->nodes = ctx.search().trick_nodes(); - } - - // Print TT stats if requested - if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { - ThreadData* thrp_ptr = ctx.thread_ptr(); - if (thrp_ptr) { - double hit_rate = thrp_ptr->tt_lookup_count > 0 ? - 100.0 * (double)thrp_ptr->tt_hit_count / - (double)thrp_ptr->tt_lookup_count : 0.0; - std::fprintf(stderr, + { + futp->nodes = ctx.search().trick_nodes(); + } + + // Print TT stats if requested + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { + ThreadData* thrp_ptr = ctx.thread_ptr(); + if (thrp_ptr) { + double hit_rate = thrp_ptr->tt_lookup_count > 0 ? + 100.0 * (double)thrp_ptr->tt_hit_count / + (double)thrp_ptr->tt_lookup_count : 0.0; + std::fprintf(stderr, "DDS_TT_STATS: lookups=%" PRIu64 " hits=%" PRIu64 " hit_rate=%.2f%%\n", thrp_ptr->tt_lookup_count, thrp_ptr->tt_hit_count, hit_rate); - if (auto* tt = ctx.trans_table()) { - int adds, overwrites, harvests; - tt->get_op_stats(adds, overwrites, harvests); - double ow_rate = adds > 0 ? - 100.0 * (double)overwrites / (double)adds : 0.0; - std::fprintf(stderr, + if (auto* tt = ctx.trans_table()) { + int adds, overwrites, harvests; + tt->get_op_stats(adds, overwrites, harvests); + double ow_rate = adds > 0 ? + 100.0 * (double)overwrites / (double)adds : 0.0; + std::fprintf(stderr, "DDS_TT_STATS: adds=%d overwrites=%d overwrite_rate=%.2f%% harvests=%d\n", adds, overwrites, ow_rate, harvests); - } + } + } } - } #ifdef DDS_MEMORY_LEAKS_WIN32 - _CrtDumpMemoryLeaks(); + _CrtDumpMemoryLeaks(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto analyse_later_board( - SolverContext& ctx, - const int leadHand, - MoveType const * move, - const int hint, - const int hintDir, - FutureTricks * futp) -> int + SolverContext& ctx, + const int leadHand, + MoveType const * move, + const int hint, + const int hintDir, + FutureTricks * futp) -> int { - // Specialized function for PlayAnalyser for cards after the - // opening lead. No further parameter checks! This function - // makes heavy reuse of parameters that are already stored in - // various places. It corresponds to: - // target == -1, solutions == 1, mode == 2. - // The function only needs to return fut.score[0]. - - // Reuse the caller's context (and its warm transposition table) instead of - // constructing a fresh one with a cold TT. The hint-bounded null-window - // search below relies on the TT state built up by the initial solve and the - // preceding cards; a cold TT yields wrong (under-counted) AnalysePlay - // results. This mirrors the calc_dd_table fix in commit 27030ba. - auto thrp = ctx.thread(); - int ini_depth = --ctx.search().ini_depth(); - int cardCount = ini_depth + 4; - int trick = (ini_depth + 3) >> 2; - int hand_rel_first = (48 - ini_depth) % 4; - { - ctx.search().trick_nodes() = 0; - } - { - ctx.search().analysis_flag() = true; - } - int handToPlay = HAND_ID(leadHand, hand_rel_first); + // Specialized function for PlayAnalyser for cards after the + // opening lead. No further parameter checks! This function + // makes heavy reuse of parameters that are already stored in + // various places. It corresponds to: + // target == -1, solutions == 1, mode == 2. + // The function only needs to return fut.score[0]. + + // Reuse the caller's context (and its warm transposition table) instead of + // constructing a fresh one with a cold TT. The hint-bounded null-window + // search below relies on the TT state built up by the initial solve and the + // preceding cards; a cold TT yields wrong (under-counted) AnalysePlay + // results. This mirrors the calc_dd_table fix in commit 27030ba. + auto thrp = ctx.thread(); + int ini_depth = --ctx.search().ini_depth(); + int cardCount = ini_depth + 4; + int trick = (ini_depth + 3) >> 2; + int hand_rel_first = (48 - ini_depth) % 4; + { + ctx.search().trick_nodes() = 0; + } + { + ctx.search().analysis_flag() = true; + } + int handToPlay = HAND_ID(leadHand, hand_rel_first); - { - if (handToPlay == 0 || handToPlay == 2) { - ctx.search().node_type_store(0) = MAXNODE; - ctx.search().node_type_store(1) = MINNODE; - ctx.search().node_type_store(2) = MAXNODE; - ctx.search().node_type_store(3) = MINNODE; + if (handToPlay == 0 || handToPlay == 2) + { + ctx.search().node_type_store(0) = MAXNODE; + ctx.search().node_type_store(1) = MINNODE; + ctx.search().node_type_store(2) = MAXNODE; + ctx.search().node_type_store(3) = MINNODE; + } + else + { + ctx.search().node_type_store(0) = MINNODE; + ctx.search().node_type_store(1) = MAXNODE; + ctx.search().node_type_store(2) = MINNODE; + ctx.search().node_type_store(3) = MAXNODE; + } + } + + if (hand_rel_first == 0) + { + ctx.move_gen().make_specific(* move, trick + 1, 3); + unsigned short int ourWinRanks[DDS_SUITS]; // Unused here + make_3(&thrp->lookAheadPos, ourWinRanks, ini_depth + 1, move, ctx); + } + else if (hand_rel_first == 1) + { + ctx.move_gen().make_specific(* move, trick, 0); + make_0(&thrp->lookAheadPos, ini_depth + 1, move); + } + else if (hand_rel_first == 2) + { + ctx.move_gen().make_specific(* move, trick, 1); + make_1(&thrp->lookAheadPos, ini_depth + 1, move); } else { - ctx.search().node_type_store(0) = MINNODE; - ctx.search().node_type_store(1) = MAXNODE; - ctx.search().node_type_store(2) = MINNODE; - ctx.search().node_type_store(3) = MAXNODE; - } - } - - if (hand_rel_first == 0) - { - ctx.move_gen().make_specific(* move, trick + 1, 3); - unsigned short int ourWinRanks[DDS_SUITS]; // Unused here - make_3(&thrp->lookAheadPos, ourWinRanks, ini_depth + 1, move, ctx); - } - else if (hand_rel_first == 1) - { - ctx.move_gen().make_specific(* move, trick, 0); - make_0(&thrp->lookAheadPos, ini_depth + 1, move); - } - else if (hand_rel_first == 2) - { - ctx.move_gen().make_specific(* move, trick, 1); - make_1(&thrp->lookAheadPos, ini_depth + 1, move); - } - else - { - ctx.move_gen().make_specific(* move, trick, 2); - make_2(&thrp->lookAheadPos, ini_depth + 1, move); - } - - if (cardCount <= 4) - { - // Last trick. - EvalType eval = evaluate_with_context(&thrp->lookAheadPos, thrp->trump, ctx); - futp->score[0] = eval.tricks; - futp->nodes = 0; + ctx.move_gen().make_specific(* move, trick, 2); + make_2(&thrp->lookAheadPos, ini_depth + 1, move); + } - return RETURN_NO_FAULT; - } + if (cardCount <= 4) + { + // Last trick. + EvalType eval = evaluate_with_context(&thrp->lookAheadPos, thrp->trump, ctx); + futp->score[0] = eval.tricks; + futp->nodes = 0; + + return RETURN_NO_FAULT; + } #ifdef DDS_AB_STATS - thrp->ABStats.Reset(); - thrp->ABStats.ResetCum(); + thrp->ABStats.Reset(); + thrp->ABStats.ResetCum(); #endif #ifdef DDS_TOP_LEVEL - { - ctx.search().nodes() = 0; - } + { + ctx.search().nodes() = 0; + } #endif - int guess = hint, - lowerbound, - upperbound; - - if (hintDir == 0) - { - lowerbound = hint; - upperbound = 13; - } - else - { - lowerbound = 0; - upperbound = hint; - } - - do - { - ctx.reset_best_moves_lite(); - - TIMER_START(TIMER_NO_AB, ini_depth); - thrp->val = (* AB_ptr_trace_list[hand_rel_first])( - &thrp->lookAheadPos, - guess, - ini_depth, - ctx); - TIMER_END(TIMER_NO_AB, ini_depth); + int guess = hint, + lowerbound, + upperbound; + + if (hintDir == 0) + { + lowerbound = hint; + upperbound = 13; + } + else + { + lowerbound = 0; + upperbound = hint; + } + + do + { + ctx.reset_best_moves_lite(); + + TIMER_START(TIMER_NO_AB, ini_depth); + thrp->val = (* AB_ptr_trace_list[hand_rel_first])( + &thrp->lookAheadPos, + guess, + ini_depth, + ctx); + TIMER_END(TIMER_NO_AB, ini_depth); #ifdef DDS_TOP_LEVEL - DumpTopLevel(thrp->fileTopLevel.GetStream(), - thrp, guess, lowerbound, upperbound, 1); + DumpTopLevel(thrp->fileTopLevel.GetStream(), + thrp, guess, lowerbound, upperbound, 1); #endif - if (thrp->val) - lowerbound = guess++; - else - upperbound = --guess; + if (thrp->val) + lowerbound = guess++; + else + upperbound = --guess; - } - while (lowerbound < upperbound); + } + while (lowerbound < upperbound); + + futp->score[0] = lowerbound; + { + futp->nodes = ctx.search().trick_nodes(); + } - futp->score[0] = lowerbound; - { - futp->nodes = ctx.search().trick_nodes(); - } - - thrp->memUsed = ctx.trans_table()->memory_in_use() + - ThreadMemoryUsed(); + thrp->memUsed = ctx.trans_table()->memory_in_use() + + ThreadMemoryUsed(); #ifdef DDS_TIMING - thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); + thrp->timerList.PrintStats(thrp->fileTimerList.GetStream()); #endif #ifdef DDS_TT_STATS - // These are for the large TT -- empty if not. - // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); - // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); - - { - ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); - } - - // These are for the small TT -- empty if not. - { - ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); - ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); - } + // These are for the large TT -- empty if not. + // thrp->transTable->PrintAllSuits(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllSuitStats(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntries(thrp->fileTTstats.GetStream()); + // thrp->transTable->PrintAllEntryStats(thrp->fileTTstats.GetStream()); + + { + ctx.trans_table()->print_summary_suit_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_summary_entry_stats(thrp->fileTTstats.GetStream()); + } + + // These are for the small TT -- empty if not. + { + ctx.trans_table()->print_node_stats(thrp->fileTTstats.GetStream()); + ctx.trans_table()->print_reset_stats(thrp->fileTTstats.GetStream()); + } #endif // Diagnostics are routed via the SolverContext MoveGen facade. #ifdef DDS_MOVES - ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_stats(thrp->fileMoves.GetStream()); #ifdef DDS_MOVES_DETAILS - ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); + ctx.move_gen().print_trick_details(thrp->fileMoves.GetStream()); #endif - ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); + ctx.move_gen().print_function_stats(thrp->fileMoves.GetStream()); #endif #ifdef DDS_MEMORY_LEAKS_WIN32 - _CrtDumpMemoryLeaks(); + _CrtDumpMemoryLeaks(); #endif - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto board_range_checks( - const Deal& dl, - const int target, - const int solutions, - const int mode) -> int + const Deal& dl, + const int target, + const int solutions, + const int mode) -> int { - if (target < -1) - { - DumpInput(RETURN_TARGET_WRONG_LO, dl, target, solutions, mode); - return RETURN_TARGET_WRONG_LO; - } - - if (target > 13) - { - DumpInput(RETURN_TARGET_WRONG_HI, dl, target, solutions, mode); - return RETURN_TARGET_WRONG_HI; - } - - if (solutions < 1) - { - DumpInput(RETURN_SOLNS_WRONG_LO, dl, target, solutions, mode); - return RETURN_SOLNS_WRONG_LO; - } - - if (solutions > 3) - { - DumpInput(RETURN_SOLNS_WRONG_HI, dl, target, solutions, mode); - return RETURN_SOLNS_WRONG_HI; - } - - if (mode < 0) - { - DumpInput(RETURN_MODE_WRONG_LO, dl, target, solutions, mode); - return RETURN_MODE_WRONG_LO; - } - - if (mode > 2) - { - DumpInput(RETURN_MODE_WRONG_HI, dl, target, solutions, mode); - return RETURN_MODE_WRONG_HI; - } - - if (dl.trump < 0 || dl.trump > 4) - { - DumpInput(RETURN_TRUMP_WRONG, dl, target, solutions, mode); - return RETURN_TRUMP_WRONG; - } - - if (dl.first < 0 || dl.first > 3) - { - DumpInput(RETURN_FIRST_WRONG, dl, target, solutions, mode); - return RETURN_FIRST_WRONG; - } - - int rankSeen[3] = {0, 0, 0}; - for (int k = 0; k < 3; k++) - { - int r = dl.currentTrickRank[k]; - if (r == 0) - continue; - - rankSeen[k] = 1; - - if (r < 2 || r > 14) - { - DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); - return RETURN_SUIT_OR_RANK; - } - - if (dl.currentTrickSuit[k] < 0 || dl.currentTrickSuit[k] > 3) - { - DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); - return RETURN_SUIT_OR_RANK; - } - } - - if ((rankSeen[2] && (! rankSeen[1] || ! rankSeen[0])) || - (rankSeen[1] && ! rankSeen[0])) - { - DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); - return RETURN_SUIT_OR_RANK; - } - - for (int h = 0; h < DDS_HANDS; h++) - { - for (int s = 0; s < DDS_SUITS; s++) + if (target < -1) + { + DumpInput(RETURN_TARGET_WRONG_LO, dl, target, solutions, mode); + return RETURN_TARGET_WRONG_LO; + } + + if (target > 13) + { + DumpInput(RETURN_TARGET_WRONG_HI, dl, target, solutions, mode); + return RETURN_TARGET_WRONG_HI; + } + + if (solutions < 1) + { + DumpInput(RETURN_SOLNS_WRONG_LO, dl, target, solutions, mode); + return RETURN_SOLNS_WRONG_LO; + } + + if (solutions > 3) + { + DumpInput(RETURN_SOLNS_WRONG_HI, dl, target, solutions, mode); + return RETURN_SOLNS_WRONG_HI; + } + + if (mode < 0) + { + DumpInput(RETURN_MODE_WRONG_LO, dl, target, solutions, mode); + return RETURN_MODE_WRONG_LO; + } + + if (mode > 2) + { + DumpInput(RETURN_MODE_WRONG_HI, dl, target, solutions, mode); + return RETURN_MODE_WRONG_HI; + } + + if (dl.trump < 0 || dl.trump > 4) + { + DumpInput(RETURN_TRUMP_WRONG, dl, target, solutions, mode); + return RETURN_TRUMP_WRONG; + } + + if (dl.first < 0 || dl.first > 3) + { + DumpInput(RETURN_FIRST_WRONG, dl, target, solutions, mode); + return RETURN_FIRST_WRONG; + } + + int rankSeen[3] = {0, 0, 0}; + for (int k = 0; k < 3; k++) + { + int r = dl.currentTrickRank[k]; + if (r == 0) + continue; + + rankSeen[k] = 1; + + if (r < 2 || r > 14) + { + DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); + return RETURN_SUIT_OR_RANK; + } + + if (dl.currentTrickSuit[k] < 0 || dl.currentTrickSuit[k] > 3) + { + DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); + return RETURN_SUIT_OR_RANK; + } + } + + if ((rankSeen[2] && (! rankSeen[1] || ! rankSeen[0])) || + (rankSeen[1] && ! rankSeen[0])) { - unsigned c = dl.remainCards[h][s]; - if (c != 0 && (c < 0x0004 || c >= 0x8000)) - { DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); return RETURN_SUIT_OR_RANK; - } } - } - return RETURN_NO_FAULT; + for (int h = 0; h < DDS_HANDS; h++) + { + for (int s = 0; s < DDS_SUITS; s++) + { + unsigned c = dl.remainCards[h][s]; + if (c != 0 && (c < 0x0004 || c >= 0x8000)) + { + DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); + return RETURN_SUIT_OR_RANK; + } + } + } + + return RETURN_NO_FAULT; } auto board_value_checks( - SolverContext& ctx, - const Deal& dl, - const int target, - const int solutions, - const int mode) -> int + SolverContext& ctx, + const Deal& dl, + const int target, + const int solutions, + const int mode) -> int { - auto thrp = ctx.thread(); - int cardCount = ctx.search().ini_depth() + 4; - if (cardCount <= 0) - { - DumpInput(RETURN_ZERO_CARDS, dl, target, solutions, mode); - return RETURN_ZERO_CARDS; - } - - if (cardCount > 52) - { - DumpInput(RETURN_TOO_MANY_CARDS, dl, target, solutions, mode); - return RETURN_TOO_MANY_CARDS; - } - - int totalTricks; - if (cardCount % 4) - totalTricks = ((cardCount - 4) >> 2) + 2; - else - totalTricks = ((cardCount - 4) >> 2) + 1; - - if (totalTricks < target) - { - DumpInput(RETURN_TARGET_TOO_HIGH, dl, target, solutions, mode); - return RETURN_TARGET_TOO_HIGH; - } - - int hand_rel_first = thrp->lookAheadPos.hand_rel_first; - - int noOfCardsPerHand[DDS_HANDS] = {0, 0, 0, 0}; - for (int k = 0; k < hand_rel_first; k++) - noOfCardsPerHand[HAND_ID(dl.first, k)] = 1; - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - noOfCardsPerHand[h] += count_table[thrp->suit[h][s]]; + auto thrp = ctx.thread(); + int cardCount = ctx.search().ini_depth() + 4; + if (cardCount <= 0) + { + DumpInput(RETURN_ZERO_CARDS, dl, target, solutions, mode); + return RETURN_ZERO_CARDS; + } - for (int h = 1; h < DDS_HANDS; h++) - { - if (noOfCardsPerHand[h] != noOfCardsPerHand[0]) + if (cardCount > 52) { - DumpInput(RETURN_CARD_COUNT, dl, target, solutions, mode); - return RETURN_CARD_COUNT; + DumpInput(RETURN_TOO_MANY_CARDS, dl, target, solutions, mode); + return RETURN_TOO_MANY_CARDS; } - } - for (int k = 0; k < hand_rel_first; k++) - { - /* board_range_checks() only validates currentTrickSuit[k] when the - matching rank is non-zero, but hand_rel_first is derived from the card - count rather than from the trick entries, so this loop can reach an - entry whose suit was never checked and index remainCards out of - bounds. Validate it here, where it is actually used as a subscript. */ - if (dl.currentTrickSuit[k] < 0 || dl.currentTrickSuit[k] >= DDS_SUITS) + int totalTricks; + if (cardCount % 4) + totalTricks = ((cardCount - 4) >> 2) + 2; + else + totalTricks = ((cardCount - 4) >> 2) + 1; + + if (totalTricks < target) { - DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); - return RETURN_SUIT_OR_RANK; + DumpInput(RETURN_TARGET_TOO_HIGH, dl, target, solutions, mode); + return RETURN_TARGET_TOO_HIGH; } - unsigned short int aggrRemain = 0; + int hand_rel_first = thrp->lookAheadPos.hand_rel_first; + + int noOfCardsPerHand[DDS_HANDS] = {0, 0, 0, 0}; + for (int k = 0; k < hand_rel_first; k++) + noOfCardsPerHand[HAND_ID(dl.first, k)] = 1; + for (int h = 0; h < DDS_HANDS; h++) - aggrRemain |= (dl.remainCards[h][dl.currentTrickSuit[k]] >> 2); + for (int s = 0; s < DDS_SUITS; s++) + noOfCardsPerHand[h] += count_table[thrp->suit[h][s]]; + + for (int h = 1; h < DDS_HANDS; h++) + { + if (noOfCardsPerHand[h] != noOfCardsPerHand[0]) + { + DumpInput(RETURN_CARD_COUNT, dl, target, solutions, mode); + return RETURN_CARD_COUNT; + } + } - if ((aggrRemain & bit_map_rank[dl.currentTrickRank[k]]) != 0) + for (int k = 0; k < hand_rel_first; k++) { - DumpInput(RETURN_PLAYED_CARD, dl, target, solutions, mode); - return RETURN_PLAYED_CARD; + /* board_range_checks() only validates currentTrickSuit[k] when the + matching rank is non-zero, but hand_rel_first is derived from the card + count rather than from the trick entries, so this loop can reach an + entry whose suit was never checked and index remainCards out of + bounds. Validate it here, where it is actually used as a subscript. */ + if (dl.currentTrickSuit[k] < 0 || dl.currentTrickSuit[k] >= DDS_SUITS) + { + DumpInput(RETURN_SUIT_OR_RANK, dl, target, solutions, mode); + return RETURN_SUIT_OR_RANK; + } + + unsigned short int aggrRemain = 0; + for (int h = 0; h < DDS_HANDS; h++) + aggrRemain |= (dl.remainCards[h][dl.currentTrickSuit[k]] >> 2); + + if ((aggrRemain & bit_map_rank[dl.currentTrickRank[k]]) != 0) + { + DumpInput(RETURN_PLAYED_CARD, dl, target, solutions, mode); + return RETURN_PLAYED_CARD; + } } - } - for (int s = 0; s < DDS_SUITS; s++) - { - for (int r = 2; r <= 14; r++) + for (int s = 0; s < DDS_SUITS; s++) { - bool found = false; - for (int h = 0; h < DDS_HANDS; h++) - { - if ((thrp->suit[h][s] & bit_map_rank[r]) != 0) + for (int r = 2; r <= 14; r++) { - if (found) - { - DumpInput(RETURN_DUPLICATE_CARDS, dl, - target, solutions, mode); - return RETURN_DUPLICATE_CARDS; - } - else - found = true; + bool found = false; + for (int h = 0; h < DDS_HANDS; h++) + { + if ((thrp->suit[h][s] & bit_map_rank[r]) != 0) + { + if (found) + { + DumpInput(RETURN_DUPLICATE_CARDS, dl, + target, solutions, mode); + return RETURN_DUPLICATE_CARDS; + } + else + found = true; + } + } } - } } - } - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } auto last_trick_winner( - const Deal& dl, - const std::shared_ptr& thrp, - const int handToPlay, - const int hand_rel_first, - int& leadRank, - int& leadSuit, - int& leadSideWins) -> void + const Deal& dl, + const std::shared_ptr& thrp, + const int handToPlay, + const int hand_rel_first, + int& leadRank, + int& leadSuit, + int& leadSideWins) -> void { - int lastTrickSuit[DDS_HANDS], - lastTrickRank[DDS_HANDS], - h, - hp; - - for (h = 0; h < hand_rel_first; h++) - { - hp = HAND_ID(dl.first, h); - lastTrickSuit[hp] = dl.currentTrickSuit[h]; - lastTrickRank[hp] = dl.currentTrickRank[h]; - } - - for (h = hand_rel_first; h < DDS_HANDS; h++) - { - hp = HAND_ID(dl.first, h); - for (int s = 0; s < DDS_SUITS; s++) + int lastTrickSuit[DDS_HANDS], + lastTrickRank[DDS_HANDS], + h, + hp; + + for (h = 0; h < hand_rel_first; h++) { - if (thrp->suit[hp][s] != 0) - { - lastTrickSuit[hp] = s; - lastTrickRank[hp] = highest_rank[thrp->suit[hp][s]]; - break; - } - } - } - - int maxRank = 0, - maxSuit, - maxHand = -1; - - /* Highest trump? */ - if (dl.trump != DDS_NOTRUMP) - { - for (h = 0; h < DDS_HANDS; h++) - { - if ((lastTrickSuit[h] == dl.trump) && - (lastTrickRank[h] > maxRank)) - { - maxRank = lastTrickRank[h]; - maxSuit = dl.trump; - maxHand = h; - } - } - } - - /* Highest card in leading suit */ - if (maxRank == 0) - { - maxRank = lastTrickRank[dl.first]; - maxSuit = lastTrickSuit[dl.first]; - maxHand = dl.first; - - for (h = 0; h < DDS_HANDS; h++) - { - if (lastTrickSuit[h] == maxSuit && - lastTrickRank[h] > maxRank) - { - maxHand = h; - maxRank = lastTrickRank[h]; - } - } - } - - hp = HAND_ID(dl.first, hand_rel_first); - leadRank = lastTrickRank[hp]; - leadSuit = lastTrickSuit[hp]; - leadSideWins = ((handToPlay == maxHand || - partner[handToPlay] == maxHand) ? 1 : 0); + hp = HAND_ID(dl.first, h); + lastTrickSuit[hp] = dl.currentTrickSuit[h]; + lastTrickRank[hp] = dl.currentTrickRank[h]; + } + + for (h = hand_rel_first; h < DDS_HANDS; h++) + { + hp = HAND_ID(dl.first, h); + for (int s = 0; s < DDS_SUITS; s++) + { + if (thrp->suit[hp][s] != 0) + { + lastTrickSuit[hp] = s; + lastTrickRank[hp] = highest_rank[thrp->suit[hp][s]]; + break; + } + } + } + + int maxRank = 0, + maxSuit, + maxHand = -1; + + /* Highest trump? */ + if (dl.trump != DDS_NOTRUMP) + { + for (h = 0; h < DDS_HANDS; h++) + { + if ((lastTrickSuit[h] == dl.trump) && + (lastTrickRank[h] > maxRank)) + { + maxRank = lastTrickRank[h]; + maxSuit = dl.trump; + maxHand = h; + } + } + } + + /* Highest card in leading suit */ + if (maxRank == 0) + { + maxRank = lastTrickRank[dl.first]; + maxSuit = lastTrickSuit[dl.first]; + maxHand = dl.first; + + for (h = 0; h < DDS_HANDS; h++) + { + if (lastTrickSuit[h] == maxSuit && + lastTrickRank[h] > maxRank) + { + maxHand = h; + maxRank = lastTrickRank[h]; + } + } + } + + hp = HAND_ID(dl.first, hand_rel_first); + leadRank = lastTrickRank[hp]; + leadSuit = lastTrickSuit[hp]; + leadSideWins = ((handToPlay == maxHand || + partner[handToPlay] == maxHand) ? 1 : 0); } diff --git a/library/src/solver_if.hpp b/library/src/solver_if.hpp index e5b6fcb0f..79356ae65 100644 --- a/library/src/solver_if.hpp +++ b/library/src/solver_if.hpp @@ -14,23 +14,23 @@ #include auto solve_board_internal( - SolverContext& ctx, - const Deal& dl, - const int target, - const int solutions, - const int mode, - FutureTricks * futp) -> int; + SolverContext& ctx, + const Deal& dl, + const int target, + const int solutions, + const int mode, + FutureTricks * futp) -> int; auto solve_same_board( - SolverContext& ctx, - const Deal& dl, - FutureTricks * futp, - const int hint) -> int; + SolverContext& ctx, + const Deal& dl, + FutureTricks * futp, + const int hint) -> int; auto analyse_later_board( - SolverContext& ctx, - const int leadHand, - MoveType const * move, - const int hint, - const int hintDir, - FutureTricks * futp) -> int; + SolverContext& ctx, + const int leadHand, + MoveType const * move, + const int hint, + const int hintDir, + FutureTricks * futp) -> int; diff --git a/library/src/system/deal_fanout.cpp b/library/src/system/deal_fanout.cpp index 79ede2897..20bc75220 100644 --- a/library/src/system/deal_fanout.cpp +++ b/library/src/system/deal_fanout.cpp @@ -25,37 +25,37 @@ std::atomic g_deal_fanout_call_count{0}; auto deal_fanout_call_count() -> int { - return g_deal_fanout_call_count.load(std::memory_order_relaxed); + return g_deal_fanout_call_count.load(std::memory_order_relaxed); } auto deal_fanout(const Deal& dl) -> int { - g_deal_fanout_call_count.fetch_add(1, std::memory_order_relaxed); + g_deal_fanout_call_count.fetch_add(1, std::memory_order_relaxed); - // The fanout for a given suit and a given player is the number - // of bit groups, so KT982 has 3 groups. In a given suit the - // maximum number over all four players is 13. - // A void counts as the sum of the other players' groups. + // The fanout for a given suit and a given player is the number + // of bit groups, so KT982 has 3 groups. In a given suit the + // maximum number over all four players is 13. + // A void counts as the sum of the other players' groups. - int fanout = 0; - int fanout_suit, num_voids, c; + int fanout = 0; + int fanout_suit, num_voids, c; - for (int h = 0; h < DDS_HANDS; h++) - { - fanout_suit = 0; - num_voids = 0; - for (int s = 0; s < DDS_SUITS; s++) + for (int h = 0; h < DDS_HANDS; h++) { - c = static_cast(dl.remainCards[h][s] >> 2); - fanout_suit += group_data[c].last_group_ + 1; - if (c == 0) - num_voids++; + fanout_suit = 0; + num_voids = 0; + for (int s = 0; s < DDS_SUITS; s++) + { + c = static_cast(dl.remainCards[h][s] >> 2); + fanout_suit += group_data[c].last_group_ + 1; + if (c == 0) + num_voids++; + } + fanout_suit += num_voids * fanout_suit; + fanout += fanout_suit; } - fanout_suit += num_voids * fanout_suit; - fanout += fanout_suit; - } - return fanout; + return fanout; } } // namespace internal diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index 98d6b1172..b8122a3d5 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -12,34 +12,34 @@ dds::File::~File() { - Close(); + Close(); } void dds::File::Reset() { - Close(); - fname_.clear(); + Close(); + fname_.clear(); } void dds::File::SetName(const std::string& fname_in) { - if (fname_in == fname_) - return; + if (fname_in == fname_) + return; - Close(); - fname_ = fname_in; + Close(); + fname_ = fname_in; } std::ofstream& dds::File::GetStream() { - if (!fout_.is_open() && !fname_.empty()) - fout_.open(fname_); + if (!fout_.is_open() && !fname_.empty()) + fout_.open(fname_); - return fout_; + return fout_; } void dds::File::Close() { - if (fout_.is_open()) - fout_.close(); + if (fout_.is_open()) + fout_.close(); } diff --git a/library/src/system/file.hpp b/library/src/system/file.hpp index 9e32be83c..456e8ca3b 100644 --- a/library/src/system/file.hpp +++ b/library/src/system/file.hpp @@ -19,12 +19,12 @@ namespace dds { */ class File { - private: + private: std::string fname_; std::ofstream fout_; - public: + public: File() = default; diff --git a/library/src/system/memory.cpp b/library/src/system/memory.cpp index 206b9310c..c603c6b0a 100644 --- a/library/src/system/memory.cpp +++ b/library/src/system/memory.cpp @@ -23,52 +23,52 @@ Memory::Memory() Memory::~Memory() { - // Clear configured thread sizes - Resize(0, DDS_TT_SMALL, 0, 0); + // Clear configured thread sizes + Resize(0, DDS_TT_SMALL, 0, 0); } void Memory::ReturnThread(const unsigned /*thrId*/) { - // No-op: ThreadData is now owned by SolverContext instances. Returning - // per-thread TT memory requires a SolverContext with the appropriate - // ThreadData pointer; call sites should perform that via their - // SolverContext instances. + // No-op: ThreadData is now owned by SolverContext instances. Returning + // per-thread TT memory requires a SolverContext with the appropriate + // ThreadData pointer; call sites should perform that via their + // SolverContext instances. } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void Memory::Resize( - const unsigned n, - const TTmemory flag, - const int /*memDefault_MB*/, - const int /*memMaximum_MB*/) // NOLINT(bugprone-easily-swappable-parameters) + const unsigned n, + const TTmemory flag, + const int /*memDefault_MB*/, + const int /*memMaximum_MB*/) // NOLINT(bugprone-easily-swappable-parameters) { - // Resize the lightweight thread size vector. Each entry is a short - // diagnostic token: "S" = small TT, "L" = large TT. - threadSizes.resize(n); - for (unsigned i = 0; i < n; ++i) - threadSizes[i] = (flag == DDS_TT_SMALL ? "S" : "L"); + // Resize the lightweight thread size vector. Each entry is a short + // diagnostic token: "S" = small TT, "L" = large TT. + threadSizes.resize(n); + for (unsigned i = 0; i < n; ++i) + threadSizes[i] = (flag == DDS_TT_SMALL ? "S" : "L"); } unsigned Memory::NumThreads() const { - return static_cast(threadSizes.size()); + return static_cast(threadSizes.size()); } double Memory::MemoryInUseMB(const unsigned /*thrId*/) const { - // We can only account for the static RelRanksType footprint here. Any - // transposition table memory is owned by SolverContext/transposition - // table instances and must be queried via those contexts when available. - return 8192. * sizeof(RelRanksType) / static_cast(1024.); + // We can only account for the static RelRanksType footprint here. Any + // transposition table memory is owned by SolverContext/transposition + // table instances and must be queried via those contexts when available. + return 8192. * sizeof(RelRanksType) / static_cast(1024.); } std::string Memory::ThreadSize(const unsigned thrId) const { - if (thrId >= threadSizes.size()) return std::string(); - return threadSizes[thrId]; + if (thrId >= threadSizes.size()) return std::string(); + return threadSizes[thrId]; } diff --git a/library/src/system/memory.hpp b/library/src/system/memory.hpp index ff8112f31..f9d935fea 100644 --- a/library/src/system/memory.hpp +++ b/library/src/system/memory.hpp @@ -18,11 +18,11 @@ #ifdef DDS_AB_STATS - #include "ab_stats.hpp" + #include "ab_stats.hpp" #endif #ifdef DDS_TIMING - #include + #include #endif @@ -36,45 +36,45 @@ */ class Memory { - private: - // Per static_memory_genmove.md step one: ThreadData instances are no - // longer stored in a central Memory::memory vector. ThreadData will be - // owned/allocated by the SolverContext (as a member) and passed down to - // call sites. This header therefore no longer keeps per-thread pointers. + private: + // Per static_memory_genmove.md step one: ThreadData instances are no + // longer stored in a central Memory::memory vector. ThreadData will be + // owned/allocated by the SolverContext (as a member) and passed down to + // call sites. This header therefore no longer keeps per-thread pointers. - // Keep a lightweight record of configured thread sizes for diagnostics - // and reporting. This replaces the previous `memory` vector which held - // full ThreadData instances. - std::vector threadSizes; + // Keep a lightweight record of configured thread sizes for diagnostics + // and reporting. This replaces the previous `memory` vector which held + // full ThreadData instances. + std::vector threadSizes; - public: + public: - /** + /** * @brief Construct a new Memory object. * * Initializes thread-local memory tracking and prepares for allocation. */ - Memory(); + Memory(); - /** + /** * @brief Destroy the Memory object and clean up resources. * * Releases all memory and performs cleanup of thread-local resources. */ - ~Memory(); + ~Memory(); - void ReturnThread(const unsigned thrId); + void ReturnThread(const unsigned thrId); - void Resize( - const unsigned n, - const TTmemory flag, - const int memDefault_MB, - const int memMaximum_MB); // NOLINT(bugprone-easily-swappable-parameters) + void Resize( + const unsigned n, + const TTmemory flag, + const int memDefault_MB, + const int memMaximum_MB); // NOLINT(bugprone-easily-swappable-parameters) - unsigned NumThreads() const; - double MemoryInUseMB(const unsigned thrId) const; + unsigned NumThreads() const; + double MemoryInUseMB(const unsigned thrId) const; - std::string ThreadSize(const unsigned thrId) const; + std::string ThreadSize(const unsigned thrId) const; }; #endif diff --git a/library/src/system/parallel_boards.cpp b/library/src/system/parallel_boards.cpp index 7ab1c68ed..f87bca5d2 100644 --- a/library/src/system/parallel_boards.cpp +++ b/library/src/system/parallel_boards.cpp @@ -34,263 +34,263 @@ std::atomic g_last_job_board_count{0}; class BoardWorkerPool { public: - BoardWorkerPool() = default; + BoardWorkerPool() = default; - ~BoardWorkerPool() - { + ~BoardWorkerPool() { - std::lock_guard lock(mu_); - stopping_ = true; - ++generation_; - job_ = nullptr; - workers_for_job_ = 0; - } - cv_start_.notify_all(); - for (auto& th : threads_) - { - if (th.joinable()) - th.join(); + { + std::lock_guard lock(mu_); + stopping_ = true; + ++generation_; + job_ = nullptr; + workers_for_job_ = 0; + } + cv_start_.notify_all(); + for (auto& th : threads_) + { + if (th.joinable()) + th.join(); + } } - } - BoardWorkerPool(const BoardWorkerPool&) = delete; - auto operator=(const BoardWorkerPool&) -> BoardWorkerPool& = delete; - - auto run( - const int workers, - const int count, - const std::function& process_board, - const bool use_order, - const std::vector* order) -> int - { - // The pool tracks exactly one outstanding job (job_, workers_for_job_, - // generation_). Serialize whole runs so a second concurrent caller queues - // up instead of overwriting the first caller's job, which would leave the - // first run waiting forever for workers that never saw its job. - std::lock_guard run_lock(run_mu_); - - ensure_workers(workers); - - std::atomic next{0}; - std::atomic first_error{RETURN_NO_FAULT}; - // finished is protected by mu_: every worker increments it under that lock - // so the unlock→lock handoff through cv_done_ happens-before the caller's - // continued use of process_board side effects (e.g. result buffers). - int finished{0}; - - Job job{ - &process_board, - order, - &next, - &first_error, - &finished, - count, - workers, - use_order}; + BoardWorkerPool(const BoardWorkerPool&) = delete; + auto operator=(const BoardWorkerPool&) -> BoardWorkerPool& = delete; + auto run( + const int workers, + const int count, + const std::function& process_board, + const bool use_order, + const std::vector* order) -> int { - std::lock_guard lock(mu_); - job_ = &job; - workers_for_job_ = workers; - ++generation_; - } - cv_start_.notify_all(); + // The pool tracks exactly one outstanding job (job_, workers_for_job_, + // generation_). Serialize whole runs so a second concurrent caller queues + // up instead of overwriting the first caller's job, which would leave the + // first run waiting forever for workers that never saw its job. + std::lock_guard run_lock(run_mu_); + + ensure_workers(workers); + + std::atomic next{0}; + std::atomic first_error{RETURN_NO_FAULT}; + // finished is protected by mu_: every worker increments it under that lock + // so the unlock→lock handoff through cv_done_ happens-before the caller's + // continued use of process_board side effects (e.g. result buffers). + int finished{0}; + + Job job{ + &process_board, + order, + &next, + &first_error, + &finished, + count, + workers, + use_order}; - { - std::unique_lock lock(mu_); - cv_done_.wait(lock, [&] { return finished >= workers; }); - job_ = nullptr; - } + { + std::lock_guard lock(mu_); + job_ = &job; + workers_for_job_ = workers; + ++generation_; + } + cv_start_.notify_all(); + + { + std::unique_lock lock(mu_); + cv_done_.wait(lock, [&] { return finished >= workers; }); + job_ = nullptr; + } - const int err = first_error.load(std::memory_order_relaxed); - return err != RETURN_NO_FAULT ? err : RETURN_NO_FAULT; - } + const int err = first_error.load(std::memory_order_relaxed); + return err != RETURN_NO_FAULT ? err : RETURN_NO_FAULT; + } private: - struct Job - { - const std::function* process_board = nullptr; - const std::vector* order = nullptr; - std::atomic* next = nullptr; - std::atomic* first_error = nullptr; - int* finished = nullptr; - int count = 0; - int workers = 0; - bool use_order = false; - }; - - void ensure_workers(const int workers) - { - std::lock_guard lock(mu_); - while (static_cast(threads_.size()) < workers) + struct Job + { + const std::function* process_board = nullptr; + const std::vector* order = nullptr; + std::atomic* next = nullptr; + std::atomic* first_error = nullptr; + int* finished = nullptr; + int count = 0; + int workers = 0; + bool use_order = false; + }; + + void ensure_workers(const int workers) { - const int worker_id = static_cast(threads_.size()); - threads_.emplace_back([this, worker_id] { worker_main(worker_id); }); - g_threads_created.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(mu_); + while (static_cast(threads_.size()) < workers) + { + const int worker_id = static_cast(threads_.size()); + threads_.emplace_back([this, worker_id] { worker_main(worker_id); }); + g_threads_created.fetch_add(1, std::memory_order_relaxed); + } } - } - void worker_main(const int worker_id) - { - std::uint64_t seen_generation = 0; - for (;;) + void worker_main(const int worker_id) { - Job local{}; - { - std::unique_lock lock(mu_); - cv_start_.wait(lock, [&] { - return stopping_ || - (job_ != nullptr && - generation_ != seen_generation && - worker_id < workers_for_job_); - }); - if (stopping_) - return; - seen_generation = generation_; - local = *job_; - } - - // Workers with id >= local.workers for this job still wake on notify_all; - // only the selected prefix participates and signals completion. - if (worker_id < local.workers) - { - try + std::uint64_t seen_generation = 0; + for (;;) { - for (;;) - { - const int slot = local.next->fetch_add(1, std::memory_order_relaxed); - if (slot >= local.count || - local.first_error->load(std::memory_order_relaxed) != RETURN_NO_FAULT) + Job local{}; { - break; + std::unique_lock lock(mu_); + cv_start_.wait(lock, [&] { + return stopping_ || + (job_ != nullptr && + generation_ != seen_generation && + worker_id < workers_for_job_); + }); + if (stopping_) + return; + seen_generation = generation_; + local = *job_; } - const int bno = - local.use_order - ? (*local.order)[static_cast(slot)] - : slot; - const int rc = (*local.process_board)(worker_id, bno); - if (rc != RETURN_NO_FAULT) + + // Workers with id >= local.workers for this job still wake on notify_all; + // only the selected prefix participates and signals completion. + if (worker_id < local.workers) { - int expected = RETURN_NO_FAULT; - local.first_error->compare_exchange_strong( - expected, rc, std::memory_order_relaxed); - break; + try + { + for (;;) + { + const int slot = local.next->fetch_add(1, std::memory_order_relaxed); + if (slot >= local.count || + local.first_error->load(std::memory_order_relaxed) != RETURN_NO_FAULT) + { + break; + } + const int bno = + local.use_order + ? (*local.order)[static_cast(slot)] + : slot; + const int rc = (*local.process_board)(worker_id, bno); + if (rc != RETURN_NO_FAULT) + { + int expected = RETURN_NO_FAULT; + local.first_error->compare_exchange_strong( + expected, rc, std::memory_order_relaxed); + break; + } + } + } + catch (...) + { + // process_board must not leave the pool hanging or abort the process + // via std::terminate. Any throw maps the run to RETURN_UNKNOWN_FAULT + // (even if another worker already recorded a non-success return code), + // then fall through to finished accounting so cv_done_ is signaled. + local.first_error->store( + RETURN_UNKNOWN_FAULT, std::memory_order_relaxed); + } + // Account completion under mu_ so (1) the predicate change cannot race + // with cv_done_.wait's check-and-sleep (lost wakeup) and (2) unlocking + // mu_ after process_board writes publishes those writes to the caller + // when wait reacquires the mutex. + bool notify = false; + { + std::lock_guard lock(mu_); + notify = ++(*local.finished) >= local.workers; + } + if (notify) + cv_done_.notify_one(); } - } } - catch (...) - { - // process_board must not leave the pool hanging or abort the process - // via std::terminate. Any throw maps the run to RETURN_UNKNOWN_FAULT - // (even if another worker already recorded a non-success return code), - // then fall through to finished accounting so cv_done_ is signaled. - local.first_error->store( - RETURN_UNKNOWN_FAULT, std::memory_order_relaxed); - } - // Account completion under mu_ so (1) the predicate change cannot race - // with cv_done_.wait's check-and-sleep (lost wakeup) and (2) unlocking - // mu_ after process_board writes publishes those writes to the caller - // when wait reacquires the mutex. - bool notify = false; - { - std::lock_guard lock(mu_); - notify = ++(*local.finished) >= local.workers; - } - if (notify) - cv_done_.notify_one(); - } } - } - - // Held for the duration of run(); see the comment there. - std::mutex run_mu_; - std::mutex mu_; - std::condition_variable cv_start_; - std::condition_variable cv_done_; - std::vector threads_; - bool stopping_ = false; - std::uint64_t generation_ = 0; - Job* job_ = nullptr; - int workers_for_job_ = 0; + + // Held for the duration of run(); see the comment there. + std::mutex run_mu_; + std::mutex mu_; + std::condition_variable cv_start_; + std::condition_variable cv_done_; + std::vector threads_; + bool stopping_ = false; + std::uint64_t generation_ = 0; + Job* job_ = nullptr; + int workers_for_job_ = 0; }; struct PoolHolder { - std::mutex mu; - std::shared_ptr pool; + std::mutex mu; + std::shared_ptr pool; }; auto pool_holder() -> PoolHolder& { - static PoolHolder holder; - return holder; + static PoolHolder holder; + return holder; } auto default_pool() -> std::shared_ptr { - auto& h = pool_holder(); - std::lock_guard lock(h.mu); - if (!h.pool) - h.pool = std::make_shared(); - return h.pool; + auto& h = pool_holder(); + std::lock_guard lock(h.mu); + if (!h.pool) + h.pool = std::make_shared(); + return h.pool; } } // namespace auto clamp_workers_to_memory_budget( - const int workers, - const int budget_mb, - const int per_worker_mb) -> int + const int workers, + const int budget_mb, + const int per_worker_mb) -> int { - const int safe_workers = std::max(1, workers); - if (budget_mb <= 0 || per_worker_mb <= 0) - return safe_workers; - return std::max(1, std::min(safe_workers, budget_mb / per_worker_mb)); + const int safe_workers = std::max(1, workers); + if (budget_mb <= 0 || per_worker_mb <= 0) + return safe_workers; + return std::max(1, std::min(safe_workers, budget_mb / per_worker_mb)); } auto resolve_worker_count( - const int max_threads, - const int count) -> int + const int max_threads, + const int count) -> int { - int workers = max_threads; - if (workers <= 0) - { - const unsigned hw = std::thread::hardware_concurrency(); - workers = hw > 0 ? static_cast(hw) : 1; - } - workers = std::max(1, std::min(workers, count)); - - // Parallel workers each keep a Large TT (via SolverContext). Under - // Emscripten the wasm32 heap cannot grow past ~2 GiB; uncapped auto - // (= HW concurrency) OOMs large multi-board batches. Native builds keep - // the uncapped count — this block must stay __EMSCRIPTEN__-only. + int workers = max_threads; + if (workers <= 0) + { + const unsigned hw = std::thread::hardware_concurrency(); + workers = hw > 0 ? static_cast(hw) : 1; + } + workers = std::max(1, std::min(workers, count)); + + // Parallel workers each keep a Large TT (via SolverContext). Under + // Emscripten the wasm32 heap cannot grow past ~2 GiB; uncapped auto + // (= HW concurrency) OOMs large multi-board batches. Native builds keep + // the uncapped count — this block must stay __EMSCRIPTEN__-only. #if defined(__EMSCRIPTEN__) - constexpr int kHeapBudgetMB = 1400; - constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; - workers = clamp_workers_to_memory_budget(workers, kHeapBudgetMB, kPerWorkerMB); + constexpr int kHeapBudgetMB = 1400; + constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; + workers = clamp_workers_to_memory_budget(workers, kHeapBudgetMB, kPerWorkerMB); #endif - return workers; + return workers; } static auto is_permutation_of_range( - const std::vector& order, - const int count) -> bool + const std::vector& order, + const int count) -> bool { - std::vector seen(static_cast(count), 0); - for (const int v : order) - { - if (v < 0 || v >= count || seen[static_cast(v)]) - return false; - seen[static_cast(v)] = 1; - } - return true; + std::vector seen(static_cast(count), 0); + for (const int v : order) + { + if (v < 0 || v >= count || seen[static_cast(v)]) + return false; + seen[static_cast(v)] = 1; + } + return true; } @@ -299,70 +299,70 @@ namespace dds::internal auto parallel_boards_worker_threads_created() -> std::uint64_t { - return g_threads_created.load(std::memory_order_relaxed); + return g_threads_created.load(std::memory_order_relaxed); } auto parallel_boards_last_job_board_count() -> int { - return g_last_job_board_count.load(std::memory_order_relaxed); + return g_last_job_board_count.load(std::memory_order_relaxed); } void shutdown_parallel_boards_pool() { - std::shared_ptr dying; - { - auto& h = pool_holder(); - std::lock_guard lock(h.mu); - dying = std::move(h.pool); - } - // Drop the last shared_ptr outside the holder lock so joins cannot deadlock - // against a concurrent parallel_all_boards_n that needs the mutex to recreate - // the pool. In-flight runs keep their own shared_ptr alive until run returns. + std::shared_ptr dying; + { + auto& h = pool_holder(); + std::lock_guard lock(h.mu); + dying = std::move(h.pool); + } + // Drop the last shared_ptr outside the holder lock so joins cannot deadlock + // against a concurrent parallel_all_boards_n that needs the mutex to recreate + // the pool. In-flight runs keep their own shared_ptr alive until run returns. } } // namespace dds::internal auto parallel_all_boards_n( - const int count, - const int worker_cap, - const std::function& process_board, - const std::vector* order) -> int + const int count, + const int worker_cap, + const std::function& process_board, + const std::vector* order) -> int { - // Always record the requested count, including early-return paths, so the - // test seam cannot report a stale prior job. - g_last_job_board_count.store(count, std::memory_order_relaxed); - - if (count <= 0) - { - return RETURN_NO_FAULT; - } - - // Map a dispatch slot to the board number to process. With an order, hand out - // boards in that sequence (e.g. hardest first); otherwise in index order. The - // order is only honored when it is a valid permutation of [0, count); a - // malformed order falls back to index order to avoid invalid board indices. - const bool use_order = - (order != nullptr && + // Always record the requested count, including early-return paths, so the + // test seam cannot report a stale prior job. + g_last_job_board_count.store(count, std::memory_order_relaxed); + + if (count <= 0) + { + return RETURN_NO_FAULT; + } + + // Map a dispatch slot to the board number to process. With an order, hand out + // boards in that sequence (e.g. hardest first); otherwise in index order. The + // order is only honored when it is a valid permutation of [0, count); a + // malformed order falls back to index order to avoid invalid board indices. + const bool use_order = + (order != nullptr && order->size() == static_cast(count) && is_permutation_of_range(*order, count)); - const int workers = resolve_worker_count(worker_cap, count); + const int workers = resolve_worker_count(worker_cap, count); - if (workers == 1) - { - for (int slot = 0; slot < count; ++slot) + if (workers == 1) { - const int bno = - use_order ? (*order)[static_cast(slot)] : slot; - const int rc = process_board(0, bno); - if (rc != RETURN_NO_FAULT) - { - return rc; - } + for (int slot = 0; slot < count; ++slot) + { + const int bno = + use_order ? (*order)[static_cast(slot)] : slot; + const int rc = process_board(0, bno); + if (rc != RETURN_NO_FAULT) + { + return rc; + } + } + return RETURN_NO_FAULT; } - return RETURN_NO_FAULT; - } - return default_pool()->run(workers, count, process_board, use_order, order); + return default_pool()->run(workers, count, process_board, use_order, order); } diff --git a/library/src/system/parallel_boards.hpp b/library/src/system/parallel_boards.hpp index 36a915cf5..e8c65dce1 100644 --- a/library/src/system/parallel_boards.hpp +++ b/library/src/system/parallel_boards.hpp @@ -27,9 +27,9 @@ * @return workers clamped to max(1, budget_mb / per_worker_mb) */ auto clamp_workers_to_memory_budget( - int workers, - int budget_mb, - int per_worker_mb) -> int; + int workers, + int budget_mb, + int per_worker_mb) -> int; /** * @brief Resolve the number of worker threads to use. @@ -70,10 +70,10 @@ auto resolve_worker_count(int max_threads, int count) -> int; * when hosts (notably Emscripten) tear down pthread Workers eagerly. */ auto parallel_all_boards_n( - int count, - int worker_cap, - const std::function& process_board, - const std::vector* order = nullptr) -> int; + int count, + int worker_cap, + const std::function& process_board, + const std::vector* order = nullptr) -> int; namespace dds::internal { diff --git a/library/src/system/scheduler.cpp b/library/src/system/scheduler.cpp index 11b3f91ac..03281345f 100644 --- a/library/src/system/scheduler.cpp +++ b/library/src/system/scheduler.cpp @@ -25,81 +25,81 @@ Scheduler::Scheduler() { - numThreads = 0; - numHands = 0; + numThreads = 0; + numHands = 0; - Scheduler::InitHighCards(); + Scheduler::InitHighCards(); #ifdef DDS_SCHEDULER - Scheduler::InitTimes(); - for (int i = 0; i < 10000; i++) - { - timeHist[i] = 0; - timeHistNT[i] = 0; - timeHistSuit[i] = 0; - } + Scheduler::InitTimes(); + for (int i = 0; i < 10000; i++) + { + timeHist[i] = 0; + timeHistNT[i] = 0; + timeHistSuit[i] = 0; + } #endif - Scheduler::RegisterThreads(1); + Scheduler::RegisterThreads(1); } void Scheduler::InitHighCards() { - // highCards[i] is a point value of a given suit holding i. - // This can be HCP, for instance. Currently it is close to - // 6 - 4 - 2 - 1 - 0.5 for A-K-Q-J-T, but with 6.5 for the ace - // in order to make the sum come out to 28, an even number, so - // that the average number is an integer. - - highCards.resize(1 << 13); - const unsigned pA = 1 << 12; - const unsigned pK = 1 << 11; - const unsigned pQ = 1 << 10; - const unsigned pJ = 1 << 9; - const unsigned pT = 1 << 8; - - for (unsigned suit = 0; suit < (1 << 13); suit++) - { - int j = 0; - if (suit & pA) j += 13; - if (suit & pK) j += 8; - if (suit & pQ) j += 4; - if (suit & pJ) j += 2; - if (suit & pT) j += 1; - highCards[suit] = j; - } + // highCards[i] is a point value of a given suit holding i. + // This can be HCP, for instance. Currently it is close to + // 6 - 4 - 2 - 1 - 0.5 for A-K-Q-J-T, but with 6.5 for the ace + // in order to make the sum come out to 28, an even number, so + // that the average number is an integer. + + highCards.resize(1 << 13); + const unsigned pA = 1 << 12; + const unsigned pK = 1 << 11; + const unsigned pQ = 1 << 10; + const unsigned pJ = 1 << 9; + const unsigned pT = 1 << 8; + + for (unsigned suit = 0; suit < (1 << 13); suit++) + { + int j = 0; + if (suit & pA) j += 13; + if (suit & pK) j += 8; + if (suit & pQ) j += 4; + if (suit & pJ) j += 2; + if (suit & pT) j += 1; + highCards[suit] = j; + } } #ifdef DDS_SCHEDULER void Scheduler::InitTimes() { - // Initialize TimeStatList members - timeStrain.Reset(); - timeRepeat.Reset(); - timeDepth.Reset(); - timeStrength.Reset(); - timeFanout.Reset(); - timeThread.Reset(); - - timeStrain.Init("Suit/NT", 2); - timeRepeat.Init("Repeat number", 16); - timeDepth.Init("Trace depth", 60); - timeStrength.Init("Evenness", 60); - timeFanout.Init("Fanout", 100); - timeThread.Init("Threads", numThreads); - - timeGroupActualStrain.Reset(); - timeGroupPredStrain.Reset(); - timeGroupDiffStrain.Reset(); - - timeGroupActualStrain.Init("Group actual suit/NT", 2); - timeGroupPredStrain.Init("Group predicted suit/NT", 2); - timeGroupDiffStrain.Init("Group diff suit/NT", 2); - - blockMax = 0; - timeBlock = 0; + // Initialize TimeStatList members + timeStrain.Reset(); + timeRepeat.Reset(); + timeDepth.Reset(); + timeStrength.Reset(); + timeFanout.Reset(); + timeThread.Reset(); + + timeStrain.Init("Suit/NT", 2); + timeRepeat.Init("Repeat number", 16); + timeDepth.Init("Trace depth", 60); + timeStrength.Init("Evenness", 60); + timeFanout.Init("Fanout", 100); + timeThread.Init("Threads", numThreads); + + timeGroupActualStrain.Reset(); + timeGroupPredStrain.Reset(); + timeGroupDiffStrain.Reset(); + + timeGroupActualStrain.Init("Group actual suit/NT", 2); + timeGroupPredStrain.Init("Group predicted suit/NT", 2); + timeGroupDiffStrain.Init("Group diff suit/NT", 2); + + blockMax = 0; + timeBlock = 0; } #endif @@ -110,358 +110,358 @@ Scheduler::~Scheduler() void Scheduler::ClearTiming() { #ifdef DDS_SCHEDULER - timeStrain.Clear(); - timeRepeat.Clear(); - timeDepth.Clear(); - timeStrength.Clear(); - timeFanout.Clear(); - timeThread.Clear(); - timeGroupActualStrain.Clear(); - timeGroupPredStrain.Clear(); - timeGroupDiffStrain.Clear(); + timeStrain.Clear(); + timeRepeat.Clear(); + timeDepth.Clear(); + timeStrength.Clear(); + timeFanout.Clear(); + timeThread.Clear(); + timeGroupActualStrain.Clear(); + timeGroupPredStrain.Clear(); + timeGroupDiffStrain.Clear(); #else - // Nothing to do if scheduler timing not compiled in; ensure hands[] times are untouched. + // Nothing to do if scheduler timing not compiled in; ensure hands[] times are untouched. #endif } void Scheduler::Reset() { - for (int b = 0; b < MAXNOOFBOARDS; b++) - { - hands[b].next = -1; - hands[b].repeatNo = 0; - hands[b].depth = 0; - hands[b].strength = 0; - hands[b].fanout = 0; - hands[b].thread = 0; - hands[b].selectFlag = 0; - hands[b].time = 0; - } - - for (int g = 0; g < MAXNOOFBOARDS; g++) - { - group[g].head = -1; - group[g].actual = 0; - group[g].repeatNo = 0; - group[g].pred = 0; - } - - numGroups = 0; - extraGroups = 0; - - // One extra for NT, one extra for splitting collisions. - for (int strain = 0; strain < DDS_SUITS + 2; strain++) - for (int key = 0; key < HASH_MAX; key++) - list[strain][key].first = -1; - - for (unsigned t = 0; t < static_cast(numThreads); t++) - { - threadGroup[t] = -1; - threadCurrGroup[t] = -1; - } - - currGroup = -1; + for (int b = 0; b < MAXNOOFBOARDS; b++) + { + hands[b].next = -1; + hands[b].repeatNo = 0; + hands[b].depth = 0; + hands[b].strength = 0; + hands[b].fanout = 0; + hands[b].thread = 0; + hands[b].selectFlag = 0; + hands[b].time = 0; + } + + for (int g = 0; g < MAXNOOFBOARDS; g++) + { + group[g].head = -1; + group[g].actual = 0; + group[g].repeatNo = 0; + group[g].pred = 0; + } + + numGroups = 0; + extraGroups = 0; + + // One extra for NT, one extra for splitting collisions. + for (int strain = 0; strain < DDS_SUITS + 2; strain++) + for (int key = 0; key < HASH_MAX; key++) + list[strain][key].first = -1; + + for (unsigned t = 0; t < static_cast(numThreads); t++) + { + threadGroup[t] = -1; + threadCurrGroup[t] = -1; + } + + currGroup = -1; } void Scheduler::RegisterThreads( - const int n) + const int n) { - if (n == numThreads) - return; - numThreads = n; + if (n == numThreads) + return; + numThreads = n; - const unsigned nu = static_cast(n); - threadGroup.resize(nu); - threadCurrGroup.resize(nu); - threadToHand.resize(nu); + const unsigned nu = static_cast(n); + threadGroup.resize(nu); + threadCurrGroup.resize(nu); + threadToHand.resize(nu); #ifdef DDS_SCHEDULER - timeThread.Init("Threads", numThreads); - timersThread.resize(numThreads); + timeThread.Init("Threads", numThreads); + timersThread.resize(numThreads); #endif } void Scheduler::RegisterRun( - const enum RunMode mode, - const Boards& bds, - const PlayTracesBin& pl) + const enum RunMode mode, + const Boards& bds, + const PlayTracesBin& pl) { - for (int b = 0; b < bds.no_of_boards; b++) - hands[b].depth = pl.plays[b].number; - - Scheduler::RegisterRun(mode, bds); + for (int b = 0; b < bds.no_of_boards; b++) + hands[b].depth = pl.plays[b].number; + + Scheduler::RegisterRun(mode, bds); } void Scheduler::RegisterRun( - const enum RunMode mode, - const Boards& bds) + const enum RunMode mode, + const Boards& bds) { - Scheduler::Reset(); + Scheduler::Reset(); - numHands = bds.no_of_boards; + numHands = bds.no_of_boards; - // First split the hands according to strain and hash key. - // This will lead to a few random collisions as well. + // First split the hands according to strain and hash key. + // This will lead to a few random collisions as well. - Scheduler::MakeGroups(bds); + Scheduler::MakeGroups(bds); - // Then check whether groups with at least two elements are - // homogeneous or whether they need to be split. + // Then check whether groups with at least two elements are + // homogeneous or whether they need to be split. - Scheduler::FinetuneGroups(); + Scheduler::FinetuneGroups(); - Scheduler::SortHands(mode); + Scheduler::SortHands(mode); } void Scheduler::SortHands(const enum RunMode mode) { - // Make predictions per group. - - if (mode == RunMode::DDS_RUN_SOLVE) - Scheduler::SortSolve(); - else if (mode == RunMode::DDS_RUN_CALC) - Scheduler::SortCalc(); - else if (mode == RunMode::DDS_RUN_TRACE) - Scheduler::SortTrace(); + // Make predictions per group. + + if (mode == RunMode::DDS_RUN_SOLVE) + Scheduler::SortSolve(); + else if (mode == RunMode::DDS_RUN_CALC) + Scheduler::SortCalc(); + else if (mode == RunMode::DDS_RUN_TRACE) + Scheduler::SortTrace(); } void Scheduler::MakeGroups(const Boards& bds) { - Deal const * dl; - listType * lp; + Deal const * dl; + listType * lp; - for (int b = 0; b < numHands; b++) - { - dl = &bds.deals[b]; + for (int b = 0; b < numHands; b++) + { + dl = &bds.deals[b]; - int strain = dl->trump; + int strain = dl->trump; - unsigned dlXor = - dl->remainCards[0][0] ^ - dl->remainCards[1][1] ^ - dl->remainCards[2][2] ^ - dl->remainCards[3][3]; + unsigned dlXor = + dl->remainCards[0][0] ^ + dl->remainCards[1][1] ^ + dl->remainCards[2][2] ^ + dl->remainCards[3][3]; - int key = static_cast(((dlXor >> 2) ^ (dlXor >> 6)) & 0x7f); + int key = static_cast(((dlXor >> 2) ^ (dlXor >> 6)) & 0x7f); - hands[b].spareKey = static_cast( - (dl->remainCards[1][0] << 17) ^ - (dl->remainCards[2][1] << 11) ^ - (dl->remainCards[3][2] << 5) ^ - (dl->remainCards[0][3] >> 2)); + hands[b].spareKey = static_cast( + (dl->remainCards[1][0] << 17) ^ + (dl->remainCards[2][1] << 11) ^ + (dl->remainCards[3][2] << 5) ^ + (dl->remainCards[0][3] >> 2)); - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - hands[b].remainCards[h][s] = dl->remainCards[h][s]; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + hands[b].remainCards[h][s] = dl->remainCards[h][s]; - hands[b].NTflag = (strain == 4 ? 1 : 0); - hands[b].first = dl->first; - hands[b].strain = strain; - hands[b].fanout = dds::internal::deal_fanout(*dl); - // hands[b].strength = Scheduler::Strength(* dl); + hands[b].NTflag = (strain == 4 ? 1 : 0); + hands[b].first = dl->first; + hands[b].strain = strain; + hands[b].fanout = dds::internal::deal_fanout(*dl); + // hands[b].strength = Scheduler::Strength(* dl); - lp = &list[strain][key]; + lp = &list[strain][key]; - if (lp->first == -1) - { - lp->first = b; - lp->last = b; - lp->length = 1; - - group[numGroups].strain = strain; - group[numGroups].hash = key; - group[numGroups].head = -1; - group[numGroups].actual = 0; - group[numGroups].repeatNo = 0; - numGroups++; - } - else - { - int l = lp->last; - hands[l].next = b; + if (lp->first == -1) + { + lp->first = b; + lp->last = b; + lp->length = 1; + + group[numGroups].strain = strain; + group[numGroups].hash = key; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; + numGroups++; + } + else + { + int l = lp->last; + hands[l].next = b; - lp->last = b; - lp->length++; + lp->last = b; + lp->length++; + } } - } } void Scheduler::FinetuneGroups() { - listType * lp; - int strain, key, b1, b2; - int numGroupsOrig = numGroups; + listType * lp; + int strain, key, b1, b2; + int numGroupsOrig = numGroups; - for (int g = 0; g < numGroupsOrig; g++) - { - strain = group[g].strain; - key = group[g].hash; - - lp = &list[strain][key]; + for (int g = 0; g < numGroupsOrig; g++) + { + strain = group[g].strain; + key = group[g].hash; - if (lp->length == 1) - continue; + lp = &list[strain][key]; - else if (lp->length == 2) - { - // This happens quite often, so worth optimizing. - - b1 = lp->first; - b2 = hands[lp->first].next; - - bool match = false; - if (hands[b1].spareKey == hands[b2].spareKey) - { - // It is now extremely likely that it is a repeat hand, - // but we have to be sure. - match = true; - for (int h = 0; h < DDS_HANDS && match; h++) - for (int s = 0; s < DDS_SUITS && match; s++) - if (hands[b1].remainCards[h][s] != hands[b2].remainCards[h][s]) - match = false; - } - - if (match) - continue; - - // Leave the first hand in place. - hands[lp->first].next = -1; - lp->last = lp->first; - lp->length = 1; - - // Move the second hand to the special list. - lp = &list[5][extraGroups]; - - lp->first = b2; - lp->last = b2; - lp->length = 1; - - group[numGroups].strain = 5; - group[numGroups].hash = extraGroups; - group[numGroups].head = -1; - group[numGroups].actual = 0; - group[numGroups].repeatNo = 0; - - numGroups++; - extraGroups++; - } + if (lp->length == 1) + continue; - else - { - // This is the general case. The comparison is not quite - // as thorough here, but it's better than above and it uses - // a different hand. - - sortType st; - sortLen = lp->length; - int index = lp->first; - - for (int i = 0; i < sortLen; i++) - { - sortList[i].number = index; - sortList[i].value = hands[index].spareKey; - - index = hands[index].next; - } - - // Sort the list heuristically by spareKey value. - - for (int i = 1; i < sortLen; i++) - { - st = sortList[i]; - int j = i; - for (; j && st.value > sortList[j - 1].value; --j) - sortList[j] = sortList[j - 1]; - sortList[j] = st; - } - - // First group stays where it is, but shorter and rejigged. - // From here on, hand comparisons are completely rigorous. - // We might miss duplicates, but we won't let different - // hands through as belonging to the same group. - - int l = 0; - while (l < sortLen-1 && - Scheduler::SameHand(sortList[l].number, sortList[l+1].number)) - l++; - - if (l == sortLen-1) - continue; - - lp->first = sortList[0].number; - lp->last = sortList[l].number; - lp->length = l + 1; - - index = lp->first; - - for (int i = 0; i < l; i++) - { - hands[index].next = sortList[i + 1].number; - index = hands[index].next; - } - - hands[index].next = -1; - - // The rest is moved to special groups. - l++; - - while (l < sortLen) - { - if (Scheduler::SameHand(sortList[l].number, sortList[l-1].number)) + else if (lp->length == 2) { - // Same group - int nOld = sortList[l - 1].number; - int nNew = sortList[l].number; - hands[nOld].next = nNew; - hands[nNew].next = -1; - - lp->last = nNew; - lp->length++; + // This happens quite often, so worth optimizing. + + b1 = lp->first; + b2 = hands[lp->first].next; + + bool match = false; + if (hands[b1].spareKey == hands[b2].spareKey) + { + // It is now extremely likely that it is a repeat hand, + // but we have to be sure. + match = true; + for (int h = 0; h < DDS_HANDS && match; h++) + for (int s = 0; s < DDS_SUITS && match; s++) + if (hands[b1].remainCards[h][s] != hands[b2].remainCards[h][s]) + match = false; + } + + if (match) + continue; + + // Leave the first hand in place. + hands[lp->first].next = -1; + lp->last = lp->first; + lp->length = 1; + + // Move the second hand to the special list. + lp = &list[5][extraGroups]; + + lp->first = b2; + lp->last = b2; + lp->length = 1; + + group[numGroups].strain = 5; + group[numGroups].hash = extraGroups; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; + + numGroups++; + extraGroups++; } + else { - // New group - int n = sortList[l].number; - hands[n].next = -1; - - lp = &list[5][extraGroups]; - lp->first = n; - lp->last = n; - lp->length = 1; - - group[numGroups].strain = 5; - group[numGroups].hash = extraGroups; - group[numGroups].head = -1; - group[numGroups].actual = 0; - group[numGroups].repeatNo = 0; - - numGroups++; - extraGroups++; + // This is the general case. The comparison is not quite + // as thorough here, but it's better than above and it uses + // a different hand. + + sortType st; + sortLen = lp->length; + int index = lp->first; + + for (int i = 0; i < sortLen; i++) + { + sortList[i].number = index; + sortList[i].value = hands[index].spareKey; + + index = hands[index].next; + } + + // Sort the list heuristically by spareKey value. + + for (int i = 1; i < sortLen; i++) + { + st = sortList[i]; + int j = i; + for (; j && st.value > sortList[j - 1].value; --j) + sortList[j] = sortList[j - 1]; + sortList[j] = st; + } + + // First group stays where it is, but shorter and rejigged. + // From here on, hand comparisons are completely rigorous. + // We might miss duplicates, but we won't let different + // hands through as belonging to the same group. + + int l = 0; + while (l < sortLen-1 && + Scheduler::SameHand(sortList[l].number, sortList[l+1].number)) + l++; + + if (l == sortLen-1) + continue; + + lp->first = sortList[0].number; + lp->last = sortList[l].number; + lp->length = l + 1; + + index = lp->first; + + for (int i = 0; i < l; i++) + { + hands[index].next = sortList[i + 1].number; + index = hands[index].next; + } + + hands[index].next = -1; + + // The rest is moved to special groups. + l++; + + while (l < sortLen) + { + if (Scheduler::SameHand(sortList[l].number, sortList[l-1].number)) + { + // Same group + int nOld = sortList[l - 1].number; + int nNew = sortList[l].number; + hands[nOld].next = nNew; + hands[nNew].next = -1; + + lp->last = nNew; + lp->length++; + } + else + { + // New group + int n = sortList[l].number; + hands[n].next = -1; + + lp = &list[5][extraGroups]; + lp->first = n; + lp->last = n; + lp->length = 1; + + group[numGroups].strain = 5; + group[numGroups].hash = extraGroups; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; + + numGroups++; + extraGroups++; + } + l++; + } } - l++; - } } - } } bool Scheduler::SameHand( - const int hno1, - const int hno2) const + const int hno1, + const int hno2) const { - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - if (hands[hno1].remainCards[h][s] != hands[hno2].remainCards[h][s]) - return false; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + if (hands[hno1].remainCards[h][s] != hands[hno2].remainCards[h][s]) + return false; - return true; + return true; } @@ -471,8 +471,8 @@ bool Scheduler::SameHand( static int SORT_SOLVE_TIMES[2][8] = { - { 284000, 91000, 37000, 23000, 17000, 15000, 13000, 4000 }, - { 388000, 140000, 60000, 40000, 30000, 23000, 18000, 6000 }, + { 284000, 91000, 37000, 23000, 17000, 15000, 13000, 4000 }, + { 388000, 140000, 60000, 40000, 30000, 23000, 18000, 6000 }, }; @@ -481,71 +481,71 @@ static int SORT_SOLVE_TIMES[2][8] = static double SORT_SOLVE_FANOUT[2][5] = { - { 30., 50., 0.07577, 1.515, 12. }, - { 30., 50., 0.08144, 1.629, 12. } + { 30., 50., 0.07577, 1.515, 12. }, + { 30., 50., 0.08144, 1.629, 12. } }; void Scheduler::SortSolve() { - listType * lp; - handType * hp; - int strain, key, index; - - for (int g = 0; g < numGroups; g++) - { - strain = group[g].strain; - key = group[g].hash; - lp = &list[strain][key]; - index = lp->first; - hp = &hands[index]; + listType * lp; + handType * hp; + int strain, key, index; - // Taking into account repeat times saves 1-2%. - - int repeatNo = 0; - int firstPrev = -1; - group[g].pred = 0; - do + for (int g = 0; g < numGroups; g++) { - // Skip complete duplicates, as we won't solve them again. - if (hands[index].first != firstPrev) - { - group[g].pred += SORT_SOLVE_TIMES[hp->NTflag][repeatNo]; - if (repeatNo < 7) - repeatNo++; - firstPrev = hands[index].first; - } - - index = hands[index].next; - } - while (index != -1); + strain = group[g].strain; + key = group[g].hash; + lp = &list[strain][key]; + index = lp->first; + hp = &hands[index]; + + // Taking into account repeat times saves 1-2%. + + int repeatNo = 0; + int firstPrev = -1; + group[g].pred = 0; + do + { + // Skip complete duplicates, as we won't solve them again. + if (hands[index].first != firstPrev) + { + group[g].pred += SORT_SOLVE_TIMES[hp->NTflag][repeatNo]; + if (repeatNo < 7) + repeatNo++; + firstPrev = hands[index].first; + } + + index = hands[index].next; + } + while (index != -1); - // Taking into account fanout saves 4-6%. + // Taking into account fanout saves 4-6%. - int fanout = hp->fanout; - double * slist = SORT_SOLVE_FANOUT[hp->NTflag]; - double fanoutFactor; + int fanout = hp->fanout; + double * slist = SORT_SOLVE_FANOUT[hp->NTflag]; + double fanoutFactor; - if (fanout < slist[0]) - fanoutFactor = 0.; // A bit extreme... - else if (fanout < slist[1]) - fanoutFactor = slist[2] * (fanout - slist[0]); - else - fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); - - group[g].pred = static_cast( - (fanoutFactor * static_cast(group[g].pred))); - } - - // Sort groups using merge sort. - groupType gp; - for (int g = 0; g < numGroups; g++) - { - gp = group[g]; - int j = g; - for (; j && gp.pred > group[j - 1].pred; --j) - group[j] = group[j - 1]; - group[j] = gp; - } + if (fanout < slist[0]) + fanoutFactor = 0.; // A bit extreme... + else if (fanout < slist[1]) + fanoutFactor = slist[2] * (fanout - slist[0]); + else + fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); + + group[g].pred = static_cast( + (fanoutFactor * static_cast(group[g].pred))); + } + + // Sort groups using merge sort. + groupType gp; + for (int g = 0; g < numGroups; g++) + { + gp = group[g]; + int j = g; + for (; j && gp.pred > group[j - 1].pred; --j) + group[j] = group[j - 1]; + group[j] = gp; + } } @@ -557,54 +557,54 @@ void Scheduler::SortSolve() static double SORT_CALC_FANOUT[2][5] = { - { 30., 50., 0.07812, 1.563, 13. }, - { 30., 50., 0.07739, 1.548, 12. } + { 30., 50., 0.07812, 1.563, 13. }, + { 30., 50., 0.07739, 1.548, 12. } }; void Scheduler::SortCalc() { - listType * lp; - handType * hp; - int strain, key, index; - - for (int g = 0; g < numGroups; g++) - { - strain = group[g].strain; - key = group[g].hash; - lp = &list[strain][key]; - index = lp->first; - hp = &hands[index]; + listType * lp; + handType * hp; + int strain, key, index; - // Taking into account repeat times saves 1-2%. + for (int g = 0; g < numGroups; g++) + { + strain = group[g].strain; + key = group[g].hash; + lp = &list[strain][key]; + index = lp->first; + hp = &hands[index]; - group[g].pred = 272000; + // Taking into account repeat times saves 1-2%. - int fanout = hp->fanout; - double * slist = SORT_CALC_FANOUT[hp->NTflag]; - double fanoutFactor; + group[g].pred = 272000; - if (fanout < slist[0]) - fanoutFactor = 0.; // A bit extreme... - else if (fanout < slist[1]) - fanoutFactor = slist[2] * (fanout - slist[0]); - else - fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); - - group[g].pred = static_cast( - (fanoutFactor * static_cast(group[g].pred))); - } - - // Sort groups using merge sort. - groupType gp; - for (int g = 0; g < numGroups; g++) - { - gp = group[g]; - int j = g; - for (; j && gp.pred > group[j - 1].pred; --j) - group[j] = group[j - 1]; - group[j] = gp; - } + int fanout = hp->fanout; + double * slist = SORT_CALC_FANOUT[hp->NTflag]; + double fanoutFactor; + + if (fanout < slist[0]) + fanoutFactor = 0.; // A bit extreme... + else if (fanout < slist[1]) + fanoutFactor = slist[2] * (fanout - slist[0]); + else + fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); + + group[g].pred = static_cast( + (fanoutFactor * static_cast(group[g].pred))); + } + + // Sort groups using merge sort. + groupType gp; + for (int g = 0; g < numGroups; g++) + { + gp = group[g]; + int j = g; + for (; j && gp.pred > group[j - 1].pred; --j) + group[j] = group[j - 1]; + group[j] = gp; + } } @@ -613,8 +613,8 @@ void Scheduler::SortCalc() static int SORT_TRACE_TIMES[2][8] = { - { 157000, 47000, 26000, 18000, 16000, 14000, 10000, 6000 }, - { 205000, 87000, 45000, 36000, 32000, 28000, 24000, 20000 }, + { 157000, 47000, 26000, 18000, 16000, 14000, 10000, 6000 }, + { 205000, 87000, 45000, 36000, 32000, 28000, 24000, 20000 }, }; // Initial value for 0 and 1 cards @@ -624,8 +624,8 @@ static int SORT_TRACE_TIMES[2][8] = static double SORT_TRACE_DEPTH[2][4] = { - { 0.742, 0.411, 0.0414, 1.820 }, - { 0.669, 0.428, 0.0346, 1.606 } + { 0.742, 0.411, 0.0414, 1.820 }, + { 0.669, 0.428, 0.0346, 1.606 } }; // Lower end of linear, upper end of linear, slope of linear, @@ -633,418 +633,418 @@ static double SORT_TRACE_DEPTH[2][4] = static double SORT_TRACE_FANOUT[2][5] = { - { 30., 50., 0.07577, 1.515, 12. }, - { 30., 50., 0.08166, 1.633, 13. } + { 30., 50., 0.07577, 1.515, 12. }, + { 30., 50., 0.08166, 1.633, 13. } }; void Scheduler::SortTrace() { - listType * lp; - handType * hp; - int strain, key, index; - - for (int g = 0; g < numGroups; g++) - { - strain = group[g].strain; - key = group[g].hash; - lp = &list[strain][key]; - index = lp->first; - hp = &hands[index]; - - // Taking into account repeat times. + listType * lp; + handType * hp; + int strain, key, index; - int repeatNo = 0; - int firstPrev = -1; - group[g].pred = 0; - do + for (int g = 0; g < numGroups; g++) { - // Skip complete duplicates, as we won't solve them again. - if (hands[index].first != firstPrev) - { - group[g].pred += SORT_TRACE_TIMES[hp->NTflag][repeatNo]; - if (repeatNo < 7) - repeatNo++; - firstPrev = hands[index].first; - } - - index = hands[index].next; - } - while (index != -1); - - double depthFactor; - int depth = hp->depth; - double * slist = SORT_TRACE_DEPTH[hp->NTflag]; - - if (depth <= 1) - depthFactor = slist[0]; - else if (depth <= 15) - depthFactor = slist[1]; - else if (depth >= 49) - depthFactor = slist[3]; - else - depthFactor = slist[1] + (depth - 15) * slist[2]; + strain = group[g].strain; + key = group[g].hash; + lp = &list[strain][key]; + index = lp->first; + hp = &hands[index]; + + // Taking into account repeat times. + + int repeatNo = 0; + int firstPrev = -1; + group[g].pred = 0; + do + { + // Skip complete duplicates, as we won't solve them again. + if (hands[index].first != firstPrev) + { + group[g].pred += SORT_TRACE_TIMES[hp->NTflag][repeatNo]; + if (repeatNo < 7) + repeatNo++; + firstPrev = hands[index].first; + } + + index = hands[index].next; + } + while (index != -1); + + double depthFactor; + int depth = hp->depth; + double * slist = SORT_TRACE_DEPTH[hp->NTflag]; + + if (depth <= 1) + depthFactor = slist[0]; + else if (depth <= 15) + depthFactor = slist[1]; + else if (depth >= 49) + depthFactor = slist[3]; + else + depthFactor = slist[1] + (depth - 15) * slist[2]; - group[g].pred = static_cast( - (depthFactor * static_cast(group[g].pred))); + group[g].pred = static_cast( + (depthFactor * static_cast(group[g].pred))); - // Taking into account fanout. + // Taking into account fanout. - int fanout = hp->fanout; - slist = SORT_TRACE_FANOUT[hp->NTflag]; - double fanoutFactor; + int fanout = hp->fanout; + slist = SORT_TRACE_FANOUT[hp->NTflag]; + double fanoutFactor; - if (fanout < slist[0]) - fanoutFactor = 0.; // A bit extreme... - else if (fanout < slist[1]) - fanoutFactor = slist[2] * (fanout - slist[0]); - else - fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); - - group[g].pred = static_cast( - (fanoutFactor * static_cast(group[g].pred))); - } - - // Sort groups using merge sort. - groupType gp; - for (int g = 0; g < numGroups; g++) - { - gp = group[g]; - int j = g; - for (; j && gp.pred > group[j - 1].pred; --j) - group[j] = group[j - 1]; - group[j] = gp; - } + if (fanout < slist[0]) + fanoutFactor = 0.; // A bit extreme... + else if (fanout < slist[1]) + fanoutFactor = slist[2] * (fanout - slist[0]); + else + fanoutFactor = slist[3] * exp( (fanout - slist[1]) / slist[4] ); + + group[g].pred = static_cast( + (fanoutFactor * static_cast(group[g].pred))); + } + + // Sort groups using merge sort. + groupType gp; + for (int g = 0; g < numGroups; g++) + { + gp = group[g]; + int j = g; + for (; j && gp.pred > group[j - 1].pred; --j) + group[j] = group[j - 1]; + group[j] = gp; + } } int Scheduler::Strength(const Deal& dl) const { - // If the strength in all suits is evenly split, then the - // "strength" returned is close to 0. Maximum is 49. + // If the strength in all suits is evenly split, then the + // "strength" returned is close to 0. Maximum is 49. - const unsigned sp = (dl.remainCards[0][0] | dl.remainCards[2][0]) >> 2; - const unsigned he = (dl.remainCards[0][1] | dl.remainCards[2][1]) >> 2; - const unsigned di = (dl.remainCards[0][2] | dl.remainCards[2][2]) >> 2; - const unsigned cl = (dl.remainCards[0][3] | dl.remainCards[2][3]) >> 2; + const unsigned sp = (dl.remainCards[0][0] | dl.remainCards[2][0]) >> 2; + const unsigned he = (dl.remainCards[0][1] | dl.remainCards[2][1]) >> 2; + const unsigned di = (dl.remainCards[0][2] | dl.remainCards[2][2]) >> 2; + const unsigned cl = (dl.remainCards[0][3] | dl.remainCards[2][3]) >> 2; - const int hsp = highCards[sp]; - const int hhe = highCards[he]; - const int hdi = highCards[di]; - const int hcl = highCards[cl]; + const int hsp = highCards[sp]; + const int hhe = highCards[he]; + const int hdi = highCards[di]; + const int hcl = highCards[cl]; - int dev = (hsp >= 14 ? hsp - 14 : 14 - hsp) + - (hhe >= 14 ? hhe - 14 : 14 - hhe) + - (hdi >= 14 ? hdi - 14 : 14 - hdi) + - (hcl >= 14 ? hcl - 14 : 14 - hcl); + int dev = (hsp >= 14 ? hsp - 14 : 14 - hsp) + + (hhe >= 14 ? hhe - 14 : 14 - hhe) + + (hdi >= 14 ? hdi - 14 : 14 - hdi) + + (hcl >= 14 ? hcl - 14 : 14 - hcl); - if (dev >= 50) dev = 49; + if (dev >= 50) dev = 49; - return dev; + return dev; } schedType Scheduler::GetNumber(const int thrId) { - const unsigned tu = static_cast(thrId); - int g = threadGroup[tu]; - listType * lp; - schedType st; - - if (g == -1) - { - // Find a new group + const unsigned tu = static_cast(thrId); + int g = threadGroup[tu]; + listType * lp; + schedType st; - if (currGroup >= numGroups - 1) + if (g == -1) { - // Out of groups. Just an optimization not to touch the - // shared variable unnecessarily. - st.number = -1; - return st; + // Find a new group + + if (currGroup >= numGroups - 1) + { + // Out of groups. Just an optimization not to touch the + // shared variable unnecessarily. + st.number = -1; + return st; + } + + // Atomic. + g = ++currGroup; + + if (g >= numGroups) + { + // Out of groups. currGroup could have changed in the + // meantime in another thread, so test again. + + st.number = -1; + return st; + } + + // A bit inelegant to duplicate this, but seems better than + // the alternative, as threadGroup must get set to -1 in some + // cases. + threadGroup[tu] = g; + threadCurrGroup[tu] = g; + group[g].repeatNo = 0; + group[g].actual = 0; } - // Atomic. - g = ++currGroup; + // Continue with existing or new group + + int strain = group[g].strain; + int key = group[g].hash; + + lp = &list[strain][key]; + st.number = lp->first; + lp->first = hands[lp->first].next; - if (g >= numGroups) + if (group[g].repeatNo == 0) { - // Out of groups. currGroup could have changed in the - // meantime in another thread, so test again. + group[g].head = st.number; + st.repeatOf = -1; - st.number = -1; - return st; + // Only first-solve suited hands for statistics right now. + hands[st.number].selectFlag = + (hands[st.number].strain == 4 ? 1 : 0); } - - // A bit inelegant to duplicate this, but seems better than - // the alternative, as threadGroup must get set to -1 in some - // cases. - threadGroup[tu] = g; - threadCurrGroup[tu] = g; - group[g].repeatNo = 0; - group[g].actual = 0; - } - - // Continue with existing or new group - - int strain = group[g].strain; - int key = group[g].hash; - - lp = &list[strain][key]; - st.number = lp->first; - lp->first = hands[lp->first].next; - - if (group[g].repeatNo == 0) - { - group[g].head = st.number; - st.repeatOf = -1; - - // Only first-solve suited hands for statistics right now. - hands[st.number].selectFlag = - (hands[st.number].strain == 4 ? 1 : 0); - } - else - { - st.repeatOf = group[g].head; - //hands[st.number].selectFlag = 0; - - if (hands[st.number].first == hands[st.repeatOf].first) - hands[st.number].selectFlag = 0; - else if (hands[st.number].strain == 4) - hands[st.number].selectFlag = 1; else - hands[st.number].selectFlag = 0; - } + { + st.repeatOf = group[g].head; + //hands[st.number].selectFlag = 0; + + if (hands[st.number].first == hands[st.repeatOf].first) + hands[st.number].selectFlag = 0; + else if (hands[st.number].strain == 4) + hands[st.number].selectFlag = 1; + else + hands[st.number].selectFlag = 0; + } - hands[st.number].repeatNo = group[g].repeatNo++; + hands[st.number].repeatNo = group[g].repeatNo++; - threadToHand[tu] = st.number; + threadToHand[tu] = st.number; - if (lp->first == -1) - threadGroup[tu] = -1; + if (lp->first == -1) + threadGroup[tu] = -1; - return st; + return st; } int Scheduler::NumGroups() const { - return numGroups; + return numGroups; } #ifdef DDS_SCHEDULER void Scheduler::StartThreadTimer(const int thrId) { - timersThread[thrId].Reset(); - timersThread[thrId].Start(); + timersThread[thrId].Reset(); + timersThread[thrId].Start(); } void Scheduler::EndThreadTimer(const int thrId) { - timersThread[thrId].End(); - int timeUser = timersThread[thrId].UserTime(); + timersThread[thrId].End(); + int timeUser = timersThread[thrId].UserTime(); - hands[ threadToHand[thrId] ].time = timeUser; - hands[ threadToHand[thrId] ].thread = thrId; + hands[ threadToHand[thrId] ].time = timeUser; + hands[ threadToHand[thrId] ].thread = thrId; - group[ threadCurrGroup[thrId] ].actual += timeUser; + group[ threadCurrGroup[thrId] ].actual += timeUser; } void Scheduler::StartBlockTimer() { - timerBlock.Reset(); - timerBlock.Start(); + timerBlock.Reset(); + timerBlock.Start(); } void Scheduler::EndBlockTimer() { - timerBlock.End(); - const int timeUserBlock = timerBlock.UserTime(); + timerBlock.End(); + const int timeUserBlock = timerBlock.UserTime(); - handType * hp; - for (int b = 0; b < numHands; b++) - { - hp = &hands[b]; - int timeUser = hp->time; - double timesq = (double) timeUser * (double) timeUser; - - if (hp->selectFlag) + handType * hp; + for (int b = 0; b < numHands; b++) { - TimeStat ts; - ts.Set(timeUser, timesq); - - timeStrain.Add(hp->NTflag, ts); - timeRepeat.Add(hp->repeatNo, ts); - timeDepth.Add(hp->depth, ts); - timeStrength.Add(hp->strength, ts); - timeFanout.Add(hp->fanout, ts); - timeThread.Add(hp->thread, ts); - } + hp = &hands[b]; + int timeUser = hp->time; + double timesq = (double) timeUser * (double) timeUser; - if (timeUser > blockMax) - blockMax = timeUser; + if (hp->selectFlag) + { + TimeStat ts; + ts.Set(timeUser, timesq); + + timeStrain.Add(hp->NTflag, ts); + timeRepeat.Add(hp->repeatNo, ts); + timeDepth.Add(hp->depth, ts); + timeStrength.Add(hp->strength, ts); + timeFanout.Add(hp->fanout, ts); + timeThread.Add(hp->thread, ts); + } - if (hp->repeatNo == 0 && timeUser > 0) - { - int bin = timeUser / 1000; - timeHist[bin]++; - if (hp->NTflag) - timeHistNT[bin]++; - else - timeHistSuit[bin]++; + if (timeUser > blockMax) + blockMax = timeUser; + + if (hp->repeatNo == 0 && timeUser > 0) + { + int bin = timeUser / 1000; + timeHist[bin]++; + if (hp->NTflag) + timeHistNT[bin]++; + else + timeHistSuit[bin]++; + } } - } - for (int g = 0; g < numGroups; g++) - { - const int head = group[g].head; - if (head < 0 || head >= numHands) - continue; + for (int g = 0; g < numGroups; g++) + { + const int head = group[g].head; + if (head < 0 || head >= numHands) + continue; - const int NTflag = (hands[head].strain == 4 ? 1 : 0); + const int NTflag = (hands[head].strain == 4 ? 1 : 0); - TimeStat ts; + TimeStat ts; - ts.Set(group[g].actual); - timeGroupActualStrain.Add(NTflag, ts); + ts.Set(group[g].actual); + timeGroupActualStrain.Add(NTflag, ts); - ts.Set(group[g].pred); - timeGroupPredStrain.Add(NTflag, ts); + ts.Set(group[g].pred); + timeGroupPredStrain.Add(NTflag, ts); - int diff = group[g].actual - group[g].pred; - ts.Set(diff); - timeGroupDiffStrain.Add(NTflag, ts); - } + int diff = group[g].actual - group[g].pred; + ts.Set(diff); + timeGroupDiffStrain.Add(NTflag, ts); + } - timeBlock += timeUserBlock; - timeMax += blockMax; - blockMax = 0; + timeBlock += timeUserBlock; + timeMax += blockMax; + blockMax = 0; } void Scheduler::PrintTiming() const { - using std::fixed; - using std::ofstream; - using std::setprecision; - using std::setw; - using std::string; - - const string fname = string(DDS_SCHEDULER_PREFIX) + DDS_DEBUG_SUFFIX; - ofstream fout; - fout.open(fname); - - fout << timeStrain.List(); - fout << timeRepeat.List(); - fout << timeDepth.List(); - fout << timeStrength.List(); - fout << timeFanout.List(); - fout << timeThread.List(); - fout << timeGroupActualStrain.List(); - fout << timeGroupPredStrain.List(); - fout << timeGroupDiffStrain.List(); + using std::fixed; + using std::ofstream; + using std::setprecision; + using std::setw; + using std::string; + + const string fname = string(DDS_SCHEDULER_PREFIX) + DDS_DEBUG_SUFFIX; + ofstream fout; + fout.open(fname); + + fout << timeStrain.List(); + fout << timeRepeat.List(); + fout << timeDepth.List(); + fout << timeStrength.List(); + fout << timeFanout.List(); + fout << timeThread.List(); + fout << timeGroupActualStrain.List(); + fout << timeGroupPredStrain.List(); + fout << timeGroupDiffStrain.List(); #if 0 - fout << setw(13) << "Hist" << - setw(10) << "Hist suit" << - setw(10) << "Hist NT" << "\n"; - for (int i = 0; i < 10000; i++) - { - if (timeHist[i] || timeHistSuit[i] || timeHistNT[i]) + fout << setw(13) << "Hist" << + setw(10) << "Hist suit" << + setw(10) << "Hist NT" << "\n"; + for (int i = 0; i < 10000; i++) { - fout << setw(4) << i << - setw(9) << timeHist[i] << - setw(10) << timeHistSuit[i] << - setw(10) << timeHistNT[i] << "\n"; + if (timeHist[i] || timeHistSuit[i] || timeHistNT[i]) + { + fout << setw(4) << i << + setw(9) << timeHist[i] << + setw(10) << timeHistSuit[i] << + setw(10) << timeHistNT[i] << "\n"; + } } - } - fout << endl; + fout << endl; #endif - if (timeBlock == 0) - return; + if (timeBlock == 0) + return; - const double avg = 100. * (double) timeMax / (double) timeBlock; - fout << "Largest hand" << - setw(13) << timeMax << - setw(13) << timeBlock << - setw(6) << setprecision(2) << fixed << avg << "%\n\n"; + const double avg = 100. * (double) timeMax / (double) timeBlock; + fout << "Largest hand" << + setw(13) << timeMax << + setw(13) << timeBlock << + setw(6) << setprecision(2) << fixed << avg << "%\n\n"; - fout.close(); + fout.close(); } #endif // DDS_SCHEDULER void Scheduler::GetBoardTimes(std::vector>& outVec) const { - outVec.clear(); - for (int b = 0; b < numHands; b++) - { - const handType& hp = hands[b]; - outVec.emplace_back(b, hp.time); - } + outVec.clear(); + for (int b = 0; b < numHands; b++) + { + const handType& hp = hands[b]; + outVec.emplace_back(b, hp.time); + } } void Scheduler::SetBoardTime(int boardIndex, long long time_us) { - if (boardIndex < 0 || boardIndex >= MAXNOOFBOARDS) return; - // store in the hand time field; this is a lightweight fallback - // for when DDS_SCHEDULER isn't enabled. No locking required for - // single-writer per-board usage pattern from the solver threads. - hands[boardIndex].time = saturate_board_time_us(time_us); + if (boardIndex < 0 || boardIndex >= MAXNOOFBOARDS) return; + // store in the hand time field; this is a lightweight fallback + // for when DDS_SCHEDULER isn't enabled. No locking required for + // single-writer per-board usage pattern from the solver threads. + hands[boardIndex].time = saturate_board_time_us(time_us); } auto saturate_board_time_us(long long time_us) -> int { - if (time_us <= 0) - return 0; - constexpr auto kMax = static_cast(std::numeric_limits::max()); - if (time_us >= kMax) - return std::numeric_limits::max(); - return static_cast(time_us); + if (time_us <= 0) + return 0; + constexpr auto kMax = static_cast(std::numeric_limits::max()); + if (time_us >= kMax) + return std::numeric_limits::max(); + return static_cast(time_us); } int Scheduler::PredictedTime( - Deal& dl, - int number) const + Deal& dl, + int number) const { - int trump = dl.trump; - int NT = (trump == 4 ? 100 : 0); + int trump = dl.trump; + int NT = (trump == 4 ? 100 : 0); + + int dev1 = Scheduler::Strength(dl); + + int pred; + if (NT) + { + if (dev1 >= 25) + pred = 125000 - 2500 * dev1; + else + // This branch is not very accurate. + pred = 200000 - 5500 * dev1; - int dev1 = Scheduler::Strength(dl); + if (number >= 1) + pred = static_cast(1.25 * pred); - int pred; - if (NT) - { - if (dev1 >= 25) - pred = 125000 - 2500 * dev1; + if (number >= 2) + pred = static_cast (pred * + (1.185 - 0.185 * exp( -(number - 1) / 6.0))); + } else - // This branch is not very accurate. - pred = 200000 - 5500 * dev1; - - if (number >= 1) - pred = static_cast(1.25 * pred); - - if (number >= 2) - pred = static_cast (pred * - (1.185 - 0.185 * exp( -(number - 1) / 6.0))); - } - else - { - pred = 125000 - 2500 * dev1; - if (number >= 1) - pred = static_cast(1.2 * pred); - - if (number >= 2) - pred = static_cast(pred * - (1.185 - 0.185 * exp( -(number - 1) / 5.5))); - } - - return pred; + { + pred = 125000 - 2500 * dev1; + if (number >= 1) + pred = static_cast(1.2 * pred); + + if (number >= 2) + pred = static_cast(pred * + (1.185 - 0.185 * exp( -(number - 1) / 5.5))); + } + + return pred; } diff --git a/library/src/system/scheduler.hpp b/library/src/system/scheduler.hpp index d226be4a3..6f85fdecb 100644 --- a/library/src/system/scheduler.hpp +++ b/library/src/system/scheduler.hpp @@ -24,22 +24,22 @@ constexpr int HASH_MAX = 200; #ifdef DDS_SCHEDULER - #define START_BLOCK_TIMER scheduler.StartBlockTimer() - #define END_BLOCK_TIMER scheduler.EndBlockTimer() - #define START_THREAD_TIMER(a) scheduler.StartThreadTimer(a) - #define END_THREAD_TIMER(a) scheduler.EndThreadTimer(a) + #define START_BLOCK_TIMER scheduler.StartBlockTimer() + #define END_BLOCK_TIMER scheduler.EndBlockTimer() + #define START_THREAD_TIMER(a) scheduler.StartThreadTimer(a) + #define END_THREAD_TIMER(a) scheduler.EndThreadTimer(a) #else - #define START_BLOCK_TIMER - #define END_BLOCK_TIMER - #define START_THREAD_TIMER(a) - #define END_THREAD_TIMER(a) + #define START_BLOCK_TIMER + #define END_BLOCK_TIMER + #define START_THREAD_TIMER(a) + #define END_THREAD_TIMER(a) #endif struct schedType { - int number; - int repeatOf; + int number; + int repeatOf; }; @@ -54,186 +54,186 @@ struct schedType */ class Scheduler { - private: + private: - struct listType - { - int first; - int last; - int length; - }; + struct listType + { + int first; + int last; + int length; + }; - struct groupType - { - int strain; - int hash; - int pred; - int actual; - int head; - int repeatNo; - }; + struct groupType + { + int strain; + int hash; + int pred; + int actual; + int head; + int repeatNo; + }; - struct sortType - { - int number; - int value; - }; + struct sortType + { + int number; + int value; + }; - struct handType - { - int next; - int spareKey; - unsigned remainCards[DDS_HANDS][DDS_SUITS]; - int NTflag; - int first; - int strain; - int repeatNo; - int depth; - int strength; - int fanout; - int thread; - int selectFlag; - int time; - }; + struct handType + { + int next; + int spareKey; + unsigned remainCards[DDS_HANDS][DDS_SUITS]; + int NTflag; + int first; + int strain; + int repeatNo; + int depth; + int strength; + int fanout; + int thread; + int selectFlag; + int time; + }; - handType hands[MAXNOOFBOARDS]; + handType hands[MAXNOOFBOARDS]; - groupType group[MAXNOOFBOARDS]; - int numGroups; - int extraGroups; + groupType group[MAXNOOFBOARDS]; + int numGroups; + int extraGroups; - std::atomic currGroup; + std::atomic currGroup; - listType list[DDS_SUITS + 2][HASH_MAX]; + listType list[DDS_SUITS + 2][HASH_MAX]; - sortType sortList[MAXNOOFBOARDS]; - int sortLen; + sortType sortList[MAXNOOFBOARDS]; + int sortLen; - std::vector threadGroup; - std::vector threadCurrGroup; - std::vector threadToHand; + std::vector threadGroup; + std::vector threadCurrGroup; + std::vector threadToHand; - int numThreads; - int numHands; + int numThreads; + int numHands; - std::vector highCards; + std::vector highCards; - void InitHighCards(); + void InitHighCards(); - void SortHands(const enum RunMode mode); + void SortHands(const enum RunMode mode); - int Strength(const Deal& dl) const; + int Strength(const Deal& dl) const; - void Reset(); + void Reset(); - std::vector timersThread; - Timer timerBlock; + std::vector timersThread; + Timer timerBlock; - void MakeGroups(const Boards& bds); + void MakeGroups(const Boards& bds); - void FinetuneGroups(); + void FinetuneGroups(); - bool SameHand( - const int hno1, - const int hno2) const; + bool SameHand( + const int hno1, + const int hno2) const; - void SortSolve(), + void SortSolve(), SortCalc(), SortTrace(); #ifdef DDS_SCHEDULER - int timeHist[10000]; - int timeHistNT[10000]; - int timeHistSuit[10000]; - - TimeStatList timeStrain; - TimeStatList timeRepeat; - TimeStatList timeDepth; - TimeStatList timeStrength; - TimeStatList timeFanout; - TimeStatList timeThread; - TimeStatList timeGroupActualStrain; - TimeStatList timeGroupPredStrain; - TimeStatList timeGroupDiffStrain; - - long long timeMax; - long long blockMax; - long long timeBlock; - - void InitTimes(); + int timeHist[10000]; + int timeHistNT[10000]; + int timeHistSuit[10000]; + + TimeStatList timeStrain; + TimeStatList timeRepeat; + TimeStatList timeDepth; + TimeStatList timeStrength; + TimeStatList timeFanout; + TimeStatList timeThread; + TimeStatList timeGroupActualStrain; + TimeStatList timeGroupPredStrain; + TimeStatList timeGroupDiffStrain; + + long long timeMax; + long long blockMax; + long long timeBlock; + + void InitTimes(); #endif - int PredictedTime( - Deal& dl, - int number) const; + int PredictedTime( + Deal& dl, + int number) const; - public: + public: - /** + /** * @brief Construct a new Scheduler object. * * Initializes all internal data structures, thread and board counters, and * prepares the scheduler for use in parallel double dummy solving. */ - Scheduler(); + Scheduler(); - /** + /** * @brief Destroy the Scheduler object and clean up resources. * * Releases all memory and performs cleanup of scheduler state. */ - /** + /** * @brief Destroy the Scheduler object and clean up resources. * * Releases all memory and performs cleanup of scheduler state. */ - ~Scheduler(); + ~Scheduler(); - void RegisterThreads( - const int n); + void RegisterThreads( + const int n); - void RegisterRun( - const enum RunMode mode, - const Boards& bds, - const PlayTracesBin& pl); + void RegisterRun( + const enum RunMode mode, + const Boards& bds, + const PlayTracesBin& pl); - void RegisterRun( - const enum RunMode mode, - const Boards& bds); + void RegisterRun( + const enum RunMode mode, + const Boards& bds); - schedType GetNumber(const int thrId); + schedType GetNumber(const int thrId); - int NumGroups() const; + int NumGroups() const; - /** + /** * @brief Retrieve per-board raw times collected by the scheduler. * * Fills outVec with pairs (boardIndex, userTimeUs) for each board in * the current run. Times are wall-clock microseconds. This is intended * for post-run reporting. */ - void GetBoardTimes(std::vector>& outVec) const; + void GetBoardTimes(std::vector>& outVec) const; - // Lightweight API to set a board's time in microseconds for reporting when - // full DDS_SCHEDULER timing is not enabled. Thread-safe for single-writer per-board. - // Values outside `[0, INT_MAX]` are saturated into `HandType::time` storage. - void SetBoardTime(int boardIndex, long long time_us); + // Lightweight API to set a board's time in microseconds for reporting when + // full DDS_SCHEDULER timing is not enabled. Thread-safe for single-writer per-board. + // Values outside `[0, INT_MAX]` are saturated into `HandType::time` storage. + void SetBoardTime(int boardIndex, long long time_us); - // Release timing storage early to avoid heavy destructor work at exit. - void ClearTiming(); + // Release timing storage early to avoid heavy destructor work at exit. + void ClearTiming(); #ifdef DDS_SCHEDULER - void StartThreadTimer(const int thrId); + void StartThreadTimer(const int thrId); - void EndThreadTimer(const int thrId); + void EndThreadTimer(const int thrId); - void StartBlockTimer(); + void StartBlockTimer(); - void EndBlockTimer(); + void EndBlockTimer(); - void PrintTiming() const; + void PrintTiming() const; #endif }; diff --git a/library/src/system/system.cpp b/library/src/system/system.cpp index 95486e579..7889fe21c 100644 --- a/library/src/system/system.cpp +++ b/library/src/system/system.cpp @@ -14,14 +14,14 @@ #include #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) - #include + #include #endif #if defined(_WIN32) || defined(__CYGWIN__) - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #include + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include #endif #include "system.hpp" @@ -33,100 +33,100 @@ using std::vector; // Boost: Disable some header warnings. #ifdef DDS_THREADS_BOOST - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4061 4191 4619 4623 5031) - #endif + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4061 4191 4619 4623 5031) + #endif - #include + #include - #ifdef _MSC_VER - #pragma warning(pop) - #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif #endif #ifdef DDS_THREADS_GCD - #include + #include #endif #ifdef DDS_THREADS_STL - #include + #include #endif #ifdef DDS_THREADS_STLIMPL - #include + #include #endif #ifdef DDS_THREADS_PPLIMPL - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4355 4619 5038) - #endif + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4355 4619 5038) + #endif - #include "ppl.h" + #include "ppl.h" - #ifdef _MSC_VER - #pragma warning(pop) - #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif #endif #ifdef DDS_THREADS_TBB - #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4574) - #endif + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4574) + #endif - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wold-style-cast" - #pragma GCC diagnostic ignored "-Wsign-conversion" - #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wold-style-cast" + #pragma GCC diagnostic ignored "-Wsign-conversion" + #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" - #include "tbb/tbb.h" - #include "tbb/tbb_thread.h" + #include "tbb/tbb.h" + #include "tbb/tbb_thread.h" - #pragma GCC diagnostic pop + #pragma GCC diagnostic pop - #ifdef _MSC_VER - #pragma warning(pop) - #endif + #ifdef _MSC_VER + #pragma warning(pop) + #endif #endif const vector DDS_SYSTEM_PLATFORM = { - "", - "Windows", - "Cygwin", - "Linux", - "Apple" + "", + "Windows", + "Cygwin", + "Linux", + "Apple" }; const vector DDS_SYSTEM_COMPILER = { - "", - "Microsoft Visual C++", - "MinGW", - "GNU g++", - "clang" + "", + "Microsoft Visual C++", + "MinGW", + "GNU g++", + "clang" }; const vector DDS_SYSTEM_CONSTRUCTOR = { - "", - "DllMain", - "Unix-style" + "", + "DllMain", + "Unix-style" }; const vector DDS_SYSTEM_THREADING = { - "None", - "Windows", - "OpenMP", - "GCD", - "Boost", - "STL", - "TBB", - "STL-impl", - "PPL-impl" + "None", + "Windows", + "OpenMP", + "GCD", + "Boost", + "STL", + "TBB", + "STL-impl", + "PPL-impl" }; constexpr int DDS_SYSTEM_THREAD_BASIC = 0; @@ -145,7 +145,7 @@ constexpr int DDS_SYSTEM_THREAD_SIZE = 9; System::System() { - System::reset(); + System::reset(); } @@ -156,143 +156,143 @@ System::~System() void System::reset() { - num_threads_ = 1; - preferred_system_ = DDS_SYSTEM_THREAD_BASIC; + num_threads_ = 1; + preferred_system_ = DDS_SYSTEM_THREAD_BASIC; - available_system_.resize(DDS_SYSTEM_THREAD_SIZE); - available_system_[DDS_SYSTEM_THREAD_BASIC] = true; - for (unsigned i = 1; i < DDS_SYSTEM_THREAD_SIZE; i++) - available_system_[i] = false; + available_system_.resize(DDS_SYSTEM_THREAD_SIZE); + available_system_[DDS_SYSTEM_THREAD_BASIC] = true; + for (unsigned i = 1; i < DDS_SYSTEM_THREAD_SIZE; i++) + available_system_[i] = false; #ifdef DDS_THREADS_WINAPI - available_system_[DDS_SYSTEM_THREAD_WINAPI] = true; + available_system_[DDS_SYSTEM_THREAD_WINAPI] = true; #endif #ifdef DDS_THREADS_OPENMP - available_system_[DDS_SYSTEM_THREAD_OPENMP] = true; + available_system_[DDS_SYSTEM_THREAD_OPENMP] = true; #endif #ifdef DDS_THREADS_GCD - available_system_[DDS_SYSTEM_THREAD_GCD] = true; + available_system_[DDS_SYSTEM_THREAD_GCD] = true; #endif #ifdef DDS_THREADS_BOOST - available_system_[DDS_SYSTEM_THREAD_BOOST] = true; + available_system_[DDS_SYSTEM_THREAD_BOOST] = true; #endif #ifdef DDS_THREADS_STL - available_system_[DDS_SYSTEM_THREAD_STL] = true; + available_system_[DDS_SYSTEM_THREAD_STL] = true; #endif #ifdef DDS_THREADS_TBB - available_system_[DDS_SYSTEM_THREAD_TBB] = true; + available_system_[DDS_SYSTEM_THREAD_TBB] = true; #endif #ifdef DDS_THREADS_STLIMPL - available_system_[DDS_SYSTEM_THREAD_STLIMPL] = true; + available_system_[DDS_SYSTEM_THREAD_STLIMPL] = true; #endif #ifdef DDS_THREADS_PPLIMPL - available_system_[DDS_SYSTEM_THREAD_PPLIMPL] = true; + available_system_[DDS_SYSTEM_THREAD_PPLIMPL] = true; #endif - // Take the first of any multi-threading system defined. - for (unsigned k = 1; k < available_system_.size(); k++) - { - if (available_system_[k]) + // Take the first of any multi-threading system defined. + for (unsigned k = 1; k < available_system_.size(); k++) { - preferred_system_ = k; - break; + if (available_system_[k]) + { + preferred_system_ = k; + break; + } } - } } void System::get_hardware( - int& core_count, - unsigned long long& kilobytes_free) const + int& core_count, + unsigned long long& kilobytes_free) const { - kilobytes_free = 0; - core_count = System::get_cores(); + kilobytes_free = 0; + core_count = System::get_cores(); #if defined(__EMSCRIPTEN__) - // sysconf/physical memory queries are unreliable under Emscripten; use a - // conservative default so SetResources allocates a usable transposition table. - // Core count comes from get_cores() / hardware_concurrency (pthreads WASM). - kilobytes_free = 512ULL * 1024; + // sysconf/physical memory queries are unreliable under Emscripten; use a + // conservative default so SetResources allocates a usable transposition table. + // Core count comes from get_cores() / hardware_concurrency (pthreads WASM). + kilobytes_free = 512ULL * 1024; #elif defined(_WIN32) || defined(__CYGWIN__) - // Using GlobalMemoryStatusEx instead of GlobalMemoryStatus - // was suggested by Lorne Anderson. - MEMORYSTATUSEX statex; - statex.dwLength = sizeof(statex); - GlobalMemoryStatusEx(&statex); - kilobytes_free = static_cast( - statex.ullTotalPhys / 1024); - - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - core_count = static_cast(sysinfo.dwNumberOfProcessors); + // Using GlobalMemoryStatusEx instead of GlobalMemoryStatus + // was suggested by Lorne Anderson. + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx(&statex); + kilobytes_free = static_cast( + statex.ullTotalPhys / 1024); + + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + core_count = static_cast(sysinfo.dwNumberOfProcessors); #elif defined(__APPLE__) - // The code for Mac OS X was suggested by Matthew Kidd. - - // This is physical memory, rather than "free" memory as below - // for Linux. Always leave 0.5 GB for the OS and other stuff. - // It would be better to find free memory (how?) but in practice - // the number of cores rather than free memory is almost certainly - // the limit for Macs which have standardized hardware (whereas - // say a 32 core Linux server is hardly unusual). - FILE * fifo = popen("sysctl -n hw.memsize", "r"); - fscanf(fifo, "%lld", &kilobytes_free); - fclose(fifo); - - kilobytes_free /= 1024; - if (kilobytes_free > 500000) - { - kilobytes_free -= 500000; - } - - core_count = sysconf(_SC_NPROCESSORS_ONLN); + // The code for Mac OS X was suggested by Matthew Kidd. + + // This is physical memory, rather than "free" memory as below + // for Linux. Always leave 0.5 GB for the OS and other stuff. + // It would be better to find free memory (how?) but in practice + // the number of cores rather than free memory is almost certainly + // the limit for Macs which have standardized hardware (whereas + // say a 32 core Linux server is hardly unusual). + FILE * fifo = popen("sysctl -n hw.memsize", "r"); + fscanf(fifo, "%lld", &kilobytes_free); + fclose(fifo); + + kilobytes_free /= 1024; + if (kilobytes_free > 500000) + { + kilobytes_free -= 500000; + } + + core_count = sysconf(_SC_NPROCESSORS_ONLN); #elif defined(__linux__) - // Use half of the physical memory - long pages = sysconf (_SC_PHYS_PAGES); - long pagesize = sysconf (_SC_PAGESIZE); - if (pages > 0 && pagesize > 0) - kilobytes_free = static_cast(pages * pagesize / 1024 / 2); - else - kilobytes_free = 1024 * 1024; // guess 1GB - - core_count = sysconf(_SC_NPROCESSORS_ONLN); + // Use half of the physical memory + long pages = sysconf (_SC_PHYS_PAGES); + long pagesize = sysconf (_SC_PAGESIZE); + if (pages > 0 && pagesize > 0) + kilobytes_free = static_cast(pages * pagesize / 1024 / 2); + else + kilobytes_free = 1024 * 1024; // guess 1GB + + core_count = sysconf(_SC_NPROCESSORS_ONLN); #else - // Fallback if no platform is detected - kilobytes_free = 512ULL * 1024; + // Fallback if no platform is detected + kilobytes_free = 512ULL * 1024; #endif } int System::register_params( - const int n_threads, - const int mem_usable_mb) + const int n_threads, + const int mem_usable_mb) { - // No upper limit -- caveat emptor. - if (n_threads < 1) - return RETURN_THREAD_INDEX; + // No upper limit -- caveat emptor. + if (n_threads < 1) + return RETURN_THREAD_INDEX; - num_threads_ = n_threads; - sys_mem_mb_ = mem_usable_mb; - return RETURN_NO_FAULT; + num_threads_ = n_threads; + sys_mem_mb_ = mem_usable_mb; + return RETURN_NO_FAULT; } int System::prefer_threading(const unsigned code) { - if (code >= DDS_SYSTEM_THREAD_SIZE) - return RETURN_THREAD_MISSING; + if (code >= DDS_SYSTEM_THREAD_SIZE) + return RETURN_THREAD_MISSING; - if (! available_system_[code]) - return RETURN_THREAD_MISSING; + if (! available_system_[code]) + return RETURN_THREAD_MISSING; - preferred_system_ = code; - return RETURN_NO_FAULT; + preferred_system_ = code; + return RETURN_NO_FAULT; } @@ -301,135 +301,135 @@ int System::prefer_threading(const unsigned code) ////////////////////////////////////////////////////////////////////// string System::get_version( - int& major, - int& minor, - int& patch) const + int& major, + int& minor, + int& patch) const { - major = DDS_VERSION / 10000; - minor = (DDS_VERSION - major * 10000) / 100; - patch = DDS_VERSION % 100; + major = DDS_VERSION / 10000; + minor = (DDS_VERSION - major * 10000) / 100; + patch = DDS_VERSION % 100; - string st = to_string(major) + "." + to_string(minor) + - "." + to_string(patch); - return st; + string st = to_string(major) + "." + to_string(minor) + + "." + to_string(patch); + return st; } string System::get_system(int& sys) const { #if defined(_WIN32) - sys = 1; + sys = 1; #elif defined(__CYGWIN__) - sys = 2; + sys = 2; #elif defined(__linux) - sys = 3; + sys = 3; #elif defined(__APPLE__) - sys = 4; + sys = 4; #else - sys = 0; + sys = 0; #endif - - return DDS_SYSTEM_PLATFORM[static_cast(sys)]; + + return DDS_SYSTEM_PLATFORM[static_cast(sys)]; } string System::get_bits(int& bits) const { #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4127) + #pragma warning(push) + #pragma warning(disable: 4127) #endif - string st; - if (sizeof(void *) == 4) - { - bits = 32; - st = "32 bits"; - } - else if (sizeof(void *) == 8) - { - bits = 64; - st = "64 bits"; - } - else - { - bits = 0; - st = "unknown"; - } + string st; + if (sizeof(void *) == 4) + { + bits = 32; + st = "32 bits"; + } + else if (sizeof(void *) == 8) + { + bits = 64; + st = "64 bits"; + } + else + { + bits = 0; + st = "unknown"; + } #ifdef _MSC_VER - #pragma warning(pop) + #pragma warning(pop) #endif - - return st; + + return st; } string System::get_compiler(int& comp) const { #if defined(_MSC_VER) - comp = 1; + comp = 1; #elif defined(__MINGW32__) - comp = 2; + comp = 2; #elif defined(__clang__) - comp = 4; // Out-of-order on purpose + comp = 4; // Out-of-order on purpose #elif defined(__GNUC__) - comp = 3; + comp = 3; #else - comp = 0; + comp = 0; #endif - return DDS_SYSTEM_COMPILER[static_cast(comp)]; + return DDS_SYSTEM_COMPILER[static_cast(comp)]; } string System::get_constructor(int& cons) const { #if defined(USES_DLLMAIN) - cons = 1; + cons = 1; #elif defined(USES_CONSTRUCTOR) - cons = 2; + cons = 2; #else - cons = 0; + cons = 0; #endif - return DDS_SYSTEM_CONSTRUCTOR[static_cast(cons)]; + return DDS_SYSTEM_CONSTRUCTOR[static_cast(cons)]; } int System::get_cores() const { - const unsigned int hw = std::thread::hardware_concurrency(); - if (hw > 0) - return static_cast(hw); + const unsigned int hw = std::thread::hardware_concurrency(); + if (hw > 0) + return static_cast(hw); - int cores = 0; + int cores = 0; #if defined(_WIN32) || defined(__CYGWIN__) - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - cores = static_cast(sysinfo.dwNumberOfProcessors); + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + cores = static_cast(sysinfo.dwNumberOfProcessors); #elif defined(__APPLE__) || defined(__linux__) - cores = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); + cores = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); #endif - return cores; + return cores; } string System::get_threading(int& thr) const { - string st = ""; - thr = 0; - for (unsigned k = 0; k < DDS_SYSTEM_THREAD_SIZE; k++) - { - if (available_system_[k]) + string st = ""; + thr = 0; + for (unsigned k = 0; k < DDS_SYSTEM_THREAD_SIZE; k++) { - st += " " + DDS_SYSTEM_THREADING[k]; - if (k == preferred_system_) - { - st += "(*)"; - thr = static_cast(k); - } + if (available_system_[k]) + { + st += " " + DDS_SYSTEM_THREADING[k]; + if (k == preferred_system_) + { + st += "(*)"; + thr = static_cast(k); + } + } } - } - return st; + return st; } \ No newline at end of file diff --git a/library/src/system/system.hpp b/library/src/system/system.hpp index ecd7b40e0..323a87ee1 100644 --- a/library/src/system/system.hpp +++ b/library/src/system/system.hpp @@ -21,7 +21,7 @@ #include typedef void (*FduplType)( - const Boards& bds, std::vector& uniques, std::vector& crossrefs); + const Boards& bds, std::vector& uniques, std::vector& crossrefs); typedef void (*FcopyType)(const std::vector& crossrefs); @@ -36,7 +36,7 @@ typedef void (*FcopyType)(const std::vector& crossrefs); */ class System { - private: + private: int num_threads_; int sys_mem_mb_; @@ -47,9 +47,9 @@ class System public: std::string get_version( - int& major, - int& minor, - int& patch) const; + int& major, + int& minor, + int& patch) const; std::string get_system(int& sys) const; std::string get_bits(int& bits) const; std::string get_compiler(int& comp) const; @@ -76,12 +76,12 @@ class System void reset(); int register_params( - const int n_threads, - const int mem_usable_mb); + const int n_threads, + const int mem_usable_mb); void get_hardware( - int& core_count, - unsigned long long& kilobytes_free) const; + int& core_count, + unsigned long long& kilobytes_free) const; int prefer_threading(const unsigned code); }; diff --git a/library/src/system/thread_data.cpp b/library/src/system/thread_data.cpp index 7b7d40725..34348ac4e 100644 --- a/library/src/system/thread_data.cpp +++ b/library/src/system/thread_data.cpp @@ -7,66 +7,66 @@ using std::string; void ThreadData::init_debug_files([[maybe_unused]] const string& suffix) { - if (debug_files_initialized_) - return; + if (debug_files_initialized_) + return; #ifdef DDS_TOP_LEVEL - fileTopLevel.SetName(DDS_TOP_LEVEL_PREFIX + suffix); + fileTopLevel.SetName(DDS_TOP_LEVEL_PREFIX + suffix); #endif #ifdef DDS_AB_STATS - fileABstats.SetName(DDS_AB_STATS_PREFIX + suffix); + fileABstats.SetName(DDS_AB_STATS_PREFIX + suffix); #endif #ifdef DDS_AB_HITS - fileRetrieved.SetName(DDS_AB_HITS_RETRIEVED_PREFIX + suffix); - fileStored.SetName(DDS_AB_HITS_STORED_PREFIX + suffix); + fileRetrieved.SetName(DDS_AB_HITS_RETRIEVED_PREFIX + suffix); + fileStored.SetName(DDS_AB_HITS_STORED_PREFIX + suffix); #endif #ifdef DDS_TT_STATS - fileTTstats.SetName(DDS_TT_STATS_PREFIX + suffix); + fileTTstats.SetName(DDS_TT_STATS_PREFIX + suffix); #endif #ifdef DDS_TIMING - fileTimerList.SetName(DDS_TIMING_PREFIX + suffix); + fileTimerList.SetName(DDS_TIMING_PREFIX + suffix); #endif #ifdef DDS_MOVES - fileMoves.SetName(DDS_MOVES_PREFIX + suffix); + fileMoves.SetName(DDS_MOVES_PREFIX + suffix); #endif - debug_files_initialized_ = true; + debug_files_initialized_ = true; } void ThreadData::close_debug_files() { - if (!debug_files_initialized_) - return; + if (!debug_files_initialized_) + return; #ifdef DDS_TOP_LEVEL - fileTopLevel.Close(); + fileTopLevel.Close(); #endif #ifdef DDS_AB_STATS - fileABstats.Close(); + fileABstats.Close(); #endif #ifdef DDS_AB_HITS - fileRetrieved.Close(); - fileStored.Close(); + fileRetrieved.Close(); + fileStored.Close(); #endif #ifdef DDS_TT_STATS - fileTTstats.Close(); + fileTTstats.Close(); #endif #ifdef DDS_TIMING - fileTimerList.Close(); + fileTimerList.Close(); #endif #ifdef DDS_MOVES - fileMoves.Close(); + fileMoves.Close(); #endif - debug_files_initialized_ = false; + debug_files_initialized_ = false; } diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index 8af9971ef..30479642d 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -13,113 +13,113 @@ #endif #if defined(DDS_TOP_LEVEL) || defined(DDS_AB_STATS) || defined(DDS_AB_HITS) || \ - defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) + defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) #include "file.hpp" #endif #ifdef DDS_TIMING - #include + #include #endif enum TTmemory { - DDS_TT_SMALL = 0, - DDS_TT_LARGE = 1 + DDS_TT_SMALL = 0, + DDS_TT_LARGE = 1 }; struct WinnerEntryType { - int suit; - int winnerRank; - int winnerHand; - int secondRank; - int secondHand; + int suit; + int winnerRank; + int winnerHand; + int secondRank; + int secondHand; }; struct WinnersType { - int number; - WinnerEntryType winner[4]; + int number; + WinnerEntryType winner[4]; }; struct ThreadData { - int nodeTypeStore[DDS_HANDS]; - int iniDepth; - bool val; - - unsigned short int suit[DDS_HANDS][DDS_SUITS]; - int trump; - - Pos lookAheadPos; // Recursive alpha-beta data - bool analysisFlag; - unsigned short int lowestWin[50][DDS_SUITS]; - WinnersType winners[13]; - MoveType forbiddenMoves[14]; - MoveType bestMove[50]; - MoveType bestMoveTT[50]; - - double memUsed; - int nodes; - int trickNodes; - // TT instrumentation (per-context, single-threaded). - // tt_lookup_count: total TT probe calls on the AB hot path. - // tt_hit_count: probes that returned a cached result (higher is better). - // hit_rate = tt_hit_count / tt_lookup_count; target: maximize. - uint64_t tt_lookup_count = 0; - uint64_t tt_hit_count = 0; - - // Constant for a given hand. - // 960 KB - RelRanksType rel[8192]; - - // Deferred TT configuration for context-owned construction - // TransTable configuration moved to SolverContext::SolverConfig and - // per-context member in SolverContext::SearchContext. - - Moves moves; + int nodeTypeStore[DDS_HANDS]; + int iniDepth; + bool val; + + unsigned short int suit[DDS_HANDS][DDS_SUITS]; + int trump; + + Pos lookAheadPos; // Recursive alpha-beta data + bool analysisFlag; + unsigned short int lowestWin[50][DDS_SUITS]; + WinnersType winners[13]; + MoveType forbiddenMoves[14]; + MoveType bestMove[50]; + MoveType bestMoveTT[50]; + + double memUsed; + int nodes; + int trickNodes; + // TT instrumentation (per-context, single-threaded). + // tt_lookup_count: total TT probe calls on the AB hot path. + // tt_hit_count: probes that returned a cached result (higher is better). + // hit_rate = tt_hit_count / tt_lookup_count; target: maximize. + uint64_t tt_lookup_count = 0; + uint64_t tt_hit_count = 0; + + // Constant for a given hand. + // 960 KB + RelRanksType rel[8192]; + + // Deferred TT configuration for context-owned construction + // TransTable configuration moved to SolverContext::SolverConfig and + // per-context member in SolverContext::SearchContext. + + Moves moves; #ifdef DDS_TOP_LEVEL - dds::File fileTopLevel; + dds::File fileTopLevel; #endif #ifdef DDS_AB_STATS - ABstats ABStats; - dds::File fileABstats; + ABstats ABStats; + dds::File fileABstats; #endif #ifdef DDS_AB_HITS - dds::File fileRetrieved; - dds::File fileStored; + dds::File fileRetrieved; + dds::File fileStored; #endif #ifdef DDS_TT_STATS - dds::File fileTTstats; + dds::File fileTTstats; #endif #ifdef DDS_TIMING - TimerList timerList; - dds::File fileTimerList; + TimerList timerList; + dds::File fileTimerList; #endif #ifdef DDS_MOVES - dds::File fileMoves; + dds::File fileMoves; #endif - // True after init_debug_files(); cleared by close_debug_files(). - bool debug_files_initialized_ = false; + // True after init_debug_files(); cleared by close_debug_files(). + bool debug_files_initialized_ = false; - // Initialize per-thread debug/stat files. suffix is appended to each debug - // prefix (e.g. "0.txt" from SolverContext serial + DDS_DEBUG_SUFFIX). - void init_debug_files([[maybe_unused]] const std::string& suffix); + // Initialize per-thread debug/stat files. suffix is appended to each debug + // prefix (e.g. "0.txt" from SolverContext serial + DDS_DEBUG_SUFFIX). + void init_debug_files([[maybe_unused]] const std::string& suffix); - // Close any open per-thread debug/stat files. - void close_debug_files(); + // Close any open per-thread debug/stat files. + void close_debug_files(); - auto debug_files_initialized() const -> bool - { - return debug_files_initialized_; - } + auto debug_files_initialized() const -> bool + { + return debug_files_initialized_; + } }; diff --git a/library/src/system/thread_mgr.cpp b/library/src/system/thread_mgr.cpp index 358cc37ee..12072bc21 100644 --- a/library/src/system/thread_mgr.cpp +++ b/library/src/system/thread_mgr.cpp @@ -37,131 +37,131 @@ ThreadMgr::~ThreadMgr() { } void ThreadMgr::Reset(const int nThreads) { - const unsigned n = static_cast(nThreads); - if (n > numRealThreads) - { - realThreads.resize(n); - for (unsigned t = numRealThreads; t < n; t++) - realThreads[t] = false; - numRealThreads = n; - } - - if (n > numMachineThreads) - { - machineThreads.resize(n); - for (unsigned t = numMachineThreads; t < n; t++) - machineThreads[t] = -1; - numMachineThreads = n; - } + const unsigned n = static_cast(nThreads); + if (n > numRealThreads) + { + realThreads.resize(n); + for (unsigned t = numRealThreads; t < n; t++) + realThreads[t] = false; + numRealThreads = n; + } + + if (n > numMachineThreads) + { + machineThreads.resize(n); + for (unsigned t = numMachineThreads; t < n; t++) + machineThreads[t] = -1; + numMachineThreads = n; + } } int ThreadMgr::Occupy(const int machineId) { - const unsigned m = static_cast(machineId); - if (m >= numMachineThreads) - { - numMachineThreads = m + 1; - machineThreads.resize(numMachineThreads); - for (unsigned t = m; t < numMachineThreads; t++) - machineThreads[t] = -1; - } - - if (machineThreads[m] != -1) - { - // Error: Already in use. - return -1; - } - - int res = -1; - - do - { - mtx.lock(); - for (unsigned t = 0; t < numRealThreads; t++) + const unsigned m = static_cast(machineId); + if (m >= numMachineThreads) { - if (realThreads[t] == false) - { - const int ti = static_cast(t); - realThreads[t] = true; - machineThreads[m] = ti; - res = ti; - break; - } + numMachineThreads = m + 1; + machineThreads.resize(numMachineThreads); + for (unsigned t = m; t < numMachineThreads; t++) + machineThreads[t] = -1; } - // ThreadMgr::Print("thr.txt", "In Occupy " + - // to_string(machineId) + " " + to_string(res)); - mtx.unlock(); - if (res == -1) + if (machineThreads[m] != -1) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + // Error: Already in use. + return -1; } - } - while (res == -1); - return res; + int res = -1; + + do + { + mtx.lock(); + for (unsigned t = 0; t < numRealThreads; t++) + { + if (realThreads[t] == false) + { + const int ti = static_cast(t); + realThreads[t] = true; + machineThreads[m] = ti; + res = ti; + break; + } + } + // ThreadMgr::Print("thr.txt", "In Occupy " + + // to_string(machineId) + " " + to_string(res)); + mtx.unlock(); + + if (res == -1) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + while (res == -1); + + return res; } bool ThreadMgr::Release(const int machineId) { - mtx.lock(); - - bool ret; - const unsigned m = static_cast(machineId); - const int r = machineThreads[m]; - const unsigned ru = static_cast(r); - - if (r == -1) - { - // Error: Not in use. - ret = false; - } - else if (! realThreads[ru]) - { - // Error: Refers to a real thread that is not in use. - ret = false; - } - else - { - realThreads[ru] = false; - machineThreads[m] = -1; - ret = true; - } - - mtx.unlock(); - return ret; + mtx.lock(); + + bool ret; + const unsigned m = static_cast(machineId); + const int r = machineThreads[m]; + const unsigned ru = static_cast(r); + + if (r == -1) + { + // Error: Not in use. + ret = false; + } + else if (! realThreads[ru]) + { + // Error: Refers to a real thread that is not in use. + ret = false; + } + else + { + realThreads[ru] = false; + machineThreads[m] = -1; + ret = true; + } + + mtx.unlock(); + return ret; } void ThreadMgr::Print( - const string& fname, - const string& tag) const + const string& fname, + const string& tag) const { - mtxPrint.lock(); - ofstream fo; - fo.open(fname, std::ios_base::app); - - fo << tag << - ": Real threads occupied (out of " << numRealThreads << "):\n"; - for (unsigned t = 0; t < numRealThreads; t++) - { - if (realThreads[t]) - fo << t << endl; - } - fo << endl; - - fo << "Machine threads overview:\n"; - for (unsigned t = 0; t < numMachineThreads; t++) - { - if (machineThreads[t] != -1) + mtxPrint.lock(); + ofstream fo; + fo.open(fname, std::ios_base::app); + + fo << tag << + ": Real threads occupied (out of " << numRealThreads << "):\n"; + for (unsigned t = 0; t < numRealThreads; t++) + { + if (realThreads[t]) + fo << t << endl; + } + fo << endl; + + fo << "Machine threads overview:\n"; + for (unsigned t = 0; t < numMachineThreads; t++) { - fo << setw(4) << left << t << machineThreads[t] << endl; + if (machineThreads[t] != -1) + { + fo << setw(4) << left << t << machineThreads[t] << endl; + } } - } - fo << endl; - fo.close(); - mtxPrint.unlock(); + fo << endl; + fo.close(); + mtxPrint.unlock(); } diff --git a/library/src/system/thread_mgr.hpp b/library/src/system/thread_mgr.hpp index 3811f026e..e729972ea 100644 --- a/library/src/system/thread_mgr.hpp +++ b/library/src/system/thread_mgr.hpp @@ -24,14 +24,14 @@ */ class ThreadMgr { - private: + private: std::vector realThreads; std::vector machineThreads; unsigned numRealThreads; unsigned numMachineThreads; - /** + /** * @brief Construct a new ThreadMgr object. * * Initializes thread tracking structures and prepares the manager for use. @@ -42,7 +42,7 @@ class ThreadMgr static ThreadMgr single_instance; - public: + public: /** * @brief Destroy the ThreadMgr object and clean up resources. @@ -62,8 +62,8 @@ class ThreadMgr bool Release(const int MachineThrId); void Print( - const std::string& fname, - const std::string& tag) const; + const std::string& fname, + const std::string& tag) const; }; #endif diff --git a/library/src/system/time_stat.cpp b/library/src/system/time_stat.cpp index 76ff054fb..301db9192 100644 --- a/library/src/system/time_stat.cpp +++ b/library/src/system/time_stat.cpp @@ -26,7 +26,7 @@ using std::stringstream; TimeStat::TimeStat() { - TimeStat::Reset(); + TimeStat::Reset(); } @@ -37,72 +37,72 @@ TimeStat::~TimeStat() void TimeStat::Reset() { - number = 0; - cum = 0; - cumsq = 0.; + number = 0; + cum = 0; + cumsq = 0.; } void TimeStat::Set(const int timeUser) { - number = 1; - cum = timeUser; - cumsq = static_cast(timeUser) * static_cast(timeUser); + number = 1; + cum = timeUser; + cumsq = static_cast(timeUser) * static_cast(timeUser); } void TimeStat::Set( - const int timeUser, - const double timesq) + const int timeUser, + const double timesq) { - number = 1; - cum = timeUser; - cumsq = timesq; + number = 1; + cum = timeUser; + cumsq = timesq; } void TimeStat::operator +=(const TimeStat& add) { - number += add.number; - cum += add.cum; - cumsq += add.cumsq; + number += add.number; + cum += add.cum; + cumsq += add.cumsq; } bool TimeStat::Used() const { - return (number > 0); + return (number > 0); } string TimeStat::Header() const { - stringstream ss; - ss << setw(5) << right << "n" << - setw(9) << right << "Number" << - setw(13) << "Cum time" << - setw(13) << "Average" << - setw(13) << "Sdev" << - setw(13) << "Sdev/mu" << "\n"; - - return ss.str(); + stringstream ss; + ss << setw(5) << right << "n" << + setw(9) << right << "Number" << + setw(13) << "Cum time" << + setw(13) << "Average" << + setw(13) << "Sdev" << + setw(13) << "Sdev/mu" << "\n"; + + return ss.str(); } string TimeStat::Line() const { - if (number == 0) - return ""; + if (number == 0) + return ""; - double avg = static_cast(cum) / static_cast(number); - double arg = (cumsq / static_cast(number)) - avg * avg; - double sdev = (arg >= 0. ? sqrt(arg) : 0.); + double avg = static_cast(cum) / static_cast(number); + double arg = (cumsq / static_cast(number)) - avg * avg; + double sdev = (arg >= 0. ? sqrt(arg) : 0.); - stringstream ss; - ss << setw(9) << right << number << - setw(13) << cum << - setw(13) << setprecision(0) << fixed << avg << - setw(13) << setprecision(0) << fixed << sdev << - setw(13) << setprecision(2) << fixed << sdev/avg << "\n"; + stringstream ss; + ss << setw(9) << right << number << + setw(13) << cum << + setw(13) << setprecision(0) << fixed << avg << + setw(13) << setprecision(0) << fixed << sdev << + setw(13) << setprecision(2) << fixed << sdev/avg << "\n"; - return ss.str(); + return ss.str(); } diff --git a/library/src/system/time_stat.hpp b/library/src/system/time_stat.hpp index bf7af4c75..3fe3b5037 100644 --- a/library/src/system/time_stat.hpp +++ b/library/src/system/time_stat.hpp @@ -22,13 +22,13 @@ */ class TimeStat { - private: + private: int number; long long cum; double cumsq; - public: + public: /** * @brief Construct a new TimeStat object. @@ -48,8 +48,8 @@ class TimeStat void Set(const int timeUser); void Set( - const int timeUser, - const double timesq); + const int timeUser, + const double timesq); void operator += (const TimeStat& add); diff --git a/library/src/system/time_stat_list.cpp b/library/src/system/time_stat_list.cpp index d2f9e8cec..f784950e7 100644 --- a/library/src/system/time_stat_list.cpp +++ b/library/src/system/time_stat_list.cpp @@ -22,13 +22,13 @@ using std::stringstream; TimeStatList::TimeStatList() { - TimeStatList::Reset(); + TimeStatList::Reset(); } TimeStatList::~TimeStatList() { - TimeStatList::Clear(); + TimeStatList::Clear(); } @@ -38,64 +38,64 @@ void TimeStatList::Reset() void TimeStatList::Clear() { - list.clear(); - name.clear(); + list.clear(); + name.clear(); } void TimeStatList::Init( - const string& tname, - const unsigned len) + const string& tname, + const unsigned len) { - name = tname; - list.resize(len); + name = tname; + list.resize(len); } void TimeStatList::Add( - const unsigned Pos, - const TimeStat& add) + const unsigned Pos, + const TimeStat& add) { - if (Pos < list.size()) { - list[Pos] += add; - } else { - std::cerr << "[E] TimeStatList::Add(): Pos " << Pos << " out of range, size " << list.size() << "\n"; - } + if (Pos < list.size()) { + list[Pos] += add; + } else { + std::cerr << "[E] TimeStatList::Add(): Pos " << Pos << " out of range, size " << list.size() << "\n"; + } } bool TimeStatList::Used() const { - for (unsigned i = 0; i < list.size(); i++) - { - if (list[i].Used()) - return true; - } - return false; + for (unsigned i = 0; i < list.size(); i++) + { + if (list[i].Used()) + return true; + } + return false; } string TimeStatList::List() const { - if (! TimeStatList::Used()) - return ""; + if (! TimeStatList::Used()) + return ""; - stringstream ss; - ss << name << "\n\n"; - ss << list[0].Header(); + stringstream ss; + ss << name << "\n\n"; + ss << list[0].Header(); - TimeStat tsum; - for (unsigned i = 0; i < list.size(); i++) - { - if (! list[i].Used()) - continue; + TimeStat tsum; + for (unsigned i = 0; i < list.size(); i++) + { + if (! list[i].Used()) + continue; - tsum += list[i]; - ss << setw(5) << right << i << list[i].Line(); - } + tsum += list[i]; + ss << setw(5) << right << i << list[i].Line(); + } - ss << setw(5) << right << "Avg" << tsum.Line() << "\n"; + ss << setw(5) << right << "Avg" << tsum.Line() << "\n"; - return ss.str(); + return ss.str(); } diff --git a/library/src/system/time_stat_list.hpp b/library/src/system/time_stat_list.hpp index 7b3129ea6..ce4e319e8 100644 --- a/library/src/system/time_stat_list.hpp +++ b/library/src/system/time_stat_list.hpp @@ -25,13 +25,13 @@ */ class TimeStatList { - private: + private: std::vector list; std::string name; - public: + public: /** * @brief Construct a new TimeStatList object. @@ -50,12 +50,12 @@ class TimeStatList void Reset(); void Init( - const std::string& tname, - const unsigned len); + const std::string& tname, + const unsigned len); void Add( - const unsigned Pos, - const TimeStat& add); + const unsigned Pos, + const TimeStat& add); bool Used() const; diff --git a/library/src/system/timer.cpp b/library/src/system/timer.cpp index 51f1cee15..3a3f8a09d 100644 --- a/library/src/system/timer.cpp +++ b/library/src/system/timer.cpp @@ -28,7 +28,7 @@ using std::stringstream; Timer::Timer() { - Timer::Reset(); + Timer::Reset(); } @@ -39,124 +39,124 @@ Timer::~Timer() void Timer::Reset() { - name = ""; - count = 0; - userCum = 0; - systCum = 0; + name = ""; + count = 0; + userCum = 0; + systCum = 0; } void Timer::SetName(const string& nameIn) { - name = nameIn; + name = nameIn; } void Timer::Start() { - user0 = Clock::now(); - syst0 = std::clock(); + user0 = Clock::now(); + syst0 = std::clock(); } void Timer::End() { - std::chrono::time_point user1 = Clock::now(); - std::clock_t syst1 = std::clock(); + std::chrono::time_point user1 = Clock::now(); + std::clock_t syst1 = std::clock(); - std::chrono::duration d = user1 - user0; - int tuser = static_cast(d.count()); + std::chrono::duration d = user1 - user0; + int tuser = static_cast(d.count()); - count++; - userCum += tuser; - systCum += static_cast(syst1) - - static_cast(syst0); + count++; + userCum += tuser; + systCum += static_cast(syst1) - + static_cast(syst0); } bool Timer::Used() const { - return (count > 0); + return (count > 0); } int Timer::UserTime() const { - return static_cast(userCum); + return static_cast(userCum); } void Timer::operator +=(const Timer& add) { - count += add.count; - userCum += add.userCum; - systCum += add.systCum; + count += add.count; + userCum += add.userCum; + systCum += add.systCum; } void Timer::operator -=(const Timer& deduct) { - if (deduct.userCum > userCum) - userCum = 0; - else - userCum -= deduct.userCum; - - if (deduct.systCum > systCum) - systCum = 0; - else - systCum -= deduct.systCum; + if (deduct.userCum > userCum) + userCum = 0; + else + userCum -= deduct.userCum; + + if (deduct.systCum > systCum) + systCum = 0; + else + systCum -= deduct.systCum; } string Timer::SumLine( - const Timer& divisor, - const string& bname) const + const Timer& divisor, + const string& bname) const { - stringstream ss; - if (count > 0) - { - ss << setw(14) << left << (bname == "" ? name : bname) << - setw(9) << right << count << - setw(11) << userCum << - setw(7) << setprecision(2) << fixed << - userCum / static_cast(count) << - setw(5) << setprecision(1) << fixed << - 100. * userCum / divisor.userCum << - setw(11) << setprecision(0) << fixed << - 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << - setw(7) << setprecision(2) << fixed << - 1000000 * systCum / static_cast(count * CLOCKS_PER_SEC) << - setw(5) << setprecision(1) << fixed << - 100. * systCum / divisor.systCum << "\n"; - } - else - { - ss << setw(14) << left << (bname == "" ? name : bname) << - setw(9) << right << count << - setw(11) << userCum << - setw(7) << "-" << - setw(5) << "-" << - setw(11) << 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << - setw(7) << "-" << - setw(5) << "-" << "\n"; - } - return ss.str(); + stringstream ss; + if (count > 0) + { + ss << setw(14) << left << (bname == "" ? name : bname) << + setw(9) << right << count << + setw(11) << userCum << + setw(7) << setprecision(2) << fixed << + userCum / static_cast(count) << + setw(5) << setprecision(1) << fixed << + 100. * userCum / divisor.userCum << + setw(11) << setprecision(0) << fixed << + 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << + setw(7) << setprecision(2) << fixed << + 1000000 * systCum / static_cast(count * CLOCKS_PER_SEC) << + setw(5) << setprecision(1) << fixed << + 100. * systCum / divisor.systCum << "\n"; + } + else + { + ss << setw(14) << left << (bname == "" ? name : bname) << + setw(9) << right << count << + setw(11) << userCum << + setw(7) << "-" << + setw(5) << "-" << + setw(11) << 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << + setw(7) << "-" << + setw(5) << "-" << "\n"; + } + return ss.str(); } string Timer::DetailLine() const { - stringstream ss; - ss << setw(15) << left << name << - setw(10) << right << count << - setw(11) << right << userCum << - setw(11) << setprecision(2) << fixed << - userCum / static_cast(count) << - setw(11) << setprecision(0) << fixed << - 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << - setw(11) << setprecision(2) << fixed << - 1000000 * systCum / - static_cast(count * CLOCKS_PER_SEC) << "\n"; - - return ss.str(); + stringstream ss; + ss << setw(15) << left << name << + setw(10) << right << count << + setw(11) << right << userCum << + setw(11) << setprecision(2) << fixed << + userCum / static_cast(count) << + setw(11) << setprecision(0) << fixed << + 1000000 * systCum / static_cast(CLOCKS_PER_SEC) << + setw(11) << setprecision(2) << fixed << + 1000000 * systCum / + static_cast(count * CLOCKS_PER_SEC) << "\n"; + + return ss.str(); } diff --git a/library/src/system/timer.hpp b/library/src/system/timer.hpp index 46e9d97f4..566dd2c3b 100644 --- a/library/src/system/timer.hpp +++ b/library/src/system/timer.hpp @@ -27,7 +27,7 @@ using Clock = std::chrono::steady_clock; */ class Timer { - private: + private: std::string name; unsigned int count; @@ -37,7 +37,7 @@ class Timer std::chrono::time_point user0; std::clock_t syst0; - public: + public: /** * @brief Construct a new Timer object. @@ -70,8 +70,8 @@ class Timer void operator -= (const Timer& deduct); std::string SumLine( - const Timer& divisor, - const std::string& bname = "") const; + const Timer& divisor, + const std::string& bname = "") const; std::string DetailLine() const; }; diff --git a/library/src/system/timer_group.cpp b/library/src/system/timer_group.cpp index 47d9f7775..6ea479570 100644 --- a/library/src/system/timer_group.cpp +++ b/library/src/system/timer_group.cpp @@ -29,7 +29,7 @@ constexpr int TIMER_DEPTH = 50; TimerGroup::TimerGroup() { - TimerGroup::Reset(); + TimerGroup::Reset(); } @@ -40,149 +40,149 @@ TimerGroup::~TimerGroup() void TimerGroup::Reset() { - timers.resize(TIMER_DEPTH); - for (unsigned i = 0; i < timers.size(); i++) - timers[i].Reset(); + timers.resize(TIMER_DEPTH); + for (unsigned i = 0; i < timers.size(); i++) + timers[i].Reset(); } void TimerGroup::SetNames(const string& baseName) { - string st; - if (baseName == "AB") - { - // Special format emphasizing the card number within the trick. - for (unsigned i = 0; i < timers.size(); i++) + string st; + if (baseName == "AB") { - st = baseName + to_string(i % 4) + " " + to_string(i); - timers[i].SetName(st); + // Special format emphasizing the card number within the trick. + for (unsigned i = 0; i < timers.size(); i++) + { + st = baseName + to_string(i % 4) + " " + to_string(i); + timers[i].SetName(st); + } } - } - else - { - for (unsigned i = 0; i < timers.size(); i++) + else { - st = baseName + to_string(i); - timers[i].SetName(st); + for (unsigned i = 0; i < timers.size(); i++) + { + st = baseName + to_string(i); + timers[i].SetName(st); + } } - } - bname = baseName; + bname = baseName; } void TimerGroup::Start(const unsigned no) { - timers[no].Start(); + timers[no].Start(); } void TimerGroup::End(const unsigned no) { - timers[no].End(); + timers[no].End(); } bool TimerGroup::Used() const { - for (unsigned i = 0; i < timers.size(); i++) - { - if (timers[i].Used()) - return true; - } - return false; + for (unsigned i = 0; i < timers.size(); i++) + { + if (timers[i].Used()) + return true; + } + return false; } void TimerGroup::Differentiate() { - for (unsigned r = 0; r < timers.size()-1; r++) - { - size_t i = timers.size() - 1 - r; - timers[i] -= timers[i-1]; - } + for (unsigned r = 0; r < timers.size()-1; r++) + { + size_t i = timers.size() - 1 - r; + timers[i] -= timers[i-1]; + } } void TimerGroup::Sum(Timer& sum) const { - sum = timers[0]; - for (unsigned i = 1; i < timers.size(); i++) - sum += timers[i]; + sum = timers[0]; + for (unsigned i = 1; i < timers.size(); i++) + sum += timers[i]; } void TimerGroup::operator -= (const TimerGroup& deduct) { - for (unsigned i = 0; i < timers.size(); i++) - timers[i] -= deduct.timers[i]; + for (unsigned i = 0; i < timers.size(); i++) + timers[i] -= deduct.timers[i]; } string TimerGroup::Header() const { - stringstream ss; - ss << setw(14) << left << "Name" << - setw(9) << right << "Count" << - setw(11) << "User" << - setw(7) << "Avg" << - setw(5) << "%" << - setw(11) << "Syst" << - setw(7) << "Avg" << - setw(5) << "%" << "\n"; - return ss.str(); + stringstream ss; + ss << setw(14) << left << "Name" << + setw(9) << right << "Count" << + setw(11) << "User" << + setw(7) << "Avg" << + setw(5) << "%" << + setw(11) << "Syst" << + setw(7) << "Avg" << + setw(5) << "%" << "\n"; + return ss.str(); } string TimerGroup::DetailHeader() const { - stringstream ss; - ss << setw(14) << left << "Name " << - setw(11) << right << "Number" << - setw(11) << "User ticks" << - setw(11) << "Avg" << - setw(11) << "System" << - setw(11) << "Avg ms" << "\n"; - return ss.str(); + stringstream ss; + ss << setw(14) << left << "Name " << + setw(11) << right << "Number" << + setw(11) << "User ticks" << + setw(11) << "Avg" << + setw(11) << "System" << + setw(11) << "Avg ms" << "\n"; + return ss.str(); } string TimerGroup::SumLine(const Timer& sumTotal) const { - Timer ownSum; - TimerGroup::Sum(ownSum); + Timer ownSum; + TimerGroup::Sum(ownSum); - return ownSum.SumLine(sumTotal, bname); + return ownSum.SumLine(sumTotal, bname); } string TimerGroup::TimerLines(const Timer& sumTotal) const { - string st = ""; - for (unsigned r = 0; r < timers.size(); r++) - { - size_t i = timers.size() - r - 1; - if (timers[i].Used()) - st += timers[i].SumLine(sumTotal); - } - return st; + string st = ""; + for (unsigned r = 0; r < timers.size(); r++) + { + size_t i = timers.size() - r - 1; + if (timers[i].Used()) + st += timers[i].SumLine(sumTotal); + } + return st; } string TimerGroup::DetailLines() const { - stringstream ss; - for (unsigned i = 0; i < timers.size(); i++) - { - if (timers[i].Used()) - ss << timers[i].DetailLine(); - } + stringstream ss; + for (unsigned i = 0; i < timers.size(); i++) + { + if (timers[i].Used()) + ss << timers[i].DetailLine(); + } - return ss.str(); + return ss.str(); } string TimerGroup::DashLine() const { - return string(69, '-') + "\n"; + return string(69, '-') + "\n"; } diff --git a/library/src/system/timer_group.hpp b/library/src/system/timer_group.hpp index 7fab875ad..5036d83ca 100644 --- a/library/src/system/timer_group.hpp +++ b/library/src/system/timer_group.hpp @@ -26,12 +26,12 @@ */ class TimerGroup { - private: + private: std::vector timers; std::string bname; - public: + public: /** * @brief Construct a new TimerGroup object. diff --git a/library/src/system/timer_list.cpp b/library/src/system/timer_list.cpp index cc6f1b1a8..b4dc580dd 100644 --- a/library/src/system/timer_list.cpp +++ b/library/src/system/timer_list.cpp @@ -22,7 +22,7 @@ using std::ofstream; TimerList::TimerList() { - TimerList::Reset(); + TimerList::Reset(); } @@ -33,102 +33,102 @@ TimerList::~TimerList() void TimerList::Reset() { - timerGroups.resize(TIMER_NO_SIZE); - - timerGroups[TIMER_NO_AB].SetNames("AB"); - timerGroups[TIMER_NO_MAKE].SetNames("Make"); - timerGroups[TIMER_NO_UNDO].SetNames("Undo"); - timerGroups[TIMER_NO_EVALUATE].SetNames("Evaluate"); - timerGroups[TIMER_NO_NEXTMOVE].SetNames("NextMove"); - timerGroups[TIMER_NO_QT].SetNames("QuickTricks"); - timerGroups[TIMER_NO_LT].SetNames("LaterTricks"); - timerGroups[TIMER_NO_MOVEGEN].SetNames("MoveGen"); - timerGroups[TIMER_NO_LOOKUP].SetNames("Lookup"); - timerGroups[TIMER_NO_BUILD].SetNames("Build"); + timerGroups.resize(TIMER_NO_SIZE); + + timerGroups[TIMER_NO_AB].SetNames("AB"); + timerGroups[TIMER_NO_MAKE].SetNames("Make"); + timerGroups[TIMER_NO_UNDO].SetNames("Undo"); + timerGroups[TIMER_NO_EVALUATE].SetNames("Evaluate"); + timerGroups[TIMER_NO_NEXTMOVE].SetNames("NextMove"); + timerGroups[TIMER_NO_QT].SetNames("QuickTricks"); + timerGroups[TIMER_NO_LT].SetNames("LaterTricks"); + timerGroups[TIMER_NO_MOVEGEN].SetNames("MoveGen"); + timerGroups[TIMER_NO_LOOKUP].SetNames("Lookup"); + timerGroups[TIMER_NO_BUILD].SetNames("Build"); } void TimerList::Start( - const ABTimerType groupno, - const unsigned timerno) + const ABTimerType groupno, + const unsigned timerno) { - if (groupno >= TIMER_NO_SIZE) - return; - timerGroups[groupno].Start(timerno); + if (groupno >= TIMER_NO_SIZE) + return; + timerGroups[groupno].Start(timerno); } void TimerList::End( - const ABTimerType groupno, - const unsigned timerno) + const ABTimerType groupno, + const unsigned timerno) { - if (groupno >= TIMER_NO_SIZE) - return; - timerGroups[groupno].End(timerno); + if (groupno >= TIMER_NO_SIZE) + return; + timerGroups[groupno].End(timerno); } bool TimerList::Used() const { - for (unsigned g = 0; g < TIMER_NO_SIZE; g++) - { - if (timerGroups[g].Used()) - return true; - } - return false; + for (unsigned g = 0; g < TIMER_NO_SIZE; g++) + { + if (timerGroups[g].Used()) + return true; + } + return false; } void TimerList::PrintStats(ofstream& fout) const { - if (! TimerList::Used()) - return; - - // Approximate the exclusive times of each function. - // The ab_search_*() functions are recursively nested, - // so subtract out the one below. - // The other ones are subtracted out based on knowledge - // of the functions. - - TimerGroup ABGroup; - ABGroup = timerGroups[0]; - ABGroup.Differentiate(); - for (unsigned g = 1; g < TIMER_NO_SIZE; g++) - ABGroup -= timerGroups[g]; - - Timer ABTotal; - ABGroup.SetNames("AB"); - ABGroup.Sum(ABTotal); - ABTotal.SetName("Sum"); - - Timer sumTotal = ABTotal; - for (unsigned g = 1; g < TIMER_NO_SIZE; g++) - { - Timer t; - timerGroups[g].Sum(t); - sumTotal += t; - } - - fout << timerGroups[0].Header(); - fout << ABGroup.SumLine(sumTotal); - for (unsigned g = 1; g < TIMER_NO_SIZE; g++) - fout << timerGroups[g].SumLine(sumTotal); - fout << timerGroups[0].DashLine(); - fout << sumTotal.SumLine(sumTotal) << endl; - - if (ABGroup.Used()) - { - fout << ABGroup.Header(); - fout << ABGroup.TimerLines(ABTotal); - fout << ABGroup.DashLine(); - fout << ABTotal.SumLine(ABTotal) << endl; - } + if (! TimerList::Used()) + return; + + // Approximate the exclusive times of each function. + // The ab_search_*() functions are recursively nested, + // so subtract out the one below. + // The other ones are subtracted out based on knowledge + // of the functions. + + TimerGroup ABGroup; + ABGroup = timerGroups[0]; + ABGroup.Differentiate(); + for (unsigned g = 1; g < TIMER_NO_SIZE; g++) + ABGroup -= timerGroups[g]; + + Timer ABTotal; + ABGroup.SetNames("AB"); + ABGroup.Sum(ABTotal); + ABTotal.SetName("Sum"); + + Timer sumTotal = ABTotal; + for (unsigned g = 1; g < TIMER_NO_SIZE; g++) + { + Timer t; + timerGroups[g].Sum(t); + sumTotal += t; + } + + fout << timerGroups[0].Header(); + fout << ABGroup.SumLine(sumTotal); + for (unsigned g = 1; g < TIMER_NO_SIZE; g++) + fout << timerGroups[g].SumLine(sumTotal); + fout << timerGroups[0].DashLine(); + fout << sumTotal.SumLine(sumTotal) << endl; + + if (ABGroup.Used()) + { + fout << ABGroup.Header(); + fout << ABGroup.TimerLines(ABTotal); + fout << ABGroup.DashLine(); + fout << ABTotal.SumLine(ABTotal) << endl; + } #ifdef DDS_TIMING_DETAILS - fout << timerGroups[0].DetailHeader(); - for (unsigned g = 0; g < TIMER_NO_SIZE; g++) - fout << timerGroups[g].DetailLines(); - fout << endl; + fout << timerGroups[0].DetailHeader(); + for (unsigned g = 0; g < TIMER_NO_SIZE; g++) + fout << timerGroups[g].DetailLines(); + fout << endl; #endif } diff --git a/library/src/system/timer_list.hpp b/library/src/system/timer_list.hpp index beb58fbf9..de41d4e53 100644 --- a/library/src/system/timer_list.hpp +++ b/library/src/system/timer_list.hpp @@ -9,7 +9,7 @@ /* TimerList consists of a number of groups, one for each piece - of the code being timed (ab_search etc). + of the code being timed (ab_search etc). Each group corresponds to something that should be timed at multiple AB depths, i.e. cards played. The first card of a @@ -34,7 +34,7 @@ to be timed, so TIMER_START(TIMER_NO_AB, depth); - ab_search(...); + ab_search(...); TIMER_END(TIMER_NO_AB, depth); This avoids the tedious #ifdef's at every place of a timer. @@ -52,26 +52,26 @@ #ifdef DDS_TIMING - #define TIMER_START(g, a) thrp->timerList.Start(g, a) - #define TIMER_END(g, a) thrp->timerList.End(g, a) + #define TIMER_START(g, a) thrp->timerList.Start(g, a) + #define TIMER_END(g, a) thrp->timerList.End(g, a) #else - #define TIMER_START(g, a) - #define TIMER_END(g, a) + #define TIMER_START(g, a) + #define TIMER_END(g, a) #endif enum ABTimerType { - TIMER_NO_AB = 0, - TIMER_NO_MAKE = 1, - TIMER_NO_UNDO = 2, - TIMER_NO_EVALUATE = 3, - TIMER_NO_NEXTMOVE = 4, - TIMER_NO_QT = 5, - TIMER_NO_LT = 6, - TIMER_NO_MOVEGEN = 7, - TIMER_NO_LOOKUP = 8, - TIMER_NO_BUILD = 9, - TIMER_NO_SIZE = 10 + TIMER_NO_AB = 0, + TIMER_NO_MAKE = 1, + TIMER_NO_UNDO = 2, + TIMER_NO_EVALUATE = 3, + TIMER_NO_NEXTMOVE = 4, + TIMER_NO_QT = 5, + TIMER_NO_LT = 6, + TIMER_NO_MOVEGEN = 7, + TIMER_NO_LOOKUP = 8, + TIMER_NO_BUILD = 9, + TIMER_NO_SIZE = 10 }; @@ -85,38 +85,38 @@ enum ABTimerType */ class TimerList { - private: + private: - std::vector timerGroups; + std::vector timerGroups; - public: - /** + public: + /** * @brief Construct a new TimerList object. * * Initializes the list of timer groups for profiling. */ - TimerList(); + TimerList(); - /** + /** * @brief Destroy the TimerList object and clean up resources. * * Releases all memory and resets the timer list state. */ - ~TimerList(); + ~TimerList(); - void Reset(); + void Reset(); - void Start( - const ABTimerType groupno, - const unsigned timerno); + void Start( + const ABTimerType groupno, + const unsigned timerno); - void End( - const ABTimerType groupno, - const unsigned timerno); + void End( + const ABTimerType groupno, + const unsigned timerno); - bool Used() const; + bool Used() const; - void PrintStats(std::ofstream& fout) const; + void PrintStats(std::ofstream& fout) const; }; #endif diff --git a/library/src/system/util/utilities.hpp b/library/src/system/util/utilities.hpp index 3ba49dddd..035ea21a0 100644 --- a/library/src/system/util/utilities.hpp +++ b/library/src/system/util/utilities.hpp @@ -11,57 +11,57 @@ namespace dds { // A tiny, instance-scoped utility bundle for logging and stats. class Utilities { public: - Utilities() = default; + Utilities() = default; - Utilities(const Utilities&) = default; - Utilities& operator=(const Utilities&) = default; + Utilities(const Utilities&) = default; + Utilities& operator=(const Utilities&) = default; - Utilities(Utilities&&) noexcept = default; - Utilities& operator=(Utilities&&) noexcept = default; + Utilities(Utilities&&) noexcept = default; + Utilities& operator=(Utilities&&) noexcept = default; - // Compile-time feature detection helpers for tests and guarded code paths. - static constexpr bool log_enabled() { + // Compile-time feature detection helpers for tests and guarded code paths. + static constexpr bool log_enabled() { #ifdef DDS_UTILITIES_LOG - return true; + return true; #else - return false; + return false; #endif - } + } - static constexpr bool stats_enabled() { + static constexpr bool stats_enabled() { #ifdef DDS_UTILITIES_STATS - return true; + return true; #else - return false; + return false; #endif - } + } - // Logging: a very simple append-only buffer; callers can flush and clear. - void log_append(const std::string& s) { log_.push_back(s); } - const std::vector& log_buffer() const { return log_; } - size_t log_size() const { return log_.size(); } - bool log_contains(const std::string& prefix) const { - for (const auto& line : log_) { - if (line.rfind(prefix, 0) == 0) return true; // prefix match + // Logging: a very simple append-only buffer; callers can flush and clear. + void log_append(const std::string& s) { log_.push_back(s); } + const std::vector& log_buffer() const { return log_; } + size_t log_size() const { return log_.size(); } + bool log_contains(const std::string& prefix) const { + for (const auto& line : log_) { + if (line.rfind(prefix, 0) == 0) return true; // prefix match + } + return false; } - return false; - } - void log_clear() { log_.clear(); } + void log_clear() { log_.clear(); } - // Minimal stats: opt-in counters for smoke validation in tests. - struct Stats { - unsigned tt_creates = 0; - unsigned tt_disposes = 0; - }; + // Minimal stats: opt-in counters for smoke validation in tests. + struct Stats { + unsigned tt_creates = 0; + unsigned tt_disposes = 0; + }; - const Stats& stats() const { return stats_; } - Stats& stats() { return stats_; } - Stats stats_snapshot() const { return stats_; } - void stats_reset() { stats_ = Stats{}; } + const Stats& stats() const { return stats_; } + Stats& stats() { return stats_; } + Stats stats_snapshot() const { return stats_; } + void stats_reset() { stats_ = Stats{}; } private: - std::vector log_{}; // minimal structured log lines - Stats stats_{}; // optional counters + std::vector log_{}; // minimal structured log lines + Stats stats_{}; // optional counters }; } // namespace dds diff --git a/library/src/table_deal_validate.hpp b/library/src/table_deal_validate.hpp index bd065141b..5121def08 100644 --- a/library/src/table_deal_validate.hpp +++ b/library/src/table_deal_validate.hpp @@ -43,40 +43,40 @@ */ inline auto table_deal_checks(DdTableDeal const & table_deal) -> int { - // Ranks 2..A occupy bits 2..14; see Deal::remainCards in dll.h. - constexpr unsigned rank_mask = 0x7FFCu; + // Ranks 2..A occupy bits 2..14; see Deal::remainCards in dll.h. + constexpr unsigned rank_mask = 0x7FFCu; - int cards_in_hand[DDS_HANDS] = {0, 0, 0, 0}; + int cards_in_hand[DDS_HANDS] = {0, 0, 0, 0}; - for (int h = 0; h < DDS_HANDS; h++) - { - for (int s = 0; s < DDS_SUITS; s++) + for (int h = 0; h < DDS_HANDS; h++) { - unsigned const holding = table_deal.cards[h][s]; + for (int s = 0; s < DDS_SUITS; s++) + { + unsigned const holding = table_deal.cards[h][s]; - if ((holding & ~rank_mask) != 0) - return RETURN_SUIT_OR_RANK; + if ((holding & ~rank_mask) != 0) + return RETURN_SUIT_OR_RANK; - for (unsigned bit = holding; bit != 0; bit &= bit - 1) - cards_in_hand[h]++; + for (unsigned bit = holding; bit != 0; bit &= bit - 1) + cards_in_hand[h]++; + } } - } - for (int s = 0; s < DDS_SUITS; s++) - { - unsigned seen = 0; - for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) { - unsigned const holding = table_deal.cards[h][s]; - if ((seen & holding) != 0) - return RETURN_DUPLICATE_CARDS; - seen |= holding; + unsigned seen = 0; + for (int h = 0; h < DDS_HANDS; h++) + { + unsigned const holding = table_deal.cards[h][s]; + if ((seen & holding) != 0) + return RETURN_DUPLICATE_CARDS; + seen |= holding; + } } - } - for (int h = 1; h < DDS_HANDS; h++) - if (cards_in_hand[h] != cards_in_hand[0]) - return RETURN_CARD_COUNT; + for (int h = 1; h < DDS_HANDS; h++) + if (cards_in_hand[h] != cards_in_hand[0]) + return RETURN_CARD_COUNT; - return RETURN_NO_FAULT; + return RETURN_NO_FAULT; } diff --git a/library/src/trans_table/trans_table.hpp b/library/src/trans_table/trans_table.hpp index 6c262d58b..1ec588b7f 100644 --- a/library/src/trans_table/trans_table.hpp +++ b/library/src/trans_table/trans_table.hpp @@ -27,13 +27,13 @@ /// performance bottlenecks and memory usage patterns. enum class ResetReason { - Unknown = 0, - TooManyNodes = 1, - NewDeal = 2, - NewTrump = 3, - MemoryExhausted = 4, - FreeMemory = 5, - Count = 6 + Unknown = 0, + TooManyNodes = 1, + NewDeal = 2, + NewTrump = 3, + MemoryExhausted = 4, + FreeMemory = 5, + Count = 6 }; inline constexpr int ResetReasonCount = static_cast(ResetReason::Count); @@ -53,11 +53,11 @@ inline constexpr int ResetReasonCount = static_cast(ResetReason::Count); /// \see ResetReason for memory management related to this structure struct NodeCards // 8 bytes { - char upper_bound; ///< Maximum tricks for side to move at this node (0-13) - char lower_bound; ///< Minimum tricks for side to move at this node (0-13) - char best_move_suit; ///< Optimal suit index (0=S, 1=H, 2=D, 3=C; matches card_suit) - char best_move_rank; ///< Absolute rank (2-14 for 2-A), 0 used as sentinel - char least_win[DDS_SUITS]; ///< Per suit, the number (0-13) of remaining cards at or + char upper_bound; ///< Maximum tricks for side to move at this node (0-13) + char lower_bound; ///< Minimum tricks for side to move at this node (0-13) + char best_move_suit; ///< Optimal suit index (0=S, 1=H, 2=D, 3=C; matches card_suit) + char best_move_rank; ///< Absolute rank (2-14 for 2-A), 0 used as sentinel + char least_win[DDS_SUITS]; ///< Per suit, the number (0-13) of remaining cards at or ///< above the lowest winning rank; ab_search feeds it to ///< win_ranks[aggr][least_win] to recover those cards. Not ///< an absolute rank: 15 - least_win is the *relative* rank @@ -65,19 +65,19 @@ struct NodeCards // 8 bytes }; #ifdef _MSC_VER - // Disable warning for unused arguments. - #pragma warning(push) - #pragma warning(disable: 4100) + // Disable warning for unused arguments. + #pragma warning(push) + #pragma warning(disable: 4100) #endif #ifdef __APPLE__ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wunused-parameter" + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunused-parameter" #endif #ifdef __GNUC__ - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-parameter" + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-parameter" #endif /// \brief Abstract base class for bridge double dummy search transposition table. @@ -108,208 +108,208 @@ struct NodeCards // 8 bytes /// \see NodeCards for the cached position data class TransTable { - public: - TransTable() = default; - - virtual ~TransTable() = default; - - /// \brief Initialize the transposition table with hand lookup configuration. - /// - /// Sets up the transposition table with the provided hand lookup tables - /// used for efficient position hashing and lookups. - /// - /// \param hand_lookup Array of hand lookup tables with shape [DDS_SUITS][15] - /// \throws std::bad_alloc if memory allocation fails during initialization - /// \pre hand_lookup must be a valid [DDS_SUITS][15] array - virtual auto init(const int hand_lookup[][15]) -> void = 0; - - /// \brief Set the default (soft) memory limit in megabytes. - /// - /// Only TransTableL treats this as a soft limit: it tries to stay below it - /// but may exceed it slightly during search, harvesting when it does. - /// TransTableS ignores the value (a no-op; only the maximum is enforced). - /// TransTableP has no soft limit either: the value only floors the hard - /// maximum at make_tt(), and 0 (unset) is ignored there. - /// - /// \param megabytes Desired soft memory limit in MB. 0 is not supported as - /// "unlimited": TransTableL would free every pooled page on the next - /// reset; pass a positive value. - virtual auto set_memory_default(int megabytes) -> void = 0; - - /// \brief Set the maximum (hard) memory limit in megabytes. - /// - /// The table will refuse allocations that would exceed this limit. - /// A memory reset is triggered if the hard limit would be exceeded. - /// - /// \param megabytes Maximum allowed memory in MB - virtual auto set_memory_maximum(int megabytes) -> void = 0; - - /// \brief Create/allocate the transposition table structures. - /// - /// Allocates memory for the transposition table according to configured - /// limits. Must be called before any lookup/add operations. - /// - /// \throws std::bad_alloc if critical allocation fails - virtual auto make_tt() -> void = 0; - - /// \brief Clear the transposition table and reset memory/statistics. - /// - /// Removes all cached positions and bumps the per-reason reset counters - /// (other statistics accumulate across resets). The memory structures are - /// retained for reuse, with one exception: on TransTableP a - /// ResetReason::MemoryExhausted reset returns the pattern blocks (in use - /// and pooled) to the allocator, since pooling them could itself allocate - /// while over budget; the table then regrows on demand. - /// - /// \param reason The reason this reset was triggered - virtual auto reset_memory(ResetReason reason) -> void = 0; - - /// \brief Release all memory used by the transposition table. - /// - /// Deallocates all structures. The table must be re-initialized with - /// make_tt() before further use. - virtual auto return_all_memory() -> void = 0; - - /// \brief Return the amount of memory currently in use (in KB). - /// - /// \return Memory usage in kilobytes - virtual auto memory_in_use() const -> double = 0; - - /// \brief Lookup a cached result for a position. - /// - /// Searches the transposition table for previously cached analysis results - /// for the given position parameters. Returns nullptr if not found. - /// - /// \param trick Current trick number (0-12) - /// \param hand Current hand to play (0-3) - /// \param aggr_target Aggregated targets per suit (4 values, one per suit) - /// \param hand_dist Card distribution for each hand (4 values) - /// \param limit Threshold for early termination, interpreted for the - /// side to move at this node - /// \param[out] lower_flag Set to true if result is a lower bound - /// \return Pointer to cached NodeCards if found; nullptr otherwise - /// \note The returned pointer is only valid until the next add() or reset - virtual auto lookup( - int trick, - int hand, - const unsigned short aggr_target[], - const int hand_dist[], - int limit, - bool& lower_flag) -> NodeCards const * = 0; - - /// \brief Add a newly computed result to the transposition table. - /// - /// Caches the result of a double dummy search for later lookup. If a - /// conflicting entry exists, it may be updated or replaced. - /// - /// \param trick Current trick number (0-12) - /// \param hand Current hand to play (0-3) - /// \param aggr_target Aggregated targets per suit - /// \param win_ranks Winning rank for each suit (optimization data) - /// \param first The cached NodeCards result to store - /// \param flag True if entry is a lower bound (incomplete search) - virtual auto add( - int trick, - int hand, - const unsigned short aggr_target[], - const unsigned short win_ranks[], - const NodeCards& first, - bool flag) -> void = 0; - - /// \brief Print cached results for a specific suit and position. - /// - /// Outputs detailed statistics about the cached entries for a given - /// trick/hand combination. Output format varies by implementation. - /// - /// \param fout Output stream for the statistics - /// \param trick Trick number (0-12) - /// \param hand Hand to analyze (0-3) - virtual auto print_suits( - std::ofstream& fout, - int trick, - int hand) const -> void = 0; - - /// \brief Print suits statistics for all tricks and hands. - virtual auto print_all_suits(std::ofstream& fout) const -> void = 0; - - /// \brief Print suit statistics for a specific trick/hand. - virtual auto print_suit_stats( - std::ofstream& fout, - int trick, - int hand) const -> void = 0; - - /// \brief Print suit statistics for all tricks and hands. - virtual auto print_all_suit_stats(std::ofstream& fout) const -> void = 0; - - /// \brief Get add/overwrite/harvest counters for instrumentation. - virtual auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void = 0; - - /// \brief Reset add/overwrite/harvest counters for per-solve stats. - virtual auto reset_op_stats() -> void = 0; - - /// \brief Print summary suit statistics. - virtual auto print_summary_suit_stats(std::ofstream& fout) const -> void = 0; - - /// \brief Print entries distribution for a specific hand. - virtual auto print_entries_dist( - std::ofstream& fout, - int trick, - int hand, - const int hand_dist[]) const -> void = 0; - - /// \brief Print entries distribution with card information. - virtual auto print_entries_dist_and_cards( - std::ofstream& fout, - int trick, - int hand, - const unsigned short aggr_target[], - const int hand_dist[]) const -> void = 0; - - /// \brief Print entries for a specific trick/hand. - virtual auto print_entries( - std::ofstream& fout, - int trick, - int hand) const -> void = 0; - - /// \brief Print all cached entries. - virtual auto print_all_entries(std::ofstream& fout) const -> void = 0; - - /// \brief Print entry statistics for a specific trick/hand. - virtual auto print_entry_stats( - std::ofstream& fout, - int trick, - int hand) const -> void = 0; - - /// \brief Print entry statistics for all tricks and hands. - virtual auto print_all_entry_stats(std::ofstream& fout) const -> void = 0; - - /// \brief Print summary entry statistics. - virtual auto print_summary_entry_stats(std::ofstream& fout) const -> void = 0; - - /// \brief Print page summary (implementation-specific, optional). - virtual auto print_page_summary(std::ofstream& /*fout*/) const -> void - { - } - - /// \brief Print node statistics (implementation-specific, optional). - virtual auto print_node_stats(std::ofstream& /*fout*/) const -> void - { - } - - /// \brief Print memory reset statistics (implementation-specific, optional). - virtual auto print_reset_stats(std::ofstream& /*fout*/) const -> void - { - } + public: + TransTable() = default; + + virtual ~TransTable() = default; + + /// \brief Initialize the transposition table with hand lookup configuration. + /// + /// Sets up the transposition table with the provided hand lookup tables + /// used for efficient position hashing and lookups. + /// + /// \param hand_lookup Array of hand lookup tables with shape [DDS_SUITS][15] + /// \throws std::bad_alloc if memory allocation fails during initialization + /// \pre hand_lookup must be a valid [DDS_SUITS][15] array + virtual auto init(const int hand_lookup[][15]) -> void = 0; + + /// \brief Set the default (soft) memory limit in megabytes. + /// + /// Only TransTableL treats this as a soft limit: it tries to stay below it + /// but may exceed it slightly during search, harvesting when it does. + /// TransTableS ignores the value (a no-op; only the maximum is enforced). + /// TransTableP has no soft limit either: the value only floors the hard + /// maximum at make_tt(), and 0 (unset) is ignored there. + /// + /// \param megabytes Desired soft memory limit in MB. 0 is not supported as + /// "unlimited": TransTableL would free every pooled page on the next + /// reset; pass a positive value. + virtual auto set_memory_default(int megabytes) -> void = 0; + + /// \brief Set the maximum (hard) memory limit in megabytes. + /// + /// The table will refuse allocations that would exceed this limit. + /// A memory reset is triggered if the hard limit would be exceeded. + /// + /// \param megabytes Maximum allowed memory in MB + virtual auto set_memory_maximum(int megabytes) -> void = 0; + + /// \brief Create/allocate the transposition table structures. + /// + /// Allocates memory for the transposition table according to configured + /// limits. Must be called before any lookup/add operations. + /// + /// \throws std::bad_alloc if critical allocation fails + virtual auto make_tt() -> void = 0; + + /// \brief Clear the transposition table and reset memory/statistics. + /// + /// Removes all cached positions and bumps the per-reason reset counters + /// (other statistics accumulate across resets). The memory structures are + /// retained for reuse, with one exception: on TransTableP a + /// ResetReason::MemoryExhausted reset returns the pattern blocks (in use + /// and pooled) to the allocator, since pooling them could itself allocate + /// while over budget; the table then regrows on demand. + /// + /// \param reason The reason this reset was triggered + virtual auto reset_memory(ResetReason reason) -> void = 0; + + /// \brief Release all memory used by the transposition table. + /// + /// Deallocates all structures. The table must be re-initialized with + /// make_tt() before further use. + virtual auto return_all_memory() -> void = 0; + + /// \brief Return the amount of memory currently in use (in KB). + /// + /// \return Memory usage in kilobytes + virtual auto memory_in_use() const -> double = 0; + + /// \brief Lookup a cached result for a position. + /// + /// Searches the transposition table for previously cached analysis results + /// for the given position parameters. Returns nullptr if not found. + /// + /// \param trick Current trick number (0-12) + /// \param hand Current hand to play (0-3) + /// \param aggr_target Aggregated targets per suit (4 values, one per suit) + /// \param hand_dist Card distribution for each hand (4 values) + /// \param limit Threshold for early termination, interpreted for the + /// side to move at this node + /// \param[out] lower_flag Set to true if result is a lower bound + /// \return Pointer to cached NodeCards if found; nullptr otherwise + /// \note The returned pointer is only valid until the next add() or reset + virtual auto lookup( + int trick, + int hand, + const unsigned short aggr_target[], + const int hand_dist[], + int limit, + bool& lower_flag) -> NodeCards const * = 0; + + /// \brief Add a newly computed result to the transposition table. + /// + /// Caches the result of a double dummy search for later lookup. If a + /// conflicting entry exists, it may be updated or replaced. + /// + /// \param trick Current trick number (0-12) + /// \param hand Current hand to play (0-3) + /// \param aggr_target Aggregated targets per suit + /// \param win_ranks Winning rank for each suit (optimization data) + /// \param first The cached NodeCards result to store + /// \param flag True if entry is a lower bound (incomplete search) + virtual auto add( + int trick, + int hand, + const unsigned short aggr_target[], + const unsigned short win_ranks[], + const NodeCards& first, + bool flag) -> void = 0; + + /// \brief Print cached results for a specific suit and position. + /// + /// Outputs detailed statistics about the cached entries for a given + /// trick/hand combination. Output format varies by implementation. + /// + /// \param fout Output stream for the statistics + /// \param trick Trick number (0-12) + /// \param hand Hand to analyze (0-3) + virtual auto print_suits( + std::ofstream& fout, + int trick, + int hand) const -> void = 0; + + /// \brief Print suits statistics for all tricks and hands. + virtual auto print_all_suits(std::ofstream& fout) const -> void = 0; + + /// \brief Print suit statistics for a specific trick/hand. + virtual auto print_suit_stats( + std::ofstream& fout, + int trick, + int hand) const -> void = 0; + + /// \brief Print suit statistics for all tricks and hands. + virtual auto print_all_suit_stats(std::ofstream& fout) const -> void = 0; + + /// \brief Get add/overwrite/harvest counters for instrumentation. + virtual auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void = 0; + + /// \brief Reset add/overwrite/harvest counters for per-solve stats. + virtual auto reset_op_stats() -> void = 0; + + /// \brief Print summary suit statistics. + virtual auto print_summary_suit_stats(std::ofstream& fout) const -> void = 0; + + /// \brief Print entries distribution for a specific hand. + virtual auto print_entries_dist( + std::ofstream& fout, + int trick, + int hand, + const int hand_dist[]) const -> void = 0; + + /// \brief Print entries distribution with card information. + virtual auto print_entries_dist_and_cards( + std::ofstream& fout, + int trick, + int hand, + const unsigned short aggr_target[], + const int hand_dist[]) const -> void = 0; + + /// \brief Print entries for a specific trick/hand. + virtual auto print_entries( + std::ofstream& fout, + int trick, + int hand) const -> void = 0; + + /// \brief Print all cached entries. + virtual auto print_all_entries(std::ofstream& fout) const -> void = 0; + + /// \brief Print entry statistics for a specific trick/hand. + virtual auto print_entry_stats( + std::ofstream& fout, + int trick, + int hand) const -> void = 0; + + /// \brief Print entry statistics for all tricks and hands. + virtual auto print_all_entry_stats(std::ofstream& fout) const -> void = 0; + + /// \brief Print summary entry statistics. + virtual auto print_summary_entry_stats(std::ofstream& fout) const -> void = 0; + + /// \brief Print page summary (implementation-specific, optional). + virtual auto print_page_summary(std::ofstream& /*fout*/) const -> void + { + } + + /// \brief Print node statistics (implementation-specific, optional). + virtual auto print_node_stats(std::ofstream& /*fout*/) const -> void + { + } + + /// \brief Print memory reset statistics (implementation-specific, optional). + virtual auto print_reset_stats(std::ofstream& /*fout*/) const -> void + { + } }; #ifdef _MSC_VER - #pragma warning(pop) + #pragma warning(pop) #endif #ifdef __APPLE__ - #pragma clang diagnostic pop + #pragma clang diagnostic pop #endif #ifdef __GNUC__ - #pragma GCC diagnostic pop + #pragma GCC diagnostic pop #endif diff --git a/library/src/trans_table/trans_table_l.cpp b/library/src/trans_table/trans_table_l.cpp index 5e8adf3d9..7f98910e3 100644 --- a/library/src/trans_table/trans_table_l.cpp +++ b/library/src/trans_table/trans_table_l.cpp @@ -23,7 +23,7 @@ play in aggr_. The ace is 14, the deuce is 2. A void counts as rank 15 ("not even the ace"). It would go horribly wrong if this rank were chosen to be 0, as might seem intuitive. - This is not the same as lowest_rank (legacy: lowestRank), the lowest absolute rank. + This is not the same as lowest_rank (legacy: lowestRank), the lowest absolute rank. maskBytes[aggr_][suit] is a set of 4 32-bit integers, where suit is 0 ..3 (spades .. clubs). Each integer only @@ -97,16 +97,16 @@ namespace { auto checked_malloc(const size_t size) -> void* { - if (void* ptr = std::malloc(size)) - return ptr; - throw std::bad_alloc(); + if (void* ptr = std::malloc(size)) + return ptr; + throw std::bad_alloc(); } auto checked_calloc(const size_t count, const size_t size) -> void* { - if (void* ptr = std::calloc(count, size)) - return ptr; - throw std::bad_alloc(); + if (void* ptr = std::calloc(count, size)) + return ptr; + throw std::bad_alloc(); } } @@ -126,62 +126,62 @@ using std::to_string; static auto tt_lowest_rank_table() -> const std::array& { - static const std::array table = []{ - std::array t{}; - unsigned int top_bit_rank = 1; - t[0] = 15; // Void - for (unsigned ind = 1; ind < 8192; ind++) { - if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ - top_bit_rank <<= 1; - t[ind] = t[ind ^ top_bit_rank] - 1; - } - return t; - }(); - return table; + static const std::array table = []{ + std::array t{}; + unsigned int top_bit_rank = 1; + t[0] = 15; // Void + for (unsigned ind = 1; ind < 8192; ind++) { + if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ + top_bit_rank <<= 1; + t[ind] = t[ind ^ top_bit_rank] - 1; + } + return t; + }(); + return table; } static auto mask_bytes_table() - -> const std::array, DDS_SUITS>, 8192>& + -> const std::array, DDS_SUITS>, 8192>& { - static const auto table = []{ - std::array, DDS_SUITS>, 8192> m{}; - unsigned int top_bit_rank = 1; - unsigned winMask[8192]; - winMask[0] = 0; - for (unsigned ind = 1; ind < 8192; ind++) { - if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ - top_bit_rank <<= 1; - winMask[ind] = (winMask[ind ^ top_bit_rank] >> 2) | (3 << 24); - - m[ind][0][0] = (winMask[ind] << 6) & 0xff000000; - m[ind][0][1] = (winMask[ind] << 14) & 0xff000000; - m[ind][0][2] = (winMask[ind] << 22) & 0xff000000; - m[ind][0][3] = (winMask[ind] << 30) & 0xff000000; - - m[ind][1][0] = (winMask[ind] >> 2) & 0x00ff0000; - m[ind][1][1] = (winMask[ind] << 6) & 0x00ff0000; - m[ind][1][2] = (winMask[ind] << 14) & 0x00ff0000; - m[ind][1][3] = (winMask[ind] << 22) & 0x00ff0000; - - m[ind][2][0] = (winMask[ind] >> 10) & 0x0000ff00; - m[ind][2][1] = (winMask[ind] >> 2) & 0x0000ff00; - m[ind][2][2] = (winMask[ind] << 6) & 0x0000ff00; - m[ind][2][3] = (winMask[ind] << 14) & 0x0000ff00; - - m[ind][3][0] = (winMask[ind] >> 18) & 0x000000ff; - m[ind][3][1] = (winMask[ind] >> 10) & 0x000000ff; - m[ind][3][2] = (winMask[ind] >> 2) & 0x000000ff; - m[ind][3][3] = (winMask[ind] << 6) & 0x000000ff; - } - return m; - }(); - return table; + static const auto table = []{ + std::array, DDS_SUITS>, 8192> m{}; + unsigned int top_bit_rank = 1; + unsigned winMask[8192]; + winMask[0] = 0; + for (unsigned ind = 1; ind < 8192; ind++) { + if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ + top_bit_rank <<= 1; + winMask[ind] = (winMask[ind ^ top_bit_rank] >> 2) | (3 << 24); + + m[ind][0][0] = (winMask[ind] << 6) & 0xff000000; + m[ind][0][1] = (winMask[ind] << 14) & 0xff000000; + m[ind][0][2] = (winMask[ind] << 22) & 0xff000000; + m[ind][0][3] = (winMask[ind] << 30) & 0xff000000; + + m[ind][1][0] = (winMask[ind] >> 2) & 0x00ff0000; + m[ind][1][1] = (winMask[ind] << 6) & 0x00ff0000; + m[ind][1][2] = (winMask[ind] << 14) & 0x00ff0000; + m[ind][1][3] = (winMask[ind] << 22) & 0x00ff0000; + + m[ind][2][0] = (winMask[ind] >> 10) & 0x0000ff00; + m[ind][2][1] = (winMask[ind] >> 2) & 0x0000ff00; + m[ind][2][2] = (winMask[ind] << 6) & 0x0000ff00; + m[ind][2][3] = (winMask[ind] << 14) & 0x0000ff00; + + m[ind][3][0] = (winMask[ind] >> 18) & 0x000000ff; + m[ind][3][1] = (winMask[ind] >> 10) & 0x000000ff; + m[ind][3][2] = (winMask[ind] >> 2) & 0x000000ff; + m[ind][3][3] = (winMask[ind] << 6) & 0x000000ff; + } + return m; + }(); + return table; } static auto players() -> const std::array& { - static const std::array p = {"North", "East", "South", "West"}; - return p; + static const std::array p = {"North", "East", "South", "West"}; + return p; } @@ -194,32 +194,32 @@ static auto players() -> const std::array& */ TransTableL::TransTableL() { - // Touch the tables once to ensure construction. - (void)tt_lowest_rank_table(); - (void)mask_bytes_table(); - (void)players(); - // Initialize all internal state to safe defaults. Some of these - // fields were previously left uninitialized and relied on implicit - // zeroing via legacy construction paths. With newer creation flows - // (via SolverContext), make the invariants explicit here. - tt_in_use_ = 0; - mem_state_ = MemState::FROM_POOL; - pages_default_ = 0; - pages_current_ = 0; - pages_maximum_ = 0; - harvest_trick_ = 0; - harvest_hand_ = 0; - page_stats_ = PageStats{0,0,0,0,0,0,0}; - timestamp_ = 0; - pool_ = nullptr; - next_block_ = nullptr; - harvested_.next_block_no_ = 0; - for (int c = 0; c < TtTricks; ++c) { - for (int h = 0; h < DDS_HANDS; ++h) { - tt_root_[c][h] = nullptr; - last_block_seen_[c][h] = nullptr; + // Touch the tables once to ensure construction. + (void)tt_lowest_rank_table(); + (void)mask_bytes_table(); + (void)players(); + // Initialize all internal state to safe defaults. Some of these + // fields were previously left uninitialized and relied on implicit + // zeroing via legacy construction paths. With newer creation flows + // (via SolverContext), make the invariants explicit here. + tt_in_use_ = 0; + mem_state_ = MemState::FROM_POOL; + pages_default_ = 0; + pages_current_ = 0; + pages_maximum_ = 0; + harvest_trick_ = 0; + harvest_hand_ = 0; + page_stats_ = PageStats{0,0,0,0,0,0,0}; + timestamp_ = 0; + pool_ = nullptr; + next_block_ = nullptr; + harvested_.next_block_no_ = 0; + for (int c = 0; c < TtTricks; ++c) { + for (int h = 0; h < DDS_HANDS; ++h) { + tt_root_[c][h] = nullptr; + last_block_seen_[c][h] = nullptr; + } } - } } /** @@ -229,7 +229,7 @@ TransTableL::TransTableL() */ TransTableL::~TransTableL() { - return_all_memory(); + return_all_memory(); } // SetConstants removed; constants are produced by TTLowestRankTable/MaskBytesTable. @@ -237,76 +237,76 @@ TransTableL::~TransTableL() auto TransTableL::init(const int hand_lookup[][15]) -> void { - // This is very similar to SetConstants, except that it - // happens with actual cards. It also makes sense to - // keep a record of aggr_ranks_ for each suit. These are - // only used later for xor_set_. - - unsigned int top_bit_rank = 1; - unsigned int top_bit_no = 2; - Aggr * ap; - - for (int s = 0; s < DDS_SUITS; s++) { - aggr_[0].aggr_ranks_[s] = 0; - aggr_[0].aggr_bytes_[s][0] = 0; - aggr_[0].aggr_bytes_[s][1] = 0; - aggr_[0].aggr_bytes_[s][2] = 0; - aggr_[0].aggr_bytes_[s][3] = 0; - } - - for (unsigned ind = 1; ind < 8192; ind++) { - if (ind >= (top_bit_rank << 1)) { - /* Next top bit */ - top_bit_rank <<= 1; - top_bit_no++; - } + // This is very similar to SetConstants, except that it + // happens with actual cards. It also makes sense to + // keep a record of aggr_ranks_ for each suit. These are + // only used later for xor_set_. - aggr_[ind] = aggr_[ind ^ top_bit_rank]; - ap = &aggr_[ind]; + unsigned int top_bit_rank = 1; + unsigned int top_bit_no = 2; + Aggr * ap; for (int s = 0; s < DDS_SUITS; s++) { - ap->aggr_ranks_[s] = (ap->aggr_ranks_[s] >> 2) | - static_cast(hand_lookup[s][top_bit_no] << 24); + aggr_[0].aggr_ranks_[s] = 0; + aggr_[0].aggr_bytes_[s][0] = 0; + aggr_[0].aggr_bytes_[s][1] = 0; + aggr_[0].aggr_bytes_[s][2] = 0; + aggr_[0].aggr_bytes_[s][3] = 0; } - ap->aggr_bytes_[0][0] = (ap->aggr_ranks_[0] << 6) & 0xff000000; - ap->aggr_bytes_[0][1] = (ap->aggr_ranks_[0] << 14) & 0xff000000; - ap->aggr_bytes_[0][2] = (ap->aggr_ranks_[0] << 22) & 0xff000000; - ap->aggr_bytes_[0][3] = (ap->aggr_ranks_[0] << 30) & 0xff000000; - - ap->aggr_bytes_[1][0] = (ap->aggr_ranks_[1] >> 2) & 0x00ff0000; - ap->aggr_bytes_[1][1] = (ap->aggr_ranks_[1] << 6) & 0x00ff0000; - ap->aggr_bytes_[1][2] = (ap->aggr_ranks_[1] << 14) & 0x00ff0000; - ap->aggr_bytes_[1][3] = (ap->aggr_ranks_[1] << 22) & 0x00ff0000; - - ap->aggr_bytes_[2][0] = (ap->aggr_ranks_[2] >> 10) & 0x0000ff00; - ap->aggr_bytes_[2][1] = (ap->aggr_ranks_[2] >> 2) & 0x0000ff00; - ap->aggr_bytes_[2][2] = (ap->aggr_ranks_[2] << 6) & 0x0000ff00; - ap->aggr_bytes_[2][3] = (ap->aggr_ranks_[2] << 14) & 0x0000ff00; - - ap->aggr_bytes_[3][0] = (ap->aggr_ranks_[3] >> 18) & 0x000000ff; - ap->aggr_bytes_[3][1] = (ap->aggr_ranks_[3] >> 10) & 0x000000ff; - ap->aggr_bytes_[3][2] = (ap->aggr_ranks_[3] >> 2) & 0x000000ff; - ap->aggr_bytes_[3][3] = (ap->aggr_ranks_[3] << 6) & 0x000000ff; - } + for (unsigned ind = 1; ind < 8192; ind++) { + if (ind >= (top_bit_rank << 1)) { + /* Next top bit */ + top_bit_rank <<= 1; + top_bit_no++; + } + + aggr_[ind] = aggr_[ind ^ top_bit_rank]; + ap = &aggr_[ind]; + + for (int s = 0; s < DDS_SUITS; s++) { + ap->aggr_ranks_[s] = (ap->aggr_ranks_[s] >> 2) | + static_cast(hand_lookup[s][top_bit_no] << 24); + } + + ap->aggr_bytes_[0][0] = (ap->aggr_ranks_[0] << 6) & 0xff000000; + ap->aggr_bytes_[0][1] = (ap->aggr_ranks_[0] << 14) & 0xff000000; + ap->aggr_bytes_[0][2] = (ap->aggr_ranks_[0] << 22) & 0xff000000; + ap->aggr_bytes_[0][3] = (ap->aggr_ranks_[0] << 30) & 0xff000000; + + ap->aggr_bytes_[1][0] = (ap->aggr_ranks_[1] >> 2) & 0x00ff0000; + ap->aggr_bytes_[1][1] = (ap->aggr_ranks_[1] << 6) & 0x00ff0000; + ap->aggr_bytes_[1][2] = (ap->aggr_ranks_[1] << 14) & 0x00ff0000; + ap->aggr_bytes_[1][3] = (ap->aggr_ranks_[1] << 22) & 0x00ff0000; + + ap->aggr_bytes_[2][0] = (ap->aggr_ranks_[2] >> 10) & 0x0000ff00; + ap->aggr_bytes_[2][1] = (ap->aggr_ranks_[2] >> 2) & 0x0000ff00; + ap->aggr_bytes_[2][2] = (ap->aggr_ranks_[2] << 6) & 0x0000ff00; + ap->aggr_bytes_[2][3] = (ap->aggr_ranks_[2] << 14) & 0x0000ff00; + + ap->aggr_bytes_[3][0] = (ap->aggr_ranks_[3] >> 18) & 0x000000ff; + ap->aggr_bytes_[3][1] = (ap->aggr_ranks_[3] >> 10) & 0x000000ff; + ap->aggr_bytes_[3][2] = (ap->aggr_ranks_[3] >> 2) & 0x000000ff; + ap->aggr_bytes_[3][3] = (ap->aggr_ranks_[3] << 6) & 0x000000ff; + } } auto TransTableL::set_memory_default(int megabytes) -> void { - double blockMem = BlocksPerPage * sizeof(WinBlock) / - static_cast(1024.); + double blockMem = BlocksPerPage * sizeof(WinBlock) / + static_cast(1024.); - pages_default_ = static_cast((1024 * megabytes) / blockMem); + pages_default_ = static_cast((1024 * megabytes) / blockMem); } auto TransTableL::set_memory_maximum(int megabytes) -> void { - double blockMem = BlocksPerPage * sizeof(WinBlock) / - static_cast(1024.); + double blockMem = BlocksPerPage * sizeof(WinBlock) / + static_cast(1024.); - pages_maximum_ = static_cast((1024 * megabytes) / blockMem); + pages_maximum_ = static_cast((1024 * megabytes) / blockMem); } @@ -318,197 +318,197 @@ auto TransTableL::set_memory_maximum(int megabytes) -> void auto TransTableL::make_tt() -> void { - if (!tt_in_use_) { - // Allocate all memory into temporaries first for exception safety. - // If any allocation throws, nothing has been modified. - DistHash* temp_roots[TtTricks][DDS_HANDS] = {}; - - try { - for (int t = 0; t < TtTricks; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - temp_roots[t][h] = static_cast( - checked_malloc(256 * sizeof(DistHash))); - } - } - } catch (...) { - // Clean up any allocated blocks on exception - for (int t = 0; t < TtTricks; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - if (temp_roots[t][h] != nullptr) { - free(temp_roots[t][h]); - temp_roots[t][h] = nullptr; - } + if (!tt_in_use_) { + // Allocate all memory into temporaries first for exception safety. + // If any allocation throws, nothing has been modified. + DistHash* temp_roots[TtTricks][DDS_HANDS] = {}; + + try { + for (int t = 0; t < TtTricks; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + temp_roots[t][h] = static_cast( + checked_malloc(256 * sizeof(DistHash))); + } + } + } catch (...) { + // Clean up any allocated blocks on exception + for (int t = 0; t < TtTricks; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + if (temp_roots[t][h] != nullptr) { + free(temp_roots[t][h]); + temp_roots[t][h] = nullptr; + } + } + } + throw; } - } - throw; - } - // All allocations succeeded; assign to members and mark as in-use - for (int t = 0; t < TtTricks; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - tt_root_[t][h] = temp_roots[t][h]; - } + // All allocations succeeded; assign to members and mark as in-use + for (int t = 0; t < TtTricks; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + tt_root_[t][h] = temp_roots[t][h]; + } + } + tt_in_use_ = 1; } - tt_in_use_ = 1; - } - TransTableL::init_tt(); + TransTableL::init_tt(); } auto TransTableL::init_tt() -> void { - for (int c = 0; c < TtTricks; c++) { - for (int h = 0; h < DDS_HANDS; h++) { - for (int i = 0; i < 256; i++) { - tt_root_[c][h][i].next_no_ = 0; - tt_root_[c][h][i].next_write_no_ = 0; + for (int c = 0; c < TtTricks; c++) { + for (int h = 0; h < DDS_HANDS; h++) { + for (int i = 0; i < 256; i++) { + tt_root_[c][h][i].next_no_ = 0; + tt_root_[c][h][i].next_write_no_ = 0; - } - last_block_seen_[c][h] = nullptr; + } + last_block_seen_[c][h] = nullptr; + } } - } } auto TransTableL::release_tt() -> void { - if (!tt_in_use_) - return; - tt_in_use_ = 0; + if (!tt_in_use_) + return; + tt_in_use_ = 0; - for (int t = 0; t < TtTricks; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - if (tt_root_[t][h] == nullptr) - continue; + for (int t = 0; t < TtTricks; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + if (tt_root_[t][h] == nullptr) + continue; - free(tt_root_[t][h]); + free(tt_root_[t][h]); + } } - } } auto TransTableL::reset_memory( - [[maybe_unused]] const ResetReason reason) -> void + [[maybe_unused]] const ResetReason reason) -> void { - if (pool_ == nullptr) - return; + if (pool_ == nullptr) + return; - // Temporary debug: observe reset parameters to diagnose crashes - #if defined(DDS_DEBUG_TT_RESET) - std::fprintf(stderr, + // Temporary debug: observe reset parameters to diagnose crashes + #if defined(DDS_DEBUG_TT_RESET) + std::fprintf(stderr, "[TransTableL::reset_memory] reason=%d current=%d default=%d pool=%p\n", static_cast(reason), pages_current_, pages_default_, static_cast(pool_)); - #endif - - page_stats_.num_resets_++; - page_stats_.num_callocs_ += pages_current_ - page_stats_.last_current_; - page_stats_.last_current_ = pages_current_; - - while (pages_current_ > pages_default_) { - // Free the tail-most pool and unlink safely even if it was the only one. - Pool* cur = pool_; - free(cur->list_); - pool_ = cur->prev_; - free(cur); - if (pool_ != nullptr) { - pool_->next_ = nullptr; - } + #endif + + page_stats_.num_resets_++; + page_stats_.num_callocs_ += pages_current_ - page_stats_.last_current_; + page_stats_.last_current_ = pages_current_; + + while (pages_current_ > pages_default_) { + // Free the tail-most pool and unlink safely even if it was the only one. + Pool* cur = pool_; + free(cur->list_); + pool_ = cur->prev_; + free(cur); + if (pool_ != nullptr) { + pool_->next_ = nullptr; + } - pages_current_--; - } + pages_current_--; + } - page_stats_.num_frees_ += page_stats_.last_current_ - pages_current_; - page_stats_.last_current_ = pages_current_; + page_stats_.num_frees_ += page_stats_.last_current_ - pages_current_; + page_stats_.last_current_ = pages_current_; - if (pool_ != nullptr) { - while (pool_->prev_) - pool_ = pool_->prev_; - } + if (pool_ != nullptr) { + while (pool_->prev_) + pool_ = pool_->prev_; + } - if (pool_ != nullptr) { - pool_->next_block_no_ = 0; - next_block_ = pool_->list_; - } - else { - // No pool => future allocations will create a fresh pool on demand - next_block_ = nullptr; - } + if (pool_ != nullptr) { + pool_->next_block_no_ = 0; + next_block_ = pool_->list_; + } + else { + // No pool => future allocations will create a fresh pool on demand + next_block_ = nullptr; + } - TransTableL::init_tt(); + TransTableL::init_tt(); - timestamp_ = 0; + timestamp_ = 0; - mem_state_ = MemState::FROM_POOL; + mem_state_ = MemState::FROM_POOL; } auto TransTableL::return_all_memory() -> void { - Pool * tmp; + Pool * tmp; - if (pool_) { - while (pool_->next_) - pool_ = pool_->next_; + if (pool_) { + while (pool_->next_) + pool_ = pool_->next_; - while (pool_) { - free(pool_->list_); - tmp = pool_; - pool_ = pool_->prev_; - free(tmp); + while (pool_) { + free(pool_->list_); + tmp = pool_; + pool_ = pool_->prev_; + free(tmp); + } } - } - pages_current_ = 0; + pages_current_ = 0; - page_stats_.num_resets_ = 0; - page_stats_.num_callocs_ = 0; - page_stats_.num_frees_ = 0; - page_stats_.num_harvests_ = 0; - page_stats_.last_current_ = 0; - page_stats_.num_adds_ = 0; - page_stats_.num_overwrites_ = 0; + page_stats_.num_resets_ = 0; + page_stats_.num_callocs_ = 0; + page_stats_.num_frees_ = 0; + page_stats_.num_harvests_ = 0; + page_stats_.last_current_ = 0; + page_stats_.num_adds_ = 0; + page_stats_.num_overwrites_ = 0; - TransTableL::release_tt(); + TransTableL::release_tt(); } auto TransTableL::blocks_in_use() const -> int { - Pool * pp = pool_; - int count = 0; + Pool * pp = pool_; + int count = 0; - do - { - count += pp->next_block_no_; - pp = pp->prev_; - } - while (pp); + do + { + count += pp->next_block_no_; + pp = pp->prev_; + } + while (pp); - return count; + return count; } auto TransTableL::memory_in_use() const -> double { - int blockMem = BlocksPerPage * pages_current_ * + int blockMem = BlocksPerPage * pages_current_ * static_cast(sizeof(WinBlock)); - int aggrMem = 8192 * static_cast(sizeof(Aggr)); - int rootMem = TtTricks * DDS_HANDS * 256 * + int aggrMem = 8192 * static_cast(sizeof(Aggr)); + int rootMem = TtTricks * DDS_HANDS * 256 * static_cast(sizeof(DistHash)); - return (blockMem + aggrMem + rootMem) / static_cast(1024.); + return (blockMem + aggrMem + rootMem) / static_cast(1024.); } auto TransTableL::get_next_card_block() -> TransTableL::WinBlock * { - /* + /* Spaghetti code. The basic idea is that there is a pool of pages. When a page runs out, we get a next pool. But we're only allowed a certain maximum number, and calloc might also @@ -522,177 +522,177 @@ auto TransTableL::get_next_card_block() -> TransTableL::WinBlock * where we left off harvesting last time. If the harvesting also fails, then we reset whatever TT memory we do have, and we continue with that. - */ + */ - if (pool_ == nullptr) { - // Have to be able to get at least one pool. - pool_ = static_cast(checked_calloc(1, sizeof(Pool))); + if (pool_ == nullptr) { + // Have to be able to get at least one pool. + pool_ = static_cast(checked_calloc(1, sizeof(Pool))); - pool_->list_ = static_cast( - checked_malloc(BlocksPerPage * sizeof(WinBlock))); + pool_->list_ = static_cast( + checked_malloc(BlocksPerPage * sizeof(WinBlock))); - pool_->next_ = nullptr; - pool_->prev_ = nullptr; - pool_->next_block_no_ = 1; + pool_->next_ = nullptr; + pool_->prev_ = nullptr; + pool_->next_block_no_ = 1; - next_block_ = pool_->list_; + next_block_ = pool_->list_; - pages_current_++; + pages_current_++; - return next_block_++; - } - else if (mem_state_ == MemState::FROM_HARVEST) { - // Not allowed to get more memory, so reuse old one. - int n = harvested_.next_block_no_; - if (n == BlocksPerPage) { - if (!TransTableL::harvest()) { - TransTableL::reset_memory(ResetReason::Unknown); - pool_->next_block_no_++; return next_block_++; - } - n = 0; - } - - harvested_.next_block_no_++; - return harvested_.list_[n]; - } - else if (pool_->next_block_no_ == BlocksPerPage) { - if (pool_->next_) { - // Reuse a dormant block that has not been freed. - pool_ = pool_->next_; - pool_->next_block_no_ = 1; - next_block_ = pool_->list_; - - return next_block_++; } - else if (pages_current_ == pages_maximum_) { - // Have to try to reclaim memory. - if (! TransTableL::harvest()) { - TransTableL::reset_memory(ResetReason::Unknown); - pool_->next_block_no_++; - return next_block_++; - } + else if (mem_state_ == MemState::FROM_HARVEST) { + // Not allowed to get more memory, so reuse old one. + int n = harvested_.next_block_no_; + if (n == BlocksPerPage) { + if (!TransTableL::harvest()) { + TransTableL::reset_memory(ResetReason::Unknown); + pool_->next_block_no_++; + return next_block_++; + } + n = 0; + } - mem_state_ = MemState::FROM_HARVEST; - harvested_.next_block_no_++; - return harvested_.list_[0]; + harvested_.next_block_no_++; + return harvested_.list_[n]; } - else - { - // Make a new pool. - Pool * newpoolp = static_cast - (calloc(1, sizeof(Pool))); - - if (newpoolp == nullptr) { - // Unexpected, but try harvesting before we give up - // and start over. - if (! TransTableL::harvest()) { - TransTableL::reset_memory(ResetReason::Unknown); - pool_->next_block_no_++; - return next_block_++; + else if (pool_->next_block_no_ == BlocksPerPage) { + if (pool_->next_) { + // Reuse a dormant block that has not been freed. + pool_ = pool_->next_; + pool_->next_block_no_ = 1; + next_block_ = pool_->list_; + + return next_block_++; } + else if (pages_current_ == pages_maximum_) { + // Have to try to reclaim memory. + if (! TransTableL::harvest()) { + TransTableL::reset_memory(ResetReason::Unknown); + pool_->next_block_no_++; + return next_block_++; + } - mem_state_ = MemState::FROM_HARVEST; - harvested_.next_block_no_++; - return harvested_.list_[0]; - } + mem_state_ = MemState::FROM_HARVEST; + harvested_.next_block_no_++; + return harvested_.list_[0]; + } + else + { + // Make a new pool. + Pool * newpoolp = static_cast + (calloc(1, sizeof(Pool))); + + if (newpoolp == nullptr) { + // Unexpected, but try harvesting before we give up + // and start over. + if (! TransTableL::harvest()) { + TransTableL::reset_memory(ResetReason::Unknown); + pool_->next_block_no_++; + return next_block_++; + } + + mem_state_ = MemState::FROM_HARVEST; + harvested_.next_block_no_++; + return harvested_.list_[0]; + } - newpoolp->list_ = static_cast - (malloc(BlocksPerPage * sizeof(WinBlock))); + newpoolp->list_ = static_cast + (malloc(BlocksPerPage * sizeof(WinBlock))); - if (! newpoolp->list_) { - if (! TransTableL::harvest()) { - TransTableL::reset_memory(ResetReason::Unknown); - pool_->next_block_no_++; - return next_block_++; - } + if (! newpoolp->list_) { + if (! TransTableL::harvest()) { + TransTableL::reset_memory(ResetReason::Unknown); + pool_->next_block_no_++; + return next_block_++; + } - mem_state_ = MemState::FROM_HARVEST; - harvested_.next_block_no_++; - return harvested_.list_[0]; - } + mem_state_ = MemState::FROM_HARVEST; + harvested_.next_block_no_++; + return harvested_.list_[0]; + } - newpoolp->next_block_no_ = 1; - newpoolp->next_ = nullptr; - newpoolp->prev_ = pool_; + newpoolp->next_block_no_ = 1; + newpoolp->next_ = nullptr; + newpoolp->prev_ = pool_; - pool_->next_ = newpoolp; - pool_ = newpoolp; + pool_->next_ = newpoolp; + pool_ = newpoolp; - next_block_ = newpoolp->list_; + next_block_ = newpoolp->list_; - pages_current_++; + pages_current_++; - return next_block_++; + return next_block_++; + } } - } - pool_->next_block_no_++; - return next_block_++; + pool_->next_block_no_++; + return next_block_++; } auto TransTableL::harvest() -> bool { - DistHash * rootptr = tt_root_[harvest_trick_][harvest_hand_]; - DistHash * ptr; - WinBlock * bp; - - int trick = harvest_trick_; - int hand = harvest_hand_; - int hash, suit, hno = 0; - - while (1) { - for (hash = 0; hash < 256; hash++) { - ptr = &rootptr[hash]; - for (suit = ptr->next_no_ - 1; suit >= 0; suit--) { - bp = ptr->list_[suit].pos_block_; - if (timestamp_ - bp->timestamp_read_ > HarvestAge) { - bp->next_match_no_ = 0; - bp->next_write_no_ = 0; - bp->timestamp_read_ = timestamp_; - harvested_.list_[hno] = bp; - - // Swap the last element down. - if (suit != ptr->next_no_ - 1) - ptr->list_[suit] = ptr->list_[ ptr->next_no_ - 1 ]; - - ptr->next_no_--; - ptr->next_write_no_ = ptr->next_no_; - - if (++hno == BlocksPerPage) { - if (++harvest_hand_ >= DDS_HANDS) { - // Skip rest of this [trick][hand] for simplicity. - harvest_hand_ = 0; - if (--harvest_trick_ < 0) - harvest_trick_ = FirstHarvestTrick; + DistHash * rootptr = tt_root_[harvest_trick_][harvest_hand_]; + DistHash * ptr; + WinBlock * bp; + + int trick = harvest_trick_; + int hand = harvest_hand_; + int hash, suit, hno = 0; + + while (1) { + for (hash = 0; hash < 256; hash++) { + ptr = &rootptr[hash]; + for (suit = ptr->next_no_ - 1; suit >= 0; suit--) { + bp = ptr->list_[suit].pos_block_; + if (timestamp_ - bp->timestamp_read_ > HarvestAge) { + bp->next_match_no_ = 0; + bp->next_write_no_ = 0; + bp->timestamp_read_ = timestamp_; + harvested_.list_[hno] = bp; + + // Swap the last element down. + if (suit != ptr->next_no_ - 1) + ptr->list_[suit] = ptr->list_[ ptr->next_no_ - 1 ]; + + ptr->next_no_--; + ptr->next_write_no_ = ptr->next_no_; + + if (++hno == BlocksPerPage) { + if (++harvest_hand_ >= DDS_HANDS) { + // Skip rest of this [trick][hand] for simplicity. + harvest_hand_ = 0; + if (--harvest_trick_ < 0) + harvest_trick_ = FirstHarvestTrick; + } + + harvested_.next_block_no_ = 0; + page_stats_.num_harvests_++; + return true; + } + } } - - harvested_.next_block_no_ = 0; - page_stats_.num_harvests_++; - return true; - } } - } - } - if (++harvest_hand_ >= DDS_HANDS) { - harvest_hand_ = 0; - if (--harvest_trick_ < 0) - harvest_trick_ = FirstHarvestTrick; - } + if (++harvest_hand_ >= DDS_HANDS) { + harvest_hand_ = 0; + if (--harvest_trick_ < 0) + harvest_trick_ = FirstHarvestTrick; + } - if (harvest_trick_ == trick && harvest_hand_ == hand) - return false; + if (harvest_trick_ == trick && harvest_hand_ == hand) + return false; - rootptr = tt_root_[harvest_trick_][harvest_hand_]; - } + rootptr = tt_root_[harvest_trick_][harvest_hand_]; + } } auto TransTableL::hash8(const int hand_dist[]) const -> int { - /* + /* hand_dist is an array of hand distributions, North .. West. Each entry is a 12-bit number with 3 groups of 4 bits. Each group is the binary representation of the number of @@ -712,461 +712,461 @@ auto TransTableL::hash8(const int hand_dist[]) const -> int but this one seems OK. It uses a small prime, 5, and its powers. The shift at the end is in order to get some use out of the bits above the first 8 ones. - */ + */ - const int h = - (hand_dist[0] ^ + const int h = + (hand_dist[0] ^ (hand_dist[1] * 5) ^ (hand_dist[2] * 25) ^ (hand_dist[3] * 125)); - return (h ^ (h >> 5)) & 0xff; + return (h ^ (h >> 5)) & 0xff; } auto TransTableL::lookup( - const int tricks, - const int hand, - const unsigned short aggr_target[], - const int hand_dist[], - const int limit, - bool& lower_flag) -> NodeCards const * + const int tricks, + const int hand, + const unsigned short aggr_target[], + const int hand_dist[], + const int limit, + bool& lower_flag) -> NodeCards const * { - // First look up distribution. - const long long suitLengths = - (static_cast(hand_dist[0]) << 36) | - (static_cast(hand_dist[1]) << 24) | - (static_cast(hand_dist[2]) << 12) | - static_cast(hand_dist[3]); - - int hashkey = hash8(hand_dist); - - bool empty; - last_block_seen_[tricks][hand] = - lookup_suit(&tt_root_[tricks][hand][hashkey], suitLengths, empty); - if (empty) - return nullptr; - - // If that worked, look up cards. - unsigned * ab0 = aggr_[aggr_target[0]].aggr_bytes_[0]; - unsigned * ab1 = aggr_[aggr_target[1]].aggr_bytes_[1]; - unsigned * ab2 = aggr_[aggr_target[2]].aggr_bytes_[2]; - unsigned * ab3 = aggr_[aggr_target[3]].aggr_bytes_[3]; - - WinMatch TTentry; - TTentry.top_set1_ = ab0[0] | ab1[0] | ab2[0] | ab3[0]; - TTentry.top_set2_ = ab0[1] | ab1[1] | ab2[1] | ab3[1]; - TTentry.top_set3_ = ab0[2] | ab1[2] | ab2[2] | ab3[2]; - TTentry.top_set4_ = ab0[3] | ab1[3] | ab2[3] | ab3[3]; - - return TransTableL::lookup_cards( - TTentry, - last_block_seen_[tricks][hand], - limit, - lower_flag); + // First look up distribution. + const long long suitLengths = + (static_cast(hand_dist[0]) << 36) | + (static_cast(hand_dist[1]) << 24) | + (static_cast(hand_dist[2]) << 12) | + static_cast(hand_dist[3]); + + int hashkey = hash8(hand_dist); + + bool empty; + last_block_seen_[tricks][hand] = + lookup_suit(&tt_root_[tricks][hand][hashkey], suitLengths, empty); + if (empty) + return nullptr; + + // If that worked, look up cards. + unsigned * ab0 = aggr_[aggr_target[0]].aggr_bytes_[0]; + unsigned * ab1 = aggr_[aggr_target[1]].aggr_bytes_[1]; + unsigned * ab2 = aggr_[aggr_target[2]].aggr_bytes_[2]; + unsigned * ab3 = aggr_[aggr_target[3]].aggr_bytes_[3]; + + WinMatch TTentry; + TTentry.top_set1_ = ab0[0] | ab1[0] | ab2[0] | ab3[0]; + TTentry.top_set2_ = ab0[1] | ab1[1] | ab2[1] | ab3[1]; + TTentry.top_set3_ = ab0[2] | ab1[2] | ab2[2] | ab3[2]; + TTentry.top_set4_ = ab0[3] | ab1[3] | ab2[3] | ab3[3]; + + return TransTableL::lookup_cards( + TTentry, + last_block_seen_[tricks][hand], + limit, + lower_flag); } auto TransTableL::lookup_suit( - DistHash * dp, - const long long key_, - bool& empty) -> TransTableL::WinBlock * + DistHash * dp, + const long long key_, + bool& empty) -> TransTableL::WinBlock * { - /* + /* Always returns a valid WinBlock. If empty == true, there was no match, so there is no point in looking for a card match. If empty == false, there were entries already. - */ + */ - int n = dp->next_no_; - for (int i = 0; i < n; i++) { - if (dp->list_[i].key_ == key_) { - empty = false; - return dp->list_[i].pos_block_; + int n = dp->next_no_; + for (int i = 0; i < n; i++) { + if (dp->list_[i].key_ == key_) { + empty = false; + return dp->list_[i].pos_block_; + } } - } - empty = true; - int m; + empty = true; + int m; - if (n == DistsPerEntry) { - // No room for new exact suits at this hash position. - // Have to reuse an existing pos_block_. - if (dp->next_write_no_ == DistsPerEntry) { - m = 0; - dp->next_write_no_ = 1; + if (n == DistsPerEntry) { + // No room for new exact suits at this hash position. + // Have to reuse an existing pos_block_. + if (dp->next_write_no_ == DistsPerEntry) { + m = 0; + dp->next_write_no_ = 1; + } + else + m = dp->next_write_no_++; } else - m = dp->next_write_no_++; - } - else - { - // Didn't find an exact match, but there is still room. - // The following looks a bit odd because it is possible that - // get_next_card_block wipes out the whole memory, so we - // have to use the up-to-date location, not m from above. - - WinBlock * bp = get_next_card_block(); - m = dp->next_write_no_++; - dp->list_[m].pos_block_ = bp; - dp->list_[m].pos_block_->timestamp_read_ = timestamp_; - dp->next_no_++; - } - - // As long as the secondary Lookup loop in ab_search exists, - // it will cause spurious extra blocks to be created here - // which are not useful, because nothing is ever Add'ed. - // This is not a memory leak, as the memory is properly freed, - // but it is also a small waste of about 0.5%. I don't mind. - - dp->list_[m].key_ = key_; - dp->list_[m].pos_block_->next_match_no_ = 0; - dp->list_[m].pos_block_->next_write_no_ = 0; - - return dp->list_[m].pos_block_; + { + // Didn't find an exact match, but there is still room. + // The following looks a bit odd because it is possible that + // get_next_card_block wipes out the whole memory, so we + // have to use the up-to-date location, not m from above. + + WinBlock * bp = get_next_card_block(); + m = dp->next_write_no_++; + dp->list_[m].pos_block_ = bp; + dp->list_[m].pos_block_->timestamp_read_ = timestamp_; + dp->next_no_++; + } + + // As long as the secondary Lookup loop in ab_search exists, + // it will cause spurious extra blocks to be created here + // which are not useful, because nothing is ever Add'ed. + // This is not a memory leak, as the memory is properly freed, + // but it is also a small waste of about 0.5%. I don't mind. + + dp->list_[m].key_ = key_; + dp->list_[m].pos_block_->next_match_no_ = 0; + dp->list_[m].pos_block_->next_write_no_ = 0; + + return dp->list_[m].pos_block_; } auto TransTableL::lookup_cards( - const WinMatch& search, - WinBlock * bp, - const int limit, - bool& lower_flag) -> NodeCards * + const WinMatch& search, + WinBlock * bp, + const int limit, + bool& lower_flag) -> NodeCards * { - const int n = bp->next_write_no_ - 1; - WinMatch * wp = &bp->list_[n]; + const int n = bp->next_write_no_ - 1; + WinMatch * wp = &bp->list_[n]; - // It may be a bit silly to duplicate the code like this. - // It could be combined to one loop with a slight overhead. + // It may be a bit silly to duplicate the code like this. + // It could be combined to one loop with a slight overhead. - for (int i = n; i >= 0; i--, wp--) { - if ((wp->top_set1_ ^ search.top_set1_) & wp->top_mask1_) - continue; + for (int i = n; i >= 0; i--, wp--) { + if ((wp->top_set1_ ^ search.top_set1_) & wp->top_mask1_) + continue; - if (wp->last_mask_no_ != 1) { - if ((wp->top_set2_ ^ search.top_set2_) & wp->top_mask2_) - continue; + if (wp->last_mask_no_ != 1) { + if ((wp->top_set2_ ^ search.top_set2_) & wp->top_mask2_) + continue; - if (wp->last_mask_no_ != 2) { - if ((wp->top_set3_ ^ search.top_set3_) & wp->top_mask3_) - continue; - } - } + if (wp->last_mask_no_ != 2) { + if ((wp->top_set3_ ^ search.top_set3_) & wp->top_mask3_) + continue; + } + } - // Check bounds. - NodeCards * nodep = &wp->first_; - if (nodep->lower_bound > limit) { - bp->timestamp_read_ = ++timestamp_; - lower_flag = true; - return nodep; - } - else if (nodep->upper_bound <= limit) { - bp->timestamp_read_ = ++timestamp_; - lower_flag = false; - return nodep; + // Check bounds. + NodeCards * nodep = &wp->first_; + if (nodep->lower_bound > limit) { + bp->timestamp_read_ = ++timestamp_; + lower_flag = true; + return nodep; + } + else if (nodep->upper_bound <= limit) { + bp->timestamp_read_ = ++timestamp_; + lower_flag = false; + return nodep; + } } - } - const int n2 = bp->next_match_no_ - 1; - wp = &bp->list_[n2]; + const int n2 = bp->next_match_no_ - 1; + wp = &bp->list_[n2]; - for (int i = n2; i > n; i--, wp--) { - if ((wp->top_set1_ ^ search.top_set1_) & wp->top_mask1_) - continue; + for (int i = n2; i > n; i--, wp--) { + if ((wp->top_set1_ ^ search.top_set1_) & wp->top_mask1_) + continue; - if (wp->last_mask_no_ != 1) { - if ((wp->top_set2_ ^ search.top_set2_) & wp->top_mask2_) - continue; + if (wp->last_mask_no_ != 1) { + if ((wp->top_set2_ ^ search.top_set2_) & wp->top_mask2_) + continue; - if (wp->last_mask_no_ != 2) { - if ((wp->top_set3_ ^ search.top_set3_) & wp->top_mask3_) - continue; - } - } + if (wp->last_mask_no_ != 2) { + if ((wp->top_set3_ ^ search.top_set3_) & wp->top_mask3_) + continue; + } + } - NodeCards * nodep = &wp->first_; - if (nodep->lower_bound > limit) { - lower_flag = true; - bp->timestamp_read_ = ++timestamp_; - return nodep; - } - else if (nodep->upper_bound <= limit) { - lower_flag = false; - bp->timestamp_read_ = ++timestamp_; - return nodep; + NodeCards * nodep = &wp->first_; + if (nodep->lower_bound > limit) { + lower_flag = true; + bp->timestamp_read_ = ++timestamp_; + return nodep; + } + else if (nodep->upper_bound <= limit) { + lower_flag = false; + bp->timestamp_read_ = ++timestamp_; + return nodep; + } } - } - return nullptr; + return nullptr; } auto TransTableL::create_or_update( - WinBlock * bp, - const WinMatch& search, - const bool flag) -> void + WinBlock * bp, + const WinMatch& search, + const bool flag) -> void { - // Either updates an existing SOP or creates a new one. - // A new one is created at the end of the bp list_ if this - // is not already full, or the oldest one in the list_ is - // overwritten. - - WinMatch * wp = bp->list_; - int n = bp->next_match_no_; - - for (int i = 0; i < n; i++, wp++) { - if (wp->xor_set_ != search.xor_set_ ) continue; - if (wp->mask_index_ != search.mask_index_) continue; - if (wp->top_set1_ != search.top_set1_ ) continue; - if (wp->top_set2_ != search.top_set2_ ) continue; - if (wp->top_set3_ != search.top_set3_ ) continue; - - NodeCards& node = wp->first_; - if (search.first_.lower_bound > node.lower_bound) - node.lower_bound = search.first_.lower_bound; - if (search.first_.upper_bound < node.upper_bound) - node.upper_bound = search.first_.upper_bound; - - node.best_move_suit = search.first_.best_move_suit; - node.best_move_rank = search.first_.best_move_rank; - return; - } - - // Instrumentation: count new insertions and overwrites - page_stats_.num_adds_++; - if (n == BlocksPerEntry) { - page_stats_.num_overwrites_++; - if (bp->next_write_no_ >= BlocksPerEntry) - bp->next_write_no_ = 0; - } - else - bp->next_match_no_++; - - - wp = &bp->list_[ bp->next_write_no_++ ]; - *wp = search; - - if (!flag) { - wp->first_.best_move_suit = 0; - wp->first_.best_move_rank = 0; - } + // Either updates an existing SOP or creates a new one. + // A new one is created at the end of the bp list_ if this + // is not already full, or the oldest one in the list_ is + // overwritten. + + WinMatch * wp = bp->list_; + int n = bp->next_match_no_; + + for (int i = 0; i < n; i++, wp++) { + if (wp->xor_set_ != search.xor_set_ ) continue; + if (wp->mask_index_ != search.mask_index_) continue; + if (wp->top_set1_ != search.top_set1_ ) continue; + if (wp->top_set2_ != search.top_set2_ ) continue; + if (wp->top_set3_ != search.top_set3_ ) continue; + + NodeCards& node = wp->first_; + if (search.first_.lower_bound > node.lower_bound) + node.lower_bound = search.first_.lower_bound; + if (search.first_.upper_bound < node.upper_bound) + node.upper_bound = search.first_.upper_bound; + + node.best_move_suit = search.first_.best_move_suit; + node.best_move_rank = search.first_.best_move_rank; + return; + } + + // Instrumentation: count new insertions and overwrites + page_stats_.num_adds_++; + if (n == BlocksPerEntry) { + page_stats_.num_overwrites_++; + if (bp->next_write_no_ >= BlocksPerEntry) + bp->next_write_no_ = 0; + } + else + bp->next_match_no_++; + + + wp = &bp->list_[ bp->next_write_no_++ ]; + *wp = search; + + if (!flag) { + wp->first_.best_move_suit = 0; + wp->first_.best_move_rank = 0; + } } auto TransTableL::add( - const int tricks, - const int hand, - const unsigned short aggr_target[], - const unsigned short our_win_ranks[], - const NodeCards& first, - const bool flag) -> void + const int tricks, + const int hand, + const unsigned short aggr_target[], + const unsigned short our_win_ranks[], + const NodeCards& first, + const bool flag) -> void { - if (last_block_seen_[tricks][hand] == nullptr) { - // We have recently reset the entire memory, and we were - // in the middle of a recursion. So we'll just have to - // drop this entry that we were supposed to be adding. - return; - } - - unsigned * ab[DDS_SUITS]; - const unsigned * mb[DDS_SUITS]; - char low[DDS_SUITS]; - unsigned short int ag; - int w; - WinMatch TTentry; - - // Inefficient, as it also copies leastWin. - // In fact I'm not quite happy with the treatment of - // leastWin in general. - - TTentry.first_ = first; - - TTentry.xor_set_ = 0; - - for (int ss = 0; ss < DDS_SUITS; ss++) { - w = static_cast(our_win_ranks[ss]); - if (w == 0) { - ab[ss] = aggr_[0].aggr_bytes_[ss]; - mb[ss] = mask_bytes_table()[0][ss].data(); - low[ss] = 15; - TTentry.first_.least_win[ss] = 0; + if (last_block_seen_[tricks][hand] == nullptr) { + // We have recently reset the entire memory, and we were + // in the middle of a recursion. So we'll just have to + // drop this entry that we were supposed to be adding. + return; } - else - { - w = w & (-w); /* Only lowest win */ - ag = static_cast(aggr_target[ss] & (-w)); - ab[ss] = aggr_[ag].aggr_bytes_[ss]; - mb[ss] = mask_bytes_table()[ag][ss].data(); - low[ss] = static_cast(tt_lowest_rank_table()[ag]); + unsigned * ab[DDS_SUITS]; + const unsigned * mb[DDS_SUITS]; + char low[DDS_SUITS]; + unsigned short int ag; + int w; + WinMatch TTentry; + + // Inefficient, as it also copies leastWin. + // In fact I'm not quite happy with the treatment of + // leastWin in general. - TTentry.first_.least_win[ss] = 15 - low[ss]; - TTentry.xor_set_ ^= aggr_[ag].aggr_ranks_[ss]; + TTentry.first_ = first; + + TTentry.xor_set_ = 0; + + for (int ss = 0; ss < DDS_SUITS; ss++) { + w = static_cast(our_win_ranks[ss]); + if (w == 0) { + ab[ss] = aggr_[0].aggr_bytes_[ss]; + mb[ss] = mask_bytes_table()[0][ss].data(); + low[ss] = 15; + TTentry.first_.least_win[ss] = 0; + } + else + { + w = w & (-w); /* Only lowest win */ + ag = static_cast(aggr_target[ss] & (-w)); + + ab[ss] = aggr_[ag].aggr_bytes_[ss]; + mb[ss] = mask_bytes_table()[ag][ss].data(); + low[ss] = static_cast(tt_lowest_rank_table()[ag]); + + TTentry.first_.least_win[ss] = 15 - low[ss]; + TTentry.xor_set_ ^= aggr_[ag].aggr_ranks_[ss]; + } } - } - - // It's a bit annoying that we may be regenerating these. - // But win_ranks can cause them to change after lookup(). - - TTentry.top_set1_ = ab[0][0] | ab[1][0] | ab[2][0] | ab[3][0]; - TTentry.top_set2_ = ab[0][1] | ab[1][1] | ab[2][1] | ab[3][1]; - TTentry.top_set3_ = ab[0][2] | ab[1][2] | ab[2][2] | ab[3][2]; - TTentry.top_set4_ = ab[0][3] | ab[1][3] | ab[2][3] | ab[3][3]; - - TTentry.top_mask1_ = mb[0][0] | mb[1][0] | mb[2][0] | mb[3][0]; - TTentry.top_mask2_ = mb[0][1] | mb[1][1] | mb[2][1] | mb[3][1]; - TTentry.top_mask3_ = mb[0][2] | mb[1][2] | mb[2][2] | mb[3][2]; - TTentry.top_mask4_ = mb[0][3] | mb[1][3] | mb[2][3] | mb[3][3]; - - TTentry.mask_index_ = - (low[0] << 12) | (low[1] << 8) | (low[2] << 4) | low[3]; - - if (TTentry.top_mask2_ == 0) - TTentry.last_mask_no_ = 1; - else if (TTentry.top_mask3_ == 0) - TTentry.last_mask_no_ = 2; - else if (TTentry.top_mask4_ == 0) - TTentry.last_mask_no_ = 3; - else - TTentry.last_mask_no_ = 4; - - TransTableL::create_or_update(last_block_seen_[tricks][hand], - TTentry, flag); + + // It's a bit annoying that we may be regenerating these. + // But win_ranks can cause them to change after lookup(). + + TTentry.top_set1_ = ab[0][0] | ab[1][0] | ab[2][0] | ab[3][0]; + TTentry.top_set2_ = ab[0][1] | ab[1][1] | ab[2][1] | ab[3][1]; + TTentry.top_set3_ = ab[0][2] | ab[1][2] | ab[2][2] | ab[3][2]; + TTentry.top_set4_ = ab[0][3] | ab[1][3] | ab[2][3] | ab[3][3]; + + TTentry.top_mask1_ = mb[0][0] | mb[1][0] | mb[2][0] | mb[3][0]; + TTentry.top_mask2_ = mb[0][1] | mb[1][1] | mb[2][1] | mb[3][1]; + TTentry.top_mask3_ = mb[0][2] | mb[1][2] | mb[2][2] | mb[3][2]; + TTentry.top_mask4_ = mb[0][3] | mb[1][3] | mb[2][3] | mb[3][3]; + + TTentry.mask_index_ = + (low[0] << 12) | (low[1] << 8) | (low[2] << 4) | low[3]; + + if (TTentry.top_mask2_ == 0) + TTentry.last_mask_no_ = 1; + else if (TTentry.top_mask3_ == 0) + TTentry.last_mask_no_ = 2; + else if (TTentry.top_mask4_ == 0) + TTentry.last_mask_no_ = 3; + else + TTentry.last_mask_no_ = 4; + + TransTableL::create_or_update(last_block_seen_[tricks][hand], + TTentry, flag); } auto TransTableL::print_match( - ofstream& fout, - const WinMatch& wp, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void + ofstream& fout, + const WinMatch& wp, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void { - vector> hands; - hands.resize(DDS_HANDS); - for (unsigned i = 0; i < DDS_HANDS; i++) - hands[i].resize(DDS_SUITS); + vector> hands; + hands.resize(DDS_HANDS); + for (unsigned i = 0; i < DDS_HANDS; i++) + hands[i].resize(DDS_SUITS); - TransTableL::set_to_partial_hands(wp.top_set1_, wp.top_mask1_, 14, 4, hands); - TransTableL::set_to_partial_hands(wp.top_set2_, wp.top_mask2_, 10, 4, hands); - TransTableL::set_to_partial_hands(wp.top_set3_, wp.top_mask3_, 6, 4, hands); - TransTableL::set_to_partial_hands(wp.top_set4_, wp.top_mask4_, 2, 1, hands); + TransTableL::set_to_partial_hands(wp.top_set1_, wp.top_mask1_, 14, 4, hands); + TransTableL::set_to_partial_hands(wp.top_set2_, wp.top_mask2_, 10, 4, hands); + TransTableL::set_to_partial_hands(wp.top_set3_, wp.top_mask3_, 6, 4, hands); + TransTableL::set_to_partial_hands(wp.top_set4_, wp.top_mask4_, 2, 1, hands); - TransTableL::dump_hands(fout, hands, lengths); + TransTableL::dump_hands(fout, hands, lengths); - TransTableL::print_node_values(fout, wp.first_); + TransTableL::print_node_values(fout, wp.first_); } auto TransTableL::print_node_values( - ofstream& fout, - const NodeCards& np) const -> void + ofstream& fout, + const NodeCards& np) const -> void { - fout << setw(16) << left << "Lowest used" << - card_suit[0] << card_rank[15-static_cast(np.least_win[0])] << ", " << - card_suit[1] << card_rank[15-static_cast(np.least_win[1])] << ", " << - card_suit[2] << card_rank[15-static_cast(np.least_win[2])] << ", " << - card_suit[3] << card_rank[15-static_cast(np.least_win[3])] << "\n"; - - fout << setw(16) << left << "Bounds" << - to_string(static_cast(np.lower_bound)) << " to " << - to_string(static_cast(np.upper_bound)) << " tricks\n"; - - fout << setw(16) << left << "Best move" << - card_suit[ static_cast(np.best_move_suit) ] << - card_rank[ static_cast(np.best_move_rank) ] << "\n\n"; + fout << setw(16) << left << "Lowest used" << + card_suit[0] << card_rank[15-static_cast(np.least_win[0])] << ", " << + card_suit[1] << card_rank[15-static_cast(np.least_win[1])] << ", " << + card_suit[2] << card_rank[15-static_cast(np.least_win[2])] << ", " << + card_suit[3] << card_rank[15-static_cast(np.least_win[3])] << "\n"; + + fout << setw(16) << left << "Bounds" << + to_string(static_cast(np.lower_bound)) << " to " << + to_string(static_cast(np.upper_bound)) << " tricks\n"; + + fout << setw(16) << left << "Best move" << + card_suit[ static_cast(np.best_move_suit) ] << + card_rank[ static_cast(np.best_move_rank) ] << "\n\n"; } auto TransTableL::make_holding( - const string& high, - const unsigned len) const -> string + const string& high, + const unsigned len) const -> string { - const size_t l = high.size(); - if (l == 0) - return "-"; - else if (l == len) - return high; - else - return high.substr(0, l) + string(len-l, 'x'); + const size_t l = high.size(); + if (l == 0) + return "-"; + else if (l == len) + return high; + else + return high.substr(0, l) + string(len-l, 'x'); } auto TransTableL::dump_hands( - ofstream& fout, - const vector>& hands, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void + ofstream& fout, + const vector>& hands, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void { - for (unsigned i = 0; i < DDS_SUITS; i++) { - fout << setw(16) << "" << - TransTableL::make_holding(hands[0][i], lengths[0][i]) << "\n"; - } - - for (unsigned i = 0; i < DDS_SUITS; i++) { - fout << setw(16) << left << - TransTableL::make_holding(hands[3][i], lengths[3][i]) << - setw(16) << "" << - setw(16) << - TransTableL::make_holding(hands[1][i], lengths[1][i]) << "\n"; - } - - for (unsigned i = 0; i < DDS_SUITS; i++) { - fout << setw(16) << "" << - TransTableL::make_holding(hands[2][i], lengths[2][i]) << "\n"; - } - fout << "\n"; + for (unsigned i = 0; i < DDS_SUITS; i++) { + fout << setw(16) << "" << + TransTableL::make_holding(hands[0][i], lengths[0][i]) << "\n"; + } + + for (unsigned i = 0; i < DDS_SUITS; i++) { + fout << setw(16) << left << + TransTableL::make_holding(hands[3][i], lengths[3][i]) << + setw(16) << "" << + setw(16) << + TransTableL::make_holding(hands[1][i], lengths[1][i]) << "\n"; + } + + for (unsigned i = 0; i < DDS_SUITS; i++) { + fout << setw(16) << "" << + TransTableL::make_holding(hands[2][i], lengths[2][i]) << "\n"; + } + fout << "\n"; } auto TransTableL::set_to_partial_hands( - const unsigned set, - const unsigned mask, - const int max_rank, - const int num_ranks, - vector>& hands) const -> void + const unsigned set, + const unsigned mask, + const int max_rank, + const int num_ranks, + vector>& hands) const -> void { - for (unsigned s = 0; s < DDS_SUITS; s++) { - for (int rank = max_rank; rank > max_rank - num_ranks; rank--) { - int shift = 8 * static_cast(3 - s) + 2 * (rank - max_rank + 3); - unsigned maskCard = mask >> shift; - - if (maskCard & 3) { - unsigned player = (set >> shift) & 3; - hands[player][s] += static_cast(card_rank[rank]); - } + for (unsigned s = 0; s < DDS_SUITS; s++) { + for (int rank = max_rank; rank > max_rank - num_ranks; rank--) { + int shift = 8 * static_cast(3 - s) + 2 * (rank - max_rank + 3); + unsigned maskCard = mask >> shift; + + if (maskCard & 3) { + unsigned player = (set >> shift) & 3; + hands[player][s] += static_cast(card_rank[rank]); + } + } } - } } auto TransTableL::key_to_dist( - const long long key_, - int hand_dist[]) const -> void + const long long key_, + int hand_dist[]) const -> void { - hand_dist[0] = static_cast((key_ >> 36) & 0x00000fff); - hand_dist[1] = static_cast((key_ >> 24) & 0x00000fff); - hand_dist[2] = static_cast((key_ >> 12) & 0x00000fff); - hand_dist[3] = static_cast((key_ ) & 0x00000fff); + hand_dist[0] = static_cast((key_ >> 36) & 0x00000fff); + hand_dist[1] = static_cast((key_ >> 24) & 0x00000fff); + hand_dist[2] = static_cast((key_ >> 12) & 0x00000fff); + hand_dist[3] = static_cast((key_ ) & 0x00000fff); } auto TransTableL::dist_to_lengths( - const int trick, - const int hand_dist[], - unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void + const int trick, + const int hand_dist[], + unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void { - for (int h = 0; h < DDS_HANDS; h++) { - lengths[h][0] = static_cast((hand_dist[h] >> 8) & 0xf); - lengths[h][1] = static_cast((hand_dist[h] >> 4) & 0xf); - lengths[h][2] = static_cast((hand_dist[h] ) & 0xf); - lengths[h][3] = static_cast - (trick + 1 - lengths[h][0] - lengths[h][1] - lengths[h][2]); - } + for (int h = 0; h < DDS_HANDS; h++) { + lengths[h][0] = static_cast((hand_dist[h] >> 8) & 0xf); + lengths[h][1] = static_cast((hand_dist[h] >> 4) & 0xf); + lengths[h][2] = static_cast((hand_dist[h] ) & 0xf); + lengths[h][3] = static_cast + (trick + 1 - lengths[h][0] - lengths[h][1] - lengths[h][2]); + } } auto TransTableL::single_len_to_str(const unsigned char len[]) const -> string { - return to_string(static_cast(len[0])) + "=" + + return to_string(static_cast(len[0])) + "=" + to_string(static_cast(len[1])) + "=" + to_string(static_cast(len[2])) + "=" + to_string(static_cast(len[3])); @@ -1174,9 +1174,9 @@ auto TransTableL::single_len_to_str(const unsigned char len[]) const -> string auto TransTableL::len_to_str( - const unsigned char len[DDS_HANDS][DDS_SUITS]) const -> string + const unsigned char len[DDS_HANDS][DDS_SUITS]) const -> string { - return TransTableL::single_len_to_str(len[0]) + " " + + return TransTableL::single_len_to_str(len[0]) + " " + TransTableL::single_len_to_str(len[1]) + " " + TransTableL::single_len_to_str(len[2]) + " " + TransTableL::single_len_to_str(len[3]); @@ -1184,661 +1184,661 @@ auto TransTableL::len_to_str( auto TransTableL::print_suits( - ofstream& fout, - const int trick, - const int hand) const -> void + ofstream& fout, + const int trick, + const int hand) const -> void { - DistHash * dp; - int hand_dist[DDS_HANDS]; - unsigned char len[DDS_HANDS][DDS_SUITS]; - - fout << setw(4) << left << "Key" << - setw(3) << right << "No" << - setw(8) << right << players()[0] << - setw(8) << players()[1] << - setw(8) << players()[2] << - setw(8) << players()[3] << "\n"; - - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - if (dp->next_no_ == 0) - continue; - - for (int i = 0; i < dp->next_no_; i++) { - if (i == 0) - fout << "0x" << setw(2) << hex << hashkey << - setw(3) << right << dec << dp->next_no_ << " "; - else - fout << setw(8) << ""; - - TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); - TransTableL::dist_to_lengths(trick, hand_dist, len); - - fout << TransTableL::len_to_str(len) << "\n"; + DistHash * dp; + int hand_dist[DDS_HANDS]; + unsigned char len[DDS_HANDS][DDS_SUITS]; + + fout << setw(4) << left << "Key" << + setw(3) << right << "No" << + setw(8) << right << players()[0] << + setw(8) << players()[1] << + setw(8) << players()[2] << + setw(8) << players()[3] << "\n"; + + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + if (dp->next_no_ == 0) + continue; + + for (int i = 0; i < dp->next_no_; i++) { + if (i == 0) + fout << "0x" << setw(2) << hex << hashkey << + setw(3) << right << dec << dp->next_no_ << " "; + else + fout << setw(8) << ""; + + TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); + TransTableL::dist_to_lengths(trick, hand_dist, len); + + fout << TransTableL::len_to_str(len) << "\n"; + } } - } - fout << "\n"; + fout << "\n"; } auto TransTableL::print_all_suits(ofstream& fout) const -> void { - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - fout << "Trick " << trick << ", hand " << - players()[static_cast(hand)] << "\n"; - fout << string(20, '=') << "\n\n"; + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + fout << "Trick " << trick << ", hand " << + players()[static_cast(hand)] << "\n"; + fout << string(20, '=') << "\n\n"; - TransTableL::print_suits(fout, trick, hand); + TransTableL::print_suits(fout, trick, hand); + } } - } } auto TransTableL::make_hist_stats( - const int hist[], - int& count, - int& prod_sum, - int& prod_sumsq, - int& max_len, - const int last_index) const -> void + const int hist[], + int& count, + int& prod_sum, + int& prod_sumsq, + int& max_len, + const int last_index) const -> void { - count = 0; - prod_sum = 0; - prod_sumsq = 0; - max_len = 0; - - for (int i = 1; i <= last_index; i++) { - if (hist[i]) { - prod_sum += i * hist[i]; - prod_sumsq += i * i * hist[i]; - count += hist[i]; - - if (i > max_len) - max_len = i; + count = 0; + prod_sum = 0; + prod_sumsq = 0; + max_len = 0; + + for (int i = 1; i <= last_index; i++) { + if (hist[i]) { + prod_sum += i * hist[i]; + prod_sumsq += i * i * hist[i]; + count += hist[i]; + + if (i > max_len) + max_len = i; + } } - } } auto TransTableL::calc_percentile( - const int hist[], - const double threshold, - const int last_index) const -> int + const int hist[], + const double threshold, + const int last_index) const -> int { - int cum = 0; - - for (int i = 1; i <= last_index; i++) { - cum += hist[i]; - if (cum >= threshold) - return i; - } - return -1; + int cum = 0; + + for (int i = 1; i <= last_index; i++) { + cum += hist[i]; + if (cum >= threshold) + return i; + } + return -1; } auto TransTableL::print_hist( - ofstream& fout, - const int hist[], - const int num_wraps, - const int last_index) const -> void + ofstream& fout, + const int hist[], + const int num_wraps, + const int last_index) const -> void { - int count, prod_sum, prod_sumsq, max_len; - - TransTableL::make_hist_stats( - hist, - count, - prod_sum, - prod_sumsq, - max_len, - last_index); - - for (int i = 1; i <= last_index; i++) - if (hist[i]) - fout << setw(7) << right << i << - setw(6) << right << hist[i] << "\n"; - - fout << "\n"; - fout << setw(7) << left << "Entries" << - setw(6) << right << count << "\n"; - - if (count > 1) { - fout << setw(7) << left << "Full" << - setw(6) << right << num_wraps << "\n"; - - double mean = prod_sum / static_cast(count); - fout << setw(7) << left << "Average" << - setw(6) << right << setprecision(2) << fixed << mean << "\n"; - - double var = (prod_sumsq - count * mean * mean) / - static_cast(count - 1); - - if (var >= 0.) - fout << setw(7) << left << "Std.dev" << - setw(6) << right << setprecision(2) << fixed << sqrt(var) << "\n"; - - fout << setw(7) << left << "Maximum" << - setw(6) << right << max_len << "\n"; - } - fout << "\n"; + int count, prod_sum, prod_sumsq, max_len; + + TransTableL::make_hist_stats( + hist, + count, + prod_sum, + prod_sumsq, + max_len, + last_index); + + for (int i = 1; i <= last_index; i++) + if (hist[i]) + fout << setw(7) << right << i << + setw(6) << right << hist[i] << "\n"; + + fout << "\n"; + fout << setw(7) << left << "Entries" << + setw(6) << right << count << "\n"; + + if (count > 1) { + fout << setw(7) << left << "Full" << + setw(6) << right << num_wraps << "\n"; + + double mean = prod_sum / static_cast(count); + fout << setw(7) << left << "Average" << + setw(6) << right << setprecision(2) << fixed << mean << "\n"; + + double var = (prod_sumsq - count * mean * mean) / + static_cast(count - 1); + + if (var >= 0.) + fout << setw(7) << left << "Std.dev" << + setw(6) << right << setprecision(2) << fixed << sqrt(var) << "\n"; + + fout << setw(7) << left << "Maximum" << + setw(6) << right << max_len << "\n"; + } + fout << "\n"; } auto TransTableL::update_suit_hist( - const int trick, - const int hand, - int hist[], - int& num_wraps) const -> void + const int trick, + const int hand, + int hist[], + int& num_wraps) const -> void { - DistHash * dp; + DistHash * dp; - num_wraps = 0; - for (int i = 0; i <= DistsPerEntry; i++) - hist[i] = 0; + num_wraps = 0; + for (int i = 0; i <= DistsPerEntry; i++) + hist[i] = 0; - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - hist[dp->next_no_]++; + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + hist[dp->next_no_]++; - if (dp->next_no_ != dp->next_write_no_) - num_wraps++; // Not entirely correct - } + if (dp->next_no_ != dp->next_write_no_) + num_wraps++; // Not entirely correct + } } auto TransTableL::update_suit_hist( - const int trick, - const int hand, - int hist[], - int suit_hist[], - int& num_wraps, - int& suit_wraps) const -> void + const int trick, + const int hand, + int hist[], + int suit_hist[], + int& num_wraps, + int& suit_wraps) const -> void { - DistHash * dp; + DistHash * dp; - num_wraps = 0; - for (int i = 0; i <= DistsPerEntry; i++) - hist[i] = 0; + num_wraps = 0; + for (int i = 0; i <= DistsPerEntry; i++) + hist[i] = 0; - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - hist[dp->next_no_]++; - suit_hist[dp->next_no_]++; + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + hist[dp->next_no_]++; + suit_hist[dp->next_no_]++; - if (dp->next_no_ != dp->next_write_no_) { - num_wraps++; // Not entirely correct - suit_wraps++; + if (dp->next_no_ != dp->next_write_no_) { + num_wraps++; // Not entirely correct + suit_wraps++; + } } - } } auto TransTableL::print_suit_stats( - ofstream& fout, - const int trick, - const int hand) const -> void + ofstream& fout, + const int trick, + const int hand) const -> void { - int hist[DistsPerEntry + 1]; - int num_wraps; + int hist[DistsPerEntry + 1]; + int num_wraps; - TransTableL::update_suit_hist(trick, hand, hist, num_wraps); + TransTableL::update_suit_hist(trick, hand, hist, num_wraps); - fout << "Suit histogram for trick " << trick << ", hand " << - players()[static_cast(hand)] << "\n"; - TransTableL::print_hist(fout, hist, num_wraps, DistsPerEntry); + fout << "Suit histogram for trick " << trick << ", hand " << + players()[static_cast(hand)] << "\n"; + TransTableL::print_hist(fout, hist, num_wraps, DistsPerEntry); } auto TransTableL::print_all_suit_stats(ofstream& fout) const -> void { - int num_wraps; - int suit_wraps = 0; + int num_wraps; + int suit_wraps = 0; - // Really the maximum of BlocksPerEntry and DistsPerEntry. - int hist[DistsPerEntry + 1]; - int suit_hist[DistsPerEntry + 1]; + // Really the maximum of BlocksPerEntry and DistsPerEntry. + int hist[DistsPerEntry + 1]; + int suit_hist[DistsPerEntry + 1]; - for (int i = 0; i <= DistsPerEntry; i++) - suit_hist[i] = 0; + for (int i = 0; i <= DistsPerEntry; i++) + suit_hist[i] = 0; - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - TransTableL::update_suit_hist(trick, hand, hist, suit_hist, - num_wraps, suit_wraps); + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + TransTableL::update_suit_hist(trick, hand, hist, suit_hist, + num_wraps, suit_wraps); - fout << "Suit histogram for trick " << trick << ", hand " << - players()[static_cast(hand)] << "\n"; - TransTableL::print_hist(fout, hist, num_wraps, DistsPerEntry); + fout << "Suit histogram for trick " << trick << ", hand " << + players()[static_cast(hand)] << "\n"; + TransTableL::print_hist(fout, hist, num_wraps, DistsPerEntry); + } } - } - fout << "Overall suit histogram\n"; - TransTableL::print_hist(fout, suit_hist, suit_wraps, DistsPerEntry); + fout << "Overall suit histogram\n"; + TransTableL::print_hist(fout, suit_hist, suit_wraps, DistsPerEntry); } auto TransTableL::print_summary_suit_stats(ofstream& fout) const -> void { - int hist[DistsPerEntry + 1]; - int count, prod_sum, prod_sumsq, max_len, num_wraps; - - fout << "Suit depth statistics\n\n"; - - fout << setw(5) << right << "Trick" << - setw(7) << "Player" << - setw(8) << "Entries" << - setw(8) << "Full" << - setw(8) << "Average" << - setw(8) << "Std.dev" << - setw(8) << "Maximum" << - " P" << setw(4) << setprecision(2) << fixed << TtPercentile << "\n"; - - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - TransTableL::update_suit_hist(trick, hand, hist, num_wraps); - TransTableL::make_hist_stats(hist, - count, prod_sum, prod_sumsq, max_len, DistsPerEntry); - - double mean = 0., var = 0.; - if (count > 1) { - mean = prod_sum / static_cast(count); - - var = (prod_sumsq - count * mean * mean) / - static_cast(count - 1); - if (var < 0.) - var = 0.; - } - - const int percentile = - TransTableL::calc_percentile(hist, - TtPercentile * count, DistsPerEntry); - - fout << setw(5) << right << trick << - setw(7) << players()[static_cast(hand)] << - setw(8) << count << - setw(8) << num_wraps; - - if (count > 0) - fout << setw(8) << mean << - setw(8) << setprecision(2) << fixed << sqrt(var); - else - fout << setw(8) << '-' << setw(8) << '-'; - - fout << setw(8) << max_len << - setw(8) << setprecision(2) << fixed << percentile << "\n"; + int hist[DistsPerEntry + 1]; + int count, prod_sum, prod_sumsq, max_len, num_wraps; + + fout << "Suit depth statistics\n\n"; + + fout << setw(5) << right << "Trick" << + setw(7) << "Player" << + setw(8) << "Entries" << + setw(8) << "Full" << + setw(8) << "Average" << + setw(8) << "Std.dev" << + setw(8) << "Maximum" << + " P" << setw(4) << setprecision(2) << fixed << TtPercentile << "\n"; + + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + TransTableL::update_suit_hist(trick, hand, hist, num_wraps); + TransTableL::make_hist_stats(hist, + count, prod_sum, prod_sumsq, max_len, DistsPerEntry); + + double mean = 0., var = 0.; + if (count > 1) { + mean = prod_sum / static_cast(count); + + var = (prod_sumsq - count * mean * mean) / + static_cast(count - 1); + if (var < 0.) + var = 0.; + } + + const int percentile = + TransTableL::calc_percentile(hist, + TtPercentile * count, DistsPerEntry); + + fout << setw(5) << right << trick << + setw(7) << players()[static_cast(hand)] << + setw(8) << count << + setw(8) << num_wraps; + + if (count > 0) + fout << setw(8) << mean << + setw(8) << setprecision(2) << fixed << sqrt(var); + else + fout << setw(8) << '-' << setw(8) << '-'; + + fout << setw(8) << max_len << + setw(8) << setprecision(2) << fixed << percentile << "\n"; + } + fout << "\n"; } fout << "\n"; - } - fout << "\n"; } auto TransTableL::find_matching_dist( - const int trick, - const int hand, - const int hand_dist_sought[]) const -> TransTableL::WinBlock const * + const int trick, + const int hand, + const int hand_dist_sought[]) const -> TransTableL::WinBlock const * { - WinBlock * bp; - DistHash * dp; - int hand_dist[DDS_HANDS]; - - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - for (int i = 0; i < dp->next_no_; i++) { - bp = dp->list_[i].pos_block_; - TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); - - bool same = true; - for (int h = 0; h < DDS_HANDS; h++) { - if (hand_dist[h] != hand_dist_sought[h]) { - same = false; - break; + WinBlock * bp; + DistHash * dp; + int hand_dist[DDS_HANDS]; + + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + for (int i = 0; i < dp->next_no_; i++) { + bp = dp->list_[i].pos_block_; + TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); + + bool same = true; + for (int h = 0; h < DDS_HANDS; h++) { + if (hand_dist[h] != hand_dist_sought[h]) { + same = false; + break; + } + } + if (same) + return bp; } - } - if (same) - return bp; } - } - return nullptr; + return nullptr; } auto TransTableL::print_entries_block( - ofstream& fout, - WinBlock const * bp, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void + ofstream& fout, + WinBlock const * bp, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void { - string st = to_string(bp->next_match_no_) + - " matches for " + TransTableL::len_to_str(lengths); + string st = to_string(bp->next_match_no_) + + " matches for " + TransTableL::len_to_str(lengths); - fout << st << "\n" << string(st.size(), '=') << "\n\n"; + fout << st << "\n" << string(st.size(), '=') << "\n\n"; - for (int j = 0; j < bp->next_match_no_; j++) { - st = "Entry number " + to_string(j + 1); - fout << st << "\n"; - fout << string(st.size(), '-') << "\n\n"; - TransTableL::print_match(fout, bp->list_[j], lengths); - } + for (int j = 0; j < bp->next_match_no_; j++) { + st = "Entry number " + to_string(j + 1); + fout << st << "\n"; + fout << string(st.size(), '-') << "\n\n"; + TransTableL::print_match(fout, bp->list_[j], lengths); + } } auto TransTableL::print_entries_dist_and_cards( - ofstream& fout, - const int trick, - const int hand, - const unsigned short aggr_target[], - const int hand_dist[]) const -> void + ofstream& fout, + const int trick, + const int hand, + const unsigned short aggr_target[], + const int hand_dist[]) const -> void { - unsigned char len[DDS_HANDS][DDS_SUITS]; + unsigned char len[DDS_HANDS][DDS_SUITS]; + + WinBlock const * bp = + TransTableL::find_matching_dist(trick, hand, hand_dist); - WinBlock const * bp = - TransTableL::find_matching_dist(trick, hand, hand_dist); + TransTableL::dist_to_lengths(trick, hand_dist, len); - TransTableL::dist_to_lengths(trick, hand_dist, len); + fout << "Looking up entry for trick " << trick << ", hand " << + players()[static_cast(hand)] << "\n"; + fout << TransTableL::len_to_str(len) << "\n\n"; - fout << "Looking up entry for trick " << trick << ", hand " << - players()[static_cast(hand)] << "\n"; - fout << TransTableL::len_to_str(len) << "\n\n"; + if (!bp) { + fout << "Entry not found\n\n"; + return; + } - if (!bp) { - fout << "Entry not found\n\n"; - return; - } + unsigned const * ab0 = aggr_[aggr_target[0]].aggr_bytes_[0]; + unsigned const * ab1 = aggr_[aggr_target[1]].aggr_bytes_[1]; + unsigned const * ab2 = aggr_[aggr_target[2]].aggr_bytes_[2]; + unsigned const * ab3 = aggr_[aggr_target[3]].aggr_bytes_[3]; - unsigned const * ab0 = aggr_[aggr_target[0]].aggr_bytes_[0]; - unsigned const * ab1 = aggr_[aggr_target[1]].aggr_bytes_[1]; - unsigned const * ab2 = aggr_[aggr_target[2]].aggr_bytes_[2]; - unsigned const * ab3 = aggr_[aggr_target[3]].aggr_bytes_[3]; + WinMatch TTentry; + TTentry.top_set1_ = ab0[0] | ab1[0] | ab2[0] | ab3[0]; + TTentry.top_set2_ = ab0[1] | ab1[1] | ab2[1] | ab3[1]; + TTentry.top_set3_ = ab0[2] | ab1[2] | ab2[2] | ab3[2]; + TTentry.top_set4_ = ab0[3] | ab1[3] | ab2[3] | ab3[3]; - WinMatch TTentry; - TTentry.top_set1_ = ab0[0] | ab1[0] | ab2[0] | ab3[0]; - TTentry.top_set2_ = ab0[1] | ab1[1] | ab2[1] | ab3[1]; - TTentry.top_set3_ = ab0[2] | ab1[2] | ab2[2] | ab3[2]; - TTentry.top_set4_ = ab0[3] | ab1[3] | ab2[3] | ab3[3]; + int matchNo = 1; + int n = bp->next_match_no_ - 1; + WinMatch const * wp = &bp->list_[n]; - int matchNo = 1; - int n = bp->next_match_no_ - 1; - WinMatch const * wp = &bp->list_[n]; + for (int i = n; i >= 0; i--, wp--) { + if ((wp->top_set1_ ^ TTentry.top_set1_) & wp->top_mask1_) + continue; - for (int i = n; i >= 0; i--, wp--) { - if ((wp->top_set1_ ^ TTentry.top_set1_) & wp->top_mask1_) - continue; + if (wp->last_mask_no_ != 1) { + if ((wp->top_set2_ ^ TTentry.top_set2_) & wp->top_mask2_) + continue; - if (wp->last_mask_no_ != 1) { - if ((wp->top_set2_ ^ TTentry.top_set2_) & wp->top_mask2_) - continue; + if (wp->last_mask_no_ != 2) { + if ((wp->top_set3_ ^ TTentry.top_set3_) & wp->top_mask3_) + continue; + } + } - if (wp->last_mask_no_ != 2) { - if ((wp->top_set3_ ^ TTentry.top_set3_) & wp->top_mask3_) - continue; - } + fout << "Match number " << matchNo++ << "\n"; + fout << string(15, '-') << "\n"; + TransTableL::print_match(fout, bp->list_[i], len); } - fout << "Match number " << matchNo++ << "\n"; - fout << string(15, '-') << "\n"; - TransTableL::print_match(fout, bp->list_[i], len); - } - - if (matchNo == 1) - fout << n << " matches for suit, none for cards\n\n"; - else - fout << "\n"; + if (matchNo == 1) + fout << n << " matches for suit, none for cards\n\n"; + else + fout << "\n"; } auto TransTableL::print_entries_dist( - ofstream& fout, - const int trick, - const int hand, - const int hand_dist[]) const -> void + ofstream& fout, + const int trick, + const int hand, + const int hand_dist[]) const -> void { - unsigned char len[DDS_HANDS][DDS_SUITS]; + unsigned char len[DDS_HANDS][DDS_SUITS]; - WinBlock const * bp = - TransTableL::find_matching_dist(trick, hand, hand_dist); + WinBlock const * bp = + TransTableL::find_matching_dist(trick, hand, hand_dist); - TransTableL::dist_to_lengths(trick, hand_dist, len); + TransTableL::dist_to_lengths(trick, hand_dist, len); - if (!bp) { - fout << "Entry not found: Trick " << trick << ", hand " << - players()[static_cast(hand)] << "\n"; - fout << TransTableL::len_to_str(len) << "\n\n"; - return; - } + if (!bp) { + fout << "Entry not found: Trick " << trick << ", hand " << + players()[static_cast(hand)] << "\n"; + fout << TransTableL::len_to_str(len) << "\n\n"; + return; + } - TransTableL::print_entries_block(fout, bp, len); + TransTableL::print_entries_block(fout, bp, len); } auto TransTableL::print_entries( - ofstream& fout, - const int trick, - const int hand) const -> void + ofstream& fout, + const int trick, + const int hand) const -> void { - WinBlock * bp; - DistHash * dp; - int hand_dist[DDS_HANDS]; - unsigned char lengths[DDS_HANDS][DDS_SUITS]; - - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - for (int i = 0; i < dp->next_no_; i++) { - bp = dp->list_[i].pos_block_; - TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); - TransTableL::dist_to_lengths(trick, hand_dist, lengths); - - TransTableL::print_entries_block(fout, bp, lengths); + WinBlock * bp; + DistHash * dp; + int hand_dist[DDS_HANDS]; + unsigned char lengths[DDS_HANDS][DDS_SUITS]; + + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + for (int i = 0; i < dp->next_no_; i++) { + bp = dp->list_[i].pos_block_; + TransTableL::key_to_dist(dp->list_[i].key_, hand_dist); + TransTableL::dist_to_lengths(trick, hand_dist, lengths); + + TransTableL::print_entries_block(fout, bp, lengths); + } } - } } auto TransTableL::print_all_entries(ofstream& fout) const -> void { - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - const string st = "Entries, trick " + to_string(trick) + - ", hand " + players()[static_cast(hand)]; - fout << st << "\n"; - fout << string(st.size(), '=') << "\n\n"; - TransTableL::print_entries(fout, trick, hand); + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + const string st = "Entries, trick " + to_string(trick) + + ", hand " + players()[static_cast(hand)]; + fout << st << "\n"; + fout << string(st.size(), '=') << "\n\n"; + TransTableL::print_entries(fout, trick, hand); + } } - } - fout << "\n"; + fout << "\n"; } auto TransTableL::update_entry_hist( - const int trick, - const int hand, - int hist[], - int& num_wraps) const -> void + const int trick, + const int hand, + int hist[], + int& num_wraps) const -> void { - DistHash * dp; + DistHash * dp; - num_wraps = 0; - for (int i = 0; i <= BlocksPerEntry; i++) - hist[i] = 0; + num_wraps = 0; + for (int i = 0; i <= BlocksPerEntry; i++) + hist[i] = 0; - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - for (int i = 0; i < dp->next_no_; i++) { - int c = dp->list_[i].pos_block_->next_match_no_; - hist[c]++; + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + for (int i = 0; i < dp->next_no_; i++) { + int c = dp->list_[i].pos_block_->next_match_no_; + hist[c]++; - if (c != dp->list_[i].pos_block_->next_write_no_) - num_wraps++; // Not entirely correct + if (c != dp->list_[i].pos_block_->next_write_no_) + num_wraps++; // Not entirely correct + } } - } } auto TransTableL::update_entry_hist( - const int trick, - const int hand, - int hist[], - int suit_hist[], - int& num_wraps, - int& suit_wraps) const -> void + const int trick, + const int hand, + int hist[], + int suit_hist[], + int& num_wraps, + int& suit_wraps) const -> void { - DistHash * dp; - - num_wraps = 0; - for (int i = 0; i <= BlocksPerEntry; i++) - hist[i] = 0; - - for (int hashkey = 0; hashkey < 256; hashkey++) { - dp = &tt_root_[trick][hand][hashkey]; - for (int i = 0; i < dp->next_no_; i++) { - int c = dp->list_[i].pos_block_->next_match_no_; - hist[c]++; - suit_hist[c]++; - - if (c != dp->list_[i].pos_block_->next_write_no_) { - num_wraps++; // Not entirely correct - suit_wraps++; - } + DistHash * dp; + + num_wraps = 0; + for (int i = 0; i <= BlocksPerEntry; i++) + hist[i] = 0; + + for (int hashkey = 0; hashkey < 256; hashkey++) { + dp = &tt_root_[trick][hand][hashkey]; + for (int i = 0; i < dp->next_no_; i++) { + int c = dp->list_[i].pos_block_->next_match_no_; + hist[c]++; + suit_hist[c]++; + + if (c != dp->list_[i].pos_block_->next_write_no_) { + num_wraps++; // Not entirely correct + suit_wraps++; + } + } } - } } auto TransTableL::print_entry_stats( - ofstream& fout, - const int trick, - const int hand) const -> void + ofstream& fout, + const int trick, + const int hand) const -> void { - int hist[BlocksPerEntry + 1]; - int num_wraps; + int hist[BlocksPerEntry + 1]; + int num_wraps; - TransTableL::update_entry_hist(trick, hand, hist, num_wraps); + TransTableL::update_entry_hist(trick, hand, hist, num_wraps); - fout << "Entry histogram for trick " << trick << ", hands " << - players()[static_cast(hand)] << "\n"; - TransTableL::print_hist(fout, hist, num_wraps, BlocksPerEntry); + fout << "Entry histogram for trick " << trick << ", hands " << + players()[static_cast(hand)] << "\n"; + TransTableL::print_hist(fout, hist, num_wraps, BlocksPerEntry); } auto TransTableL::print_all_entry_stats(ofstream& fout) const -> void { - int hist[BlocksPerEntry + 1]; - int num_wraps; - - int suit_wraps = 0; - int suit_hist[BlocksPerEntry + 1]; - for (int i = 0; i <= BlocksPerEntry; i++) - suit_hist[i] = 0; - - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - TransTableL::update_entry_hist(trick, hand, hist, suit_hist, - num_wraps, suit_wraps); - - fout << "Entry histogram for trick " << trick << ", hands " << - players()[static_cast(hand)] << "\n"; - TransTableL::print_hist(fout, hist, num_wraps, BlocksPerEntry); + int hist[BlocksPerEntry + 1]; + int num_wraps; + + int suit_wraps = 0; + int suit_hist[BlocksPerEntry + 1]; + for (int i = 0; i <= BlocksPerEntry; i++) + suit_hist[i] = 0; + + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + TransTableL::update_entry_hist(trick, hand, hist, suit_hist, + num_wraps, suit_wraps); + + fout << "Entry histogram for trick " << trick << ", hands " << + players()[static_cast(hand)] << "\n"; + TransTableL::print_hist(fout, hist, num_wraps, BlocksPerEntry); + } } - } - fout << "Overall entry histogram\n"; - TransTableL::print_hist(fout, suit_hist, suit_wraps, BlocksPerEntry); + fout << "Overall entry histogram\n"; + TransTableL::print_hist(fout, suit_hist, suit_wraps, BlocksPerEntry); } auto TransTableL::effect_of_block_bound( - const int hist[], - const int size) const -> int + const int hist[], + const int size) const -> int { - // Calculates the number of blocks used if the blocks - // are divided up in units of size, rather than in units - // of BlocksPerEntry. Only makes sense if size is less - // than BlocksPerEntry, as we won't have statistics for - // how many blocks above BlocksPerEntry would be created - // if BlocksPerEntry were larger. - - int cum_memory = 0; - int unit_size = 0; - - for (int i = 1; i <= BlocksPerEntry; i++) { - if ((i - 1) % size == 0) - unit_size += size; - - cum_memory += hist[i] * unit_size; - } - return cum_memory; + // Calculates the number of blocks used if the blocks + // are divided up in units of size, rather than in units + // of BlocksPerEntry. Only makes sense if size is less + // than BlocksPerEntry, as we won't have statistics for + // how many blocks above BlocksPerEntry would be created + // if BlocksPerEntry were larger. + + int cum_memory = 0; + int unit_size = 0; + + for (int i = 1; i <= BlocksPerEntry; i++) { + if ((i - 1) % size == 0) + unit_size += size; + + cum_memory += hist[i] * unit_size; + } + return cum_memory; } auto TransTableL::print_summary_entry_stats(ofstream& fout) const -> void { - int hist[BlocksPerEntry + 1]; - int count, prod_sum, prod_sumsq, max_len, num_wraps; - - int cumCount = 0; - double cumProd = 0.; - int cumMemory = 0; - - fout << "Entry depth statistics\n\n"; - - fout << setw(5) << right << "Trick" << - setw(7) << "Player" << - setw(8) << "Entries" << - setw(8) << "Full" << - setw(8) << "Average" << - setw(8) << "Std.dev" << - setw(8) << "Maximum" << - " P" << setw(4) << setprecision(2) << fixed << TtPercentile << "\n"; - - for (int trick = 11; trick >= 1; trick--) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - TransTableL::update_entry_hist(trick, hand, hist, num_wraps); - TransTableL::make_hist_stats(hist, - count, prod_sum, prod_sumsq, max_len, BlocksPerEntry); - - cumCount += count; - cumProd += prod_sum; - cumMemory += TransTableL::effect_of_block_bound(hist, 20); - - double mean = prod_sum / static_cast(count); - double var = (count > 1 ? - (prod_sumsq - count * mean * mean) / - static_cast(count - 1) : 0.); - - if (var < 0.) - var = 0.; - - const int percentile = TransTableL::calc_percentile( - hist, - TtPercentile * count, - BlocksPerEntry); - - fout << setw(5) << right << trick << - setw(7) << players()[static_cast(hand)] << - setw(8) << count << - setw(8) << num_wraps << - setw(8) << mean << - setw(8) << sqrt(var) << - setw(8) << max_len << - setw(8) << setprecision(2) << fixed << percentile << "\n"; + int hist[BlocksPerEntry + 1]; + int count, prod_sum, prod_sumsq, max_len, num_wraps; + + int cumCount = 0; + double cumProd = 0.; + int cumMemory = 0; + + fout << "Entry depth statistics\n\n"; + + fout << setw(5) << right << "Trick" << + setw(7) << "Player" << + setw(8) << "Entries" << + setw(8) << "Full" << + setw(8) << "Average" << + setw(8) << "Std.dev" << + setw(8) << "Maximum" << + " P" << setw(4) << setprecision(2) << fixed << TtPercentile << "\n"; + + for (int trick = 11; trick >= 1; trick--) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + TransTableL::update_entry_hist(trick, hand, hist, num_wraps); + TransTableL::make_hist_stats(hist, + count, prod_sum, prod_sumsq, max_len, BlocksPerEntry); + + cumCount += count; + cumProd += prod_sum; + cumMemory += TransTableL::effect_of_block_bound(hist, 20); + + double mean = prod_sum / static_cast(count); + double var = (count > 1 ? + (prod_sumsq - count * mean * mean) / + static_cast(count - 1) : 0.); + + if (var < 0.) + var = 0.; + + const int percentile = TransTableL::calc_percentile( + hist, + TtPercentile * count, + BlocksPerEntry); + + fout << setw(5) << right << trick << + setw(7) << players()[static_cast(hand)] << + setw(8) << count << + setw(8) << num_wraps << + setw(8) << mean << + setw(8) << sqrt(var) << + setw(8) << max_len << + setw(8) << setprecision(2) << fixed << percentile << "\n"; + } + fout << "\n"; } fout << "\n"; - } - fout << "\n"; - - fout << setw(16) << left << "Blocks counted " << - setw(8) << right << cumCount << "\n"; - fout << setw(16) << left << "Blocks produced " << - setw(8) << right << TransTableL::blocks_in_use() << "\n"; - fout << setw(16) << left << "Mem scenario" << - setw(7) << right << setprecision(2) << fixed << - 100. * cumMemory / - static_cast(BlocksPerEntry * cumCount) << "%\n"; - - if (cumCount) - fout << setw(16) << left << "Fullness" << - setw(7) << right << setprecision(2) << fixed << - 100. * cumProd / (BlocksPerEntry * cumCount) << "%\n"; - fout << "\n"; + + fout << setw(16) << left << "Blocks counted " << + setw(8) << right << cumCount << "\n"; + fout << setw(16) << left << "Blocks produced " << + setw(8) << right << TransTableL::blocks_in_use() << "\n"; + fout << setw(16) << left << "Mem scenario" << + setw(7) << right << setprecision(2) << fixed << + 100. * cumMemory / + static_cast(BlocksPerEntry * cumCount) << "%\n"; + + if (cumCount) + fout << setw(16) << left << "Fullness" << + setw(7) << right << setprecision(2) << fixed << + 100. * cumProd / (BlocksPerEntry * cumCount) << "%\n"; + fout << "\n"; } diff --git a/library/src/trans_table/trans_table_l.hpp b/library/src/trans_table/trans_table_l.hpp index 98f17089e..8963b44c6 100644 --- a/library/src/trans_table/trans_table_l.hpp +++ b/library/src/trans_table/trans_table_l.hpp @@ -23,15 +23,15 @@ enum { - NumPagesDefault = 15, - NumPagesMaximum = 25, - BlocksPerPage = 1000, - DistsPerEntry = 32, - BlocksPerEntry = 125, - FirstHarvestTrick = 8, - HarvestAge = 10000, - TtBytes = 4, - TtTricks = 12 + NumPagesDefault = 15, + NumPagesMaximum = 25, + BlocksPerPage = 1000, + DistsPerEntry = 32, + BlocksPerEntry = 125, + FirstHarvestTrick = 8, + HarvestAge = 10000, + TtBytes = 4, + TtTricks = 12 }; inline constexpr double TtPercentile = 0.9; @@ -81,464 +81,464 @@ inline constexpr double TtPercentile = 0.9; /// \see NodeCards for cached position data class TransTableL: public TransTable { - private: - - /// \brief A cached position match in the transposition table (52 bytes). - struct WinMatch // 52 bytes - { - unsigned xor_set_; ///< XOR of card holdings - unsigned top_set1_, top_set2_, top_set3_, top_set4_; ///< Top card sets - unsigned top_mask1_, top_mask2_, top_mask3_, top_mask4_; ///< Top masks - int mask_index_; ///< Index into mask array - int last_mask_no_; ///< Last mask number - NodeCards first_; ///< Cached search result - }; - - /// \brief Block of match entries (6508 bytes). - struct WinBlock // 6508 bytes when BlocksPerEntry == 125 - { - int next_match_no_; ///< Index of next available entry - int next_write_no_; ///< Index for next write - int timestamp_read_; ///< When last accessed (for harvesting) - WinMatch list_[BlocksPerEntry]; ///< Array of match entries - }; - - /// \brief Hash entry for a particular card distribution (16 bytes). - struct PosSearch // 16 bytes (inefficiency, 12 bytes enough) - { - WinBlock * pos_block_; ///< Block containing this distribution - long long key_; ///< Distribution hash key - }; - - /// \brief Hash table for a particular trick/hand (520 bytes). - struct DistHash // 520 bytes when DistsPerEntry == 32 - { - int next_no_; ///< Next entry index - int next_write_no_; ///< Next write index - PosSearch list_[DistsPerEntry]; ///< Hash entries for distributions - }; - - /// \brief Aggregated targeting information per hand (80 bytes). - struct Aggr // 80 bytes - { - unsigned aggr_ranks_[DDS_SUITS]; ///< Target tricks per suit - unsigned aggr_bytes_[DDS_SUITS][TtBytes]; ///< Encoded bytes per suit - }; - - /// \brief Pool node for memory block linked list (16 bytes). - struct Pool // 16 bytes - { - Pool * next_; ///< Next pool in list - Pool * prev_; ///< Previous pool in list - int next_block_no_; ///< Next available block index - WinBlock * list_; ///< Array of blocks in this pool - }; - - /// \brief Statistics tracking for memory page usage. - struct PageStats - { - int num_resets_; ///< Total resets performed - int num_callocs_; ///< Total allocations - int num_frees_; ///< Total deallocations - int num_harvests_; ///< Total harvest operations - int last_current_; ///< Last current page number - int num_adds_; ///< Total new entries inserted - int num_overwrites_; ///< Insertions that overwrote existing entries - }; - - /// \brief Harvested blocks saved for potential reuse (16 bytes). - struct Harvested // 16 bytes - { - int next_block_no_; ///< Index of next available block - WinBlock * list_[BlocksPerPage]; ///< Array of harvested blocks - }; - - enum class MemState - { - FROM_POOL, - FROM_HARVEST - }; - - // Private data for the full memory version. - MemState mem_state_; - - int pages_default_; - int pages_current_; - int pages_maximum_; - - int harvest_trick_; - int harvest_hand_; - - PageStats page_stats_; - - // aggr is constant for a given hand. - Aggr aggr_[8192]; // 64 KB - - // This is the real transposition table. - // The last index is the hash. - // 6240 KB with above assumptions - // DistHash tt_root_[TtTricks][DDS_HANDS][256]; - DistHash * tt_root_[TtTricks][DDS_HANDS]; - - // It is useful to remember the last block we looked at. - WinBlock * last_block_seen_[TtTricks][DDS_HANDS]; - - // The pool of card entries for a given suit distribution. - Pool * pool_; - WinBlock * next_block_; - Harvested harvested_; - - int timestamp_; - int tt_in_use_; - - - auto init_tt() -> void; - - auto release_tt() -> void; - - // Constants are provided via internal function-local static tables. - - auto hash8(const int hand_dist[]) const -> int; - - auto get_next_card_block() -> WinBlock *; - - auto lookup_suit( - DistHash * dp, - long long key, - bool& empty) -> WinBlock *; - - auto lookup_cards( - const WinMatch& search, - WinBlock * bp, - int limit, - bool& lowerFlag) -> NodeCards *; - - auto create_or_update( - WinBlock * bp, - const WinMatch& search, - bool flag) -> void; - - auto harvest() -> bool; - - // Debug functions from here on. - - auto key_to_dist( - long long key, - int hand_dist[]) const -> void; - - auto dist_to_lengths( - int trick, - const int hand_dist[], - unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; - - auto single_len_to_str(const unsigned char length[]) const -> std::string; - - auto len_to_str( - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> std::string; - - auto make_hist_stats( - const int hist[], - int& count, - int& prodSum, - int& prodSumsq, - int& maxLen, - int lastIndex) const -> void; - - auto calc_percentile( - const int hist[], - double threshold, - int lastIndex) const -> int; - - auto print_hist( - std::ofstream& fout, - const int hist[], - int numWraps, - int lastIndex) const -> void; - - auto update_suit_hist( - int trick, - int hand, - int hist[], - int& numWraps) const -> void; - - auto update_suit_hist( - int trick, - int hand, - int hist[], - int suit_hist[], - int& num_wraps, - int& suit_wraps) const -> void; - - auto find_matching_dist( - int trick, - int hand, - const int hand_dist_sought[]) const -> WinBlock const *; - - auto print_entries_block( - std::ofstream& fout, - WinBlock const * bp, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; - - auto update_entry_hist( - int trick, - int hand, - int hist[], - int& numWraps) const -> void; - - auto update_entry_hist( - int trick, - int hand, - int hist[], - int suitHist[], - int& numWraps, - int& suitWraps) const -> void; - - auto effect_of_block_bound( - const int hist[], - int size) const -> int; - - auto print_node_values( - std::ofstream& fout, - const NodeCards& node) const -> void; - - auto print_match( - std::ofstream& fout, - const WinMatch& match, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; - - auto make_holding( - const std::string& high, - unsigned len) const -> std::string; - - auto dump_hands( - std::ofstream& fout, - const std::vector>& hands, - const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; - - auto set_to_partial_hands( - const unsigned set, - const unsigned mask, - const int max_rank, - const int num_ranks, - std::vector>& hands) const -> void; - - auto blocks_in_use() const -> int; - - // Legacy implementation helpers removed; modern overrides are canonical. - - public: - /// \brief Construct a large transposition table instance. - /// - /// Initializes the large TT with default memory limits. Must call - /// make_tt() to actually allocate memory. - TransTableL(); - - /// \brief Destroy the large transposition table. - /// - /// Releases all allocated memory and internal structures. - ~TransTableL(); - - /// \brief Initialize the transposition table with hand lookup tables. - /// - /// Sets up the TT with hand lookup configuration for position hashing. - /// - /// \param hand_lookup Hand lookup table array of size [DDS_SUITS][15] - /// (i.e., parameter type const int hand_lookup[DDS_SUITS][15], 4 suits by 15 ranks). - /// \throws std::bad_alloc if initialization fails - /// \par Usage Example - /// \code - /// unsigned short ag[DDS_HANDS] = { 0x1fff, 0x1fff, 0x0f75, 0x1fff }; - /// int hd[DDS_HANDS] = { 0x0342, 0x0334, 0x0232, 0x0531 }; - /// bool lower_flag = false; - /// int wr = 0; - /// int result = 0; - /// thrp->transTable.lookup(11, 1, ag, hd, 13, lower_flag); - /// thrp->transTable.add(11, 1, ag, wr, result, false); - /// \endcode - auto init(const int hand_lookup[][15]) -> void override; - - /// \brief Set the default (soft) memory limit. - /// - /// \param megabytes Desired soft memory limit in MB - auto set_memory_default(int megabytes) -> void override; - - /// \brief Set the maximum (hard) memory limit. - /// - /// \param megabytes Maximum allowed memory in MB - auto set_memory_maximum(int megabytes) -> void override; - - /// \brief Allocate transposition table memory structures. - /// - /// \throws std::bad_alloc if memory allocation fails - auto make_tt() -> void override; - - /// \brief Clear cached entries and reset statistics. - /// - /// \param reason Why the reset is occurring - auto reset_memory(ResetReason reason) -> void override; - - /// \brief Deallocate all transposition table memory. - /// - /// After calling this, must call make_tt() before further lookups. - auto return_all_memory() -> void override; - - /// \brief Return current memory usage in kilobytes. - /// - /// \return Memory in use (KB) - auto memory_in_use() const -> double override; - - /// \brief Lookup a cached position result. - /// - /// Searches for previously cached analysis of the given position. - /// Returns nullptr if not found. - /// - /// \param trick Current trick (0-12) - /// \param hand Hand to play (0-3) - /// \param aggr_target Target tricks per suit - /// \param hand_dist Card distribution - /// \param limit Early termination threshold - /// \param[out] lower_flag Set to true if result is a lower bound - /// \return Cached result or nullptr - /// \note Lookup updates the timestamp for harvesting considerations - auto lookup( - int trick, - int hand, - const unsigned short aggr_target[], - const int hand_dist[], - int limit, - bool& lower_flag) -> NodeCards const * override; - - /// \brief Add a computed result to the transposition table. - /// - /// Caches a newly computed search result for later lookup. May trigger - /// harvesting if memory limits are approached. - /// - /// \param trick Current trick (0-12) - /// \param hand Hand to play (0-3) - /// \param aggr_target Target tricks per suit - /// \param win_ranks_arg Winning ranks (optimization data) - /// \param first Computed result to cache - /// \param flag True if this is a lower bound (incomplete search) - /// \throws std::bad_alloc if critical memory allocation fails - /// \note May trigger harvest if soft memory limit exceeded - auto add( - int trick, - int hand, - const unsigned short aggr_target[], - const unsigned short win_ranks_arg[], - const NodeCards& first, - bool flag) -> void override; - - /// \brief Print cached results for a specific suit and position. - /// - /// Outputs detailed analysis of cached entries for the given trick/hand. - /// Large TT provides detailed suit-level statistics. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - auto print_suits( - std::ofstream& fout, - int trick, - int hand) const -> void override; - - /// \brief Print all suit results in the transposition table. - /// - /// \param fout Output stream - auto print_all_suits(std::ofstream& fout) const -> void override; - - /// \brief Print suit statistics for a specific position. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - auto print_suit_stats( - std::ofstream& fout, - int trick, - int hand) const -> void override; - - /// \brief Print suit statistics for all positions. - /// - /// \param fout Output stream - auto print_all_suit_stats(std::ofstream& fout) const -> void override; - - /// \brief Reset per-solve operation counters (adds, overwrites, harvests). - auto reset_op_stats() -> void override - { - page_stats_.num_adds_ = 0; - page_stats_.num_overwrites_ = 0; - page_stats_.num_harvests_ = 0; - } - /// \brief Get per-solve operation counters for instrumentation. - auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override - { - adds = page_stats_.num_adds_; - overwrites = page_stats_.num_overwrites_; - harvests = page_stats_.num_harvests_; - } - /// \brief Print summary suit statistics. - /// - /// \param fout Output stream - auto print_summary_suit_stats(std::ofstream& fout) const -> void override; - - /// \brief Print entries for a specific hand distribution. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - /// \param hand_dist Card distribution array - auto print_entries_dist( - std::ofstream& fout, - int trick, - int hand, - const int hand_dist[]) const -> void override; - - /// \brief Print entries and card details for a hand distribution. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - /// \param aggr_target Target tricks per suit - /// \param hand_dist Card distribution array - auto print_entries_dist_and_cards( - std::ofstream& fout, - int trick, - int hand, - const unsigned short aggr_target[], - const int hand_dist[]) const -> void override; - - /// \brief Print entries for a specific trick/hand. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - auto print_entries( - std::ofstream& fout, - int trick, - int hand) const -> void override; - - /// \brief Print all cached entries in the table. - /// - /// \param fout Output stream - auto print_all_entries(std::ofstream& fout) const -> void override; - - /// \brief Print entry statistics for a specific position. - /// - /// \param fout Output stream - /// \param trick Trick number (0-12) - /// \param hand Hand (0-3) - auto print_entry_stats( - std::ofstream& fout, - int trick, - int hand) const -> void override; - - /// \brief Print entry statistics for all positions. - /// - /// \param fout Output stream - auto print_all_entry_stats(std::ofstream& fout) const -> void override; - - /// \brief Print summary entry statistics. - /// - /// \param fout Output stream - auto print_summary_entry_stats(std::ofstream& fout) const -> void override; + private: + + /// \brief A cached position match in the transposition table (52 bytes). + struct WinMatch // 52 bytes + { + unsigned xor_set_; ///< XOR of card holdings + unsigned top_set1_, top_set2_, top_set3_, top_set4_; ///< Top card sets + unsigned top_mask1_, top_mask2_, top_mask3_, top_mask4_; ///< Top masks + int mask_index_; ///< Index into mask array + int last_mask_no_; ///< Last mask number + NodeCards first_; ///< Cached search result + }; + + /// \brief Block of match entries (6508 bytes). + struct WinBlock // 6508 bytes when BlocksPerEntry == 125 + { + int next_match_no_; ///< Index of next available entry + int next_write_no_; ///< Index for next write + int timestamp_read_; ///< When last accessed (for harvesting) + WinMatch list_[BlocksPerEntry]; ///< Array of match entries + }; + + /// \brief Hash entry for a particular card distribution (16 bytes). + struct PosSearch // 16 bytes (inefficiency, 12 bytes enough) + { + WinBlock * pos_block_; ///< Block containing this distribution + long long key_; ///< Distribution hash key + }; + + /// \brief Hash table for a particular trick/hand (520 bytes). + struct DistHash // 520 bytes when DistsPerEntry == 32 + { + int next_no_; ///< Next entry index + int next_write_no_; ///< Next write index + PosSearch list_[DistsPerEntry]; ///< Hash entries for distributions + }; + + /// \brief Aggregated targeting information per hand (80 bytes). + struct Aggr // 80 bytes + { + unsigned aggr_ranks_[DDS_SUITS]; ///< Target tricks per suit + unsigned aggr_bytes_[DDS_SUITS][TtBytes]; ///< Encoded bytes per suit + }; + + /// \brief Pool node for memory block linked list (16 bytes). + struct Pool // 16 bytes + { + Pool * next_; ///< Next pool in list + Pool * prev_; ///< Previous pool in list + int next_block_no_; ///< Next available block index + WinBlock * list_; ///< Array of blocks in this pool + }; + + /// \brief Statistics tracking for memory page usage. + struct PageStats + { + int num_resets_; ///< Total resets performed + int num_callocs_; ///< Total allocations + int num_frees_; ///< Total deallocations + int num_harvests_; ///< Total harvest operations + int last_current_; ///< Last current page number + int num_adds_; ///< Total new entries inserted + int num_overwrites_; ///< Insertions that overwrote existing entries + }; + + /// \brief Harvested blocks saved for potential reuse (16 bytes). + struct Harvested // 16 bytes + { + int next_block_no_; ///< Index of next available block + WinBlock * list_[BlocksPerPage]; ///< Array of harvested blocks + }; + + enum class MemState + { + FROM_POOL, + FROM_HARVEST + }; + + // Private data for the full memory version. + MemState mem_state_; + + int pages_default_; + int pages_current_; + int pages_maximum_; + + int harvest_trick_; + int harvest_hand_; + + PageStats page_stats_; + + // aggr is constant for a given hand. + Aggr aggr_[8192]; // 64 KB + + // This is the real transposition table. + // The last index is the hash. + // 6240 KB with above assumptions + // DistHash tt_root_[TtTricks][DDS_HANDS][256]; + DistHash * tt_root_[TtTricks][DDS_HANDS]; + + // It is useful to remember the last block we looked at. + WinBlock * last_block_seen_[TtTricks][DDS_HANDS]; + + // The pool of card entries for a given suit distribution. + Pool * pool_; + WinBlock * next_block_; + Harvested harvested_; + + int timestamp_; + int tt_in_use_; + + + auto init_tt() -> void; + + auto release_tt() -> void; + + // Constants are provided via internal function-local static tables. + + auto hash8(const int hand_dist[]) const -> int; + + auto get_next_card_block() -> WinBlock *; + + auto lookup_suit( + DistHash * dp, + long long key, + bool& empty) -> WinBlock *; + + auto lookup_cards( + const WinMatch& search, + WinBlock * bp, + int limit, + bool& lowerFlag) -> NodeCards *; + + auto create_or_update( + WinBlock * bp, + const WinMatch& search, + bool flag) -> void; + + auto harvest() -> bool; + + // Debug functions from here on. + + auto key_to_dist( + long long key, + int hand_dist[]) const -> void; + + auto dist_to_lengths( + int trick, + const int hand_dist[], + unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; + + auto single_len_to_str(const unsigned char length[]) const -> std::string; + + auto len_to_str( + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> std::string; + + auto make_hist_stats( + const int hist[], + int& count, + int& prodSum, + int& prodSumsq, + int& maxLen, + int lastIndex) const -> void; + + auto calc_percentile( + const int hist[], + double threshold, + int lastIndex) const -> int; + + auto print_hist( + std::ofstream& fout, + const int hist[], + int numWraps, + int lastIndex) const -> void; + + auto update_suit_hist( + int trick, + int hand, + int hist[], + int& numWraps) const -> void; + + auto update_suit_hist( + int trick, + int hand, + int hist[], + int suit_hist[], + int& num_wraps, + int& suit_wraps) const -> void; + + auto find_matching_dist( + int trick, + int hand, + const int hand_dist_sought[]) const -> WinBlock const *; + + auto print_entries_block( + std::ofstream& fout, + WinBlock const * bp, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; + + auto update_entry_hist( + int trick, + int hand, + int hist[], + int& numWraps) const -> void; + + auto update_entry_hist( + int trick, + int hand, + int hist[], + int suitHist[], + int& numWraps, + int& suitWraps) const -> void; + + auto effect_of_block_bound( + const int hist[], + int size) const -> int; + + auto print_node_values( + std::ofstream& fout, + const NodeCards& node) const -> void; + + auto print_match( + std::ofstream& fout, + const WinMatch& match, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; + + auto make_holding( + const std::string& high, + unsigned len) const -> std::string; + + auto dump_hands( + std::ofstream& fout, + const std::vector>& hands, + const unsigned char lengths[DDS_HANDS][DDS_SUITS]) const -> void; + + auto set_to_partial_hands( + const unsigned set, + const unsigned mask, + const int max_rank, + const int num_ranks, + std::vector>& hands) const -> void; + + auto blocks_in_use() const -> int; + + // Legacy implementation helpers removed; modern overrides are canonical. + + public: + /// \brief Construct a large transposition table instance. + /// + /// Initializes the large TT with default memory limits. Must call + /// make_tt() to actually allocate memory. + TransTableL(); + + /// \brief Destroy the large transposition table. + /// + /// Releases all allocated memory and internal structures. + ~TransTableL(); + + /// \brief Initialize the transposition table with hand lookup tables. + /// + /// Sets up the TT with hand lookup configuration for position hashing. + /// + /// \param hand_lookup Hand lookup table array of size [DDS_SUITS][15] + /// (i.e., parameter type const int hand_lookup[DDS_SUITS][15], 4 suits by 15 ranks). + /// \throws std::bad_alloc if initialization fails + /// \par Usage Example + /// \code + /// unsigned short ag[DDS_HANDS] = { 0x1fff, 0x1fff, 0x0f75, 0x1fff }; + /// int hd[DDS_HANDS] = { 0x0342, 0x0334, 0x0232, 0x0531 }; + /// bool lower_flag = false; + /// int wr = 0; + /// int result = 0; + /// thrp->transTable.lookup(11, 1, ag, hd, 13, lower_flag); + /// thrp->transTable.add(11, 1, ag, wr, result, false); + /// \endcode + auto init(const int hand_lookup[][15]) -> void override; + + /// \brief Set the default (soft) memory limit. + /// + /// \param megabytes Desired soft memory limit in MB + auto set_memory_default(int megabytes) -> void override; + + /// \brief Set the maximum (hard) memory limit. + /// + /// \param megabytes Maximum allowed memory in MB + auto set_memory_maximum(int megabytes) -> void override; + + /// \brief Allocate transposition table memory structures. + /// + /// \throws std::bad_alloc if memory allocation fails + auto make_tt() -> void override; + + /// \brief Clear cached entries and reset statistics. + /// + /// \param reason Why the reset is occurring + auto reset_memory(ResetReason reason) -> void override; + + /// \brief Deallocate all transposition table memory. + /// + /// After calling this, must call make_tt() before further lookups. + auto return_all_memory() -> void override; + + /// \brief Return current memory usage in kilobytes. + /// + /// \return Memory in use (KB) + auto memory_in_use() const -> double override; + + /// \brief Lookup a cached position result. + /// + /// Searches for previously cached analysis of the given position. + /// Returns nullptr if not found. + /// + /// \param trick Current trick (0-12) + /// \param hand Hand to play (0-3) + /// \param aggr_target Target tricks per suit + /// \param hand_dist Card distribution + /// \param limit Early termination threshold + /// \param[out] lower_flag Set to true if result is a lower bound + /// \return Cached result or nullptr + /// \note Lookup updates the timestamp for harvesting considerations + auto lookup( + int trick, + int hand, + const unsigned short aggr_target[], + const int hand_dist[], + int limit, + bool& lower_flag) -> NodeCards const * override; + + /// \brief Add a computed result to the transposition table. + /// + /// Caches a newly computed search result for later lookup. May trigger + /// harvesting if memory limits are approached. + /// + /// \param trick Current trick (0-12) + /// \param hand Hand to play (0-3) + /// \param aggr_target Target tricks per suit + /// \param win_ranks_arg Winning ranks (optimization data) + /// \param first Computed result to cache + /// \param flag True if this is a lower bound (incomplete search) + /// \throws std::bad_alloc if critical memory allocation fails + /// \note May trigger harvest if soft memory limit exceeded + auto add( + int trick, + int hand, + const unsigned short aggr_target[], + const unsigned short win_ranks_arg[], + const NodeCards& first, + bool flag) -> void override; + + /// \brief Print cached results for a specific suit and position. + /// + /// Outputs detailed analysis of cached entries for the given trick/hand. + /// Large TT provides detailed suit-level statistics. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + auto print_suits( + std::ofstream& fout, + int trick, + int hand) const -> void override; + + /// \brief Print all suit results in the transposition table. + /// + /// \param fout Output stream + auto print_all_suits(std::ofstream& fout) const -> void override; + + /// \brief Print suit statistics for a specific position. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + auto print_suit_stats( + std::ofstream& fout, + int trick, + int hand) const -> void override; + + /// \brief Print suit statistics for all positions. + /// + /// \param fout Output stream + auto print_all_suit_stats(std::ofstream& fout) const -> void override; + + /// \brief Reset per-solve operation counters (adds, overwrites, harvests). + auto reset_op_stats() -> void override + { + page_stats_.num_adds_ = 0; + page_stats_.num_overwrites_ = 0; + page_stats_.num_harvests_ = 0; + } + /// \brief Get per-solve operation counters for instrumentation. + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override + { + adds = page_stats_.num_adds_; + overwrites = page_stats_.num_overwrites_; + harvests = page_stats_.num_harvests_; + } + /// \brief Print summary suit statistics. + /// + /// \param fout Output stream + auto print_summary_suit_stats(std::ofstream& fout) const -> void override; + + /// \brief Print entries for a specific hand distribution. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + /// \param hand_dist Card distribution array + auto print_entries_dist( + std::ofstream& fout, + int trick, + int hand, + const int hand_dist[]) const -> void override; + + /// \brief Print entries and card details for a hand distribution. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + /// \param aggr_target Target tricks per suit + /// \param hand_dist Card distribution array + auto print_entries_dist_and_cards( + std::ofstream& fout, + int trick, + int hand, + const unsigned short aggr_target[], + const int hand_dist[]) const -> void override; + + /// \brief Print entries for a specific trick/hand. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + auto print_entries( + std::ofstream& fout, + int trick, + int hand) const -> void override; + + /// \brief Print all cached entries in the table. + /// + /// \param fout Output stream + auto print_all_entries(std::ofstream& fout) const -> void override; + + /// \brief Print entry statistics for a specific position. + /// + /// \param fout Output stream + /// \param trick Trick number (0-12) + /// \param hand Hand (0-3) + auto print_entry_stats( + std::ofstream& fout, + int trick, + int hand) const -> void override; + + /// \brief Print entry statistics for all positions. + /// + /// \param fout Output stream + auto print_all_entry_stats(std::ofstream& fout) const -> void override; + + /// \brief Print summary entry statistics. + /// + /// \param fout Output stream + auto print_summary_entry_stats(std::ofstream& fout) const -> void override; }; \ No newline at end of file diff --git a/library/src/trans_table/trans_table_p.cpp b/library/src/trans_table/trans_table_p.cpp index 2c99aaa04..af34fab32 100644 --- a/library/src/trans_table/trans_table_p.cpp +++ b/library/src/trans_table/trans_table_p.cpp @@ -767,13 +767,13 @@ auto TransTableP::print_entries_dist_and_cards( } ++matched; lines << " [" << static_cast(stored.cards.lower_bound) << ", " - << static_cast(stored.cards.upper_bound) << "] least_win"; + << static_cast(stored.cards.upper_bound) << "] least_win"; for (int s = 0; s < DDS_SUITS; ++s) { lines << ' ' << static_cast(stored.cards.least_win[s]); } lines << ' ' << owners_of(stored.key) << " best move " - << static_cast(stored.cards.best_move_suit) << '/' - << static_cast(stored.cards.best_move_rank) << '\n'; + << static_cast(stored.cards.best_move_suit) << '/' + << static_cast(stored.cards.best_move_rank) << '\n'; } fout << "Trick " << trick << " hand " << hand << ": " << total << " patterns, " << matched << " match the cards\n" << lines.str(); diff --git a/library/src/trans_table/trans_table_p.hpp b/library/src/trans_table/trans_table_p.hpp index 9f9548a31..a8d5e6072 100644 --- a/library/src/trans_table/trans_table_p.hpp +++ b/library/src/trans_table/trans_table_p.hpp @@ -56,7 +56,7 @@ /// Not thread-safe. Must be accessed from a single thread. class TransTableP : public TransTable { - public: + public: TransTableP(); ~TransTableP() override; @@ -94,14 +94,14 @@ class TransTableP : public TransTable auto print_all_suit_stats(std::ofstream& fout) const -> void override; auto reset_op_stats() -> void override { - num_adds_ = 0; + num_adds_ = 0; } auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { - adds = num_adds_; - overwrites = 0; // PatternTT has no eviction; use tightens() for in-place updates - harvests = 0; // TransTableP has no harvest mechanism + adds = num_adds_; + overwrites = 0; // PatternTT has no eviction; use tightens() for in-place updates + harvests = 0; // TransTableP has no harvest mechanism } // Instrumentation counters mutable int num_adds_ = 0; @@ -128,7 +128,7 @@ class TransTableP : public TransTable /// \brief Number of distinct (trick, hand, shape) keys with stored patterns. auto shape_count() const -> std::size_t; - private: + private: /// Relative-rank ownership uses 2 bits per card and 4 cards per suit per /// word; three words cover the top twelve cards of every suit. The /// thirteenth card is implied by the shape and the other twelve. @@ -205,10 +205,10 @@ class TransTableP : public TransTable auto insert(std::size_t at, const PatternNode& node) -> void; }; static_assert(sizeof(PatternTree) % CacheLine == 0 && - sizeof(PatternTree) == TreeHeaderBytes + TreePaddingBytes, - "PatternTree header must fill whole cache lines with no implicit padding"); + sizeof(PatternTree) == TreeHeaderBytes + TreePaddingBytes, + "PatternTree header must fill whole cache lines with no implicit padding"); static_assert(sizeof(PatternNode) == 32 && CacheLine % sizeof(PatternNode) == 0, - "two PatternNodes must fit exactly in a cache line"); + "two PatternNodes must fit exactly in a cache line"); struct ShapeSlot { diff --git a/library/src/trans_table/trans_table_s.cpp b/library/src/trans_table/trans_table_s.cpp index 28c604f2a..8b45e14ed 100644 --- a/library/src/trans_table/trans_table_s.cpp +++ b/library/src/trans_table/trans_table_s.cpp @@ -28,28 +28,28 @@ namespace { auto checked_calloc(const size_t count, const size_t size) -> void* { - if (void* ptr = std::calloc(count, size)) - return ptr; - throw std::bad_alloc(); + if (void* ptr = std::calloc(count, size)) + return ptr; + throw std::bad_alloc(); } } // Accessor for a lazily initialized, immutable TTlowestRank table. static const std::array& tt_lowest_rank_table() { - static const std::array table = []{ - std::array t{}; - unsigned int top_bit_rank = 1; - t[0] = 15; // Void - for (unsigned ind = 1; ind < 8192; ind++) - { - if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ - top_bit_rank <<= 1; - t[ind] = t[ind ^ top_bit_rank] - 1; - } - return t; - }(); - return table; + static const std::array table = []{ + std::array t{}; + unsigned int top_bit_rank = 1; + t[0] = 15; // Void + for (unsigned ind = 1; ind < 8192; ind++) + { + if (ind >= (top_bit_rank + top_bit_rank)) /* Next top bit */ + top_bit_rank <<= 1; + t[ind] = t[ind ^ top_bit_rank] - 1; + } + return t; + }(); + return table; } // Local using-declarations for readability in this implementation file only. @@ -70,9 +70,9 @@ using std::string; */ TransTableS::TransTableS() { - // Ensure the table is built once. - (void)tt_lowest_rank_table(); - tt_in_use_ = 0; + // Ensure the table is built once. + (void)tt_lowest_rank_table(); + tt_in_use_ = 0; } @@ -83,7 +83,7 @@ TransTableS::TransTableS() */ TransTableS::~TransTableS() { - TransTableS::return_all_memory(); + TransTableS::return_all_memory(); } @@ -92,292 +92,292 @@ TransTableS::~TransTableS() auto TransTableS::init(const int hand_lookup[][15]) -> void { - unsigned int top_bit_rank = 1; - unsigned int top_bit_no = 2; - - for (int s = 0; s < DDS_SUITS; s++) - { - aggp_[0].aggr_ranks_[s] = 0; - aggp_[0].win_mask_[s] = 0; - } - - for (unsigned int ind = 1; ind < 8192; ind++) - { - if (ind >= (top_bit_rank + top_bit_rank)) + unsigned int top_bit_rank = 1; + unsigned int top_bit_no = 2; + + for (int s = 0; s < DDS_SUITS; s++) { - /* Next top bit */ - top_bit_rank <<= 1; - top_bit_no++; + aggp_[0].aggr_ranks_[s] = 0; + aggp_[0].win_mask_[s] = 0; } - aggp_[ind] = aggp_[ind ^ top_bit_rank]; - for (int s = 0; s < 4; s++) + for (unsigned int ind = 1; ind < 8192; ind++) { - aggp_[ind].aggr_ranks_[s] = - (aggp_[ind].aggr_ranks_[s] >> 2) | - (hand_lookup[s][top_bit_no] << 24); + if (ind >= (top_bit_rank + top_bit_rank)) + { + /* Next top bit */ + top_bit_rank <<= 1; + top_bit_no++; + } + aggp_[ind] = aggp_[ind ^ top_bit_rank]; + + for (int s = 0; s < 4; s++) + { + aggp_[ind].aggr_ranks_[s] = + (aggp_[ind].aggr_ranks_[s] >> 2) | + (hand_lookup[s][top_bit_no] << 24); - aggp_[ind].win_mask_[s] = - (aggp_[ind].win_mask_[s] >> 2) | (3 << 24); + aggp_[ind].win_mask_[s] = + (aggp_[ind].win_mask_[s] >> 2) | (3 << 24); + } } - } - reset_text_.resize(ResetReasonCount); - reset_text_[static_cast(ResetReason::Unknown)] = "Unknown reason"; - reset_text_[static_cast(ResetReason::TooManyNodes)] = "Too many nodes"; - reset_text_[static_cast(ResetReason::NewDeal)] = "New Deal"; - reset_text_[static_cast(ResetReason::NewTrump)] = "New trump"; - reset_text_[static_cast(ResetReason::MemoryExhausted)] = "Memory exhausted"; - reset_text_[static_cast(ResetReason::FreeMemory)] = "Free thread memory"; + reset_text_.resize(ResetReasonCount); + reset_text_[static_cast(ResetReason::Unknown)] = "Unknown reason"; + reset_text_[static_cast(ResetReason::TooManyNodes)] = "Too many nodes"; + reset_text_[static_cast(ResetReason::NewDeal)] = "New Deal"; + reset_text_[static_cast(ResetReason::NewTrump)] = "New trump"; + reset_text_[static_cast(ResetReason::MemoryExhausted)] = "Memory exhausted"; + reset_text_[static_cast(ResetReason::FreeMemory)] = "Free thread memory"; } auto TransTableS::set_memory_default( - [[maybe_unused]] const int megabytes) -> void + [[maybe_unused]] const int megabytes) -> void { } auto TransTableS::set_memory_maximum(const int megabytes) -> void { - maxmem_ = 1000000ULL * static_cast(megabytes); + maxmem_ = 1000000ULL * static_cast(megabytes); } auto TransTableS::make_tt() -> void { - // Note: keep local variables minimal; indices are declared in inner scopes. - - if (!tt_in_use_) - { - // Calculate memory requirements before any allocation - summem_ = (1ULL * (WINIT + 1) * sizeof(WinCard)) + - (1ULL * (NINIT + 1) * sizeof(NodeCards)) + - (1ULL * (LSIZE + 1) * 52 * sizeof(PosSearchSmall)); - wmem_ = static_cast(1ULL * (WSIZE + 1) * sizeof(WinCard)); - nmem_ = static_cast(1ULL * (NSIZE + 1) * sizeof(NodeCards)); - - // Compute how many additional slabs we could potentially allocate. - // Guard against negative values if maxmem_ < summem_ (which can happen - // with very small configured limits). - if (maxmem_ <= summem_) - max_index_ = 0; - else - { - const unsigned long long denom = - static_cast(1ULL * (WSIZE + 1) * sizeof(WinCard)); - max_index_ = static_cast((maxmem_ - summem_) / denom); - if (max_index_ < 0) - max_index_ = 0; - } + // Note: keep local variables minimal; indices are declared in inner scopes. - // Optional debug to aid troubleshooting when tuning memory limits. - if (const char* dbg = std::getenv("DDS_DEBUG_TT_CREATE")) + if (!tt_in_use_) { - if (*dbg) - { - std::cerr << "[DDS] TT(S) init: maxmem_=" << maxmem_ - << " summem_=" << summem_ - << " wmem_=" << wmem_ - << " nmem_=" << nmem_ - << " max_index_=" << max_index_ - << std::endl; - } - } - - // Allocate to temporaries for exception safety - WinCard** temp_pw = nullptr; - NodeCards** temp_pn = nullptr; - PosSearchSmall** temp_pl[14][DDS_HANDS] = {}; - TtAggr* temp_aggp = nullptr; - - try { - temp_pw = static_cast(checked_calloc( - static_cast(max_index_ + 1), - sizeof(WinCard *))); - - temp_pn = static_cast(checked_calloc( - static_cast(max_index_ + 1), - sizeof(NodeCards *))); - - for (int k = 1; k <= 13; k++) - for (int h = 0; h < DDS_HANDS; h++) + // Calculate memory requirements before any allocation + summem_ = (1ULL * (WINIT + 1) * sizeof(WinCard)) + + (1ULL * (NINIT + 1) * sizeof(NodeCards)) + + (1ULL * (LSIZE + 1) * 52 * sizeof(PosSearchSmall)); + wmem_ = static_cast(1ULL * (WSIZE + 1) * sizeof(WinCard)); + nmem_ = static_cast(1ULL * (NSIZE + 1) * sizeof(NodeCards)); + + // Compute how many additional slabs we could potentially allocate. + // Guard against negative values if maxmem_ < summem_ (which can happen + // with very small configured limits). + if (maxmem_ <= summem_) + max_index_ = 0; + else { - temp_pl[k][h] = static_cast(checked_calloc( - static_cast(max_index_ + 1), - sizeof(PosSearchSmall *))); + const unsigned long long denom = + static_cast(1ULL * (WSIZE + 1) * sizeof(WinCard)); + max_index_ = static_cast((maxmem_ - summem_) / denom); + if (max_index_ < 0) + max_index_ = 0; } - temp_pw[0] = static_cast( - checked_calloc(WINIT + 1, sizeof(WinCard))); - - temp_pn[0] = static_cast( - checked_calloc(NINIT + 1, sizeof(NodeCards))); - - for (int k = 1; k <= 13; k++) - for (int h = 0; h < DDS_HANDS; h++) + // Optional debug to aid troubleshooting when tuning memory limits. + if (const char* dbg = std::getenv("DDS_DEBUG_TT_CREATE")) { - temp_pl[k][h][0] = static_cast(checked_calloc( - (LSIZE + 1), - sizeof(PosSearchSmall))); + if (*dbg) + { + std::cerr << "[DDS] TT(S) init: maxmem_=" << maxmem_ + << " summem_=" << summem_ + << " wmem_=" << wmem_ + << " nmem_=" << nmem_ + << " max_index_=" << max_index_ + << std::endl; + } } - temp_aggp = static_cast(checked_calloc(8192, sizeof(TtAggr))); - - } catch (...) { - // Clean up any allocations on exception - if (temp_pw) { - if (temp_pw[0]) free(temp_pw[0]); - free(temp_pw); - } - if (temp_pn) { - if (temp_pn[0]) free(temp_pn[0]); - free(temp_pn); - } - for (int k = 1; k <= 13; k++) - for (int h = 0; h < DDS_HANDS; h++) - if (temp_pl[k][h]) { - if (temp_pl[k][h][0]) free(temp_pl[k][h][0]); - free(temp_pl[k][h]); - } - if (temp_aggp) free(temp_aggp); - throw; - } + // Allocate to temporaries for exception safety + WinCard** temp_pw = nullptr; + NodeCards** temp_pn = nullptr; + PosSearchSmall** temp_pl[14][DDS_HANDS] = {}; + TtAggr* temp_aggp = nullptr; + + try { + temp_pw = static_cast(checked_calloc( + static_cast(max_index_ + 1), + sizeof(WinCard *))); + + temp_pn = static_cast(checked_calloc( + static_cast(max_index_ + 1), + sizeof(NodeCards *))); + + for (int k = 1; k <= 13; k++) + for (int h = 0; h < DDS_HANDS; h++) + { + temp_pl[k][h] = static_cast(checked_calloc( + static_cast(max_index_ + 1), + sizeof(PosSearchSmall *))); + } + + temp_pw[0] = static_cast( + checked_calloc(WINIT + 1, sizeof(WinCard))); + + temp_pn[0] = static_cast( + checked_calloc(NINIT + 1, sizeof(NodeCards))); + + for (int k = 1; k <= 13; k++) + for (int h = 0; h < DDS_HANDS; h++) + { + temp_pl[k][h][0] = static_cast(checked_calloc( + (LSIZE + 1), + sizeof(PosSearchSmall))); + } + + temp_aggp = static_cast(checked_calloc(8192, sizeof(TtAggr))); + + } catch (...) { + // Clean up any allocations on exception + if (temp_pw) { + if (temp_pw[0]) free(temp_pw[0]); + free(temp_pw); + } + if (temp_pn) { + if (temp_pn[0]) free(temp_pn[0]); + free(temp_pn); + } + for (int k = 1; k <= 13; k++) + for (int h = 0; h < DDS_HANDS; h++) + if (temp_pl[k][h]) { + if (temp_pl[k][h][0]) free(temp_pl[k][h][0]); + free(temp_pl[k][h]); + } + if (temp_aggp) free(temp_aggp); + throw; + } - // All allocations succeeded; assign to members - pw_ = temp_pw; - pn_ = temp_pn; - for (int k = 1; k <= 13; k++) - for (int h = 0; h < DDS_HANDS; h++) - pl_[k][h] = temp_pl[k][h]; - aggp_ = temp_aggp; + // All allocations succeeded; assign to members + pw_ = temp_pw; + pn_ = temp_pn; + for (int k = 1; k <= 13; k++) + for (int h = 0; h < DDS_HANDS; h++) + pl_[k][h] = temp_pl[k][h]; + aggp_ = temp_aggp; - // Now mark as in-use after all allocations succeeded - tt_in_use_ = 1; + // Now mark as in-use after all allocations succeeded + tt_in_use_ = 1; - init_tt(); + init_tt(); - for (int k = 1; k <= 13; k++) - aggr_len_sets_[k] = 0; - stats_resets_.no_of_resets_ = 0; - for (int k = 0; k < ResetReasonCount; k++) - stats_resets_.aggr_resets_[k] = 0; + for (int k = 1; k <= 13; k++) + aggr_len_sets_[k] = 0; + stats_resets_.no_of_resets_ = 0; + for (int k = 0; k < ResetReasonCount; k++) + stats_resets_.aggr_resets_[k] = 0; - } + } } auto TransTableS::wipe() -> void { - int m; - - for (m = 1; m <= wcount_; m++) - { - if (pw_[m]) - free(pw_[m]); - pw_[m] = nullptr; - } - for (m = 1; m <= ncount_; m++) - { - if (pn_[m]) - free(pn_[m]); - pn_[m] = nullptr; - } - - for (int k = 1; k <= 13; k++) - { - for (int h = 0; h < DDS_HANDS; h++) + int m; + + for (m = 1; m <= wcount_; m++) { - for (m = 1; m <= lcount_[k][h]; m++) - { - if (pl_[k][h][m]) - free(pl_[k][h][m]); - pl_[k][h][m] = nullptr; - } + if (pw_[m]) + free(pw_[m]); + pw_[m] = nullptr; + } + for (m = 1; m <= ncount_; m++) + { + if (pn_[m]) + free(pn_[m]); + pn_[m] = nullptr; } - } - allocmem_ = summem_; + for (int k = 1; k <= 13; k++) + { + for (int h = 0; h < DDS_HANDS; h++) + { + for (m = 1; m <= lcount_[k][h]; m++) + { + if (pl_[k][h][m]) + free(pl_[k][h][m]); + pl_[k][h][m] = nullptr; + } + } + } + + allocmem_ = summem_; } auto TransTableS::init_tt() -> void { - win_set_size_limit_ = WINIT; - node_set_size_limit_ = NINIT; - allocmem_ = (WINIT + 1) * sizeof(WinCard); - allocmem_ += 1ULL * (NINIT + 1) * sizeof(NodeCards); - allocmem_ += 1ULL * (LSIZE + 1) * 52 * sizeof(PosSearchSmall); - win_cards_ = pw_[0]; - node_cards_ = pn_[0]; - wcount_ = 0; - ncount_ = 0; - - node_set_size_ = 0; - win_set_size_ = 0; - - clear_tt_flag_ = false; - windex_ = -1; - - for (int k = 1; k <= 13; k++) - for (int h = 0; h < DDS_HANDS; h++) - { - pos_search_[k][h] = pl_[k][h][0]; - // Set len_set_ind_ to 1 (not 0) because index 0 is reserved for the root node. - // This ensures that the first Lookup/Add can safely use index 1, - // and avoids overwriting the valid empty node at index 0. - len_set_ind_[k][h] = 1; - lcount_[k][h] = 0; - // Initialize the root node to a valid empty node so that a - // first Lookup/Add can function even before ResetMemory. - pos_search_[k][h][0].suit_lengths_ = 0; - pos_search_[k][h][0].pos_search_point_ = nullptr; - pos_search_[k][h][0].left_ = nullptr; - pos_search_[k][h][0].right_ = nullptr; - rootnp_[k][h] = &(pos_search_[k][h][0]); - } + win_set_size_limit_ = WINIT; + node_set_size_limit_ = NINIT; + allocmem_ = (WINIT + 1) * sizeof(WinCard); + allocmem_ += 1ULL * (NINIT + 1) * sizeof(NodeCards); + allocmem_ += 1ULL * (LSIZE + 1) * 52 * sizeof(PosSearchSmall); + win_cards_ = pw_[0]; + node_cards_ = pn_[0]; + wcount_ = 0; + ncount_ = 0; + + node_set_size_ = 0; + win_set_size_ = 0; + + clear_tt_flag_ = false; + windex_ = -1; + + for (int k = 1; k <= 13; k++) + for (int h = 0; h < DDS_HANDS; h++) + { + pos_search_[k][h] = pl_[k][h][0]; + // Set len_set_ind_ to 1 (not 0) because index 0 is reserved for the root node. + // This ensures that the first Lookup/Add can safely use index 1, + // and avoids overwriting the valid empty node at index 0. + len_set_ind_[k][h] = 1; + lcount_[k][h] = 0; + // Initialize the root node to a valid empty node so that a + // first Lookup/Add can function even before ResetMemory. + pos_search_[k][h][0].suit_lengths_ = 0; + pos_search_[k][h][0].pos_search_point_ = nullptr; + pos_search_[k][h][0].left_ = nullptr; + pos_search_[k][h][0].right_ = nullptr; + rootnp_[k][h] = &(pos_search_[k][h][0]); + } } auto TransTableS::reset_memory( - [[maybe_unused]] const ResetReason reason) -> void + [[maybe_unused]] const ResetReason reason) -> void { - // Nothing to reset when the pools have been returned: return_all_memory() - // frees pw_/pn_/pl_ and clears tt_in_use_, and make_tt() reallocates lazily - // before the next lookup. Without this guard init_tt() below dereferences - // the freed pools (pw_[0]) and segfaults. TransTableL's reset_memory() - // already guards the equivalent case with `pool_ == nullptr`. - // - // Defensive: SolverContext::clear_tt() disposes the TT instance rather than - // returning its memory, so no public-API sequence reaches this today. It is - // covered directly by TransTableSMemoryTest.ResetAfterReturnAllMemoryIsInert. - if (!tt_in_use_) - return; + // Nothing to reset when the pools have been returned: return_all_memory() + // frees pw_/pn_/pl_ and clears tt_in_use_, and make_tt() reallocates lazily + // before the next lookup. Without this guard init_tt() below dereferences + // the freed pools (pw_[0]) and segfaults. TransTableL's reset_memory() + // already guards the equivalent case with `pool_ == nullptr`. + // + // Defensive: SolverContext::clear_tt() disposes the TT instance rather than + // returning its memory, so no public-API sequence reaches this today. It is + // covered directly by TransTableSMemoryTest.ResetAfterReturnAllMemoryIsInert. + if (!tt_in_use_) + return; + + wipe(); - wipe(); - - init_tt(); + init_tt(); - for (int k = 1; k <= 13; k++) - { - for (int h = 0; h < DDS_HANDS; h++) + for (int k = 1; k <= 13; k++) { - rootnp_[k][h] = &(pos_search_[k][h][0]); - pos_search_[k][h][0].suit_lengths_ = 0; - pos_search_[k][h][0].pos_search_point_ = nullptr; - pos_search_[k][h][0].left_ = nullptr; - pos_search_[k][h][0].right_ = nullptr; + for (int h = 0; h < DDS_HANDS; h++) + { + rootnp_[k][h] = &(pos_search_[k][h][0]); + pos_search_[k][h][0].suit_lengths_ = 0; + pos_search_[k][h][0].pos_search_point_ = nullptr; + pos_search_[k][h][0].left_ = nullptr; + pos_search_[k][h][0].right_ = nullptr; - len_set_ind_[k][h] = 1; + len_set_ind_[k][h] = 1; + } } - } #if defined(DDS_TT_STATS) - stats_resets_.no_of_resets_++; - stats_resets_.aggr_resets_[static_cast(reason)]++; + stats_resets_.no_of_resets_++; + stats_resets_.aggr_resets_[static_cast(reason)]++; #endif } @@ -385,652 +385,652 @@ auto TransTableS::reset_memory( auto TransTableS::return_all_memory() -> void { - if (!tt_in_use_) - return; - tt_in_use_ = 0; + if (!tt_in_use_) + return; + tt_in_use_ = 0; - wipe(); + wipe(); - if (pw_[0]) - free(static_cast(pw_[0])); - pw_[0] = nullptr; + if (pw_[0]) + free(static_cast(pw_[0])); + pw_[0] = nullptr; - if (pn_[0]) - free(static_cast(pn_[0])); - pn_[0] = nullptr; + if (pn_[0]) + free(static_cast(pn_[0])); + pn_[0] = nullptr; - for (int k = 1; k <= 13; k++) - { - for (int h = 0; h < DDS_HANDS; h++) + for (int k = 1; k <= 13; k++) { - if (pl_[k][h]) - { - if (pl_[k][h][0]) - free(static_cast(pl_[k][h][0])); - pl_[k][h][0] = nullptr; - free(static_cast(pl_[k][h])); - pl_[k][h] = nullptr; - } + for (int h = 0; h < DDS_HANDS; h++) + { + if (pl_[k][h]) + { + if (pl_[k][h][0]) + free(static_cast(pl_[k][h][0])); + pl_[k][h][0] = nullptr; + free(static_cast(pl_[k][h])); + pl_[k][h] = nullptr; + } + } } - } - if (pw_) - free(static_cast(pw_)); - pw_ = nullptr; + if (pw_) + free(static_cast(pw_)); + pw_ = nullptr; - if (pn_) - free(static_cast(pn_)); - pn_ = nullptr; + if (pn_) + free(static_cast(pn_)); + pn_ = nullptr; - if (aggp_) - free(aggp_); - aggp_ = nullptr; + if (aggp_) + free(aggp_); + aggp_ = nullptr; - return; + return; } auto TransTableS::memory_in_use() const -> double { - int ttMem = static_cast(allocmem_); - int aggrMem = 8192 * static_cast(sizeof(TtAggr)); - return (ttMem + aggrMem) / static_cast(1024.); + int ttMem = static_cast(allocmem_); + int aggrMem = 8192 * static_cast(sizeof(TtAggr)); + return (ttMem + aggrMem) / static_cast(1024.); } auto TransTableS::lookup( - const int trick, - const int hand, - const unsigned short aggr_target[], - const int hand_dist[], - const int limit, - bool& lower_flag) -> NodeCards const * + const int trick, + const int hand, + const unsigned short aggr_target[], + const int hand_dist[], + const int limit, + bool& lower_flag) -> NodeCards const * { - bool res; - PosSearchSmall * pp; - int order_set_[DDS_SUITS]; - NodeCards const * cardsP; - - suit_lengths_[trick] = - (static_cast(hand_dist[0]) << 36) | - (static_cast(hand_dist[1]) << 24) | - (static_cast(hand_dist[2]) << 12) | - (static_cast(hand_dist[3])); - - pp = search_len_and_insert( - rootnp_[trick][hand], - suit_lengths_[trick], - false, - trick, - hand, - res); - - /* Find node that fits the suit lengths */ - if ((pp != nullptr) && res) - { - for (int ss = 0; ss < DDS_SUITS; ss++) + bool res; + PosSearchSmall * pp; + int order_set_[DDS_SUITS]; + NodeCards const * cardsP; + + suit_lengths_[trick] = + (static_cast(hand_dist[0]) << 36) | + (static_cast(hand_dist[1]) << 24) | + (static_cast(hand_dist[2]) << 12) | + (static_cast(hand_dist[3])); + + pp = search_len_and_insert( + rootnp_[trick][hand], + suit_lengths_[trick], + false, + trick, + hand, + res); + + /* Find node that fits the suit lengths */ + if ((pp != nullptr) && res) { - order_set_[ss] = - aggp_[aggr_target[ss]].aggr_ranks_[ss]; - } + for (int ss = 0; ss < DDS_SUITS; ss++) + { + order_set_[ss] = + aggp_[aggr_target[ss]].aggr_ranks_[ss]; + } - if (pp->pos_search_point_ == nullptr) - cardsP = nullptr; + if (pp->pos_search_point_ == nullptr) + cardsP = nullptr; + else + { + cardsP = find_sop(order_set_, limit, pp->pos_search_point_, lower_flag); + + if (cardsP == nullptr) + return cardsP; + } + } else { - cardsP = find_sop(order_set_, limit, pp->pos_search_point_, lower_flag); - - if (cardsP == nullptr) - return cardsP; + cardsP = nullptr; } - } - else - { - cardsP = nullptr; - } - return cardsP; + return cardsP; } auto TransTableS::add( - const int tricks, - const int hand, - const unsigned short aggr_target[], - const unsigned short our_win_ranks[], - const NodeCards& first, - const bool flag) -> void + const int tricks, + const int hand, + const unsigned short aggr_target[], + const unsigned short our_win_ranks[], + const NodeCards& first, + const bool flag) -> void { - build_sop( - our_win_ranks, - aggr_target, - first, - suit_lengths_[tricks], - tricks, - hand, - flag); - - if (clear_tt_flag_) - reset_memory(ResetReason::MemoryExhausted); + build_sop( + our_win_ranks, + aggr_target, + first, + suit_lengths_[tricks], + tricks, + hand, + flag); + + if (clear_tt_flag_) + reset_memory(ResetReason::MemoryExhausted); } auto TransTableS::add_win_set() -> void { - if (clear_tt_flag_) - { - windex_++; - win_set_size_ = windex_; - win_cards_ = &(temp_win_[windex_]); - } - else if (win_set_size_ >= win_set_size_limit_) - { - /* The memory chunk for the win_cards_ structure will be exceeded. */ - if (((allocmem_ + static_cast(wmem_)) > maxmem_) || (wcount_ >= max_index_) || - (win_set_size_ > SIMILARMAXWINNODES)) + if (clear_tt_flag_) { - /* Already allocated memory plus needed allocation overshot maxmem_ */ - windex_++; - win_set_size_ = windex_; - clear_tt_flag_ = true; - win_cards_ = &(temp_win_[windex_]); - } - else - { - wcount_++; - win_set_size_limit_ = WSIZE; - pw_[wcount_] = - static_cast(malloc((WSIZE + 1) * sizeof(WinCard))); - if (pw_[wcount_] == nullptr) - { - clear_tt_flag_ = true; windex_++; win_set_size_ = windex_; win_cards_ = &(temp_win_[windex_]); - } - else - { - allocmem_ += (WSIZE + 1) * sizeof(WinCard); - win_set_size_ = 0; - win_cards_ = pw_[wcount_]; - } } - } - else - win_set_size_++; + else if (win_set_size_ >= win_set_size_limit_) + { + /* The memory chunk for the win_cards_ structure will be exceeded. */ + if (((allocmem_ + static_cast(wmem_)) > maxmem_) || (wcount_ >= max_index_) || + (win_set_size_ > SIMILARMAXWINNODES)) + { + /* Already allocated memory plus needed allocation overshot maxmem_ */ + windex_++; + win_set_size_ = windex_; + clear_tt_flag_ = true; + win_cards_ = &(temp_win_[windex_]); + } + else + { + wcount_++; + win_set_size_limit_ = WSIZE; + pw_[wcount_] = + static_cast(malloc((WSIZE + 1) * sizeof(WinCard))); + if (pw_[wcount_] == nullptr) + { + clear_tt_flag_ = true; + windex_++; + win_set_size_ = windex_; + win_cards_ = &(temp_win_[windex_]); + } + else + { + allocmem_ += (WSIZE + 1) * sizeof(WinCard); + win_set_size_ = 0; + win_cards_ = pw_[wcount_]; + } + } + } + else + win_set_size_++; } auto TransTableS::add_node_set() -> void { - if (node_set_size_ >= node_set_size_limit_) - { - /* The memory chunk for the node_cards_ structure will be exceeded. */ - if (((allocmem_ + static_cast(nmem_)) > maxmem_) || (ncount_ >= max_index_)) + if (node_set_size_ >= node_set_size_limit_) { - /* Already allocated memory plus needed allocation overshot maxmem_ */ - clear_tt_flag_ = true; + /* The memory chunk for the node_cards_ structure will be exceeded. */ + if (((allocmem_ + static_cast(nmem_)) > maxmem_) || (ncount_ >= max_index_)) + { + /* Already allocated memory plus needed allocation overshot maxmem_ */ + clear_tt_flag_ = true; + } + else + { + ncount_++; + node_set_size_limit_ = NSIZE; + pn_[ncount_] = + static_cast(malloc((NSIZE + 1) * sizeof(NodeCards))); + if (pn_[ncount_] == nullptr) + { + clear_tt_flag_ = true; + } + else + { + allocmem_ += (NSIZE + 1) * sizeof(NodeCards); + node_set_size_ = 0; + node_cards_ = pn_[ncount_]; + } + } } else - { - ncount_++; - node_set_size_limit_ = NSIZE; - pn_[ncount_] = - static_cast(malloc((NSIZE + 1) * sizeof(NodeCards))); - if (pn_[ncount_] == nullptr) - { - clear_tt_flag_ = true; - } - else - { - allocmem_ += (NSIZE + 1) * sizeof(NodeCards); - node_set_size_ = 0; - node_cards_ = pn_[ncount_]; - } - } - } - else - node_set_size_++; + node_set_size_++; } auto TransTableS::add_len_set( - const int trick, - const int first_hand) -> void + const int trick, + const int first_hand) -> void { - if (len_set_ind_[trick][first_hand] < LSIZE) { - len_set_ind_[trick][first_hand]++; + if (len_set_ind_[trick][first_hand] < LSIZE) { + len_set_ind_[trick][first_hand]++; #if defined(DDS_TT_STATS) - aggr_len_sets_[trick]++; + aggr_len_sets_[trick]++; #endif - return; - } + return; + } - // The memory chunk for the PosSearchSmall structure - // will be exceeded. + // The memory chunk for the PosSearchSmall structure + // will be exceeded. - const int incr = (LSIZE + 1) * sizeof(PosSearchSmall); + const int incr = (LSIZE + 1) * sizeof(PosSearchSmall); - if ((allocmem_ + incr > maxmem_) || - (lcount_[trick][first_hand] >= max_index_)) - { - // Already allocated memory plus needed allocation overshot maxmem_. - clear_tt_flag_ = true; - return; - } + if ((allocmem_ + incr > maxmem_) || + (lcount_[trick][first_hand] >= max_index_)) + { + // Already allocated memory plus needed allocation overshot maxmem_. + clear_tt_flag_ = true; + return; + } - // Obtain another memory chunk LSIZE. + // Obtain another memory chunk LSIZE. - lcount_[trick][first_hand]++; + lcount_[trick][first_hand]++; - pl_[trick][first_hand][lcount_[trick][first_hand]] = - static_cast(malloc(incr)); + pl_[trick][first_hand][lcount_[trick][first_hand]] = + static_cast(malloc(incr)); - if (pl_[trick][first_hand][lcount_[trick][first_hand]] == nullptr) - { - clear_tt_flag_ = true; - return; - } + if (pl_[trick][first_hand][lcount_[trick][first_hand]] == nullptr) + { + clear_tt_flag_ = true; + return; + } - allocmem_ += incr; - len_set_ind_[trick][first_hand] = 0; - pos_search_[trick][first_hand] = - pl_[trick][first_hand][lcount_[trick][first_hand]]; + allocmem_ += incr; + len_set_ind_[trick][first_hand] = 0; + pos_search_[trick][first_hand] = + pl_[trick][first_hand][lcount_[trick][first_hand]]; #if defined(DDS_TT_STATS) - aggr_len_sets_[trick]++; + aggr_len_sets_[trick]++; #endif } auto TransTableS::build_sop( - const unsigned short our_win_ranks[DDS_SUITS], - const unsigned short aggr_arg[DDS_SUITS], - const NodeCards& first, - const long long lengths, - const int tricks, - const int first_hand, - const bool flag) -> void + const unsigned short our_win_ranks[DDS_SUITS], + const unsigned short aggr_arg[DDS_SUITS], + const NodeCards& first, + const long long lengths, + const int tricks, + const int first_hand, + const bool flag) -> void { - int win_mask_[DDS_SUITS]; - int win_order_set[DDS_SUITS]; - char low[DDS_SUITS]; - - for (int ss = 0; ss < DDS_SUITS; ss++) - { - int w = our_win_ranks[ss]; - if (w == 0) - { - win_mask_[ss] = 0; - win_order_set[ss] = 0; - low[ss] = 15; - } - else - { - w = w & (-w); /* Only lowest win */ - const unsigned short temp = - static_cast(aggr_arg[ss] & (-w)); - - win_mask_[ss] = aggp_[temp].win_mask_[ss]; - win_order_set[ss] = aggp_[temp].aggr_ranks_[ss]; - low[ss] = static_cast( - tt_lowest_rank_table()[static_cast(temp)]); - } - } - - bool res; - PosSearchSmall * np = search_len_and_insert( - rootnp_[tricks][first_hand], lengths, true, tricks, first_hand, res); - - NodeCards * cardsP = build_path( - win_mask_, - win_order_set, - static_cast(first.upper_bound), - static_cast(first.lower_bound), - static_cast(first.best_move_suit), - static_cast(first.best_move_rank), - np, - res); - - if (res) - { - cardsP->upper_bound = static_cast(first.upper_bound); - cardsP->lower_bound = static_cast(first.lower_bound); - - if (flag) + int win_mask_[DDS_SUITS]; + int win_order_set[DDS_SUITS]; + char low[DDS_SUITS]; + + for (int ss = 0; ss < DDS_SUITS; ss++) { - cardsP->best_move_suit = static_cast(first.best_move_suit); - cardsP->best_move_rank = static_cast(first.best_move_rank); + int w = our_win_ranks[ss]; + if (w == 0) + { + win_mask_[ss] = 0; + win_order_set[ss] = 0; + low[ss] = 15; + } + else + { + w = w & (-w); /* Only lowest win */ + const unsigned short temp = + static_cast(aggr_arg[ss] & (-w)); + + win_mask_[ss] = aggp_[temp].win_mask_[ss]; + win_order_set[ss] = aggp_[temp].aggr_ranks_[ss]; + low[ss] = static_cast( + tt_lowest_rank_table()[static_cast(temp)]); + } } - else + + bool res; + PosSearchSmall * np = search_len_and_insert( + rootnp_[tricks][first_hand], lengths, true, tricks, first_hand, res); + + NodeCards * cardsP = build_path( + win_mask_, + win_order_set, + static_cast(first.upper_bound), + static_cast(first.lower_bound), + static_cast(first.best_move_suit), + static_cast(first.best_move_rank), + np, + res); + + if (res) { - cardsP->best_move_suit = 0; - cardsP->best_move_rank = 0; - } + cardsP->upper_bound = static_cast(first.upper_bound); + cardsP->lower_bound = static_cast(first.lower_bound); - for (int k = 0; k < DDS_SUITS; k++) - cardsP->least_win[k] = static_cast(15 - low[k]); - } + if (flag) + { + cardsP->best_move_suit = static_cast(first.best_move_suit); + cardsP->best_move_rank = static_cast(first.best_move_rank); + } + else + { + cardsP->best_move_suit = 0; + cardsP->best_move_rank = 0; + } + + for (int k = 0; k < DDS_SUITS; k++) + cardsP->least_win[k] = static_cast(15 - low[k]); + } } auto TransTableS::build_path( - const int win_mask_[], - const int win_order_set[], - const int u_bound, - const int l_bound, - const char best_move_suit, - const char best_move_rank, - PosSearchSmall * node_ptr, - bool& result) -> NodeCards * + const int win_mask_[], + const int win_order_set[], + const int u_bound, + const int l_bound, + const char best_move_suit, + const char best_move_rank, + PosSearchSmall * node_ptr, + bool& result) -> NodeCards * { - /* If result is TRUE, a new SOP has been created and build_path returns a - pointer to it. If result is FALSE, an existing SOP is used and build_path - returns a pointer to the SOP */ - - bool found; - WinCard * np, *p2, *nprev; - NodeCards *p; - - np = node_ptr->pos_search_point_; - nprev = nullptr; - int suit = 0; - - /* If winning node has a card that equals the next winning card deduced - from the position, then there already exists a (partial) path */ - - if (np == nullptr) - { - /* There is no winning list created yet */ - /* Create winning nodes */ - p2 = &(win_cards_[win_set_size_]); - add_win_set(); - p2->next_ = nullptr; - p2->next_win_ = nullptr; - p2->prev_win_ = nullptr; - node_ptr->pos_search_point_ = p2; - p2->win_mask_ = win_mask_[suit]; - p2->order_set_ = win_order_set[suit]; - p2->first_ = nullptr; - np = p2; /* Latest winning node */ - suit++; - while (suit < DDS_SUITS) + /* If result is TRUE, a new SOP has been created and build_path returns a + pointer to it. If result is FALSE, an existing SOP is used and build_path + returns a pointer to the SOP */ + + bool found; + WinCard * np, *p2, *nprev; + NodeCards *p; + + np = node_ptr->pos_search_point_; + nprev = nullptr; + int suit = 0; + + /* If winning node has a card that equals the next winning card deduced + from the position, then there already exists a (partial) path */ + + if (np == nullptr) { - p2 = &(win_cards_[win_set_size_]); - add_win_set(); - np->next_win_ = p2; - p2->prev_win_ = np; - p2->next_ = nullptr; - p2->next_win_ = nullptr; - p2->win_mask_ = win_mask_[suit]; - p2->order_set_ = win_order_set[suit]; - p2->first_ = nullptr; - np = p2; /* Latest winning node */ - suit++; + /* There is no winning list created yet */ + /* Create winning nodes */ + p2 = &(win_cards_[win_set_size_]); + add_win_set(); + p2->next_ = nullptr; + p2->next_win_ = nullptr; + p2->prev_win_ = nullptr; + node_ptr->pos_search_point_ = p2; + p2->win_mask_ = win_mask_[suit]; + p2->order_set_ = win_order_set[suit]; + p2->first_ = nullptr; + np = p2; /* Latest winning node */ + suit++; + while (suit < DDS_SUITS) + { + p2 = &(win_cards_[win_set_size_]); + add_win_set(); + np->next_win_ = p2; + p2->prev_win_ = np; + p2->next_ = nullptr; + p2->next_win_ = nullptr; + p2->win_mask_ = win_mask_[suit]; + p2->order_set_ = win_order_set[suit]; + p2->first_ = nullptr; + np = p2; /* Latest winning node */ + suit++; + } + p = &(node_cards_[node_set_size_]); + add_node_set(); + np->first_ = p; + result = true; + return p; } - p = &(node_cards_[node_set_size_]); - add_node_set(); - np->first_ = p; - result = true; - return p; - } - else - { - /* Winning list exists */ - while (1) + else { - /* Find all winning nodes that correspond to current position */ - found = false; - while (1) /* Find node among alternatives */ - { - if ((np->win_mask_ == win_mask_[suit]) && - (np->order_set_ == win_order_set[suit])) + /* Winning list exists */ + while (1) + { + /* Find all winning nodes that correspond to current position */ + found = false; + while (1) /* Find node among alternatives */ + { + if ((np->win_mask_ == win_mask_[suit]) && + (np->order_set_ == win_order_set[suit])) + { + /* Part of path found */ + found = true; + nprev = np; + break; + } + if (np->next_ != nullptr) + np = np->next_; + else + break; + } + if (found) + { + suit++; + if (suit >= DDS_SUITS) + { + result = false; + return update_sop(u_bound, l_bound, best_move_suit, best_move_rank, + np->first_); + } + else + { + np = np->next_win_; /* Find next winning node */ + continue; + } + } + else + break; /* Node was not found */ + } /* End outer while */ + + /* Create additional node, coupled to existing node(s) */ + p2 = &(win_cards_[win_set_size_]); + add_win_set(); + p2->prev_win_ = nprev; + if (nprev != nullptr) { - /* Part of path found */ - found = true; - nprev = np; - break; + p2->next_ = nprev->next_win_; + nprev->next_win_ = p2; } - if (np->next_ != nullptr) - np = np->next_; else - break; - } - if (found) - { - suit++; - if (suit >= DDS_SUITS) { - result = false; - return update_sop(u_bound, l_bound, best_move_suit, best_move_rank, - np->first_); + p2->next_ = node_ptr->pos_search_point_; + node_ptr->pos_search_point_ = p2; } - else + p2->next_win_ = nullptr; + p2->win_mask_ = win_mask_[suit]; + p2->order_set_ = win_order_set[suit]; + p2->first_ = nullptr; + np = p2; /* Latest winning node */ + suit++; + + /* Rest of path must be created */ + while (suit < 4) { - np = np->next_win_; /* Find next winning node */ - continue; + p2 = &(win_cards_[win_set_size_]); + add_win_set(); + np->next_win_ = p2; + p2->prev_win_ = np; + p2->next_ = nullptr; + p2->win_mask_ = win_mask_[suit]; + p2->order_set_ = win_order_set[suit]; + p2->first_ = nullptr; + p2->next_win_ = nullptr; + np = p2; /* Latest winning node */ + suit++; } - } - else - break; /* Node was not found */ - } /* End outer while */ - - /* Create additional node, coupled to existing node(s) */ - p2 = &(win_cards_[win_set_size_]); - add_win_set(); - p2->prev_win_ = nprev; - if (nprev != nullptr) - { - p2->next_ = nprev->next_win_; - nprev->next_win_ = p2; - } - else - { - p2->next_ = node_ptr->pos_search_point_; - node_ptr->pos_search_point_ = p2; - } - p2->next_win_ = nullptr; - p2->win_mask_ = win_mask_[suit]; - p2->order_set_ = win_order_set[suit]; - p2->first_ = nullptr; - np = p2; /* Latest winning node */ - suit++; - - /* Rest of path must be created */ - while (suit < 4) - { - p2 = &(win_cards_[win_set_size_]); - add_win_set(); - np->next_win_ = p2; - p2->prev_win_ = np; - p2->next_ = nullptr; - p2->win_mask_ = win_mask_[suit]; - p2->order_set_ = win_order_set[suit]; - p2->first_ = nullptr; - p2->next_win_ = nullptr; - np = p2; /* Latest winning node */ - suit++; - } - /* All winning nodes in SOP have been traversed and new nodes created */ - p = &(node_cards_[node_set_size_]); - add_node_set(); - np->first_ = p; - result = true; - return p; - } + /* All winning nodes in SOP have been traversed and new nodes created */ + p = &(node_cards_[node_set_size_]); + add_node_set(); + np->first_ = p; + result = true; + return p; + } } auto TransTableS::search_len_and_insert( - PosSearchSmall * root_ptr, - const long long key, - const bool insert_node, - const int trick, - const int first_hand, - bool& result) -> TransTableS::PosSearchSmall * + PosSearchSmall * root_ptr, + const long long key, + const bool insert_node, + const int trick, + const int first_hand, + bool& result) -> TransTableS::PosSearchSmall * { - /* Search for node which matches with the suit length combination - given by parameter key. If no such node is found, nullptr is - returned if parameter insert_node is FALSE, otherwise a new - node is inserted with suit_lengths_ set to key, the pointer to - this node is returned. - The algorithm used is defined in Knuth "The art of computer - programming", vol.3 "Sorting and searching", 6.2.2 Algorithm T, - page 424. */ - - PosSearchSmall * np, *p, *sp; - - sp = nullptr; - if (insert_node) - sp = &(pos_search_[trick][first_hand][len_set_ind_[trick][first_hand]]); - - np = root_ptr; - while (1) - { - if (key == np->suit_lengths_) - { - result = true; - return np; - } - else if (key < np->suit_lengths_) - { - if (np->left_ != nullptr) - np = np->left_; - else if (insert_node) - { - p = sp; - add_len_set(trick, first_hand); - np->left_ = p; - p->pos_search_point_ = nullptr; - p->suit_lengths_ = key; - p->left_ = nullptr; - p->right_ = nullptr; - result = true; - return p; - } - else - { - result = false; - return nullptr; - } - } - else /* key > suit_lengths_ */ + /* Search for node which matches with the suit length combination + given by parameter key. If no such node is found, nullptr is + returned if parameter insert_node is FALSE, otherwise a new + node is inserted with suit_lengths_ set to key, the pointer to + this node is returned. + The algorithm used is defined in Knuth "The art of computer + programming", vol.3 "Sorting and searching", 6.2.2 Algorithm T, + page 424. */ + + PosSearchSmall * np, *p, *sp; + + sp = nullptr; + if (insert_node) + sp = &(pos_search_[trick][first_hand][len_set_ind_[trick][first_hand]]); + + np = root_ptr; + while (1) { - if (np->right_ != nullptr) - np = np->right_; - else if (insert_node) - { - p = sp; - add_len_set(trick, first_hand); - np->right_ = p; - p->pos_search_point_ = nullptr; - p->suit_lengths_ = key; - p->left_ = nullptr; - p->right_ = nullptr; - result = true; - return p; - } - else - { - result = false; - return nullptr; - } + if (key == np->suit_lengths_) + { + result = true; + return np; + } + else if (key < np->suit_lengths_) + { + if (np->left_ != nullptr) + np = np->left_; + else if (insert_node) + { + p = sp; + add_len_set(trick, first_hand); + np->left_ = p; + p->pos_search_point_ = nullptr; + p->suit_lengths_ = key; + p->left_ = nullptr; + p->right_ = nullptr; + result = true; + return p; + } + else + { + result = false; + return nullptr; + } + } + else /* key > suit_lengths_ */ + { + if (np->right_ != nullptr) + np = np->right_; + else if (insert_node) + { + p = sp; + add_len_set(trick, first_hand); + np->right_ = p; + p->pos_search_point_ = nullptr; + p->suit_lengths_ = key; + p->left_ = nullptr; + p->right_ = nullptr; + result = true; + return p; + } + else + { + result = false; + return nullptr; + } + } } - } } auto TransTableS::update_sop( - int u_bound, - int l_bound, - char best_move_suit, - char best_move_rank, - NodeCards * node_ptr) -> NodeCards * + int u_bound, + int l_bound, + char best_move_suit, + char best_move_rank, + NodeCards * node_ptr) -> NodeCards * { - /* Update SOP node with new values for upper and lower - bounds. */ - if (l_bound > node_ptr->lower_bound) - node_ptr->lower_bound = static_cast(l_bound); - if (u_bound < node_ptr->upper_bound) - node_ptr->upper_bound = static_cast(u_bound); + /* Update SOP node with new values for upper and lower + bounds. */ + if (l_bound > node_ptr->lower_bound) + node_ptr->lower_bound = static_cast(l_bound); + if (u_bound < node_ptr->upper_bound) + node_ptr->upper_bound = static_cast(u_bound); - node_ptr->best_move_suit = best_move_suit; - node_ptr->best_move_rank = best_move_rank; + node_ptr->best_move_suit = best_move_suit; + node_ptr->best_move_rank = best_move_rank; - return node_ptr; + return node_ptr; } auto TransTableS::find_sop( - const int order_set_[], - const int limit, - WinCard * nodeP, - bool& lower_flag) -> NodeCards const * + const int order_set_[], + const int limit, + WinCard * nodeP, + bool& lower_flag) -> NodeCards const * { - WinCard * np; + WinCard * np; - np = nodeP; - int s = 0; + np = nodeP; + int s = 0; - while (np) - { - if ((np->win_mask_ & order_set_[s]) == np->order_set_) + while (np) { - /* Winning rank set fits position */ - if (s != 3) - { - np = np->next_win_; - s++; - continue; - } - - if (np->first_->lower_bound > limit) - { - lower_flag = true; - return np->first_; - } - else if (np->first_->upper_bound <= limit) - { - lower_flag = false; - return np->first_; - } - } + if ((np->win_mask_ & order_set_[s]) == np->order_set_) + { + /* Winning rank set fits position */ + if (s != 3) + { + np = np->next_win_; + s++; + continue; + } + + if (np->first_->lower_bound > limit) + { + lower_flag = true; + return np->first_; + } + else if (np->first_->upper_bound <= limit) + { + lower_flag = false; + return np->first_; + } + } - while (np->next_ == nullptr) - { - np = np->prev_win_; - s--; - if (np == nullptr) /* Previous node is header node? */ - return nullptr; + while (np->next_ == nullptr) + { + np = np->prev_win_; + s--; + if (np == nullptr) /* Previous node is header node? */ + return nullptr; + } + np = np->next_; } - np = np->next_; - } - return nullptr; + return nullptr; } auto TransTableS::print_node_stats_impl(ofstream& fout) const -> void { - fout << "Report of generated PosSearch nodes per trick level.\n"; - fout << "Trick level 13 is highest level with all 52 cards.\n"; - fout << string(51, '-') << "\n"; + fout << "Report of generated PosSearch nodes per trick level.\n"; + fout << "Trick level 13 is highest level with all 52 cards.\n"; + fout << string(51, '-') << "\n"; - fout << setw(5) << "Trick" << - setw(14) << right << "Created nodes" << "\n"; + fout << setw(5) << "Trick" << + setw(14) << right << "Created nodes" << "\n"; - for (int k = 13; k > 0; k--) - fout << setw(5) << k << setw(14) << aggr_len_sets_[k - 1] << "\n"; + for (int k = 13; k > 0; k--) + fout << setw(5) << k << setw(14) << aggr_len_sets_[k - 1] << "\n"; - fout << endl; + fout << endl; } auto TransTableS::print_reset_stats_impl(ofstream& fout) const -> void { - fout << "Total no. of resets: " << stats_resets_.no_of_resets_ << "\n" << endl; + fout << "Total no. of resets: " << stats_resets_.no_of_resets_ << "\n" << endl; - fout << setw(18) << left << "Reason" << - setw(6) << right << "Count" << "\n"; + fout << setw(18) << left << "Reason" << + setw(6) << right << "Count" << "\n"; - for (unsigned k = 0; k < ResetReasonCount; k++) - fout << setw(18) << left << reset_text_[k] << - setw(6) << right << stats_resets_.aggr_resets_[k] << "\n"; + for (unsigned k = 0; k < ResetReasonCount; k++) + fout << setw(18) << left << reset_text_[k] << + setw(6) << right << stats_resets_.aggr_resets_[k] << "\n"; } diff --git a/library/src/trans_table/trans_table_s.hpp b/library/src/trans_table/trans_table_s.hpp index 6560c225f..58123edd3 100644 --- a/library/src/trans_table/trans_table_s.hpp +++ b/library/src/trans_table/trans_table_s.hpp @@ -48,40 +48,40 @@ /// \see NodeCards for cached position data class TransTableS: public TransTable { - private: + private: /// \brief Card entry in the small TT with win mask information. struct WinCard { - int order_set_; ///< Bitmask of card orders - int win_mask_; ///< Bitmask of winning ranks - NodeCards * first_; ///< Pointer to cached result - WinCard * prev_win_; ///< Link in win set list - WinCard * next_win_; ///< Link in win set list - WinCard * next_; ///< Link in position search tree + int order_set_; ///< Bitmask of card orders + int win_mask_; ///< Bitmask of winning ranks + NodeCards * first_; ///< Pointer to cached result + WinCard * prev_win_; ///< Link in win set list + WinCard * next_win_; ///< Link in win set list + WinCard * next_; ///< Link in position search tree }; /// \brief Tree node for binary search on card distributions. struct PosSearchSmall { - WinCard * pos_search_point_; ///< Associated card entry - long long suit_lengths_; ///< Card length distribution key - PosSearchSmall * left_; ///< Left subtree - PosSearchSmall * right_; ///< Right subtree + WinCard * pos_search_point_; ///< Associated card entry + long long suit_lengths_; ///< Card length distribution key + PosSearchSmall * left_; ///< Left subtree + PosSearchSmall * right_; ///< Right subtree }; /// \brief Aggregated targets and win masks for a given hand. struct TtAggr { - int aggr_ranks_[DDS_SUITS]; ///< Target tricks per suit - int win_mask_[DDS_SUITS]; ///< Win mask per suit + int aggr_ranks_[DDS_SUITS]; ///< Target tricks per suit + int win_mask_[DDS_SUITS]; ///< Win mask per suit }; /// \brief Statistics about table resets. struct StatsResets { - int no_of_resets_; ///< Total number of resets - int aggr_resets_[ResetReasonCount]; ///< Reset counts by reason + int no_of_resets_; ///< Total number of resets + int aggr_resets_[ResetReasonCount]; ///< Reset counts by reason }; @@ -132,55 +132,55 @@ class TransTableS: public TransTable auto add_node_set() -> void; auto add_len_set( - int trick, - int first_hand) -> void; + int trick, + int first_hand) -> void; auto build_sop( - const unsigned short our_win_ranks[DDS_SUITS], - const unsigned short aggr_arg[DDS_SUITS], - const NodeCards& first, - long long lengths, - int tricks, - int first_hand, - bool flag + const unsigned short our_win_ranks[DDS_SUITS], + const unsigned short aggr_arg[DDS_SUITS], + const NodeCards& first, + long long lengths, + int tricks, + int first_hand, + bool flag ) -> void; auto build_path( - const int win_mask[], - const int win_order_set[], - int u_bound, - int l_bound, - char best_move_suit, - char best_move_rank, - PosSearchSmall * node_ptr, - bool& result + const int win_mask[], + const int win_order_set[], + int u_bound, + int l_bound, + char best_move_suit, + char best_move_rank, + PosSearchSmall * node_ptr, + bool& result ) -> NodeCards *; auto search_len_and_insert( - PosSearchSmall * root_ptr, - long long key, - bool insert_node, - int trick, - int first_hand, - bool& result + PosSearchSmall * root_ptr, + long long key, + bool insert_node, + int trick, + int first_hand, + bool& result ) -> PosSearchSmall *; auto update_sop( - int u_bound, - int l_bound, - char best_move_suit, - char best_move_rank, - NodeCards * node + int u_bound, + int l_bound, + char best_move_suit, + char best_move_rank, + NodeCards * node ) -> NodeCards *; auto find_sop( - const int order_set[], - int limit, - WinCard * node_p, - bool& lower_flag + const int order_set[], + int limit, + WinCard * node_p, + bool& lower_flag ) -> NodeCards const *; - public: + public: /// \brief Construct a small transposition table instance. /// @@ -250,12 +250,12 @@ class TransTableS: public TransTable /// \param[out] lower_flag Set to true if result is a lower bound /// \return Cached result or nullptr auto lookup( - int trick, - int hand, - const unsigned short aggr_target[], - const int hand_dist[], - int limit, - bool& lower_flag + int trick, + int hand, + const unsigned short aggr_target[], + const int hand_dist[], + int limit, + bool& lower_flag ) -> NodeCards const * override; /// \brief Add a computed result to the transposition table. @@ -269,12 +269,12 @@ class TransTableS: public TransTable /// \param first Computed result to cache /// \param flag True if this is a lower bound (incomplete search) auto add( - int trick, - int hand, - const unsigned short aggr_target[], - const unsigned short win_ranks_arg[], - const NodeCards& first, - bool flag + int trick, + int hand, + const unsigned short aggr_target[], + const unsigned short win_ranks_arg[], + const NodeCards& first, + bool flag ) -> void override; /// \brief No-op print implementation for small TT. @@ -282,18 +282,18 @@ class TransTableS: public TransTable /// The small transposition table does not support detailed dumping. /// These methods are no-op implementations of the base class interface. auto print_suits( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/) const -> void override { } auto print_all_suits(std::ofstream& /*fout*/) const -> void override { } auto print_suit_stats( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/) const -> void override { } auto print_all_suit_stats(std::ofstream& /*fout*/) const -> void override @@ -304,41 +304,41 @@ class TransTableS: public TransTable } auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { - adds = 0; - overwrites = 0; - harvests = 0; // TransTableS not instrumented + adds = 0; + overwrites = 0; + harvests = 0; // TransTableS not instrumented } auto print_summary_suit_stats(std::ofstream& /*fout*/) const -> void override { } auto print_entries_dist( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/, - const int /*hand_dist*/[]) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/, + const int /*hand_dist*/[]) const -> void override { } auto print_entries_dist_and_cards( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/, - const unsigned short /*aggr_target*/[], - const int /*hand_dist*/[]) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/, + const unsigned short /*aggr_target*/[], + const int /*hand_dist*/[]) const -> void override { } auto print_entries( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/) const -> void override { } auto print_all_entries(std::ofstream& /*fout*/) const -> void override { } auto print_entry_stats( - std::ofstream& /*fout*/, - int /*trick*/, - int /*hand*/) const -> void override + std::ofstream& /*fout*/, + int /*trick*/, + int /*hand*/) const -> void override { } auto print_all_entry_stats(std::ofstream& /*fout*/) const -> void override @@ -353,7 +353,7 @@ class TransTableS: public TransTable /// Delegates to print_node_stats_impl() implementation. auto print_node_stats(std::ofstream& fout) const -> void override { - print_node_stats_impl(fout); + print_node_stats_impl(fout); } /// \brief Bridge to reset statistics printer. @@ -361,7 +361,7 @@ class TransTableS: public TransTable /// Delegates to print_reset_stats_impl() implementation. auto print_reset_stats(std::ofstream& fout) const -> void override { - print_reset_stats_impl(fout); + print_reset_stats_impl(fout); } /// \brief Print node statistics from the small TT. diff --git a/library/tests/TestTimer.cpp b/library/tests/TestTimer.cpp index 4cda18bfc..80e029072 100644 --- a/library/tests/TestTimer.cpp +++ b/library/tests/TestTimer.cpp @@ -31,7 +31,7 @@ using std::ostream; TestTimer::TestTimer() { - TestTimer::reset(); + TestTimer::reset(); } @@ -42,222 +42,222 @@ TestTimer::~TestTimer() void TestTimer::reset() { - name_ = ""; - count_ = 0; - user_cum_ = 0; - user_cum_old_ = 0; - sys_cum_ = 0; - pending_hands_ = 0; - sys_time_known_ = true; - running_line_active_ = false; + name_ = ""; + count_ = 0; + user_cum_ = 0; + user_cum_old_ = 0; + sys_cum_ = 0; + pending_hands_ = 0; + sys_time_known_ = true; + running_line_active_ = false; } void TestTimer::mark_sys_time_unavailable() { - sys_time_known_ = false; + sys_time_known_ = false; } bool TestTimer::sys_time_known() const { - return sys_time_known_; + return sys_time_known_; } void TestTimer::set_name(const string& s) { - name_ = s; + name_ = s; } long clock_delta_to_ms(std::clock_t delta) { - return static_cast( - (1000.0 * static_cast(delta)) / - static_cast(CLOCKS_PER_SEC)); + return static_cast( + (1000.0 * static_cast(delta)) / + static_cast(CLOCKS_PER_SEC)); } void TestTimer::start(const int number) { - pending_hands_ = number; - user0_ = Clock::now(); - sys0_ = std::clock(); - if (sys0_ == static_cast(-1)) - sys_time_known_ = false; + pending_hands_ = number; + user0_ = Clock::now(); + sys0_ = std::clock(); + if (sys0_ == static_cast(-1)) + sys_time_known_ = false; } void TestTimer::end() { - std::chrono::time_point user1 = Clock::now(); - std::clock_t sys1 = std::clock(); - - duration d = user1 - user0_; - const long tuser = static_cast(d.count()); - long tsys = 0; - if (sys_time_known_) - { - if (sys1 == static_cast(-1)) - sys_time_known_ = false; - else - tsys = clock_delta_to_ms(sys1 - sys0_); - } + std::chrono::time_point user1 = Clock::now(); + std::clock_t sys1 = std::clock(); + + duration d = user1 - user0_; + const long tuser = static_cast(d.count()); + long tsys = 0; + if (sys_time_known_) + { + if (sys1 == static_cast(-1)) + sys_time_known_ = false; + else + tsys = clock_delta_to_ms(sys1 - sys0_); + } - TestTimer::record(pending_hands_, tuser, tsys); - pending_hands_ = 0; + TestTimer::record(pending_hands_, tuser, tsys); + pending_hands_ = 0; } void TestTimer::record(const int hands, const long user_ms, const long sys_ms) { - if (hands <= 0) - return; + if (hands <= 0) + return; - count_ += hands; - user_cum_ += user_ms; - sys_cum_ += sys_ms; + count_ += hands; + user_cum_ += user_ms; + sys_cum_ += sys_ms; } void TestTimer::print_running( - const int reached, - const int divisor) + const int reached, + const int divisor) { - if (count_ == 0) - return; - - // Overwrite a single terminal line: clear, return to column 0, rewrite, flush. - cout << "\033[2K\r" << setw(8) << reached << " (" << - setw(6) << setprecision(1) << right << fixed << - 100. * reached / - static_cast(divisor) << "%)" << - setw(15) << right << fixed << setprecision(0) << - (user_cum_ - user_cum_old_) << std::flush; - - user_cum_old_ = user_cum_; - running_line_active_ = true; + if (count_ == 0) + return; + + // Overwrite a single terminal line: clear, return to column 0, rewrite, flush. + cout << "\033[2K\r" << setw(8) << reached << " (" << + setw(6) << setprecision(1) << right << fixed << + 100. * reached / + static_cast(divisor) << "%)" << + setw(15) << right << fixed << setprecision(0) << + (user_cum_ - user_cum_old_) << std::flush; + + user_cum_old_ = user_cum_; + running_line_active_ = true; } void TestTimer::finish_running() { - if (!running_line_active_) - return; - // Erase the in-place progress line so it does not remain after the run. - cout << "\033[2K\r" << std::flush; - running_line_active_ = false; + if (!running_line_active_) + return; + // Erase the in-place progress line so it does not remain after the run. + cout << "\033[2K\r" << std::flush; + running_line_active_ = false; } void TestTimer::print_basic() const { - if (count_ == 0) - return; - - if (name_ != "") - cout << setw(19) << left << "Timer name" << ": " << name_ << "\n"; - - cout << setw(19) << left << "Number of calls" << ": " << count_ << "\n"; - - if (user_cum_ == 0) - cout << setw(19) << left << "User time" << ": " << "zero" << "\n"; - else - { - cout << setw(19) << left << "User time/ticks" << ": " << - user_cum_ << "\n"; - cout << setw(19) << left << "User per call" << ": " << - setprecision(2) << user_cum_ / static_cast(count_) << "\n"; - } - - if (!sys_time_known_) - cout << setw(19) << left << "Sys time (ms)" << ": " << "n/a" << "\n"; - else if (sys_cum_ == 0) - cout << setw(19) << left << "Sys time (ms)" << ": " << "zero" << "\n"; - else - { - cout << setw(19) << left << "Sys time/ticks" << ": " << - sys_cum_ << "\n"; - cout << setw(19) << left << "Sys per call" << ": " << - setprecision(2) << sys_cum_ / static_cast(count_) << "\n"; - if (user_cum_ > 0) { - cout << setw(19) << left << "Ratio" << ": " << - setprecision(2) << sys_cum_ / static_cast(user_cum_); + if (count_ == 0) + return; + + if (name_ != "") + cout << setw(19) << left << "Timer name" << ": " << name_ << "\n"; + + cout << setw(19) << left << "Number of calls" << ": " << count_ << "\n"; + + if (user_cum_ == 0) + cout << setw(19) << left << "User time" << ": " << "zero" << "\n"; + else + { + cout << setw(19) << left << "User time/ticks" << ": " << + user_cum_ << "\n"; + cout << setw(19) << left << "User per call" << ": " << + setprecision(2) << user_cum_ / static_cast(count_) << "\n"; + } + + if (!sys_time_known_) + cout << setw(19) << left << "Sys time (ms)" << ": " << "n/a" << "\n"; + else if (sys_cum_ == 0) + cout << setw(19) << left << "Sys time (ms)" << ": " << "zero" << "\n"; + else + { + cout << setw(19) << left << "Sys time/ticks" << ": " << + sys_cum_ << "\n"; + cout << setw(19) << left << "Sys per call" << ": " << + setprecision(2) << sys_cum_ / static_cast(count_) << "\n"; + if (user_cum_ > 0) { + cout << setw(19) << left << "Ratio" << ": " << + setprecision(2) << sys_cum_ / static_cast(user_cum_); + } } - } - cout << endl; + cout << endl; } void TestTimer::print_hands(ostream& out) const { - struct StreamFormatGuard - { - explicit StreamFormatGuard(ostream& os) - : os_(os), - flags_(os.flags()), - precision_(os.precision()), - fill_(os.fill()) + struct StreamFormatGuard { - } - - ~StreamFormatGuard() + explicit StreamFormatGuard(ostream& os) + : os_(os), + flags_(os.flags()), + precision_(os.precision()), + fill_(os.fill()) + { + } + + ~StreamFormatGuard() + { + os_.flags(flags_); + os_.precision(precision_); + os_.fill(fill_); + } + + ostream& os_; + const std::ios_base::fmtflags flags_; + const std::streamsize precision_; + const char fill_; + }; + + const StreamFormatGuard format_guard(out); + + if (name_ != "") + out << setw(21) << left << "Timer name" << + setw(12) << right << name_ << "\n"; + + out << setw(21) << left << "Number of hands" << + setw(12) << right << count_ << "\n"; + + if (count_ == 0) + return; + + if (user_cum_ == 0) + out << setw(21) << left << "User time (ms)" << + setw(12) << right << "zero" << "\n"; + else { - os_.flags(flags_); - os_.precision(precision_); - os_.fill(fill_); + out << setw(21) << left << "User time (ms)" << + setw(12) << right << fixed << + setprecision(0) << user_cum_ << "\n"; + out << setw(21) << left << "Avg user time (ms)" << + setw(12) << right << fixed << setprecision(2) << user_cum_ / + static_cast(count_) << "\n"; } - ostream& os_; - const std::ios_base::fmtflags flags_; - const std::streamsize precision_; - const char fill_; - }; - - const StreamFormatGuard format_guard(out); - - if (name_ != "") - out << setw(21) << left << "Timer name" << - setw(12) << right << name_ << "\n"; - - out << setw(21) << left << "Number of hands" << - setw(12) << right << count_ << "\n"; - - if (count_ == 0) - return; - - if (user_cum_ == 0) - out << setw(21) << left << "User time (ms)" << - setw(12) << right << "zero" << "\n"; - else - { - out << setw(21) << left << "User time (ms)" << - setw(12) << right << fixed << - setprecision(0) << user_cum_ << "\n"; - out << setw(21) << left << "Avg user time (ms)" << - setw(12) << right << fixed << setprecision(2) << user_cum_ / - static_cast(count_) << "\n"; - } - - if (!sys_time_known_) - out << setw(21) << left << "Sys time (ms)" << - setw(12) << right << "n/a" << "\n"; - else if (sys_cum_ == 0) - out << setw(21) << left << "Sys time (ms)" << - setw(12) << right << "zero" << "\n"; - else - { - out << setw(21) << left << "Sys time (ms)" << - setw(12) << right << fixed << setprecision(0) << sys_cum_ << "\n"; - out << setw(21) << left << "Avg sys time (ms)" << - setw(12) << right << fixed << setprecision(2) << sys_cum_ / - static_cast(count_) << "\n"; - if (user_cum_ > 0) { - out << setw(21) << left << "Ratio" << - setw(12) << right << fixed << setprecision(2) << - sys_cum_ / static_cast(user_cum_); + if (!sys_time_known_) + out << setw(21) << left << "Sys time (ms)" << + setw(12) << right << "n/a" << "\n"; + else if (sys_cum_ == 0) + out << setw(21) << left << "Sys time (ms)" << + setw(12) << right << "zero" << "\n"; + else + { + out << setw(21) << left << "Sys time (ms)" << + setw(12) << right << fixed << setprecision(0) << sys_cum_ << "\n"; + out << setw(21) << left << "Avg sys time (ms)" << + setw(12) << right << fixed << setprecision(2) << sys_cum_ / + static_cast(count_) << "\n"; + if (user_cum_ > 0) { + out << setw(21) << left << "Ratio" << + setw(12) << right << fixed << setprecision(2) << + sys_cum_ / static_cast(user_cum_); + } } - } - out << endl; + out << endl; } diff --git a/library/tests/TestTimer.hpp b/library/tests/TestTimer.hpp index ed8e5bf70..3b82db49e 100644 --- a/library/tests/TestTimer.hpp +++ b/library/tests/TestTimer.hpp @@ -34,7 +34,7 @@ long clock_delta_to_ms(std::clock_t delta); /// Tracks both wall-clock (user) and CPU (system) time for test execution. class TestTimer { - private: + private: std::string name_; ///< Timer name for display long count_; ///< Number of times started/stopped long user_cum_; ///< Cumulative user time (milliseconds) @@ -47,7 +47,7 @@ class TestTimer std::chrono::time_point user0_; ///< Wall-clock start time std::clock_t sys0_; ///< CPU start time - public: + public: TestTimer(); ~TestTimer(); @@ -71,7 +71,7 @@ class TestTimer /// Start timing an operation. /// @param number Number of iterations (for per-iteration reporting) void start(const int number = 1); - + /// Stop timing and accumulate results. void end(); @@ -92,10 +92,10 @@ class TestTimer /// End an in-place progress line by clearing it (no leftover 100% row). void finish_running(); - + /// Print basic timer summary. void print_basic() const; - + /// Print detailed per-hand timer results. /// @param out Output stream void print_hands(std::ostream& out = std::cout) const; diff --git a/library/tests/ab_search/ab_stats_test.cpp b/library/tests/ab_search/ab_stats_test.cpp index 65b8c4ac6..4adaf94b4 100644 --- a/library/tests/ab_search/ab_stats_test.cpp +++ b/library/tests/ab_search/ab_stats_test.cpp @@ -7,42 +7,42 @@ TEST(ABstatsTest, GetPosCountStartsAtZeroAfterReset) { - ABstats stats; - stats.Reset(); - stats.ResetCum(); + ABstats stats; + stats.Reset(); + stats.ResetCum(); - EXPECT_EQ(stats.GetPosCount(AB_MAIN_LOOKUP), 0); + EXPECT_EQ(stats.GetPosCount(AB_MAIN_LOOKUP), 0); } TEST(ABstatsTest, IncrPosIncrementsGetPosCount) { - ABstats stats; - stats.Reset(); - stats.ResetCum(); + ABstats stats; + stats.Reset(); + stats.ResetCum(); - stats.IncrPos(AB_MAIN_LOOKUP, /*side=*/true, /*depth=*/20); + stats.IncrPos(AB_MAIN_LOOKUP, /*side=*/true, /*depth=*/20); - EXPECT_EQ(stats.GetPosCount(AB_MAIN_LOOKUP), 1); - EXPECT_EQ(stats.GetPosCount(AB_MOVE_LOOP), 0); + EXPECT_EQ(stats.GetPosCount(AB_MAIN_LOOKUP), 1); + EXPECT_EQ(stats.GetPosCount(AB_MOVE_LOOP), 0); } TEST(ABstatsTest, GetPosCountRejectsOutOfRangePlace) { - ABstats stats; - stats.Reset(); - stats.ResetCum(); + ABstats stats; + stats.Reset(); + stats.ResetCum(); - EXPECT_EQ(stats.GetPosCount(-1), 0); - EXPECT_EQ(stats.GetPosCount(AB_SIZE), 0); - EXPECT_EQ(stats.GetPosCount(100), 0); + EXPECT_EQ(stats.GetPosCount(-1), 0); + EXPECT_EQ(stats.GetPosCount(AB_SIZE), 0); + EXPECT_EQ(stats.GetPosCount(100), 0); } TEST(ABstatsTest, IncrNodeAfterConstructionUsesZeroedCumNodeList) { - // ResetCum must clear ABnodesCum.list[]; otherwise IncrNode increments - // indeterminate values (UBSan) and PrintStatsDepth reads garbage. - ABstats stats; - stats.IncrNode(/*depth=*/5); + // ResetCum must clear ABnodesCum.list[]; otherwise IncrNode increments + // indeterminate values (UBSan) and PrintStatsDepth reads garbage. + ABstats stats; + stats.IncrNode(/*depth=*/5); - EXPECT_EQ(stats.GetNodes(), 1); + EXPECT_EQ(stats.GetNodes(), 1); } diff --git a/library/tests/ab_search/estimator_cut_tt_test.cpp b/library/tests/ab_search/estimator_cut_tt_test.cpp index c778813b7..db718c17d 100644 --- a/library/tests/ab_search/estimator_cut_tt_test.cpp +++ b/library/tests/ab_search/estimator_cut_tt_test.cpp @@ -38,201 +38,201 @@ constexpr int kHand = 0; // North leads void set_env_var(const char* name, const char* value) { #ifdef _WIN32 - _putenv_s(name, value != nullptr ? value : ""); + _putenv_s(name, value != nullptr ? value : ""); #else - if (value == nullptr || value[0] == '\0') - unsetenv(name); - else - setenv(name, value, 1); + if (value == nullptr || value[0] == '\0') + unsetenv(name); + else + setenv(name, value, 1); #endif } /// Restores the previous value (or absence) of an environment variable. struct ScopedEnv { - ScopedEnv(const char* name, const char* value) : name_(name) - { - if (const char* old = std::getenv(name)) { - had_old_ = true; - old_ = old; + ScopedEnv(const char* name, const char* value) : name_(name) + { + if (const char* old = std::getenv(name)) { + had_old_ = true; + old_ = old; + } + set_env_var(name, value); } - set_env_var(name, value); - } - ~ScopedEnv() - { - set_env_var(name_, had_old_ ? old_.c_str() : nullptr); - } - const char* name_; - bool had_old_ = false; - std::string old_; + ~ScopedEnv() + { + set_env_var(name_, had_old_ ? old_.c_str() : nullptr); + } + const char* name_; + bool had_old_ = false; + std::string old_; }; class EstimatorCutTtTest : public ::testing::Test { protected: - // Exact remaining-card keys are Pattern-TT specific: Small/Large ignore - // swapped equal-length holdings. SolverContext would honor DDS_TT_KIND. - ScopedEnv no_tt_kind_override_{"DDS_TT_KIND", nullptr}; - - void SetUp() override - { - InitializeStaticMemory(); - if (memory.NumThreads() == 0) { - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + // Exact remaining-card keys are Pattern-TT specific: Small/Large ignore + // swapped equal-length holdings. SolverContext would honor DDS_TT_KIND. + ScopedEnv no_tt_kind_override_{"DDS_TT_KIND", nullptr}; + + void SetUp() override + { + InitializeStaticMemory(); + if (memory.NumThreads() == 0) { + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + } + + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Pattern; + ctx_ = std::make_unique(cfg); + auto* thrp = ctx_->thread_ptr(); + ASSERT_NE(thrp, nullptr); + ASSERT_NE(nullptr, dynamic_cast(ctx_->trans_table())) + << "estimator-cut keys are Pattern-TT specific"; + std::memset(thrp->suit, 0, sizeof(thrp->suit)); + thrp->trump = DDS_NOTRUMP; + std::memset(&thrp->lookAheadPos, 0, sizeof(thrp->lookAheadPos)); } - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Pattern; - ctx_ = std::make_unique(cfg); - auto* thrp = ctx_->thread_ptr(); - ASSERT_NE(thrp, nullptr); - ASSERT_NE(nullptr, dynamic_cast(ctx_->trans_table())) - << "estimator-cut keys are Pattern-TT specific"; - std::memset(thrp->suit, 0, sizeof(thrp->suit)); - thrp->trump = DDS_NOTRUMP; - std::memset(&thrp->lookAheadPos, 0, sizeof(thrp->lookAheadPos)); - } - - void place(int hand, int suit, int rank) - { - ctx_->thread_ptr()->suit[hand][suit] = - static_cast(ctx_->thread_ptr()->suit[hand][suit] | bit_map_rank[rank]); - } - - void finish_deal() - { - auto thrp = ctx_->thread(); - SetDeal(thrp); - SetDealTables(*ctx_); - - Deal dl{}; - dl.first = kHand; - thrp->lookAheadPos.first[kDepth] = kHand; - thrp->lookAheadPos.hand_rel_first = 0; - InitWinners(dl, thrp->lookAheadPos, thrp); - - ctx_->search().node_type_store(0) = MAXNODE; - ctx_->search().node_type_store(1) = MINNODE; - ctx_->search().node_type_store(2) = MAXNODE; - ctx_->search().node_type_store(3) = MINNODE; - } - - auto pos() -> Pos& { return ctx_->thread_ptr()->lookAheadPos; } - - auto lookup_score(int target) -> bool - { - bool score_flag = false; - const int tricks = kDepth >> 2; - const bool hit = apply_ab_tt_lookup( - &pos(), target, kDepth, tricks, kHand, *ctx_, score_flag); - EXPECT_TRUE(hit) << "expected a TT hit after store_ab_tt_result"; - return score_flag; - } - - std::unique_ptr ctx_; + void place(int hand, int suit, int rank) + { + ctx_->thread_ptr()->suit[hand][suit] = + static_cast(ctx_->thread_ptr()->suit[hand][suit] | bit_map_rank[rank]); + } + + void finish_deal() + { + auto thrp = ctx_->thread(); + SetDeal(thrp); + SetDealTables(*ctx_); + + Deal dl{}; + dl.first = kHand; + thrp->lookAheadPos.first[kDepth] = kHand; + thrp->lookAheadPos.hand_rel_first = 0; + InitWinners(dl, thrp->lookAheadPos, thrp); + + ctx_->search().node_type_store(0) = MAXNODE; + ctx_->search().node_type_store(1) = MINNODE; + ctx_->search().node_type_store(2) = MAXNODE; + ctx_->search().node_type_store(3) = MINNODE; + } + + auto pos() -> Pos& { return ctx_->thread_ptr()->lookAheadPos; } + + auto lookup_score(int target) -> bool + { + bool score_flag = false; + const int tricks = kDepth >> 2; + const bool hit = apply_ab_tt_lookup( + &pos(), target, kDepth, tricks, kHand, *ctx_, score_flag); + EXPECT_TRUE(hit) << "expected a TT hit after store_ab_tt_result"; + return score_flag; + } + + std::unique_ptr ctx_; }; } // namespace TEST_F(EstimatorCutTtTest, SearchDoesNotStoreAQuickTricksCutoff) { - place(0, 0, 14); - place(1, 0, 13); - place(0, 1, 2); - place(1, 1, 3); - finish_deal(); - pos().tricks_max = 0; - - bool qt_cut = false; - ASSERT_GE(QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_), 1); - ASSERT_TRUE(qt_cut); - - EXPECT_TRUE(ab_search_0(&pos(), /*target*/ 1, kDepth, *ctx_)); - - bool score_flag = true; - EXPECT_FALSE(apply_ab_tt_lookup( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, score_flag)); + place(0, 0, 14); + place(1, 0, 13); + place(0, 1, 2); + place(1, 1, 3); + finish_deal(); + pos().tricks_max = 0; + + bool qt_cut = false; + ASSERT_GE(QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_), 1); + ASSERT_TRUE(qt_cut); + + EXPECT_TRUE(ab_search_0(&pos(), /*target*/ 1, kDepth, *ctx_)); + + bool score_flag = true; + EXPECT_FALSE(apply_ab_tt_lookup( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, score_flag)); } TEST_F(EstimatorCutTtTest, StoreHelperMemoizesAQuickTricksCutoffExactly) { - place(0, 0, 14); - place(1, 0, 13); - place(0, 1, 2); - place(1, 1, 3); - finish_deal(); - pos().tricks_max = 0; - - bool qt_cut = false; - const int qtricks = QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_); - ASSERT_TRUE(qt_cut); - ASSERT_GE(qtricks, 1); - - bool preexisting = false; - ASSERT_FALSE(apply_ab_tt_lookup( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); - - store_ab_tt_result( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, /*value*/ true, *ctx_, pos().aggr); - - EXPECT_TRUE(lookup_score(/*target*/ 1)); - - for (int s = 0; s < DDS_SUITS; ++s) { - pos().winner[s] = HighCardType{0, -1}; - pos().second_best[s] = HighCardType{0, -1}; - } - EXPECT_TRUE(lookup_score(/*target*/ 1)); + place(0, 0, 14); + place(1, 0, 13); + place(0, 1, 2); + place(1, 1, 3); + finish_deal(); + pos().tricks_max = 0; + + bool qt_cut = false; + const int qtricks = QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_); + ASSERT_TRUE(qt_cut); + ASSERT_GE(qtricks, 1); + + bool preexisting = false; + ASSERT_FALSE(apply_ab_tt_lookup( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); + + store_ab_tt_result( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, /*value*/ true, *ctx_, pos().aggr); + + EXPECT_TRUE(lookup_score(/*target*/ 1)); + + for (int s = 0; s < DDS_SUITS; ++s) { + pos().winner[s] = HighCardType{0, -1}; + pos().second_best[s] = HighCardType{0, -1}; + } + EXPECT_TRUE(lookup_score(/*target*/ 1)); } TEST_F(EstimatorCutTtTest, ExactEstimatorKeyDoesNotMatchSwappedLowCards) { - place(0, 0, 14); - place(1, 0, 13); - place(0, 1, 2); - place(1, 1, 3); - finish_deal(); - pos().tricks_max = 0; - - bool qt_cut = false; - ASSERT_TRUE(QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_) >= 1); - ASSERT_TRUE(qt_cut); - bool preexisting = false; - ASSERT_FALSE(apply_ab_tt_lookup( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); - store_ab_tt_result( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, /*value*/ true, *ctx_, pos().aggr); - - ctx_->thread_ptr()->suit[0][1] = bit_map_rank[3]; - ctx_->thread_ptr()->suit[1][1] = bit_map_rank[2]; - finish_deal(); - pos().tricks_max = 0; - - bool score_flag = false; - EXPECT_FALSE(apply_ab_tt_lookup( - &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, score_flag)); + place(0, 0, 14); + place(1, 0, 13); + place(0, 1, 2); + place(1, 1, 3); + finish_deal(); + pos().tricks_max = 0; + + bool qt_cut = false; + ASSERT_TRUE(QuickTricks(pos(), kHand, kDepth, /*target*/ 1, DDS_NOTRUMP, qt_cut, *ctx_) >= 1); + ASSERT_TRUE(qt_cut); + bool preexisting = false; + ASSERT_FALSE(apply_ab_tt_lookup( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); + store_ab_tt_result( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, /*value*/ true, *ctx_, pos().aggr); + + ctx_->thread_ptr()->suit[0][1] = bit_map_rank[3]; + ctx_->thread_ptr()->suit[1][1] = bit_map_rank[2]; + finish_deal(); + pos().tricks_max = 0; + + bool score_flag = false; + EXPECT_FALSE(apply_ab_tt_lookup( + &pos(), /*target*/ 1, kDepth, kDepth >> 2, kHand, *ctx_, score_flag)); } TEST_F(EstimatorCutTtTest, StoreHelperMemoizesALaterTricksCutoff) { - place(0, 0, 14); - place(1, 0, 13); - place(1, 1, 14); - place(3, 2, 14); - place(1, 3, 14); - finish_deal(); - pos().tricks_max = 0; - - const int target = 6; - bool qt_cut = true; - (void)QuickTricks(pos(), kHand, kDepth, target, DDS_NOTRUMP, qt_cut, *ctx_); - ASSERT_FALSE(qt_cut); - ASSERT_FALSE(LaterTricksMIN(pos(), kHand, kDepth, target, DDS_NOTRUMP, *ctx_)); - - bool preexisting = false; - ASSERT_FALSE(apply_ab_tt_lookup( - &pos(), target, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); - - store_ab_tt_result( - &pos(), target, kDepth, kDepth >> 2, kHand, /*value*/ false, *ctx_, pos().aggr); - - EXPECT_FALSE(lookup_score(target)); + place(0, 0, 14); + place(1, 0, 13); + place(1, 1, 14); + place(3, 2, 14); + place(1, 3, 14); + finish_deal(); + pos().tricks_max = 0; + + const int target = 6; + bool qt_cut = true; + (void)QuickTricks(pos(), kHand, kDepth, target, DDS_NOTRUMP, qt_cut, *ctx_); + ASSERT_FALSE(qt_cut); + ASSERT_FALSE(LaterTricksMIN(pos(), kHand, kDepth, target, DDS_NOTRUMP, *ctx_)); + + bool preexisting = false; + ASSERT_FALSE(apply_ab_tt_lookup( + &pos(), target, kDepth, kDepth >> 2, kHand, *ctx_, preexisting)); + + store_ab_tt_result( + &pos(), target, kDepth, kDepth >> 2, kHand, /*value*/ false, *ctx_, pos().aggr); + + EXPECT_FALSE(lookup_score(target)); } diff --git a/library/tests/ab_search/make3_test.cpp b/library/tests/ab_search/make3_test.cpp index c5509f35f..cc0b5f61f 100644 --- a/library/tests/ab_search/make3_test.cpp +++ b/library/tests/ab_search/make3_test.cpp @@ -26,125 +26,125 @@ constexpr int kHandDelta[DDS_SUITS] = {256, 16, 1, 0}; class Make3Test : public ::testing::Test { protected: - void SetUp() override - { - InitializeStaticMemory(); - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - - ctx_ = std::make_unique(); - std::memset(&pos_, 0, sizeof(pos_)); - - // Leader North; fourth hand is West (rel 3). - leader_ = 0; - fourth_ = HAND_ID(leader_, 3); - pos_.first[kDepth] = leader_; - - suit_ = 0; // spades - rank_ = 14; // Ace from fourth hand — wins the trick - pos_.rank_in_suit[fourth_][suit_] = bit_map_rank[rank_]; - pos_.aggr[suit_] = bit_map_rank[rank_] | bit_map_rank[2] | bit_map_rank[3] | + void SetUp() override + { + InitializeStaticMemory(); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + + ctx_ = std::make_unique(); + std::memset(&pos_, 0, sizeof(pos_)); + + // Leader North; fourth hand is West (rel 3). + leader_ = 0; + fourth_ = HAND_ID(leader_, 3); + pos_.first[kDepth] = leader_; + + suit_ = 0; // spades + rank_ = 14; // Ace from fourth hand — wins the trick + pos_.rank_in_suit[fourth_][suit_] = bit_map_rank[rank_]; + pos_.aggr[suit_] = bit_map_rank[rank_] | bit_map_rank[2] | bit_map_rank[3] | bit_map_rank[4]; - pos_.length[fourth_][suit_] = 1; - pos_.hand_dist[fourth_] = kHandDelta[suit_]; - - // Snapshot winners that make_3 should save and undo_0 restore. - pos_.winner[suit_].rank = 14; - pos_.winner[suit_].hand = fourth_; - pos_.second_best[suit_].rank = 4; - pos_.second_best[suit_].hand = HAND_ID(leader_, 2); - - MoveType plays[4] = { - {suit_, 2, 0, 0}, - {suit_, 3, 0, 0}, - {suit_, 4, 0, 0}, - {suit_, rank_, 0, 0}, - }; - for (int rel = 0; rel < 4; ++rel) - ctx_->move_gen().make_specific(plays[rel], kTrick, rel); - - fourth_move_ = plays[3]; - } - - std::unique_ptr ctx_; - Pos pos_{}; - MoveType fourth_move_{}; - int leader_ = 0; - int fourth_ = 0; - int suit_ = 0; - int rank_ = 0; + pos_.length[fourth_][suit_] = 1; + pos_.hand_dist[fourth_] = kHandDelta[suit_]; + + // Snapshot winners that make_3 should save and undo_0 restore. + pos_.winner[suit_].rank = 14; + pos_.winner[suit_].hand = fourth_; + pos_.second_best[suit_].rank = 4; + pos_.second_best[suit_].hand = HAND_ID(leader_, 2); + + MoveType plays[4] = { + {suit_, 2, 0, 0}, + {suit_, 3, 0, 0}, + {suit_, 4, 0, 0}, + {suit_, rank_, 0, 0}, + }; + for (int rel = 0; rel < 4; ++rel) + ctx_->move_gen().make_specific(plays[rel], kTrick, rel); + + fourth_move_ = plays[3]; + } + + std::unique_ptr ctx_; + Pos pos_{}; + MoveType fourth_move_{}; + int leader_ = 0; + int fourth_ = 0; + int suit_ = 0; + int rank_ = 0; }; } // namespace TEST_F(Make3Test, RemovesFourthHandCardAndSetsNextLeader) { - unsigned short trick_cards[DDS_SUITS] = {1, 1, 1, 1}; - - make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); - - EXPECT_EQ(pos_.rank_in_suit[fourth_][suit_], 0); - EXPECT_EQ(pos_.length[fourth_][suit_], 0); - EXPECT_EQ(pos_.hand_dist[fourth_], 0); - // Ace won; relative winner is 3 → next leader is West. - EXPECT_EQ(pos_.first[kDepth - 1], fourth_); - // Four cards in the suit → trickCards records the winning sequence. - EXPECT_NE(trick_cards[suit_] & bit_map_rank[rank_], 0); - for (int s = 0; s < DDS_SUITS; ++s) - { - if (s != suit_) - EXPECT_EQ(trick_cards[s], 0); - } + unsigned short trick_cards[DDS_SUITS] = {1, 1, 1, 1}; + + make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); + + EXPECT_EQ(pos_.rank_in_suit[fourth_][suit_], 0); + EXPECT_EQ(pos_.length[fourth_][suit_], 0); + EXPECT_EQ(pos_.hand_dist[fourth_], 0); + // Ace won; relative winner is 3 → next leader is West. + EXPECT_EQ(pos_.first[kDepth - 1], fourth_); + // Four cards in the suit → trickCards records the winning sequence. + EXPECT_NE(trick_cards[suit_] & bit_map_rank[rank_], 0); + for (int s = 0; s < DDS_SUITS; ++s) + { + if (s != suit_) + EXPECT_EQ(trick_cards[s], 0); + } } TEST_F(Make3Test, SnapshotsWinnersForPlayedSuits) { - unsigned short trick_cards[DDS_SUITS] = {}; - const int saved_winner_rank = pos_.winner[suit_].rank; - const int saved_winner_hand = pos_.winner[suit_].hand; - const int saved_second_rank = pos_.second_best[suit_].rank; - const int saved_second_hand = pos_.second_best[suit_].hand; - - make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); - - WinnersType const& wp = ctx_->search().winners(kTrick); - ASSERT_GE(wp.number, 1); - bool found = false; - for (int n = 0; n < wp.number; ++n) - { - if (wp.winner[n].suit == suit_) + unsigned short trick_cards[DDS_SUITS] = {}; + const int saved_winner_rank = pos_.winner[suit_].rank; + const int saved_winner_hand = pos_.winner[suit_].hand; + const int saved_second_rank = pos_.second_best[suit_].rank; + const int saved_second_hand = pos_.second_best[suit_].hand; + + make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); + + WinnersType const& wp = ctx_->search().winners(kTrick); + ASSERT_GE(wp.number, 1); + bool found = false; + for (int n = 0; n < wp.number; ++n) { - found = true; - EXPECT_EQ(wp.winner[n].winnerRank, saved_winner_rank); - EXPECT_EQ(wp.winner[n].winnerHand, saved_winner_hand); - EXPECT_EQ(wp.winner[n].secondRank, saved_second_rank); - EXPECT_EQ(wp.winner[n].secondHand, saved_second_hand); + if (wp.winner[n].suit == suit_) + { + found = true; + EXPECT_EQ(wp.winner[n].winnerRank, saved_winner_rank); + EXPECT_EQ(wp.winner[n].winnerHand, saved_winner_hand); + EXPECT_EQ(wp.winner[n].secondRank, saved_second_rank); + EXPECT_EQ(wp.winner[n].secondHand, saved_second_hand); + } } - } - EXPECT_TRUE(found); + EXPECT_TRUE(found); } TEST_F(Make3Test, Undo0RestoresCardAndWinners) { - unsigned short trick_cards[DDS_SUITS] = {}; - const auto ris = pos_.rank_in_suit[fourth_][suit_]; - const auto aggr = pos_.aggr[suit_]; - const auto len = pos_.length[fourth_][suit_]; - const auto dist = pos_.hand_dist[fourth_]; - const auto win_rank = pos_.winner[suit_].rank; - const auto win_hand = pos_.winner[suit_].hand; - const auto sec_rank = pos_.second_best[suit_].rank; - const auto sec_hand = pos_.second_best[suit_].hand; - - make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); - undo_0(&pos_, kDepth, fourth_move_, *ctx_); - - EXPECT_EQ(pos_.rank_in_suit[fourth_][suit_], ris); - EXPECT_EQ(pos_.aggr[suit_], aggr); - EXPECT_EQ(pos_.length[fourth_][suit_], len); - EXPECT_EQ(pos_.hand_dist[fourth_], dist); - EXPECT_EQ(pos_.winner[suit_].rank, win_rank); - EXPECT_EQ(pos_.winner[suit_].hand, win_hand); - EXPECT_EQ(pos_.second_best[suit_].rank, sec_rank); - EXPECT_EQ(pos_.second_best[suit_].hand, sec_hand); + unsigned short trick_cards[DDS_SUITS] = {}; + const auto ris = pos_.rank_in_suit[fourth_][suit_]; + const auto aggr = pos_.aggr[suit_]; + const auto len = pos_.length[fourth_][suit_]; + const auto dist = pos_.hand_dist[fourth_]; + const auto win_rank = pos_.winner[suit_].rank; + const auto win_hand = pos_.winner[suit_].hand; + const auto sec_rank = pos_.second_best[suit_].rank; + const auto sec_hand = pos_.second_best[suit_].hand; + + make_3(&pos_, trick_cards, kDepth, &fourth_move_, *ctx_); + undo_0(&pos_, kDepth, fourth_move_, *ctx_); + + EXPECT_EQ(pos_.rank_in_suit[fourth_][suit_], ris); + EXPECT_EQ(pos_.aggr[suit_], aggr); + EXPECT_EQ(pos_.length[fourth_][suit_], len); + EXPECT_EQ(pos_.hand_dist[fourth_], dist); + EXPECT_EQ(pos_.winner[suit_].rank, win_rank); + EXPECT_EQ(pos_.winner[suit_].hand, win_hand); + EXPECT_EQ(pos_.second_best[suit_].rank, sec_rank); + EXPECT_EQ(pos_.second_best[suit_].hand, sec_hand); } diff --git a/library/tests/ab_search/make_undo_test.cpp b/library/tests/ab_search/make_undo_test.cpp index 44e694185..b7036ec1d 100644 --- a/library/tests/ab_search/make_undo_test.cpp +++ b/library/tests/ab_search/make_undo_test.cpp @@ -18,27 +18,27 @@ constexpr int kDepth = 4; constexpr int kHandDelta[DDS_SUITS] = {256, 16, 1, 0}; struct CardFixture { - Pos pos{}; - MoveType move{}; - int leader = 0; - int player = 0; - int suit = 0; - int rank = 14; // Ace - - CardFixture(int first_hand, int relative_hand, int suit, int rank) - : leader(first_hand), - player(HAND_ID(first_hand, relative_hand)), - suit(suit), - rank(rank) - { - std::memset(&pos, 0, sizeof(pos)); - pos.first[kDepth] = leader; - pos.rank_in_suit[player][suit] = bit_map_rank[rank]; - pos.aggr[suit] = bit_map_rank[rank]; - pos.length[player][suit] = 1; - pos.hand_dist[player] = kHandDelta[suit]; - move = MoveType{suit, rank, 0, 0}; - } + Pos pos{}; + MoveType move{}; + int leader = 0; + int player = 0; + int suit = 0; + int rank = 14; // Ace + + CardFixture(int first_hand, int relative_hand, int suit, int rank) + : leader(first_hand), + player(HAND_ID(first_hand, relative_hand)), + suit(suit), + rank(rank) + { + std::memset(&pos, 0, sizeof(pos)); + pos.first[kDepth] = leader; + pos.rank_in_suit[player][suit] = bit_map_rank[rank]; + pos.aggr[suit] = bit_map_rank[rank]; + pos.length[player][suit] = 1; + pos.hand_dist[player] = kHandDelta[suit]; + move = MoveType{suit, rank, 0, 0}; + } }; } // namespace @@ -47,110 +47,110 @@ class MakeUndoHands012 : public ::testing::Test {}; TEST_F(MakeUndoHands012, Make0RemovesLeadersCardAndStoresMove) { - CardFixture fx(/*first*/ 2, /*rel*/ 0, /*suit*/ 1, /*rank*/ 13); + CardFixture fx(/*first*/ 2, /*rel*/ 0, /*suit*/ 1, /*rank*/ 13); - make_0(&fx.pos, kDepth, &fx.move); + make_0(&fx.pos, kDepth, &fx.move); - EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); - EXPECT_EQ(fx.pos.move[kDepth].suit, fx.suit); - EXPECT_EQ(fx.pos.move[kDepth].rank, fx.rank); - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.aggr[fx.suit], 0); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); + EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); + EXPECT_EQ(fx.pos.move[kDepth].suit, fx.suit); + EXPECT_EQ(fx.pos.move[kDepth].rank, fx.rank); + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.aggr[fx.suit], 0); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); } TEST_F(MakeUndoHands012, Make1RemovesSecondHandsCard) { - CardFixture fx(/*first*/ 0, /*rel*/ 1, /*suit*/ 0, /*rank*/ 14); + CardFixture fx(/*first*/ 0, /*rel*/ 1, /*suit*/ 0, /*rank*/ 14); - make_1(&fx.pos, kDepth, &fx.move); + make_1(&fx.pos, kDepth, &fx.move); - EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.aggr[fx.suit], 0); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); + EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.aggr[fx.suit], 0); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); } TEST_F(MakeUndoHands012, Make2RemovesThirdHandsCard) { - CardFixture fx(/*first*/ 1, /*rel*/ 2, /*suit*/ 2, /*rank*/ 10); + CardFixture fx(/*first*/ 1, /*rel*/ 2, /*suit*/ 2, /*rank*/ 10); - make_2(&fx.pos, kDepth, &fx.move); + make_2(&fx.pos, kDepth, &fx.move); - EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.aggr[fx.suit], 0); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); - EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); + EXPECT_EQ(fx.pos.first[kDepth - 1], fx.leader); + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.aggr[fx.suit], 0); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], 0); + EXPECT_EQ(fx.pos.hand_dist[fx.player], 0); } TEST_F(MakeUndoHands012, Undo1RestoresAfterMake0) { - CardFixture fx(/*first*/ 3, /*rel*/ 0, /*suit*/ 3, /*rank*/ 7); - const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; - const auto aggr = fx.pos.aggr[fx.suit]; - const auto len = fx.pos.length[fx.player][fx.suit]; - const auto dist = fx.pos.hand_dist[fx.player]; - - make_0(&fx.pos, kDepth, &fx.move); - undo_1(&fx.pos, kDepth, fx.move); - - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); - EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); - EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); + CardFixture fx(/*first*/ 3, /*rel*/ 0, /*suit*/ 3, /*rank*/ 7); + const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; + const auto aggr = fx.pos.aggr[fx.suit]; + const auto len = fx.pos.length[fx.player][fx.suit]; + const auto dist = fx.pos.hand_dist[fx.player]; + + make_0(&fx.pos, kDepth, &fx.move); + undo_1(&fx.pos, kDepth, fx.move); + + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); + EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); + EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); } TEST_F(MakeUndoHands012, Undo2RestoresAfterMake1) { - CardFixture fx(/*first*/ 2, /*rel*/ 1, /*suit*/ 0, /*rank*/ 12); - const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; - const auto aggr = fx.pos.aggr[fx.suit]; - const auto len = fx.pos.length[fx.player][fx.suit]; - const auto dist = fx.pos.hand_dist[fx.player]; - - make_1(&fx.pos, kDepth, &fx.move); - undo_2(&fx.pos, kDepth, fx.move); - - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); - EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); - EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); + CardFixture fx(/*first*/ 2, /*rel*/ 1, /*suit*/ 0, /*rank*/ 12); + const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; + const auto aggr = fx.pos.aggr[fx.suit]; + const auto len = fx.pos.length[fx.player][fx.suit]; + const auto dist = fx.pos.hand_dist[fx.player]; + + make_1(&fx.pos, kDepth, &fx.move); + undo_2(&fx.pos, kDepth, fx.move); + + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); + EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); + EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); } TEST_F(MakeUndoHands012, Undo3RestoresAfterMake2) { - CardFixture fx(/*first*/ 0, /*rel*/ 2, /*suit*/ 1, /*rank*/ 9); - const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; - const auto aggr = fx.pos.aggr[fx.suit]; - const auto len = fx.pos.length[fx.player][fx.suit]; - const auto dist = fx.pos.hand_dist[fx.player]; - - make_2(&fx.pos, kDepth, &fx.move); - undo_3(&fx.pos, kDepth, fx.move); - - EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); - EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); - EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); - EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); + CardFixture fx(/*first*/ 0, /*rel*/ 2, /*suit*/ 1, /*rank*/ 9); + const auto ris = fx.pos.rank_in_suit[fx.player][fx.suit]; + const auto aggr = fx.pos.aggr[fx.suit]; + const auto len = fx.pos.length[fx.player][fx.suit]; + const auto dist = fx.pos.hand_dist[fx.player]; + + make_2(&fx.pos, kDepth, &fx.move); + undo_3(&fx.pos, kDepth, fx.move); + + EXPECT_EQ(fx.pos.rank_in_suit[fx.player][fx.suit], ris); + EXPECT_EQ(fx.pos.aggr[fx.suit], aggr); + EXPECT_EQ(fx.pos.length[fx.player][fx.suit], len); + EXPECT_EQ(fx.pos.hand_dist[fx.player], dist); } TEST_F(MakeUndoHands012, MakeDoesNotAffectOtherHands) { - CardFixture fx(/*first*/ 0, /*rel*/ 1, /*suit*/ 0, /*rank*/ 14); - // Give another hand a different card in the same suit. - const int other = HAND_ID(fx.leader, 2); - fx.pos.rank_in_suit[other][fx.suit] = bit_map_rank[13]; - fx.pos.aggr[fx.suit] |= bit_map_rank[13]; - fx.pos.length[other][fx.suit] = 1; - fx.pos.hand_dist[other] = kHandDelta[fx.suit]; - - make_1(&fx.pos, kDepth, &fx.move); - - EXPECT_EQ(fx.pos.rank_in_suit[other][fx.suit], bit_map_rank[13]); - EXPECT_EQ(fx.pos.length[other][fx.suit], 1); - EXPECT_EQ(fx.pos.hand_dist[other], kHandDelta[fx.suit]); - EXPECT_EQ(fx.pos.aggr[fx.suit], bit_map_rank[13]); + CardFixture fx(/*first*/ 0, /*rel*/ 1, /*suit*/ 0, /*rank*/ 14); + // Give another hand a different card in the same suit. + const int other = HAND_ID(fx.leader, 2); + fx.pos.rank_in_suit[other][fx.suit] = bit_map_rank[13]; + fx.pos.aggr[fx.suit] |= bit_map_rank[13]; + fx.pos.length[other][fx.suit] = 1; + fx.pos.hand_dist[other] = kHandDelta[fx.suit]; + + make_1(&fx.pos, kDepth, &fx.move); + + EXPECT_EQ(fx.pos.rank_in_suit[other][fx.suit], bit_map_rank[13]); + EXPECT_EQ(fx.pos.length[other][fx.suit], 1); + EXPECT_EQ(fx.pos.hand_dist[other], kHandDelta[fx.suit]); + EXPECT_EQ(fx.pos.aggr[fx.suit], bit_map_rank[13]); } diff --git a/library/tests/ab_search/tt_lookup_test.cpp b/library/tests/ab_search/tt_lookup_test.cpp index 998125d7c..2b91582b8 100644 --- a/library/tests/ab_search/tt_lookup_test.cpp +++ b/library/tests/ab_search/tt_lookup_test.cpp @@ -22,148 +22,148 @@ namespace { class AbTtLookupTest : public ::testing::Test { protected: - void SetUp() override - { - InitializeStaticMemory(); - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - - ctx_ = std::make_unique(); - auto* tt = ctx_->trans_table(); - ASSERT_NE(tt, nullptr); - ctx_->reset_for_solve(); - - int hand_lookup[DDS_SUITS][15] = {}; - tt->init(hand_lookup); - - std::memset(&pos_, 0, sizeof(pos_)); - pos_.first[depth_] = hand_; - ctx_->search().node_type_store(0) = MAXNODE; - } - - void SeedTtEntry(char best_suit, char best_rank) - { - // Lookup first so suit_lengths_[tricks_] is populated for add(). - bool lower_flag = false; - (void)ctx_->trans_table()->lookup( - tricks_, hand_, pos_.aggr, pos_.hand_dist, /*limit*/0, lower_flag); - - // Bounds that are conclusive for limit==0 (MAXNODE with target=1): - // upper_bound <= limit yields a hit with lower_flag=false. - NodeCards first{}; - first.lower_bound = 0; - first.upper_bound = 0; - first.best_move_suit = best_suit; - first.best_move_rank = best_rank; - std::memset(first.least_win, 0, sizeof(first.least_win)); - - unsigned short win_ranks[DDS_SUITS] = {}; - // flag=true stores best-move hint; false would clear it. - ctx_->trans_table()->add( - tricks_, hand_, pos_.aggr, win_ranks, first, /*flag*/ true); - } - - std::unique_ptr ctx_; - Pos pos_{}; - const int depth_ = 20; // tricks = 5; also valid for the late (depth < 20) path - const int tricks_ = 5; - const int hand_ = 0; - const int target_ = 1; + void SetUp() override + { + InitializeStaticMemory(); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + + ctx_ = std::make_unique(); + auto* tt = ctx_->trans_table(); + ASSERT_NE(tt, nullptr); + ctx_->reset_for_solve(); + + int hand_lookup[DDS_SUITS][15] = {}; + tt->init(hand_lookup); + + std::memset(&pos_, 0, sizeof(pos_)); + pos_.first[depth_] = hand_; + ctx_->search().node_type_store(0) = MAXNODE; + } + + void SeedTtEntry(char best_suit, char best_rank) + { + // Lookup first so suit_lengths_[tricks_] is populated for add(). + bool lower_flag = false; + (void)ctx_->trans_table()->lookup( + tricks_, hand_, pos_.aggr, pos_.hand_dist, /*limit*/0, lower_flag); + + // Bounds that are conclusive for limit==0 (MAXNODE with target=1): + // upper_bound <= limit yields a hit with lower_flag=false. + NodeCards first{}; + first.lower_bound = 0; + first.upper_bound = 0; + first.best_move_suit = best_suit; + first.best_move_rank = best_rank; + std::memset(first.least_win, 0, sizeof(first.least_win)); + + unsigned short win_ranks[DDS_SUITS] = {}; + // flag=true stores best-move hint; false would clear it. + ctx_->trans_table()->add( + tricks_, hand_, pos_.aggr, win_ranks, first, /*flag*/ true); + } + + std::unique_ptr ctx_; + Pos pos_{}; + const int depth_ = 20; // tricks = 5; also valid for the late (depth < 20) path + const int tricks_ = 5; + const int hand_ = 0; + const int target_ = 1; }; } // namespace TEST_F(AbTtLookupTest, MissReturnsFalse) { - bool score_flag = true; - EXPECT_FALSE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); + bool score_flag = true; + EXPECT_FALSE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); } TEST_F(AbTtLookupTest, HitReturnsScoreForMaxNode) { - SeedTtEntry(/*suit*/ 0, /*rank*/ 0); + SeedTtEntry(/*suit*/ 0, /*rank*/ 0); - bool score_flag = true; - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); - // upper_bound <= limit → lower_flag false → MAXNODE score is false. - EXPECT_FALSE(score_flag); + bool score_flag = true; + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); + // upper_bound <= limit → lower_flag false → MAXNODE score is false. + EXPECT_FALSE(score_flag); } TEST_F(AbTtLookupTest, HitUpdatesBestMoveTtWhenRankNonZero) { - SeedTtEntry(/*suit*/ 2, /*rank*/ 14); + SeedTtEntry(/*suit*/ 2, /*rank*/ 14); - bool score_flag = false; - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); + bool score_flag = false; + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); - EXPECT_EQ(ctx_->search().best_move_tt(depth_).suit, 2); - EXPECT_EQ(ctx_->search().best_move_tt(depth_).rank, 14); + EXPECT_EQ(ctx_->search().best_move_tt(depth_).suit, 2); + EXPECT_EQ(ctx_->search().best_move_tt(depth_).rank, 14); } TEST_F(AbTtLookupTest, HitInvertsScoreForMinNode) { - ctx_->search().node_type_store(0) = MINNODE; - SeedTtEntry(/*suit*/ 0, /*rank*/ 0); + ctx_->search().node_type_store(0) = MINNODE; + SeedTtEntry(/*suit*/ 0, /*rank*/ 0); - bool max_score = false; - ctx_->search().node_type_store(0) = MAXNODE; - // Re-seed is unnecessary; same entry. Lookup twice with different node types. - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, max_score)); + bool max_score = false; + ctx_->search().node_type_store(0) = MAXNODE; + // Re-seed is unnecessary; same entry. Lookup twice with different node types. + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, max_score)); - bool min_score = false; - ctx_->search().node_type_store(0) = MINNODE; - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, min_score)); + bool min_score = false; + ctx_->search().node_type_store(0) = MINNODE; + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, min_score)); - EXPECT_NE(max_score, min_score); + EXPECT_NE(max_score, min_score); } TEST_F(AbTtLookupTest, WorksForShallowDepthPath) { - // Same helper is used for depth < 20; ensure a shallow depth still hits. - const int shallow_depth = 16; - pos_.first[shallow_depth] = hand_; - SeedTtEntry(/*suit*/ 1, /*rank*/ 13); - - bool score_flag = false; - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, shallow_depth, tricks_, hand_, *ctx_, score_flag)); - EXPECT_EQ(ctx_->search().best_move_tt(shallow_depth).suit, 1); - EXPECT_EQ(ctx_->search().best_move_tt(shallow_depth).rank, 13); + // Same helper is used for depth < 20; ensure a shallow depth still hits. + const int shallow_depth = 16; + pos_.first[shallow_depth] = hand_; + SeedTtEntry(/*suit*/ 1, /*rank*/ 13); + + bool score_flag = false; + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, shallow_depth, tricks_, hand_, *ctx_, score_flag)); + EXPECT_EQ(ctx_->search().best_move_tt(shallow_depth).suit, 1); + EXPECT_EQ(ctx_->search().best_move_tt(shallow_depth).rank, 13); } #ifdef DDS_AB_STATS TEST_F(AbTtLookupTest, HitCountsMainLookup) { - // AB_COUNT(AB_MAIN_LOOKUP, ...) must resolve thrp from the SolverContext. - SeedTtEntry(/*suit*/ 0, /*rank*/ 0); - ThreadData* thrp = ctx_->thread_ptr(); - ASSERT_NE(thrp, nullptr); - thrp->ABStats.Reset(); - thrp->ABStats.ResetCum(); - - bool score_flag = false; - ASSERT_TRUE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); - - EXPECT_EQ(thrp->ABStats.GetPosCount(AB_MAIN_LOOKUP), 1); + // AB_COUNT(AB_MAIN_LOOKUP, ...) must resolve thrp from the SolverContext. + SeedTtEntry(/*suit*/ 0, /*rank*/ 0); + ThreadData* thrp = ctx_->thread_ptr(); + ASSERT_NE(thrp, nullptr); + thrp->ABStats.Reset(); + thrp->ABStats.ResetCum(); + + bool score_flag = false; + ASSERT_TRUE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); + + EXPECT_EQ(thrp->ABStats.GetPosCount(AB_MAIN_LOOKUP), 1); } TEST_F(AbTtLookupTest, MissDoesNotCountMainLookup) { - ThreadData* thrp = ctx_->thread_ptr(); - ASSERT_NE(thrp, nullptr); - thrp->ABStats.Reset(); - thrp->ABStats.ResetCum(); + ThreadData* thrp = ctx_->thread_ptr(); + ASSERT_NE(thrp, nullptr); + thrp->ABStats.Reset(); + thrp->ABStats.ResetCum(); - bool score_flag = true; - ASSERT_FALSE(apply_ab_tt_lookup( - &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); + bool score_flag = true; + ASSERT_FALSE(apply_ab_tt_lookup( + &pos_, target_, depth_, tricks_, hand_, *ctx_, score_flag)); - EXPECT_EQ(thrp->ABStats.GetPosCount(AB_MAIN_LOOKUP), 0); + EXPECT_EQ(thrp->ABStats.GetPosCount(AB_MAIN_LOOKUP), 0); } #endif diff --git a/library/tests/args.cpp b/library/tests/args.cpp index 99060f79b..07b2853ad 100644 --- a/library/tests/args.cpp +++ b/library/tests/args.cpp @@ -38,45 +38,45 @@ extern OptionsType options; struct optEntry { - string shortName; - string longName; - unsigned numArgs; + string shortName; + string longName; + unsigned numArgs; }; constexpr int DTEST_NUM_OPTIONS = 5; enum DtestOpt { - OPT_FILE = 0, - OPT_SOLVER = 1, - OPT_NUMTHR = 2, - OPT_MEMORY = 3, - OPT_REPORT = 4 + OPT_FILE = 0, + OPT_SOLVER = 1, + OPT_NUMTHR = 2, + OPT_MEMORY = 3, + OPT_REPORT = 4 }; const optEntry optList[DTEST_NUM_OPTIONS] = { - {"f", "file", 1}, - {"s", "solver", 1}, - {"n", "numthr", 1}, - {"m", "memory", 1}, - {"r", "report", 0} + {"f", "file", 1}, + {"s", "solver", 1}, + {"n", "numthr", 1}, + {"m", "memory", 1}, + {"r", "report", 0} }; const vector solverList = { - "solve", - "calc", - "play", - "par", - "dealerpar" + "solve", + "calc", + "play", + "par", + "dealerpar" }; string shortOptsAll, shortOptsWithArg; int GetNextArgToken( - int argc, - char * argv[]); + int argc, + char * argv[]); void SetDefaults(); @@ -84,39 +84,39 @@ bool ParseRound(); void usage( - const char base[]) + const char base[]) { - const string basename = fs::path(base).filename().string(); - - cout << - "Usage: " << basename << " [options]\n\n" << - "-f, --file s Input file, or the number n;\n" << - " Relative paths (and '100' → hands/list100.txt) are\n" << - " resolved under the current directory, then under\n" << - " BUILD_WORKING_DIRECTORY / BUILD_WORKSPACE_DIRECTORY\n" << - " (bazel run), else under the workspace root inferred\n" << - " from the dtest binary (bazel-bin/library/tests/).\n" << - " (Default: input.txt)\n" << - "\n" << - "-s, --solver One of: solve, calc, play, par, dealerpar.\n" << - " (Default: solve)\n" << - "\n" << - "-n, --numthr n Worker threads for solve/calc/play batches.\n" << - " 0 = auto (hardware concurrency), 1 = sequential.\n" << - " (Default: 0)\n" << - "\n" << - "-m, --memory n Total DDS memory size in MB (legacy option).\n" << - " (Default: 0 uses DDS/library defaults; when using\n" << - " the modern SolverContext API, prefer configuring\n" << - " memory via SolverConfig instead of this option.)\n" << - "\n" << - "-r, --report Print per-deal timings in ms (two decimals) for every\n" << - " hand in the input (solve and calc modes), longest\n" << - " first, plus a min/max/mean/median/stddev summary.\n" << - " For calc, each deal time is the sum of its strain-\n" << - " board solve times.\n" << - "\n" << - endl; + const string basename = fs::path(base).filename().string(); + + cout << + "Usage: " << basename << " [options]\n\n" << + "-f, --file s Input file, or the number n;\n" << + " Relative paths (and '100' → hands/list100.txt) are\n" << + " resolved under the current directory, then under\n" << + " BUILD_WORKING_DIRECTORY / BUILD_WORKSPACE_DIRECTORY\n" << + " (bazel run), else under the workspace root inferred\n" << + " from the dtest binary (bazel-bin/library/tests/).\n" << + " (Default: input.txt)\n" << + "\n" << + "-s, --solver One of: solve, calc, play, par, dealerpar.\n" << + " (Default: solve)\n" << + "\n" << + "-n, --numthr n Worker threads for solve/calc/play batches.\n" << + " 0 = auto (hardware concurrency), 1 = sequential.\n" << + " (Default: 0)\n" << + "\n" << + "-m, --memory n Total DDS memory size in MB (legacy option).\n" << + " (Default: 0 uses DDS/library defaults; when using\n" << + " the modern SolverContext API, prefer configuring\n" << + " memory via SolverConfig instead of this option.)\n" << + "\n" << + "-r, --report Print per-deal timings in ms (two decimals) for every\n" << + " hand in the input (solve and calc modes), longest\n" << + " first, plus a min/max/mean/median/stddev summary.\n" << + " For calc, each deal time is the sum of its strain-\n" << + " board solve times.\n" << + "\n" << + endl; } @@ -124,62 +124,62 @@ int nextToken = 1; char * optarg; int GetNextArgToken( - int argc, - char * argv[]) + int argc, + char * argv[]) { - // 0 means done, -1 means error. + // 0 means done, -1 means error. - if (nextToken >= argc) - return 0; + if (nextToken >= argc) + return 0; - string str(argv[nextToken]); - if (str[0] != '-' || str.size() == 1) - return -1; + string str(argv[nextToken]); + if (str[0] != '-' || str.size() == 1) + return -1; - if (str[1] == '-') - { - if (str.size() == 2) - return -1; - str.erase(0, 2); - } - else if (str.size() == 2) - str.erase(0, 1); - else - return -1; + if (str[1] == '-') + { + if (str.size() == 2) + return -1; + str.erase(0, 2); + } + else if (str.size() == 2) + str.erase(0, 1); + else + return -1; - for (unsigned i = 0; i < DTEST_NUM_OPTIONS; i++) - { - const bool short_ok = - !optList[i].shortName.empty() && str == optList[i].shortName; - if (short_ok || str == optList[i].longName) + for (unsigned i = 0; i < DTEST_NUM_OPTIONS; i++) { - if (optList[i].numArgs == 1) - { - if (nextToken+1 >= argc) - return -1; - - optarg = argv[nextToken+1]; - nextToken += 2; - } - else - nextToken++; - - // Return 1-based option index so 0 can mean "done". - return static_cast(i) + 1; + const bool short_ok = + !optList[i].shortName.empty() && str == optList[i].shortName; + if (short_ok || str == optList[i].longName) + { + if (optList[i].numArgs == 1) + { + if (nextToken+1 >= argc) + return -1; + + optarg = argv[nextToken+1]; + nextToken += 2; + } + else + nextToken++; + + // Return 1-based option index so 0 can mean "done". + return static_cast(i) + 1; + } } - } - return -1; + return -1; } void SetDefaults() { - options.fname_ = "input.txt"; - options.solver_ = Solver::DTEST_SOLVER_SOLVE; - options.num_threads_ = 0; - options.memory_mb_ = 0; - options.report_slow_boards_ = false; + options.fname_ = "input.txt"; + options.solver_ = Solver::DTEST_SOLVER_SOLVE; + options.num_threads_ = 0; + options.memory_mb_ = 0; + options.report_slow_boards_ = false; } @@ -189,59 +189,59 @@ namespace #ifdef _WIN32 bool is_unc_path(const string& path) { - if (path.size() < 5) - return false; - const bool unc_slash = - (path[0] == '\\' && path[1] == '\\') || - (path[0] == '/' && path[1] == '/'); - if (!unc_slash) - return false; - - size_t i = 2; - while (i < path.size() && (path[i] == '\\' || path[i] == '/')) - ++i; - if (i >= path.size()) - return false; - - const size_t server_start = i; - while (i < path.size() && path[i] != '\\' && path[i] != '/') - ++i; - if (i == server_start || i >= path.size()) - return false; - - while (i < path.size() && (path[i] == '\\' || path[i] == '/')) - ++i; - if (i >= path.size()) - return false; - - const size_t share_start = i; - while (i < path.size() && path[i] != '\\' && path[i] != '/') - ++i; - return i > share_start; + if (path.size() < 5) + return false; + const bool unc_slash = + (path[0] == '\\' && path[1] == '\\') || + (path[0] == '/' && path[1] == '/'); + if (!unc_slash) + return false; + + size_t i = 2; + while (i < path.size() && (path[i] == '\\' || path[i] == '/')) + ++i; + if (i >= path.size()) + return false; + + const size_t server_start = i; + while (i < path.size() && path[i] != '\\' && path[i] != '/') + ++i; + if (i == server_start || i >= path.size()) + return false; + + while (i < path.size() && (path[i] == '\\' || path[i] == '/')) + ++i; + if (i >= path.size()) + return false; + + const size_t share_start = i; + while (i < path.size() && path[i] != '\\' && path[i] != '/') + ++i; + return i > share_start; } #endif bool is_absolute_path(const string& path) { - if (path.empty()) - return false; + if (path.empty()) + return false; #ifdef _WIN32 - if (is_unc_path(path)) - return true; - // Current-drive rooted: "\foo" or "/foo" (single leading separator, not UNC). - if ((path[0] == '\\' || path[0] == '/') && - (path.size() == 1 || (path[1] != '\\' && path[1] != '/'))) - { - return true; - } - // Drive-rooted absolute: "C:\..." or "C:/...". "C:foo" is drive-relative. - return path.size() >= 3 && - std::isalpha(static_cast(path[0])) && - path[1] == ':' && - (path[2] == '\\' || path[2] == '/'); + if (is_unc_path(path)) + return true; + // Current-drive rooted: "\foo" or "/foo" (single leading separator, not UNC). + if ((path[0] == '\\' || path[0] == '/') && + (path.size() == 1 || (path[1] != '\\' && path[1] != '/'))) + { + return true; + } + // Drive-rooted absolute: "C:\..." or "C:/...". "C:foo" is drive-relative. + return path.size() >= 3 && + std::isalpha(static_cast(path[0])) && + path[1] == ':' && + (path[2] == '\\' || path[2] == '/'); #else - return path[0] == '/'; + return path[0] == '/'; #endif } @@ -249,18 +249,18 @@ bool is_absolute_path(const string& path) // Collapse "." / ".." path segments without resolving symlinks. string normalize_logical_path(const string& path) { - if (path.empty()) - return path; - return fs::path(path).lexically_normal().make_preferred().string(); + if (path.empty()) + return path; + return fs::path(path).lexically_normal().make_preferred().string(); } bool path_exists(const fs::path& path) { - std::error_code ec; - // -f / resolve_dtest_input_file expect a readable input *file*; directories - // must not count (exists() is true for them and leads to a later parse error). - return fs::is_regular_file(path, ec); + std::error_code ec; + // -f / resolve_dtest_input_file expect a readable input *file*; directories + // must not count (exists() is true for them and leads to a later parse error). + return fs::is_regular_file(path, ec); } @@ -268,63 +268,63 @@ bool path_exists(const fs::path& path) // of bazel-bin still lands on the workspace, not the execroot). string absolute_path_logical(const string& path) { - std::error_code ec; - const fs::path cwd = fs::current_path(ec); + std::error_code ec; + const fs::path cwd = fs::current_path(ec); - if (is_absolute_path(path)) - { -#ifdef _WIN32 - // Current-drive rooted "\foo" → "X:\foo" using the cwd drive letter. - // std::filesystem treats these as relative (no root-name), so handle here. - if ((path[0] == '\\' || path[0] == '/') && - (path.size() == 1 || (path[1] != '\\' && path[1] != '/'))) + if (is_absolute_path(path)) { - if (!ec && cwd.has_root_name()) - return normalize_logical_path(cwd.root_name().string() + path); - } +#ifdef _WIN32 + // Current-drive rooted "\foo" → "X:\foo" using the cwd drive letter. + // std::filesystem treats these as relative (no root-name), so handle here. + if ((path[0] == '\\' || path[0] == '/') && + (path.size() == 1 || (path[1] != '\\' && path[1] != '/'))) + { + if (!ec && cwd.has_root_name()) + return normalize_logical_path(cwd.root_name().string() + path); + } #endif - return normalize_logical_path(path); - } + return normalize_logical_path(path); + } - if (ec) - return normalize_logical_path(path); + if (ec) + return normalize_logical_path(path); #ifdef _WIN32 - // Drive-relative "C:foo": resolve against cwd when cwd is on the same drive. - if (path.size() >= 2 && - std::isalpha(static_cast(path[0])) && - path[1] == ':') - { - const string cwd_s = cwd.string(); - if (cwd_s.size() >= 2 && - std::tolower(static_cast(cwd_s[0])) == - std::tolower(static_cast(path[0])) && - cwd_s[1] == ':') + // Drive-relative "C:foo": resolve against cwd when cwd is on the same drive. + if (path.size() >= 2 && + std::isalpha(static_cast(path[0])) && + path[1] == ':') { - return normalize_logical_path((cwd / path.substr(2)).string()); + const string cwd_s = cwd.string(); + if (cwd_s.size() >= 2 && + std::tolower(static_cast(cwd_s[0])) == + std::tolower(static_cast(path[0])) && + cwd_s[1] == ':') + { + return normalize_logical_path((cwd / path.substr(2)).string()); + } + return normalize_logical_path(path); } - return normalize_logical_path(path); - } #endif - if (path.empty()) - return normalize_logical_path(cwd.string()); - return normalize_logical_path((cwd / path).string()); + if (path.empty()) + return normalize_logical_path(cwd.string()); + return normalize_logical_path((cwd / path).string()); } bool is_dtest_list_shorthand_arg(const string& arg) { - if (arg.empty()) - return false; - for (unsigned char c : arg) - { - if (c == '/' || c == '\\') - return false; - if (!std::isdigit(c)) - return false; - } - return true; + if (arg.empty()) + return false; + for (unsigned char c : arg) + { + if (c == '/' || c == '\\') + return false; + if (!std::isdigit(c)) + return false; + } + return true; } } // namespace @@ -332,258 +332,258 @@ bool is_dtest_list_shorthand_arg(const string& arg) bool is_dtest_absolute_path(const string& path) { - return is_absolute_path(path); + return is_absolute_path(path); } string resolve_dtest_input_file( - const string& arg, - const string& argv0) + const string& arg, + const string& argv0) { - if (path_exists(arg)) - return arg; - if (is_absolute_path(arg)) - return string(); - // Windows drive-relative "C:foo" has a root-name; fs::path join may discard - // the BUILD_* / argv0 base. Only the literal path is attempted. - if (fs::path(arg).has_root_name()) - return string(); + if (path_exists(arg)) + return arg; + if (is_absolute_path(arg)) + return string(); + // Windows drive-relative "C:foo" has a root-name; fs::path join may discard + // the BUILD_* / argv0 base. Only the literal path is attempted. + if (fs::path(arg).has_root_name()) + return string(); + + const bool use_list_shorthand = is_dtest_list_shorthand_arg(arg); + const string list_name = use_list_shorthand ? "list" + arg + ".txt" : string(); + + if (use_list_shorthand) + { + // Keep generic separators so cwd hits match the documented hands/listN.txt form. + const string cwd_candidate = + (fs::path("hands") / list_name).generic_string(); + if (path_exists(cwd_candidate)) + return cwd_candidate; + } - const bool use_list_shorthand = is_dtest_list_shorthand_arg(arg); - const string list_name = use_list_shorthand ? "list" + arg + ".txt" : string(); - - if (use_list_shorthand) - { - // Keep generic separators so cwd hits match the documented hands/listN.txt form. - const string cwd_candidate = - (fs::path("hands") / list_name).generic_string(); - if (path_exists(cwd_candidate)) - return cwd_candidate; - } - - // bazel run moves CWD into the runfiles tree; it exports the invoke-time - // shell cwd and the workspace root so relative -f paths still resolve. - auto from_env_dir = [&](const char* env_name, const fs::path& rel) -> string - { - if (rel.has_root_name() || rel.has_root_directory()) - return string(); - const char* dir = std::getenv(env_name); - if (dir == nullptr || dir[0] == '\0') - return string(); - const string candidate = - normalize_logical_path((fs::path(dir) / rel).string()); - if (path_exists(candidate)) - return candidate; - return string(); - }; - - // Climb parents in the path *string* (do not use "/../" with filesystem - // resolution — that follows a bazel-bin symlink into the execroot and misses - // the workspace hands/ directory). bazel-bin/library/tests/dtest → four - // parent_path steps to the repo root. - auto workspace_root_from_argv0 = [&]() -> fs::path - { - fs::path dir(absolute_path_logical(argv0)); - for (unsigned i = 0; i < 4; ++i) + // bazel run moves CWD into the runfiles tree; it exports the invoke-time + // shell cwd and the workspace root so relative -f paths still resolve. + auto from_env_dir = [&](const char* env_name, const fs::path& rel) -> string + { + if (rel.has_root_name() || rel.has_root_directory()) + return string(); + const char* dir = std::getenv(env_name); + if (dir == nullptr || dir[0] == '\0') + return string(); + const string candidate = + normalize_logical_path((fs::path(dir) / rel).string()); + if (path_exists(candidate)) + return candidate; + return string(); + }; + + // Climb parents in the path *string* (do not use "/../" with filesystem + // resolution — that follows a bazel-bin symlink into the execroot and misses + // the workspace hands/ directory). bazel-bin/library/tests/dtest → four + // parent_path steps to the repo root. + auto workspace_root_from_argv0 = [&]() -> fs::path { - const fs::path parent = dir.parent_path(); - if (parent.empty()) - return {}; - dir = parent; + fs::path dir(absolute_path_logical(argv0)); + for (unsigned i = 0; i < 4; ++i) + { + const fs::path parent = dir.parent_path(); + if (parent.empty()) + return {}; + dir = parent; + } + return dir; + }; + + if (use_list_shorthand) + { + const fs::path list_rel = fs::path("hands") / list_name; + if (const string found = + from_env_dir("BUILD_WORKING_DIRECTORY", list_rel); !found.empty()) + { + return found; + } + if (const string found = + from_env_dir("BUILD_WORKSPACE_DIRECTORY", list_rel); !found.empty()) + { + return found; + } } - return dir; - }; - if (use_list_shorthand) - { - const fs::path list_rel = fs::path("hands") / list_name; + // Prefer list shorthand under bazel dirs / argv0 before a bare relative + // name (so -f 42 keeps resolving to hands/list42.txt even if a file named + // "42" exists at the workspace root). if (const string found = - from_env_dir("BUILD_WORKING_DIRECTORY", list_rel); !found.empty()) + from_env_dir("BUILD_WORKING_DIRECTORY", arg); !found.empty()) { - return found; + return found; } if (const string found = - from_env_dir("BUILD_WORKSPACE_DIRECTORY", list_rel); !found.empty()) + from_env_dir("BUILD_WORKSPACE_DIRECTORY", arg); !found.empty()) { - return found; + return found; } - } - - // Prefer list shorthand under bazel dirs / argv0 before a bare relative - // name (so -f 42 keeps resolving to hands/list42.txt even if a file named - // "42" exists at the workspace root). - if (const string found = - from_env_dir("BUILD_WORKING_DIRECTORY", arg); !found.empty()) - { - return found; - } - if (const string found = - from_env_dir("BUILD_WORKSPACE_DIRECTORY", arg); !found.empty()) - { - return found; - } - - const fs::path root = workspace_root_from_argv0(); - if (root.empty()) - return string(); - if (use_list_shorthand) - { - const string bin_candidate = - normalize_logical_path((root / "hands" / list_name).string()); - if (path_exists(bin_candidate)) - return bin_candidate; - } + const fs::path root = workspace_root_from_argv0(); + if (root.empty()) + return string(); - const string bin_literal = - normalize_logical_path((root / arg).string()); - if (path_exists(bin_literal)) - return bin_literal; + if (use_list_shorthand) + { + const string bin_candidate = + normalize_logical_path((root / "hands" / list_name).string()); + if (path_exists(bin_candidate)) + return bin_candidate; + } - return string(); + const string bin_literal = + normalize_logical_path((root / arg).string()); + if (path_exists(bin_literal)) + return bin_literal; + + return string(); } void print_options() { - cout << left; - cout << setw(12) << "file" << - setw(12) << options.fname_ << "\n"; - cout << setw(12) << "solver" << setw(12) << - solverList[static_cast(options.solver_)] << "\n"; - cout << setw(12) << "threads" << setw(12) << - options.num_threads_ << "\n"; - cout << setw(12) << "memory" << setw(12) << - options.memory_mb_ << " MB\n"; - cout << "\n" << right; + cout << left; + cout << setw(12) << "file" << + setw(12) << options.fname_ << "\n"; + cout << setw(12) << "solver" << setw(12) << + solverList[static_cast(options.solver_)] << "\n"; + cout << setw(12) << "threads" << setw(12) << + options.num_threads_ << "\n"; + cout << setw(12) << "memory" << setw(12) << + options.memory_mb_ << " MB\n"; + cout << "\n" << right; } void read_args( - int argc, - char * argv[]) + int argc, + char * argv[]) { - nextToken = 1; - shortOptsAll.clear(); - shortOptsWithArg.clear(); - - for (unsigned i = 0; i < DTEST_NUM_OPTIONS; i++) - { - shortOptsAll += optList[i].shortName; - if (optList[i].numArgs) - shortOptsWithArg += optList[i].shortName; - } - - if (argc == 1) - { - usage(argv[0]); - exit(0); - } - - SetDefaults(); - - int c, m = 0; - bool errFlag = false, matchFlag; - string stmp; - char * ctmp; - - while ((c = GetNextArgToken(argc, argv)) > 0) - { - switch(c - 1) + nextToken = 1; + shortOptsAll.clear(); + shortOptsWithArg.clear(); + + for (unsigned i = 0; i < DTEST_NUM_OPTIONS; i++) { - case OPT_FILE: - { - const string resolved = - resolve_dtest_input_file(string(optarg), string(argv[0])); - if (!resolved.empty()) - { - options.fname_ = resolved; - break; - } + shortOptsAll += optList[i].shortName; + if (optList[i].numArgs) + shortOptsWithArg += optList[i].shortName; + } - cout << "Input file '" << optarg << "' not found\n"; - // Absolute / drive-relative args only attempt the literal path. - if (!is_dtest_absolute_path(optarg) && - !fs::path(optarg).has_root_name()) - { - cout << "Also tried that path under the current directory, " - "BUILD_WORKING_DIRECTORY, BUILD_WORKSPACE_DIRECTORY, " - "and under the workspace root inferred from the dtest binary; " - "for numeric -f N, also hands/listN.txt\n"; - } - nextToken -= 2; - errFlag = true; - break; - } - - case OPT_SOLVER: - matchFlag = false; - stmp = optarg; - transform(stmp.begin(), stmp.end(), stmp.begin(), - [](unsigned char c) { return static_cast(::tolower(c)); }); - - for (unsigned i = 0; i < static_cast(Solver::DTEST_SOLVER_SIZE) && ! matchFlag; i++) - { - string s = solverList[i]; - transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return static_cast(::tolower(c)); }); - if (stmp == s) - { - m = static_cast(i); - matchFlag = true; - } - } + if (argc == 1) + { + usage(argv[0]); + exit(0); + } - if (matchFlag) - options.solver_ = static_cast(m); - else - { - cout << "Solver '" << optarg << "' not found\n"; - nextToken -= 2; - errFlag = true; - } - break; + SetDefaults(); - case OPT_NUMTHR: - m = static_cast(strtol(optarg, &ctmp, 0)); - if (m < 0) - { - cout << "Number of threads must be >= 0\n\n"; - nextToken -= 2; - errFlag = true; - } - options.num_threads_ = m; - break; + int c, m = 0; + bool errFlag = false, matchFlag; + string stmp; + char * ctmp; - case OPT_MEMORY: - m = static_cast(strtol(optarg, &ctmp, 0)); - if (m < 0) + while ((c = GetNextArgToken(argc, argv)) > 0) + { + switch(c - 1) { - cout << "Memory in MB must be >= 0\n\n"; - nextToken -= 2; - errFlag = true; + case OPT_FILE: + { + const string resolved = + resolve_dtest_input_file(string(optarg), string(argv[0])); + if (!resolved.empty()) + { + options.fname_ = resolved; + break; + } + + cout << "Input file '" << optarg << "' not found\n"; + // Absolute / drive-relative args only attempt the literal path. + if (!is_dtest_absolute_path(optarg) && + !fs::path(optarg).has_root_name()) + { + cout << "Also tried that path under the current directory, " + "BUILD_WORKING_DIRECTORY, BUILD_WORKSPACE_DIRECTORY, " + "and under the workspace root inferred from the dtest binary; " + "for numeric -f N, also hands/listN.txt\n"; + } + nextToken -= 2; + errFlag = true; + break; + } + + case OPT_SOLVER: + matchFlag = false; + stmp = optarg; + transform(stmp.begin(), stmp.end(), stmp.begin(), + [](unsigned char c) { return static_cast(::tolower(c)); }); + + for (unsigned i = 0; i < static_cast(Solver::DTEST_SOLVER_SIZE) && ! matchFlag; i++) + { + string s = solverList[i]; + transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return static_cast(::tolower(c)); }); + if (stmp == s) + { + m = static_cast(i); + matchFlag = true; + } + } + + if (matchFlag) + options.solver_ = static_cast(m); + else + { + cout << "Solver '" << optarg << "' not found\n"; + nextToken -= 2; + errFlag = true; + } + break; + + case OPT_NUMTHR: + m = static_cast(strtol(optarg, &ctmp, 0)); + if (m < 0) + { + cout << "Number of threads must be >= 0\n\n"; + nextToken -= 2; + errFlag = true; + } + options.num_threads_ = m; + break; + + case OPT_MEMORY: + m = static_cast(strtol(optarg, &ctmp, 0)); + if (m < 0) + { + cout << "Memory in MB must be >= 0\n\n"; + nextToken -= 2; + errFlag = true; + } + options.memory_mb_ = m; + break; + + case OPT_REPORT: + options.report_slow_boards_ = true; + break; + + default: + cout << "Unknown option\n"; + errFlag = true; + break; } - options.memory_mb_ = m; - break; - - case OPT_REPORT: - options.report_slow_boards_ = true; - break; + if (errFlag) + break; + } - default: - cout << "Unknown option\n"; - errFlag = true; - break; + if (errFlag || c == -1) + { + cout << "Error while parsing option '" << argv[nextToken] << "'\n"; + cout << "Invoke the program without arguments for help" << endl; + exit(0); } - if (errFlag) - break; - } - - if (errFlag || c == -1) - { - cout << "Error while parsing option '" << argv[nextToken] << "'\n"; - cout << "Invoke the program without arguments for help" << endl; - exit(0); - } } diff --git a/library/tests/args_test.cpp b/library/tests/args_test.cpp index 1ac1ad2d3..3352e03c8 100644 --- a/library/tests/args_test.cpp +++ b/library/tests/args_test.cpp @@ -32,40 +32,40 @@ namespace #ifdef _WIN32 int change_dir(const char* path) { - return _chdir(path); + return _chdir(path); } std::string current_dir() { - std::vector buf(4096); - if (_getcwd(buf.data(), static_cast(buf.size())) == nullptr) - return {}; - return buf.data(); + std::vector buf(4096); + if (_getcwd(buf.data(), static_cast(buf.size())) == nullptr) + return {}; + return buf.data(); } #else int change_dir(const char* path) { - return chdir(path); + return chdir(path); } std::string current_dir() { - std::vector buf(4096); - if (getcwd(buf.data(), buf.size()) == nullptr) - return {}; - return buf.data(); + std::vector buf(4096); + if (getcwd(buf.data(), buf.size()) == nullptr) + return {}; + return buf.data(); } #endif bool path_is_directory(const std::string& path) { - struct stat st; - if (stat(path.c_str(), &st) != 0) - return false; + struct stat st; + if (stat(path.c_str(), &st) != 0) + return false; #ifdef _WIN32 - return (st.st_mode & S_IFMT) == S_IFDIR; + return (st.st_mode & S_IFMT) == S_IFDIR; #else - return S_ISDIR(st.st_mode); + return S_ISDIR(st.st_mode); #endif } @@ -74,24 +74,24 @@ bool path_is_directory(const std::string& path) bool make_dir(const std::string& path) { #ifdef _WIN32 - if (_mkdir(path.c_str()) == 0) - return true; + if (_mkdir(path.c_str()) == 0) + return true; #else - if (mkdir(path.c_str(), 0755) == 0) - return true; + if (mkdir(path.c_str(), 0755) == 0) + return true; #endif - return path_is_directory(path); + return path_is_directory(path); } void set_env_var(const char* name, const char* value) { #ifdef _WIN32 - _putenv_s(name, value != nullptr ? value : ""); + _putenv_s(name, value != nullptr ? value : ""); #else - if (value == nullptr || value[0] == '\0') - unsetenv(name); - else - setenv(name, value, 1); + if (value == nullptr || value[0] == '\0') + unsetenv(name); + else + setenv(name, value, 1); #endif } @@ -99,62 +99,62 @@ void set_env_var(const char* name, const char* value) class EnvVarGuard { public: - explicit EnvVarGuard(const char* name) - : name_(name) - { - const char* prev = std::getenv(name_); - if (prev != nullptr) + explicit EnvVarGuard(const char* name) + : name_(name) { - had_value_ = true; - previous_ = prev; + const char* prev = std::getenv(name_); + if (prev != nullptr) + { + had_value_ = true; + previous_ = prev; + } } - } - ~EnvVarGuard() - { - if (had_value_) - set_env_var(name_, previous_.c_str()); - else - set_env_var(name_, nullptr); - } + ~EnvVarGuard() + { + if (had_value_) + set_env_var(name_, previous_.c_str()); + else + set_env_var(name_, nullptr); + } - EnvVarGuard(const EnvVarGuard&) = delete; - auto operator=(const EnvVarGuard&) -> EnvVarGuard& = delete; + EnvVarGuard(const EnvVarGuard&) = delete; + auto operator=(const EnvVarGuard&) -> EnvVarGuard& = delete; - void set(const char* value) const - { - set_env_var(name_, value); - } + void set(const char* value) const + { + set_env_var(name_, value); + } private: - const char* name_; - bool had_value_ = false; - std::string previous_; + const char* name_; + bool had_value_ = false; + std::string previous_; }; std::string make_temp_input_file() { - const std::string path = - std::string(::testing::TempDir()) + "args_test_input.txt"; - std::ofstream out(path); - out << "placeholder\n"; - return path; + const std::string path = + std::string(::testing::TempDir()) + "args_test_input.txt"; + std::ofstream out(path); + out << "placeholder\n"; + return path; } /// Compare paths ignoring '\\' vs '/' so Windows TempDir / resolver mixes match. bool same_path(std::string a, std::string b) { - for (char& c : a) - { - if (c == '\\') - c = '/'; - } - for (char& c : b) - { - if (c == '\\') - c = '/'; - } - return a == b; + for (char& c : a) + { + if (c == '\\') + c = '/'; + } + for (char& c : b) + { + if (c == '\\') + c = '/'; + } + return a == b; } /// Temporary tree: `{root}/hands/list{N}.txt` and @@ -162,451 +162,451 @@ bool same_path(std::string a, std::string b) class HandsLayoutFixture : public ::testing::Test { protected: - void SetUp() override - { - original_cwd_ = current_dir(); - ASSERT_FALSE(original_cwd_.empty()); - - root_ = std::string(::testing::TempDir()) + "dtest_hands_layout/"; - ASSERT_TRUE(make_dir(root_)); - ASSERT_TRUE(make_dir(root_ + "hands")); - ASSERT_TRUE(make_dir(root_ + "bazel-bin")); - ASSERT_TRUE(make_dir(root_ + "bazel-bin/library")); - ASSERT_TRUE(make_dir(root_ + "bazel-bin/library/tests")); - + void SetUp() override { - std::ofstream out(root_ + "hands/list42.txt"); - out << "placeholder\n"; + original_cwd_ = current_dir(); + ASSERT_FALSE(original_cwd_.empty()); + + root_ = std::string(::testing::TempDir()) + "dtest_hands_layout/"; + ASSERT_TRUE(make_dir(root_)); + ASSERT_TRUE(make_dir(root_ + "hands")); + ASSERT_TRUE(make_dir(root_ + "bazel-bin")); + ASSERT_TRUE(make_dir(root_ + "bazel-bin/library")); + ASSERT_TRUE(make_dir(root_ + "bazel-bin/library/tests")); + + { + std::ofstream out(root_ + "hands/list42.txt"); + out << "placeholder\n"; + } + + binary_path_ = root_ + "bazel-bin/library/tests/dtest"; } - binary_path_ = root_ + "bazel-bin/library/tests/dtest"; - } - - void TearDown() override - { - if (!original_cwd_.empty()) - change_dir(original_cwd_.c_str()); - } + void TearDown() override + { + if (!original_cwd_.empty()) + change_dir(original_cwd_.c_str()); + } - std::string root_; - std::string binary_path_; - std::string original_cwd_; + std::string root_; + std::string binary_path_; + std::string original_cwd_; }; } // namespace TEST(Args, MakeDirSucceedsWhenDirectoryAlreadyExists) { - const std::string dir = - std::string(::testing::TempDir()) + "args_make_dir_ok/"; - ASSERT_TRUE(make_dir(dir)); - EXPECT_TRUE(make_dir(dir)); + const std::string dir = + std::string(::testing::TempDir()) + "args_make_dir_ok/"; + ASSERT_TRUE(make_dir(dir)); + EXPECT_TRUE(make_dir(dir)); } TEST(Args, MakeDirFailsWhenPathIsExistingFile) { - const std::string path = - std::string(::testing::TempDir()) + "args_make_dir_file"; - { - std::ofstream out(path); - out << "not-a-directory\n"; - } - EXPECT_FALSE(make_dir(path)); + const std::string path = + std::string(::testing::TempDir()) + "args_make_dir_file"; + { + std::ofstream out(path); + out << "not-a-directory\n"; + } + EXPECT_FALSE(make_dir(path)); } TEST(Args, UnknownMinMaxFlagsAreRejected) { - const std::string path = make_temp_input_file(); - char arg0[] = "dtest"; - char arg_f[] = "-f"; - char arg_min[] = "--min"; - char arg_max[] = "--max"; - char* argv_min[] = {arg0, arg_f, const_cast(path.c_str()), arg_min}; - char* argv_max[] = {arg0, arg_f, const_cast(path.c_str()), arg_max}; - EXPECT_EXIT(read_args(4, argv_min), ::testing::ExitedWithCode(0), ".*"); - EXPECT_EXIT(read_args(4, argv_max), ::testing::ExitedWithCode(0), ".*"); + const std::string path = make_temp_input_file(); + char arg0[] = "dtest"; + char arg_f[] = "-f"; + char arg_min[] = "--min"; + char arg_max[] = "--max"; + char* argv_min[] = {arg0, arg_f, const_cast(path.c_str()), arg_min}; + char* argv_max[] = {arg0, arg_f, const_cast(path.c_str()), arg_max}; + EXPECT_EXIT(read_args(4, argv_min), ::testing::ExitedWithCode(0), ".*"); + EXPECT_EXIT(read_args(4, argv_max), ::testing::ExitedWithCode(0), ".*"); } TEST(Args, ResolvePrefersLiteralExistingPath) { - const std::string path = make_temp_input_file(); - EXPECT_EQ(resolve_dtest_input_file(path, "dtest"), path); + const std::string path = make_temp_input_file(); + EXPECT_EQ(resolve_dtest_input_file(path, "dtest"), path); } TEST_F(HandsLayoutFixture, ResolveNumericUsesHandsUnderCwd) { - ASSERT_EQ(change_dir(root_.c_str()), 0); - EXPECT_EQ(resolve_dtest_input_file("42", "dtest"), "hands/list42.txt"); + ASSERT_EQ(change_dir(root_.c_str()), 0); + EXPECT_EQ(resolve_dtest_input_file("42", "dtest"), "hands/list42.txt"); } TEST_F(HandsLayoutFixture, ResolveNumericFallsBackRelativeToBinary) { - // CWD has no hands/; argv0 points at the usual bazel-bin layout. - ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", binary_path_), - root_ + "hands/list42.txt")); + // CWD has no hands/; argv0 points at the usual bazel-bin layout. + ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", binary_path_), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericUsesBazelWorkingDirectory) { - // bazel run sets CWD to the runfiles tree (no hands/) and exports - // BUILD_WORKING_DIRECTORY as the invoke-time shell cwd. - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); + // bazel run sets CWD to the runfiles tree (no hands/) and exports + // BUILD_WORKING_DIRECTORY as the invoke-time shell cwd. + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(root_.c_str()); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(root_.c_str()); + workspace.set(nullptr); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", "dtest"), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", "dtest"), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericPrefersListOverLiteralUnderBazelWorking) { - // A workspace-root file named "42" must not win over hands/list42.txt when - // resolving numeric -f under BUILD_WORKING_DIRECTORY. - { - std::ofstream out(root_ + "42"); - out << "literal-trap\n"; - } + // A workspace-root file named "42" must not win over hands/list42.txt when + // resolving numeric -f under BUILD_WORKING_DIRECTORY. + { + std::ofstream out(root_ + "42"); + out << "literal-trap\n"; + } - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles_num_pref/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles_num_pref/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(root_.c_str()); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(root_.c_str()); + workspace.set(nullptr); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", "dtest"), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", "dtest"), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolvePathLikeArgDoesNotUseListShorthand) { - // A path-like -f must not probe nonsense list-shorthand candidates such as - // hands/listhands/list42.txt.txt when the literal path is missing. - const std::string trap = root_ + "hands/listhands/list42.txt.txt"; - ASSERT_TRUE(make_dir(root_ + "hands/listhands")); - { - std::ofstream out(trap); - out << "trap\n"; - } - { - std::error_code ec; - std::filesystem::remove(root_ + "hands/list42.txt", ec); - ASSERT_FALSE(ec) << ec.message(); - } - - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles_no_shorthand/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); - - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(root_.c_str()); - workspace.set(nullptr); - - EXPECT_TRUE( - resolve_dtest_input_file("hands/list42.txt", "dtest").empty()); + // A path-like -f must not probe nonsense list-shorthand candidates such as + // hands/listhands/list42.txt.txt when the literal path is missing. + const std::string trap = root_ + "hands/listhands/list42.txt.txt"; + ASSERT_TRUE(make_dir(root_ + "hands/listhands")); + { + std::ofstream out(trap); + out << "trap\n"; + } + { + std::error_code ec; + std::filesystem::remove(root_ + "hands/list42.txt", ec); + ASSERT_FALSE(ec) << ec.message(); + } + + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles_no_shorthand/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); + + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(root_.c_str()); + workspace.set(nullptr); + + EXPECT_TRUE( + resolve_dtest_input_file("hands/list42.txt", "dtest").empty()); } TEST_F(HandsLayoutFixture, ResolveLiteralRelativeUsesBazelWorkingDirectory) { - // bazelisk run //library/tests:dtest -- -f hands/list42.txt must find the - // path relative to the invoke-time shell cwd, not the runfiles tree. - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles_lit/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); + // bazelisk run //library/tests:dtest -- -f hands/list42.txt must find the + // path relative to the invoke-time shell cwd, not the runfiles tree. + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles_lit/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(root_.c_str()); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(root_.c_str()); + workspace.set(nullptr); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("hands/list42.txt", "dtest"), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("hands/list42.txt", "dtest"), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericUsesBazelWorkspaceDirectory) { - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles_ws/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles_ws/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(root_.c_str()); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(root_.c_str()); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", "dtest"), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", "dtest"), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveLiteralRelativeUsesBazelWorkspaceDirectory) { - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_runfiles_ws_lit/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_runfiles_ws_lit/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(root_.c_str()); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(root_.c_str()); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("hands/list42.txt", "dtest"), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("hands/list42.txt", "dtest"), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveLiteralRelativeFallsBackRelativeToBinary) { - // Same layout as numeric binary-relative lookup, but with an explicit path. - ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); + // Same layout as numeric binary-relative lookup, but with an explicit path. + ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(nullptr); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("hands/list42.txt", binary_path_), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("hands/list42.txt", binary_path_), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericPrefersListOverLiteralRelativeToBinary) { - // Workspace-root file "42" must not shadow hands/list42.txt for numeric -f - // when falling back via argv0. - { - std::ofstream out(root_ + "42"); - out << "literal-trap\n"; - } - ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); + // Workspace-root file "42" must not shadow hands/list42.txt for numeric -f + // when falling back via argv0. + { + std::ofstream out(root_ + "42"); + out << "literal-trap\n"; + } + ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(nullptr); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", binary_path_), - root_ + "hands/list42.txt")); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", binary_path_), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericWithRelativeArgv0FromOtherCwd) { - // Mimic running `../bazel-bin/library/tests/dtest -f 42` from a sibling of - // the repo root: argv0 is relative, CWD is not the repo root. - const std::string sibling = - std::string(::testing::TempDir()) + "dtest_hands_sibling/"; - ASSERT_TRUE(make_dir(sibling)); - ASSERT_EQ(change_dir(sibling.c_str()), 0); + // Mimic running `../bazel-bin/library/tests/dtest -f 42` from a sibling of + // the repo root: argv0 is relative, CWD is not the repo root. + const std::string sibling = + std::string(::testing::TempDir()) + "dtest_hands_sibling/"; + ASSERT_TRUE(make_dir(sibling)); + ASSERT_EQ(change_dir(sibling.c_str()), 0); - // root_ and sibling share the same parent (TempDir), so this relative - // argv0 reaches the fixture binary path. - const std::string rel_argv0 = - "../dtest_hands_layout/bazel-bin/library/tests/dtest"; - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", rel_argv0), - root_ + "hands/list42.txt")); + // root_ and sibling share the same parent (TempDir), so this relative + // argv0 reaches the fixture binary path. + const std::string rel_argv0 = + "../dtest_hands_layout/bazel-bin/library/tests/dtest"; + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", rel_argv0), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericPrefersCwdOverBinaryRelative) { - // A different list under cwd must win even when the binary-relative file - // also exists. - const std::string other_root = - std::string(::testing::TempDir()) + "dtest_hands_cwd_wins/"; - ASSERT_TRUE(make_dir(other_root)); - ASSERT_TRUE(make_dir(other_root + "hands")); - const std::string cwd_file = other_root + "hands/list42.txt"; - { - std::ofstream out(cwd_file); - out << "from-cwd\n"; - } + // A different list under cwd must win even when the binary-relative file + // also exists. + const std::string other_root = + std::string(::testing::TempDir()) + "dtest_hands_cwd_wins/"; + ASSERT_TRUE(make_dir(other_root)); + ASSERT_TRUE(make_dir(other_root + "hands")); + const std::string cwd_file = other_root + "hands/list42.txt"; + { + std::ofstream out(cwd_file); + out << "from-cwd\n"; + } - ASSERT_EQ(change_dir(other_root.c_str()), 0); - EXPECT_EQ(resolve_dtest_input_file("42", binary_path_), "hands/list42.txt"); + ASSERT_EQ(change_dir(other_root.c_str()), 0); + EXPECT_EQ(resolve_dtest_input_file("42", binary_path_), "hands/list42.txt"); } TEST(Args, ResolveReturnsEmptyWhenMissing) { - EXPECT_TRUE(resolve_dtest_input_file("no-such-list-999001", "dtest").empty()); + EXPECT_TRUE(resolve_dtest_input_file("no-such-list-999001", "dtest").empty()); } TEST_F(HandsLayoutFixture, ResolveRejectsDirectoryAsLiteralPath) { - // -f is an input *file*; an existing directory must not short-circuit - // resolution (otherwise read_file later fails with a confusing parse error). - ASSERT_EQ(change_dir(root_.c_str()), 0); - EXPECT_TRUE(resolve_dtest_input_file("hands", "dtest").empty()); - EXPECT_TRUE(resolve_dtest_input_file(root_ + "hands", "dtest").empty()); + // -f is an input *file*; an existing directory must not short-circuit + // resolution (otherwise read_file later fails with a confusing parse error). + ASSERT_EQ(change_dir(root_.c_str()), 0); + EXPECT_TRUE(resolve_dtest_input_file("hands", "dtest").empty()); + EXPECT_TRUE(resolve_dtest_input_file(root_ + "hands", "dtest").empty()); } TEST_F(HandsLayoutFixture, ResolveAbsoluteMissingDoesNotUseListShorthand) { - // A missing absolute -f must not fall through to hands/list{arg}.txt - // (list shorthand concatenates the absolute path into a nested name). - ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); + // A missing absolute -f must not fall through to hands/list{arg}.txt + // (list shorthand concatenates the absolute path into a nested name). + ASSERT_EQ(change_dir(original_cwd_.c_str()), 0); - // Pick a unique absolute path that is not present on this machine. - const std::string token = - "no_such_dtest_abs_" + - std::to_string(static_cast( - reinterpret_cast(this))); + // Pick a unique absolute path that is not present on this machine. + const std::string token = + "no_such_dtest_abs_" + + std::to_string(static_cast( + reinterpret_cast(this))); #ifdef _WIN32 - const std::string missing_abs = "\\" + token; + const std::string missing_abs = "\\" + token; #else - const std::string missing_abs = "/" + token; + const std::string missing_abs = "/" + token; #endif - ASSERT_FALSE(std::filesystem::exists(missing_abs)); - - // Trap file at the binary-relative list-shorthand location that a buggy - // fallthrough would incorrectly accept. - const std::filesystem::path trap = - std::filesystem::path(root_) / "hands" / - ("list" + missing_abs + ".txt"); - { - std::error_code ec; - std::filesystem::create_directories(trap.parent_path(), ec); - ASSERT_FALSE(ec) << ec.message(); - } - { - std::ofstream out(trap); - ASSERT_TRUE(out) << trap.string(); - out << "trap\n"; - } - - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(nullptr); - - EXPECT_TRUE( - resolve_dtest_input_file(missing_abs, binary_path_).empty()); + ASSERT_FALSE(std::filesystem::exists(missing_abs)); + + // Trap file at the binary-relative list-shorthand location that a buggy + // fallthrough would incorrectly accept. + const std::filesystem::path trap = + std::filesystem::path(root_) / "hands" / + ("list" + missing_abs + ".txt"); + { + std::error_code ec; + std::filesystem::create_directories(trap.parent_path(), ec); + ASSERT_FALSE(ec) << ec.message(); + } + { + std::ofstream out(trap); + ASSERT_TRUE(out) << trap.string(); + out << "trap\n"; + } + + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(nullptr); + + EXPECT_TRUE( + resolve_dtest_input_file(missing_abs, binary_path_).empty()); } #ifdef _WIN32 TEST_F(HandsLayoutFixture, ResolveDriveRelativeMissingDoesNotJoinUnderBazelDirs) { - // Drive-relative "X:foo" is not absolute, but fs::path join can discard the - // BUILD_* base. Missing drive-relative -f must not resolve via env joins. - ASSERT_GE(root_.size(), 2u); - ASSERT_EQ(root_[1], ':'); - - const std::string token = - "no_such_dtest_drive_rel_" + - std::to_string(static_cast( - reinterpret_cast(this))); - const std::string missing_drive_rel = - std::string(1, root_[0]) + ":" + token; - ASSERT_FALSE(is_dtest_absolute_path(missing_drive_rel)); - ASSERT_FALSE(std::filesystem::exists(missing_drive_rel)); - - const std::string runfiles = - std::string(::testing::TempDir()) + "dtest_hands_drive_rel/"; - ASSERT_TRUE(make_dir(runfiles)); - ASSERT_EQ(change_dir(runfiles.c_str()), 0); - - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(root_.c_str()); - workspace.set(root_.c_str()); - - EXPECT_TRUE( - resolve_dtest_input_file(missing_drive_rel, binary_path_).empty()); + // Drive-relative "X:foo" is not absolute, but fs::path join can discard the + // BUILD_* base. Missing drive-relative -f must not resolve via env joins. + ASSERT_GE(root_.size(), 2u); + ASSERT_EQ(root_[1], ':'); + + const std::string token = + "no_such_dtest_drive_rel_" + + std::to_string(static_cast( + reinterpret_cast(this))); + const std::string missing_drive_rel = + std::string(1, root_[0]) + ":" + token; + ASSERT_FALSE(is_dtest_absolute_path(missing_drive_rel)); + ASSERT_FALSE(std::filesystem::exists(missing_drive_rel)); + + const std::string runfiles = + std::string(::testing::TempDir()) + "dtest_hands_drive_rel/"; + ASSERT_TRUE(make_dir(runfiles)); + ASSERT_EQ(change_dir(runfiles.c_str()), 0); + + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(root_.c_str()); + workspace.set(root_.c_str()); + + EXPECT_TRUE( + resolve_dtest_input_file(missing_drive_rel, binary_path_).empty()); } #endif TEST(Args, AbsolutePathDetection) { - EXPECT_FALSE(is_dtest_absolute_path("tmp/dtest")); - EXPECT_FALSE(is_dtest_absolute_path("")); + EXPECT_FALSE(is_dtest_absolute_path("tmp/dtest")); + EXPECT_FALSE(is_dtest_absolute_path("")); #ifndef _WIN32 - EXPECT_TRUE(is_dtest_absolute_path("/tmp/dtest")); - // On POSIX, leading backslash is not absolute. - EXPECT_FALSE(is_dtest_absolute_path("\\tmp\\dtest")); + EXPECT_TRUE(is_dtest_absolute_path("/tmp/dtest")); + // On POSIX, leading backslash is not absolute. + EXPECT_FALSE(is_dtest_absolute_path("\\tmp\\dtest")); #else - // Drive-relative (no root separator after the colon) is not absolute. - EXPECT_FALSE(is_dtest_absolute_path("C:bin\\dtest")); - EXPECT_FALSE(is_dtest_absolute_path("C:bin/dtest")); - EXPECT_FALSE(is_dtest_absolute_path("C:")); - EXPECT_TRUE(is_dtest_absolute_path("C:\\bin\\dtest")); - EXPECT_TRUE(is_dtest_absolute_path("C:/bin/dtest")); - EXPECT_TRUE(is_dtest_absolute_path("c:\\")); - // Current-drive rooted (leading single separator) is absolute on Windows. - EXPECT_TRUE(is_dtest_absolute_path("\\repo\\bazel-bin\\dtest")); - EXPECT_TRUE(is_dtest_absolute_path("/repo/bazel-bin/dtest")); - EXPECT_TRUE(is_dtest_absolute_path("\\")); - EXPECT_TRUE(is_dtest_absolute_path("/")); - EXPECT_TRUE(is_dtest_absolute_path("\\\\server\\share")); - EXPECT_TRUE(is_dtest_absolute_path("\\\\server\\share\\bazel-bin\\dtest")); - EXPECT_TRUE(is_dtest_absolute_path("//server/share/dtest")); - EXPECT_FALSE(is_dtest_absolute_path("\\\\server")); - EXPECT_FALSE(is_dtest_absolute_path("\\\\server\\")); + // Drive-relative (no root separator after the colon) is not absolute. + EXPECT_FALSE(is_dtest_absolute_path("C:bin\\dtest")); + EXPECT_FALSE(is_dtest_absolute_path("C:bin/dtest")); + EXPECT_FALSE(is_dtest_absolute_path("C:")); + EXPECT_TRUE(is_dtest_absolute_path("C:\\bin\\dtest")); + EXPECT_TRUE(is_dtest_absolute_path("C:/bin/dtest")); + EXPECT_TRUE(is_dtest_absolute_path("c:\\")); + // Current-drive rooted (leading single separator) is absolute on Windows. + EXPECT_TRUE(is_dtest_absolute_path("\\repo\\bazel-bin\\dtest")); + EXPECT_TRUE(is_dtest_absolute_path("/repo/bazel-bin/dtest")); + EXPECT_TRUE(is_dtest_absolute_path("\\")); + EXPECT_TRUE(is_dtest_absolute_path("/")); + EXPECT_TRUE(is_dtest_absolute_path("\\\\server\\share")); + EXPECT_TRUE(is_dtest_absolute_path("\\\\server\\share\\bazel-bin\\dtest")); + EXPECT_TRUE(is_dtest_absolute_path("//server/share/dtest")); + EXPECT_FALSE(is_dtest_absolute_path("\\\\server")); + EXPECT_FALSE(is_dtest_absolute_path("\\\\server\\")); #endif } #ifdef _WIN32 TEST_F(HandsLayoutFixture, ResolveNumericWithDriveRelativeArgv0) { - // Sit in the fake binary dir so cwd has no hands/ (otherwise the numeric - // lookup would short-circuit before argv0). "X:dtest" is drive-relative to - // that directory — not rooted at X:\. - const std::string bin_dir = root_ + "bazel-bin/library/tests"; - ASSERT_EQ(change_dir(bin_dir.c_str()), 0); - ASSERT_GE(root_.size(), 2u); - ASSERT_EQ(root_[1], ':'); + // Sit in the fake binary dir so cwd has no hands/ (otherwise the numeric + // lookup would short-circuit before argv0). "X:dtest" is drive-relative to + // that directory — not rooted at X:\. + const std::string bin_dir = root_ + "bazel-bin/library/tests"; + ASSERT_EQ(change_dir(bin_dir.c_str()), 0); + ASSERT_GE(root_.size(), 2u); + ASSERT_EQ(root_[1], ':'); - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(nullptr); + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(nullptr); - const std::string drive_rel = std::string(1, root_[0]) + ":dtest"; - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", drive_rel), - root_ + "hands/list42.txt")); + const std::string drive_rel = std::string(1, root_[0]) + ":dtest"; + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", drive_rel), + root_ + "hands/list42.txt")); } TEST_F(HandsLayoutFixture, ResolveNumericWithCurrentDriveRootedArgv0) { - // "\path\..." is absolute on the current drive; do not prepend cwd. - ASSERT_GE(root_.size(), 2u); - ASSERT_EQ(root_[1], ':'); - const std::string sibling = - std::string(::testing::TempDir()) + "dtest_hands_rootrel_cwd/"; - ASSERT_TRUE(make_dir(sibling)); - ASSERT_EQ(change_dir(sibling.c_str()), 0); - - const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); - const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); - working.set(nullptr); - workspace.set(nullptr); - - // Drop the drive letter so argv0 is current-drive rooted. - const std::string root_rel_argv0 = - root_.substr(2) + "bazel-bin/library/tests/dtest"; - ASSERT_TRUE( - root_rel_argv0[0] == '\\' || root_rel_argv0[0] == '/'); - EXPECT_TRUE(same_path( - resolve_dtest_input_file("42", root_rel_argv0), - root_ + "hands/list42.txt")); + // "\path\..." is absolute on the current drive; do not prepend cwd. + ASSERT_GE(root_.size(), 2u); + ASSERT_EQ(root_[1], ':'); + const std::string sibling = + std::string(::testing::TempDir()) + "dtest_hands_rootrel_cwd/"; + ASSERT_TRUE(make_dir(sibling)); + ASSERT_EQ(change_dir(sibling.c_str()), 0); + + const EnvVarGuard working("BUILD_WORKING_DIRECTORY"); + const EnvVarGuard workspace("BUILD_WORKSPACE_DIRECTORY"); + working.set(nullptr); + workspace.set(nullptr); + + // Drop the drive letter so argv0 is current-drive rooted. + const std::string root_rel_argv0 = + root_.substr(2) + "bazel-bin/library/tests/dtest"; + ASSERT_TRUE( + root_rel_argv0[0] == '\\' || root_rel_argv0[0] == '/'); + EXPECT_TRUE(same_path( + resolve_dtest_input_file("42", root_rel_argv0), + root_ + "hands/list42.txt")); } #endif diff --git a/library/tests/calc_par_test.cpp b/library/tests/calc_par_test.cpp index ab0d0e9e3..7b656aded 100644 --- a/library/tests/calc_par_test.cpp +++ b/library/tests/calc_par_test.cpp @@ -35,7 +35,7 @@ class CalcParTest : public ::testing::Test deal0_.cards[h][s] = holdings0_[s][h]; } } - + // Test hand 1: Different vulnerability // Par: NS 100, EW -100, contracts: "NS:EW 4Sx", "EW:EW 4Sx" for (int h = 0; h < DDS_HANDS; h++) { @@ -43,7 +43,7 @@ class CalcParTest : public ::testing::Test deal1_.cards[h][s] = holdings1_[s][h]; } } - + // Test hand 2: Another variation // Par: NS -300, EW 300, contracts: "NS:NS 5Hx", "EW:NS 5Hx" for (int h = 0; h < DDS_HANDS; h++) { @@ -69,7 +69,7 @@ class CalcParTest : public ::testing::Test {RJ|R8|R5, RA|RT|R7|R6|R4, RK|RQ|R9, R3|R2}, // Diamonds {RT|R9|R8, RQ|R4, RA|R7|R6|R5|R2, RK|RJ|R3} // Clubs }; - + // Hand 1: VUL_NS (2) // PBN: E:QJT5432.T.6.QJ82 .J97543.K7532.94 87.A62.QJT4.AT75 AK96.KQ8.A98.K63 unsigned int holdings1_[4][4] = { @@ -78,7 +78,7 @@ class CalcParTest : public ::testing::Test {RA|R9|R8, R6, RK|R7|R5|R3|R2, RQ|RJ|RT|R4}, // Diamonds {RK|R6|R3, RQ|RJ|R8|R2, R9|R4, RA|RT|R7|R5} // Clubs }; - + // Hand 2: VUL_NONE (0) // PBN: N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93 unsigned int holdings2_[4][4] = { @@ -91,20 +91,20 @@ class CalcParTest : public ::testing::Test DdTableDeal deal0_; DdTableDeal deal1_; DdTableDeal deal2_; - + // Expected par results const char* expected_par_score_[3][2] = { { "NS -110", "EW 110" }, { "NS 100", "EW -100" }, { "NS -300", "EW 300" } }; - + const char* expected_par_contracts_[3][2] = { { "NS:EW 2S", "EW:EW 2S" }, { "NS:EW 4Sx", "EW:EW 4Sx" }, { "NS:NS 5Hx", "EW:NS 5Hx" } }; - + int vulnerability_[3] = { 0, 2, 0 }; // None, NS, None }; @@ -113,15 +113,15 @@ TEST_F(CalcParTest, BasicCalcParHand0) { DdTableResults table; ParResults par; - + int result = calc_par(deal0_, vulnerability_[0], &table, &par); - + ASSERT_EQ(result, RETURN_NO_FAULT) << "calc_par should succeed"; - + // Verify par scores EXPECT_STREQ(par.par_score[0], expected_par_score_[0][0]); EXPECT_STREQ(par.par_score[1], expected_par_score_[0][1]); - + // Verify par contracts EXPECT_STREQ(par.par_contracts_string[0], expected_par_contracts_[0][0]); EXPECT_STREQ(par.par_contracts_string[1], expected_par_contracts_[0][1]); @@ -131,11 +131,11 @@ TEST_F(CalcParTest, BasicCalcParHand1) { DdTableResults table; ParResults par; - + int result = calc_par(deal1_, vulnerability_[1], &table, &par); - + ASSERT_EQ(result, RETURN_NO_FAULT); - + EXPECT_STREQ(par.par_score[0], expected_par_score_[1][0]); EXPECT_STREQ(par.par_score[1], expected_par_score_[1][1]); EXPECT_STREQ(par.par_contracts_string[0], expected_par_contracts_[1][0]); @@ -146,11 +146,11 @@ TEST_F(CalcParTest, BasicCalcParHand2) { DdTableResults table; ParResults par; - + int result = calc_par(deal2_, vulnerability_[2], &table, &par); - + ASSERT_EQ(result, RETURN_NO_FAULT); - + EXPECT_STREQ(par.par_score[0], expected_par_score_[2][0]); EXPECT_STREQ(par.par_score[1], expected_par_score_[2][1]); EXPECT_STREQ(par.par_contracts_string[0], expected_par_contracts_[2][0]); @@ -163,11 +163,11 @@ TEST_F(CalcParTest, CalcParWithContext) SolverContext ctx; DdTableResults table; ParResults par; - + int result = calc_par(ctx, deal0_, vulnerability_[0], &table, &par); - + ASSERT_EQ(result, RETURN_NO_FAULT); - + EXPECT_STREQ(par.par_score[0], expected_par_score_[0][0]); EXPECT_STREQ(par.par_score[1], expected_par_score_[0][1]); EXPECT_STREQ(par.par_contracts_string[0], expected_par_contracts_[0][0]); @@ -178,21 +178,21 @@ TEST_F(CalcParTest, CalcParWithContext) TEST_F(CalcParTest, ContextReuseMultipleCalls) { SolverContext ctx; - + // First call DdTableResults table1; ParResults par1; int result1 = calc_par(ctx, deal0_, vulnerability_[0], &table1, &par1); ASSERT_EQ(result1, RETURN_NO_FAULT); EXPECT_STREQ(par1.par_score[0], expected_par_score_[0][0]); - + // Second call with same context, different deal DdTableResults table2; ParResults par2; int result2 = calc_par(ctx, deal1_, vulnerability_[1], &table2, &par2); ASSERT_EQ(result2, RETURN_NO_FAULT); EXPECT_STREQ(par2.par_score[0], expected_par_score_[1][0]); - + // Third call DdTableResults table3; ParResults par3; @@ -209,12 +209,12 @@ TEST_F(CalcParTest, CalcParFromTableHand0) ParResults par_full; int result1 = calc_par(deal0_, vulnerability_[0], &table, &par_full); ASSERT_EQ(result1, RETURN_NO_FAULT); - + // Now compute par from the table only ParResults par_from_table; int result2 = calc_par_from_table(&table, vulnerability_[0], &par_from_table); ASSERT_EQ(result2, RETURN_NO_FAULT); - + // Results should be identical EXPECT_STREQ(par_from_table.par_score[0], par_full.par_score[0]); EXPECT_STREQ(par_from_table.par_score[1], par_full.par_score[1]); @@ -227,13 +227,13 @@ TEST_F(CalcParTest, VulnerabilityVariations) { DdTableResults table; ParResults par; - + // Test all vulnerability conditions on same deal for (int vuln = 0; vuln <= 3; vuln++) { int result = calc_par(deal0_, vuln, &table, &par); EXPECT_EQ(result, RETURN_NO_FAULT) << "calc_par should succeed for vulnerability " << vuln; - + // Just verify it returns some result (scores depend on vulnerability) EXPECT_NE(par.par_score[0][0], '\0') << "Par score NS should not be empty"; EXPECT_NE(par.par_score[1][0], '\0') << "Par score EW should not be empty"; @@ -245,10 +245,10 @@ TEST_F(CalcParTest, TableResultsPopulated) { DdTableResults table; ParResults par; - + int result = calc_par(deal0_, vulnerability_[0], &table, &par); ASSERT_EQ(result, RETURN_NO_FAULT); - + // Verify DD table has valid trick counts (0-13) for (int strain = 0; strain < DDS_STRAINS; strain++) { for (int hand = 0; hand < DDS_HANDS; hand++) { @@ -267,7 +267,7 @@ TEST_F(CalcParTest, CalcParFromTableVulnerability) ParResults par_temp; int result = calc_par(deal0_, 0, &table, &par_temp); ASSERT_EQ(result, RETURN_NO_FAULT); - + // Compute par for different vulnerabilities using same table for (int vuln = 0; vuln <= 3; vuln++) { ParResults par; @@ -282,11 +282,11 @@ TEST_F(CalcParTest, InvalidVulnerability) { DdTableResults table; ParResults par; - + // Test with out-of-range vulnerability (valid range is 0-3) // Note: The C API may or may not validate this - we're testing behavior int result = calc_par(deal0_, -1, &table, &par); - + // Either it fails with error or succeeds (implementation-dependent) // Just verify it doesn't crash EXPECT_TRUE(result == RETURN_NO_FAULT || result < 0) @@ -302,13 +302,13 @@ TEST_F(CalcParTest, EmptyDealHandling) empty_deal.cards[h][s] = 0; } } - + DdTableResults table; ParResults par; - + // This should fail with appropriate error (no cards) int result = calc_par(empty_deal, 0, &table, &par); - + // Expecting an error (not RETURN_NO_FAULT) EXPECT_NE(result, RETURN_NO_FAULT) << "Empty deal should produce an error"; @@ -322,18 +322,18 @@ TEST_F(CalcParTest, ConsistencyCalcParVsFromTable) if (hand_idx == 0) deal = &deal0_; else if (hand_idx == 1) deal = &deal1_; else deal = &deal2_; - + // Method 1: calc_par (computes table + par) DdTableResults table1; ParResults par1; int res1 = calc_par(*deal, vulnerability_[hand_idx], &table1, &par1); ASSERT_EQ(res1, RETURN_NO_FAULT); - + // Method 2: calc_par_from_table using computed table ParResults par2; int res2 = calc_par_from_table(&table1, vulnerability_[hand_idx], &par2); ASSERT_EQ(res2, RETURN_NO_FAULT); - + // Results should be identical EXPECT_STREQ(par1.par_score[0], par2.par_score[0]) << "Hand " << hand_idx << " NS scores should match"; diff --git a/library/tests/compare.cpp b/library/tests/compare.cpp index c2c8e5d8b..c6f88eeee 100644 --- a/library/tests/compare.cpp +++ b/library/tests/compare.cpp @@ -17,109 +17,109 @@ using std::vector; bool compare_PBN( - const DealPBN& dl1, - const DealPBN& dl2) + const DealPBN& dl1, + const DealPBN& dl2) { - if (dl1.trump != dl2.trump) - return false; - if (dl1.first != dl2.first) - return false; - if (strcmp(dl1.remainCards, dl2.remainCards)) - return false; - - return true; + if (dl1.trump != dl2.trump) + return false; + if (dl1.first != dl2.first) + return false; + if (strcmp(dl1.remainCards, dl2.remainCards)) + return false; + + return true; } bool compare_FUT( - const FutureTricks& fut1, - const FutureTricks& fut2) + const FutureTricks& fut1, + const FutureTricks& fut2) { - if (fut1.cards != fut2.cards) - return false; - - for (int i = 0; i < fut1.cards; i++) - { - if (fut1.suit[i] != fut2.suit[i]) - return false; - if (fut1.rank[i] != fut2.rank[i]) - return false; - if (fut1.equals[i] != fut2.equals[i]) - return false; - if (fut1.score[i] != fut2.score[i]) - return false; - } - - return true; + if (fut1.cards != fut2.cards) + return false; + + for (int i = 0; i < fut1.cards; i++) + { + if (fut1.suit[i] != fut2.suit[i]) + return false; + if (fut1.rank[i] != fut2.rank[i]) + return false; + if (fut1.equals[i] != fut2.equals[i]) + return false; + if (fut1.score[i] != fut2.score[i]) + return false; + } + + return true; } bool compare_TABLE( - const DdTableResults& table1, - const DdTableResults& table2) + const DdTableResults& table1, + const DdTableResults& table2) { - for (int strain = 0; strain < DDS_STRAINS; strain++) - { - for (int pl = 0; pl < DDS_HANDS; pl++) - if (table1.res_table[strain][pl] != table2.res_table[strain][pl]) - return false; - } + for (int strain = 0; strain < DDS_STRAINS; strain++) + { + for (int pl = 0; pl < DDS_HANDS; pl++) + if (table1.res_table[strain][pl] != table2.res_table[strain][pl]) + return false; + } - return true; + return true; } bool compare_PAR( - const ParResults& par1, - const ParResults& par2) + const ParResults& par1, + const ParResults& par2) { - if (strcmp(par1.par_score[0], par2.par_score[0])) - return false; - if (strcmp(par1.par_score[1], par2.par_score[1])) - return false; - if (strcmp(par1.par_contracts_string[0], par2.par_contracts_string[0])) - return false; - if (strcmp(par1.par_contracts_string[1], par2.par_contracts_string[1])) - return false; - - return true; + if (strcmp(par1.par_score[0], par2.par_score[0])) + return false; + if (strcmp(par1.par_score[1], par2.par_score[1])) + return false; + if (strcmp(par1.par_contracts_string[0], par2.par_contracts_string[0])) + return false; + if (strcmp(par1.par_contracts_string[1], par2.par_contracts_string[1])) + return false; + + return true; } bool compare_DEALERPAR( - const ParResultsDealer& par1, - const ParResultsDealer& par2) + const ParResultsDealer& par1, + const ParResultsDealer& par2) { - if (par1.score != par2.score) - return false; + if (par1.score != par2.score) + return false; - for (int i = 0; i < par1.number; i++) - if (strcmp(par1.contracts[i], par2.contracts[i])) - return false; + for (int i = 0; i < par1.number; i++) + if (strcmp(par1.contracts[i], par2.contracts[i])) + return false; - return true; + return true; } bool compare_TRACE( - const SolvedPlay& trace1, - const SolvedPlay& trace2) + const SolvedPlay& trace1, + const SolvedPlay& trace2) { - // In a buglet, Trace returned trace1 == -3 if there is - // no input at all (trace2 is then 0). - if (trace1.number != trace2.number && trace2.number > 0) - return false; + // In a buglet, Trace returned trace1 == -3 if there is + // no input at all (trace2 is then 0). + if (trace1.number != trace2.number && trace2.number > 0) + return false; - // Once that was fixed, the input file had length 0, not 1. - if (trace1.number == 1 && trace2.number == 0) - return true; + // Once that was fixed, the input file had length 0, not 1. + if (trace1.number == 1 && trace2.number == 0) + return true; - for (int i = 0; i < trace1.number; i++) - { - if (trace1.tricks[i] != trace2.tricks[i]) - return false; - } + for (int i = 0; i < trace1.number; i++) + { + if (trace1.tricks[i] != trace2.tricks[i]) + return false; + } - return true; + return true; } diff --git a/library/tests/compare_test.cpp b/library/tests/compare_test.cpp index 5acdce1d7..2de793044 100644 --- a/library/tests/compare_test.cpp +++ b/library/tests/compare_test.cpp @@ -10,36 +10,36 @@ namespace auto filled_table(const int value) -> DdTableResults { - DdTableResults table{}; - for (int strain = 0; strain < DDS_STRAINS; ++strain) - { - for (int hand = 0; hand < DDS_HANDS; ++hand) - table.res_table[strain][hand] = value; - } - return table; + DdTableResults table{}; + for (int strain = 0; strain < DDS_STRAINS; ++strain) + { + for (int hand = 0; hand < DDS_HANDS; ++hand) + table.res_table[strain][hand] = value; + } + return table; } } // namespace TEST(CompareTable, EqualTablesMatchIncludingNt) { - const DdTableResults a = filled_table(7); - const DdTableResults b = filled_table(7); - EXPECT_TRUE(compare_TABLE(a, b)); + const DdTableResults a = filled_table(7); + const DdTableResults b = filled_table(7); + EXPECT_TRUE(compare_TABLE(a, b)); } TEST(CompareTable, DetectsNtOnlyMismatch) { - DdTableResults a = filled_table(7); - DdTableResults b = filled_table(7); - b.res_table[4][0] = 8; // NT / North - EXPECT_FALSE(compare_TABLE(a, b)); + DdTableResults a = filled_table(7); + DdTableResults b = filled_table(7); + b.res_table[4][0] = 8; // NT / North + EXPECT_FALSE(compare_TABLE(a, b)); } TEST(CompareTable, DetectsSuitMismatch) { - DdTableResults a = filled_table(7); - DdTableResults b = filled_table(7); - b.res_table[0][1] = 3; // Spades / East - EXPECT_FALSE(compare_TABLE(a, b)); + DdTableResults a = filled_table(7); + DdTableResults b = filled_table(7); + b.res_table[0][1] = 3; // Spades / East + EXPECT_FALSE(compare_TABLE(a, b)); } diff --git a/library/tests/context_equivalence.cpp b/library/tests/context_equivalence.cpp index 0461462b6..b8598c038 100644 --- a/library/tests/context_equivalence.cpp +++ b/library/tests/context_equivalence.cpp @@ -10,34 +10,34 @@ extern Memory memory; static Deal make_empty_deal() { - Deal dl{}; // zero-initialized; remainCards all zero - dl.trump = 0; - dl.first = 0; - std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); - std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); - std::memset(dl.remainCards, 0, sizeof(dl.remainCards)); - return dl; + Deal dl{}; // zero-initialized; remainCards all zero + dl.trump = 0; + dl.first = 0; + std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); + std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); + std::memset(dl.remainCards, 0, sizeof(dl.remainCards)); + return dl; } int main() { - // Arrange - const int thr = 0; - FutureTricks ft1{}; - FutureTricks ft2{}; - Deal dl = make_empty_deal(); + // Arrange + const int thr = 0; + FutureTricks ft1{}; + FutureTricks ft2{}; + Deal dl = make_empty_deal(); - // Act: legacy - int r1 = SolveBoard(dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft1, thr); + // Act: legacy + int r1 = SolveBoard(dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft1, thr); - // Act: context - SolverContext ctx; - int r2 = solve_board(ctx, dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft2); + // Act: context + SolverContext ctx; + int r2 = solve_board(ctx, dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft2); - // Assert: return codes identical (both should be error on empty Deal) - if (r1 != r2) { - std::cerr << "Return codes differ: legacy=" << r1 << " ctx=" << r2 << std::endl; - return 1; - } - return 0; + // Assert: return codes identical (both should be error on empty Deal) + if (r1 != r2) { + std::cerr << "Return codes differ: legacy=" << r1 << " ctx=" << r2 << std::endl; + return 1; + } + return 0; } diff --git a/library/tests/cst.hpp b/library/tests/cst.hpp index 9785284fc..0014ddc7f 100644 --- a/library/tests/cst.hpp +++ b/library/tests/cst.hpp @@ -20,21 +20,21 @@ /// Solver operation mode enumeration. enum class Solver { - DTEST_SOLVER_SOLVE = 0, ///< Solve single board - DTEST_SOLVER_CALC = 1, ///< Calculate DD table - DTEST_SOLVER_PLAY = 2, ///< Play out deal - DTEST_SOLVER_PAR = 3, ///< Calculate PAR score - DTEST_SOLVER_DEALERPAR = 4, ///< Calculate dealer PAR - DTEST_SOLVER_SIZE = 5 ///< Number of solver modes + DTEST_SOLVER_SOLVE = 0, ///< Solve single board + DTEST_SOLVER_CALC = 1, ///< Calculate DD table + DTEST_SOLVER_PLAY = 2, ///< Play out deal + DTEST_SOLVER_PAR = 3, ///< Calculate PAR score + DTEST_SOLVER_DEALERPAR = 4, ///< Calculate dealer PAR + DTEST_SOLVER_SIZE = 5 ///< Number of solver modes }; /// Global test options structure. struct OptionsType { - std::string fname_; ///< Input file path - Solver solver_; ///< Solver mode - int num_threads_; ///< Number of threads to use - int memory_mb_; ///< Memory allocation in MB - bool report_slow_boards_; ///< Report slow-executing hands + std::string fname_; ///< Input file path + Solver solver_; ///< Solver mode + int num_threads_; ///< Number of threads to use + int memory_mb_; ///< Memory allocation in MB + bool report_slow_boards_; ///< Report slow-executing hands }; diff --git a/library/tests/dds_c_api_test.cpp b/library/tests/dds_c_api_test.cpp index 0faaba4f5..f46f8b7d7 100644 --- a/library/tests/dds_c_api_test.cpp +++ b/library/tests/dds_c_api_test.cpp @@ -325,18 +325,18 @@ TEST(DdsCApiDdTable, PbnTableMatchesBinaryTable) const struct DdTableDeal binary_deal = MakeReferenceTableDeal(); struct DdTableResults binary_results = {}; ASSERT_EQ(dds_c_calc_dd_table(ctx, &binary_deal, &binary_results), - RETURN_NO_FAULT); + RETURN_NO_FAULT); struct DdTableDealPBN pbn_deal = {}; std::snprintf(pbn_deal.cards, sizeof pbn_deal.cards, "%s", kReferencePbn); struct DdTableResults pbn_results = {}; ASSERT_EQ(dds_c_calc_dd_table_pbn(ctx, &pbn_deal, &pbn_results), - RETURN_NO_FAULT); + RETURN_NO_FAULT); for (int strain = 0; strain < DDS_STRAINS; ++strain) for (int hand = 0; hand < DDS_HANDS; ++hand) EXPECT_EQ(pbn_results.res_table[strain][hand], - binary_results.res_table[strain][hand]) + binary_results.res_table[strain][hand]) << "res_table[" << strain << "][" << hand << "]"; dds_c_destroy_solvercontext(ctx); @@ -351,7 +351,7 @@ TEST(DdsCApiPar, ProducesNonEmptyScore) struct DdTableResults results = {}; struct ParResults par = {}; ASSERT_EQ(dds_c_calc_par(ctx, &deal, 0 /* vulnerable: none */, &results, &par), - RETURN_NO_FAULT); + RETURN_NO_FAULT); EXPECT_GT(std::strlen(par.par_score[0]), 0U); @@ -380,14 +380,14 @@ TEST(DdsCApiPar, PbnMatchesBinary) struct DdTableResults binary_results = {}; struct ParResults binary_par = {}; ASSERT_EQ(dds_c_calc_par(ctx, &binary_deal, 0, &binary_results, &binary_par), - RETURN_NO_FAULT); + RETURN_NO_FAULT); struct DdTableDealPBN pbn_deal = {}; std::snprintf(pbn_deal.cards, sizeof pbn_deal.cards, "%s", kReferencePbn); struct DdTableResults pbn_results = {}; struct ParResults pbn_par = {}; ASSERT_EQ(dds_c_calc_par_pbn(ctx, &pbn_deal, 0, &pbn_results, &pbn_par), - RETURN_NO_FAULT); + RETURN_NO_FAULT); EXPECT_STREQ(pbn_par.par_score[0], binary_par.par_score[0]); EXPECT_STREQ(pbn_par.par_score[1], binary_par.par_score[1]); diff --git a/library/tests/deal_input_validation_test.cpp b/library/tests/deal_input_validation_test.cpp index 2ffaabf2a..b50c45214 100644 --- a/library/tests/deal_input_validation_test.cpp +++ b/library/tests/deal_input_validation_test.cpp @@ -30,35 +30,35 @@ namespace { constexpr char kLegalDeal[] = - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; /// The same deal one card short: north's spades are T8, not T98. Contains /// only legal PBN characters -- the shape a truncated PBN file takes. constexpr char kShortOneCard[] = - "N:QJ6.K652.J85.T8 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + "N:QJ6.K652.J85.T8 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; /// The same deal with a card replaced by an invalid rank. convert_from_pbn() /// silently skips unrecognized characters, so this also arrives one card short. constexpr char kBadRank[] = - "N:QJ6.K652.J85.TZ8 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + "N:QJ6.K652.J85.TZ8 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; auto pbn_deal(const char * cards) -> DdTableDealPBN { - DdTableDealPBN deal; - std::memset(&deal, 0, sizeof(deal)); - std::strncpy(deal.cards, cards, sizeof(deal.cards) - 1); - return deal; + DdTableDealPBN deal; + std::memset(&deal, 0, sizeof(deal)); + std::strncpy(deal.cards, cards, sizeof(deal.cards) - 1); + return deal; } /// A legal binary deal: each hand holds one complete suit. auto one_suit_each() -> DdTableDeal { - constexpr unsigned kAllRanks = 0x7FFC; // ranks 2..A - DdTableDeal deal; - std::memset(&deal, 0, sizeof(deal)); - for (int h = 0; h < DDS_HANDS; h++) - deal.cards[h][h] = kAllRanks; - return deal; + constexpr unsigned kAllRanks = 0x7FFC; // ranks 2..A + DdTableDeal deal; + std::memset(&deal, 0, sizeof(deal)); + for (int h = 0; h < DDS_HANDS; h++) + deal.cards[h][h] = kAllRanks; + return deal; } // --------------------------------------------------------------------------- @@ -67,154 +67,154 @@ auto one_suit_each() -> DdTableDeal TEST(CalcTableValidation, ShortPbnDealIsRejected) { - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(CalcDDtablePBN(pbn_deal(kShortOneCard), &table), - RETURN_CARD_COUNT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(CalcDDtablePBN(pbn_deal(kShortOneCard), &table), + RETURN_CARD_COUNT); } TEST(CalcTableValidation, InvalidRankCharacterIsRejected) { - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - // Rejected for the card count, since the invalid rank is skipped by the - // parser rather than refused outright. - EXPECT_EQ(CalcDDtablePBN(pbn_deal(kBadRank), &table), RETURN_CARD_COUNT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + // Rejected for the card count, since the invalid rank is skipped by the + // parser rather than refused outright. + EXPECT_EQ(CalcDDtablePBN(pbn_deal(kBadRank), &table), RETURN_CARD_COUNT); } TEST(CalcTableValidation, UnbalancedBinaryDealIsRejected) { - DdTableDeal deal = one_suit_each(); - deal.cards[0][0] &= ~0x4000u; // remove north's ace + DdTableDeal deal = one_suit_each(); + deal.cards[0][0] &= ~0x4000u; // remove north's ace - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(CalcDDtable(deal, &table), RETURN_CARD_COUNT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_CARD_COUNT); } TEST(CalcTableValidation, DuplicateCardIsRejected) { - DdTableDeal deal = one_suit_each(); - // Give north a card east already holds, keeping the hand counts equal. - deal.cards[0][1] |= 0x4000u; - deal.cards[0][0] &= ~0x4000u; - deal.cards[1][1] |= 0x4000u; - - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(CalcDDtable(deal, &table), RETURN_DUPLICATE_CARDS); + DdTableDeal deal = one_suit_each(); + // Give north a card east already holds, keeping the hand counts equal. + deal.cards[0][1] |= 0x4000u; + deal.cards[0][0] &= ~0x4000u; + deal.cards[1][1] |= 0x4000u; + + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_DUPLICATE_CARDS); } TEST(CalcTableValidation, BitsOutsideRankRangeAreRejected) { - DdTableDeal deal = one_suit_each(); - deal.cards[2][2] |= 0x8000u; // above the ace bit + DdTableDeal deal = one_suit_each(); + deal.cards[2][2] |= 0x8000u; // above the ace bit - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(CalcDDtable(deal, &table), RETURN_SUIT_OR_RANK); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_SUIT_OR_RANK); - DdTableDeal low = one_suit_each(); - low.cards[1][1] |= 0x0001u; // below the deuce bit - EXPECT_EQ(CalcDDtable(low, &table), RETURN_SUIT_OR_RANK); + DdTableDeal low = one_suit_each(); + low.cards[1][1] |= 0x0001u; // below the deuce bit + EXPECT_EQ(CalcDDtable(low, &table), RETURN_SUIT_OR_RANK); } TEST(CalcTableValidation, LegalDealStillProducesATable) { - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - ASSERT_EQ(CalcDDtablePBN(pbn_deal(kLegalDeal), &table), RETURN_NO_FAULT); - - for (int d = 0; d < DDS_STRAINS; d++) - for (int h = 0; h < DDS_HANDS; h++) - EXPECT_GE(table.res_table[d][h], 0) << "strain " << d << " hand " << h; - for (int d = 0; d < DDS_STRAINS; d++) - for (int h = 0; h < DDS_HANDS; h++) - EXPECT_LE(table.res_table[d][h], 13) << "strain " << d << " hand " << h; + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + ASSERT_EQ(CalcDDtablePBN(pbn_deal(kLegalDeal), &table), RETURN_NO_FAULT); + + for (int d = 0; d < DDS_STRAINS; d++) + for (int h = 0; h < DDS_HANDS; h++) + EXPECT_GE(table.res_table[d][h], 0) << "strain " << d << " hand " << h; + for (int d = 0; d < DDS_STRAINS; d++) + for (int h = 0; h < DDS_HANDS; h++) + EXPECT_LE(table.res_table[d][h], 13) << "strain " << d << " hand " << h; } TEST(CalcTableValidation, LegalBinaryDealStillProducesATable) { - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(CalcDDtable(one_suit_each(), &table), RETURN_NO_FAULT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(CalcDDtable(one_suit_each(), &table), RETURN_NO_FAULT); } TEST(CalcTableValidation, CalcAllTablesRejectsUnbalancedDeal) { - DdTableDeals deals; - std::memset(&deals, 0, sizeof(deals)); - deals.no_of_tables = 1; - deals.deals[0] = one_suit_each(); - deals.deals[0].cards[3][3] &= ~0x4000u; // west one card short - - DdTablesRes res; - std::memset(&res, 0, sizeof(res)); - AllParResults par; - std::memset(&par, 0, sizeof(par)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), RETURN_CARD_COUNT); + DdTableDeals deals; + std::memset(&deals, 0, sizeof(deals)); + deals.no_of_tables = 1; + deals.deals[0] = one_suit_each(); + deals.deals[0].cards[3][3] &= ~0x4000u; // west one card short + + DdTablesRes res; + std::memset(&res, 0, sizeof(res)); + AllParResults par; + std::memset(&par, 0, sizeof(par)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), RETURN_CARD_COUNT); } TEST(CalcTableValidation, CppOverloadRejectsUnbalancedDeal) { - // The C++ calc_dd_table() overloads build Boards directly rather than going - // through CalcDDtableN(), so they need the same guard. - DdTableDeal deal = one_suit_each(); - deal.cards[0][0] &= ~0x4000u; - - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(calc_dd_table(deal, &table), RETURN_CARD_COUNT); + // The C++ calc_dd_table() overloads build Boards directly rather than going + // through CalcDDtableN(), so they need the same guard. + DdTableDeal deal = one_suit_each(); + deal.cards[0][0] &= ~0x4000u; + + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(calc_dd_table(deal, &table), RETURN_CARD_COUNT); } TEST(CalcTableValidation, CppContextOverloadRejectsUnbalancedDeal) { - DdTableDeal deal = one_suit_each(); - deal.cards[0][0] &= ~0x4000u; + DdTableDeal deal = one_suit_each(); + deal.cards[0][0] &= ~0x4000u; - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - SolverContext ctx; - EXPECT_EQ(calc_dd_table(ctx, deal, &table), RETURN_CARD_COUNT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + SolverContext ctx; + EXPECT_EQ(calc_dd_table(ctx, deal, &table), RETURN_CARD_COUNT); } TEST(CalcTableValidation, CppPbnOverloadRejectsShortDeal) { - DdTableDealPBN const deal = pbn_deal(kShortOneCard); + DdTableDealPBN const deal = pbn_deal(kShortOneCard); - DdTableResults table; - std::memset(&table, 0, sizeof(table)); - EXPECT_EQ(calc_dd_table_pbn(deal, &table), RETURN_CARD_COUNT); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); + EXPECT_EQ(calc_dd_table_pbn(deal, &table), RETURN_CARD_COUNT); } TEST(CalcTableValidation, CShimRejectsUnbalancedDeal) { - // dds_c_calc_dd_table delegates to the C++ overload guarded above. - DdTableDeal deal = one_suit_each(); - deal.cards[0][0] &= ~0x4000u; + // dds_c_calc_dd_table delegates to the C++ overload guarded above. + DdTableDeal deal = one_suit_each(); + deal.cards[0][0] &= ~0x4000u; - DdTableResults table; - std::memset(&table, 0, sizeof(table)); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); - DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); - ASSERT_NE(ctx, nullptr); - EXPECT_EQ(dds_c_calc_dd_table(ctx, &deal, &table), RETURN_CARD_COUNT); - dds_c_destroy_solvercontext(ctx); + DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); + ASSERT_NE(ctx, nullptr); + EXPECT_EQ(dds_c_calc_dd_table(ctx, &deal, &table), RETURN_CARD_COUNT); + dds_c_destroy_solvercontext(ctx); } TEST(CalcTableValidation, CShimPbnRejectsShortDeal) { - DdTableDealPBN const deal = pbn_deal(kShortOneCard); + DdTableDealPBN const deal = pbn_deal(kShortOneCard); - DdTableResults table; - std::memset(&table, 0, sizeof(table)); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); - DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); - ASSERT_NE(ctx, nullptr); - EXPECT_EQ(dds_c_calc_dd_table_pbn(ctx, &deal, &table), RETURN_CARD_COUNT); - dds_c_destroy_solvercontext(ctx); + DDS_C_SOLVER_CTX ctx = dds_c_create_solvercontext_default(); + ASSERT_NE(ctx, nullptr); + EXPECT_EQ(dds_c_calc_dd_table_pbn(ctx, &deal, &table), RETURN_CARD_COUNT); + dds_c_destroy_solvercontext(ctx); } // --------------------------------------------------------------------------- @@ -226,77 +226,77 @@ TEST(CalcTableValidation, CShimPbnRejectsShortDeal) TEST(CalcTableValidation, CalcAllTablesRejectsOversizedTableCount) { - DdTableDeals deals; - std::memset(&deals, 0, sizeof(deals)); - deals.no_of_tables = MAXNOOFTABLES * DDS_STRAINS + 1; - for (int i = 0; i < MAXNOOFTABLES * DDS_STRAINS; i++) - deals.deals[i] = one_suit_each(); - - DdTablesRes res; - std::memset(&res, 0, sizeof(res)); - AllParResults par; - std::memset(&par, 0, sizeof(par)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), - RETURN_TOO_MANY_TABLES); + DdTableDeals deals; + std::memset(&deals, 0, sizeof(deals)); + deals.no_of_tables = MAXNOOFTABLES * DDS_STRAINS + 1; + for (int i = 0; i < MAXNOOFTABLES * DDS_STRAINS; i++) + deals.deals[i] = one_suit_each(); + + DdTablesRes res; + std::memset(&res, 0, sizeof(res)); + AllParResults par; + std::memset(&par, 0, sizeof(par)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), + RETURN_TOO_MANY_TABLES); } TEST(CalcTableValidation, CalcAllTablesRejectsNegativeTableCount) { - DdTableDeals deals; - std::memset(&deals, 0, sizeof(deals)); - deals.no_of_tables = -1; - - DdTablesRes res; - std::memset(&res, 0, sizeof(res)); - AllParResults par; - std::memset(&par, 0, sizeof(par)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), - RETURN_TOO_MANY_TABLES); + DdTableDeals deals; + std::memset(&deals, 0, sizeof(deals)); + deals.no_of_tables = -1; + + DdTablesRes res; + std::memset(&res, 0, sizeof(res)); + AllParResults par; + std::memset(&par, 0, sizeof(par)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), + RETURN_TOO_MANY_TABLES); } TEST(CalcTableValidation, CalcAllTablesWithZeroDealsSolvesNothing) { - // With no deals the board-building loop writes nothing, but the board count - // was derived from a last-index variable initialized to 0 and so claimed - // one board -- solving an uninitialized entry of a stack-local Boards. - // MemorySanitizer reports it; found by the calc_all_tables fuzz harness. - DdTableDeals deals; - std::memset(&deals, 0, sizeof(deals)); - deals.no_of_tables = 0; - - DdTablesRes res; - std::memset(&res, 0, sizeof(res)); - AllParResults par; - std::memset(&par, 0, sizeof(par)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), RETURN_NO_FAULT); - EXPECT_EQ(res.no_of_boards, 0); + // With no deals the board-building loop writes nothing, but the board count + // was derived from a last-index variable initialized to 0 and so claimed + // one board -- solving an uninitialized entry of a stack-local Boards. + // MemorySanitizer reports it; found by the calc_all_tables fuzz harness. + DdTableDeals deals; + std::memset(&deals, 0, sizeof(deals)); + deals.no_of_tables = 0; + + DdTablesRes res; + std::memset(&res, 0, sizeof(res)); + AllParResults par; + std::memset(&par, 0, sizeof(par)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ(CalcAllTables(&deals, -1, filter, &res, &par), RETURN_NO_FAULT); + EXPECT_EQ(res.no_of_boards, 0); } TEST(CalcTableValidation, CalcAllTablesPbnRejectsOversizedTableCount) { - // Before the guard this copied no_of_tables records into a fixed-size - // local DdTableDeals, overflowing it on the stack. - auto deals = std::make_unique(); - std::memset(deals.get(), 0, sizeof(DdTableDealsPBN)); - deals->no_of_tables = MAXNOOFTABLES * DDS_STRAINS + 64; - for (int i = 0; i < MAXNOOFTABLES * DDS_STRAINS; i++) - std::strncpy(deals->deals[i].cards, kLegalDeal, + // Before the guard this copied no_of_tables records into a fixed-size + // local DdTableDeals, overflowing it on the stack. + auto deals = std::make_unique(); + std::memset(deals.get(), 0, sizeof(DdTableDealsPBN)); + deals->no_of_tables = MAXNOOFTABLES * DDS_STRAINS + 64; + for (int i = 0; i < MAXNOOFTABLES * DDS_STRAINS; i++) + std::strncpy(deals->deals[i].cards, kLegalDeal, sizeof(deals->deals[i].cards) - 1); - auto res = std::make_unique(); - std::memset(res.get(), 0, sizeof(DdTablesRes)); - auto par = std::make_unique(); - std::memset(par.get(), 0, sizeof(AllParResults)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + auto res = std::make_unique(); + std::memset(res.get(), 0, sizeof(DdTablesRes)); + auto par = std::make_unique(); + std::memset(par.get(), 0, sizeof(AllParResults)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - EXPECT_EQ(CalcAllTablesPBN(deals.get(), -1, filter, res.get(), par.get()), - RETURN_TOO_MANY_TABLES); + EXPECT_EQ(CalcAllTablesPBN(deals.get(), -1, filter, res.get(), par.get()), + RETURN_TOO_MANY_TABLES); } // --------------------------------------------------------------------------- @@ -305,151 +305,151 @@ TEST(CalcTableValidation, CalcAllTablesPbnRejectsOversizedTableCount) TEST(DumpInputSafety, OutOfRangeTrickSuitAndRankAreReportedNotIndexed) { - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 0; - deal.first = 0; - for (int k = 0; k < 3; k++) - { - deal.currentTrickSuit[k] = 7; // card_suit has 5 entries - deal.currentTrickRank[k] = 99; // card_rank has 16 entries - } - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 0; + deal.first = 0; + for (int k = 0; k < 3; k++) + { + deal.currentTrickSuit[k] = 7; // card_suit has 5 entries + deal.currentTrickRank[k] = 99; // card_rank has 16 entries + } + + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); } TEST(DumpInputSafety, TrickSuitOfFourIsRejectedNotRenderedAsNoTrump) { - // 4 is a legal trump (no-trump) but not a legal trick suit. Sharing one - // bound between the two would render it as "N" in the dump and hide the - // value that was rejected. - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 4; // no-trump: legal - deal.first = 0; - deal.currentTrickSuit[0] = 4; // not a legal suit - deal.currentTrickRank[0] = 5; - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); + // 4 is a legal trump (no-trump) but not a legal trick suit. Sharing one + // bound between the two would render it as "N" in the dump and hide the + // value that was rejected. + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 4; // no-trump: legal + deal.first = 0; + deal.currentTrickSuit[0] = 4; // not a legal suit + deal.currentTrickRank[0] = 5; + + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); } TEST(DumpInputSafety, OutOfRangeTrumpIsReportedNotIndexed) { - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 99; - deal.first = 0; - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_TRUMP_WRONG); + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 99; + deal.first = 0; + + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_TRUMP_WRONG); } TEST(DumpInputSafety, OutOfRangeFirstIsReportedNotIndexed) { - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 0; - deal.first = 99; - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_FIRST_WRONG); -} + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 0; + deal.first = 99; -TEST(DumpInputSafety, NegativeTrickValuesAreReportedNotIndexed) -{ - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 0; - deal.first = 0; - deal.currentTrickSuit[0] = -3; - deal.currentTrickRank[0] = -7; - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_FIRST_WRONG); } -TEST(DumpInputSafety, UncheckedTrickSuitIsNotUsedAsSubscript) +TEST(DumpInputSafety, NegativeTrickValuesAreReportedNotIndexed) { - // board_range_checks() only validates currentTrickSuit[k] when the matching - // rank is non-zero, but hand_rel_first is derived from the card count, so - // board_value_checks() could reach an unchecked suit and index remainCards - // out of bounds. Found by the solve_board fuzz harness. - Deal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.trump = 4; - deal.first = 0; - deal.currentTrickSuit[1] = 24832; // never validated: rank below is zero - deal.remainCards[0][2] = 0x100; - deal.remainCards[1][1] = 0x40; - deal.remainCards[2][2] = 0x40; - deal.remainCards[3][2] = 0x2000; - deal.remainCards[3][3] = 0x4000; - - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); - EXPECT_EQ(SolveBoard(deal, 0, 3, 0, &fut, 0), RETURN_SUIT_OR_RANK); -} + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 0; + deal.first = 0; + deal.currentTrickSuit[0] = -3; + deal.currentTrickRank[0] = -7; -TEST(CalcTableValidation, CalcAllTablesPbnXRejectsOverflowingCountWithoutAllocating) -{ - // numDeals * included would overflow, so the result is already decided. - // The preflight must run before the vector allocation and the O(numDeals) - // conversion loop; otherwise this either exhausts memory or reports - // RETURN_UNKNOWN_FAULT from a caught bad_alloc. - DdTableResults results; - std::memset(&results, 0, sizeof(results)); - ParResults par; - std::memset(&par, 0, sizeof(par)); - int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - DdTableDealPBN one; - std::memset(&one, 0, sizeof(one)); - std::strncpy(one.cards, kLegalDeal, sizeof(one.cards) - 1); - - EXPECT_EQ(CalcAllTablesPBNX(2147483647, &one, -1, filter, &results, &par, 1), - RETURN_TOO_MANY_TABLES); - EXPECT_EQ(CalcAllTablesX(2147483647, nullptr, -1, filter, &results, &par, 1), - RETURN_UNKNOWN_FAULT); // null array caught before the count + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + EXPECT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK); } -TEST(DumpInputSafety, RejectedRanksAreShownRawNotAsSentinels) +TEST(DumpInputSafety, UncheckedTrickSuitIsNotUsedAsSubscript) { - // card_rank[] holds sentinels 'x' at 0..1 and '-' at 15, but a trick rank is - // only legal in 2..14. Bounding by the array size rendered a rejected rank - // of 1 or 15 as a sentinel character, hiding the value in dump.txt. - for (int bad_rank : {1, 15}) - { - std::remove("dump.txt"); - + // board_range_checks() only validates currentTrickSuit[k] when the matching + // rank is non-zero, but hand_rel_first is derived from the card count, so + // board_value_checks() could reach an unchecked suit and index remainCards + // out of bounds. Found by the solve_board fuzz harness. Deal deal; std::memset(&deal, 0, sizeof(deal)); - deal.trump = 0; + deal.trump = 4; deal.first = 0; - deal.currentTrickSuit[0] = 0; - deal.currentTrickRank[0] = bad_rank; + deal.currentTrickSuit[1] = 24832; // never validated: rank below is zero + deal.remainCards[0][2] = 0x100; + deal.remainCards[1][1] = 0x40; + deal.remainCards[2][2] = 0x40; + deal.remainCards[3][2] = 0x2000; + deal.remainCards[3][3] = 0x4000; FutureTricks fut; std::memset(&fut, 0, sizeof(fut)); - ASSERT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK) - << "rank " << bad_rank; + EXPECT_EQ(SolveBoard(deal, 0, 3, 0, &fut, 0), RETURN_SUIT_OR_RANK); +} - std::ifstream dump("dump.txt"); - if (!dump) - continue; // DDS_NO_DUMP_ON_ERROR build: nothing to check. +TEST(CalcTableValidation, CalcAllTablesPbnXRejectsOverflowingCountWithoutAllocating) +{ + // numDeals * included would overflow, so the result is already decided. + // The preflight must run before the vector allocation and the O(numDeals) + // conversion loop; otherwise this either exhausts memory or reports + // RETURN_UNKNOWN_FAULT from a caught bad_alloc. + DdTableResults results; + std::memset(&results, 0, sizeof(results)); + ParResults par; + std::memset(&par, 0, sizeof(par)); + int const filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + DdTableDealPBN one; + std::memset(&one, 0, sizeof(one)); + std::strncpy(one.cards, kLegalDeal, sizeof(one.cards) - 1); + + EXPECT_EQ(CalcAllTablesPBNX(2147483647, &one, -1, filter, &results, &par, 1), + RETURN_TOO_MANY_TABLES); + EXPECT_EQ(CalcAllTablesX(2147483647, nullptr, -1, filter, &results, &par, 1), + RETURN_UNKNOWN_FAULT); // null array caught before the count +} - std::string const text((std::istreambuf_iterator(dump)), +TEST(DumpInputSafety, RejectedRanksAreShownRawNotAsSentinels) +{ + // card_rank[] holds sentinels 'x' at 0..1 and '-' at 15, but a trick rank is + // only legal in 2..14. Bounding by the array size rendered a rejected rank + // of 1 or 15 as a sentinel character, hiding the value in dump.txt. + for (int bad_rank : {1, 15}) + { + std::remove("dump.txt"); + + Deal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.trump = 0; + deal.first = 0; + deal.currentTrickSuit[0] = 0; + deal.currentTrickRank[0] = bad_rank; + + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); + ASSERT_EQ(SolveBoard(deal, -1, 1, 1, &fut, 0), RETURN_SUIT_OR_RANK) + << "rank " << bad_rank; + + std::ifstream dump("dump.txt"); + if (!dump) + continue; // DDS_NO_DUMP_ON_ERROR build: nothing to check. + + std::string const text((std::istreambuf_iterator(dump)), std::istreambuf_iterator()); - EXPECT_NE(text.find("?(" + std::to_string(bad_rank) + ")"), std::string::npos) - << "rank " << bad_rank << " not reported raw in dump.txt"; - } - std::remove("dump.txt"); + EXPECT_NE(text.find("?(" + std::to_string(bad_rank) + ")"), std::string::npos) + << "rank " << bad_rank << " not reported raw in dump.txt"; + } + std::remove("dump.txt"); } } // namespace diff --git a/library/tests/dtest.cpp b/library/tests/dtest.cpp index f309728f0..cfc6ad518 100644 --- a/library/tests/dtest.cpp +++ b/library/tests/dtest.cpp @@ -11,7 +11,7 @@ #include #include #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) - #include + #include #endif #include @@ -24,29 +24,29 @@ #include EM_JS(void, dtest_schedule_pthread_clean_exit, (int code), { - var exitCode = code; - var attempts = 100; - var finish = function () { - if (typeof PThread !== 'undefined') { - var workers = [].concat( - PThread.unusedWorkers || [], - PThread.runningWorkers || []); - var pending = workers.some(function (w) { return !w.loaded; }); - if (pending && attempts-- > 0) { + var exitCode = code; + var attempts = 100; + var finish = function () { + if (typeof PThread !== 'undefined') { + var workers = [].concat( + PThread.unusedWorkers || [], + PThread.runningWorkers || []); + var pending = workers.some(function (w) { return !w.loaded; }); + if (pending && attempts-- > 0) { + setTimeout(finish, 0); + return; + } + if (PThread.terminateAllThreads) + PThread.terminateAllThreads(); + } + if (typeof process !== 'undefined') + process.exit(exitCode); + }; + // Node processes Worker messages before setImmediate callbacks. + if (typeof setImmediate === 'function') + setImmediate(finish); + else setTimeout(finish, 0); - return; - } - if (PThread.terminateAllThreads) - PThread.terminateAllThreads(); - } - if (typeof process !== 'undefined') - process.exit(exitCode); - }; - // Node processes Worker messages before setImmediate callbacks. - if (typeof setImmediate === 'function') - setImmediate(finish); - else - setTimeout(finish, 0); }); // Join C++ workers, let pending Worker "loaded" messages flush, then tear down @@ -55,9 +55,9 @@ EM_JS(void, dtest_schedule_pthread_clean_exit, (int code), { // precreated PTHREAD_POOL_SIZE. [[noreturn]] static void dtest_emscripten_clean_exit(const int code) { - dds::internal::shutdown_parallel_boards_pool(); - dtest_schedule_pthread_clean_exit(code); - emscripten_exit_with_live_runtime(); + dds::internal::shutdown_parallel_boards_pool(); + dtest_schedule_pthread_clean_exit(code); + emscripten_exit_with_live_runtime(); } #endif @@ -70,24 +70,24 @@ OptionsType options; int main(int argc, char * argv[]) { - read_args(argc, argv); + read_args(argc, argv); - SetResources(options.memory_mb_, 0); + SetResources(options.memory_mb_, 0); - DDSInfo info; - GetDDSInfo(&info); - cout << info.systemString << endl; - if (options.num_threads_ == 0) - cout << "dtest worker threads: auto\n"; - else - cout << "dtest worker threads: " << options.num_threads_ << "\n"; + DDSInfo info; + GetDDSInfo(&info); + cout << info.systemString << endl; + if (options.num_threads_ == 0) + cout << "dtest worker threads: auto\n"; + else + cout << "dtest worker threads: " << options.num_threads_ << "\n"; - const int status = real_main(argc, argv); + const int status = real_main(argc, argv); #if defined(__EMSCRIPTEN__) - dtest_emscripten_clean_exit(status); + dtest_emscripten_clean_exit(status); #else - // Restore normal termination so destructors / atexit handlers run. - exit(status); + // Restore normal termination so destructors / atexit handlers run. + exit(status); #endif } diff --git a/library/tests/dtest_parallel.cpp b/library/tests/dtest_parallel.cpp index 4d496deb6..ac075cbff 100644 --- a/library/tests/dtest_parallel.cpp +++ b/library/tests/dtest_parallel.cpp @@ -20,77 +20,77 @@ int dtest_effective_threads(const int requested, const int workload) { - if (workload <= 1) - return 1; + if (workload <= 1) + return 1; - const unsigned hw = std::thread::hardware_concurrency(); - const int auto_count = hw > 0 ? static_cast(hw) : 1; + const unsigned hw = std::thread::hardware_concurrency(); + const int auto_count = hw > 0 ? static_cast(hw) : 1; - int n = requested > 0 ? requested : auto_count; - n = std::max(1, std::min(n, workload)); - return n; + int n = requested > 0 ? requested : auto_count; + n = std::max(1, std::min(n, workload)); + return n; } int dtest_run_parallel( - const int count, - const int requested_threads, - const std::function & body) + const int count, + const int requested_threads, + const std::function & body) { - if (count <= 0) - return RETURN_NO_FAULT; + if (count <= 0) + return RETURN_NO_FAULT; - const int nthreads = dtest_effective_threads(requested_threads, count); - if (nthreads <= 1) - { - for (int i = 0; i < count; ++i) + const int nthreads = dtest_effective_threads(requested_threads, count); + if (nthreads <= 1) { - const int rc = body(i); - if (rc != RETURN_NO_FAULT) - return rc; + for (int i = 0; i < count; ++i) + { + const int rc = body(i); + if (rc != RETURN_NO_FAULT) + return rc; + } + return RETURN_NO_FAULT; } - return RETURN_NO_FAULT; - } - std::atomic next{0}; - std::atomic first_error{0}; - - auto worker = [&] { - for (;;) + std::atomic next{0}; + std::atomic first_error{0}; + + auto worker = [&] { + for (;;) + { + const int i = next.fetch_add(1, std::memory_order_relaxed); + if (i >= count || first_error.load(std::memory_order_relaxed) != 0) + break; + + const int rc = body(i); + if (rc != RETURN_NO_FAULT) + { + int expected = 0; + first_error.compare_exchange_strong( + expected, rc, std::memory_order_relaxed); + break; + } + } + }; + + std::vector threads; + threads.reserve(static_cast(nthreads)); + try + { + for (int t = 0; t < nthreads; ++t) + threads.emplace_back(worker); + } + catch (...) { - const int i = next.fetch_add(1, std::memory_order_relaxed); - if (i >= count || first_error.load(std::memory_order_relaxed) != 0) - break; - - const int rc = body(i); - if (rc != RETURN_NO_FAULT) - { - int expected = 0; - first_error.compare_exchange_strong( - expected, rc, std::memory_order_relaxed); - break; - } + for (auto & th : threads) + if (th.joinable()) + th.join(); + throw; } - }; - - std::vector threads; - threads.reserve(static_cast(nthreads)); - try - { - for (int t = 0; t < nthreads; ++t) - threads.emplace_back(worker); - } - catch (...) - { + for (auto & th : threads) - if (th.joinable()) th.join(); - throw; - } - - for (auto & th : threads) - th.join(); - const int err = first_error.load(std::memory_order_relaxed); - return err != 0 ? err : RETURN_NO_FAULT; + const int err = first_error.load(std::memory_order_relaxed); + return err != 0 ? err : RETURN_NO_FAULT; } diff --git a/library/tests/dtest_parallel.hpp b/library/tests/dtest_parallel.hpp index 0e88414aa..4fba1e54a 100644 --- a/library/tests/dtest_parallel.hpp +++ b/library/tests/dtest_parallel.hpp @@ -23,6 +23,6 @@ int dtest_effective_threads(int requested, int workload); /// @p body must return RETURN_NO_FAULT (1) on success. /// @return First non-success code from @p body, or RETURN_NO_FAULT. int dtest_run_parallel( - int count, - int requested_threads, - const std::function & body); + int count, + int requested_threads, + const std::function & body); diff --git a/library/tests/fuzz/calc_all_tables_fuzz.cpp b/library/tests/fuzz/calc_all_tables_fuzz.cpp index b42c82a7a..b5ed05b95 100644 --- a/library/tests/fuzz/calc_all_tables_fuzz.cpp +++ b/library/tests/fuzz/calc_all_tables_fuzz.cpp @@ -57,155 +57,155 @@ constexpr char kFillPbn[] = "N:A... Q... K... J..."; class Reader { public: - Reader(const uint8_t * data, size_t size) : data_(data), left_(size) {} - - auto take(void * out, size_t n) -> bool - { - if (left_ < n) - return false; - if (n == 0) - return true; // memcpy's source is declared nonnull; data_ may be null. - std::memcpy(out, data_, n); - data_ += n; - left_ -= n; - return true; - } - - auto byte(uint8_t fallback) -> uint8_t - { - uint8_t v = fallback; - take(&v, 1); - return v; - } - - auto remaining() const -> size_t { return left_; } + Reader(const uint8_t * data, size_t size) : data_(data), left_(size) {} + + auto take(void * out, size_t n) -> bool + { + if (left_ < n) + return false; + if (n == 0) + return true; // memcpy's source is declared nonnull; data_ may be null. + std::memcpy(out, data_, n); + data_ += n; + left_ -= n; + return true; + } + + auto byte(uint8_t fallback) -> uint8_t + { + uint8_t v = fallback; + take(&v, 1); + return v; + } + + auto remaining() const -> size_t { return left_; } private: - const uint8_t * data_; - size_t left_; + const uint8_t * data_; + size_t left_; }; /// The binary equivalent of kFillPbn: one spade each, so every slot is a /// valid deal that solves immediately. auto legal_deal() -> DdTableDeal { - DdTableDeal deal; - std::memset(&deal, 0, sizeof(deal)); - deal.cards[0][0] = 0x4000; // A - deal.cards[1][0] = 0x1000; // Q - deal.cards[2][0] = 0x2000; // K - deal.cards[3][0] = 0x0800; // J - return deal; + DdTableDeal deal; + std::memset(&deal, 0, sizeof(deal)); + deal.cards[0][0] = 0x4000; // A + deal.cards[1][0] = 0x1000; // Q + deal.cards[2][0] = 0x2000; // K + deal.cards[3][0] = 0x0800; // J + return deal; } } // namespace extern "C" auto LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) -> int { - // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose - // thread argument is ignored, so it never capped anything here. Worker - // counts come from each call's explicit maxThreads instead. - InitializeStaticMemory(); - return 0; + // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose + // thread argument is ignored, so it never capped anything here. Worker + // counts come from each call's explicit maxThreads instead. + InitializeStaticMemory(); + return 0; } extern "C" auto LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -> int { - Reader reader(data, size); + Reader reader(data, size); - // Passed to the library verbatim: bounding it is the library's job. - int32_t raw_count = 0; - if (!reader.take(&raw_count, sizeof(raw_count))) - return 0; + // Passed to the library verbatim: bounding it is the library's job. + int32_t raw_count = 0; + if (!reader.take(&raw_count, sizeof(raw_count))) + return 0; - int trump_filter[DDS_STRAINS]; - for (int k = 0; k < DDS_STRAINS; k++) - trump_filter[k] = (reader.byte(0) & 1); + int trump_filter[DDS_STRAINS]; + for (int k = 0; k < DDS_STRAINS; k++) + trump_filter[k] = (reader.byte(0) & 1); - // -1 disables the par calculation; 0..3 select a vulnerability. Values - // outside that are worth passing too. - int const mode = static_cast(reader.byte(0) % 8) - 2; + // -1 disables the par calculation; 0..3 select a vulnerability. Values + // outside that are worth passing too. + int const mode = static_cast(reader.byte(0) % 8) - 2; - uint8_t const selector = reader.byte(0); - int const populate = static_cast(reader.byte(0) % (kMaxPopulated + 1)); + uint8_t const selector = reader.byte(0); + int const populate = static_cast(reader.byte(0) % (kMaxPopulated + 1)); - auto results = std::make_unique(); - std::memset(results.get(), 0, sizeof(DdTablesRes)); - auto par = std::make_unique(); - std::memset(par.get(), 0, sizeof(AllParResults)); + auto results = std::make_unique(); + std::memset(results.get(), 0, sizeof(DdTablesRes)); + auto par = std::make_unique(); + std::memset(par.get(), 0, sizeof(AllParResults)); - switch (selector % 3) - { - case 0: + switch (selector % 3) { - auto deals = std::make_unique(); - std::memset(deals.get(), 0, sizeof(DdTableDeals)); - deals->no_of_tables = raw_count; - - DdTableDeal const fill = legal_deal(); - for (auto & slot : deals->deals) - slot = fill; - - // Perturb the first few from the input so malformed deals are - // reachable too, while the rest stay valid. - for (int i = 0; i < populate; i++) - reader.take(&deals->deals[i], sizeof(DdTableDeal)); - - CalcAllTablesN(deals.get(), mode, trump_filter, + case 0: + { + auto deals = std::make_unique(); + std::memset(deals.get(), 0, sizeof(DdTableDeals)); + deals->no_of_tables = raw_count; + + DdTableDeal const fill = legal_deal(); + for (auto & slot : deals->deals) + slot = fill; + + // Perturb the first few from the input so malformed deals are + // reachable too, while the rest stay valid. + for (int i = 0; i < populate; i++) + reader.take(&deals->deals[i], sizeof(DdTableDeal)); + + CalcAllTablesN(deals.get(), mode, trump_filter, results.get(), par.get(), 1); - break; - } - - case 1: - { - auto deals = std::make_unique(); - std::memset(deals.get(), 0, sizeof(DdTableDealsPBN)); - deals->no_of_tables = raw_count; - - for (auto & slot : deals->deals) - std::memcpy(slot.cards, kFillPbn, sizeof(kFillPbn)); - - for (int i = 0; i < populate; i++) - { - // cards is a fixed char[80] the library reads as a C string, so the - // harness terminates it; feeding a non-terminated array would report - // a harness bug as a library one. - auto & cards = deals->deals[i].cards; - reader.take(cards, sizeof(cards) - 1); - cards[sizeof(cards) - 1] = '\0'; - } - - CalcAllTablesPBNN(deals.get(), mode, trump_filter, - results.get(), par.get(), 1); - break; - } - - default: - { - // Count and array must agree here: see the file comment. - int num_deals = raw_count < 0 ? 0 : raw_count % (kMaxDealsForX + 1); - - std::vector deals(static_cast(num_deals)); - for (int i = 0; i < num_deals; i++) - { - deals[static_cast(i)] = legal_deal(); - if (i < populate) - reader.take(&deals[static_cast(i)], sizeof(DdTableDeal)); - } - - // CalcAllTablesX writes results[m] and par[m] for m < num_deals (see - // the writes near the end of CalcAllTablesX), so one entry each per - // deal. The +1 keeps .data() non-null when num_deals is 0. - std::vector table_results( - static_cast(num_deals) + 1); - std::vector par_results( - static_cast(num_deals) + 1); - - CalcAllTablesX(num_deals, deals.data(), mode, trump_filter, + break; + } + + case 1: + { + auto deals = std::make_unique(); + std::memset(deals.get(), 0, sizeof(DdTableDealsPBN)); + deals->no_of_tables = raw_count; + + for (auto & slot : deals->deals) + std::memcpy(slot.cards, kFillPbn, sizeof(kFillPbn)); + + for (int i = 0; i < populate; i++) + { + // cards is a fixed char[80] the library reads as a C string, so the + // harness terminates it; feeding a non-terminated array would report + // a harness bug as a library one. + auto & cards = deals->deals[i].cards; + reader.take(cards, sizeof(cards) - 1); + cards[sizeof(cards) - 1] = '\0'; + } + + CalcAllTablesPBNN(deals.get(), mode, trump_filter, + results.get(), par.get(), 1); + break; + } + + default: + { + // Count and array must agree here: see the file comment. + int num_deals = raw_count < 0 ? 0 : raw_count % (kMaxDealsForX + 1); + + std::vector deals(static_cast(num_deals)); + for (int i = 0; i < num_deals; i++) + { + deals[static_cast(i)] = legal_deal(); + if (i < populate) + reader.take(&deals[static_cast(i)], sizeof(DdTableDeal)); + } + + // CalcAllTablesX writes results[m] and par[m] for m < num_deals (see + // the writes near the end of CalcAllTablesX), so one entry each per + // deal. The +1 keeps .data() non-null when num_deals is 0. + std::vector table_results( + static_cast(num_deals) + 1); + std::vector par_results( + static_cast(num_deals) + 1); + + CalcAllTablesX(num_deals, deals.data(), mode, trump_filter, table_results.data(), par_results.data(), 1); - break; + break; + } } - } - return 0; + return 0; } diff --git a/library/tests/fuzz/calc_dd_table_pbn_fuzz.cpp b/library/tests/fuzz/calc_dd_table_pbn_fuzz.cpp index 54e0dec7c..844e57855 100644 --- a/library/tests/fuzz/calc_dd_table_pbn_fuzz.cpp +++ b/library/tests/fuzz/calc_dd_table_pbn_fuzz.cpp @@ -21,34 +21,34 @@ extern "C" auto LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) -> int { - // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose - // thread argument is ignored, so it never capped anything here. Worker - // counts come from each call's explicit maxThreads instead. - InitializeStaticMemory(); - return 0; + // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose + // thread argument is ignored, so it never capped anything here. Worker + // counts come from each call's explicit maxThreads instead. + InitializeStaticMemory(); + return 0; } extern "C" auto LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -> int { - DdTableDealPBN table_deal; - std::memset(&table_deal, 0, sizeof(table_deal)); + DdTableDealPBN table_deal; + std::memset(&table_deal, 0, sizeof(table_deal)); - size_t const n = size < sizeof(table_deal.cards) - 1 + size_t const n = size < sizeof(table_deal.cards) - 1 ? size : sizeof(table_deal.cards) - 1; - // libFuzzer may pass (nullptr, 0), and memcpy's source is declared nonnull, - // so an empty copy from a null pointer is undefined even though it moves - // nothing. UBSan on glibc reports it; guard rather than rely on the libc. - if (n > 0) - std::memcpy(table_deal.cards, data, n); - table_deal.cards[n] = '\0'; + // libFuzzer may pass (nullptr, 0), and memcpy's source is declared nonnull, + // so an empty copy from a null pointer is undefined even though it moves + // nothing. UBSan on glibc reports it; guard rather than rely on the libc. + if (n > 0) + std::memcpy(table_deal.cards, data, n); + table_deal.cards[n] = '\0'; - DdTableResults table; - std::memset(&table, 0, sizeof(table)); + DdTableResults table; + std::memset(&table, 0, sizeof(table)); - // The non-N entry point delegates with maxThreads = 0, which selects - // hardware concurrency; call the N variant so one input uses one worker. - CalcDDtablePBNN(table_deal, &table, 1); + // The non-N entry point delegates with maxThreads = 0, which selects + // hardware concurrency; call the N variant so one input uses one worker. + CalcDDtablePBNN(table_deal, &table, 1); - return 0; + return 0; } diff --git a/library/tests/fuzz/fuzz_corpus_main.cpp b/library/tests/fuzz/fuzz_corpus_main.cpp index 1abbda63a..92d6655e7 100644 --- a/library/tests/fuzz/fuzz_corpus_main.cpp +++ b/library/tests/fuzz/fuzz_corpus_main.cpp @@ -43,174 +43,174 @@ namespace fs = std::filesystem; auto env_path(char const * name) -> std::string { - char const * value = std::getenv(name); - return value == nullptr ? std::string() : std::string(value); + char const * value = std::getenv(name); + return value == nullptr ? std::string() : std::string(value); } /// Files under a runfiles tree, if one contains `relpath` as a directory. auto files_from_tree(std::string const & relpath) -> std::vector { - std::vector found; + std::vector found; - for (char const * key : {"RUNFILES_DIR", "TEST_SRCDIR"}) - { - std::string const root = env_path(key); - if (root.empty()) - continue; - - for (fs::path const & candidate : - {fs::path(root) / relpath, fs::path(root) / "_main" / relpath}) + for (char const * key : {"RUNFILES_DIR", "TEST_SRCDIR"}) { - std::error_code ec; - if (!fs::is_directory(candidate, ec)) - continue; + std::string const root = env_path(key); + if (root.empty()) + continue; - for (auto const & entry : fs::recursive_directory_iterator(candidate, ec)) - if (entry.is_regular_file()) - found.push_back(entry.path()); - - if (!found.empty()) - return found; + for (fs::path const & candidate : + {fs::path(root) / relpath, fs::path(root) / "_main" / relpath}) + { + std::error_code ec; + if (!fs::is_directory(candidate, ec)) + continue; + + for (auto const & entry : fs::recursive_directory_iterator(candidate, ec)) + if (entry.is_regular_file()) + found.push_back(entry.path()); + + if (!found.empty()) + return found; + } } - } - return found; + return found; } /// Files under `relpath` named by the runfiles manifest (Windows). auto files_from_manifest(std::string const & relpath) -> std::vector { - std::vector found; + std::vector found; - std::string const manifest = env_path("RUNFILES_MANIFEST_FILE"); - if (manifest.empty()) - return found; + std::string const manifest = env_path("RUNFILES_MANIFEST_FILE"); + if (manifest.empty()) + return found; - std::ifstream in(manifest); - if (!in) - return found; + std::ifstream in(manifest); + if (!in) + return found; - // Manifest keys use forward slashes and may or may not carry the repo name. - std::string const with_repo = "_main/" + relpath + "/"; - std::string const bare = relpath + "/"; + // Manifest keys use forward slashes and may or may not carry the repo name. + std::string const with_repo = "_main/" + relpath + "/"; + std::string const bare = relpath + "/"; - std::string line; - while (std::getline(in, line)) - { - if (line.empty() || line.front() == '[' || line.front() == ' ') - continue; + std::string line; + while (std::getline(in, line)) + { + if (line.empty() || line.front() == '[' || line.front() == ' ') + continue; - auto const space = line.find(' '); - if (space == std::string::npos) - continue; + auto const space = line.find(' '); + if (space == std::string::npos) + continue; - std::string const key = line.substr(0, space); - std::string const value = line.substr(space + 1); - if (value.empty()) - continue; + std::string const key = line.substr(0, space); + std::string const value = line.substr(space + 1); + if (value.empty()) + continue; - if (key.rfind(with_repo, 0) != 0 && key.rfind(bare, 0) != 0) - continue; + if (key.rfind(with_repo, 0) != 0 && key.rfind(bare, 0) != 0) + continue; - std::error_code ec; - if (fs::is_regular_file(value, ec)) - found.emplace_back(value); - } + std::error_code ec; + if (fs::is_regular_file(value, ec)) + found.emplace_back(value); + } - return found; + return found; } /// Every file under `arg`, whether it names a runfiles directory, a plain /// directory, or a single file. auto corpus_files(std::string const & arg) -> std::vector { - std::vector found = files_from_tree(arg); - if (!found.empty()) - return found; + std::vector found = files_from_tree(arg); + if (!found.empty()) + return found; - found = files_from_manifest(arg); - if (!found.empty()) - return found; + found = files_from_manifest(arg); + if (!found.empty()) + return found; - // Direct invocation from a shell, where the path is simply on disk. - std::error_code ec; - if (fs::is_directory(arg, ec)) - { - for (auto const & entry : fs::recursive_directory_iterator(arg, ec)) - if (entry.is_regular_file()) - found.push_back(entry.path()); - } - else if (fs::is_regular_file(arg, ec)) - { - found.emplace_back(arg); - } - - return found; + // Direct invocation from a shell, where the path is simply on disk. + std::error_code ec; + if (fs::is_directory(arg, ec)) + { + for (auto const & entry : fs::recursive_directory_iterator(arg, ec)) + if (entry.is_regular_file()) + found.push_back(entry.path()); + } + else if (fs::is_regular_file(arg, ec)) + { + found.emplace_back(arg); + } + + return found; } auto run_one(fs::path const & path) -> bool { - std::ifstream in(path, std::ios::binary); - if (!in) - { - std::fprintf(stderr, "cannot open %s\n", path.string().c_str()); - return false; - } - - std::vector const bytes( - (std::istreambuf_iterator(in)), std::istreambuf_iterator()); - - LLVMFuzzerTestOneInput(bytes.data(), bytes.size()); - return true; + std::ifstream in(path, std::ios::binary); + if (!in) + { + std::fprintf(stderr, "cannot open %s\n", path.string().c_str()); + return false; + } + + std::vector const bytes( + (std::istreambuf_iterator(in)), std::istreambuf_iterator()); + + LLVMFuzzerTestOneInput(bytes.data(), bytes.size()); + return true; } } // namespace auto main(int argc, char ** argv) -> int { - // libFuzzer calls this before the first input; the replay driver must too, - // or harnesses relying on it (e.g. InitializeStaticMemory) run unconfigured. - LLVMFuzzerInitialize(&argc, &argv); - - // Degenerate inputs every harness must survive, independent of the corpus. - uint8_t const zero[32] = {0}; - uint8_t const ones[32] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - LLVMFuzzerTestOneInput(nullptr, 0); - LLVMFuzzerTestOneInput(zero, sizeof(zero)); - LLVMFuzzerTestOneInput(ones, sizeof(ones)); - - int files = 0; - bool ok = true; - - for (int i = 1; i < argc; i++) - { - std::vector const found = corpus_files(argv[i]); - - if (found.empty()) + // libFuzzer calls this before the first input; the replay driver must too, + // or harnesses relying on it (e.g. InitializeStaticMemory) run unconfigured. + LLVMFuzzerInitialize(&argc, &argv); + + // Degenerate inputs every harness must survive, independent of the corpus. + uint8_t const zero[32] = {0}; + uint8_t const ones[32] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + LLVMFuzzerTestOneInput(nullptr, 0); + LLVMFuzzerTestOneInput(zero, sizeof(zero)); + LLVMFuzzerTestOneInput(ones, sizeof(ones)); + + int files = 0; + bool ok = true; + + for (int i = 1; i < argc; i++) { - std::fprintf(stderr, "no corpus files under: %s\n", argv[i]); - ok = false; - continue; + std::vector const found = corpus_files(argv[i]); + + if (found.empty()) + { + std::fprintf(stderr, "no corpus files under: %s\n", argv[i]); + ok = false; + continue; + } + + for (fs::path const & path : found) + { + ok = run_one(path) && ok; + files++; + } } - for (fs::path const & path : found) + // A corpus that silently resolves to nothing would make this test vacuous. + if (argc > 1 && files == 0) { - ok = run_one(path) && ok; - files++; + std::fprintf(stderr, "corpus resolved to 0 files\n"); + return 1; } - } - - // A corpus that silently resolves to nothing would make this test vacuous. - if (argc > 1 && files == 0) - { - std::fprintf(stderr, "corpus resolved to 0 files\n"); - return 1; - } - std::printf("replayed %d corpus file(s)\n", files); - return ok ? 0 : 1; + std::printf("replayed %d corpus file(s)\n", files); + return ok ? 0 : 1; } diff --git a/library/tests/fuzz/par_fuzz.cpp b/library/tests/fuzz/par_fuzz.cpp index 1c02df27d..8a1fe2f38 100644 --- a/library/tests/fuzz/par_fuzz.cpp +++ b/library/tests/fuzz/par_fuzz.cpp @@ -25,69 +25,69 @@ namespace { class Reader { public: - Reader(const uint8_t * data, size_t size) : data_(data), left_(size) {} - - auto take(void * out, size_t n) -> bool - { - if (left_ < n) - return false; - if (n == 0) - return true; // memcpy's source is declared nonnull; data_ may be null. - std::memcpy(out, data_, n); - data_ += n; - left_ -= n; - return true; - } + Reader(const uint8_t * data, size_t size) : data_(data), left_(size) {} + + auto take(void * out, size_t n) -> bool + { + if (left_ < n) + return false; + if (n == 0) + return true; // memcpy's source is declared nonnull; data_ may be null. + std::memcpy(out, data_, n); + data_ += n; + left_ -= n; + return true; + } private: - const uint8_t * data_; - size_t left_; + const uint8_t * data_; + size_t left_; }; } // namespace extern "C" auto LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) -> int { - // Nothing to configure; defined so the corpus-replay driver links. - return 0; + // Nothing to configure; defined so the corpus-replay driver links. + return 0; } extern "C" auto LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -> int { - Reader reader(data, size); + Reader reader(data, size); - DdTableResults table; - if (!reader.take(&table, sizeof(table))) - return 0; + DdTableResults table; + if (!reader.take(&table, sizeof(table))) + return 0; - uint8_t selector = 0; - if (!reader.take(&selector, sizeof(selector))) - return 0; + uint8_t selector = 0; + if (!reader.take(&selector, sizeof(selector))) + return 0; - // Exercise the full legal vulnerability range plus out-of-range values, - // since DealerPar() indexes a lookup table with this parameter. - int const vulnerable = static_cast(selector % 8) - 2; - int const dealer = static_cast((selector / 8) % 6) - 1; + // Exercise the full legal vulnerability range plus out-of-range values, + // since DealerPar() indexes a lookup table with this parameter. + int const vulnerable = static_cast(selector % 8) - 2; + int const dealer = static_cast((selector / 8) % 6) - 1; - ParResults par_results; - std::memset(&par_results, 0, sizeof(par_results)); - Par(&table, &par_results, vulnerable); + ParResults par_results; + std::memset(&par_results, 0, sizeof(par_results)); + Par(&table, &par_results, vulnerable); - ParResultsDealer sides[2]; - std::memset(sides, 0, sizeof(sides)); - SidesPar(&table, sides, vulnerable); + ParResultsDealer sides[2]; + std::memset(sides, 0, sizeof(sides)); + SidesPar(&table, sides, vulnerable); - ParResultsMaster sides_bin[2]; - std::memset(sides_bin, 0, sizeof(sides_bin)); - SidesParBin(&table, sides_bin, vulnerable); + ParResultsMaster sides_bin[2]; + std::memset(sides_bin, 0, sizeof(sides_bin)); + SidesParBin(&table, sides_bin, vulnerable); - ParResultsDealer dealer_res; - std::memset(&dealer_res, 0, sizeof(dealer_res)); - DealerPar(&table, &dealer_res, dealer, vulnerable); + ParResultsDealer dealer_res; + std::memset(&dealer_res, 0, sizeof(dealer_res)); + DealerPar(&table, &dealer_res, dealer, vulnerable); - ParResultsMaster dealer_bin; - std::memset(&dealer_bin, 0, sizeof(dealer_bin)); - DealerParBin(&table, &dealer_bin, dealer, vulnerable); + ParResultsMaster dealer_bin; + std::memset(&dealer_bin, 0, sizeof(dealer_bin)); + DealerParBin(&table, &dealer_bin, dealer, vulnerable); - return 0; + return 0; } diff --git a/library/tests/fuzz/pbn_fuzz.cpp b/library/tests/fuzz/pbn_fuzz.cpp index 367e15d70..bd9309c66 100644 --- a/library/tests/fuzz/pbn_fuzz.cpp +++ b/library/tests/fuzz/pbn_fuzz.cpp @@ -22,25 +22,25 @@ extern "C" auto LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) -> int { - // Nothing to configure; defined so the corpus-replay driver links. - return 0; + // Nothing to configure; defined so the corpus-replay driver links. + return 0; } extern "C" auto LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -> int { - // PBN deal strings are bounded in practice; keep inputs in that range so - // the fuzzer spends its budget on parser states rather than on length. - if (size > 4096) - return 0; + // PBN deal strings are bounded in practice; keep inputs in that range so + // the fuzzer spends its budget on parser states rather than on length. + if (size > 4096) + return 0; - // Same nonnull caveat as the memcpy in calc_dd_table_pbn_fuzz.cpp: building - // a string from (nullptr, 0) is undefined, so handle the empty case first. - std::string const deal = - size == 0 ? std::string() - : std::string(reinterpret_cast(data), size); + // Same nonnull caveat as the memcpy in calc_dd_table_pbn_fuzz.cpp: building + // a string from (nullptr, 0) is undefined, so handle the empty case first. + std::string const deal = + size == 0 ? std::string() + : std::string(reinterpret_cast(data), size); - unsigned int remain_cards[DDS_HANDS][DDS_SUITS]; - convert_from_pbn(deal.c_str(), remain_cards); + unsigned int remain_cards[DDS_HANDS][DDS_SUITS]; + convert_from_pbn(deal.c_str(), remain_cards); - return 0; + return 0; } diff --git a/library/tests/fuzz/solve_board_fuzz.cpp b/library/tests/fuzz/solve_board_fuzz.cpp index 41cd52770..d470c3d8d 100644 --- a/library/tests/fuzz/solve_board_fuzz.cpp +++ b/library/tests/fuzz/solve_board_fuzz.cpp @@ -21,34 +21,34 @@ extern "C" auto LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) -> int { - // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose - // thread argument is ignored, so it never capped anything here. Worker - // counts come from each call's explicit maxThreads instead. - InitializeStaticMemory(); - return 0; + // SetMaxThreads() is a deprecated alias of InitializeStaticMemory() whose + // thread argument is ignored, so it never capped anything here. Worker + // counts come from each call's explicit maxThreads instead. + InitializeStaticMemory(); + return 0; } extern "C" auto LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -> int { - // trump, first, currentTrickSuit[3], currentTrickRank[3], remainCards[4][4], - // plus one selector byte for target/solutions/mode. - Deal deal; - if (size < sizeof(deal) + 1) - return 0; + // trump, first, currentTrickSuit[3], currentTrickRank[3], remainCards[4][4], + // plus one selector byte for target/solutions/mode. + Deal deal; + if (size < sizeof(deal) + 1) + return 0; - std::memcpy(&deal, data, sizeof(deal)); - uint8_t const selector = data[sizeof(deal)]; + std::memcpy(&deal, data, sizeof(deal)); + uint8_t const selector = data[sizeof(deal)]; - // Cover the documented ranges and a little either side of them, so the - // parameter validation is exercised as well as the search. - int const target = static_cast(selector % 16) - 1; - int const solutions = static_cast((selector / 16) % 5) - 1; - int const mode = static_cast((selector / 80) % 4) - 1; + // Cover the documented ranges and a little either side of them, so the + // parameter validation is exercised as well as the search. + int const target = static_cast(selector % 16) - 1; + int const solutions = static_cast((selector / 16) % 5) - 1; + int const mode = static_cast((selector / 80) % 4) - 1; - FutureTricks fut; - std::memset(&fut, 0, sizeof(fut)); + FutureTricks fut; + std::memset(&fut, 0, sizeof(fut)); - SolveBoard(deal, target, solutions, mode, &fut, 0); + SolveBoard(deal, target, solutions, mode, &fut, 0); - return 0; + return 0; } diff --git a/library/tests/heuristic_sorting/dispatch_findex_test.cpp b/library/tests/heuristic_sorting/dispatch_findex_test.cpp index 93e34ad2d..067bd9b1e 100644 --- a/library/tests/heuristic_sorting/dispatch_findex_test.cpp +++ b/library/tests/heuristic_sorting/dispatch_findex_test.cpp @@ -21,82 +21,82 @@ static RelRanksType rel_ranks[kNumRelRanks] = {}; struct DispatchFixture { - Pos tpos{}; - MoveType best_move{}; - MoveType best_move_tt{}; - RelRanksType* rel = rel_ranks; - TrackType track{}; - MoveType moves_expected[kNumMoves]{}; - MoveType moves_dispatched[kNumMoves]{}; + Pos tpos{}; + MoveType best_move{}; + MoveType best_move_tt{}; + RelRanksType* rel = rel_ranks; + TrackType track{}; + MoveType moves_expected[kNumMoves]{}; + MoveType moves_dispatched[kNumMoves]{}; }; void fill_position(DispatchFixture& f, const int curr_hand, const int lead_suit) { - for (int h = 0; h < DDS_HANDS; h++) - { + for (int h = 0; h < DDS_HANDS; h++) + { + for (int s = 0; s < DDS_SUITS; s++) + { + f.tpos.rank_in_suit[h][s] = + static_cast(0x0155u << ((h + s) % 3)); + f.tpos.length[h][s] = static_cast(3 + ((h + s) % 3)); + } + f.tpos.hand_dist[h] = 13; + } + for (int s = 0; s < DDS_SUITS; s++) + { + f.tpos.aggr[s] = 0x1FF5; + f.tpos.winner[s].rank = 14; + f.tpos.winner[s].hand = (s + 1) % DDS_HANDS; + f.tpos.second_best[s].rank = 13; + f.tpos.second_best[s].hand = (s + 2) % DDS_HANDS; + } + + f.track.lead_suit = lead_suit; + f.track.move[0] = ExtCard{lead_suit, 8, 0}; + f.track.move[1] = ExtCard{lead_suit, 10, 0}; + f.track.move[2] = ExtCard{lead_suit, 6, 0}; + f.track.high[1] = 1; + f.track.high[2] = 1; for (int s = 0; s < DDS_SUITS; s++) + f.track.removed_ranks[s] = 0x0022; + + for (int k = 0; k < kNumMoves; k++) { - f.tpos.rank_in_suit[h][s] = - static_cast(0x0155u << ((h + s) % 3)); - f.tpos.length[h][s] = static_cast(3 + ((h + s) % 3)); + f.moves_expected[k] = MoveType{lead_suit, 12 - 2 * k, 0, 0}; + f.moves_dispatched[k] = f.moves_expected[k]; } - f.tpos.hand_dist[h] = 13; - } - for (int s = 0; s < DDS_SUITS; s++) - { - f.tpos.aggr[s] = 0x1FF5; - f.tpos.winner[s].rank = 14; - f.tpos.winner[s].hand = (s + 1) % DDS_HANDS; - f.tpos.second_best[s].rank = 13; - f.tpos.second_best[s].hand = (s + 2) % DDS_HANDS; - } - - f.track.lead_suit = lead_suit; - f.track.move[0] = ExtCard{lead_suit, 8, 0}; - f.track.move[1] = ExtCard{lead_suit, 10, 0}; - f.track.move[2] = ExtCard{lead_suit, 6, 0}; - f.track.high[1] = 1; - f.track.high[2] = 1; - for (int s = 0; s < DDS_SUITS; s++) - f.track.removed_ranks[s] = 0x0022; - - for (int k = 0; k < kNumMoves; k++) - { - f.moves_expected[k] = MoveType{lead_suit, 12 - 2 * k, 0, 0}; - f.moves_dispatched[k] = f.moves_expected[k]; - } } HeuristicContext make_context(DispatchFixture& f, MoveType* mply, - const int curr_hand, const int lead_hand, - const int lead_suit) + const int curr_hand, const int lead_hand, + const int lead_suit) { - HeuristicContext ctx = { - f.tpos, - f.best_move, - f.best_move_tt, - f.rel, - mply, - kNumMoves, // num_moves - 0, // last_num_moves - DDS_NOTRUMP, - lead_suit, // suit under consideration - &f.track, - 5, // curr_trick - curr_hand, - lead_hand, - lead_suit, - }; - for (int s = 0; s < DDS_SUITS; s++) - ctx.removed_ranks[s] = f.track.removed_ranks[s]; - ctx.move1_rank = f.track.move[1].rank; - ctx.high1 = f.track.high[1]; - ctx.move1_suit = f.track.move[1].suit; - ctx.move2_rank = f.track.move[2].rank; - ctx.move2_suit = f.track.move[2].suit; - ctx.high2 = f.track.high[2]; - ctx.lead0_rank = f.track.move[0].rank; - return ctx; + HeuristicContext ctx = { + f.tpos, + f.best_move, + f.best_move_tt, + f.rel, + mply, + kNumMoves, // num_moves + 0, // last_num_moves + DDS_NOTRUMP, + lead_suit, // suit under consideration + &f.track, + 5, // curr_trick + curr_hand, + lead_hand, + lead_suit, + }; + for (int s = 0; s < DDS_SUITS; s++) + ctx.removed_ranks[s] = f.track.removed_ranks[s]; + ctx.move1_rank = f.track.move[1].rank; + ctx.high1 = f.track.high[1]; + ctx.move1_suit = f.track.move[1].suit; + ctx.move2_rank = f.track.move[2].rank; + ctx.move2_suit = f.track.move[2].suit; + ctx.high2 = f.track.high[2]; + ctx.lead0_rank = f.track.move[0].rank; + return ctx; } } // namespace @@ -106,55 +106,55 @@ HeuristicContext make_context(DispatchFixture& f, MoveType* mply, // scramble MergeSort. TEST(DispatchWeightCase, InvalidWeightCaseFallsBackToBasicNt0Weights) { - for (const int bad_value : {2, 3, 16, -1, 99}) - { - DispatchFixture f; - const int lead_hand = 0; - fill_position(f, lead_hand, /*lead_suit=*/0); - - constexpr int kStale = 0x7f0f0f0f; - for (int k = 0; k < kNumMoves; k++) - { - f.moves_expected[k].weight = 0; - f.moves_dispatched[k].weight = kStale; - } - - HeuristicContext expected = make_context( - f, f.moves_expected, lead_hand, lead_hand, 0); - HeuristicContext dispatched = make_context( - f, f.moves_dispatched, lead_hand, lead_hand, 0); - - weight_alloc_nt0(expected); - call_heuristic(dispatched, static_cast(bad_value)); - - for (int k = 0; k < kNumMoves; k++) + for (const int bad_value : {2, 3, 16, -1, 99}) { - EXPECT_NE(f.moves_dispatched[k].weight, kStale) - << "weight_case=" << bad_value << " move=" << k - << " left a stale weight"; - EXPECT_EQ(f.moves_expected[k].weight, f.moves_dispatched[k].weight) - << "weight_case=" << bad_value << " move=" << k - << " must match weight_alloc_nt0 fallback"; + DispatchFixture f; + const int lead_hand = 0; + fill_position(f, lead_hand, /*lead_suit=*/0); + + constexpr int kStale = 0x7f0f0f0f; + for (int k = 0; k < kNumMoves; k++) + { + f.moves_expected[k].weight = 0; + f.moves_dispatched[k].weight = kStale; + } + + HeuristicContext expected = make_context( + f, f.moves_expected, lead_hand, lead_hand, 0); + HeuristicContext dispatched = make_context( + f, f.moves_dispatched, lead_hand, lead_hand, 0); + + weight_alloc_nt0(expected); + call_heuristic(dispatched, static_cast(bad_value)); + + for (int k = 0; k < kNumMoves; k++) + { + EXPECT_NE(f.moves_dispatched[k].weight, kStale) + << "weight_case=" << bad_value << " move=" << k + << " left a stale weight"; + EXPECT_EQ(f.moves_expected[k].weight, f.moves_dispatched[k].weight) + << "weight_case=" << bad_value << " move=" << k + << " must match weight_alloc_nt0 fallback"; + } } - } } // Known WeightCase values must select the matching weight_alloc_* helper. TEST(DispatchWeightCase, Nt0DispatchesToWeightAllocNt0) { - DispatchFixture f; - const int lead_hand = 0; - fill_position(f, lead_hand, /*lead_suit=*/0); + DispatchFixture f; + const int lead_hand = 0; + fill_position(f, lead_hand, /*lead_suit=*/0); - HeuristicContext expected = make_context( - f, f.moves_expected, lead_hand, lead_hand, 0); - HeuristicContext dispatched = make_context( - f, f.moves_dispatched, lead_hand, lead_hand, 0); + HeuristicContext expected = make_context( + f, f.moves_expected, lead_hand, lead_hand, 0); + HeuristicContext dispatched = make_context( + f, f.moves_dispatched, lead_hand, lead_hand, 0); - weight_alloc_nt0(expected); - call_heuristic(dispatched, WeightCase::Nt0); + weight_alloc_nt0(expected); + call_heuristic(dispatched, WeightCase::Nt0); - for (int k = 0; k < kNumMoves; k++) - EXPECT_EQ(f.moves_expected[k].weight, f.moves_dispatched[k].weight) - << "move=" << k; + for (int k = 0; k < kNumMoves; k++) + EXPECT_EQ(f.moves_expected[k].weight, f.moves_dispatched[k].weight) + << "move=" << k; } diff --git a/library/tests/heuristic_sorting/heuristic_sorting_test.cpp b/library/tests/heuristic_sorting/heuristic_sorting_test.cpp index 51ddb680d..b00c5e883 100644 --- a/library/tests/heuristic_sorting/heuristic_sorting_test.cpp +++ b/library/tests/heuristic_sorting/heuristic_sorting_test.cpp @@ -16,246 +16,246 @@ class HeuristicSortingUnitTest : public ::testing::Test { protected: - HeuristicSortingUnitTest() = default; - - // Helper function to create a basic position - Pos createBasicPosition() { - Pos tpos = {}; - // Initialize with some basic data - for (int hand = 0; hand < DDS_HANDS; hand++) { - for (int suit = 0; suit < DDS_SUITS; suit++) { - tpos.rank_in_suit[hand][suit] = 0; - tpos.length[hand][suit] = 0; - } + HeuristicSortingUnitTest() = default; + + // Helper function to create a basic position + Pos createBasicPosition() { + Pos tpos = {}; + // Initialize with some basic data + for (int hand = 0; hand < DDS_HANDS; hand++) { + for (int suit = 0; suit < DDS_SUITS; suit++) { + tpos.rank_in_suit[hand][suit] = 0; + tpos.length[hand][suit] = 0; + } + } + return tpos; + } + + // Helper function to create a basic context with modifiable position + HeuristicContext createBasicContext(Pos& tpos, MoveType* mply, int numMoves) { + static MoveType bestMove = {}; + static MoveType bestMoveTT = {}; + static RelRanksType thrp_rel[1] = {}; + static TrackType track = {}; + + return HeuristicContext { + tpos, + bestMove, + bestMoveTT, + thrp_rel, + mply, + numMoves, + 0, // lastNumMoves + 0, // trump (spades) + 0, // suit (spades) + &track, + 1, // currTrick + 0, // currHand + 0, // leadHand + 0 // leadSuit + }; } - return tpos; - } - - // Helper function to create a basic context with modifiable position - HeuristicContext createBasicContext(Pos& tpos, MoveType* mply, int numMoves) { - static MoveType bestMove = {}; - static MoveType bestMoveTT = {}; - static RelRanksType thrp_rel[1] = {}; - static TrackType track = {}; - - return HeuristicContext { - tpos, - bestMove, - bestMoveTT, - thrp_rel, - mply, - numMoves, - 0, // lastNumMoves - 0, // trump (spades) - 0, // suit (spades) - &track, - 1, // currTrick - 0, // currHand - 0, // leadHand - 0 // leadSuit - }; - } }; // Test weight_alloc_trump0 function TEST_F(HeuristicSortingUnitTest, TestWeightAllocTrump0SetsWeight) { - MoveType mply[10]; - Pos tpos = createBasicPosition(); - - // Initialize a move - mply[0].suit = 0; // Spades - mply[0].rank = 14; // Ace - mply[0].weight = 0; - mply[0].sequence = 1; - - auto context = createBasicContext(tpos, mply, 1); - - // Modify context settings (we can now modify the fields through const_cast) - const_cast(context.trump) = 1; // Hearts are trump - - // Call the function under test - weight_alloc_trump0(context); - - // The weight should have been modified - EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump0"; - - std::cout << "Testweight_alloc_trump0 passed. Weight: " << mply[0].weight << std::endl; + MoveType mply[10]; + Pos tpos = createBasicPosition(); + + // Initialize a move + mply[0].suit = 0; // Spades + mply[0].rank = 14; // Ace + mply[0].weight = 0; + mply[0].sequence = 1; + + auto context = createBasicContext(tpos, mply, 1); + + // Modify context settings (we can now modify the fields through const_cast) + const_cast(context.trump) = 1; // Hearts are trump + + // Call the function under test + weight_alloc_trump0(context); + + // The weight should have been modified + EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump0"; + + std::cout << "Testweight_alloc_trump0 passed. Weight: " << mply[0].weight << std::endl; } // Test weight_alloc_trump0 function TEST_F(HeuristicSortingUnitTest, TestWeightAllocTrump0) { - MoveType mply[10]; - Pos tpos = createBasicPosition(); - - // Initialize a move - mply[0].suit = 0; // Spades - mply[0].rank = 14; // Ace - mply[0].weight = 0; - mply[0].sequence = 1; - - auto context = createBasicContext(tpos, mply, 1); - - // Modify context settings (these are not const) - const_cast(context.trump) = 1; // Hearts are trump - - // Call the function under test - weight_alloc_trump0(context); - - // The weight should have been modified - EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump0"; - - std::cout << "Testweight_alloc_trump0 passed. Weight: " << mply[0].weight << std::endl; + MoveType mply[10]; + Pos tpos = createBasicPosition(); + + // Initialize a move + mply[0].suit = 0; // Spades + mply[0].rank = 14; // Ace + mply[0].weight = 0; + mply[0].sequence = 1; + + auto context = createBasicContext(tpos, mply, 1); + + // Modify context settings (these are not const) + const_cast(context.trump) = 1; // Hearts are trump + + // Call the function under test + weight_alloc_trump0(context); + + // The weight should have been modified + EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump0"; + + std::cout << "Testweight_alloc_trump0 passed. Weight: " << mply[0].weight << std::endl; } // Test weight_alloc_nt0 function TEST_F(HeuristicSortingUnitTest, TestWeightAllocNt0) { - MoveType mply[10]; - Pos tpos = createBasicPosition(); - - // Initialize a move - mply[0].suit = 0; // Spades - mply[0].rank = 14; // Ace - mply[0].weight = 0; - mply[0].sequence = 1; - - auto context = createBasicContext(tpos, mply, 1); - - // Modify context settings - const_cast(context.trump) = DDS_NOTRUMP; // No trump - - // Call the function under test - weight_alloc_nt0(context); - - // The weight should have been modified - EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_nt0"; - - std::cout << "Testweight_alloc_nt0 passed. Weight: " << mply[0].weight << std::endl; + MoveType mply[10]; + Pos tpos = createBasicPosition(); + + // Initialize a move + mply[0].suit = 0; // Spades + mply[0].rank = 14; // Ace + mply[0].weight = 0; + mply[0].sequence = 1; + + auto context = createBasicContext(tpos, mply, 1); + + // Modify context settings + const_cast(context.trump) = DDS_NOTRUMP; // No trump + + // Call the function under test + weight_alloc_nt0(context); + + // The weight should have been modified + EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_nt0"; + + std::cout << "Testweight_alloc_nt0 passed. Weight: " << mply[0].weight << std::endl; } // Test weight_alloc_trump_notvoid1 function TEST_F(HeuristicSortingUnitTest, TestWeightAllocTrumpNotvoid1) { - MoveType mply[10]; - Pos tpos = createBasicPosition(); - - // Initialize a move - mply[0].suit = 0; // Spades (lead suit) - mply[0].rank = 12; // Queen - mply[0].weight = 0; - mply[0].sequence = 1; - - auto context = createBasicContext(tpos, mply, 1); - - // Modify context settings - const_cast(context.trump) = 1; // Hearts are trump - const_cast(context.lead_suit) = 0; // Spades led - const_cast(context.curr_hand) = 1; // Second hand to play - - // Call the function under test - weight_alloc_trump_notvoid1(context); - - // The weight should have been modified - EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump_notvoid1"; - - std::cout << "Testweight_alloc_trump_notvoid1 passed. Weight: " << mply[0].weight << std::endl; + MoveType mply[10]; + Pos tpos = createBasicPosition(); + + // Initialize a move + mply[0].suit = 0; // Spades (lead suit) + mply[0].rank = 12; // Queen + mply[0].weight = 0; + mply[0].sequence = 1; + + auto context = createBasicContext(tpos, mply, 1); + + // Modify context settings + const_cast(context.trump) = 1; // Hearts are trump + const_cast(context.lead_suit) = 0; // Spades led + const_cast(context.curr_hand) = 1; // Second hand to play + + // Call the function under test + weight_alloc_trump_notvoid1(context); + + // The weight should have been modified + EXPECT_NE(mply[0].weight, 0) << "Weight should be set by weight_alloc_trump_notvoid1"; + + std::cout << "Testweight_alloc_trump_notvoid1 passed. Weight: " << mply[0].weight << std::endl; } // Test all missing WeightAlloc functions for complete coverage TEST_F(HeuristicSortingUnitTest, TestAllMissingWeightAllocFunctions) { - MoveType mply[5]; - Pos tpos = createBasicPosition(); - - // Initialize multiple moves for better testing - for (int i = 0; i < 5; i++) { - mply[i].suit = i % 4; - mply[i].rank = 14 - i; - mply[i].weight = 0; - mply[i].sequence = 1 << (14 - i); - } - - auto context = createBasicContext(tpos, mply, 5); - - // Test Position 1 functions - std::cout << "Testing Position 1 functions..." << std::endl; - - // weight_alloc_nt_notvoid1 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = DDS_NOTRUMP; - const_cast(context.curr_hand) = 1; - const_cast(context.lead_suit) = 0; - weight_alloc_nt_notvoid1(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_nt_notvoid1 should assign weights"; - - // weight_alloc_trump_void1 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = 1; - weight_alloc_trump_void1(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_trump_void1 should assign weights"; - - // weight_alloc_nt_void1 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = DDS_NOTRUMP; - weight_alloc_nt_void1(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_nt_void1 should assign weights"; - - // Test Position 2 functions - std::cout << "Testing Position 2 functions..." << std::endl; - const_cast(context.curr_hand) = 2; - - // weight_alloc_trump_notvoid2 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = 1; - weight_alloc_trump_notvoid2(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_trump_notvoid2 should assign weights"; - - // weight_alloc_nt_notvoid2 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = DDS_NOTRUMP; - weight_alloc_nt_notvoid2(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_nt_notvoid2 should assign weights"; - - // weight_alloc_trump_void2 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = 1; - weight_alloc_trump_void2(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_trump_void2 should assign weights"; - - // weight_alloc_nt_void2 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = DDS_NOTRUMP; - weight_alloc_nt_void2(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_nt_void2 should assign weights"; - - // Test Position 3 functions - std::cout << "Testing Position 3 functions..." << std::endl; - const_cast(context.curr_hand) = 3; - - // weight_alloc_combined_notvoid3 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = 1; - weight_alloc_combined_notvoid3(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_combined_notvoid3 should assign weights"; - - // weight_alloc_trump_void3 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = 1; - weight_alloc_trump_void3(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_trump_void3 should assign weights"; - - // weight_alloc_nt_void3 - for (int i = 0; i < 5; i++) mply[i].weight = 0; - const_cast(context.trump) = DDS_NOTRUMP; - weight_alloc_nt_void3(context); - EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) - << "weight_alloc_nt_void3 should assign weights"; - - std::cout << "All 13 WeightAlloc functions tested successfully!" << std::endl; + MoveType mply[5]; + Pos tpos = createBasicPosition(); + + // Initialize multiple moves for better testing + for (int i = 0; i < 5; i++) { + mply[i].suit = i % 4; + mply[i].rank = 14 - i; + mply[i].weight = 0; + mply[i].sequence = 1 << (14 - i); + } + + auto context = createBasicContext(tpos, mply, 5); + + // Test Position 1 functions + std::cout << "Testing Position 1 functions..." << std::endl; + + // weight_alloc_nt_notvoid1 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = DDS_NOTRUMP; + const_cast(context.curr_hand) = 1; + const_cast(context.lead_suit) = 0; + weight_alloc_nt_notvoid1(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_nt_notvoid1 should assign weights"; + + // weight_alloc_trump_void1 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = 1; + weight_alloc_trump_void1(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_trump_void1 should assign weights"; + + // weight_alloc_nt_void1 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = DDS_NOTRUMP; + weight_alloc_nt_void1(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_nt_void1 should assign weights"; + + // Test Position 2 functions + std::cout << "Testing Position 2 functions..." << std::endl; + const_cast(context.curr_hand) = 2; + + // weight_alloc_trump_notvoid2 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = 1; + weight_alloc_trump_notvoid2(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_trump_notvoid2 should assign weights"; + + // weight_alloc_nt_notvoid2 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = DDS_NOTRUMP; + weight_alloc_nt_notvoid2(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_nt_notvoid2 should assign weights"; + + // weight_alloc_trump_void2 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = 1; + weight_alloc_trump_void2(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_trump_void2 should assign weights"; + + // weight_alloc_nt_void2 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = DDS_NOTRUMP; + weight_alloc_nt_void2(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_nt_void2 should assign weights"; + + // Test Position 3 functions + std::cout << "Testing Position 3 functions..." << std::endl; + const_cast(context.curr_hand) = 3; + + // weight_alloc_combined_notvoid3 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = 1; + weight_alloc_combined_notvoid3(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_combined_notvoid3 should assign weights"; + + // weight_alloc_trump_void3 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = 1; + weight_alloc_trump_void3(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_trump_void3 should assign weights"; + + // weight_alloc_nt_void3 + for (int i = 0; i < 5; i++) mply[i].weight = 0; + const_cast(context.trump) = DDS_NOTRUMP; + weight_alloc_nt_void3(context); + EXPECT_TRUE(std::any_of(mply, mply + 5, [](const MoveType& m) { return m.weight != 0; })) + << "weight_alloc_nt_void3 should assign weights"; + + std::cout << "All 13 WeightAlloc functions tested successfully!" << std::endl; } diff --git a/library/tests/heuristic_sorting/merge_scratch_test.cpp b/library/tests/heuristic_sorting/merge_scratch_test.cpp index 7ad572a36..9c18bf1a7 100644 --- a/library/tests/heuristic_sorting/merge_scratch_test.cpp +++ b/library/tests/heuristic_sorting/merge_scratch_test.cpp @@ -13,33 +13,33 @@ // varying weights, call MergeSort via Sort(), and verify descending order. TEST(MergeScratchTest, OrdersByWeightDescending) { - Moves mv; - const int trick = 12; - const int relHand = 0; + Moves mv; + const int trick = 12; + const int relHand = 0; - // Build a tiny list of moves with out-of-order weights - MovePlyType& list = mv.moveList[trick][relHand]; - list.last = 4; - list.current = 0; - // Initialize entries - for (int i = 0; i <= list.last; ++i) { - list.move[i].suit = 0; - list.move[i].rank = i + 2; - list.move[i].sequence = 0; - list.move[i].weight = 0; - } - list.move[0].weight = 5; - list.move[1].weight = 10; - list.move[2].weight = 7; - list.move[3].weight = 9; - list.move[4].weight = 3; + // Build a tiny list of moves with out-of-order weights + MovePlyType& list = mv.moveList[trick][relHand]; + list.last = 4; + list.current = 0; + // Initialize entries + for (int i = 0; i <= list.last; ++i) { + list.move[i].suit = 0; + list.move[i].rank = i + 2; + list.move[i].sequence = 0; + list.move[i].weight = 0; + } + list.move[0].weight = 5; + list.move[1].weight = 10; + list.move[2].weight = 7; + list.move[3].weight = 9; + list.move[4].weight = 3; - mv.Sort(trick, relHand); + mv.Sort(trick, relHand); - // Verify non-increasing order of weights - int prev = list.move[0].weight; - for (int i = 1; i <= list.last; ++i) { - EXPECT_LE(list.move[i].weight, prev); - prev = list.move[i].weight; - } + // Verify non-increasing order of weights + int prev = list.move[0].weight; + for (int i = 1; i <= list.last; ++i) { + EXPECT_LE(list.move[i].weight, prev); + prev = list.move[i].weight; + } } diff --git a/library/tests/heuristic_sorting/minimal_new_test.cpp b/library/tests/heuristic_sorting/minimal_new_test.cpp index c298d301b..2126b6780 100644 --- a/library/tests/heuristic_sorting/minimal_new_test.cpp +++ b/library/tests/heuristic_sorting/minimal_new_test.cpp @@ -13,11 +13,11 @@ TEST(MinimalNewTest, TestWeightAllocTrump0) { std::cout << "Testing minimal weight_alloc_trump0 call..." << std::endl; - + // Create minimal Pos structure Pos tpos = {}; // Note: trump is passed separately in the context - + // Initialize some basic data for (int h = 0; h < 4; h++) { for (int s = 0; s < 4; s++) { @@ -25,25 +25,25 @@ TEST(MinimalNewTest, TestWeightAllocTrump0) { tpos.rank_in_suit[h][s] = 0x7000; // Some high cards } } - + // Create moves MoveType moves[3]; moves[0] = {0, 14, 0, 0}; // Ace of spades moves[1] = {0, 13, 0, 0}; // King of spades moves[2] = {0, 12, 0, 0}; // Queen of spades - + for (int i = 0; i < 3; i++) { moves[i].weight = 0; } - + MoveType bestMove = {0, 14, 1, 0}; MoveType bestMoveTT = {0, 13, 1, 0}; RelRanksType thrp_rel = {}; - + TrackType track = {}; track.lead_hand = 0; track.lead_suit = 0; - + HeuristicContext context = { tpos, // Pos bestMove, // bestMove @@ -60,17 +60,17 @@ TEST(MinimalNewTest, TestWeightAllocTrump0) { 0, // leadHand 0 // leadSuit (spades) }; - + std::cout << "About to call weight_alloc_trump0..." << std::endl; - + // This is where the segfault likely occurs weight_alloc_trump0(context); - + std::cout << "weight_alloc_trump0 completed successfully!" << std::endl; - + for (int i = 0; i < 3; i++) { std::cout << "Move " << i << " weight: " << moves[i].weight << std::endl; } - + EXPECT_TRUE(true); // If we get here, no segfault occurred } diff --git a/library/tests/heuristic_sorting/minimal_weight_test.cpp b/library/tests/heuristic_sorting/minimal_weight_test.cpp index 9e9fb160b..e0e513a48 100644 --- a/library/tests/heuristic_sorting/minimal_weight_test.cpp +++ b/library/tests/heuristic_sorting/minimal_weight_test.cpp @@ -22,31 +22,31 @@ class MinimalWeightTest : public ::testing::Test TEST_F(MinimalWeightTest, BasicWeightAllocCall) { std::cout << "Creating basic position..." << std::endl; - + // Create a basic position structure Pos tpos; memset(&tpos, 0, sizeof(Pos)); - + // Set minimal required data tpos.length[0][0] = 4; // North has 4 spades tpos.length[1][0] = 3; // East has 3 spades tpos.length[2][0] = 3; // South has 3 spades tpos.length[3][0] = 3; // West has 3 spades - + // Create some test moves MoveType moves[3] = { {0, 14, 0, 0}, // Ace of spades {0, 13, 0, 0}, // King of spades {0, 12, 0, 0} // Queen of spades }; - + // Create other required structures MoveType bestMove = {0, 14, 1, 0}; MoveType bestMoveTT = {0, 13, 1, 0}; RelRanksType thrp_rel[4] = {}; - + std::cout << "Creating HeuristicContext..." << std::endl; - + // Create HeuristicContext using constructor syntax HeuristicContext context = { tpos, // const Pos& tpos @@ -64,20 +64,20 @@ TEST_F(MinimalWeightTest, BasicWeightAllocCall) { 0, // lead_hand 0 // lead_suit }; - + std::cout << "Calling weight_alloc_trump0..." << std::endl; - + try { weight_alloc_trump0(context); std::cout << "SUCCESS: weight_alloc_trump0 completed without crash!" << std::endl; - + // Print results for (int i = 0; i < 3; i++) { std::cout << "Move " << i << ": suit=" << moves[i].suit - << " rank=" << moves[i].rank - << " weight=" << moves[i].weight << std::endl; + << " rank=" << moves[i].rank + << " weight=" << moves[i].weight << std::endl; } - + SUCCEED(); } catch (...) { FAIL() << "weight_alloc_trump0 threw an exception"; diff --git a/library/tests/heuristic_sorting/targeted_unit_tests.cpp b/library/tests/heuristic_sorting/targeted_unit_tests.cpp index 6390667bc..f9f103dde 100644 --- a/library/tests/heuristic_sorting/targeted_unit_tests.cpp +++ b/library/tests/heuristic_sorting/targeted_unit_tests.cpp @@ -15,71 +15,71 @@ // Targeted unit tests for small helper functions and golden expectations TEST(TargetedUnitTests, RankForcesAceBasic) { - // Construct a minimal context and exercise rank_forces_ace for different cards4th - Pos tpos = {}; - memset(&tpos, 0, sizeof(tpos)); + // Construct a minimal context and exercise rank_forces_ace for different cards4th + Pos tpos = {}; + memset(&tpos, 0, sizeof(tpos)); - // Build a safe HeuristicContext using local objects - MoveType bm = {}; - MoveType bmtt = {}; - RelRanksType thrp_rel_dummy[1] = {}; - MoveType mply_dummy[1] = {}; - TrackType track_dummy = {}; + // Build a safe HeuristicContext using local objects + MoveType bm = {}; + MoveType bmtt = {}; + RelRanksType thrp_rel_dummy[1] = {}; + MoveType mply_dummy[1] = {}; + TrackType track_dummy = {}; - HeuristicContext ctx = { - tpos, // Pos - bm, // bestMove - bmtt, // bestMoveTT - thrp_rel_dummy, // thrp_rel - mply_dummy, // mply - 0, // numMoves - 0, // lastNumMoves - DDS_NOTRUMP, // trump - 0, // suit - &track_dummy, // trackp - 0, // currTrick - 0, // currHand - 0, // leadHand - 0 // leadSuit - }; + HeuristicContext ctx = { + tpos, // Pos + bm, // bestMove + bmtt, // bestMoveTT + thrp_rel_dummy, // thrp_rel + mply_dummy, // mply + 0, // numMoves + 0, // lastNumMoves + DDS_NOTRUMP, // trump + 0, // suit + &track_dummy, // trackp + 0, // currTrick + 0, // currHand + 0, // leadHand + 0 // leadSuit + }; - // Sanity: ensure function is callable and returns in-range values - int res0 = rank_forces_ace(ctx, 0); - int res1 = rank_forces_ace(ctx, 1); - int res5 = rank_forces_ace(ctx, 5); + // Sanity: ensure function is callable and returns in-range values + int res0 = rank_forces_ace(ctx, 0); + int res1 = rank_forces_ace(ctx, 1); + int res5 = rank_forces_ace(ctx, 5); - // rank_forces_ace may return -1 when no forcing rank exists; ensure value is sane - EXPECT_GE(res0, -1); - EXPECT_GE(res1, -1); - EXPECT_GE(res5, -1); - EXPECT_LE(res0, 14); - EXPECT_LE(res1, 14); - EXPECT_LE(res5, 14); + // rank_forces_ace may return -1 when no forcing rank exists; ensure value is sane + EXPECT_GE(res0, -1); + EXPECT_GE(res1, -1); + EXPECT_GE(res5, -1); + EXPECT_LE(res0, 14); + EXPECT_LE(res1, 14); + EXPECT_LE(res5, 14); } TEST(TargetedUnitTests, GetTopNumberEdgeCases) { - Pos tpos = {}; - memset(&tpos, 0, sizeof(tpos)); + Pos tpos = {}; + memset(&tpos, 0, sizeof(tpos)); - int topNumber = -1; - int mno = -1; - // Build a small HeuristicContext for get_top_number - MoveType bm = {}; - MoveType bmtt = {}; - RelRanksType thrp_rel_dummy[1] = {}; - MoveType mply_dummy[1] = {}; - TrackType track_dummy = {}; - HeuristicContext ctx = { tpos, bm, bmtt, thrp_rel_dummy, mply_dummy, 0, 0, DDS_NOTRUMP, 0, &track_dummy, 0, 0, 0, 0 }; + int topNumber = -1; + int mno = -1; + // Build a small HeuristicContext for get_top_number + MoveType bm = {}; + MoveType bmtt = {}; + RelRanksType thrp_rel_dummy[1] = {}; + MoveType mply_dummy[1] = {}; + TrackType track_dummy = {}; + HeuristicContext ctx = { tpos, bm, bmtt, thrp_rel_dummy, mply_dummy, 0, 0, DDS_NOTRUMP, 0, &track_dummy, 0, 0, 0, 0 }; - // Empty suit (ris == 0): no groups; must not index fullseq_[-1]. - get_top_number(ctx, 0, 14, topNumber, mno); - EXPECT_EQ(topNumber, -1); - EXPECT_GE(mno, 0); - EXPECT_LE(mno, 13); + // Empty suit (ris == 0): no groups; must not index fullseq_[-1]. + get_top_number(ctx, 0, 14, topNumber, mno); + EXPECT_EQ(topNumber, -1); + EXPECT_GE(mno, 0); + EXPECT_LE(mno, 13); - get_top_number(ctx, 5, 10, topNumber, mno); - EXPECT_GE(topNumber, -1); - EXPECT_LE(topNumber, 14); - EXPECT_GE(mno, 0); - EXPECT_LE(mno, 13); + get_top_number(ctx, 5, 10, topNumber, mno); + EXPECT_GE(topNumber, -1); + EXPECT_LE(topNumber, 14); + EXPECT_GE(mno, 0); + EXPECT_LE(mno, 13); } diff --git a/library/tests/heuristic_sorting/test_utils.cpp b/library/tests/heuristic_sorting/test_utils.cpp index f807b65f9..d3b1e75db 100644 --- a/library/tests/heuristic_sorting/test_utils.cpp +++ b/library/tests/heuristic_sorting/test_utils.cpp @@ -15,301 +15,301 @@ // Normalization: stable textual representation (same as serialize for now) std::string normalize_ordering(const MoveType* moves, int num_moves, bool include_scores) { - // Create an index array and sort it deterministically by: - // 1) weight (descending) - // 2) suit (ascending) - // 3) rank (descending) - // 4) sequence (ascending) - std::vector idx(num_moves); - for (int i = 0; i < num_moves; ++i) idx[i] = i; + // Create an index array and sort it deterministically by: + // 1) weight (descending) + // 2) suit (ascending) + // 3) rank (descending) + // 4) sequence (ascending) + std::vector idx(num_moves); + for (int i = 0; i < num_moves; ++i) idx[i] = i; - auto cmp = [&](int a, int b) { - // higher weight first - if (moves[a].weight != moves[b].weight) return moves[a].weight > moves[b].weight; - if (moves[a].suit != moves[b].suit) return moves[a].suit < moves[b].suit; - if (moves[a].rank != moves[b].rank) return moves[a].rank > moves[b].rank; - return moves[a].sequence < moves[b].sequence; - }; + auto cmp = [&](int a, int b) { + // higher weight first + if (moves[a].weight != moves[b].weight) return moves[a].weight > moves[b].weight; + if (moves[a].suit != moves[b].suit) return moves[a].suit < moves[b].suit; + if (moves[a].rank != moves[b].rank) return moves[a].rank > moves[b].rank; + return moves[a].sequence < moves[b].sequence; + }; - // stable sort to preserve original relative order when comparator reports equal - std::stable_sort(idx.begin(), idx.end(), cmp); + // stable sort to preserve original relative order when comparator reports equal + std::stable_sort(idx.begin(), idx.end(), cmp); - std::ostringstream out; - out << "["; - for (int k = 0; k < num_moves; ++k) { - int i = idx[k]; - if (k) out << ", "; - out << "{"; - out << "\"suit\":" << moves[i].suit << ","; - out << "\"rank\":" << moves[i].rank; - if (include_scores) out << ",\"weight\":" << moves[i].weight; - out << "}"; - } - out << "]"; - return out.str(); + std::ostringstream out; + out << "["; + for (int k = 0; k < num_moves; ++k) { + int i = idx[k]; + if (k) out << ", "; + out << "{"; + out << "\"suit\":" << moves[i].suit << ","; + out << "\"rank\":" << moves[i].rank; + if (include_scores) out << ",\"weight\":" << moves[i].weight; + out << "}"; + } + out << "]"; + return out.str(); } // Initialize relRanks table and TrackType based on a given Pos (used by fuzz tests) void init_rel_and_track(const Pos& tpos, RelRanksType* rel_table /* size 8192 assumed */, TrackType* track_p, - int cards_played, const MoveType* played_moves, int lead_hand, int trump) { - // zero track and set sane defaults - if (track_p) { - std::memset(track_p, 0, sizeof(*track_p)); - track_p->lead_hand = lead_hand; - track_p->lead_suit = 0; - for (int p = 0; p < DDS_HANDS; ++p) { - track_p->high[p] = 0; - track_p->play_suits[p] = 0; - track_p->play_ranks[p] = 0; - track_p->move[p].suit = 0; - track_p->move[p].rank = 0; - track_p->move[p].sequence = 0; - } - for (int s = 0; s < DDS_SUITS; ++s) { - track_p->removed_ranks[s] = 0; // will OR in present cards below - for (int h = 0; h < DDS_HANDS; ++h) - track_p->lowest_win[h][s] = 0; + int cards_played, const MoveType* played_moves, int lead_hand, int trump) { + // zero track and set sane defaults + if (track_p) { + std::memset(track_p, 0, sizeof(*track_p)); + track_p->lead_hand = lead_hand; + track_p->lead_suit = 0; + for (int p = 0; p < DDS_HANDS; ++p) { + track_p->high[p] = 0; + track_p->play_suits[p] = 0; + track_p->play_ranks[p] = 0; + track_p->move[p].suit = 0; + track_p->move[p].rank = 0; + track_p->move[p].sequence = 0; + } + for (int s = 0; s < DDS_SUITS; ++s) { + track_p->removed_ranks[s] = 0; // will OR in present cards below + for (int h = 0; h < DDS_HANDS; ++h) + track_p->lowest_win[h][s] = 0; + } + // default trickData + for (int s = 0; s < DDS_SUITS; ++s) track_p->trick_data.play_count[s] = 0; + track_p->trick_data.best_rank = 0; + track_p->trick_data.best_suit = 0; + track_p->trick_data.best_sequence = 0; + track_p->trick_data.rel_winner = 0; + track_p->trick_data.next_lead_hand = lead_hand; } - // default trickData - for (int s = 0; s < DDS_SUITS; ++s) track_p->trick_data.play_count[s] = 0; - track_p->trick_data.best_rank = 0; - track_p->trick_data.best_suit = 0; - track_p->trick_data.best_sequence = 0; - track_p->trick_data.rel_winner = 0; - track_p->trick_data.next_lead_hand = lead_hand; - } - - if (!rel_table) return; - // Work on a mutable copy of Pos so we can simulate cards removed by plays - Pos localPos = tpos; - if (cards_played > 0 && played_moves) { - // played_moves are in play order starting from lead_hand (absolute) - for (int i = 0; i < cards_played; ++i) { - const MoveType &m = played_moves[i]; - int absHand = (lead_hand + i) % 4; - if (m.rank > 0 && m.rank < 16) { - unsigned short mask = bit_map_rank[m.rank]; - // remove the card from localPos - localPos.rank_in_suit[absHand][m.suit] &= static_cast(~mask); - // update aggregate and lengths - localPos.aggr[m.suit] &= static_cast(~mask); - if (localPos.length[absHand][m.suit] > 0) localPos.length[absHand][m.suit]--; - if (localPos.hand_dist[absHand] > 0) localPos.hand_dist[absHand]--; - } - } + if (!rel_table) return; - // Recompute winner/second_best conservatively for each suit based on remaining cards - for (int s = 0; s < DDS_SUITS; ++s) { - localPos.winner[s].rank = 0; localPos.winner[s].hand = 0; - localPos.second_best[s].rank = 0; localPos.second_best[s].hand = 0; - for (int h = 0; h < DDS_HANDS; ++h) { - unsigned short ris = localPos.rank_in_suit[h][s]; - if (!ris) continue; - for (int r = 13; r >= 1; --r) { - if (ris & (1u << r)) { - if (r > localPos.winner[s].rank) { - localPos.second_best[s] = localPos.winner[s]; - localPos.winner[s].rank = r; - localPos.winner[s].hand = h; - } else if (r > localPos.second_best[s].rank) { - localPos.second_best[s].rank = r; - localPos.second_best[s].hand = h; + // Work on a mutable copy of Pos so we can simulate cards removed by plays + Pos localPos = tpos; + if (cards_played > 0 && played_moves) { + // played_moves are in play order starting from lead_hand (absolute) + for (int i = 0; i < cards_played; ++i) { + const MoveType &m = played_moves[i]; + int absHand = (lead_hand + i) % 4; + if (m.rank > 0 && m.rank < 16) { + unsigned short mask = bit_map_rank[m.rank]; + // remove the card from localPos + localPos.rank_in_suit[absHand][m.suit] &= static_cast(~mask); + // update aggregate and lengths + localPos.aggr[m.suit] &= static_cast(~mask); + if (localPos.length[absHand][m.suit] > 0) localPos.length[absHand][m.suit]--; + if (localPos.hand_dist[absHand] > 0) localPos.hand_dist[absHand]--; } - break; - } } - } - } - } - // Initialize rel_table[0] - for (int s = 0; s < DDS_SUITS; s++) { - for (int ord = 1; ord <= 13; ord++) { - rel_table[0].abs_rank[ord][s].hand = -1; - rel_table[0].abs_rank[ord][s].rank = 0; - } - } - - // Build handLookup from current Deal (use localPos which may have had cards removed) - int handLookup[DDS_SUITS][15]; - for (int s = 0; s < DDS_SUITS; s++) { - for (int r = 14; r >= 2; r--) { - handLookup[s][r] = 0; - for (int h = 0; h < DDS_HANDS; h++) { - if (localPos.rank_in_suit[h][s] & bit_map_rank[r]) { - handLookup[s][r] = h; - break; + // Recompute winner/second_best conservatively for each suit based on remaining cards + for (int s = 0; s < DDS_SUITS; ++s) { + localPos.winner[s].rank = 0; localPos.winner[s].hand = 0; + localPos.second_best[s].rank = 0; localPos.second_best[s].hand = 0; + for (int h = 0; h < DDS_HANDS; ++h) { + unsigned short ris = localPos.rank_in_suit[h][s]; + if (!ris) continue; + for (int r = 13; r >= 1; --r) { + if (ris & (1u << r)) { + if (r > localPos.winner[s].rank) { + localPos.second_best[s] = localPos.winner[s]; + localPos.winner[s].rank = r; + localPos.winner[s].hand = h; + } else if (r > localPos.second_best[s].rank) { + localPos.second_best[s].rank = r; + localPos.second_best[s].hand = h; + } + break; + } + } + } } - } } - } - unsigned int topBitRank = 1; - unsigned int topBitNo = 2; - for (unsigned int aggr = 1; aggr < 8192; aggr++) { - if (aggr >= (topBitRank << 1)) { - topBitRank <<= 1; - topBitNo++; + // Initialize rel_table[0] + for (int s = 0; s < DDS_SUITS; s++) { + for (int ord = 1; ord <= 13; ord++) { + rel_table[0].abs_rank[ord][s].hand = -1; + rel_table[0].abs_rank[ord][s].rank = 0; + } } - rel_table[aggr] = rel_table[aggr ^ topBitRank]; - RelRanksType * relp = &rel_table[aggr]; - - int weight = count_table[aggr]; - for (int c = weight; c >= 2; c--) { - for (int s = 0; s < DDS_SUITS; s++) { - relp->abs_rank[c][s].hand = relp->abs_rank[c - 1][s].hand; - relp->abs_rank[c][s].rank = relp->abs_rank[c - 1][s].rank; - } - } + // Build handLookup from current Deal (use localPos which may have had cards removed) + int handLookup[DDS_SUITS][15]; for (int s = 0; s < DDS_SUITS; s++) { - relp->abs_rank[1][s].hand = static_cast(handLookup[s][topBitNo]); - relp->abs_rank[1][s].rank = static_cast(topBitNo); + for (int r = 14; r >= 2; r--) { + handLookup[s][r] = 0; + for (int h = 0; h < DDS_HANDS; h++) { + if (localPos.rank_in_suit[h][s] & bit_map_rank[r]) { + handLookup[s][r] = h; + break; + } + } + } } - } - // If requested, simulate cards already played in the current trick. - // played_moves is expected to be an array of length >= cards_played with - // moves in play order (first played -> last played). We will set - // track_p->play_suits/play_ranks/move/high and update removed_ranks and - // trickData accordingly; also set lead_suit from the first played card. - if (track_p && cards_played > 0 && played_moves) { - if (cards_played > DDS_HANDS) cards_played = DDS_HANDS; - int relIndex = 0; - for (int i = 0; i < cards_played; ++i) { - const MoveType& m = played_moves[i]; - // relative index in trick: 0..cards_played-1 - relIndex = i; - track_p->play_suits[relIndex] = m.suit; - track_p->play_ranks[relIndex] = m.rank; - track_p->move[relIndex].suit = m.suit; - track_p->move[relIndex].rank = m.rank; - track_p->move[relIndex].sequence = m.sequence; + unsigned int topBitRank = 1; + unsigned int topBitNo = 2; + for (unsigned int aggr = 1; aggr < 8192; aggr++) { + if (aggr >= (topBitRank << 1)) { + topBitRank <<= 1; + topBitNo++; + } - // maintain removedRanks: mark that the card has been played - if (m.rank > 0 && m.rank < 16) - track_p->removed_ranks[m.suit] |= bit_map_rank[m.rank]; + rel_table[aggr] = rel_table[aggr ^ topBitRank]; + RelRanksType * relp = &rel_table[aggr]; - // update high[]: who currently wins among the played cards - if (relIndex == 0) { - track_p->high[0] = 0; - // lead_suit is the first card's suit - track_p->lead_suit = m.suit; - } else { - // compare with previous winning card - ExtCard prev = track_p->move[track_p->high[relIndex - 1]]; - bool newIsWinning = false; - if (m.suit == prev.suit) { - if (m.rank > prev.rank) newIsWinning = true; - } else if (m.suit == trump) { - // trump beats non-trump - if (trump != DDS_NOTRUMP) newIsWinning = true; + int weight = count_table[aggr]; + for (int c = weight; c >= 2; c--) { + for (int s = 0; s < DDS_SUITS; s++) { + relp->abs_rank[c][s].hand = relp->abs_rank[c - 1][s].hand; + relp->abs_rank[c][s].rank = relp->abs_rank[c - 1][s].rank; + } + } + for (int s = 0; s < DDS_SUITS; s++) { + relp->abs_rank[1][s].hand = static_cast(handLookup[s][topBitNo]); + relp->abs_rank[1][s].rank = static_cast(topBitNo); } - if (newIsWinning) track_p->high[relIndex] = relIndex; - else track_p->high[relIndex] = track_p->high[relIndex - 1]; - } } - // Fill remaining high[] entries (for unplayed positions) with last known - for (int p = cards_played; p < DDS_HANDS; ++p) track_p->high[p] = track_p->high[cards_played - 1]; + // If requested, simulate cards already played in the current trick. + // played_moves is expected to be an array of length >= cards_played with + // moves in play order (first played -> last played). We will set + // track_p->play_suits/play_ranks/move/high and update removed_ranks and + // trickData accordingly; also set lead_suit from the first played card. + if (track_p && cards_played > 0 && played_moves) { + if (cards_played > DDS_HANDS) cards_played = DDS_HANDS; + int relIndex = 0; + for (int i = 0; i < cards_played; ++i) { + const MoveType& m = played_moves[i]; + // relative index in trick: 0..cards_played-1 + relIndex = i; + track_p->play_suits[relIndex] = m.suit; + track_p->play_ranks[relIndex] = m.rank; + track_p->move[relIndex].suit = m.suit; + track_p->move[relIndex].rank = m.rank; + track_p->move[relIndex].sequence = m.sequence; - // Update trickData play counts - for (int p = 0; p < cards_played; ++p) - track_p->trick_data.play_count[ track_p->play_suits[p] ]++; + // maintain removedRanks: mark that the card has been played + if (m.rank > 0 && m.rank < 16) + track_p->removed_ranks[m.suit] |= bit_map_rank[m.rank]; - // Update trickData best values from the last play - track_p->trick_data.best_rank = track_p->move[cards_played - 1].rank; - track_p->trick_data.best_suit = track_p->move[cards_played - 1].suit; - track_p->trick_data.best_sequence = track_p->move[cards_played - 1].sequence; - track_p->trick_data.rel_winner = track_p->high[cards_played - 1]; - // next_lead_hand if trick completes would be based on high[cards_played-1] - track_p->trick_data.next_lead_hand = (track_p->lead_hand + track_p->trick_data.rel_winner) % 4; - } + // update high[]: who currently wins among the played cards + if (relIndex == 0) { + track_p->high[0] = 0; + // lead_suit is the first card's suit + track_p->lead_suit = m.suit; + } else { + // compare with previous winning card + ExtCard prev = track_p->move[track_p->high[relIndex - 1]]; + bool newIsWinning = false; + if (m.suit == prev.suit) { + if (m.rank > prev.rank) newIsWinning = true; + } else if (m.suit == trump) { + // trump beats non-trump + if (trump != DDS_NOTRUMP) newIsWinning = true; + } + if (newIsWinning) track_p->high[relIndex] = relIndex; + else track_p->high[relIndex] = track_p->high[relIndex - 1]; + } + } - // Populate lowest_win: compute a more precise minimal winning rank for - // each relative hand (relh) and suit (s) given the current trick state. - if (track_p) { - // helpers to find ranks in a bitmask - auto find_smallest_rank = [](unsigned short ris) -> int { - for (int r = 1; r <= 13; ++r) if (ris & (1u << r)) return r; - return 0; - }; - auto find_smallest_rank_greater = [](unsigned short ris, int thr) -> int { - for (int r = thr + 1; r <= 13; ++r) if (ris & (1u << r)) return r; - return 0; - }; + // Fill remaining high[] entries (for unplayed positions) with last known + for (int p = cards_played; p < DDS_HANDS; ++p) track_p->high[p] = track_p->high[cards_played - 1]; - // current best on trick (if any) - bool hasCurrentBest = (cards_played > 0); - int curBestSuit = -1; - int curBestRank = 0; - if (hasCurrentBest) { - int lastRel = cards_played - 1; - int rel_winner = track_p->high[lastRel]; - ExtCard best = track_p->move[rel_winner]; - curBestSuit = best.suit; - curBestRank = best.rank; + // Update trickData play counts + for (int p = 0; p < cards_played; ++p) + track_p->trick_data.play_count[ track_p->play_suits[p] ]++; + + // Update trickData best values from the last play + track_p->trick_data.best_rank = track_p->move[cards_played - 1].rank; + track_p->trick_data.best_suit = track_p->move[cards_played - 1].suit; + track_p->trick_data.best_sequence = track_p->move[cards_played - 1].sequence; + track_p->trick_data.rel_winner = track_p->high[cards_played - 1]; + // next_lead_hand if trick completes would be based on high[cards_played-1] + track_p->trick_data.next_lead_hand = (track_p->lead_hand + track_p->trick_data.rel_winner) % 4; } - for (int relh = 0; relh < DDS_HANDS; ++relh) { - for (int s = 0; s < DDS_SUITS; ++s) { - track_p->lowest_win[relh][s] = 0; - // If this relative hand already played in this trick, skip - if (cards_played > 0 && relh < cards_played) { - track_p->lowest_win[relh][s] = 0; - continue; + // Populate lowest_win: compute a more precise minimal winning rank for + // each relative hand (relh) and suit (s) given the current trick state. + if (track_p) { + // helpers to find ranks in a bitmask + auto find_smallest_rank = [](unsigned short ris) -> int { + for (int r = 1; r <= 13; ++r) if (ris & (1u << r)) return r; + return 0; + }; + auto find_smallest_rank_greater = [](unsigned short ris, int thr) -> int { + for (int r = thr + 1; r <= 13; ++r) if (ris & (1u << r)) return r; + return 0; + }; + + // current best on trick (if any) + bool hasCurrentBest = (cards_played > 0); + int curBestSuit = -1; + int curBestRank = 0; + if (hasCurrentBest) { + int lastRel = cards_played - 1; + int rel_winner = track_p->high[lastRel]; + ExtCard best = track_p->move[rel_winner]; + curBestSuit = best.suit; + curBestRank = best.rank; } - int absHand = (track_p->lead_hand + relh) % 4; - unsigned short ris = localPos.rank_in_suit[absHand][s]; - if (!ris) { track_p->lowest_win[relh][s] = 0; continue; } + for (int relh = 0; relh < DDS_HANDS; ++relh) { + for (int s = 0; s < DDS_SUITS; ++s) { + track_p->lowest_win[relh][s] = 0; + // If this relative hand already played in this trick, skip + if (cards_played > 0 && relh < cards_played) { + track_p->lowest_win[relh][s] = 0; + continue; + } - // If there is no current best (lead not played), use smallest rank - if (!hasCurrentBest) { - track_p->lowest_win[relh][s] = find_smallest_rank(ris); - continue; - } + int absHand = (track_p->lead_hand + relh) % 4; + unsigned short ris = localPos.rank_in_suit[absHand][s]; + if (!ris) { track_p->lowest_win[relh][s] = 0; continue; } - // If candidate suit equals current best suit - if (s == curBestSuit) { - // need a higher rank than current best - track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); - continue; - } + // If there is no current best (lead not played), use smallest rank + if (!hasCurrentBest) { + track_p->lowest_win[relh][s] = find_smallest_rank(ris); + continue; + } - // If candidate is trump - if (s == trump) { - if (curBestSuit != trump) { - // any trump will beat non-trump; choose smallest trump in hand - track_p->lowest_win[relh][s] = find_smallest_rank(ris); - } else { - // best is also trump: need higher trump - track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); - } - continue; - } + // If candidate suit equals current best suit + if (s == curBestSuit) { + // need a higher rank than current best + track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); + continue; + } - // Candidate is non-trump and not equal to current best suit. - // If current best is trump, non-trump cannot win. - if (curBestSuit == trump) { - track_p->lowest_win[relh][s] = 0; - continue; - } + // If candidate is trump + if (s == trump) { + if (curBestSuit != trump) { + // any trump will beat non-trump; choose smallest trump in hand + track_p->lowest_win[relh][s] = find_smallest_rank(ris); + } else { + // best is also trump: need higher trump + track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); + } + continue; + } - // If current best is of a different suit (not trump), then only a card - // in the lead suit can beat it; if candidate suit equals lead_suit, - // we can try to beat that; otherwise cannot win. - if (s == track_p->lead_suit) { - // If current best is also lead_suit this case is handled earlier; - // here current best is different suit => it must be that someone - // trumped already, which we handled above. As a fallback, require - // higher rank than any current best of this suit. - track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); - } else { - track_p->lowest_win[relh][s] = 0; + // Candidate is non-trump and not equal to current best suit. + // If current best is trump, non-trump cannot win. + if (curBestSuit == trump) { + track_p->lowest_win[relh][s] = 0; + continue; + } + + // If current best is of a different suit (not trump), then only a card + // in the lead suit can beat it; if candidate suit equals lead_suit, + // we can try to beat that; otherwise cannot win. + if (s == track_p->lead_suit) { + // If current best is also lead_suit this case is handled earlier; + // here current best is different suit => it must be that someone + // trumped already, which we handled above. As a fallback, require + // higher rank than any current best of this suit. + track_p->lowest_win[relh][s] = find_smallest_rank_greater(ris, curBestRank); + } else { + track_p->lowest_win[relh][s] = 0; + } + } } - } } - } } diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 049cf7e33..1a0c46302 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -45,376 +45,376 @@ namespace { auto report_dds_error(const char* where, const int code) -> void { - char line[80]; - ErrorMessage(code, line); - cout << where << ": " << line << " (" << code << ")\n"; + char line[80]; + ErrorMessage(code, line); + cout << where << ": " << line << " (" << code << ")\n"; } } // namespace auto loop_solve( - BoardsPBN * bop, - SolvedBoards * solvedbdp, - DealPBN * deal_list, - FutureTricks * fut_list, - const int number, - const int stepsize, - std::vector>* board_times) -> bool + BoardsPBN * bop, + SolvedBoards * solvedbdp, + DealPBN * deal_list, + FutureTricks * fut_list, + const int number, + const int stepsize, + std::vector>* board_times) -> bool { - for (int i = 0; i < number; i += stepsize) - { - int count = (i + stepsize > number ? number - i : stepsize); - - bop->no_of_boards = count; - for (int j = 0; j < count; j++) + for (int i = 0; i < number; i += stepsize) { - bop->deals[j] = deal_list[i + j]; - bop->target[j] = -1; - bop->solutions[j] = 3; - bop->mode[j] = 1; - // (no-op) - } - - timer.start(count); - int ret; - if (dtest_effective_threads(options.num_threads_, count) <= 1) - { - ret = SolveAllBoardsSeq(bop, solvedbdp); - } - else - { - ret = solve_all_boards_pbn_n(*bop, *solvedbdp, - dtest_effective_threads(options.num_threads_, count)); - } - if (ret != RETURN_NO_FAULT) - { - timer.end(); - timer.finish_running(); - report_dds_error("loop_solve", ret); - cout << "loop_solve: i " << i << "\n"; - return false; - } - timer.end(); + int count = (i + stepsize > number ? number - i : stepsize); + + bop->no_of_boards = count; + for (int j = 0; j < count; j++) + { + bop->deals[j] = deal_list[i + j]; + bop->target[j] = -1; + bop->solutions[j] = 3; + bop->mode[j] = 1; + // (no-op) + } + + timer.start(count); + int ret; + if (dtest_effective_threads(options.num_threads_, count) <= 1) + { + ret = SolveAllBoardsSeq(bop, solvedbdp); + } + else + { + ret = solve_all_boards_pbn_n(*bop, *solvedbdp, + dtest_effective_threads(options.num_threads_, count)); + } + if (ret != RETURN_NO_FAULT) + { + timer.end(); + timer.finish_running(); + report_dds_error("loop_solve", ret); + cout << "loop_solve: i " << i << "\n"; + return false; + } + timer.end(); - if (board_times != nullptr) - { - std::vector> batch_times; - scheduler.GetBoardTimes(batch_times); - append_batch_board_times(*board_times, batch_times, i); - } + if (board_times != nullptr) + { + std::vector> batch_times; + scheduler.GetBoardTimes(batch_times); + append_batch_board_times(*board_times, batch_times, i); + } #ifdef BATCHTIMES - timer.print_running(i+count, number); + timer.print_running(i+count, number); #endif - for (int j = 0; j < count; j++) - { - if (compare_FUT(solvedbdp->solved_board[j], fut_list[i + j])) - continue; - - timer.finish_running(); - cout << "loop_solve: i " << i << ", j " << j << ": " << - "Difference\n\n"; - print_FUT(solvedbdp->solved_board[j]); - cout << "\n"; - print_FUT(fut_list[i+j]); - cout << "\n"; - return false; + for (int j = 0; j < count; j++) + { + if (compare_FUT(solvedbdp->solved_board[j], fut_list[i + j])) + continue; + + timer.finish_running(); + cout << "loop_solve: i " << i << ", j " << j << ": " << + "Difference\n\n"; + print_FUT(solvedbdp->solved_board[j]); + cout << "\n"; + print_FUT(fut_list[i+j]); + cout << "\n"; + return false; + } } - } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - return true; + return true; } auto loop_calc( - DealPBN * deal_list, - DdTableResults * table_list, - const int number, - const int stepsize, - std::vector>* board_times) -> bool + DealPBN * deal_list, + DdTableResults * table_list, + const int number, + const int stepsize, + std::vector>* board_times) -> bool { - // dtest harness progress only: call CalcAllTablesPBNX repeatedly with - // `stepsize` deals (typically MAXNOOFBOARDS). Each call still expands to - // count×strains boards in one parallel job — the X-API single-job contract - // is unchanged; we intentionally do not pass the whole file in one call so - // print_running can update between chunks. - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - const int strain_count = DDS_STRAINS; - if (number <= 0) - return true; - - int batch = stepsize; - if (batch <= 0) - batch = number; - - std::vector deals(static_cast(batch)); - std::vector results(static_cast(batch)); - - for (int i = 0; i < number; i += batch) - { - const int count = (i + batch > number ? number - i : batch); - - for (int j = 0; j < count; j++) + // dtest harness progress only: call CalcAllTablesPBNX repeatedly with + // `stepsize` deals (typically MAXNOOFBOARDS). Each call still expands to + // count×strains boards in one parallel job — the X-API single-job contract + // is unchanged; we intentionally do not pass the whole file in one call so + // print_running can update between chunks. + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + const int strain_count = DDS_STRAINS; + if (number <= 0) + return true; + + int batch = stepsize; + if (batch <= 0) + batch = number; + + std::vector deals(static_cast(batch)); + std::vector results(static_cast(batch)); + + for (int i = 0; i < number; i += batch) { - std::strncpy( - deals[static_cast(j)].cards, - deal_list[i + j].remainCards, - sizeof(deals[0].cards)); - deals[static_cast(j)].cards[sizeof(deals[0].cards) - 1] = '\0'; - } + const int count = (i + batch > number ? number - i : batch); + + for (int j = 0; j < count; j++) + { + std::strncpy( + deals[static_cast(j)].cards, + deal_list[i + j].remainCards, + sizeof(deals[0].cards)); + deals[static_cast(j)].cards[sizeof(deals[0].cards) - 1] = '\0'; + } + + timer.start(count); + const int workload = count * strain_count; + const int threads = dtest_effective_threads(options.num_threads_, workload); + std::vector strain_times; + const int ret = calc_all_tables_pbn_x( + count, + deals.data(), + -1, + filter, + results.data(), + nullptr, + threads, + board_times != nullptr ? &strain_times : nullptr); + if (ret != RETURN_NO_FAULT) + { + timer.end(); + timer.finish_running(); + report_dds_error("loop_calc", ret); + cout << "loop_calc: i " << i << "\n"; + return false; + } + timer.end(); - timer.start(count); - const int workload = count * strain_count; - const int threads = dtest_effective_threads(options.num_threads_, workload); - std::vector strain_times; - const int ret = calc_all_tables_pbn_x( - count, - deals.data(), - -1, - filter, - results.data(), - nullptr, - threads, - board_times != nullptr ? &strain_times : nullptr); - if (ret != RETURN_NO_FAULT) - { - timer.end(); - timer.finish_running(); - report_dds_error("loop_calc", ret); - cout << "loop_calc: i " << i << "\n"; - return false; - } - timer.end(); - - if (board_times != nullptr) - { - append_calc_batch_deal_times( - *board_times, strain_times, strain_count, i); - } + if (board_times != nullptr) + { + append_calc_batch_deal_times( + *board_times, strain_times, strain_count, i); + } #ifdef BATCHTIMES - timer.print_running(i + count, number); + timer.print_running(i + count, number); #endif - for (int j = 0; j < count; j++) - { - if (compare_TABLE(results[static_cast(j)], table_list[i + j])) - continue; - - timer.finish_running(); - cout << "loop_calc: j " << (i + j) << ": Difference\n\n"; - print_TABLE(results[static_cast(j)]); - cout << "\n"; - print_TABLE(table_list[i + j]); - cout << "\n"; - return false; + for (int j = 0; j < count; j++) + { + if (compare_TABLE(results[static_cast(j)], table_list[i + j])) + continue; + + timer.finish_running(); + cout << "loop_calc: j " << (i + j) << ": Difference\n\n"; + print_TABLE(results[static_cast(j)]); + cout << "\n"; + print_TABLE(table_list[i + j]); + cout << "\n"; + return false; + } } - } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - return true; + return true; } auto loop_par( - int * vul_list, - DdTableResults * table_list, - ParResults * par_list, - const int number, - const int stepsize) -> bool + int * vul_list, + DdTableResults * table_list, + ParResults * par_list, + const int number, + const int stepsize) -> bool { - // This is so fast that there is no batch or multi-threaded - // version. We run it many times just to get meaningful times. + // This is so fast that there is no batch or multi-threaded + // version. We run it many times just to get meaningful times. - ParResults presp; + ParResults presp; - for (int i = 0; i < number; i++) - { - timer.start(1); - for (int j = 0; j < stepsize; j++) + for (int i = 0; i < number; i++) { - int ret; - if ((ret = Par(&table_list[i], &presp, vul_list[i])) - != RETURN_NO_FAULT) - { + timer.start(1); + for (int j = 0; j < stepsize; j++) + { + int ret; + if ((ret = Par(&table_list[i], &presp, vul_list[i])) + != RETURN_NO_FAULT) + { + timer.end(); + timer.finish_running(); + report_dds_error("loop_par", ret); + cout << "loop_par: i " << i << ", j " << j << "\n"; + return false; + } + } timer.end(); - timer.finish_running(); - report_dds_error("loop_par", ret); - cout << "loop_par: i " << i << ", j " << j << "\n"; - return false; - } - } - timer.end(); - if (compare_PAR(presp, par_list[i])) - { + if (compare_PAR(presp, par_list[i])) + { #ifdef BATCHTIMES - timer.print_running(i + 1, number); + timer.print_running(i + 1, number); #endif - continue; - } + continue; + } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - cout << "loop_par i " << i << ": Difference\n\n"; - print_PAR(presp); - cout << "\n"; - print_PAR(par_list[i]); - cout << "\n"; - return false; - } + cout << "loop_par i " << i << ": Difference\n\n"; + print_PAR(presp); + cout << "\n"; + print_PAR(par_list[i]); + cout << "\n"; + return false; + } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - return true; + return true; } auto loop_dealerpar( - int * dealer_list, - int * vul_list, - DdTableResults * table_list, - ParResultsDealer * dealerpar_list, - const int number, - const int stepsize) -> bool + int * dealer_list, + int * vul_list, + DdTableResults * table_list, + ParResultsDealer * dealerpar_list, + const int number, + const int stepsize) -> bool { - // This is so fast that there is no batch or multi-threaded - // version. We run it many times just to get meaningful times. + // This is so fast that there is no batch or multi-threaded + // version. We run it many times just to get meaningful times. - ParResultsDealer presp; + ParResultsDealer presp; - for (int i = 0; i < number; i++) - { - timer.start(1); - for (int j = 0; j < stepsize; j++) + for (int i = 0; i < number; i++) { - int ret; - if ((ret = DealerPar(&table_list[i], &presp, - dealer_list[i], vul_list[i])) != RETURN_NO_FAULT) - { + timer.start(1); + for (int j = 0; j < stepsize; j++) + { + int ret; + if ((ret = DealerPar(&table_list[i], &presp, + dealer_list[i], vul_list[i])) != RETURN_NO_FAULT) + { + timer.end(); + timer.finish_running(); + report_dds_error("loop_dealerpar", ret); + cout << "loop_dealerpar: i " << i << ", j " << j << "\n"; + return false; + } + } timer.end(); - timer.finish_running(); - report_dds_error("loop_dealerpar", ret); - cout << "loop_dealerpar: i " << i << ", j " << j << "\n"; - return false; - } - } - timer.end(); - if (compare_DEALERPAR(presp, dealerpar_list[i])) - { + if (compare_DEALERPAR(presp, dealerpar_list[i])) + { #ifdef BATCHTIMES - timer.print_running(i + 1, number); + timer.print_running(i + 1, number); #endif - continue; - } + continue; + } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - cout << "loop_dealerpar i " << i << ": Difference\n\n"; - print_DEALERPAR(presp); - cout << "\n"; - print_DEALERPAR(dealerpar_list[i]); - cout << "\n"; - return false; - } + cout << "loop_dealerpar i " << i << ": Difference\n\n"; + print_DEALERPAR(presp); + cout << "\n"; + print_DEALERPAR(dealerpar_list[i]); + cout << "\n"; + return false; + } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - return true; + return true; } auto loop_play( - BoardsPBN * bop, - PlayTracesPBN * playsp, - SolvedPlays * solvedplp, - DealPBN * deal_list, - PlayTracePBN * play_list, - SolvedPlay * trace_list, - const int number, - const int stepsize) -> bool + BoardsPBN * bop, + PlayTracesPBN * playsp, + SolvedPlays * solvedplp, + DealPBN * deal_list, + PlayTracePBN * play_list, + SolvedPlay * trace_list, + const int number, + const int stepsize) -> bool { - for (int i = 0; i < number; i += stepsize) - { - int count = (i + stepsize > number ? number - i : stepsize); - - bop->no_of_boards = count; - playsp->no_of_boards = count; - - for (int j = 0; j < count; j++) - { - bop->deals[j] = deal_list[i + j]; - bop->target[j] = 0; - bop->solutions[j] = 3; - bop->mode[j] = 1; - - playsp->plays[j] = play_list[i + j]; - } - - timer.start(count); - int ret; - if (dtest_effective_threads(options.num_threads_, count) <= 1) - { - ret = AnalyseAllPlaysPBN(bop, playsp, solvedplp, 1); - } - else - { - solvedplp->no_of_boards = count; - ret = dtest_run_parallel(count, options.num_threads_, - [&](const int j) -> int { - return AnalysePlayPBN( - bop->deals[j], playsp->plays[j], &solvedplp->solved[j], 0); - }); - } - if (ret != RETURN_NO_FAULT) + for (int i = 0; i < number; i += stepsize) { - timer.end(); - timer.finish_running(); - report_dds_error("loop_play", ret); - cout << "loop_play: i " << i << "\n"; - return false; - } - timer.end(); + int count = (i + stepsize > number ? number - i : stepsize); + + bop->no_of_boards = count; + playsp->no_of_boards = count; + + for (int j = 0; j < count; j++) + { + bop->deals[j] = deal_list[i + j]; + bop->target[j] = 0; + bop->solutions[j] = 3; + bop->mode[j] = 1; + + playsp->plays[j] = play_list[i + j]; + } + + timer.start(count); + int ret; + if (dtest_effective_threads(options.num_threads_, count) <= 1) + { + ret = AnalyseAllPlaysPBN(bop, playsp, solvedplp, 1); + } + else + { + solvedplp->no_of_boards = count; + ret = dtest_run_parallel(count, options.num_threads_, + [&](const int j) -> int { + return AnalysePlayPBN( + bop->deals[j], playsp->plays[j], &solvedplp->solved[j], 0); + }); + } + if (ret != RETURN_NO_FAULT) + { + timer.end(); + timer.finish_running(); + report_dds_error("loop_play", ret); + cout << "loop_play: i " << i << "\n"; + return false; + } + timer.end(); #ifdef BATCHTIMES - timer.print_running(i+count, number); + timer.print_running(i+count, number); #endif - for (int j = 0; j < count; j++) - { - if (compare_TRACE(solvedplp->solved[j], trace_list[i+j])) - continue; - - timer.finish_running(); - printf("loop_play i %d, j %d: Difference\n", i, j); - cout << "loop_play: i " << i << ", j " << j << ": " << - "Difference\n\n"; - print_double_TRACE(solvedplp->solved[j], trace_list[i+j]); - cout << "\n"; - return false; + for (int j = 0; j < count; j++) + { + if (compare_TRACE(solvedplp->solved[j], trace_list[i+j])) + continue; + + timer.finish_running(); + printf("loop_play i %d, j %d: Difference\n", i, j); + cout << "loop_play: i " << i << ", j " << j << ": " << + "Difference\n\n"; + print_double_TRACE(solvedplp->solved[j], trace_list[i+j]); + cout << "\n"; + return false; + } } - } #ifdef BATCHTIMES - timer.finish_running(); + timer.finish_running(); #endif - return true; + return true; } diff --git a/library/tests/loop_failure_test.cpp b/library/tests/loop_failure_test.cpp index d7b7f3012..8dfe841db 100644 --- a/library/tests/loop_failure_test.cpp +++ b/library/tests/loop_failure_test.cpp @@ -38,94 +38,94 @@ TRACE 49 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 struct HandLists { - int number = 0; - bool gib_mode = false; - int* dealer_list = nullptr; - int* vul_list = nullptr; - DealPBN* deal_list = nullptr; - FutureTricks* fut_list = nullptr; - DdTableResults* table_list = nullptr; - ParResults* par_list = nullptr; - ParResultsDealer* dealerpar_list = nullptr; - PlayTracePBN* play_list = nullptr; - SolvedPlay* trace_list = nullptr; - std::string path; - - HandLists() = default; - HandLists(const HandLists&) = delete; - auto operator=(const HandLists&) -> HandLists& = delete; - - HandLists(HandLists&& other) noexcept - { - *this = std::move(other); - } - - auto operator=(HandLists&& other) noexcept -> HandLists& - { - if (this == &other) - return *this; - release(); - number = other.number; - gib_mode = other.gib_mode; - dealer_list = other.dealer_list; - vul_list = other.vul_list; - deal_list = other.deal_list; - fut_list = other.fut_list; - table_list = other.table_list; - par_list = other.par_list; - dealerpar_list = other.dealerpar_list; - play_list = other.play_list; - trace_list = other.trace_list; - path = std::move(other.path); - other.number = 0; - other.gib_mode = false; - other.dealer_list = nullptr; - other.vul_list = nullptr; - other.deal_list = nullptr; - other.fut_list = nullptr; - other.table_list = nullptr; - other.par_list = nullptr; - other.dealerpar_list = nullptr; - other.play_list = nullptr; - other.trace_list = nullptr; - // std::move leaves the source string unspecified; clear so ~HandLists - // cannot unlink the file now owned by *this. - other.path.clear(); - return *this; - } - - ~HandLists() - { - release(); - } + int number = 0; + bool gib_mode = false; + int* dealer_list = nullptr; + int* vul_list = nullptr; + DealPBN* deal_list = nullptr; + FutureTricks* fut_list = nullptr; + DdTableResults* table_list = nullptr; + ParResults* par_list = nullptr; + ParResultsDealer* dealerpar_list = nullptr; + PlayTracePBN* play_list = nullptr; + SolvedPlay* trace_list = nullptr; + std::string path; + + HandLists() = default; + HandLists(const HandLists&) = delete; + auto operator=(const HandLists&) -> HandLists& = delete; + + HandLists(HandLists&& other) noexcept + { + *this = std::move(other); + } + + auto operator=(HandLists&& other) noexcept -> HandLists& + { + if (this == &other) + return *this; + release(); + number = other.number; + gib_mode = other.gib_mode; + dealer_list = other.dealer_list; + vul_list = other.vul_list; + deal_list = other.deal_list; + fut_list = other.fut_list; + table_list = other.table_list; + par_list = other.par_list; + dealerpar_list = other.dealerpar_list; + play_list = other.play_list; + trace_list = other.trace_list; + path = std::move(other.path); + other.number = 0; + other.gib_mode = false; + other.dealer_list = nullptr; + other.vul_list = nullptr; + other.deal_list = nullptr; + other.fut_list = nullptr; + other.table_list = nullptr; + other.par_list = nullptr; + other.dealerpar_list = nullptr; + other.play_list = nullptr; + other.trace_list = nullptr; + // std::move leaves the source string unspecified; clear so ~HandLists + // cannot unlink the file now owned by *this. + other.path.clear(); + return *this; + } + + ~HandLists() + { + release(); + } private: - auto release() -> void - { - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - dealer_list = nullptr; - vul_list = nullptr; - deal_list = nullptr; - fut_list = nullptr; - table_list = nullptr; - par_list = nullptr; - dealerpar_list = nullptr; - play_list = nullptr; - trace_list = nullptr; - if (!path.empty()) + auto release() -> void { - std::remove(path.c_str()); - path.clear(); + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + dealer_list = nullptr; + vul_list = nullptr; + deal_list = nullptr; + fut_list = nullptr; + table_list = nullptr; + par_list = nullptr; + dealerpar_list = nullptr; + play_list = nullptr; + trace_list = nullptr; + if (!path.empty()) + { + std::remove(path.c_str()); + path.clear(); + } } - } }; static_assert(!std::is_copy_constructible_v); @@ -135,76 +135,76 @@ static_assert(std::is_move_assignable_v); TEST(HandListsMove, MovedFromPathIsClearedSoDestructorDoesNotUnlinkOwnedFile) { - // Arrange: a temp file owned by a HandLists that we then move-from. - const std::string path = - std::string(::testing::TempDir()) + "handlists_move_path.txt"; - { - std::ofstream out(path, std::ios::out | std::ios::trunc); - out << "probe\n"; - } - ASSERT_TRUE(std::ifstream(path).good()); - - HandLists owner; - { - HandLists donor; - donor.path = path; - owner = std::move(donor); - // Act/Assert: moved-from path must be empty so ~HandLists cannot remove - // the file now owned by `owner` (std::move leaves string unspecified). - EXPECT_TRUE(donor.path.empty()); - EXPECT_EQ(owner.path, path); - } - // Donor destroyed; owned file must still exist for the assignee. - EXPECT_TRUE(std::ifstream(path).good()) << path; + // Arrange: a temp file owned by a HandLists that we then move-from. + const std::string path = + std::string(::testing::TempDir()) + "handlists_move_path.txt"; + { + std::ofstream out(path, std::ios::out | std::ios::trunc); + out << "probe\n"; + } + ASSERT_TRUE(std::ifstream(path).good()); + + HandLists owner; + { + HandLists donor; + donor.path = path; + owner = std::move(donor); + // Act/Assert: moved-from path must be empty so ~HandLists cannot remove + // the file now owned by `owner` (std::move leaves string unspecified). + EXPECT_TRUE(donor.path.empty()); + EXPECT_EQ(owner.path, path); + } + // Donor destroyed; owned file must still exist for the assignee. + EXPECT_TRUE(std::ifstream(path).good()) << path; } auto write_hands(const std::string& name, const std::string& body) - -> std::string + -> std::string { - const std::string path = std::string(::testing::TempDir()) + name; - std::ofstream out(path, std::ios::out | std::ios::trunc); - out << body; - return path; + const std::string path = std::string(::testing::TempDir()) + name; + std::ofstream out(path, std::ios::out | std::ios::trunc); + out << body; + return path; } auto load_hands(const std::string& name, const std::string& body) -> HandLists { - HandLists hands; - hands.path = write_hands(name, body); - EXPECT_TRUE(read_file( - hands.path, - hands.number, - hands.gib_mode, - &hands.dealer_list, - &hands.vul_list, - &hands.deal_list, - &hands.fut_list, - &hands.table_list, - &hands.par_list, - &hands.dealerpar_list, - &hands.play_list, - &hands.trace_list)); - return hands; + HandLists hands; + hands.path = write_hands(name, body); + EXPECT_TRUE(read_file( + hands.path, + hands.number, + hands.gib_mode, + &hands.dealer_list, + &hands.vul_list, + &hands.deal_list, + &hands.fut_list, + &hands.table_list, + &hands.par_list, + &hands.dealerpar_list, + &hands.play_list, + &hands.trace_list)); + return hands; } auto two_deal_body(const std::string& first, const std::string& second) - -> std::string + -> std::string { - return std::string("NUMBER 2 \n") + first + second; + return std::string("NUMBER 2 \n") + first + second; } auto corrupt_table(DdTableResults* table) -> void { - for (int d = 0; d < DDS_STRAINS; d++) - for (int h = 0; h < DDS_HANDS; h++) - table->res_table[d][h] = 2000000000; + for (int d = 0; d < DDS_STRAINS; d++) + for (int h = 0; h < DDS_HANDS; h++) + table->res_table[d][h] = 2000000000; } auto capture_print_hands(const TestTimer& test_timer) -> std::string { - std::ostringstream out; - test_timer.print_hands(out); - return out.str(); + std::ostringstream out; + test_timer.print_hands(out); + return out.str(); } } // namespace @@ -212,334 +212,334 @@ auto capture_print_hands(const TestTimer& test_timer) -> std::string class LoopFailureTest : public ::testing::Test { protected: - static void SetUpTestSuite() - { - InitializeStaticMemory(); - } - - void SetUp() override - { - options = OptionsType{}; - options.num_threads_ = 1; - timer.reset(); - } + static void SetUpTestSuite() + { + InitializeStaticMemory(); + } + + void SetUp() override + { + options = OptionsType{}; + options.num_threads_ = 1; + timer.reset(); + } }; TEST_F(LoopFailureTest, SolveStopsOnFirstExpectedMismatch) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("10 10 10 10 10 10 10 10 10 9"); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("10 10 10 10 10 10 10 10 10 9"), - "10 10 10 10 10 10 10 10 10 0"); - - auto hands = load_hands( - "loop_fail_solve.txt", two_deal_body(wrong, wrong)); - ASSERT_EQ(hands.number, 2); - - BoardsPBN bop{}; - SolvedBoards solved{}; - testing::internal::CaptureStdout(); - const bool ok = - loop_solve(&bop, &solved, hands.deal_list, hands.fut_list, 2, 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_solve: i 0, j 0: Difference"), std::string::npos); - EXPECT_EQ(out.find("loop_solve: i 1"), std::string::npos); - // Progress was printed for the failed batch then cleared before the report. - EXPECT_NE(out.find("\033[2K"), std::string::npos); - const auto diff_at = out.find("Difference"); - ASSERT_NE(diff_at, std::string::npos); - EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("10 10 10 10 10 10 10 10 10 9"); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("10 10 10 10 10 10 10 10 10 9"), + "10 10 10 10 10 10 10 10 10 0"); + + auto hands = load_hands( + "loop_fail_solve.txt", two_deal_body(wrong, wrong)); + ASSERT_EQ(hands.number, 2); + + BoardsPBN bop{}; + SolvedBoards solved{}; + testing::internal::CaptureStdout(); + const bool ok = + loop_solve(&bop, &solved, hands.deal_list, hands.fut_list, 2, 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_solve: i 0, j 0: Difference"), std::string::npos); + EXPECT_EQ(out.find("loop_solve: i 1"), std::string::npos); + // Progress was printed for the failed batch then cleared before the report. + EXPECT_NE(out.find("\033[2K"), std::string::npos); + const auto diff_at = out.find("Difference"); + ASSERT_NE(diff_at, std::string::npos); + EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); } TEST_F(LoopFailureTest, CalcStopsOnFirstExpectedMismatchAndClearsProgress) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("TABLE 11 "); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("TABLE 11 "), "TABLE 0 "); - - auto hands = load_hands( - "loop_fail_calc.txt", two_deal_body(wrong, wrong)); - ASSERT_EQ(hands.number, 2); - - testing::internal::CaptureStdout(); - const bool ok = loop_calc(hands.deal_list, hands.table_list, 2, 2); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_calc: j 0: Difference"), std::string::npos); - EXPECT_EQ(out.find("loop_calc: j 1:"), std::string::npos); - const auto diff_at = out.find("Difference"); - ASSERT_NE(diff_at, std::string::npos); - EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("TABLE 11 "); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("TABLE 11 "), "TABLE 0 "); + + auto hands = load_hands( + "loop_fail_calc.txt", two_deal_body(wrong, wrong)); + ASSERT_EQ(hands.number, 2); + + testing::internal::CaptureStdout(); + const bool ok = loop_calc(hands.deal_list, hands.table_list, 2, 2); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_calc: j 0: Difference"), std::string::npos); + EXPECT_EQ(out.find("loop_calc: j 1:"), std::string::npos); + const auto diff_at = out.find("Difference"); + ASSERT_NE(diff_at, std::string::npos); + EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); } TEST_F(LoopFailureTest, CalcPrintsIntermediateProgressWhenBatched) { - // Two matching deals with stepsize 1 must emit a mid-run progress tick - // (reached=1) before the final tick (reached=2). - auto hands = load_hands( - "loop_calc_progress.txt", two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - - testing::internal::CaptureStdout(); - ASSERT_TRUE(loop_calc(hands.deal_list, hands.table_list, 2, 1)); - const std::string out = testing::internal::GetCapturedStdout(); - - const auto mid = out.find("1 ("); - const auto end = out.find("2 ("); - ASSERT_NE(mid, std::string::npos) << out; - ASSERT_NE(end, std::string::npos) << out; - EXPECT_LT(mid, end); - EXPECT_EQ(out.find('\n'), std::string::npos); + // Two matching deals with stepsize 1 must emit a mid-run progress tick + // (reached=1) before the final tick (reached=2). + auto hands = load_hands( + "loop_calc_progress.txt", two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + + testing::internal::CaptureStdout(); + ASSERT_TRUE(loop_calc(hands.deal_list, hands.table_list, 2, 1)); + const std::string out = testing::internal::GetCapturedStdout(); + + const auto mid = out.find("1 ("); + const auto end = out.find("2 ("); + ASSERT_NE(mid, std::string::npos) << out; + ASSERT_NE(end, std::string::npos) << out; + EXPECT_LT(mid, end); + EXPECT_EQ(out.find('\n'), std::string::npos); } TEST_F(LoopFailureTest, CalcGibFilePrintsIntermediateProgressWhenBatched) { - // GIB one-line-per-deal input must use the same batched progress path as - // NUMBER list files (e.g. sol100000.txt with stepsize MAXNOOFBOARDS). - const std::string gib = - "T5.K4.652.A98542 K6.QJT976.QT7.Q6 432.A.AKJ93.JT73 AQJ987.8532.84.K:" - "65658888888843433232\n" - "T98.AKQT4.K853.8 Q6532.8.AJ2.9753 AK.76532.96.QJ62 J74.J9.QT74.AKT4:" - "66769999333376769999\n"; - auto hands = load_hands("loop_calc_gib_progress.txt", gib); - ASSERT_TRUE(hands.gib_mode); - ASSERT_EQ(hands.number, 2); - - testing::internal::CaptureStdout(); - ASSERT_TRUE(loop_calc(hands.deal_list, hands.table_list, 2, 1)); - const std::string out = testing::internal::GetCapturedStdout(); - - const auto mid = out.find("1 ("); - const auto end = out.find("2 ("); - ASSERT_NE(mid, std::string::npos) << out; - ASSERT_NE(end, std::string::npos) << out; - EXPECT_LT(mid, end); + // GIB one-line-per-deal input must use the same batched progress path as + // NUMBER list files (e.g. sol100000.txt with stepsize MAXNOOFBOARDS). + const std::string gib = + "T5.K4.652.A98542 K6.QJT976.QT7.Q6 432.A.AKJ93.JT73 AQJ987.8532.84.K:" + "65658888888843433232\n" + "T98.AKQT4.K853.8 Q6532.8.AJ2.9753 AK.76532.96.QJ62 J74.J9.QT74.AKT4:" + "66769999333376769999\n"; + auto hands = load_hands("loop_calc_gib_progress.txt", gib); + ASSERT_TRUE(hands.gib_mode); + ASSERT_EQ(hands.number, 2); + + testing::internal::CaptureStdout(); + ASSERT_TRUE(loop_calc(hands.deal_list, hands.table_list, 2, 1)); + const std::string out = testing::internal::GetCapturedStdout(); + + const auto mid = out.find("1 ("); + const auto end = out.find("2 ("); + ASSERT_NE(mid, std::string::npos) << out; + ASSERT_NE(end, std::string::npos) << out; + EXPECT_LT(mid, end); } TEST_F(LoopFailureTest, CalcMismatchInLaterBatchUsesAbsoluteIndex) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("TABLE 11 "); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("TABLE 11 "), "TABLE 0 "); - - auto hands = load_hands( - "loop_calc_batch_mismatch.txt", - two_deal_body(kDealBody, wrong)); - ASSERT_EQ(hands.number, 2); - - testing::internal::CaptureStdout(); - const bool ok = loop_calc(hands.deal_list, hands.table_list, 2, 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("1 ("), std::string::npos) << out; - EXPECT_NE(out.find("loop_calc: j 1: Difference"), std::string::npos); - EXPECT_EQ(out.find("loop_calc: j 0:"), std::string::npos); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("TABLE 11 "); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("TABLE 11 "), "TABLE 0 "); + + auto hands = load_hands( + "loop_calc_batch_mismatch.txt", + two_deal_body(kDealBody, wrong)); + ASSERT_EQ(hands.number, 2); + + testing::internal::CaptureStdout(); + const bool ok = loop_calc(hands.deal_list, hands.table_list, 2, 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("1 ("), std::string::npos) << out; + EXPECT_NE(out.find("loop_calc: j 1: Difference"), std::string::npos); + EXPECT_EQ(out.find("loop_calc: j 0:"), std::string::npos); } TEST_F(LoopFailureTest, CalcReportCollectsPerDealTimesAcrossBatches) { - // dtest -r for -s calc must publish one (file_index, time_us) per deal, - // remapping batch-local strain aggregates across stepsize chunks. - auto hands = load_hands( - "loop_calc_report_times.txt", two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - - std::vector> board_times; - testing::internal::CaptureStdout(); - ASSERT_TRUE( - loop_calc(hands.deal_list, hands.table_list, 2, 1, &board_times)); - (void)testing::internal::GetCapturedStdout(); - - ASSERT_EQ(board_times.size(), 2u); - EXPECT_EQ(board_times[0].first, 0); - EXPECT_EQ(board_times[1].first, 1); - EXPECT_GT(board_times[0].second, 0); - EXPECT_GT(board_times[1].second, 0); + // dtest -r for -s calc must publish one (file_index, time_us) per deal, + // remapping batch-local strain aggregates across stepsize chunks. + auto hands = load_hands( + "loop_calc_report_times.txt", two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + + std::vector> board_times; + testing::internal::CaptureStdout(); + ASSERT_TRUE( + loop_calc(hands.deal_list, hands.table_list, 2, 1, &board_times)); + (void)testing::internal::GetCapturedStdout(); + + ASSERT_EQ(board_times.size(), 2u); + EXPECT_EQ(board_times[0].first, 0); + EXPECT_EQ(board_times[1].first, 1); + EXPECT_GT(board_times[0].second, 0); + EXPECT_GT(board_times[1].second, 0); } TEST_F(LoopFailureTest, PlayStopsOnFirstExpectedMismatch) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("TRACE 49 3 "); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("TRACE 49 3 "), "TRACE 49 0 "); - - auto hands = load_hands( - "loop_fail_play.txt", two_deal_body(wrong, wrong)); - ASSERT_EQ(hands.number, 2); - - BoardsPBN bop{}; - PlayTracesPBN plays{}; - SolvedPlays solved{}; - testing::internal::CaptureStdout(); - const bool ok = loop_play( - &bop, - &plays, - &solved, - hands.deal_list, - hands.play_list, - hands.trace_list, - 2, - 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_play: i 0, j 0: Difference"), std::string::npos); - EXPECT_EQ(out.find("loop_play: i 1"), std::string::npos); - const auto diff_at = out.find("Difference"); - ASSERT_NE(diff_at, std::string::npos); - EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("TRACE 49 3 "); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("TRACE 49 3 "), "TRACE 49 0 "); + + auto hands = load_hands( + "loop_fail_play.txt", two_deal_body(wrong, wrong)); + ASSERT_EQ(hands.number, 2); + + BoardsPBN bop{}; + PlayTracesPBN plays{}; + SolvedPlays solved{}; + testing::internal::CaptureStdout(); + const bool ok = loop_play( + &bop, + &plays, + &solved, + hands.deal_list, + hands.play_list, + hands.trace_list, + 2, + 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_play: i 0, j 0: Difference"), std::string::npos); + EXPECT_EQ(out.find("loop_play: i 1"), std::string::npos); + const auto diff_at = out.find("Difference"); + ASSERT_NE(diff_at, std::string::npos); + EXPECT_NE(out.rfind("\033[2K\r", diff_at), std::string::npos); } TEST_F(LoopFailureTest, DealerParStopsOnFirstExpectedMismatch) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("PAR2 \"450\""); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("PAR2 \"450\""), "PAR2 \"999\""); - - auto hands = load_hands( - "loop_fail_dealerpar.txt", two_deal_body(wrong, wrong)); - ASSERT_EQ(hands.number, 2); - - testing::internal::CaptureStdout(); - const bool ok = loop_dealerpar( - hands.dealer_list, hands.vul_list, hands.table_list, - hands.dealerpar_list, 2, 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_dealerpar i 0: Difference"), std::string::npos); - EXPECT_EQ(out.find("loop_dealerpar i 1:"), std::string::npos); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("PAR2 \"450\""); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("PAR2 \"450\""), "PAR2 \"999\""); + + auto hands = load_hands( + "loop_fail_dealerpar.txt", two_deal_body(wrong, wrong)); + ASSERT_EQ(hands.number, 2); + + testing::internal::CaptureStdout(); + const bool ok = loop_dealerpar( + hands.dealer_list, hands.vul_list, hands.table_list, + hands.dealerpar_list, 2, 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_dealerpar i 0: Difference"), std::string::npos); + EXPECT_EQ(out.find("loop_dealerpar i 1:"), std::string::npos); } TEST_F(LoopFailureTest, ParClosesTimerBeforeReturningOnMismatch) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("PAR \"NS 450\""); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("PAR \"NS 450\""), "PAR \"NS -999\""); - - auto hands = load_hands( - "loop_fail_par_timer.txt", - std::string("NUMBER 1 \n") + wrong); - ASSERT_EQ(hands.number, 1); - - testing::internal::CaptureStdout(); - EXPECT_FALSE(loop_par( - hands.vul_list, hands.table_list, hands.par_list, 1, 1)); - testing::internal::GetCapturedStdout(); - - const std::string summary = capture_print_hands(timer); - EXPECT_TRUE(std::regex_search( - summary, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("PAR \"NS 450\""); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("PAR \"NS 450\""), "PAR \"NS -999\""); + + auto hands = load_hands( + "loop_fail_par_timer.txt", + std::string("NUMBER 1 \n") + wrong); + ASSERT_EQ(hands.number, 1); + + testing::internal::CaptureStdout(); + EXPECT_FALSE(loop_par( + hands.vul_list, hands.table_list, hands.par_list, 1, 1)); + testing::internal::GetCapturedStdout(); + + const std::string summary = capture_print_hands(timer); + EXPECT_TRUE(std::regex_search( + summary, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); } TEST_F(LoopFailureTest, DealerParClosesTimerBeforeReturningOnMismatch) { - auto wrong = std::string(kDealBody); - const auto pos = wrong.find("PAR2 \"450\""); - ASSERT_NE(pos, std::string::npos); - wrong.replace(pos, std::strlen("PAR2 \"450\""), "PAR2 \"999\""); - - auto hands = load_hands( - "loop_fail_dealerpar_timer.txt", - std::string("NUMBER 1 \n") + wrong); - ASSERT_EQ(hands.number, 1); - - testing::internal::CaptureStdout(); - EXPECT_FALSE(loop_dealerpar( - hands.dealer_list, hands.vul_list, hands.table_list, - hands.dealerpar_list, 1, 1)); - testing::internal::GetCapturedStdout(); - - const std::string summary = capture_print_hands(timer); - EXPECT_TRUE(std::regex_search( - summary, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); + auto wrong = std::string(kDealBody); + const auto pos = wrong.find("PAR2 \"450\""); + ASSERT_NE(pos, std::string::npos); + wrong.replace(pos, std::strlen("PAR2 \"450\""), "PAR2 \"999\""); + + auto hands = load_hands( + "loop_fail_dealerpar_timer.txt", + std::string("NUMBER 1 \n") + wrong); + ASSERT_EQ(hands.number, 1); + + testing::internal::CaptureStdout(); + EXPECT_FALSE(loop_dealerpar( + hands.dealer_list, hands.vul_list, hands.table_list, + hands.dealerpar_list, 1, 1)); + testing::internal::GetCapturedStdout(); + + const std::string summary = capture_print_hands(timer); + EXPECT_TRUE(std::regex_search( + summary, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); } TEST_F(LoopFailureTest, ParStopsOnApiFaultWithoutProcessingLaterDeal) { - auto hands = load_hands( - "loop_fail_par_api.txt", two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - corrupt_table(&hands.table_list[0]); - - testing::internal::CaptureStdout(); - const bool ok = loop_par( - hands.vul_list, hands.table_list, hands.par_list, 2, 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_par:"), std::string::npos); - EXPECT_NE(out.find("loop_par: i 0"), std::string::npos); - EXPECT_EQ(out.find("loop_par: i 1"), std::string::npos); - EXPECT_EQ(out.find("Difference"), std::string::npos); + auto hands = load_hands( + "loop_fail_par_api.txt", two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + corrupt_table(&hands.table_list[0]); + + testing::internal::CaptureStdout(); + const bool ok = loop_par( + hands.vul_list, hands.table_list, hands.par_list, 2, 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_par:"), std::string::npos); + EXPECT_NE(out.find("loop_par: i 0"), std::string::npos); + EXPECT_EQ(out.find("loop_par: i 1"), std::string::npos); + EXPECT_EQ(out.find("Difference"), std::string::npos); } TEST_F(LoopFailureTest, ParApiFaultAfterProgressClearsRunningLine) { - // First deal succeeds and prints progress; second deal's API fault must - // clear that line before the error text. - auto hands = load_hands( - "loop_fail_par_api_after_progress.txt", - two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - corrupt_table(&hands.table_list[1]); - - testing::internal::CaptureStdout(); - EXPECT_FALSE(loop_par( - hands.vul_list, hands.table_list, hands.par_list, 2, 1)); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_TRUE(std::regex_search(out, std::regex(R"(1\s+\()"))) << out; - EXPECT_NE(out.find("loop_par: i 1"), std::string::npos) << out; - // finish_running emits clear+CR immediately before the error report. - EXPECT_NE(out.find("\033[2K\rloop_par:"), std::string::npos) << out; + // First deal succeeds and prints progress; second deal's API fault must + // clear that line before the error text. + auto hands = load_hands( + "loop_fail_par_api_after_progress.txt", + two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + corrupt_table(&hands.table_list[1]); + + testing::internal::CaptureStdout(); + EXPECT_FALSE(loop_par( + hands.vul_list, hands.table_list, hands.par_list, 2, 1)); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_TRUE(std::regex_search(out, std::regex(R"(1\s+\()"))) << out; + EXPECT_NE(out.find("loop_par: i 1"), std::string::npos) << out; + // finish_running emits clear+CR immediately before the error report. + EXPECT_NE(out.find("\033[2K\rloop_par:"), std::string::npos) << out; } TEST_F(LoopFailureTest, DealerParStopsOnApiFaultWithoutProcessingLaterDeal) { - auto hands = load_hands( - "loop_fail_dealerpar_api.txt", two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - corrupt_table(&hands.table_list[0]); - - testing::internal::CaptureStdout(); - const bool ok = loop_dealerpar( - hands.dealer_list, hands.vul_list, hands.table_list, - hands.dealerpar_list, 2, 1); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(out.find("loop_dealerpar:"), std::string::npos); - EXPECT_NE(out.find("loop_dealerpar: i 0"), std::string::npos); - EXPECT_EQ(out.find("loop_dealerpar: i 1"), std::string::npos); - EXPECT_EQ(out.find("Difference"), std::string::npos); + auto hands = load_hands( + "loop_fail_dealerpar_api.txt", two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + corrupt_table(&hands.table_list[0]); + + testing::internal::CaptureStdout(); + const bool ok = loop_dealerpar( + hands.dealer_list, hands.vul_list, hands.table_list, + hands.dealerpar_list, 2, 1); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(out.find("loop_dealerpar:"), std::string::npos); + EXPECT_NE(out.find("loop_dealerpar: i 0"), std::string::npos); + EXPECT_EQ(out.find("loop_dealerpar: i 1"), std::string::npos); + EXPECT_EQ(out.find("Difference"), std::string::npos); } TEST_F(LoopFailureTest, DealerParApiFaultAfterProgressClearsRunningLine) { - auto hands = load_hands( - "loop_fail_dealerpar_api_after_progress.txt", - two_deal_body(kDealBody, kDealBody)); - ASSERT_EQ(hands.number, 2); - corrupt_table(&hands.table_list[1]); - - testing::internal::CaptureStdout(); - EXPECT_FALSE(loop_dealerpar( - hands.dealer_list, hands.vul_list, hands.table_list, - hands.dealerpar_list, 2, 1)); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_TRUE(std::regex_search(out, std::regex(R"(1\s+\()"))) << out; - EXPECT_NE(out.find("loop_dealerpar: i 1"), std::string::npos) << out; - EXPECT_NE(out.find("\033[2K\rloop_dealerpar:"), std::string::npos) << out; + auto hands = load_hands( + "loop_fail_dealerpar_api_after_progress.txt", + two_deal_body(kDealBody, kDealBody)); + ASSERT_EQ(hands.number, 2); + corrupt_table(&hands.table_list[1]); + + testing::internal::CaptureStdout(); + EXPECT_FALSE(loop_dealerpar( + hands.dealer_list, hands.vul_list, hands.table_list, + hands.dealerpar_list, 2, 1)); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_TRUE(std::regex_search(out, std::regex(R"(1\s+\()"))) << out; + EXPECT_NE(out.find("loop_dealerpar: i 1"), std::string::npos) << out; + EXPECT_NE(out.find("\033[2K\rloop_dealerpar:"), std::string::npos) << out; } diff --git a/library/tests/loop_par_test.cpp b/library/tests/loop_par_test.cpp index af4e6176e..325d50c6e 100644 --- a/library/tests/loop_par_test.cpp +++ b/library/tests/loop_par_test.cpp @@ -26,158 +26,158 @@ namespace auto write_temp_hand_list(const std::string& body) -> std::string { - // Bazel runfiles CWD can be read-only; write under TEST_TMPDIR via TempDir(). - const std::string path = - std::string(::testing::TempDir()) + "loop_par_test_hand.txt"; - std::ofstream out(path, std::ios::out | std::ios::trunc); - out << "NUMBER 1 \n" << body; - out.close(); - return path; + // Bazel runfiles CWD can be read-only; write under TEST_TMPDIR via TempDir(). + const std::string path = + std::string(::testing::TempDir()) + "loop_par_test_hand.txt"; + std::ofstream out(path, std::ios::out | std::ios::trunc); + out << "NUMBER 1 \n" << body; + out.close(); + return path; } auto cleanup(const std::string& path) -> void { - std::remove(path.c_str()); + std::remove(path.c_str()); } auto capture_print_hands(const TestTimer& test_timer) -> std::string { - std::ostringstream out; - test_timer.print_hands(out); - return out.str(); + std::ostringstream out; + test_timer.print_hands(out); + return out.str(); } } // namespace TEST(LoopPar, RecordsHandCountInTimer) { - const std::string path = write_temp_hand_list( - "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " - "AT942.AQ4.32.KJ3\" \n" - "FUT 0 \n" - "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" - "PAR \"NS -110\" \"EW 110\" \"NS:EW 2S\" \"EW:EW 2S\" \n" - "PAR2 \"-110\" \"2S-EW\" \n" - "PLAY 0 \"\" \n" - "TRACE 1 0 \n"); - - int number = 0; - bool gib_mode = false; - int* dealer_list = nullptr; - int* vul_list = nullptr; - DealPBN* deal_list = nullptr; - FutureTricks* fut_list = nullptr; - DdTableResults* table_list = nullptr; - ParResults* par_list = nullptr; - ParResultsDealer* dealerpar_list = nullptr; - PlayTracePBN* play_list = nullptr; - SolvedPlay* trace_list = nullptr; - - ASSERT_TRUE(read_file( - path, - number, - gib_mode, - &dealer_list, - &vul_list, - &deal_list, - &fut_list, - &table_list, - &par_list, - &dealerpar_list, - &play_list, - &trace_list)); - ASSERT_EQ(number, 1); - - timer.reset(); - ASSERT_TRUE(loop_par(vul_list, table_list, par_list, number, 1)); - - const std::string out = capture_print_hands(timer); - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); - - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - cleanup(path); + const std::string path = write_temp_hand_list( + "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + "AT942.AQ4.32.KJ3\" \n" + "FUT 0 \n" + "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" + "PAR \"NS -110\" \"EW 110\" \"NS:EW 2S\" \"EW:EW 2S\" \n" + "PAR2 \"-110\" \"2S-EW\" \n" + "PLAY 0 \"\" \n" + "TRACE 1 0 \n"); + + int number = 0; + bool gib_mode = false; + int* dealer_list = nullptr; + int* vul_list = nullptr; + DealPBN* deal_list = nullptr; + FutureTricks* fut_list = nullptr; + DdTableResults* table_list = nullptr; + ParResults* par_list = nullptr; + ParResultsDealer* dealerpar_list = nullptr; + PlayTracePBN* play_list = nullptr; + SolvedPlay* trace_list = nullptr; + + ASSERT_TRUE(read_file( + path, + number, + gib_mode, + &dealer_list, + &vul_list, + &deal_list, + &fut_list, + &table_list, + &par_list, + &dealerpar_list, + &play_list, + &trace_list)); + ASSERT_EQ(number, 1); + + timer.reset(); + ASSERT_TRUE(loop_par(vul_list, table_list, par_list, number, 1)); + + const std::string out = capture_print_hands(timer); + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Number of hands\s+1\s*(?:\n|$))"))); + + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + cleanup(path); } TEST(LoopPar, StopsAndReportsOnExpectedMismatch) { - // Two mismatched deals: stopping on the first must skip the second report. - const std::string path = - std::string(::testing::TempDir()) + "loop_par_test_mismatch.txt"; - { - std::ofstream out(path, std::ios::out | std::ios::trunc); - out << "NUMBER 2 \n" - << "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + // Two mismatched deals: stopping on the first must skip the second report. + const std::string path = + std::string(::testing::TempDir()) + "loop_par_test_mismatch.txt"; + { + std::ofstream out(path, std::ios::out | std::ios::trunc); + out << "NUMBER 2 \n" + << "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " "AT942.AQ4.32.KJ3\" \n" - << "FUT 0 \n" - << "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" - << "PAR \"NS -999\" \"EW 999\" \"NS:EW 7N\" \"EW:EW 7N\" \n" - << "PAR2 \"-999\" \"7N-EW\" \n" - << "PLAY 0 \"\" \n" - << "TRACE 1 0 \n" - << "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + << "FUT 0 \n" + << "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" + << "PAR \"NS -999\" \"EW 999\" \"NS:EW 7N\" \"EW:EW 7N\" \n" + << "PAR2 \"-999\" \"7N-EW\" \n" + << "PLAY 0 \"\" \n" + << "TRACE 1 0 \n" + << "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " "AT942.AQ4.32.KJ3\" \n" - << "FUT 0 \n" - << "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" - << "PAR \"NS -888\" \"EW 888\" \"NS:EW 6N\" \"EW:EW 6N\" \n" - << "PAR2 \"-888\" \"6N-EW\" \n" - << "PLAY 0 \"\" \n" - << "TRACE 1 0 \n"; - } - - int number = 0; - bool gib_mode = false; - int* dealer_list = nullptr; - int* vul_list = nullptr; - DealPBN* deal_list = nullptr; - FutureTricks* fut_list = nullptr; - DdTableResults* table_list = nullptr; - ParResults* par_list = nullptr; - ParResultsDealer* dealerpar_list = nullptr; - PlayTracePBN* play_list = nullptr; - SolvedPlay* trace_list = nullptr; - - ASSERT_TRUE(read_file( - path, - number, - gib_mode, - &dealer_list, - &vul_list, - &deal_list, - &fut_list, - &table_list, - &par_list, - &dealerpar_list, - &play_list, - &trace_list)); - ASSERT_EQ(number, 2); - - timer.reset(); - testing::internal::CaptureStdout(); - const bool ok = loop_par(vul_list, table_list, par_list, number, 1); - const std::string stdout_text = testing::internal::GetCapturedStdout(); - - EXPECT_FALSE(ok); - EXPECT_NE(stdout_text.find("loop_par i 0: Difference"), std::string::npos); - EXPECT_EQ(stdout_text.find("loop_par i 1:"), std::string::npos); - - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - cleanup(path); + << "FUT 0 \n" + << "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" + << "PAR \"NS -888\" \"EW 888\" \"NS:EW 6N\" \"EW:EW 6N\" \n" + << "PAR2 \"-888\" \"6N-EW\" \n" + << "PLAY 0 \"\" \n" + << "TRACE 1 0 \n"; + } + + int number = 0; + bool gib_mode = false; + int* dealer_list = nullptr; + int* vul_list = nullptr; + DealPBN* deal_list = nullptr; + FutureTricks* fut_list = nullptr; + DdTableResults* table_list = nullptr; + ParResults* par_list = nullptr; + ParResultsDealer* dealerpar_list = nullptr; + PlayTracePBN* play_list = nullptr; + SolvedPlay* trace_list = nullptr; + + ASSERT_TRUE(read_file( + path, + number, + gib_mode, + &dealer_list, + &vul_list, + &deal_list, + &fut_list, + &table_list, + &par_list, + &dealerpar_list, + &play_list, + &trace_list)); + ASSERT_EQ(number, 2); + + timer.reset(); + testing::internal::CaptureStdout(); + const bool ok = loop_par(vul_list, table_list, par_list, number, 1); + const std::string stdout_text = testing::internal::GetCapturedStdout(); + + EXPECT_FALSE(ok); + EXPECT_NE(stdout_text.find("loop_par i 0: Difference"), std::string::npos); + EXPECT_EQ(stdout_text.find("loop_par i 1:"), std::string::npos); + + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + cleanup(path); } diff --git a/library/tests/moves/moves_test.cpp b/library/tests/moves/moves_test.cpp index 4144567ac..29922d0c0 100644 --- a/library/tests/moves/moves_test.cpp +++ b/library/tests/moves/moves_test.cpp @@ -26,31 +26,31 @@ */ class MovesTest : public ::testing::Test { protected: - MovesTest() = default; - - void SetUp() override { - // Initialize test objects - moves = std::make_unique(); - } - - void TearDown() override { - moves.reset(); - } - - /** + MovesTest() = default; + + void SetUp() override { + // Initialize test objects + moves = std::make_unique(); + } + + void TearDown() override { + moves.reset(); + } + + /** * @brief Get sample rank_in_suit data for testing */ - const unsigned short (*getSampleRankInSuit())[4] { - static unsigned short data[4][4] = { - {0x3fff, 0x3fff, 0x3fff, 0x3fff}, - {0x3fff, 0x3fff, 0x3fff, 0x3fff}, - {0x3fff, 0x3fff, 0x3fff, 0x3fff}, - {0x3fff, 0x3fff, 0x3fff, 0x3fff} - }; - return data; - } - - std::unique_ptr moves; + const unsigned short (*getSampleRankInSuit())[4] { + static unsigned short data[4][4] = { + {0x3fff, 0x3fff, 0x3fff, 0x3fff}, + {0x3fff, 0x3fff, 0x3fff, 0x3fff}, + {0x3fff, 0x3fff, 0x3fff, 0x3fff}, + {0x3fff, 0x3fff, 0x3fff, 0x3fff} + }; + return data; + } + + std::unique_ptr moves; }; /** @@ -58,85 +58,85 @@ class MovesTest : public ::testing::Test { */ TEST_F(MovesTest, ConstructorInitializesState) { - // Verify constructor initializes object properly - EXPECT_NE(moves.get(), nullptr); - - // Verify function names are initialized - for (int i = 0; i < static_cast(MgType::SIZE); i++) { - EXPECT_FALSE(moves->funcName[i].empty()); - } - - // Verify statistics are zeroed - EXPECT_EQ(moves->trickFuncTable.nfuncs, 0); - EXPECT_EQ(moves->trickFuncSuitTable.nfuncs, 0); - - // make_heuristic_context snapshots these; they must not be indeterminate - // after construction (and MoveGen0/123 re-set them before hoisting a context). - EXPECT_EQ(moves->leadHand, 0); - EXPECT_EQ(moves->currHand, 0); - EXPECT_EQ(moves->leadSuit, 0); - EXPECT_EQ(moves->currTrick, 0); - EXPECT_EQ(moves->trump, DDS_NOTRUMP); - EXPECT_EQ(moves->suit, 0); - EXPECT_EQ(moves->numMoves, 0); - EXPECT_EQ(moves->lastNumMoves, 0); + // Verify constructor initializes object properly + EXPECT_NE(moves.get(), nullptr); + + // Verify function names are initialized + for (int i = 0; i < static_cast(MgType::SIZE); i++) { + EXPECT_FALSE(moves->funcName[i].empty()); + } + + // Verify statistics are zeroed + EXPECT_EQ(moves->trickFuncTable.nfuncs, 0); + EXPECT_EQ(moves->trickFuncSuitTable.nfuncs, 0); + + // make_heuristic_context snapshots these; they must not be indeterminate + // after construction (and MoveGen0/123 re-set them before hoisting a context). + EXPECT_EQ(moves->leadHand, 0); + EXPECT_EQ(moves->currHand, 0); + EXPECT_EQ(moves->leadSuit, 0); + EXPECT_EQ(moves->currTrick, 0); + EXPECT_EQ(moves->trump, DDS_NOTRUMP); + EXPECT_EQ(moves->suit, 0); + EXPECT_EQ(moves->numMoves, 0); + EXPECT_EQ(moves->lastNumMoves, 0); } TEST_F(MovesTest, InitializesTrackingState) { - // Initialize with trick 5, starting from relative hand 0 - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - - // Verify state is initialized - EXPECT_EQ(moves->currTrick, 5); - EXPECT_EQ(moves->trump, 3); // 3 = notrump - - // Verify move lists are reset - for (int h = 0; h < DDS_HANDS; h++) { - EXPECT_EQ(moves->moveList[5][h].current, 0); - EXPECT_EQ(moves->moveList[5][h].last, 0); - } + // Initialize with trick 5, starting from relative hand 0 + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + + // Verify state is initialized + EXPECT_EQ(moves->currTrick, 5); + EXPECT_EQ(moves->trump, 3); // 3 = notrump + + // Verify move lists are reset + for (int h = 0; h < DDS_HANDS; h++) { + EXPECT_EQ(moves->moveList[5][h].current, 0); + EXPECT_EQ(moves->moveList[5][h].last, 0); + } } TEST_F(MovesTest, ReinitUpdateLeadHand) { - // Initialize first - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(7, 0, nullptr, nullptr, rankInSuit, 0, 1); - - // Reinit with different lead hand - moves->Reinit(7, 2); - - // Verify lead hand updated - EXPECT_EQ(moves->track[7].lead_hand, 2); + // Initialize first + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(7, 0, nullptr, nullptr, rankInSuit, 0, 1); + + // Reinit with different lead hand + moves->Reinit(7, 2); + + // Verify lead hand updated + EXPECT_EQ(moves->track[7].lead_hand, 2); } TEST_F(MovesTest, GetLengthReturnsCorrectCount) { - // GetLength should return valid counts - // Note: moveList is initialized with last=0, so GetLength returns last+1 - EXPECT_GE(moves->GetLength(3, 0), 0); - EXPECT_LE(moves->GetLength(3, 0), 14); // Max 13 cards + 1 - EXPECT_GE(moves->GetLength(12, 3), 0); - EXPECT_LE(moves->GetLength(12, 3), 14); + // GetLength should return valid counts + // Note: moveList is initialized with last=0, so GetLength returns last+1 + EXPECT_GE(moves->GetLength(3, 0), 0); + EXPECT_LE(moves->GetLength(3, 0), 14); // Max 13 cards + 1 + EXPECT_GE(moves->GetLength(12, 3), 0); + EXPECT_LE(moves->GetLength(12, 3), 14); } TEST_F(MovesTest, GetLengthHandlesEmptyList) { - // Verify list lengths are reasonable (0-14 for max 13 cards) - for (int t = 0; t < 13; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - int length = moves->GetLength(t, h); - EXPECT_GE(length, 0); - EXPECT_LE(length, 14); + // Verify list lengths are reasonable (0-14 for max 13 cards) + for (int t = 0; t < 13; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + int length = moves->GetLength(t, h); + EXPECT_GE(length, 0); + EXPECT_LE(length, 14); + } } - } } TEST_F(MovesTest, PrintMoveReturnsValidString) { - // PrintMove should return a string when given a MovePlyType - // It's primarily for debugging, so just verify it doesn't crash - EXPECT_NO_THROW({ - auto result = moves->PrintMove(moves->moveList[0][0]); - EXPECT_FALSE(result.empty()); - }); + // PrintMove should return a string when given a MovePlyType + // It's primarily for debugging, so just verify it doesn't crash + EXPECT_NO_THROW({ + auto result = moves->PrintMove(moves->moveList[0][0]); + EXPECT_FALSE(result.empty()); + }); } /** @@ -144,18 +144,18 @@ TEST_F(MovesTest, PrintMoveReturnsValidString) { */ TEST_F(MovesTest, PointersInitializedToNullptr) { - // After construction, pointers should be nullptr - EXPECT_EQ(moves->trackp, nullptr); - EXPECT_EQ(moves->mply, nullptr); + // After construction, pointers should be nullptr + EXPECT_EQ(moves->trackp, nullptr); + EXPECT_EQ(moves->mply, nullptr); } TEST_F(MovesTest, PointersSetCorrectlyDuringInit) { - // After init, trackp should still be nullptr (it's set later in MoveGen0) - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - - // After init, trackp should still be nullptr (it's set later in MoveGen0) - EXPECT_EQ(moves->trackp, nullptr); + // After init, trackp should still be nullptr (it's set later in MoveGen0) + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + + // After init, trackp should still be nullptr (it's set later in MoveGen0) + EXPECT_EQ(moves->trackp, nullptr); } /** @@ -163,28 +163,28 @@ TEST_F(MovesTest, PointersSetCorrectlyDuringInit) { */ TEST_F(MovesTest, MgTypeEnumHasExpectedValues) { - // Verify enum values are as expected - EXPECT_EQ(static_cast(MgType::NT0), 0); - EXPECT_EQ(static_cast(MgType::TRUMP0), 1); - EXPECT_EQ(static_cast(MgType::NT_VOID1), 2); - EXPECT_EQ(static_cast(MgType::TRUMP_VOID1), 3); - EXPECT_EQ(static_cast(MgType::NT_NOTVOID1), 4); - EXPECT_EQ(static_cast(MgType::TRUMP_NOTVOID1), 5); - EXPECT_EQ(static_cast(MgType::NT_VOID2), 6); - EXPECT_EQ(static_cast(MgType::TRUMP_VOID2), 7); - // Verify SIZE is last - EXPECT_GT(static_cast(MgType::SIZE), 7); + // Verify enum values are as expected + EXPECT_EQ(static_cast(MgType::NT0), 0); + EXPECT_EQ(static_cast(MgType::TRUMP0), 1); + EXPECT_EQ(static_cast(MgType::NT_VOID1), 2); + EXPECT_EQ(static_cast(MgType::TRUMP_VOID1), 3); + EXPECT_EQ(static_cast(MgType::NT_NOTVOID1), 4); + EXPECT_EQ(static_cast(MgType::TRUMP_NOTVOID1), 5); + EXPECT_EQ(static_cast(MgType::NT_VOID2), 6); + EXPECT_EQ(static_cast(MgType::TRUMP_VOID2), 7); + // Verify SIZE is last + EXPECT_GT(static_cast(MgType::SIZE), 7); } TEST_F(MovesTest, FuncNameArrayHasSizeElements) { - // Verify funcName array has correct size - int count = 0; - for (int i = 0; i < static_cast(MgType::SIZE); i++) { - if (!moves->funcName[i].empty()) { - count++; + // Verify funcName array has correct size + int count = 0; + for (int i = 0; i < static_cast(MgType::SIZE); i++) { + if (!moves->funcName[i].empty()) { + count++; + } } - } - EXPECT_EQ(count, static_cast(MgType::SIZE)); + EXPECT_EQ(count, static_cast(MgType::SIZE)); } /** @@ -192,30 +192,30 @@ TEST_F(MovesTest, FuncNameArrayHasSizeElements) { */ TEST_F(MovesTest, TrackArrayHas13Tricks) { - // Verify track array has 13 tricks - EXPECT_EQ(std::size(moves->track), 13); + // Verify track array has 13 tricks + EXPECT_EQ(std::size(moves->track), 13); } TEST_F(MovesTest, MoveListArrayHas13TricksAnd4Hands) { - // Verify moveList array dimensions - EXPECT_EQ(std::size(moves->moveList), 13); - for (int t = 0; t < 13; t++) { - EXPECT_EQ(std::size(moves->moveList[t]), 4); - } + // Verify moveList array dimensions + EXPECT_EQ(std::size(moves->moveList), 13); + for (int t = 0; t < 13; t++) { + EXPECT_EQ(std::size(moves->moveList[t]), 4); + } } TEST_F(MovesTest, LastCallArrayHas13TricksAnd4Hands) { - // Verify lastCall array dimensions - EXPECT_EQ(std::size(moves->lastCall), 13); - for (int t = 0; t < 13; t++) { - EXPECT_EQ(std::size(moves->lastCall[t]), 4); - } + // Verify lastCall array dimensions + EXPECT_EQ(std::size(moves->lastCall), 13); + for (int t = 0; t < 13; t++) { + EXPECT_EQ(std::size(moves->lastCall[t]), 4); + } } TEST_F(MovesTest, StatisticsStructuresProperlyInitialized) { - // Verify statistics structures are initialized - EXPECT_EQ(moves->trickFuncTable.nfuncs, 0); - EXPECT_EQ(moves->trickFuncSuitTable.nfuncs, 0); + // Verify statistics structures are initialized + EXPECT_EQ(moves->trickFuncTable.nfuncs, 0); + EXPECT_EQ(moves->trickFuncSuitTable.nfuncs, 0); } /** @@ -223,32 +223,32 @@ TEST_F(MovesTest, StatisticsStructuresProperlyInitialized) { */ TEST_F(MovesTest, CreateAndDestroySuccessfully) { - // Verify object can be created and destroyed - auto testMoves = std::make_unique(); - EXPECT_NE(testMoves.get(), nullptr); - testMoves.reset(); - EXPECT_TRUE(true); // If we got here, no crash + // Verify object can be created and destroyed + auto testMoves = std::make_unique(); + EXPECT_NE(testMoves.get(), nullptr); + testMoves.reset(); + EXPECT_TRUE(true); // If we got here, no crash } TEST_F(MovesTest, MultipleInitializeCallsWork) { - // Verify multiple Init calls work correctly - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - - for (int t = 0; t < 13; t++) { - moves->Init(t, 0, nullptr, nullptr, rankInSuit, 0, t % 4); - EXPECT_EQ(moves->currTrick, t); - } + // Verify multiple Init calls work correctly + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + + for (int t = 0; t < 13; t++) { + moves->Init(t, 0, nullptr, nullptr, rankInSuit, 0, t % 4); + EXPECT_EQ(moves->currTrick, t); + } } TEST_F(MovesTest, GetLengthWithValidBounds) { - // Verify GetLength works for all valid bounds - for (int t = 0; t < 13; t++) { - for (int h = 0; h < DDS_HANDS; h++) { - auto length = moves->GetLength(t, h); - EXPECT_GE(length, 0); - EXPECT_LE(length, 13); + // Verify GetLength works for all valid bounds + for (int t = 0; t < 13; t++) { + for (int h = 0; h < DDS_HANDS; h++) { + auto length = moves->GetLength(t, h); + EXPECT_GE(length, 0); + EXPECT_LE(length, 13); + } } - } } /** @@ -256,23 +256,23 @@ TEST_F(MovesTest, GetLengthWithValidBounds) { */ TEST_F(MovesTest, FunctionNamesAreHumanReadable) { - // Verify function names are readable strings - for (int i = 0; i < static_cast(MgType::SIZE); i++) { - const auto& name = moves->funcName[i]; - EXPECT_FALSE(name.empty()); - EXPECT_GT(name.length(), 0); - // Name should have printable characters - for (char c : name) { - EXPECT_TRUE(std::isprint(c) || c == ' '); + // Verify function names are readable strings + for (int i = 0; i < static_cast(MgType::SIZE); i++) { + const auto& name = moves->funcName[i]; + EXPECT_FALSE(name.empty()); + EXPECT_GT(name.length(), 0); + // Name should have printable characters + for (char c : name) { + EXPECT_TRUE(std::isprint(c) || c == ' '); + } } - } } TEST_F(MovesTest, MemorySafetyFeaturesArePresent) { - // Verify key memory safety features are in place - EXPECT_EQ(moves->trackp, nullptr); // Non-owning pointer initialized - EXPECT_EQ(moves->mply, nullptr); // Non-owning pointer initialized - EXPECT_FALSE(moves->funcName[0].empty()); // funcName array exists and is initialized + // Verify key memory safety features are in place + EXPECT_EQ(moves->trackp, nullptr); // Non-owning pointer initialized + EXPECT_EQ(moves->mply, nullptr); // Non-owning pointer initialized + EXPECT_FALSE(moves->funcName[0].empty()); // funcName array exists and is initialized } namespace @@ -280,17 +280,17 @@ namespace auto poisoned_track() -> TrackType { - TrackType tr{}; - for (int s = 0; s < DDS_SUITS; ++s) - tr.removed_ranks[s] = 0x10 + s; - // Non-zero trick slots: only those defined for the current hand_rel should - // appear in the HeuristicContext snapshot. - tr.move[0] = ExtCard{0, 14, 0}; - tr.move[1] = ExtCard{2, 12, 0}; - tr.high[1] = 1; - tr.move[2] = ExtCard{3, 9, 0}; - tr.high[2] = 2; - return tr; + TrackType tr{}; + for (int s = 0; s < DDS_SUITS; ++s) + tr.removed_ranks[s] = 0x10 + s; + // Non-zero trick slots: only those defined for the current hand_rel should + // appear in the HeuristicContext snapshot. + tr.move[0] = ExtCard{0, 14, 0}; + tr.move[1] = ExtCard{2, 12, 0}; + tr.high[1] = 1; + tr.move[2] = ExtCard{3, 9, 0}; + tr.high[2] = 2; + return tr; } // HeuristicContext stores references to these; they must outlive the returned @@ -300,17 +300,17 @@ static const MoveType kBest{}; static const MoveType kBestTt{}; auto context_for_hand_rel(Moves& m, const TrackType& tr, const int hand_rel) - -> HeuristicContext + -> HeuristicContext { - m.leadHand = 0; - m.currHand = hand_rel; // leadHand 0 ⇒ hand_rel == currHand - m.leadSuit = 0; - m.currTrick = 0; - m.trump = DDS_NOTRUMP; - m.suit = 0; - m.numMoves = 0; - m.lastNumMoves = 0; - return m.make_heuristic_context(kTpos, kBest, kBestTt, nullptr, tr); + m.leadHand = 0; + m.currHand = hand_rel; // leadHand 0 ⇒ hand_rel == currHand + m.leadSuit = 0; + m.currTrick = 0; + m.trump = DDS_NOTRUMP; + m.suit = 0; + m.numMoves = 0; + m.lastNumMoves = 0; + return m.make_heuristic_context(kTpos, kBest, kBestTt, nullptr, tr); } } // namespace @@ -322,79 +322,79 @@ auto context_for_hand_rel(Moves& m, const TrackType& tr, const int hand_rel) */ TEST_F(MovesTest, MakeHeuristicContextSnapshotsExplicitTrack) { - const TrackType tr = poisoned_track(); - ASSERT_EQ(moves->trackp, nullptr); + const TrackType tr = poisoned_track(); + ASSERT_EQ(moves->trackp, nullptr); - const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/3); + const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/3); - for (int s = 0; s < DDS_SUITS; ++s) - EXPECT_EQ(ctx.removed_ranks[s], 0x10 + s) << "suit=" << s; - EXPECT_EQ(ctx.lead0_rank, 14); - EXPECT_EQ(ctx.move1_rank, 12); - EXPECT_EQ(ctx.move1_suit, 2); - EXPECT_EQ(ctx.high1, 1); - EXPECT_EQ(ctx.move2_rank, 9); - EXPECT_EQ(ctx.move2_suit, 3); - EXPECT_EQ(ctx.high2, 2); - EXPECT_EQ(ctx.trackp, &tr); + for (int s = 0; s < DDS_SUITS; ++s) + EXPECT_EQ(ctx.removed_ranks[s], 0x10 + s) << "suit=" << s; + EXPECT_EQ(ctx.lead0_rank, 14); + EXPECT_EQ(ctx.move1_rank, 12); + EXPECT_EQ(ctx.move1_suit, 2); + EXPECT_EQ(ctx.high1, 1); + EXPECT_EQ(ctx.move2_rank, 9); + EXPECT_EQ(ctx.move2_suit, 3); + EXPECT_EQ(ctx.high2, 2); + EXPECT_EQ(ctx.trackp, &tr); } // HeuristicContext holds references to tpos / best_move / best_move_tt; the // helper must bind them to storage that outlives the returned context. TEST_F(MovesTest, MakeHeuristicContextBindsStableRefs) { - const TrackType tr = poisoned_track(); - const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/0); + const TrackType tr = poisoned_track(); + const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/0); - EXPECT_EQ(&ctx.tpos, &kTpos); - EXPECT_EQ(&ctx.best_move, &kBest); - EXPECT_EQ(&ctx.best_move_tt, &kBestTt); + EXPECT_EQ(&ctx.tpos, &kTpos); + EXPECT_EQ(&ctx.best_move, &kBest); + EXPECT_EQ(&ctx.best_move_tt, &kBestTt); } // Leading hand: move[0..2] are not played yet — leave trick snapshots at 0 // even if the TrackType buffer holds stale/poisoned values. TEST_F(MovesTest, MakeHeuristicContextLeadHandOmitsUnsetTrickCards) { - const TrackType tr = poisoned_track(); - const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/0); + const TrackType tr = poisoned_track(); + const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/0); - EXPECT_EQ(ctx.lead0_rank, 0); - EXPECT_EQ(ctx.move1_rank, 0); - EXPECT_EQ(ctx.move1_suit, 0); - EXPECT_EQ(ctx.high1, 0); - EXPECT_EQ(ctx.move2_rank, 0); - EXPECT_EQ(ctx.move2_suit, 0); - EXPECT_EQ(ctx.high2, 0); + EXPECT_EQ(ctx.lead0_rank, 0); + EXPECT_EQ(ctx.move1_rank, 0); + EXPECT_EQ(ctx.move1_suit, 0); + EXPECT_EQ(ctx.high1, 0); + EXPECT_EQ(ctx.move2_rank, 0); + EXPECT_EQ(ctx.move2_suit, 0); + EXPECT_EQ(ctx.high2, 0); } // Second hand: only the lead card is defined. TEST_F(MovesTest, MakeHeuristicContextSecondHandSnapshotsOnlyLead) { - const TrackType tr = poisoned_track(); - const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/1); + const TrackType tr = poisoned_track(); + const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/1); - EXPECT_EQ(ctx.lead0_rank, 14); - EXPECT_EQ(ctx.move1_rank, 0); - EXPECT_EQ(ctx.move1_suit, 0); - EXPECT_EQ(ctx.high1, 0); - EXPECT_EQ(ctx.move2_rank, 0); - EXPECT_EQ(ctx.move2_suit, 0); - EXPECT_EQ(ctx.high2, 0); + EXPECT_EQ(ctx.lead0_rank, 14); + EXPECT_EQ(ctx.move1_rank, 0); + EXPECT_EQ(ctx.move1_suit, 0); + EXPECT_EQ(ctx.high1, 0); + EXPECT_EQ(ctx.move2_rank, 0); + EXPECT_EQ(ctx.move2_suit, 0); + EXPECT_EQ(ctx.high2, 0); } // Third hand: lead + second-hand card/high are defined; move[2] is not. TEST_F(MovesTest, MakeHeuristicContextThirdHandSnapshotsThroughMove1) { - const TrackType tr = poisoned_track(); - const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/2); + const TrackType tr = poisoned_track(); + const HeuristicContext ctx = context_for_hand_rel(*moves, tr, /*hand_rel=*/2); - EXPECT_EQ(ctx.lead0_rank, 14); - EXPECT_EQ(ctx.move1_rank, 12); - EXPECT_EQ(ctx.move1_suit, 2); - EXPECT_EQ(ctx.high1, 1); - EXPECT_EQ(ctx.move2_rank, 0); - EXPECT_EQ(ctx.move2_suit, 0); - EXPECT_EQ(ctx.high2, 0); + EXPECT_EQ(ctx.lead0_rank, 14); + EXPECT_EQ(ctx.move1_rank, 12); + EXPECT_EQ(ctx.move1_suit, 2); + EXPECT_EQ(ctx.high1, 1); + EXPECT_EQ(ctx.move2_rank, 0); + EXPECT_EQ(ctx.move2_suit, 0); + EXPECT_EQ(ctx.high2, 0); } namespace @@ -405,54 +405,54 @@ namespace // suit) overflows that buffer and trips ASan. struct CompactDeal { - unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS]{}; - Pos tpos{}; + unsigned short rank_in_suit[DDS_HANDS][DDS_SUITS]{}; + Pos tpos{}; }; auto popcount16(unsigned short bits) -> int { - int n = 0; - for (; bits != 0; bits = static_cast(bits & (bits - 1))) - ++n; - return n; + int n = 0; + for (; bits != 0; bits = static_cast(bits & (bits - 1))) + ++n; + return n; } auto make_compact_deal() -> CompactDeal { - CompactDeal d{}; - // Hand 0 (lead): AK of suit 0, Q of suit 1. - d.rank_in_suit[0][0] = 0x1800; - d.rank_in_suit[0][1] = 0x0400; - // Other hands: one card each for length/winner lookups. - d.rank_in_suit[1][0] = 0x0200; - d.rank_in_suit[1][2] = 0x0100; - d.rank_in_suit[2][0] = 0x0080; - d.rank_in_suit[3][1] = 0x0040; - - for (int h = 0; h < DDS_HANDS; ++h) - { - for (int s = 0; s < DDS_SUITS; ++s) + CompactDeal d{}; + // Hand 0 (lead): AK of suit 0, Q of suit 1. + d.rank_in_suit[0][0] = 0x1800; + d.rank_in_suit[0][1] = 0x0400; + // Other hands: one card each for length/winner lookups. + d.rank_in_suit[1][0] = 0x0200; + d.rank_in_suit[1][2] = 0x0100; + d.rank_in_suit[2][0] = 0x0080; + d.rank_in_suit[3][1] = 0x0040; + + for (int h = 0; h < DDS_HANDS; ++h) { - d.tpos.rank_in_suit[h][s] = d.rank_in_suit[h][s]; - d.tpos.length[h][s] = - static_cast(popcount16(d.rank_in_suit[h][s])); - d.tpos.aggr[s] = - static_cast(d.tpos.aggr[s] | d.rank_in_suit[h][s]); + for (int s = 0; s < DDS_SUITS; ++s) + { + d.tpos.rank_in_suit[h][s] = d.rank_in_suit[h][s]; + d.tpos.length[h][s] = + static_cast(popcount16(d.rank_in_suit[h][s])); + d.tpos.aggr[s] = + static_cast(d.tpos.aggr[s] | d.rank_in_suit[h][s]); + } } - } - return d; + return d; } auto move_list_weights(const Moves& m, const int tricks, const int hand_rel, const int n) -> std::vector { - EXPECT_GT(n, 0); - EXPECT_LE(n, 14); - std::vector weights; - weights.reserve(static_cast(n)); - for (int i = 0; i < n; ++i) - weights.push_back(m.moveList[tricks][hand_rel].move[i].weight); - return weights; + EXPECT_GT(n, 0); + EXPECT_LE(n, 14); + std::vector weights; + weights.reserve(static_cast(n)); + for (int i = 0; i < n; ++i) + weights.push_back(m.moveList[tricks][hand_rel].move[i].weight); + return weights; } } // namespace @@ -461,50 +461,50 @@ auto move_list_weights(const Moves& m, const int tricks, const int hand_rel, // legacy heuristic dispatcher. Out-of-range values behave like no-trump. TEST_F(MovesTest, MoveGen0OutOfRangeTrumpMatchesNoTrump) { - static RelRanksType rel[8192] = {}; - constexpr int tricks = 5; - const CompactDeal deal = make_compact_deal(); - const MoveType best{}; - - auto run = [&](const int trump) { - auto local = std::make_unique(); - local->Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, trump, 0); - const int n = local->MoveGen0(tricks, deal.tpos, best, best, rel); - return move_list_weights(*local, tricks, 0, n); - }; - - const auto nt = run(DDS_NOTRUMP); - // DDS_NOTRUMP == DDS_SUITS, so use values strictly outside [0, DDS_SUITS). - for (const int bad_trump : {-1, DDS_SUITS + 1, 99}) - { - const auto bad = run(bad_trump); - EXPECT_EQ(bad, nt) << "trump=" << bad_trump; - } + static RelRanksType rel[8192] = {}; + constexpr int tricks = 5; + const CompactDeal deal = make_compact_deal(); + const MoveType best{}; + + auto run = [&](const int trump) { + auto local = std::make_unique(); + local->Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, trump, 0); + const int n = local->MoveGen0(tricks, deal.tpos, best, best, rel); + return move_list_weights(*local, tricks, 0, n); + }; + + const auto nt = run(DDS_NOTRUMP); + // DDS_NOTRUMP == DDS_SUITS, so use values strictly outside [0, DDS_SUITS). + for (const int bad_trump : {-1, DDS_SUITS + 1, 99}) + { + const auto bad = run(bad_trump); + EXPECT_EQ(bad, nt) << "trump=" << bad_trump; + } } // MoveGen123 likewise must not index winner[trump] when trump is out of range. TEST_F(MovesTest, MoveGen123OutOfRangeTrumpMatchesNoTrump) { - constexpr int tricks = 5; - constexpr int hand_rel = 1; - constexpr int lead_suit = 0; - const CompactDeal deal = make_compact_deal(); - - auto run = [&](const int trump) { - auto local = std::make_unique(); - local->Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, trump, 0); - local->track[tricks].lead_suit = lead_suit; - local->track[tricks].move[0] = ExtCard{lead_suit, 8, 0}; - const int n = local->MoveGen123(tricks, hand_rel, deal.tpos); - return move_list_weights(*local, tricks, hand_rel, n); - }; - - const auto nt = run(DDS_NOTRUMP); - for (const int bad_trump : {-1, DDS_SUITS + 1, 99}) - { - const auto bad = run(bad_trump); - EXPECT_EQ(bad, nt) << "trump=" << bad_trump; - } + constexpr int tricks = 5; + constexpr int hand_rel = 1; + constexpr int lead_suit = 0; + const CompactDeal deal = make_compact_deal(); + + auto run = [&](const int trump) { + auto local = std::make_unique(); + local->Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, trump, 0); + local->track[tricks].lead_suit = lead_suit; + local->track[tricks].move[0] = ExtCard{lead_suit, 8, 0}; + const int n = local->MoveGen123(tricks, hand_rel, deal.tpos); + return move_list_weights(*local, tricks, hand_rel, n); + }; + + const auto nt = run(DDS_NOTRUMP); + for (const int bad_trump : {-1, DDS_SUITS + 1, 99}) + { + const auto bad = run(bad_trump); + EXPECT_EQ(bad, nt) << "trump=" << bad_trump; + } } // Hoisted make_heuristic_context must not observe stale suit/lastNumMoves from @@ -512,43 +512,43 @@ TEST_F(MovesTest, MoveGen123OutOfRangeTrumpMatchesNoTrump) // snapshotting so weights match a clean instance. TEST_F(MovesTest, MoveGenResetsSnapshottedFieldsBeforeHeuristicContext) { - static RelRanksType rel[8192] = {}; - constexpr int tricks = 5; - constexpr int lead_suit = 0; - CompactDeal deal = make_compact_deal(); - // Force a void-in-lead path for hand_rel=1 so suit/lastNumMoves are used - // by the void weight functions after the hoisted context is built. - deal.rank_in_suit[1][lead_suit] = 0; - deal.tpos.rank_in_suit[1][lead_suit] = 0; - deal.tpos.length[1][lead_suit] = 0; - const MoveType best{}; - - auto run_gen0 = [&](Moves& m) { - m.Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, DDS_NOTRUMP, 0); - return move_list_weights( - m, tricks, 0, m.MoveGen0(tricks, deal.tpos, best, best, rel)); - }; - auto run_gen123 = [&](Moves& m) { - m.Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, DDS_NOTRUMP, 0); - m.track[tricks].lead_suit = lead_suit; - m.track[tricks].move[0] = ExtCard{lead_suit, 8, 0}; - return move_list_weights( - m, tricks, 1, m.MoveGen123(tricks, /*handRel=*/1, deal.tpos)); - }; - - Moves clean; - const auto gen0_clean = run_gen0(clean); - const auto gen123_clean = run_gen123(clean); - - Moves poisoned; - poisoned.suit = 3; - poisoned.lastNumMoves = 99; - poisoned.leadSuit = 2; - EXPECT_EQ(run_gen0(poisoned), gen0_clean); - poisoned.suit = 3; - poisoned.lastNumMoves = 99; - poisoned.leadSuit = 2; - EXPECT_EQ(run_gen123(poisoned), gen123_clean); + static RelRanksType rel[8192] = {}; + constexpr int tricks = 5; + constexpr int lead_suit = 0; + CompactDeal deal = make_compact_deal(); + // Force a void-in-lead path for hand_rel=1 so suit/lastNumMoves are used + // by the void weight functions after the hoisted context is built. + deal.rank_in_suit[1][lead_suit] = 0; + deal.tpos.rank_in_suit[1][lead_suit] = 0; + deal.tpos.length[1][lead_suit] = 0; + const MoveType best{}; + + auto run_gen0 = [&](Moves& m) { + m.Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, DDS_NOTRUMP, 0); + return move_list_weights( + m, tricks, 0, m.MoveGen0(tricks, deal.tpos, best, best, rel)); + }; + auto run_gen123 = [&](Moves& m) { + m.Init(tricks, 0, nullptr, nullptr, deal.rank_in_suit, DDS_NOTRUMP, 0); + m.track[tricks].lead_suit = lead_suit; + m.track[tricks].move[0] = ExtCard{lead_suit, 8, 0}; + return move_list_weights( + m, tricks, 1, m.MoveGen123(tricks, /*handRel=*/1, deal.tpos)); + }; + + Moves clean; + const auto gen0_clean = run_gen0(clean); + const auto gen123_clean = run_gen123(clean); + + Moves poisoned; + poisoned.suit = 3; + poisoned.lastNumMoves = 99; + poisoned.leadSuit = 2; + EXPECT_EQ(run_gen0(poisoned), gen0_clean); + poisoned.suit = 3; + poisoned.lastNumMoves = 99; + poisoned.leadSuit = 2; + EXPECT_EQ(run_gen123(poisoned), gen123_clean); } /** @@ -556,169 +556,169 @@ TEST_F(MovesTest, MoveGenResetsSnapshottedFieldsBeforeHeuristicContext) */ TEST_F(MovesTest, ConstructionIsQuick) { - // Verify construction is fast - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < 1000; i++) { - auto temp = std::make_unique(); - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - // Should complete 1000 constructions in reasonable time - EXPECT_LT(duration.count(), 1000); // Less than 1 second for 1000 + // Verify construction is fast + auto start = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 1000; i++) { + auto temp = std::make_unique(); + } + + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + // Should complete 1000 constructions in reasonable time + EXPECT_LT(duration.count(), 1000); // Less than 1 second for 1000 } TEST_F(MovesTest, GetLengthIsQuick) { - // Verify GetLength is fast - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < 100000; i++) { - volatile int result = moves->GetLength(i % 13, i % 4); - (void)result; // Use result to prevent optimization - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - // Should complete 100k calls in reasonable time - EXPECT_LT(duration.count(), 500); // Less than 500ms for 100k + // Verify GetLength is fast + auto start = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 100000; i++) { + volatile int result = moves->GetLength(i % 13, i % 4); + (void)result; // Use result to prevent optimization + } + + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + // Should complete 100k calls in reasonable time + EXPECT_LT(duration.count(), 500); // Less than 500ms for 100k } TEST_F(MovesTest, ApplyMoveToTrackLeadHand) { - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - moves->trackp = &moves->track[5]; + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + moves->trackp = &moves->track[5]; - MoveType move; - move.suit = 2; // Diamonds - move.rank = 10; - move.sequence = 0; + MoveType move; + move.suit = 2; // Diamonds + move.rank = 10; + move.sequence = 0; - moves->apply_move_to_track(move, 0, 5); + moves->apply_move_to_track(move, 0, 5); - EXPECT_EQ(moves->trackp->move[0].suit, 2); - EXPECT_EQ(moves->trackp->move[0].rank, 10); - EXPECT_EQ(moves->trackp->high[0], 0); - EXPECT_EQ(moves->trackp->lead_suit, 2); - EXPECT_EQ(moves->trackp->play_suits[0], 2); - EXPECT_EQ(moves->trackp->play_ranks[0], 10); + EXPECT_EQ(moves->trackp->move[0].suit, 2); + EXPECT_EQ(moves->trackp->move[0].rank, 10); + EXPECT_EQ(moves->trackp->high[0], 0); + EXPECT_EQ(moves->trackp->lead_suit, 2); + EXPECT_EQ(moves->trackp->play_suits[0], 2); + EXPECT_EQ(moves->trackp->play_ranks[0], 10); } TEST_F(MovesTest, ApplyMoveToTrackFollowSuit) { - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - moves->trackp = &moves->track[5]; - - // Lead with Ace of Spades - MoveType lead; - lead.suit = 0; - lead.rank = 14; - lead.sequence = 0; - moves->apply_move_to_track(lead, 0, 5); - - // Follow with King of Spades — lower rank loses - MoveType follow; - follow.suit = 0; - follow.rank = 13; - follow.sequence = 0; - moves->apply_move_to_track(follow, 1, 5); - - // King < Ace so high stays at 0 (lead hand wins) - EXPECT_EQ(moves->trackp->high[1], 0); - EXPECT_EQ(moves->trackp->play_suits[1], 0); - EXPECT_EQ(moves->trackp->play_ranks[1], 13); + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + moves->trackp = &moves->track[5]; + + // Lead with Ace of Spades + MoveType lead; + lead.suit = 0; + lead.rank = 14; + lead.sequence = 0; + moves->apply_move_to_track(lead, 0, 5); + + // Follow with King of Spades — lower rank loses + MoveType follow; + follow.suit = 0; + follow.rank = 13; + follow.sequence = 0; + moves->apply_move_to_track(follow, 1, 5); + + // King < Ace so high stays at 0 (lead hand wins) + EXPECT_EQ(moves->trackp->high[1], 0); + EXPECT_EQ(moves->trackp->play_suits[1], 0); + EXPECT_EQ(moves->trackp->play_ranks[1], 13); } TEST_F(MovesTest, ApplyMoveToTrackTrumpBeatsNonTrump) { - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - // trump = 0 (Spades) - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 0, 0); - moves->trackp = &moves->track[5]; - - // Lead with Hearts (non-trump) - MoveType lead; - lead.suit = 1; - lead.rank = 14; - lead.sequence = 0; - moves->apply_move_to_track(lead, 0, 5); - - // Follow with Spades (trump) — trump wins - MoveType trump_card; - trump_card.suit = 0; - trump_card.rank = 2; - trump_card.sequence = 0; - moves->apply_move_to_track(trump_card, 1, 5); - - EXPECT_EQ(moves->trackp->high[1], 1); // hand 1 wins with trump - EXPECT_EQ(moves->trackp->move[1].suit, 0); - EXPECT_EQ(moves->trackp->move[1].rank, 2); + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + // trump = 0 (Spades) + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 0, 0); + moves->trackp = &moves->track[5]; + + // Lead with Hearts (non-trump) + MoveType lead; + lead.suit = 1; + lead.rank = 14; + lead.sequence = 0; + moves->apply_move_to_track(lead, 0, 5); + + // Follow with Spades (trump) — trump wins + MoveType trump_card; + trump_card.suit = 0; + trump_card.rank = 2; + trump_card.sequence = 0; + moves->apply_move_to_track(trump_card, 1, 5); + + EXPECT_EQ(moves->trackp->high[1], 1); // hand 1 wins with trump + EXPECT_EQ(moves->trackp->move[1].suit, 0); + EXPECT_EQ(moves->trackp->move[1].rank, 2); } TEST_F(MovesTest, ApplyMoveToTrackTrickCompletion) { - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - moves->trackp = &moves->track[5]; - moves->track[5].lead_hand = 0; - // Zero removed_ranks so we can verify apply_move_to_track sets specific bits - for (int s = 0; s < DDS_SUITS; s++) { - moves->track[5].removed_ranks[s] = 0; - moves->track[4].removed_ranks[s] = 0; - } - // All four hands play spades: A K Q J - MoveType cards[DDS_HANDS]; - for (int h = 0; h < DDS_HANDS; h++) { - cards[h].suit = 0; - cards[h].rank = 14 - h; - cards[h].sequence = 0; - moves->apply_move_to_track(cards[h], h, 5); - } - // Hand 0 played Ace - should win - EXPECT_EQ(moves->trackp->high[3], 0); - // Next trick lead_hand should be hand 0 - EXPECT_EQ(moves->track[4].lead_hand, 0); - // removed_ranks[0] (spades) should have bits set for A(0x1000) K(0x0800) Q(0x0400) J(0x0200) - EXPECT_EQ(moves->track[4].removed_ranks[0], 0x1E00); - // Other suits untouched - should remain 0 - EXPECT_EQ(moves->track[4].removed_ranks[1], 0); - EXPECT_EQ(moves->track[4].removed_ranks[2], 0); - EXPECT_EQ(moves->track[4].removed_ranks[3], 0); + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + moves->trackp = &moves->track[5]; + moves->track[5].lead_hand = 0; + // Zero removed_ranks so we can verify apply_move_to_track sets specific bits + for (int s = 0; s < DDS_SUITS; s++) { + moves->track[5].removed_ranks[s] = 0; + moves->track[4].removed_ranks[s] = 0; + } + // All four hands play spades: A K Q J + MoveType cards[DDS_HANDS]; + for (int h = 0; h < DDS_HANDS; h++) { + cards[h].suit = 0; + cards[h].rank = 14 - h; + cards[h].sequence = 0; + moves->apply_move_to_track(cards[h], h, 5); + } + // Hand 0 played Ace - should win + EXPECT_EQ(moves->trackp->high[3], 0); + // Next trick lead_hand should be hand 0 + EXPECT_EQ(moves->track[4].lead_hand, 0); + // removed_ranks[0] (spades) should have bits set for A(0x1000) K(0x0800) Q(0x0400) J(0x0200) + EXPECT_EQ(moves->track[4].removed_ranks[0], 0x1E00); + // Other suits untouched - should remain 0 + EXPECT_EQ(moves->track[4].removed_ranks[1], 0); + EXPECT_EQ(moves->track[4].removed_ranks[2], 0); + EXPECT_EQ(moves->track[4].removed_ranks[3], 0); } TEST_F(MovesTest, MakeNextSimplePropagatesRemovedRanksOnTrickCompletion) { - const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); - moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); - moves->track[5].lead_hand = 0; - - for (int s = 0; s < DDS_SUITS; s++) { - moves->track[5].removed_ranks[s] = 0; - moves->track[4].removed_ranks[s] = 0xFFFF; - } - - for (int h = 0; h < 3; h++) { - MoveType played; - played.suit = 0; - played.rank = 14 - h; - played.sequence = 0; - moves->apply_move_to_track(played, h, 5); - } - - MovePlyType &list = moves->moveList[5][3]; - list.current = 0; - list.last = 0; - list.move[0].suit = 0; - list.move[0].rank = 11; - list.move[0].sequence = 0; - - MoveType const *mp = moves->MakeNextSimple(5, 3); - ASSERT_NE(mp, nullptr); - EXPECT_EQ(mp->rank, 11); - - EXPECT_EQ(moves->track[4].removed_ranks[0], 0x1E00); - EXPECT_EQ(moves->track[4].removed_ranks[1], 0); - EXPECT_EQ(moves->track[4].removed_ranks[2], 0); - EXPECT_EQ(moves->track[4].removed_ranks[3], 0); - EXPECT_EQ(moves->track[4].lead_hand, 0); + const unsigned short (*rankInSuit)[DDS_SUITS] = getSampleRankInSuit(); + moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0); + moves->track[5].lead_hand = 0; + + for (int s = 0; s < DDS_SUITS; s++) { + moves->track[5].removed_ranks[s] = 0; + moves->track[4].removed_ranks[s] = 0xFFFF; + } + + for (int h = 0; h < 3; h++) { + MoveType played; + played.suit = 0; + played.rank = 14 - h; + played.sequence = 0; + moves->apply_move_to_track(played, h, 5); + } + + MovePlyType &list = moves->moveList[5][3]; + list.current = 0; + list.last = 0; + list.move[0].suit = 0; + list.move[0].rank = 11; + list.move[0].sequence = 0; + + MoveType const *mp = moves->MakeNextSimple(5, 3); + ASSERT_NE(mp, nullptr); + EXPECT_EQ(mp->rank, 11); + + EXPECT_EQ(moves->track[4].removed_ranks[0], 0x1E00); + EXPECT_EQ(moves->track[4].removed_ranks[1], 0); + EXPECT_EQ(moves->track[4].removed_ranks[2], 0); + EXPECT_EQ(moves->track[4].removed_ranks[3], 0); + EXPECT_EQ(moves->track[4].lead_hand, 0); } diff --git a/library/tests/par_validation_test.cpp b/library/tests/par_validation_test.cpp index 15f5fb158..4d941a9de 100644 --- a/library/tests/par_validation_test.cpp +++ b/library/tests/par_validation_test.cpp @@ -23,27 +23,27 @@ namespace { /// A legal table: trick counts per strain summing to 13 across the two sides. auto legal_table() -> DdTableResults { - DdTableResults tab; - std::memset(&tab, 0, sizeof(tab)); - for (int d = 0; d < DDS_STRAINS; d++) - { - tab.res_table[d][0] = 7; // North - tab.res_table[d][1] = 6; // East - tab.res_table[d][2] = 7; // South - tab.res_table[d][3] = 6; // West - } - return tab; + DdTableResults tab; + std::memset(&tab, 0, sizeof(tab)); + for (int d = 0; d < DDS_STRAINS; d++) + { + tab.res_table[d][0] = 7; // North + tab.res_table[d][1] = 6; // East + tab.res_table[d][2] = 7; // South + tab.res_table[d][3] = 6; // West + } + return tab; } /// A table with every entry set to `v`. auto uniform_table(int v) -> DdTableResults { - DdTableResults tab; - std::memset(&tab, 0, sizeof(tab)); - for (int d = 0; d < DDS_STRAINS; d++) - for (int h = 0; h < DDS_HANDS; h++) - tab.res_table[d][h] = v; - return tab; + DdTableResults tab; + std::memset(&tab, 0, sizeof(tab)); + for (int d = 0; d < DDS_STRAINS; d++) + for (int h = 0; h < DDS_HANDS; h++) + tab.res_table[d][h] = v; + return tab; } // --------------------------------------------------------------------------- @@ -52,49 +52,49 @@ auto uniform_table(int v) -> DdTableResults TEST(ParValidation, ParRejectsOverflowingTable) { - DdTableResults const tab = uniform_table(2000000000); - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); + DdTableResults const tab = uniform_table(2000000000); + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); + EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); } TEST(ParValidation, SidesParRejectsOverflowingTable) { - DdTableResults const tab = uniform_table(2000000000); - ParResultsDealer sides[2]; - std::memset(sides, 0, sizeof(sides)); + DdTableResults const tab = uniform_table(2000000000); + ParResultsDealer sides[2]; + std::memset(sides, 0, sizeof(sides)); - EXPECT_EQ(SidesPar(&tab, sides, 0), RETURN_PAR_TABLE_FAULT); - // The pre-fix failure wrote 26 characters into this 10-byte field. - EXPECT_LT(std::strlen(sides[0].contracts[0]), sizeof(sides[0].contracts[0])); + EXPECT_EQ(SidesPar(&tab, sides, 0), RETURN_PAR_TABLE_FAULT); + // The pre-fix failure wrote 26 characters into this 10-byte field. + EXPECT_LT(std::strlen(sides[0].contracts[0]), sizeof(sides[0].contracts[0])); } TEST(ParValidation, SidesParBinRejectsOverflowingTable) { - DdTableResults const tab = uniform_table(2000000000); - ParResultsMaster sides[2]; - std::memset(sides, 0, sizeof(sides)); + DdTableResults const tab = uniform_table(2000000000); + ParResultsMaster sides[2]; + std::memset(sides, 0, sizeof(sides)); - EXPECT_EQ(SidesParBin(&tab, sides, 0), RETURN_PAR_TABLE_FAULT); + EXPECT_EQ(SidesParBin(&tab, sides, 0), RETURN_PAR_TABLE_FAULT); } TEST(ParValidation, DealerParRejectsOverflowingTable) { - DdTableResults const tab = uniform_table(2000000000); - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); + DdTableResults const tab = uniform_table(2000000000); + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerPar(&tab, &resp, 0, 0), RETURN_PAR_TABLE_FAULT); + EXPECT_EQ(DealerPar(&tab, &resp, 0, 0), RETURN_PAR_TABLE_FAULT); } TEST(ParValidation, DealerParBinRejectsOverflowingTable) { - DdTableResults const tab = uniform_table(2000000000); - ParResultsMaster resp; - std::memset(&resp, 0, sizeof(resp)); + DdTableResults const tab = uniform_table(2000000000); + ParResultsMaster resp; + std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerParBin(&tab, &resp, 0, 0), RETURN_PAR_TABLE_FAULT); + EXPECT_EQ(DealerParBin(&tab, &resp, 0, 0), RETURN_PAR_TABLE_FAULT); } // --------------------------------------------------------------------------- @@ -103,61 +103,61 @@ TEST(ParValidation, DealerParBinRejectsOverflowingTable) TEST(ParValidation, ThirteenTricksIsAccepted) { - // 13 is the largest legal trick count and must not be rejected. - DdTableResults tab = legal_table(); - tab.res_table[0][0] = 13; - tab.res_table[0][2] = 13; - tab.res_table[0][1] = 0; - tab.res_table[0][3] = 0; - - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, 0), RETURN_NO_FAULT); + // 13 is the largest legal trick count and must not be rejected. + DdTableResults tab = legal_table(); + tab.res_table[0][0] = 13; + tab.res_table[0][2] = 13; + tab.res_table[0][1] = 0; + tab.res_table[0][3] = 0; + + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(&tab, &resp, 0), RETURN_NO_FAULT); } TEST(ParValidation, FourteenTricksIsRejected) { - DdTableResults tab = legal_table(); - tab.res_table[2][1] = 14; + DdTableResults tab = legal_table(); + tab.res_table[2][1] = 14; - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); } TEST(ParValidation, NegativeTrickCountIsRejected) { - DdTableResults tab = legal_table(); - tab.res_table[3][2] = -1; + DdTableResults tab = legal_table(); + tab.res_table[3][2] = -1; - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT); } TEST(ParValidation, SingleBadEntryAnywhereIsRejected) { - // Every position is checked, not just the first. - for (int d = 0; d < DDS_STRAINS; d++) - { - for (int h = 0; h < DDS_HANDS; h++) + // Every position is checked, not just the first. + for (int d = 0; d < DDS_STRAINS; d++) { - DdTableResults tab = legal_table(); - tab.res_table[d][h] = 99; - - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT) - << "strain " << d << ", hand " << h; + for (int h = 0; h < DDS_HANDS; h++) + { + DdTableResults tab = legal_table(); + tab.res_table[d][h] = 99; + + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(&tab, &resp, 0), RETURN_PAR_TABLE_FAULT) + << "strain " << d << ", hand " << h; + } } - } } TEST(ParValidation, NullTableIsRejected) { - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(nullptr, &resp, 0), RETURN_PAR_TABLE_FAULT); + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(nullptr, &resp, 0), RETURN_PAR_TABLE_FAULT); } // --------------------------------------------------------------------------- @@ -166,48 +166,48 @@ TEST(ParValidation, NullTableIsRejected) TEST(ParValidation, LegalTableStillProducesAParResult) { - DdTableResults const tab = legal_table(); - - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - ASSERT_EQ(Par(&tab, &resp, 0), RETURN_NO_FAULT); - EXPECT_GT(std::strlen(resp.par_score[0]), 0u); - EXPECT_LT(std::strlen(resp.par_score[0]), sizeof(resp.par_score[0])); - EXPECT_LT(std::strlen(resp.par_contracts_string[0]), - sizeof(resp.par_contracts_string[0])); + DdTableResults const tab = legal_table(); + + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + ASSERT_EQ(Par(&tab, &resp, 0), RETURN_NO_FAULT); + EXPECT_GT(std::strlen(resp.par_score[0]), 0u); + EXPECT_LT(std::strlen(resp.par_score[0]), sizeof(resp.par_score[0])); + EXPECT_LT(std::strlen(resp.par_contracts_string[0]), + sizeof(resp.par_contracts_string[0])); } TEST(ParValidation, LegalTableAcceptedByAllEntryPoints) { - DdTableResults const tab = legal_table(); + DdTableResults const tab = legal_table(); - ParResultsDealer sides[2]; - std::memset(sides, 0, sizeof(sides)); - EXPECT_EQ(SidesPar(&tab, sides, 0), RETURN_NO_FAULT); + ParResultsDealer sides[2]; + std::memset(sides, 0, sizeof(sides)); + EXPECT_EQ(SidesPar(&tab, sides, 0), RETURN_NO_FAULT); - ParResultsMaster sidesBin[2]; - std::memset(sidesBin, 0, sizeof(sidesBin)); - EXPECT_EQ(SidesParBin(&tab, sidesBin, 0), RETURN_NO_FAULT); + ParResultsMaster sidesBin[2]; + std::memset(sidesBin, 0, sizeof(sidesBin)); + EXPECT_EQ(SidesParBin(&tab, sidesBin, 0), RETURN_NO_FAULT); - ParResultsDealer dealerRes; - std::memset(&dealerRes, 0, sizeof(dealerRes)); - EXPECT_EQ(DealerPar(&tab, &dealerRes, 0, 0), RETURN_NO_FAULT); + ParResultsDealer dealerRes; + std::memset(&dealerRes, 0, sizeof(dealerRes)); + EXPECT_EQ(DealerPar(&tab, &dealerRes, 0, 0), RETURN_NO_FAULT); - ParResultsMaster dealerBin; - std::memset(&dealerBin, 0, sizeof(dealerBin)); - EXPECT_EQ(DealerParBin(&tab, &dealerBin, 0, 0), RETURN_NO_FAULT); + ParResultsMaster dealerBin; + std::memset(&dealerBin, 0, sizeof(dealerBin)); + EXPECT_EQ(DealerParBin(&tab, &dealerBin, 0, 0), RETURN_NO_FAULT); } TEST(ParValidation, AllLegalVulnerabilitiesAccepted) { - DdTableResults const tab = legal_table(); - for (int vul = 0; vul <= 3; vul++) - { - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerPar(&tab, &resp, 0, vul), RETURN_NO_FAULT) - << "vulnerable = " << vul; - } + DdTableResults const tab = legal_table(); + for (int vul = 0; vul <= 3; vul++) + { + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(DealerPar(&tab, &resp, 0, vul), RETURN_NO_FAULT) + << "vulnerable = " << vul; + } } // --------------------------------------------------------------------------- @@ -217,15 +217,15 @@ TEST(ParValidation, AllLegalVulnerabilitiesAccepted) TEST(ParValidation, DealerParRejectsOutOfRangeVulnerability) { - DdTableResults const tab = legal_table(); + DdTableResults const tab = legal_table(); - for (int vul : {-1, 4, 99}) - { - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerPar(&tab, &resp, 0, vul), RETURN_UNKNOWN_FAULT) - << "vulnerable = " << vul; - } + for (int vul : {-1, 4, 99}) + { + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(DealerPar(&tab, &resp, 0, vul), RETURN_UNKNOWN_FAULT) + << "vulnerable = " << vul; + } } // --------------------------------------------------------------------------- @@ -238,72 +238,72 @@ TEST(ParValidation, DealerParRejectsOutOfRangeVulnerability) TEST(ParValidation, DealerParRejectsOutOfRangeDealer) { - DdTableResults const tab = legal_table(); + DdTableResults const tab = legal_table(); - for (int dealer : {-1, 4, 99, -2147483647}) - { - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_UNKNOWN_FAULT) - << "dealer = " << dealer; - } + for (int dealer : {-1, 4, 99, -2147483647}) + { + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_UNKNOWN_FAULT) + << "dealer = " << dealer; + } } TEST(ParValidation, DealerParBinRejectsOutOfRangeDealer) { - DdTableResults const tab = legal_table(); + DdTableResults const tab = legal_table(); - for (int dealer : {-1, 4}) - { - ParResultsMaster resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerParBin(&tab, &resp, dealer, 0), RETURN_UNKNOWN_FAULT) - << "dealer = " << dealer; - } + for (int dealer : {-1, 4}) + { + ParResultsMaster resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(DealerParBin(&tab, &resp, dealer, 0), RETURN_UNKNOWN_FAULT) + << "dealer = " << dealer; + } } TEST(ParValidation, AllLegalDealersAccepted) { - DdTableResults const tab = legal_table(); + DdTableResults const tab = legal_table(); - for (int dealer = 0; dealer <= 3; dealer++) - { - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_NO_FAULT) - << "dealer = " << dealer; - } + for (int dealer = 0; dealer <= 3; dealer++) + { + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_NO_FAULT) + << "dealer = " << dealer; + } } TEST(ParValidation, SacrificeContractTextIsWellFormed) { - // A table where sacrificing is right, so the text path that used to index - // NUMBER_TO_PLAYER out of bounds actually runs. No "?" placeholder should - // appear: that would mean an index escaped DealerPar()'s range checks. - DdTableResults tab; - std::memset(&tab, 0, sizeof(tab)); - for (int d = 0; d < DDS_STRAINS; d++) - { - tab.res_table[d][0] = 12; - tab.res_table[d][1] = 1; - tab.res_table[d][2] = 12; - tab.res_table[d][3] = 1; - } - - for (int dealer = 0; dealer <= 3; dealer++) - { - ParResultsDealer resp; - std::memset(&resp, 0, sizeof(resp)); - ASSERT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_NO_FAULT); + // A table where sacrificing is right, so the text path that used to index + // NUMBER_TO_PLAYER out of bounds actually runs. No "?" placeholder should + // appear: that would mean an index escaped DealerPar()'s range checks. + DdTableResults tab; + std::memset(&tab, 0, sizeof(tab)); + for (int d = 0; d < DDS_STRAINS; d++) + { + tab.res_table[d][0] = 12; + tab.res_table[d][1] = 1; + tab.res_table[d][2] = 12; + tab.res_table[d][3] = 1; + } - for (int k = 0; k < resp.number; k++) + for (int dealer = 0; dealer <= 3; dealer++) { - std::string const contract(resp.contracts[k]); - EXPECT_EQ(contract.find('?'), std::string::npos) - << "dealer " << dealer << " contract " << k << ": " << contract; - EXPECT_LT(contract.size(), sizeof(resp.contracts[k])); + ParResultsDealer resp; + std::memset(&resp, 0, sizeof(resp)); + ASSERT_EQ(DealerPar(&tab, &resp, dealer, 0), RETURN_NO_FAULT); + + for (int k = 0; k < resp.number; k++) + { + std::string const contract(resp.contracts[k]); + EXPECT_EQ(contract.find('?'), std::string::npos) + << "dealer " << dealer << " contract " << k << ": " << contract; + EXPECT_LT(contract.size(), sizeof(resp.contracts[k])); + } } - } } // --------------------------------------------------------------------------- @@ -315,58 +315,58 @@ TEST(ParValidation, SacrificeContractTextIsWellFormed) TEST(ParValidation, DirectEntryPointsRejectOutOfRangeVulnerability) { - DdTableResults const tab = legal_table(); - - for (int vul : {-1, 4, 99}) - { - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - EXPECT_EQ(Par(&tab, &resp, vul), RETURN_UNKNOWN_FAULT) - << "Par, vulnerable = " << vul; + DdTableResults const tab = legal_table(); - ParResultsDealer sides[2]; - std::memset(sides, 0, sizeof(sides)); - EXPECT_EQ(SidesPar(&tab, sides, vul), RETURN_UNKNOWN_FAULT) - << "SidesPar, vulnerable = " << vul; - - ParResultsMaster sides_bin[2]; - std::memset(sides_bin, 0, sizeof(sides_bin)); - EXPECT_EQ(SidesParBin(&tab, sides_bin, vul), RETURN_UNKNOWN_FAULT) - << "SidesParBin, vulnerable = " << vul; - } + for (int vul : {-1, 4, 99}) + { + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + EXPECT_EQ(Par(&tab, &resp, vul), RETURN_UNKNOWN_FAULT) + << "Par, vulnerable = " << vul; + + ParResultsDealer sides[2]; + std::memset(sides, 0, sizeof(sides)); + EXPECT_EQ(SidesPar(&tab, sides, vul), RETURN_UNKNOWN_FAULT) + << "SidesPar, vulnerable = " << vul; + + ParResultsMaster sides_bin[2]; + std::memset(sides_bin, 0, sizeof(sides_bin)); + EXPECT_EQ(SidesParBin(&tab, sides_bin, vul), RETURN_UNKNOWN_FAULT) + << "SidesParBin, vulnerable = " << vul; + } } TEST(ParValidation, VulnerabilityActuallyChangesTheParScore) { - // Guards against the check above being satisfied by a stub: the four legal - // vulnerabilities must not all produce identical output. A sacrifice table - // is used because that is where doubled undertricks make vulnerability - // change the score; a flat partscore table scores the same either way. - DdTableResults tab; - std::memset(&tab, 0, sizeof(tab)); - for (int d = 0; d < DDS_STRAINS; d++) - { - tab.res_table[d][0] = 12; - tab.res_table[d][1] = 1; - tab.res_table[d][2] = 12; - tab.res_table[d][3] = 1; - } - - std::string first; - bool differs = false; - for (int vul = 0; vul <= 3; vul++) - { - ParResults resp; - std::memset(&resp, 0, sizeof(resp)); - ASSERT_EQ(Par(&tab, &resp, vul), RETURN_NO_FAULT) << "vulnerable " << vul; - - std::string const score(resp.par_score[0]); - if (vul == 0) - first = score; - else if (score != first) - differs = true; - } - EXPECT_TRUE(differs) << "par score identical across all vulnerabilities"; + // Guards against the check above being satisfied by a stub: the four legal + // vulnerabilities must not all produce identical output. A sacrifice table + // is used because that is where doubled undertricks make vulnerability + // change the score; a flat partscore table scores the same either way. + DdTableResults tab; + std::memset(&tab, 0, sizeof(tab)); + for (int d = 0; d < DDS_STRAINS; d++) + { + tab.res_table[d][0] = 12; + tab.res_table[d][1] = 1; + tab.res_table[d][2] = 12; + tab.res_table[d][3] = 1; + } + + std::string first; + bool differs = false; + for (int vul = 0; vul <= 3; vul++) + { + ParResults resp; + std::memset(&resp, 0, sizeof(resp)); + ASSERT_EQ(Par(&tab, &resp, vul), RETURN_NO_FAULT) << "vulnerable " << vul; + + std::string const score(resp.par_score[0]); + if (vul == 0) + first = score; + else if (score != first) + differs = true; + } + EXPECT_TRUE(differs) << "par score identical across all vulnerabilities"; } // --------------------------------------------------------------------------- @@ -375,12 +375,12 @@ TEST(ParValidation, VulnerabilityActuallyChangesTheParScore) TEST(ParValidation, ErrorMessageDescribesTableFault) { - char line[80]; - std::memset(line, 0, sizeof(line)); - ErrorMessage(RETURN_PAR_TABLE_FAULT, line); + char line[80]; + std::memset(line, 0, sizeof(line)); + ErrorMessage(RETURN_PAR_TABLE_FAULT, line); - EXPECT_STREQ(line, TEXT_PAR_TABLE_FAULT); - EXPECT_GT(std::strlen(line), 0u); + EXPECT_STREQ(line, TEXT_PAR_TABLE_FAULT); + EXPECT_GT(std::strlen(line), 0u); } } // namespace diff --git a/library/tests/parse.cpp b/library/tests/parse.cpp index 6a7662e63..25b97f7fd 100644 --- a/library/tests/parse.cpp +++ b/library/tests/parse.cpp @@ -29,699 +29,699 @@ using std::out_of_range; bool parse_PBN( - const vector& list, - int& dealer, - int& vul, - DealPBN * dl); + const vector& list, + int& dealer, + int& vul, + DealPBN * dl); bool parse_FUT( - const vector& list, - FutureTricks * fut); + const vector& list, + FutureTricks * fut); bool parse_TABLE( - const vector& list, - DdTableResults * table); + const vector& list, + DdTableResults * table); bool parse_PAR( - const vector& list, - ParResults * par); + const vector& list, + ParResults * par); bool parse_DEALERPAR( - const vector& list, - ParResultsDealer * par); + const vector& list, + ParResultsDealer * par); bool parse_PLAY( - const vector& list, - PlayTracePBN * play); + const vector& list, + PlayTracePBN * play); bool parse_TRACE( - const vector& list, - SolvedPlay * solved); + const vector& list, + SolvedPlay * solved); bool parseable_GIB(const string& line); bool parse_GIB( - const string& line, - DealPBN * dl, - DdTableResults * table); + const string& line, + DealPBN * dl, + DdTableResults * table); bool get_any_line( - ifstream& fin, - vector& list, - const string& tag, - const int n); + ifstream& fin, + vector& list, + const string& tag, + const int n); bool get_head_element( - const string& elem, - const string& expected); + const string& elem, + const string& expected); bool get_int_element( - const string& elem, - int& res, - const string& errtext); + const string& elem, + int& res, + const string& errtext); bool strip_quotes( - const string& st, - char * cstr, - const string& errtag); + const string& st, + char * cstr, + const string& errtag); bool strip_quotes( - const string& st, - int& res, - const string& errtag); + const string& st, + int& res, + const string& errtag); string trimTrailing( - const string& st, - const char c); + const string& st, + const char c); void splitIntoWords( - const string& text, - vector& words); + const string& text, + vector& words); bool str2int( - const string& text, - int& res); + const string& text, + int& res); bool read_file( - const string& fname, - int& number, - bool& GIBmode, - int ** dealer_list, - int ** vul_list, - DealPBN ** deal_list, - FutureTricks ** fut_list, - DdTableResults ** table_list, - ParResults ** par_list, - ParResultsDealer ** dealerpar_list, - PlayTracePBN ** play_list, - SolvedPlay ** trace_list) + const string& fname, + int& number, + bool& GIBmode, + int ** dealer_list, + int ** vul_list, + DealPBN ** deal_list, + FutureTricks ** fut_list, + DdTableResults ** table_list, + ParResults ** par_list, + ParResultsDealer ** dealerpar_list, + PlayTracePBN ** play_list, + SolvedPlay ** trace_list) { - ifstream fin; - fin.open(fname); - - string line; - if (! getline(fin, line)) - { - cout << "First line bad: '" << line << "'" << endl; - return false; - } - - vector list; - list.clear(); - splitIntoWords(line, list); - - if (list.size() == 2 && get_head_element(list[0], "NUMBER")) - { - // Hopefully a txt-style file. - if (! str2int(list[1], number)) + ifstream fin; + fin.open(fname); + + string line; + if (! getline(fin, line)) + { + cout << "First line bad: '" << line << "'" << endl; + return false; + } + + vector list; + list.clear(); + splitIntoWords(line, list); + + if (list.size() == 2 && get_head_element(list[0], "NUMBER")) { - cout << "Not a number of hands: '" << list[1] << "'" << endl; - return false; + // Hopefully a txt-style file. + if (! str2int(list[1], number)) + { + cout << "Not a number of hands: '" << list[1] << "'" << endl; + return false; + } + else if (number <= 0 || number > 100000) + { + cout << "Suspect number of hands: " << number << endl; + return false; + } } - else if (number <= 0 || number > 100000) + else if (! parseable_GIB(line)) { - cout << "Suspect number of hands: " << number << endl; - return false; + cout << "Not a GIB-style start: '" << line << "'" << endl; + return false; } - } - else if (! parseable_GIB(line)) - { - cout << "Not a GIB-style start: '" << line << "'" << endl; - return false; - } - else - { - // Count the lines, then start over. - GIBmode = 1; - number = 1; - while (1) + else { - if (! getline(fin, line)) - break; - number++; + // Count the lines, then start over. + GIBmode = 1; + number = 1; + while (1) + { + if (! getline(fin, line)) + break; + number++; + } + fin.close(); + fin.open(fname); } - fin.close(); - fin.open(fname); - } - // Make enough room for the hands. + // Make enough room for the hands. - const size_t number_t = static_cast(number); + const size_t number_t = static_cast(number); - if ((*dealer_list = static_cast - (calloc(number_t, sizeof(int)))) == NULL) - return false; + if ((*dealer_list = static_cast + (calloc(number_t, sizeof(int)))) == NULL) + return false; - if ((*vul_list = static_cast - (calloc(number_t, sizeof(int)))) == NULL) - return false; + if ((*vul_list = static_cast + (calloc(number_t, sizeof(int)))) == NULL) + return false; - if ((*deal_list = static_cast - (calloc(number_t, sizeof(DealPBN)))) == NULL) - return false; + if ((*deal_list = static_cast + (calloc(number_t, sizeof(DealPBN)))) == NULL) + return false; - if ((*fut_list = static_cast - (calloc(number_t, sizeof(FutureTricks)))) == NULL) - return false; + if ((*fut_list = static_cast + (calloc(number_t, sizeof(FutureTricks)))) == NULL) + return false; - if ((*table_list = static_cast - (calloc(number_t, sizeof(DdTableResults)))) == NULL) - return false; + if ((*table_list = static_cast + (calloc(number_t, sizeof(DdTableResults)))) == NULL) + return false; - if ((*par_list = static_cast - (calloc(number_t, sizeof(ParResults)))) == NULL) - return false; + if ((*par_list = static_cast + (calloc(number_t, sizeof(ParResults)))) == NULL) + return false; - if ((*dealerpar_list = static_cast - (calloc(number_t, sizeof(ParResultsDealer)))) == NULL) - return false; + if ((*dealerpar_list = static_cast + (calloc(number_t, sizeof(ParResultsDealer)))) == NULL) + return false; - if ((*play_list = static_cast - (calloc(number_t, sizeof(PlayTracePBN)))) == NULL) - return false; + if ((*play_list = static_cast + (calloc(number_t, sizeof(PlayTracePBN)))) == NULL) + return false; - if ((*trace_list = static_cast - (calloc(number_t, sizeof(SolvedPlay)))) == NULL) - return false; + if ((*trace_list = static_cast + (calloc(number_t, sizeof(SolvedPlay)))) == NULL) + return false; - if (GIBmode) - { - for (int n = 0; n < number; n++) + if (GIBmode) { - if (! getline(fin, line)) - { - cout << "Expected GIB line " << n << endl; - return false; - } - if (! parse_GIB(line, &(*deal_list)[n], &(*table_list)[n])) - return false; + for (int n = 0; n < number; n++) + { + if (! getline(fin, line)) + { + cout << "Expected GIB line " << n << endl; + return false; + } + if (! parse_GIB(line, &(*deal_list)[n], &(*table_list)[n])) + return false; + } } - } - else - { - for (int n = 0; n < number; n++) + else { - if (! get_any_line(fin, list, "PBN", n)) - return false; - if (! parse_PBN(list, (*dealer_list)[n], - (*vul_list)[n], &(*deal_list)[n])) - return false; + for (int n = 0; n < number; n++) + { + if (! get_any_line(fin, list, "PBN", n)) + return false; + if (! parse_PBN(list, (*dealer_list)[n], + (*vul_list)[n], &(*deal_list)[n])) + return false; + + if (! get_any_line(fin, list, "FUT", n)) + return false; + if (! parse_FUT(list, &(*fut_list)[n])) + return false; + + if (! get_any_line(fin, list, "TABLE", n)) + return false; + if (! parse_TABLE(list, &(*table_list)[n])) + return false; + + if (! get_any_line(fin, list, "PAR", n)) + return false; + if (! parse_PAR(list, &(*par_list)[n])) + return false; + + if (! get_any_line(fin, list, "DEALERPAR", n)) + return false; + if (! parse_DEALERPAR(list, &(*dealerpar_list)[n])) + return false; + + if (! get_any_line(fin, list, "PLAY", n)) + return false; + if (! parse_PLAY(list, &(*play_list)[n])) + return false; + + if (! get_any_line(fin, list, "TRACE", n)) + return false; + if (! parse_TRACE(list, &(*trace_list)[n])) + return false; + } + } - if (! get_any_line(fin, list, "FUT", n)) - return false; - if (! parse_FUT(list, &(*fut_list)[n])) + fin.close(); + return true; +} + + +bool parse_PBN( + const vector& list, + int& dealer, + int& vul, + DealPBN * dl) +{ + if (list.size() != 9) + { + cout << "PBN list does not have 9 elements: " << list.size() << "\n"; return false; + } - if (! get_any_line(fin, list, "TABLE", n)) + if (! get_head_element(list[0], "PBN")) return false; - if (! parse_TABLE(list, &(*table_list)[n])) + if (! get_int_element(list[1], dealer, "PBN dealer failed")) return false; - - if (! get_any_line(fin, list, "PAR", n)) + if (! get_int_element(list[2], vul, "PBN vul failed")) return false; - if (! parse_PAR(list, &(*par_list)[n])) + if (! get_int_element(list[3], dl->trump, "PBN trump failed")) + return false; + if (! get_int_element(list[4], dl->first, "PBN trump failed")) return false; - if (! get_any_line(fin, list, "DEALERPAR", n)) + for (int i = 0; i < 3; i++) + { + dl->currentTrickSuit[i] = 0; + dl->currentTrickRank[i] = 0; + } + + if (! strip_quotes( + list[5] + " " + list[6] + " " + list[7] + " " + list[8], + dl->remainCards, "PBN string")) return false; - if (! parse_DEALERPAR(list, &(*dealerpar_list)[n])) + + return true; +} + + +bool parse_FUT( + const vector& list, + FutureTricks * fut) +{ + if (list.size() < 2) + { + cout << "PBN list does not have 2+ elements: " << list.size() << endl; return false; + } - if (! get_any_line(fin, list, "PLAY", n)) + if (! get_head_element(list[0], "FUT")) return false; - if (! parse_PLAY(list, &(*play_list)[n])) + if (! get_int_element(list[1], fut->cards, "FUT cards")) return false; - if (! get_any_line(fin, list, "TRACE", n)) - return false; - if (! parse_TRACE(list, &(*trace_list)[n])) + if (static_cast(list.size()) != 4 * fut->cards + 2) + { + cout << "PBN list does not have right length: " << list.size() << endl; return false; } - } - fin.close(); - return true; -} + const unsigned nu = static_cast(fut->cards); + for (unsigned c = 0; c < nu; c++) + if (! get_int_element(list[c+2], fut->suit[c], "FUT suit")) + return false; + for (unsigned c = 0; c < nu; c++) + if (! get_int_element(list[c+nu+2], fut->rank[c], "FUT rank")) + return false; -bool parse_PBN( - const vector& list, - int& dealer, - int& vul, - DealPBN * dl) -{ - if (list.size() != 9) - { - cout << "PBN list does not have 9 elements: " << list.size() << "\n"; - return false; - } - - if (! get_head_element(list[0], "PBN")) - return false; - if (! get_int_element(list[1], dealer, "PBN dealer failed")) - return false; - if (! get_int_element(list[2], vul, "PBN vul failed")) - return false; - if (! get_int_element(list[3], dl->trump, "PBN trump failed")) - return false; - if (! get_int_element(list[4], dl->first, "PBN trump failed")) - return false; - - for (int i = 0; i < 3; i++) - { - dl->currentTrickSuit[i] = 0; - dl->currentTrickRank[i] = 0; - } - - if (! strip_quotes( - list[5] + " " + list[6] + " " + list[7] + " " + list[8], - dl->remainCards, "PBN string")) - return false; - - return true; -} + for (unsigned c = 0; c < nu; c++) + if (! get_int_element(list[c+2*nu+2], fut->equals[c], "FUT equals")) + return false; + for (unsigned c = 0; c < nu; c++) + if (! get_int_element(list[c+3*nu+2], fut->score[c], "FUT score")) + return false; -bool parse_FUT( - const vector& list, - FutureTricks * fut) -{ - if (list.size() < 2) - { - cout << "PBN list does not have 2+ elements: " << list.size() << endl; - return false; - } - - if (! get_head_element(list[0], "FUT")) - return false; - if (! get_int_element(list[1], fut->cards, "FUT cards")) - return false; - - if (static_cast(list.size()) != 4 * fut->cards + 2) - { - cout << "PBN list does not have right length: " << list.size() << endl; - return false; - } - - const unsigned nu = static_cast(fut->cards); - for (unsigned c = 0; c < nu; c++) - if (! get_int_element(list[c+2], fut->suit[c], "FUT suit")) - return false; - - for (unsigned c = 0; c < nu; c++) - if (! get_int_element(list[c+nu+2], fut->rank[c], "FUT rank")) - return false; - - for (unsigned c = 0; c < nu; c++) - if (! get_int_element(list[c+2*nu+2], fut->equals[c], "FUT equals")) - return false; - - for (unsigned c = 0; c < nu; c++) - if (! get_int_element(list[c+3*nu+2], fut->score[c], "FUT score")) - return false; - - return true; + return true; } bool parse_TABLE( - const vector& list, - DdTableResults * table) + const vector& list, + DdTableResults * table) { - if (list.size() != 21) - { - cout << "Table list does not have 21 elements: " << list.size() << endl; - return false; - } - - if (! get_head_element(list[0], "TABLE")) - return false; - - for (unsigned suit = 0; suit < DDS_STRAINS; suit++) - { - for (unsigned pl = 0; pl < DDS_HANDS; pl++) + if (list.size() != 21) { - if (! get_int_element(list[DDS_HANDS * suit + pl + 1], - table->res_table[suit][pl], "TABLE entry")) + cout << "Table list does not have 21 elements: " << list.size() << endl; + return false; + } + + if (! get_head_element(list[0], "TABLE")) return false; + + for (unsigned suit = 0; suit < DDS_STRAINS; suit++) + { + for (unsigned pl = 0; pl < DDS_HANDS; pl++) + { + if (! get_int_element(list[DDS_HANDS * suit + pl + 1], + table->res_table[suit][pl], "TABLE entry")) + return false; + } } - } - return true; + return true; } bool parse_PAR( - const vector& list, - ParResults * par) + const vector& list, + ParResults * par) { - // Minimum: PAR + 4 score tokens ("NS" score / "EW" score) + 2 contract - // tokens. Pass-out contracts ("NS:" / "EW:") have no internal spaces, so - // the line has only 7 whitespace tokens; normal contracts with spaces need 9+. - if (list.size() < 7) - { - cout << "PAR list does not have 7+ elements: " << list.size() << endl; - return false; - } - - if (! get_head_element(list[0], "PAR")) - return false; - - if (! strip_quotes(list[1] + " " + list[2], par->par_score[0], - "PAR score 0")) - return false; - - if (! strip_quotes(list[3] + " " + list[4], par->par_score[1], - "PAR score 1")) - return false; - - unsigned i = 5; - string st = ""; - while (i < list.size()) - { - st += " " + list[i++]; - if (st.back() == '"') - break; - } - - if (! strip_quotes(st.substr(1), par->par_contracts_string[0], - "PAR contract 0")) - return false; - - st = ""; - while (i < list.size()) - { - st += " " + list[i++]; - if (st.back() == '"') - break; - } - - if (! strip_quotes(st.substr(1), par->par_contracts_string[1], - "PAR contract 1")) - return false; - - return true; + // Minimum: PAR + 4 score tokens ("NS" score / "EW" score) + 2 contract + // tokens. Pass-out contracts ("NS:" / "EW:") have no internal spaces, so + // the line has only 7 whitespace tokens; normal contracts with spaces need 9+. + if (list.size() < 7) + { + cout << "PAR list does not have 7+ elements: " << list.size() << endl; + return false; + } + + if (! get_head_element(list[0], "PAR")) + return false; + + if (! strip_quotes(list[1] + " " + list[2], par->par_score[0], + "PAR score 0")) + return false; + + if (! strip_quotes(list[3] + " " + list[4], par->par_score[1], + "PAR score 1")) + return false; + + unsigned i = 5; + string st = ""; + while (i < list.size()) + { + st += " " + list[i++]; + if (st.back() == '"') + break; + } + + if (! strip_quotes(st.substr(1), par->par_contracts_string[0], + "PAR contract 0")) + return false; + + st = ""; + while (i < list.size()) + { + st += " " + list[i++]; + if (st.back() == '"') + break; + } + + if (! strip_quotes(st.substr(1), par->par_contracts_string[1], + "PAR contract 1")) + return false; + + return true; } bool parse_DEALERPAR( - const vector& list, - ParResultsDealer * par) + const vector& list, + ParResultsDealer * par) { - const size_t l = list.size(); - if (l < 3) - { - cout << "PAR2 list does not have 3+ elements: " << l << endl; - return false; - } - - if (! get_head_element(list[0], "PAR2")) - return false; - - if (! strip_quotes(list[1], par->score, "PBN string")) - return false; - - unsigned no = 0; - while (no+2 < l) - { - if (! strip_quotes(list[no+2], par->contracts[no], "PAR2 contract")) - break; - no++; - } - - par->number = static_cast(no); - return true; + const size_t l = list.size(); + if (l < 3) + { + cout << "PAR2 list does not have 3+ elements: " << l << endl; + return false; + } + + if (! get_head_element(list[0], "PAR2")) + return false; + + if (! strip_quotes(list[1], par->score, "PBN string")) + return false; + + unsigned no = 0; + while (no+2 < l) + { + if (! strip_quotes(list[no+2], par->contracts[no], "PAR2 contract")) + break; + no++; + } + + par->number = static_cast(no); + return true; } bool parse_PLAY( - const vector& list, - PlayTracePBN * playp) + const vector& list, + PlayTracePBN * playp) { - if (list.size() != 3) - { - cout << "PLAY list does not have 3 elements: " << list.size() << endl; - return false; - } + if (list.size() != 3) + { + cout << "PLAY list does not have 3 elements: " << list.size() << endl; + return false; + } - if (! get_head_element(list[0], "PLAY")) - return false; + if (! get_head_element(list[0], "PLAY")) + return false; - if (! get_int_element(list[1], playp->number, "PLAY number")) - return false; + if (! get_int_element(list[1], playp->number, "PLAY number")) + return false; - if (! strip_quotes(list[2], playp->cards, "PLAY string")) - return false; + if (! strip_quotes(list[2], playp->cards, "PLAY string")) + return false; - return true; + return true; } bool parse_TRACE( - const vector& list, - SolvedPlay * solvedp) + const vector& list, + SolvedPlay * solvedp) { - if (list.size() < 2) - { - cout << "TRACE list does not have 2+ elements: " << list.size() << endl; - return false; - } + if (list.size() < 2) + { + cout << "TRACE list does not have 2+ elements: " << list.size() << endl; + return false; + } - if (! get_head_element(list[0], "TRACE")) - return false; + if (! get_head_element(list[0], "TRACE")) + return false; - if (! get_int_element(list[1], solvedp->number, "TRACE number")) - return false; + if (! get_int_element(list[1], solvedp->number, "TRACE number")) + return false; - for (unsigned i = 0; i < static_cast(solvedp->number); i++) - if (! get_int_element(list[2+i], solvedp->tricks[i], "TRACE element")) - return false; + for (unsigned i = 0; i < static_cast(solvedp->number); i++) + if (! get_int_element(list[2+i], solvedp->tricks[i], "TRACE element")) + return false; - return true; + return true; } bool parseable_GIB(const string& line) { - if (line.size() != 88) - return false; + if (line.size() != 88) + return false; - if (line.substr(67, 1) != ":") - return false; + if (line.substr(67, 1) != ":") + return false; - return true; + return true; } int GIB_TO_DDS[4] = {1, 0, 3, 2}; bool parse_GIB( - const string& line, - DealPBN * dl, - DdTableResults * table) + const string& line, + DealPBN * dl, + DdTableResults * table) { - string st = "W:" + line.substr(0, 67); - strcpy(dl->remainCards, st.c_str()); - - int dds_strain, dds_hand; - for (int s = 0; s < DDS_STRAINS; s++) - { - dds_strain = (s == 0 ? 4 : s - 1); - for (unsigned h = 0; h < DDS_HANDS; h++) - { - dds_hand = GIB_TO_DDS[h]; - char const c = (line.substr( - 68 + 4*static_cast(s) + h, 1).c_str())[0]; - int d; - if (c >= 48 && c <= 57) // 0, 9 - d = c-48; - else if (c >= 65 && c <= 70) // A, F - d = c-55; - else - return false; - - if (dds_hand & 1) - d = 13 - d; + string st = "W:" + line.substr(0, 67); + strcpy(dl->remainCards, st.c_str()); - table->res_table[dds_strain][dds_hand] = d; + int dds_strain, dds_hand; + for (int s = 0; s < DDS_STRAINS; s++) + { + dds_strain = (s == 0 ? 4 : s - 1); + for (unsigned h = 0; h < DDS_HANDS; h++) + { + dds_hand = GIB_TO_DDS[h]; + char const c = (line.substr( + 68 + 4*static_cast(s) + h, 1).c_str())[0]; + int d; + if (c >= 48 && c <= 57) // 0, 9 + d = c-48; + else if (c >= 65 && c <= 70) // A, F + d = c-55; + else + return false; + + if (dds_hand & 1) + d = 13 - d; + + table->res_table[dds_strain][dds_hand] = d; + } } - } - return true; + return true; } bool get_any_line( - ifstream& fin, - vector& list, - const string& tag, - const int n) + ifstream& fin, + vector& list, + const string& tag, + const int n) { - string line; - if (! getline(fin, line)) - { - cout << "Expected txt " << tag << " line " << n << endl; - return false; - } - - list.clear(); - splitIntoWords(line, list); - return true; + string line; + if (! getline(fin, line)) + { + cout << "Expected txt " << tag << " line " << n << endl; + return false; + } + + list.clear(); + splitIntoWords(line, list); + return true; } bool get_head_element( - const string& elem, - const string& expected) + const string& elem, + const string& expected) { - if (elem != expected) - { - cout << "PBN list does not start with " << expected << - ": '" << elem << "'" << endl; - return false; - } - else - return true; + if (elem != expected) + { + cout << "PBN list does not start with " << expected << + ": '" << elem << "'" << endl; + return false; + } + else + return true; } bool get_int_element( - const string& elem, - int& res, - const string& errtext) + const string& elem, + int& res, + const string& errtext) { - if (! str2int(elem, res)) - { - cout << errtext << ": '" << elem << "'\n"; - return false; - } - else - return true; + if (! str2int(elem, res)) + { + cout << errtext << ": '" << elem << "'\n"; + return false; + } + else + return true; } bool strip_quotes( - const string& st, - char * cstr, - const string& errtag) + const string& st, + char * cstr, + const string& errtag) { - // Could just be past the last one. - if (st.size() == 0) - return false; - - if (st.front() != '\"' || st.back() != '\"') - { - cout << errtag << " not in quotations: '" << st << "'\n"; - return false; - } - strcpy(cstr, st.substr(1, st.size()-2).c_str()); - return true; + // Could just be past the last one. + if (st.size() == 0) + return false; + + if (st.front() != '\"' || st.back() != '\"') + { + cout << errtag << " not in quotations: '" << st << "'\n"; + return false; + } + strcpy(cstr, st.substr(1, st.size()-2).c_str()); + return true; } bool strip_quotes( - const string& st, - int& res, - const string& errtag) + const string& st, + int& res, + const string& errtag) { - if (st.front() != '\"' || st.back() != '\"') - { - cout << errtag << " not in quotations: '" << st << "'" << endl; - return false; - } - - if (! str2int(st.substr(1, st.size()-2).c_str(), res)) - { - cout << st << " not an int" << endl; - return false; - } - - return true; + if (st.front() != '\"' || st.back() != '\"') + { + cout << errtag << " not in quotations: '" << st << "'" << endl; + return false; + } + + if (! str2int(st.substr(1, st.size()-2).c_str(), res)) + { + cout << st << " not an int" << endl; + return false; + } + + return true; } string trimTrailing( - const string& text, - const char c) + const string& text, + const char c) { - unsigned Pos = static_cast(text.length()); - while (Pos >= 1 && text.at(Pos-1) == c) - Pos--; - - if (Pos == 0) - return ""; - else - return text.substr(0, Pos); + unsigned Pos = static_cast(text.length()); + while (Pos >= 1 && text.at(Pos-1) == c) + Pos--; + + if (Pos == 0) + return ""; + else + return text.substr(0, Pos); } void splitIntoWords( - const string& text, - vector& words) + const string& text, + vector& words) { - // Split into words (split on \s+, effectively). - unsigned Pos = 0; - unsigned startPos = 0; - bool isSpace = true; - - // It seems compilers have different ideas about files. - const size_t tl = text.length(); - string ttext; - if (text.back() == ' ') - ttext = text.substr(0, tl-1); - else if (text.at(tl-2) == ' ') - ttext = text.substr(0, tl-2); - else - ttext = text; - - const unsigned l = static_cast(ttext.length()); - - while (Pos < l) - { - if (ttext.at(Pos) == ' ') + // Split into words (split on \s+, effectively). + unsigned Pos = 0; + unsigned startPos = 0; + bool isSpace = true; + + // It seems compilers have different ideas about files. + const size_t tl = text.length(); + string ttext; + if (text.back() == ' ') + ttext = text.substr(0, tl-1); + else if (text.at(tl-2) == ' ') + ttext = text.substr(0, tl-2); + else + ttext = text; + + const unsigned l = static_cast(ttext.length()); + + while (Pos < l) { - if (! isSpace) - { - words.push_back(ttext.substr(startPos, Pos-startPos)); - isSpace = true; - } - } - else if (isSpace) - { - isSpace = false; - startPos = Pos; + if (ttext.at(Pos) == ' ') + { + if (! isSpace) + { + words.push_back(ttext.substr(startPos, Pos-startPos)); + isSpace = true; + } + } + else if (isSpace) + { + isSpace = false; + startPos = Pos; + } + Pos++; } - Pos++; - } - if (! isSpace) - words.push_back(ttext.substr(startPos, Pos-startPos)); + if (! isSpace) + words.push_back(ttext.substr(startPos, Pos-startPos)); } bool str2int( - const string& text, - int& res) + const string& text, + int& res) { - int i; - size_t Pos; - try - { - i = stoi(text, &Pos); - if (Pos != text.size()) - return false; - - } - catch ([[maybe_unused]] const invalid_argument& ia) - { - return false; - } - catch ([[maybe_unused]] const out_of_range& ia) - { - return false; - } - - res = i; - return true; + int i; + size_t Pos; + try + { + i = stoi(text, &Pos); + if (Pos != text.size()) + return false; + + } + catch ([[maybe_unused]] const invalid_argument& ia) + { + return false; + } + catch ([[maybe_unused]] const out_of_range& ia) + { + return false; + } + + res = i; + return true; } diff --git a/library/tests/parse_par_test.cpp b/library/tests/parse_par_test.cpp index bf147932e..a16614810 100644 --- a/library/tests/parse_par_test.cpp +++ b/library/tests/parse_par_test.cpp @@ -17,127 +17,127 @@ namespace auto write_temp_hand_list(const std::string& body) -> std::string { - const std::string path = "parse_par_test_hand.txt"; - std::ofstream out(path); - out << "NUMBER 1 \n" << body; - out.close(); - return path; + const std::string path = "parse_par_test_hand.txt"; + std::ofstream out(path); + out << "NUMBER 1 \n" << body; + out.close(); + return path; } auto cleanup(const std::string& path) -> void { - std::remove(path.c_str()); + std::remove(path.c_str()); } } // namespace TEST(ParsePar, AcceptsPassOutContractsWithoutInternalSpaces) { - // DDS pass-out contracts are "NS:" / "EW:" (no space). Whitespace-split that - // yields only 7 tokens; dtest must still accept the line. - const std::string path = write_temp_hand_list( - "PBN 0 2 4 0 \"N:AJ93.952.Q943.96 T74.84.AKJ6.QT72 K62.KJ6.872.AKJ3 " - "Q85.AQT73.T5.854\" \n" - "FUT 0 \n" - "TABLE 6 5 6 5 5 6 5 6 6 6 6 6 5 6 6 6 5 6 5 6 \n" - "PAR \"NS 0\" \"EW 0\" \"NS:\" \"EW:\" \n" - "PAR2 \"0\" \"pass\" \n" - "PLAY 0 \"\" \n" - "TRACE 1 0 \n"); - - int number = 0; - bool gib_mode = false; - int* dealer_list = nullptr; - int* vul_list = nullptr; - DealPBN* deal_list = nullptr; - FutureTricks* fut_list = nullptr; - DdTableResults* table_list = nullptr; - ParResults* par_list = nullptr; - ParResultsDealer* dealerpar_list = nullptr; - PlayTracePBN* play_list = nullptr; - SolvedPlay* trace_list = nullptr; - - ASSERT_TRUE(read_file( - path, - number, - gib_mode, - &dealer_list, - &vul_list, - &deal_list, - &fut_list, - &table_list, - &par_list, - &dealerpar_list, - &play_list, - &trace_list)); - ASSERT_EQ(number, 1); - ASSERT_NE(par_list, nullptr); - EXPECT_STREQ(par_list[0].par_score[0], "NS 0"); - EXPECT_STREQ(par_list[0].par_score[1], "EW 0"); - EXPECT_STREQ(par_list[0].par_contracts_string[0], "NS:"); - EXPECT_STREQ(par_list[0].par_contracts_string[1], "EW:"); - - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - cleanup(path); + // DDS pass-out contracts are "NS:" / "EW:" (no space). Whitespace-split that + // yields only 7 tokens; dtest must still accept the line. + const std::string path = write_temp_hand_list( + "PBN 0 2 4 0 \"N:AJ93.952.Q943.96 T74.84.AKJ6.QT72 K62.KJ6.872.AKJ3 " + "Q85.AQT73.T5.854\" \n" + "FUT 0 \n" + "TABLE 6 5 6 5 5 6 5 6 6 6 6 6 5 6 6 6 5 6 5 6 \n" + "PAR \"NS 0\" \"EW 0\" \"NS:\" \"EW:\" \n" + "PAR2 \"0\" \"pass\" \n" + "PLAY 0 \"\" \n" + "TRACE 1 0 \n"); + + int number = 0; + bool gib_mode = false; + int* dealer_list = nullptr; + int* vul_list = nullptr; + DealPBN* deal_list = nullptr; + FutureTricks* fut_list = nullptr; + DdTableResults* table_list = nullptr; + ParResults* par_list = nullptr; + ParResultsDealer* dealerpar_list = nullptr; + PlayTracePBN* play_list = nullptr; + SolvedPlay* trace_list = nullptr; + + ASSERT_TRUE(read_file( + path, + number, + gib_mode, + &dealer_list, + &vul_list, + &deal_list, + &fut_list, + &table_list, + &par_list, + &dealerpar_list, + &play_list, + &trace_list)); + ASSERT_EQ(number, 1); + ASSERT_NE(par_list, nullptr); + EXPECT_STREQ(par_list[0].par_score[0], "NS 0"); + EXPECT_STREQ(par_list[0].par_score[1], "EW 0"); + EXPECT_STREQ(par_list[0].par_contracts_string[0], "NS:"); + EXPECT_STREQ(par_list[0].par_contracts_string[1], "EW:"); + + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + cleanup(path); } TEST(ParsePar, StillAcceptsNormalContractsWithSpaces) { - const std::string path = write_temp_hand_list( - "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " - "AT942.AQ4.32.KJ3\" \n" - "FUT 0 \n" - "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" - "PAR \"NS -110\" \"EW 110\" \"NS:EW 2S\" \"EW:EW 2S\" \n" - "PAR2 \"-110\" \"2S-EW\" \n" - "PLAY 0 \"\" \n" - "TRACE 1 0 \n"); - - int number = 0; - bool gib_mode = false; - int* dealer_list = nullptr; - int* vul_list = nullptr; - DealPBN* deal_list = nullptr; - FutureTricks* fut_list = nullptr; - DdTableResults* table_list = nullptr; - ParResults* par_list = nullptr; - ParResultsDealer* dealerpar_list = nullptr; - PlayTracePBN* play_list = nullptr; - SolvedPlay* trace_list = nullptr; - - ASSERT_TRUE(read_file( - path, - number, - gib_mode, - &dealer_list, - &vul_list, - &deal_list, - &fut_list, - &table_list, - &par_list, - &dealerpar_list, - &play_list, - &trace_list)); - ASSERT_EQ(number, 1); - EXPECT_STREQ(par_list[0].par_contracts_string[0], "NS:EW 2S"); - EXPECT_STREQ(par_list[0].par_contracts_string[1], "EW:EW 2S"); - - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - cleanup(path); + const std::string path = write_temp_hand_list( + "PBN 0 0 0 0 \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + "AT942.AQ4.32.KJ3\" \n" + "FUT 0 \n" + "TABLE 5 8 5 8 6 6 6 6 5 7 5 7 7 5 7 5 6 6 6 6 \n" + "PAR \"NS -110\" \"EW 110\" \"NS:EW 2S\" \"EW:EW 2S\" \n" + "PAR2 \"-110\" \"2S-EW\" \n" + "PLAY 0 \"\" \n" + "TRACE 1 0 \n"); + + int number = 0; + bool gib_mode = false; + int* dealer_list = nullptr; + int* vul_list = nullptr; + DealPBN* deal_list = nullptr; + FutureTricks* fut_list = nullptr; + DdTableResults* table_list = nullptr; + ParResults* par_list = nullptr; + ParResultsDealer* dealerpar_list = nullptr; + PlayTracePBN* play_list = nullptr; + SolvedPlay* trace_list = nullptr; + + ASSERT_TRUE(read_file( + path, + number, + gib_mode, + &dealer_list, + &vul_list, + &deal_list, + &fut_list, + &table_list, + &par_list, + &dealerpar_list, + &play_list, + &trace_list)); + ASSERT_EQ(number, 1); + EXPECT_STREQ(par_list[0].par_contracts_string[0], "NS:EW 2S"); + EXPECT_STREQ(par_list[0].par_contracts_string[1], "EW:EW 2S"); + + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + cleanup(path); } diff --git a/library/tests/pbn_test.cpp b/library/tests/pbn_test.cpp index afe843701..fc75f16c6 100644 --- a/library/tests/pbn_test.cpp +++ b/library/tests/pbn_test.cpp @@ -12,139 +12,139 @@ namespace { constexpr char kNorthFirst[] = - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; constexpr char kEastFirst[] = - "E:QJT5432.T.6.QJ82 .J97543.K7532.94 87.A62.QJT4.AT75 AK96.KQ8.A98.K63"; + "E:QJT5432.T.6.QJ82 .J97543.K7532.94 87.A62.QJT4.AT75 AK96.KQ8.A98.K63"; auto convert(const char* pbn) -> int { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - return convert_from_pbn(pbn, remain); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + return convert_from_pbn(pbn, remain); } } // namespace TEST(ConvertFromPbn, AcceptsNorthFirstWithoutLaterDirections) { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - EXPECT_EQ(convert_from_pbn(kNorthFirst, remain), RETURN_NO_FAULT); - EXPECT_NE(remain[0][0], 0u); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + EXPECT_EQ(convert_from_pbn(kNorthFirst, remain), RETURN_NO_FAULT); + EXPECT_NE(remain[0][0], 0u); } TEST(ConvertFromPbn, AcceptsEastFirstWithoutLaterDirections) { - EXPECT_EQ(convert(kEastFirst), RETURN_NO_FAULT); + EXPECT_EQ(convert(kEastFirst), RETURN_NO_FAULT); } TEST(ConvertFromPbn, AcceptsLowercaseFirstHandDirection) { - EXPECT_EQ( - convert( - "n:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"), - RETURN_NO_FAULT); + EXPECT_EQ( + convert( + "n:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"), + RETURN_NO_FAULT); } TEST(ConvertFromPbn, RejectsClockwiseSeatLettersOnLaterHands) { - EXPECT_EQ( - convert( - "N:QJ6.K652.J85.T98 E:873.J97.AT764.Q4 S:K5.T83.KQ9.A7652 " - "W:AT942.AQ4.32.KJ3"), - 0); + EXPECT_EQ( + convert( + "N:QJ6.K652.J85.T98 E:873.J97.AT764.Q4 S:K5.T83.KQ9.A7652 " + "W:AT942.AQ4.32.KJ3"), + 0); } TEST(ConvertFromPbn, RejectsASingleExtraSeatLetter) { - EXPECT_EQ( - convert( - "N:QJ6.K652.J85.T98 W:873.J97.AT764.Q4 K5.T83.KQ9.A7652 " - "AT942.AQ4.32.KJ3"), - 0); + EXPECT_EQ( + convert( + "N:QJ6.K652.J85.T98 W:873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + "AT942.AQ4.32.KJ3"), + 0); } TEST(ConvertFromPbn, RejectsLowercaseExtraSeatLetter) { - EXPECT_EQ( - convert( - "N:QJ6.K652.J85.T98 e:873.J97.AT764.Q4 K5.T83.KQ9.A7652 " - "AT942.AQ4.32.KJ3"), - 0); + EXPECT_EQ( + convert( + "N:QJ6.K652.J85.T98 e:873.J97.AT764.Q4 K5.T83.KQ9.A7652 " + "AT942.AQ4.32.KJ3"), + 0); } TEST(ConvertFromPbn, RejectsNullPointerDealBuffer) { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - EXPECT_EQ(convert_from_pbn(nullptr, remain), 0); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + EXPECT_EQ(convert_from_pbn(nullptr, remain), 0); } TEST(ConvertFromPbn, ClearsOutputOnNullDealBuffer) { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - remain[0][0] = 0xFFFF; - EXPECT_EQ(convert_from_pbn(nullptr, remain), 0); - EXPECT_EQ(remain[0][0], 0u); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + remain[0][0] = 0xFFFF; + EXPECT_EQ(convert_from_pbn(nullptr, remain), 0); + EXPECT_EQ(remain[0][0], 0u); } TEST(ConvertFromPbn, ClearsOutputOnInvalidDeal) { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - remain[0][0] = 0xFFFF; - EXPECT_EQ(convert_from_pbn("xx", remain), 0); - EXPECT_EQ(remain[0][0], 0u); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + remain[0][0] = 0xFFFF; + EXPECT_EQ(convert_from_pbn("xx", remain), 0); + EXPECT_EQ(remain[0][0], 0u); } TEST(ConvertFromPbn, RejectsNullOutputBuffer) { - EXPECT_EQ(convert_from_pbn(kNorthFirst, nullptr), 0); + EXPECT_EQ(convert_from_pbn(kNorthFirst, nullptr), 0); } TEST(ConvertFromPbn, RejectsEmptyAndMissingSeatPrefixInputs) { - EXPECT_EQ(convert(""), 0); - EXPECT_EQ(convert("N"), 0); - EXPECT_EQ(convert("xx"), 0); + EXPECT_EQ(convert(""), 0); + EXPECT_EQ(convert("N"), 0); + EXPECT_EQ(convert("xx"), 0); } TEST(ConvertFromPbn, RejectsTooManySuitsInHand) { - EXPECT_EQ(convert("N:AK.K.K.K.A"), 0); + EXPECT_EQ(convert("N:AK.K.K.K.A"), 0); } TEST(ConvertFromPbn, RejectsTooManyHands) { - unsigned int remain[DDS_HANDS][DDS_SUITS]{}; - EXPECT_EQ(convert_from_pbn("N:AK.K.K.K A", remain), 0); + unsigned int remain[DDS_HANDS][DDS_SUITS]{}; + EXPECT_EQ(convert_from_pbn("N:AK.K.K.K A", remain), 0); } TEST(ConvertFromPbn, RejectsTruncatedDealWithFewerThanFourHands) { - EXPECT_EQ(convert("N:AK.QJ.T9.876"), 0); // 1 hand - EXPECT_EQ(convert("N:AK.QJ.T9.876 AK.QJ.T9.876"), 0); // 2 hands - EXPECT_EQ(convert("N:AK.QJ.T9.876 AK.QJ.T9.876 AK.QJ.T9.876"), 0); // 3 hands + EXPECT_EQ(convert("N:AK.QJ.T9.876"), 0); // 1 hand + EXPECT_EQ(convert("N:AK.QJ.T9.876 AK.QJ.T9.876"), 0); // 2 hands + EXPECT_EQ(convert("N:AK.QJ.T9.876 AK.QJ.T9.876 AK.QJ.T9.876"), 0); // 3 hands } TEST(ConvertFromPbn, RejectsInputLongerThanRemainCardsBuffer) { - constexpr auto kBufSize = sizeof(DealPBN::remainCards); - std::string pbn = "N:"; - pbn.append(kBufSize, 'A'); - ASSERT_GT(pbn.size(), kBufSize); - EXPECT_EQ(convert(pbn.c_str()), 0); + constexpr auto kBufSize = sizeof(DealPBN::remainCards); + std::string pbn = "N:"; + pbn.append(kBufSize, 'A'); + ASSERT_GT(pbn.size(), kBufSize); + EXPECT_EQ(convert(pbn.c_str()), 0); } TEST(ConvertFromPbn, RejectsInputExactlyAtRemainCardsBufferLimit) { - constexpr auto kBufSize = sizeof(DealPBN::remainCards); - std::string pbn = "N:"; - pbn.append(kBufSize - 2, 'A'); - ASSERT_EQ(pbn.size(), kBufSize); - EXPECT_EQ(convert(pbn.c_str()), 0); + constexpr auto kBufSize = sizeof(DealPBN::remainCards); + std::string pbn = "N:"; + pbn.append(kBufSize - 2, 'A'); + ASSERT_EQ(pbn.size(), kBufSize); + EXPECT_EQ(convert(pbn.c_str()), 0); } TEST(ConvertFromPbn, AcceptsInputThatFitsRemainCardsBuffer) { - constexpr auto kBufSize = sizeof(DealPBN::remainCards); - ASSERT_LT(std::char_traits::length(kNorthFirst), kBufSize); - EXPECT_EQ(convert(kNorthFirst), RETURN_NO_FAULT); + constexpr auto kBufSize = sizeof(DealPBN::remainCards); + ASSERT_LT(std::char_traits::length(kNorthFirst), kBufSize); + EXPECT_EQ(convert(kNorthFirst), RETURN_NO_FAULT); } diff --git a/library/tests/print.cpp b/library/tests/print.cpp index 68d146a82..7cd7f04a2 100644 --- a/library/tests/print.cpp +++ b/library/tests/print.cpp @@ -33,136 +33,136 @@ string equals_to_string(const int equals); void set_constants() { - dbit_map_rank[15] = 0x2000; - dbit_map_rank[14] = 0x1000; - dbit_map_rank[13] = 0x0800; - dbit_map_rank[12] = 0x0400; - dbit_map_rank[11] = 0x0200; - dbit_map_rank[10] = 0x0100; - dbit_map_rank[ 9] = 0x0080; - dbit_map_rank[ 8] = 0x0040; - dbit_map_rank[ 7] = 0x0020; - dbit_map_rank[ 6] = 0x0010; - dbit_map_rank[ 5] = 0x0008; - dbit_map_rank[ 4] = 0x0004; - dbit_map_rank[ 3] = 0x0002; - dbit_map_rank[ 2] = 0x0001; - dbit_map_rank[ 1] = 0; - dbit_map_rank[ 0] = 0; - - dcard_rank[ 2] = '2'; - dcard_rank[ 3] = '3'; - dcard_rank[ 4] = '4'; - dcard_rank[ 5] = '5'; - dcard_rank[ 6] = '6'; - dcard_rank[ 7] = '7'; - dcard_rank[ 8] = '8'; - dcard_rank[ 9] = '9'; - dcard_rank[10] = 'T'; - dcard_rank[11] = 'J'; - dcard_rank[12] = 'Q'; - dcard_rank[13] = 'K'; - dcard_rank[14] = 'A'; - dcard_rank[15] = '-'; - - dcard_suit[0] = 'S'; - dcard_suit[1] = 'H'; - dcard_suit[2] = 'D'; - dcard_suit[3] = 'C'; - dcard_suit[4] = 'N'; + dbit_map_rank[15] = 0x2000; + dbit_map_rank[14] = 0x1000; + dbit_map_rank[13] = 0x0800; + dbit_map_rank[12] = 0x0400; + dbit_map_rank[11] = 0x0200; + dbit_map_rank[10] = 0x0100; + dbit_map_rank[ 9] = 0x0080; + dbit_map_rank[ 8] = 0x0040; + dbit_map_rank[ 7] = 0x0020; + dbit_map_rank[ 6] = 0x0010; + dbit_map_rank[ 5] = 0x0008; + dbit_map_rank[ 4] = 0x0004; + dbit_map_rank[ 3] = 0x0002; + dbit_map_rank[ 2] = 0x0001; + dbit_map_rank[ 1] = 0; + dbit_map_rank[ 0] = 0; + + dcard_rank[ 2] = '2'; + dcard_rank[ 3] = '3'; + dcard_rank[ 4] = '4'; + dcard_rank[ 5] = '5'; + dcard_rank[ 6] = '6'; + dcard_rank[ 7] = '7'; + dcard_rank[ 8] = '8'; + dcard_rank[ 9] = '9'; + dcard_rank[10] = 'T'; + dcard_rank[11] = 'J'; + dcard_rank[12] = 'Q'; + dcard_rank[13] = 'K'; + dcard_rank[14] = 'A'; + dcard_rank[15] = '-'; + + dcard_suit[0] = 'S'; + dcard_suit[1] = 'H'; + dcard_suit[2] = 'D'; + dcard_suit[3] = 'C'; + dcard_suit[4] = 'N'; } void print_PBN(const DealPBN& dl) { - cout << setw(10) << left << "trump" << dl.trump << "\n"; - cout << setw(10) << "first" << dl.first << "\n"; - cout << setw(10) << "cards" << dl.remainCards << "\n"; + cout << setw(10) << left << "trump" << dl.trump << "\n"; + cout << setw(10) << "first" << dl.first << "\n"; + cout << setw(10) << "cards" << dl.remainCards << "\n"; } void print_FUT(const FutureTricks& fut) { - cout << setw(6) << left << "cards" << fut.cards << "\n"; - cout << setw(6) << right << "No." << - setw(7) << "suit" << - setw(7) << "rank" << - setw(7) << "equals" << - setw(7) << "score" << "\n"; - - for (int i = 0; i < fut.cards; i++) - { - cout << setw(6) << right << i << - setw(7) << dcard_suit[ fut.suit[i] ] << - setw(7) << dcard_rank[ fut.rank[i] ] << - setw(7) << equals_to_string(fut.equals[i]) << - setw(7) << fut.score[i] << "\n"; - } + cout << setw(6) << left << "cards" << fut.cards << "\n"; + cout << setw(6) << right << "No." << + setw(7) << "suit" << + setw(7) << "rank" << + setw(7) << "equals" << + setw(7) << "score" << "\n"; + + for (int i = 0; i < fut.cards; i++) + { + cout << setw(6) << right << i << + setw(7) << dcard_suit[ fut.suit[i] ] << + setw(7) << dcard_rank[ fut.rank[i] ] << + setw(7) << equals_to_string(fut.equals[i]) << + setw(7) << fut.score[i] << "\n"; + } } string equals_to_string(const int equals) { - string st = ""; - for (unsigned i = 15; i >= 2; i--) - { - if ((equals >> 2) & dbit_map_rank[i]) - st += static_cast(dcard_rank[i]); - } - return (st == "" ? "-" : st); + string st = ""; + for (unsigned i = 15; i >= 2; i--) + { + if ((equals >> 2) & dbit_map_rank[i]) + st += static_cast(dcard_rank[i]); + } + return (st == "" ? "-" : st); } void print_TABLE(const DdTableResults& table) { - cout << setw(5) << right << "" << - setw(6) << "North" << - setw(6) << "South" << - setw(6) << "East" << - setw(6) << "West" << "\n"; - - cout << setw(5) << right << "NT" << - setw(6) << table.res_table[4][0] << - setw(6) << table.res_table[4][2] << - setw(6) << table.res_table[4][1] << - setw(6) << table.res_table[4][3] << "\n"; - - for (int suit = 0; suit <= 3; suit++) - { - cout << setw(5) << right << dcard_suit[suit] << - setw(6) << table.res_table[suit][0] << - setw(6) << table.res_table[suit][2] << - setw(6) << table.res_table[suit][1] << - setw(6) << table.res_table[suit][3] << "\n"; - } + cout << setw(5) << right << "" << + setw(6) << "North" << + setw(6) << "South" << + setw(6) << "East" << + setw(6) << "West" << "\n"; + + cout << setw(5) << right << "NT" << + setw(6) << table.res_table[4][0] << + setw(6) << table.res_table[4][2] << + setw(6) << table.res_table[4][1] << + setw(6) << table.res_table[4][3] << "\n"; + + for (int suit = 0; suit <= 3; suit++) + { + cout << setw(5) << right << dcard_suit[suit] << + setw(6) << table.res_table[suit][0] << + setw(6) << table.res_table[suit][2] << + setw(6) << table.res_table[suit][1] << + setw(6) << table.res_table[suit][3] << "\n"; + } } void print_PAR(const ParResults& par) { - cout << setw(9) << left << "NS score" << par.par_score[0] << "\n"; - cout << setw(9) << "EW score" << par.par_score[1] << "\n"; - cout << setw(9) << "NS list" << par.par_contracts_string[0] << "\n"; - cout << setw(9) << "EW list" << par.par_contracts_string[1] << "\n"; + cout << setw(9) << left << "NS score" << par.par_score[0] << "\n"; + cout << setw(9) << "EW score" << par.par_score[1] << "\n"; + cout << setw(9) << "NS list" << par.par_contracts_string[0] << "\n"; + cout << setw(9) << "EW list" << par.par_contracts_string[1] << "\n"; } void print_DEALERPAR(const ParResultsDealer& par) { - cout << setw(6) << left << "Score" << par.score << "\n"; - cout << setw(6) << left << "Pars" << par.number << "\n"; + cout << setw(6) << left << "Score" << par.score << "\n"; + cout << setw(6) << left << "Pars" << par.number << "\n"; - for (int i = 0; i < par.number; i++) - cout << left << "Par " << setw(2) << i << par.contracts[i] << "\n"; + for (int i = 0; i < par.number; i++) + cout << left << "Par " << setw(2) << i << par.contracts[i] << "\n"; } void print_PLAY(const PlayTracePBN& play) { - cout << setw(6) << right << "Number" << - setw(5) << play.number << "\n"; + cout << setw(6) << right << "Number" << + setw(5) << play.number << "\n"; - for (int i = 0; i < play.number; i++) + for (int i = 0; i < play.number; i++) cout << setw(6) << i << " " << play.cards[2*i] << play.cards[2*i+1] << "\n"; } @@ -170,40 +170,40 @@ void print_PLAY(const PlayTracePBN& play) void print_TRACE(const SolvedPlay& solved) { - cout << setw(6) << right << "Number" << - setw(5) << solved.number << "\n"; + cout << setw(6) << right << "Number" << + setw(5) << solved.number << "\n"; - for (int i = 0; i < solved.number; i++) + for (int i = 0; i < solved.number; i++) cout << setw(6) << i << setw(5) << solved.tricks[i] << "\n"; } void print_double_TRACE( - const SolvedPlay& solved, - const SolvedPlay& ref) + const SolvedPlay& solved, + const SolvedPlay& ref) { - cout << "Number solved vs ref: " << solved.number << " vs. " << - ref.number << "\n"; - - const int m = min(solved.number, ref.number); - for (int i = 0; i < m; i++) - { - cout << "Trick " << i << ": " << - solved.tricks[i] << " vs " << - ref.tricks[i] << - (solved.tricks[i] == ref.tricks[i] ? "" : " ERROR") << "\n"; - } - - if (solved.number > m) - { - for (int i = m; i < solved.number; i++) - cout << "Solved " << i << ": " << solved.tricks[i] << "\n"; - } - else if (ref.number > m) - { - for (int i = m; i < ref.number; i++) - cout << "Ref " << i << ": " << ref.tricks[i] << "\n"; - } + cout << "Number solved vs ref: " << solved.number << " vs. " << + ref.number << "\n"; + + const int m = min(solved.number, ref.number); + for (int i = 0; i < m; i++) + { + cout << "Trick " << i << ": " << + solved.tricks[i] << " vs " << + ref.tricks[i] << + (solved.tricks[i] == ref.tricks[i] ? "" : " ERROR") << "\n"; + } + + if (solved.number > m) + { + for (int i = m; i < solved.number; i++) + cout << "Solved " << i << ": " << solved.tricks[i] << "\n"; + } + else if (ref.number > m) + { + for (int i = m; i < ref.number; i++) + cout << "Ref " << i << ": " << ref.tricks[i] << "\n"; + } } diff --git a/library/tests/quick_tricks/next_suit_test.cpp b/library/tests/quick_tricks/next_suit_test.cpp index 7eea61fd4..df6ea1108 100644 --- a/library/tests/quick_tricks/next_suit_test.cpp +++ b/library/tests/quick_tricks/next_suit_test.cpp @@ -14,73 +14,73 @@ namespace { /// Reference copy of the duplicated QuickTricks advance logic. auto reference_next_suit(int suit, int trump) -> int { - if ((trump != DDS_NOTRUMP) && (suit == trump)) - { - if (trump == 0) - return 1; - return 0; - } - suit++; - if ((trump != DDS_NOTRUMP) && (suit == trump)) + if ((trump != DDS_NOTRUMP) && (suit == trump)) + { + if (trump == 0) + return 1; + return 0; + } suit++; - return suit; + if ((trump != DDS_NOTRUMP) && (suit == trump)) + suit++; + return suit; } } // namespace TEST(NextQuickTrickSuit, NoTrumpAdvancesSequentially) { - EXPECT_EQ(next_quick_trick_suit(0, DDS_NOTRUMP), 1); - EXPECT_EQ(next_quick_trick_suit(1, DDS_NOTRUMP), 2); - EXPECT_EQ(next_quick_trick_suit(2, DDS_NOTRUMP), 3); - EXPECT_EQ(next_quick_trick_suit(3, DDS_NOTRUMP), 4); + EXPECT_EQ(next_quick_trick_suit(0, DDS_NOTRUMP), 1); + EXPECT_EQ(next_quick_trick_suit(1, DDS_NOTRUMP), 2); + EXPECT_EQ(next_quick_trick_suit(2, DDS_NOTRUMP), 3); + EXPECT_EQ(next_quick_trick_suit(3, DDS_NOTRUMP), 4); } TEST(NextQuickTrickSuit, AfterTrumpJumpsToSuitZeroOrOne) { - // Spades trump: after visiting trump (0), continue at hearts (1). - EXPECT_EQ(next_quick_trick_suit(0, 0), 1); + // Spades trump: after visiting trump (0), continue at hearts (1). + EXPECT_EQ(next_quick_trick_suit(0, 0), 1); - // Hearts trump: after visiting trump (1), restart at spades (0). - EXPECT_EQ(next_quick_trick_suit(1, 1), 0); + // Hearts trump: after visiting trump (1), restart at spades (0). + EXPECT_EQ(next_quick_trick_suit(1, 1), 0); - // Diamonds / clubs trump likewise restart at spades. - EXPECT_EQ(next_quick_trick_suit(2, 2), 0); - EXPECT_EQ(next_quick_trick_suit(3, 3), 0); + // Diamonds / clubs trump likewise restart at spades. + EXPECT_EQ(next_quick_trick_suit(2, 2), 0); + EXPECT_EQ(next_quick_trick_suit(3, 3), 0); } TEST(NextQuickTrickSuit, SkipsTrumpWhenAdvancingPastIt) { - // Hearts trump: 0 -> 2 (skip 1), 2 -> 3, 3 -> 4. - EXPECT_EQ(next_quick_trick_suit(0, 1), 2); - EXPECT_EQ(next_quick_trick_suit(2, 1), 3); - EXPECT_EQ(next_quick_trick_suit(3, 1), 4); + // Hearts trump: 0 -> 2 (skip 1), 2 -> 3, 3 -> 4. + EXPECT_EQ(next_quick_trick_suit(0, 1), 2); + EXPECT_EQ(next_quick_trick_suit(2, 1), 3); + EXPECT_EQ(next_quick_trick_suit(3, 1), 4); - // Diamonds trump: 0 -> 1, 1 -> 3 (skip 2), 3 -> 4. - EXPECT_EQ(next_quick_trick_suit(0, 2), 1); - EXPECT_EQ(next_quick_trick_suit(1, 2), 3); - EXPECT_EQ(next_quick_trick_suit(3, 2), 4); + // Diamonds trump: 0 -> 1, 1 -> 3 (skip 2), 3 -> 4. + EXPECT_EQ(next_quick_trick_suit(0, 2), 1); + EXPECT_EQ(next_quick_trick_suit(1, 2), 3); + EXPECT_EQ(next_quick_trick_suit(3, 2), 4); } TEST(NextQuickTrickSuit, MatchesReferenceForAllSuitTrumpPairs) { - for (int trump = 0; trump <= DDS_NOTRUMP; ++trump) - { - for (int suit = 0; suit < DDS_SUITS; ++suit) + for (int trump = 0; trump <= DDS_NOTRUMP; ++trump) { - EXPECT_EQ(next_quick_trick_suit(suit, trump), - reference_next_suit(suit, trump)) - << "suit=" << suit << " trump=" << trump; + for (int suit = 0; suit < DDS_SUITS; ++suit) + { + EXPECT_EQ(next_quick_trick_suit(suit, trump), + reference_next_suit(suit, trump)) + << "suit=" << suit << " trump=" << trump; + } } - } } TEST(NextQuickTrickSuit, FullTrumpFirstIterationOrder) { - // Hearts trump iteration: 1, then 0, 2, 3. - int suit = 1; - EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 0); - EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 2); - EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 3); - EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 4); + // Hearts trump iteration: 1, then 0, 2, 3. + int suit = 1; + EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 0); + EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 2); + EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 3); + EXPECT_EQ((suit = next_quick_trick_suit(suit, 1)), 4); } diff --git a/library/tests/report_board_timings.cpp b/library/tests/report_board_timings.cpp index be93b705c..9651ca4a1 100644 --- a/library/tests/report_board_timings.cpp +++ b/library/tests/report_board_timings.cpp @@ -19,172 +19,172 @@ namespace int decimal_digits(int value) { - const int n = std::abs(value); - int digits = 1; - int x = n; - while (x >= 10) - { - x /= 10; - ++digits; - } - if (value < 0) - ++digits; - return digits; + const int n = std::abs(value); + int digits = 1; + int x = n; + while (x >= 10) + { + x /= 10; + ++digits; + } + if (value < 0) + ++digits; + return digits; } int board_column_width(const std::vector>& times) { - constexpr int kHeaderWidth = 5; // "board" - int width = kHeaderWidth; - for (const auto& p : times) - width = std::max(width, decimal_digits(p.first)); - return width; + constexpr int kHeaderWidth = 5; // "board" + int width = kHeaderWidth; + for (const auto& p : times) + width = std::max(width, decimal_digits(p.first)); + return width; } int ms_column_width(const std::vector>& times) { - constexpr int kHeaderWidth = 2; // "ms" - int width = kHeaderWidth; - for (const auto& p : times) - { - std::ostringstream formatted; - formatted << std::fixed << std::setprecision(2) - << (static_cast(p.second) / 1000.0); - width = std::max(width, static_cast(formatted.str().size())); - } - return width; + constexpr int kHeaderWidth = 2; // "ms" + int width = kHeaderWidth; + for (const auto& p : times) + { + std::ostringstream formatted; + formatted << std::fixed << std::setprecision(2) + << (static_cast(p.second) / 1000.0); + width = std::max(width, static_cast(formatted.str().size())); + } + return width; } /// @p times must be non-empty and sorted longest-first by time_us. struct TimingSummaryMs { - double min; - double max; - double mean; - double median; - double stddev; + double min; + double max; + double mean; + double median; + double stddev; }; TimingSummaryMs timing_summary_ms(const std::vector>& times) { - const double min_ms = static_cast(times.back().second) / 1000.0; - const double max_ms = static_cast(times.front().second) / 1000.0; - - double sum_us = 0.0; - for (const auto& p : times) - sum_us += static_cast(p.second); - const double mean_ms = (sum_us / static_cast(times.size())) / 1000.0; - - const std::size_t n = times.size(); - double median_ms = 0.0; - if (n % 2 == 1) - { - median_ms = static_cast(times[n / 2].second) / 1000.0; - } - else - { - const double lo = static_cast(times[n / 2].second); - const double hi = static_cast(times[n / 2 - 1].second); - median_ms = ((lo + hi) / 2.0) / 1000.0; - } - - double stddev_ms = 0.0; - if (n > 1) - { - const double mean_us = sum_us / static_cast(n); - double sum_sq_us = 0.0; + const double min_ms = static_cast(times.back().second) / 1000.0; + const double max_ms = static_cast(times.front().second) / 1000.0; + + double sum_us = 0.0; for (const auto& p : times) + sum_us += static_cast(p.second); + const double mean_ms = (sum_us / static_cast(times.size())) / 1000.0; + + const std::size_t n = times.size(); + double median_ms = 0.0; + if (n % 2 == 1) + { + median_ms = static_cast(times[n / 2].second) / 1000.0; + } + else + { + const double lo = static_cast(times[n / 2].second); + const double hi = static_cast(times[n / 2 - 1].second); + median_ms = ((lo + hi) / 2.0) / 1000.0; + } + + double stddev_ms = 0.0; + if (n > 1) { - const double d = static_cast(p.second) - mean_us; - sum_sq_us += d * d; + const double mean_us = sum_us / static_cast(n); + double sum_sq_us = 0.0; + for (const auto& p : times) + { + const double d = static_cast(p.second) - mean_us; + sum_sq_us += d * d; + } + stddev_ms = std::sqrt(sum_sq_us / static_cast(n - 1)) / 1000.0; } - stddev_ms = std::sqrt(sum_sq_us / static_cast(n - 1)) / 1000.0; - } - return TimingSummaryMs{min_ms, max_ms, mean_ms, median_ms, stddev_ms}; + return TimingSummaryMs{min_ms, max_ms, mean_ms, median_ms, stddev_ms}; } } // namespace void print_per_board_timings( - std::ostream& out, - std::vector> times) + std::ostream& out, + std::vector> times) { - std::sort(times.begin(), times.end(), [](const auto& a, const auto& b) { - return a.second > b.second; - }); - - const int ms_width = ms_column_width(times); - const int board_width = board_column_width(times); - - // Preserve caller formatting; setw is one-shot but fixed/precision/align persist. - const auto saved_flags = out.flags(); - const auto saved_precision = out.precision(); - - out << "\nPer-board timings (ms) sorted by longest first:\n\n"; - out << std::right << std::setw(ms_width) << "ms" << " " - << std::setw(board_width) << "board" << "\n"; - out << std::fixed << std::setprecision(2); - for (const auto& p : times) - { - out << std::right << std::setw(ms_width) - << (static_cast(p.second) / 1000.0) << " " - << std::setw(board_width) << p.first << "\n"; - } - - if (!times.empty()) - { - const TimingSummaryMs summary = timing_summary_ms(times); - out << "\n" - << "ms min " << summary.min - << " max " << summary.max - << " mean " << summary.mean - << " median " << summary.median - << " stddev " << summary.stddev - << "\n"; - } - - out.flags(saved_flags); - out.precision(saved_precision); + std::sort(times.begin(), times.end(), [](const auto& a, const auto& b) { + return a.second > b.second; + }); + + const int ms_width = ms_column_width(times); + const int board_width = board_column_width(times); + + // Preserve caller formatting; setw is one-shot but fixed/precision/align persist. + const auto saved_flags = out.flags(); + const auto saved_precision = out.precision(); + + out << "\nPer-board timings (ms) sorted by longest first:\n\n"; + out << std::right << std::setw(ms_width) << "ms" << " " + << std::setw(board_width) << "board" << "\n"; + out << std::fixed << std::setprecision(2); + for (const auto& p : times) + { + out << std::right << std::setw(ms_width) + << (static_cast(p.second) / 1000.0) << " " + << std::setw(board_width) << p.first << "\n"; + } + + if (!times.empty()) + { + const TimingSummaryMs summary = timing_summary_ms(times); + out << "\n" + << "ms min " << summary.min + << " max " << summary.max + << " mean " << summary.mean + << " median " << summary.median + << " stddev " << summary.stddev + << "\n"; + } + + out.flags(saved_flags); + out.precision(saved_precision); } void append_batch_board_times( - std::vector>& accumulated, - const std::vector>& batch_times, - int file_offset) + std::vector>& accumulated, + const std::vector>& batch_times, + int file_offset) { - accumulated.reserve(accumulated.size() + batch_times.size()); - for (const auto& p : batch_times) - accumulated.emplace_back(p.first + file_offset, p.second); + accumulated.reserve(accumulated.size() + batch_times.size()); + for (const auto& p : batch_times) + accumulated.emplace_back(p.first + file_offset, p.second); } void append_calc_batch_deal_times( - std::vector>& accumulated, - const std::vector& strain_times_us, - int strains_per_deal, - int file_offset) + std::vector>& accumulated, + const std::vector& strain_times_us, + int strains_per_deal, + int file_offset) { - if (strains_per_deal <= 0 || strain_times_us.empty()) - return; - - const int deal_count = - static_cast(strain_times_us.size() / static_cast(strains_per_deal)); - accumulated.reserve(accumulated.size() + static_cast(deal_count)); - - constexpr auto kMax = static_cast(std::numeric_limits::max()); - for (int d = 0; d < deal_count; ++d) - { - long long sum_us = 0; - const int base = d * strains_per_deal; - for (int s = 0; s < strains_per_deal; ++s) + if (strains_per_deal <= 0 || strain_times_us.empty()) + return; + + const int deal_count = + static_cast(strain_times_us.size() / static_cast(strains_per_deal)); + accumulated.reserve(accumulated.size() + static_cast(deal_count)); + + constexpr auto kMax = static_cast(std::numeric_limits::max()); + for (int d = 0; d < deal_count; ++d) { - sum_us += strain_times_us[static_cast(base + s)]; - if (sum_us >= kMax) - { - sum_us = kMax; - break; - } + long long sum_us = 0; + const int base = d * strains_per_deal; + for (int s = 0; s < strains_per_deal; ++s) + { + sum_us += strain_times_us[static_cast(base + s)]; + if (sum_us >= kMax) + { + sum_us = kMax; + break; + } + } + accumulated.emplace_back(d + file_offset, static_cast(sum_us)); } - accumulated.emplace_back(d + file_offset, static_cast(sum_us)); - } } diff --git a/library/tests/report_board_timings_test.cpp b/library/tests/report_board_timings_test.cpp index fd6bed5b1..298780dca 100644 --- a/library/tests/report_board_timings_test.cpp +++ b/library/tests/report_board_timings_test.cpp @@ -15,226 +15,226 @@ TEST(ReportBoardTimings, PrintsColumnHeadingsThenSortedRows) { - // Arrange: stored times are microseconds; report shows ms with two decimals. - // Both columns are space-padded and right-aligned (no tabs — tab stops - // shift when the ms field width varies). - // Summary uses the same ms scale: min 7.00, max 42.50, - // mean (42.50+10.10+7.00)/3 = 19.87, median 10.10, - // sample stddev = 19.66. - std::vector> times = { - {2, 10100}, - {5, 42500}, - {1, 7000}, - }; - std::ostringstream out; - - // Act - print_per_board_timings(out, times); - - // Assert - EXPECT_EQ( - out.str(), - "\nPer-board timings (ms) sorted by longest first:\n" - "\n" - " ms board\n" - "42.50 5\n" - "10.10 2\n" - " 7.00 1\n" - "\n" - "ms min 7.00 max 42.50 mean 19.87 median 10.10 stddev 19.66\n"); + // Arrange: stored times are microseconds; report shows ms with two decimals. + // Both columns are space-padded and right-aligned (no tabs — tab stops + // shift when the ms field width varies). + // Summary uses the same ms scale: min 7.00, max 42.50, + // mean (42.50+10.10+7.00)/3 = 19.87, median 10.10, + // sample stddev = 19.66. + std::vector> times = { + {2, 10100}, + {5, 42500}, + {1, 7000}, + }; + std::ostringstream out; + + // Act + print_per_board_timings(out, times); + + // Assert + EXPECT_EQ( + out.str(), + "\nPer-board timings (ms) sorted by longest first:\n" + "\n" + " ms board\n" + "42.50 5\n" + "10.10 2\n" + " 7.00 1\n" + "\n" + "ms min 7.00 max 42.50 mean 19.87 median 10.10 stddev 19.66\n"); } TEST(ReportBoardTimings, RightAlignsBoardWiderThanHeader) { - // Even count: median is the average of the two middle values. - std::vector> times = { - {12, 1000}, - {3456, 2000}, - }; - std::ostringstream out; - - print_per_board_timings(out, times); - - EXPECT_EQ( - out.str(), - "\nPer-board timings (ms) sorted by longest first:\n" - "\n" - " ms board\n" - "2.00 3456\n" - "1.00 12\n" - "\n" - "ms min 1.00 max 2.00 mean 1.50 median 1.50 stddev 0.71\n"); + // Even count: median is the average of the two middle values. + std::vector> times = { + {12, 1000}, + {3456, 2000}, + }; + std::ostringstream out; + + print_per_board_timings(out, times); + + EXPECT_EQ( + out.str(), + "\nPer-board timings (ms) sorted by longest first:\n" + "\n" + " ms board\n" + "2.00 3456\n" + "1.00 12\n" + "\n" + "ms min 1.00 max 2.00 mean 1.50 median 1.50 stddev 0.71\n"); } TEST(ReportBoardTimings, RightAlignsMixedMsWidthsWithSpaces) { - // Tab-separated layout breaks once ms strings cross a tab stop; spaces keep - // the board column fixed. - // mean (202.00+164.10+158.40+148.30)/4 = 168.20; - // median avg(158.40, 164.10) = 161.25; - // sample stddev = 23.46. - std::vector> times = { - {1, 202000}, - {13, 164100}, - {7, 158400}, - {92, 148300}, - }; - std::ostringstream out; - - print_per_board_timings(out, times); - - EXPECT_EQ( - out.str(), - "\nPer-board timings (ms) sorted by longest first:\n" - "\n" - " ms board\n" - "202.00 1\n" - "164.10 13\n" - "158.40 7\n" - "148.30 92\n" - "\n" - "ms min 148.30 max 202.00 mean 168.20 median 161.25 stddev 23.46\n"); + // Tab-separated layout breaks once ms strings cross a tab stop; spaces keep + // the board column fixed. + // mean (202.00+164.10+158.40+148.30)/4 = 168.20; + // median avg(158.40, 164.10) = 161.25; + // sample stddev = 23.46. + std::vector> times = { + {1, 202000}, + {13, 164100}, + {7, 158400}, + {92, 148300}, + }; + std::ostringstream out; + + print_per_board_timings(out, times); + + EXPECT_EQ( + out.str(), + "\nPer-board timings (ms) sorted by longest first:\n" + "\n" + " ms board\n" + "202.00 1\n" + "164.10 13\n" + "158.40 7\n" + "148.30 92\n" + "\n" + "ms min 148.30 max 202.00 mean 168.20 median 161.25 stddev 23.46\n"); } TEST(ReportBoardTimings, EmptyInputPrintsTitleAndHeadingsOnly) { - std::ostringstream out; - print_per_board_timings(out, {}); - - EXPECT_EQ( - out.str(), - "\nPer-board timings (ms) sorted by longest first:\n" - "\n" - "ms board\n"); + std::ostringstream out; + print_per_board_timings(out, {}); + + EXPECT_EQ( + out.str(), + "\nPer-board timings (ms) sorted by longest first:\n" + "\n" + "ms board\n"); } TEST(ReportBoardTimings, RestoresStreamFormattingState) { - // Arrange: caller-owned formatting that must survive the report printer. - std::ostringstream out; - out << std::scientific << std::setprecision(4) << std::left; - const auto flags_before = out.flags(); - const auto precision_before = out.precision(); - - // Act - print_per_board_timings(out, {{1, 1000}}); - - // Assert - EXPECT_EQ(out.flags(), flags_before); - EXPECT_EQ(out.precision(), precision_before); + // Arrange: caller-owned formatting that must survive the report printer. + std::ostringstream out; + out << std::scientific << std::setprecision(4) << std::left; + const auto flags_before = out.flags(); + const auto precision_before = out.precision(); + + // Act + print_per_board_timings(out, {{1, 1000}}); + + // Assert + EXPECT_EQ(out.flags(), flags_before); + EXPECT_EQ(out.precision(), precision_before); } TEST(ReportBoardTimings, SingleBoardSummaryHasEqualStats) { - std::ostringstream out; - print_per_board_timings(out, {{3, 12500}}); - - EXPECT_EQ( - out.str(), - "\nPer-board timings (ms) sorted by longest first:\n" - "\n" - " ms board\n" - "12.50 3\n" - "\n" - "ms min 12.50 max 12.50 mean 12.50 median 12.50 stddev 0.00\n"); + std::ostringstream out; + print_per_board_timings(out, {{3, 12500}}); + + EXPECT_EQ( + out.str(), + "\nPer-board timings (ms) sorted by longest first:\n" + "\n" + " ms board\n" + "12.50 3\n" + "\n" + "ms min 12.50 max 12.50 mean 12.50 median 12.50 stddev 0.00\n"); } TEST(AppendBatchBoardTimes, RemapsBatchLocalIndicesByFileOffset) { - // Arrange: two MAXNOOFBOARDS-sized chunks would look like this after - // RegisterRun resets scheduler times between batches. - std::vector> accumulated; - const std::vector> first_batch = { - {0, 11}, - {1, 22}, - }; - const std::vector> second_batch = { - {0, 33}, - {1, 44}, - }; - - // Act - append_batch_board_times(accumulated, first_batch, /*file_offset=*/0); - append_batch_board_times(accumulated, second_batch, /*file_offset=*/200); - - // Assert: every input deal is present with its file index, not batch index. - ASSERT_EQ(accumulated.size(), 4u); - EXPECT_EQ(accumulated[0], (std::pair{0, 11})); - EXPECT_EQ(accumulated[1], (std::pair{1, 22})); - EXPECT_EQ(accumulated[2], (std::pair{200, 33})); - EXPECT_EQ(accumulated[3], (std::pair{201, 44})); + // Arrange: two MAXNOOFBOARDS-sized chunks would look like this after + // RegisterRun resets scheduler times between batches. + std::vector> accumulated; + const std::vector> first_batch = { + {0, 11}, + {1, 22}, + }; + const std::vector> second_batch = { + {0, 33}, + {1, 44}, + }; + + // Act + append_batch_board_times(accumulated, first_batch, /*file_offset=*/0); + append_batch_board_times(accumulated, second_batch, /*file_offset=*/200); + + // Assert: every input deal is present with its file index, not batch index. + ASSERT_EQ(accumulated.size(), 4u); + EXPECT_EQ(accumulated[0], (std::pair{0, 11})); + EXPECT_EQ(accumulated[1], (std::pair{1, 22})); + EXPECT_EQ(accumulated[2], (std::pair{200, 33})); + EXPECT_EQ(accumulated[3], (std::pair{201, 44})); } TEST(AppendBatchBoardTimes, EmptyBatchIsNoOp) { - std::vector> accumulated = {{7, 1}}; - append_batch_board_times(accumulated, {}, /*file_offset=*/200); - ASSERT_EQ(accumulated.size(), 1u); - EXPECT_EQ(accumulated[0], (std::pair{7, 1})); + std::vector> accumulated = {{7, 1}}; + append_batch_board_times(accumulated, {}, /*file_offset=*/200); + ASSERT_EQ(accumulated.size(), 1u); + EXPECT_EQ(accumulated[0], (std::pair{7, 1})); } TEST(AppendBatchBoardTimes, PreReserveKeepsCapacityAcrossBatches) { - // Arrange: dtest -r reserves one slot per input deal before multi-batch appends. - std::vector> accumulated; - accumulated.reserve(4); - const auto capacity_before = accumulated.capacity(); - - // Act - append_batch_board_times(accumulated, {{0, 11}, {1, 22}}, /*file_offset=*/0); - append_batch_board_times(accumulated, {{0, 33}, {1, 44}}, /*file_offset=*/2); - - // Assert: no reallocation beyond the file-wide reserve. - ASSERT_EQ(accumulated.size(), 4u); - EXPECT_EQ(accumulated.capacity(), capacity_before); + // Arrange: dtest -r reserves one slot per input deal before multi-batch appends. + std::vector> accumulated; + accumulated.reserve(4); + const auto capacity_before = accumulated.capacity(); + + // Act + append_batch_board_times(accumulated, {{0, 11}, {1, 22}}, /*file_offset=*/0); + append_batch_board_times(accumulated, {{0, 33}, {1, 44}}, /*file_offset=*/2); + + // Assert: no reallocation beyond the file-wide reserve. + ASSERT_EQ(accumulated.size(), 4u); + EXPECT_EQ(accumulated.capacity(), capacity_before); } TEST(AppendCalcBatchDealTimes, SumsStrainTimesIntoFileRelativeDeals) { - // Arrange: calc expands each deal to strains_per_deal scheduler boards. - // Batch-local strain times for two deals with 5 strains each, then a second - // batch of one deal remapped by file_offset. - std::vector> accumulated; - const std::vector first_batch_strains = { - 10, 20, 30, 40, 50, // deal 0 -> 150 - 1, 2, 3, 4, 5, // deal 1 -> 15 - }; - const std::vector second_batch_strains = { - 100, 0, 0, 0, 7, // deal 2 -> 107 - }; - - // Act - append_calc_batch_deal_times( - accumulated, first_batch_strains, /*strains_per_deal=*/5, /*file_offset=*/0); - append_calc_batch_deal_times( - accumulated, second_batch_strains, /*strains_per_deal=*/5, /*file_offset=*/2); - - // Assert - ASSERT_EQ(accumulated.size(), 3u); - EXPECT_EQ(accumulated[0], (std::pair{0, 150})); - EXPECT_EQ(accumulated[1], (std::pair{1, 15})); - EXPECT_EQ(accumulated[2], (std::pair{2, 107})); + // Arrange: calc expands each deal to strains_per_deal scheduler boards. + // Batch-local strain times for two deals with 5 strains each, then a second + // batch of one deal remapped by file_offset. + std::vector> accumulated; + const std::vector first_batch_strains = { + 10, 20, 30, 40, 50, // deal 0 -> 150 + 1, 2, 3, 4, 5, // deal 1 -> 15 + }; + const std::vector second_batch_strains = { + 100, 0, 0, 0, 7, // deal 2 -> 107 + }; + + // Act + append_calc_batch_deal_times( + accumulated, first_batch_strains, /*strains_per_deal=*/5, /*file_offset=*/0); + append_calc_batch_deal_times( + accumulated, second_batch_strains, /*strains_per_deal=*/5, /*file_offset=*/2); + + // Assert + ASSERT_EQ(accumulated.size(), 3u); + EXPECT_EQ(accumulated[0], (std::pair{0, 150})); + EXPECT_EQ(accumulated[1], (std::pair{1, 15})); + EXPECT_EQ(accumulated[2], (std::pair{2, 107})); } TEST(AppendCalcBatchDealTimes, EmptyStrainTimesIsNoOp) { - std::vector> accumulated = {{9, 1}}; - append_calc_batch_deal_times( - accumulated, {}, /*strains_per_deal=*/5, /*file_offset=*/10); - ASSERT_EQ(accumulated.size(), 1u); - EXPECT_EQ(accumulated[0], (std::pair{9, 1})); + std::vector> accumulated = {{9, 1}}; + append_calc_batch_deal_times( + accumulated, {}, /*strains_per_deal=*/5, /*file_offset=*/10); + ASSERT_EQ(accumulated.size(), 1u); + EXPECT_EQ(accumulated[0], (std::pair{9, 1})); } TEST(AppendCalcBatchDealTimes, SaturatesSumThatExceedsIntMax) { - std::vector> accumulated; - const int almost_max = std::numeric_limits::max() - 5; - const std::vector strains = {almost_max, 10}; + std::vector> accumulated; + const int almost_max = std::numeric_limits::max() - 5; + const std::vector strains = {almost_max, 10}; - append_calc_batch_deal_times( - accumulated, strains, /*strains_per_deal=*/2, /*file_offset=*/3); + append_calc_batch_deal_times( + accumulated, strains, /*strains_per_deal=*/2, /*file_offset=*/3); - ASSERT_EQ(accumulated.size(), 1u); - EXPECT_EQ(accumulated[0].first, 3); - EXPECT_EQ(accumulated[0].second, std::numeric_limits::max()); + ASSERT_EQ(accumulated.size(), 1u); + EXPECT_EQ(accumulated[0].first, 3); + EXPECT_EQ(accumulated[0].second, std::numeric_limits::max()); } diff --git a/library/tests/solve_board/analyse_play_consistency.cpp b/library/tests/solve_board/analyse_play_consistency.cpp index 14d5f22b6..c5f98ba27 100644 --- a/library/tests/solve_board/analyse_play_consistency.cpp +++ b/library/tests/solve_board/analyse_play_consistency.cpp @@ -34,221 +34,221 @@ const char kSuitChar[4] = {'S', 'H', 'D', 'C'}; auto rank_char(int r) -> char { - static const char* kRanks = "23456789TJQKA"; // index 0 -> rank 2 - return kRanks[r - 2]; + static const char* kRanks = "23456789TJQKA"; // index 0 -> rank 2 + return kRanks[r - 2]; } auto rank_value(char c) -> int { - switch (c) { - case 'A': return 14; - case 'K': return 13; - case 'Q': return 12; - case 'J': return 11; - case 'T': return 10; - default: return c - '0'; - } + switch (c) { + case 'A': return 14; + case 'K': return 13; + case 'Q': return 12; + case 'J': return 11; + case 'T': return 10; + default: return c - '0'; + } } auto suit_index(char c) -> int { - switch (c) { - case 'S': return 0; - case 'H': return 1; - case 'D': return 2; - default: return 3; // 'C' - } + switch (c) { + case 'S': return 0; + case 'H': return 1; + case 'D': return 2; + default: return 3; // 'C' + } } // Emit a PBN deal string ("N: ") from the four hands. auto hands_to_pbn(const Hands& hands) -> std::string { - std::string out = "N:"; - for (int h = 0; h < 4; ++h) { - if (h) out += ' '; - std::array, 4> by_suit; - for (const auto& [s, r] : hands[static_cast(h)]) - by_suit[static_cast(s)].push_back(r); - for (int s = 0; s < 4; ++s) { - if (s) out += '.'; - auto& v = by_suit[static_cast(s)]; - std::sort(v.rbegin(), v.rend()); - for (int r : v) out += rank_char(r); + std::string out = "N:"; + for (int h = 0; h < 4; ++h) { + if (h) out += ' '; + std::array, 4> by_suit; + for (const auto& [s, r] : hands[static_cast(h)]) + by_suit[static_cast(s)].push_back(r); + for (int s = 0; s < 4; ++s) { + if (s) out += '.'; + auto& v = by_suit[static_cast(s)]; + std::sort(v.rbegin(), v.rend()); + for (int r : v) out += rank_char(r); + } } - } - return out; + return out; } // Winner (0..3) of a completed four-card trick; cards are in play order // starting from `leader`. trump == kStrainNT means no trump. auto trick_winner(const std::array& cards, int trump, int leader) -> int { - const int lead_suit = cards[0].first; - int best = 0; - for (int i = 1; i < 4; ++i) { - const auto [si, ri] = cards[static_cast(i)]; - const auto [sb, rb] = cards[static_cast(best)]; - const bool i_trump = (trump != kStrainNT && si == trump); - const bool b_trump = (trump != kStrainNT && sb == trump); - if (i_trump && !b_trump) best = i; - else if (i_trump && b_trump) { if (ri > rb) best = i; } - else if (!i_trump && !b_trump) { - if (si == lead_suit && (sb != lead_suit || ri > rb)) best = i; + const int lead_suit = cards[0].first; + int best = 0; + for (int i = 1; i < 4; ++i) { + const auto [si, ri] = cards[static_cast(i)]; + const auto [sb, rb] = cards[static_cast(best)]; + const bool i_trump = (trump != kStrainNT && si == trump); + const bool b_trump = (trump != kStrainNT && sb == trump); + if (i_trump && !b_trump) best = i; + else if (i_trump && b_trump) { if (ri > rb) best = i; } + else if (!i_trump && !b_trump) { + if (si == lead_suit && (sb != lead_suit || ri > rb)) best = i; + } } - } - return (leader + best) % 4; + return (leader + best) % 4; } // SolveBoardPBN: max tricks for the side to play from the given position. auto solve_max(int trump, int leader, const std::vector& cur, const Hands& hands) -> int { - struct DealPBN dl; - std::memset(&dl, 0, sizeof(dl)); - dl.trump = trump; - dl.first = leader; - for (size_t i = 0; i < cur.size() && i < 3; ++i) { - dl.currentTrickSuit[i] = cur[i].first; - dl.currentTrickRank[i] = cur[i].second; - } - const std::string pbn = hands_to_pbn(hands); - std::strncpy(dl.remainCards, pbn.c_str(), sizeof(dl.remainCards) - 1); + struct DealPBN dl; + std::memset(&dl, 0, sizeof(dl)); + dl.trump = trump; + dl.first = leader; + for (size_t i = 0; i < cur.size() && i < 3; ++i) { + dl.currentTrickSuit[i] = cur[i].first; + dl.currentTrickRank[i] = cur[i].second; + } + const std::string pbn = hands_to_pbn(hands); + std::strncpy(dl.remainCards, pbn.c_str(), sizeof(dl.remainCards) - 1); - struct FutureTricks fut; - const int rc = SolveBoardPBN(dl, -1, 1, 1, &fut, 0); - EXPECT_EQ(RETURN_NO_FAULT, rc); - return fut.score[0]; + struct FutureTricks fut; + const int rc = SolveBoardPBN(dl, -1, 1, 1, &fut, 0); + EXPECT_EQ(RETURN_NO_FAULT, rc); + return fut.score[0]; } // Core check: AnalysePlayPBN's per-card trick count must match an independent // SolveBoardPBN of the reconstructed position at every analyzed ply. auto check_self_consistency(const Hands& hands, int trump, int opening_leader, - const std::vector& play) -> void + const std::vector& play) -> void { - // --- Run AnalysePlayPBN over the whole play. --- - struct DealPBN dl; - std::memset(&dl, 0, sizeof(dl)); - dl.trump = trump; - dl.first = opening_leader; - const std::string deal_pbn = hands_to_pbn(hands); - std::strncpy(dl.remainCards, deal_pbn.c_str(), sizeof(dl.remainCards) - 1); + // --- Run AnalysePlayPBN over the whole play. --- + struct DealPBN dl; + std::memset(&dl, 0, sizeof(dl)); + dl.trump = trump; + dl.first = opening_leader; + const std::string deal_pbn = hands_to_pbn(hands); + std::strncpy(dl.remainCards, deal_pbn.c_str(), sizeof(dl.remainCards) - 1); - struct PlayTracePBN trace; - std::memset(&trace, 0, sizeof(trace)); - trace.number = static_cast(play.size()); - std::string play_str; - for (const auto& [s, r] : play) { - play_str += kSuitChar[s]; - play_str += rank_char(r); - } - std::strncpy(trace.cards, play_str.c_str(), sizeof(trace.cards) - 1); + struct PlayTracePBN trace; + std::memset(&trace, 0, sizeof(trace)); + trace.number = static_cast(play.size()); + std::string play_str; + for (const auto& [s, r] : play) { + play_str += kSuitChar[s]; + play_str += rank_char(r); + } + std::strncpy(trace.cards, play_str.c_str(), sizeof(trace.cards) - 1); - struct SolvedPlay solved; - std::memset(&solved, 0, sizeof(solved)); - ASSERT_EQ(RETURN_NO_FAULT, AnalysePlayPBN(dl, trace, &solved, 0)); + struct SolvedPlay solved; + std::memset(&solved, 0, sizeof(solved)); + ASSERT_EQ(RETURN_NO_FAULT, AnalysePlayPBN(dl, trace, &solved, 0)); - // --- Reconstruct each position and compare. --- - // AnalysePlay reports cumulative tricks for the declaring side (the side NOT - // on opening lead). tricks[0..number-1] are meaningful; tricks[number] is a - // terminal boundary entry that is not compared. - const int decl_parity = 1 - (opening_leader % 2); + // --- Reconstruct each position and compare. --- + // AnalysePlay reports cumulative tricks for the declaring side (the side NOT + // on opening lead). tricks[0..number-1] are meaningful; tricks[number] is a + // terminal boundary entry that is not compared. + const int decl_parity = 1 - (opening_leader % 2); - Hands cur_hands = hands; - std::vector cur; // cards in the current (incomplete) trick - int leader = opening_leader; - int completed = 0; // completed tricks so far - int decl_won = 0; // completed tricks won by the declaring side + Hands cur_hands = hands; + std::vector cur; // cards in the current (incomplete) trick + int leader = opening_leader; + int completed = 0; // completed tricks so far + int decl_won = 0; // completed tricks won by the declaring side - for (size_t k = 0; k < play.size(); ++k) { - if (static_cast(k) < solved.number) { - const int remaining = 13 - completed; - const int player_to_act = (leader + static_cast(cur.size())) % 4; - const int sb = solve_max(trump, leader, cur, cur_hands); - const int decl_remaining = - (player_to_act % 2 == decl_parity) ? sb : (remaining - sb); - const int expected = decl_won + decl_remaining; - EXPECT_EQ(expected, solved.tricks[k]) - << "AnalysePlay disagrees with SolveBoard at ply " << k - << " (deal " << deal_pbn << ", trump " << trump - << ", leader " << opening_leader << ")"; - } + for (size_t k = 0; k < play.size(); ++k) { + if (static_cast(k) < solved.number) { + const int remaining = 13 - completed; + const int player_to_act = (leader + static_cast(cur.size())) % 4; + const int sb = solve_max(trump, leader, cur, cur_hands); + const int decl_remaining = + (player_to_act % 2 == decl_parity) ? sb : (remaining - sb); + const int expected = decl_won + decl_remaining; + EXPECT_EQ(expected, solved.tricks[k]) + << "AnalysePlay disagrees with SolveBoard at ply " << k + << " (deal " << deal_pbn << ", trump " << trump + << ", leader " << opening_leader << ")"; + } - // Advance the reconstruction by playing card k. - const Card card = play[k]; - const int player = (leader + static_cast(cur.size())) % 4; - auto& ph = cur_hands[static_cast(player)]; - ph.erase(std::remove(ph.begin(), ph.end(), card), ph.end()); - cur.push_back(card); - if (cur.size() == 4) { - std::array t{cur[0], cur[1], cur[2], cur[3]}; - const int w = trick_winner(t, trump, leader); - if (w % 2 == decl_parity) ++decl_won; - ++completed; - leader = w; - cur.clear(); + // Advance the reconstruction by playing card k. + const Card card = play[k]; + const int player = (leader + static_cast(cur.size())) % 4; + auto& ph = cur_hands[static_cast(player)]; + ph.erase(std::remove(ph.begin(), ph.end(), card), ph.end()); + cur.push_back(card); + if (cur.size() == 4) { + std::array t{cur[0], cur[1], cur[2], cur[3]}; + const int w = trick_winner(t, trump, leader); + if (w % 2 == decl_parity) ++decl_won; + ++completed; + leader = w; + cur.clear(); + } } - } } // Deal 52 cards into four hands using a deterministic generator. auto make_deal(std::mt19937& rng) -> Hands { - std::vector deck; - for (int s = 0; s < 4; ++s) - for (int r = 2; r <= 14; ++r) - deck.emplace_back(s, r); - std::shuffle(deck.begin(), deck.end(), rng); - Hands hands; - for (int h = 0; h < 4; ++h) - for (int i = 0; i < 13; ++i) - hands[static_cast(h)].push_back(deck[static_cast(h * 13 + i)]); - return hands; + std::vector deck; + for (int s = 0; s < 4; ++s) + for (int r = 2; r <= 14; ++r) + deck.emplace_back(s, r); + std::shuffle(deck.begin(), deck.end(), rng); + Hands hands; + for (int h = 0; h < 4; ++h) + for (int i = 0; i < 13; ++i) + hands[static_cast(h)].push_back(deck[static_cast(h * 13 + i)]); + return hands; } // Generate a full legal 52-card play, following suit. auto make_play(const Hands& deal, int leader, int trump, std::mt19937& rng) - -> std::vector + -> std::vector { - Hands hands = deal; - std::vector out; - std::vector cur; - int cur_leader = leader; - int player = leader; - for (int ply = 0; ply < 52; ++ply) { - auto& hand = hands[static_cast(player)]; - Card card; - if (cur.empty()) { - card = hand[rng() % hand.size()]; - } else { - const int ls = cur[0].first; - std::vector follow; - for (const auto& c : hand) if (c.first == ls) follow.push_back(c); - const auto& pool = follow.empty() ? hand : follow; - card = pool[rng() % pool.size()]; + Hands hands = deal; + std::vector out; + std::vector cur; + int cur_leader = leader; + int player = leader; + for (int ply = 0; ply < 52; ++ply) { + auto& hand = hands[static_cast(player)]; + Card card; + if (cur.empty()) { + card = hand[rng() % hand.size()]; + } else { + const int ls = cur[0].first; + std::vector follow; + for (const auto& c : hand) if (c.first == ls) follow.push_back(c); + const auto& pool = follow.empty() ? hand : follow; + card = pool[rng() % pool.size()]; + } + hand.erase(std::remove(hand.begin(), hand.end(), card), hand.end()); + out.push_back(card); + cur.push_back(card); + player = (player + 1) % 4; + if (cur.size() == 4) { + std::array t{cur[0], cur[1], cur[2], cur[3]}; + cur_leader = trick_winner(t, trump, cur_leader); + player = cur_leader; + cur.clear(); + } } - hand.erase(std::remove(hand.begin(), hand.end(), card), hand.end()); - out.push_back(card); - cur.push_back(card); - player = (player + 1) % 4; - if (cur.size() == 4) { - std::array t{cur[0], cur[1], cur[2], cur[3]}; - cur_leader = trick_winner(t, trump, cur_leader); - player = cur_leader; - cur.clear(); - } - } - return out; + return out; } // Parse "S: "-ordered PBN-like literal hands into our N,E,S,W // layout from explicit per-seat holding strings (suits "s.h.d.c"). auto hand_from_holdings(const std::array& suits) -> std::vector { - std::vector cards; - for (int s = 0; s < 4; ++s) - for (char c : suits[static_cast(s)]) - cards.emplace_back(s, rank_value(c)); - return cards; + std::vector cards; + for (int s = 0; s < 4; ++s) + for (char c : suits[static_cast(s)]) + cards.emplace_back(s, rank_value(c)); + return cards; } class AnalysePlayConsistency : public ::testing::Test @@ -258,37 +258,37 @@ class AnalysePlayConsistency : public ::testing::Test // The exact deal from dds-bridge/dds issue #156. TEST_F(AnalysePlayConsistency, Issue156) { - Hands hands; - // N,E,S,W (suits S.H.D.C): - hands[0] = hand_from_holdings({"AKT4", "5", "762", "J9864"}); // North - hands[1] = hand_from_holdings({"8", "AT98", "A54", "KT753"}); // East - hands[2] = hand_from_holdings({"97652", "K632", "K8", "AQ"}); // South - hands[3] = hand_from_holdings({"QJ3", "QJ74", "QJT93", "2"}); // West + Hands hands; + // N,E,S,W (suits S.H.D.C): + hands[0] = hand_from_holdings({"AKT4", "5", "762", "J9864"}); // North + hands[1] = hand_from_holdings({"8", "AT98", "A54", "KT753"}); // East + hands[2] = hand_from_holdings({"97652", "K632", "K8", "AQ"}); // South + hands[3] = hand_from_holdings({"QJ3", "QJ74", "QJT93", "2"}); // West - const int trump = 1; // Hearts - const int leader = 0; // North leads - const std::string play_str = - "SKS8S2S3H5HTHKH4H2HQD2H8SQSAHAS5H9H3HJC4H7D6C5H6DQD7DAD8" - "D5DKD3C6S9SJS4C7DJC8D4S6DTC9CTCQD9CJCKS7C2STC3CA"; - std::vector play; - for (size_t i = 0; i + 1 < play_str.size(); i += 2) - play.emplace_back(suit_index(play_str[i]), rank_value(play_str[i + 1])); + const int trump = 1; // Hearts + const int leader = 0; // North leads + const std::string play_str = + "SKS8S2S3H5HTHKH4H2HQD2H8SQSAHAS5H9H3HJC4H7D6C5H6DQD7DAD8" + "D5DKD3C6S9SJS4C7DJC8D4S6DTC9CTCQD9CJCKS7C2STC3CA"; + std::vector play; + for (size_t i = 0; i + 1 < play_str.size(); i += 2) + play.emplace_back(suit_index(play_str[i]), rank_value(play_str[i + 1])); - check_self_consistency(hands, trump, leader, play); + check_self_consistency(hands, trump, leader, play); } // Broad coverage: deterministic random deals, full legal play-outs. TEST_F(AnalysePlayConsistency, MatchesSolveBoardAcrossRandomPlayouts) { - std::mt19937 rng(20260529u); - constexpr int kDeals = 25; - for (int d = 0; d < kDeals; ++d) { - const Hands hands = make_deal(rng); - const int trump = static_cast(rng() % 5); - const int leader = static_cast(rng() % 4); - const std::vector play = make_play(hands, leader, trump, rng); - check_self_consistency(hands, trump, leader, play); - } + std::mt19937 rng(20260529u); + constexpr int kDeals = 25; + for (int d = 0; d < kDeals; ++d) { + const Hands hands = make_deal(rng); + const int trump = static_cast(rng() % 5); + const int leader = static_cast(rng() % 4); + const std::vector play = make_play(hands, leader, trump, rng); + check_self_consistency(hands, trump, leader, play); + } } } // namespace diff --git a/library/tests/system/calc_all_tables_x_test.cpp b/library/tests/system/calc_all_tables_x_test.cpp index 4e7766ce4..5e5256ee7 100644 --- a/library/tests/system/calc_all_tables_x_test.cpp +++ b/library/tests/system/calc_all_tables_x_test.cpp @@ -20,226 +20,226 @@ namespace DdTableDeal make_known_deal() { - DdTableDeal deal{}; - deal.cards[0][0] = 0x1800 | 0x0040; - deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; - deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; - deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; - deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; - deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; - deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; - deal.cards[1][3] = 0x1000 | 0x0010; - deal.cards[2][0] = 0x2000 | 0x0020; - deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; - deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; - deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; - deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; - deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; - deal.cards[3][2] = 0x0008 | 0x0004; - deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; - return deal; + DdTableDeal deal{}; + deal.cards[0][0] = 0x1800 | 0x0040; + deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; + deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; + deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; + deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; + deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; + deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; + deal.cards[1][3] = 0x1000 | 0x0010; + deal.cards[2][0] = 0x2000 | 0x0020; + deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; + deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; + deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; + deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; + deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; + deal.cards[3][2] = 0x0008 | 0x0004; + deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; + return deal; } void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) { - for (int strain = 0; strain < DDS_STRAINS; strain++) - for (int hand = 0; hand < DDS_HANDS; hand++) - EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) - << "Mismatch at strain=" << strain << " hand=" << hand; + for (int strain = 0; strain < DDS_STRAINS; strain++) + for (int hand = 0; hand < DDS_HANDS; hand++) + EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) + << "Mismatch at strain=" << strain << " hand=" << hand; } // strncpy does not guarantee NUL-termination when src length >= dest size. void copy_pbn_cards(char* dest, const std::size_t dest_size, const char* src) { - std::strncpy(dest, src, dest_size - 1); - dest[dest_size - 1] = '\0'; + std::strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = '\0'; } } // namespace TEST(CalcAllTablesX, NullPointersReturnUnknownFault) { - InitializeStaticMemory(); - const DdTableDeal known = make_known_deal(); - DdTableDeal deal = known; - DdTableResults result{}; - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ( - CalcAllTablesX(1, nullptr, -1, filter, &result, nullptr, 1), - RETURN_UNKNOWN_FAULT); - EXPECT_EQ( - CalcAllTablesX(1, &deal, -1, filter, nullptr, nullptr, 1), - RETURN_UNKNOWN_FAULT); - EXPECT_EQ( - CalcAllTablesX(1, &deal, -1, nullptr, &result, nullptr, 1), - RETURN_UNKNOWN_FAULT); - // mode in [0, 3] with all strains included requests par output. - EXPECT_EQ( - CalcAllTablesX(1, &deal, /*mode=*/0, filter, &result, nullptr, 1), - RETURN_UNKNOWN_FAULT); + InitializeStaticMemory(); + const DdTableDeal known = make_known_deal(); + DdTableDeal deal = known; + DdTableResults result{}; + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ( + CalcAllTablesX(1, nullptr, -1, filter, &result, nullptr, 1), + RETURN_UNKNOWN_FAULT); + EXPECT_EQ( + CalcAllTablesX(1, &deal, -1, filter, nullptr, nullptr, 1), + RETURN_UNKNOWN_FAULT); + EXPECT_EQ( + CalcAllTablesX(1, &deal, -1, nullptr, &result, nullptr, 1), + RETURN_UNKNOWN_FAULT); + // mode in [0, 3] with all strains included requests par output. + EXPECT_EQ( + CalcAllTablesX(1, &deal, /*mode=*/0, filter, &result, nullptr, 1), + RETURN_UNKNOWN_FAULT); } TEST(CalcAllTablesPBNX, NullPointersReturnUnknownFault) { - // Also documents the C ABI contract: heap allocation failure (or any throw) - // inside CalcAllTablesPBNX must surface as RETURN_UNKNOWN_FAULT, never as an - // exception crossing the C boundary. - InitializeStaticMemory(); - DdTableDealPBN deal{}; - copy_pbn_cards( - deal.cards, - sizeof(deal.cards), - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); - DdTableResults result{}; - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ( - CalcAllTablesPBNX(1, nullptr, -1, filter, &result, nullptr, 1), - RETURN_UNKNOWN_FAULT); - EXPECT_EQ( - CalcAllTablesPBNX(1, &deal, -1, filter, nullptr, nullptr, 1), - RETURN_UNKNOWN_FAULT); - EXPECT_EQ( - CalcAllTablesPBNX(1, &deal, -1, nullptr, &result, nullptr, 1), - RETURN_UNKNOWN_FAULT); + // Also documents the C ABI contract: heap allocation failure (or any throw) + // inside CalcAllTablesPBNX must surface as RETURN_UNKNOWN_FAULT, never as an + // exception crossing the C boundary. + InitializeStaticMemory(); + DdTableDealPBN deal{}; + copy_pbn_cards( + deal.cards, + sizeof(deal.cards), + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); + DdTableResults result{}; + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ( + CalcAllTablesPBNX(1, nullptr, -1, filter, &result, nullptr, 1), + RETURN_UNKNOWN_FAULT); + EXPECT_EQ( + CalcAllTablesPBNX(1, &deal, -1, filter, nullptr, nullptr, 1), + RETURN_UNKNOWN_FAULT); + EXPECT_EQ( + CalcAllTablesPBNX(1, &deal, -1, nullptr, &result, nullptr, 1), + RETURN_UNKNOWN_FAULT); } TEST(CalcAllTablesX, ZeroDealsClearsReusedStrainTimes) { - InitializeStaticMemory(); - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - std::vector strain_times = {11, 22, 33}; - - ASSERT_EQ( - calc_all_tables_x( - /*numDeals=*/0, - /*deals=*/nullptr, - -1, - filter, - /*results=*/nullptr, - nullptr, - 1, - &strain_times), - RETURN_NO_FAULT); - EXPECT_TRUE(strain_times.empty()); + InitializeStaticMemory(); + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + std::vector strain_times = {11, 22, 33}; + + ASSERT_EQ( + calc_all_tables_x( + /*numDeals=*/0, + /*deals=*/nullptr, + -1, + filter, + /*results=*/nullptr, + nullptr, + 1, + &strain_times), + RETURN_NO_FAULT); + EXPECT_TRUE(strain_times.empty()); } TEST(CalcAllTablesPBNX, ZeroDealsClearsReusedStrainTimes) { - InitializeStaticMemory(); - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - std::vector strain_times = {7, 8}; - - ASSERT_EQ( - calc_all_tables_pbn_x( - /*numDeals=*/0, - /*deals=*/nullptr, - -1, - filter, - /*results=*/nullptr, - nullptr, - 1, - &strain_times), - RETURN_NO_FAULT); - EXPECT_TRUE(strain_times.empty()); + InitializeStaticMemory(); + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + std::vector strain_times = {7, 8}; + + ASSERT_EQ( + calc_all_tables_pbn_x( + /*numDeals=*/0, + /*deals=*/nullptr, + -1, + filter, + /*results=*/nullptr, + nullptr, + 1, + &strain_times), + RETURN_NO_FAULT); + EXPECT_TRUE(strain_times.empty()); } TEST(CalcAllTablesPBNX, InvalidPbnReturnsPbnFault) { - InitializeStaticMemory(); - DdTableDealPBN deal{}; - // Must not start with N/E/S/W (case-insensitive) or convert_from_pbn may - // attempt a partial parse and fail later with a different code. - copy_pbn_cards(deal.cards, sizeof(deal.cards), "ZZZ:not-a-deal"); - DdTableResults result{}; - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - - EXPECT_EQ( - CalcAllTablesPBNX(1, &deal, -1, filter, &result, nullptr, 1), - RETURN_PBN_FAULT); + InitializeStaticMemory(); + DdTableDealPBN deal{}; + // Must not start with N/E/S/W (case-insensitive) or convert_from_pbn may + // attempt a partial parse and fail later with a different code. + copy_pbn_cards(deal.cards, sizeof(deal.cards), "ZZZ:not-a-deal"); + DdTableResults result{}; + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + + EXPECT_EQ( + CalcAllTablesPBNX(1, &deal, -1, filter, &result, nullptr, 1), + RETURN_PBN_FAULT); } TEST(CalcAllTablesX, LegacyRejectsMoreThanMaxTables) { - InitializeStaticMemory(); - DdTableDeals deals{}; - deals.no_of_tables = MAXNOOFTABLES + 1; - const DdTableDeal known = make_known_deal(); - // Fill only the in-bounds slots; CalcAllTablesN must reject on count alone. - for (int i = 0; i < MAXNOOFTABLES; i++) - deals.deals[i] = known; - - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - DdTablesRes results{}; - AllParResults par{}; - EXPECT_EQ( - CalcAllTablesN(&deals, -1, filter, &results, &par, /*maxThreads=*/1), - RETURN_TOO_MANY_TABLES); + InitializeStaticMemory(); + DdTableDeals deals{}; + deals.no_of_tables = MAXNOOFTABLES + 1; + const DdTableDeal known = make_known_deal(); + // Fill only the in-bounds slots; CalcAllTablesN must reject on count alone. + for (int i = 0; i < MAXNOOFTABLES; i++) + deals.deals[i] = known; + + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + DdTablesRes results{}; + AllParResults par{}; + EXPECT_EQ( + CalcAllTablesN(&deals, -1, filter, &results, &par, /*maxThreads=*/1), + RETURN_TOO_MANY_TABLES); } TEST(CalcAllTablesX, AcceptsMoreThanMaxTablesAndMatchesLegacy) { - InitializeStaticMemory(); - const DdTableDeal known = make_known_deal(); - // One strain keeps the >MAXNOOFTABLES path cheap enough for TSAN CI. - int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; - - DdTableDeals legacy{}; - legacy.no_of_tables = 2; - legacy.deals[0] = known; - legacy.deals[1] = known; - DdTablesRes legacy_results{}; - AllParResults par{}; - ASSERT_EQ( - CalcAllTablesN(&legacy, -1, filter, &legacy_results, &par, 1), - RETURN_NO_FAULT); - - constexpr int kNum = MAXNOOFTABLES + 1; - std::vector deals(static_cast(kNum), known); - std::vector results(static_cast(kNum)); - ASSERT_EQ( - CalcAllTablesX( - kNum, deals.data(), -1, filter, results.data(), nullptr, 1), - RETURN_NO_FAULT); - - expect_tables_equal(legacy_results.results[0], results[0]); - expect_tables_equal(legacy_results.results[0], results[static_cast(kNum - 1)]); + InitializeStaticMemory(); + const DdTableDeal known = make_known_deal(); + // One strain keeps the >MAXNOOFTABLES path cheap enough for TSAN CI. + int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; + + DdTableDeals legacy{}; + legacy.no_of_tables = 2; + legacy.deals[0] = known; + legacy.deals[1] = known; + DdTablesRes legacy_results{}; + AllParResults par{}; + ASSERT_EQ( + CalcAllTablesN(&legacy, -1, filter, &legacy_results, &par, 1), + RETURN_NO_FAULT); + + constexpr int kNum = MAXNOOFTABLES + 1; + std::vector deals(static_cast(kNum), known); + std::vector results(static_cast(kNum)); + ASSERT_EQ( + CalcAllTablesX( + kNum, deals.data(), -1, filter, results.data(), nullptr, 1), + RETURN_NO_FAULT); + + expect_tables_equal(legacy_results.results[0], results[0]); + expect_tables_equal(legacy_results.results[0], results[static_cast(kNum - 1)]); } // Single-worker path must not pay for hardest-first fanout+sort (mirrors // calc_all_boards_n). Multi-worker still estimates every board once. TEST(CalcAllTablesX, SingleWorkerSkipsFanoutSort) { - InitializeStaticMemory(); - const DdTableDeal known = make_known_deal(); - constexpr int kNum = MAXNOOFTABLES + 1; - constexpr int kIncludedStrains = 1; - int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; - const int expected_boards = kNum * kIncludedStrains; - - std::vector deals(static_cast(kNum), known); - std::vector results(static_cast(kNum)); - - const int before_single = dds::internal::deal_fanout_call_count(); - ASSERT_EQ( - CalcAllTablesX( - kNum, deals.data(), -1, filter, results.data(), nullptr, - /*maxThreads=*/1), - RETURN_NO_FAULT); - EXPECT_EQ(dds::internal::deal_fanout_call_count(), before_single) - << "single-worker CalcAllTablesX must skip deal_fanout/sort"; - - const int before_multi = dds::internal::deal_fanout_call_count(); - ASSERT_EQ( - CalcAllTablesX( - kNum, deals.data(), -1, filter, results.data(), nullptr, - /*maxThreads=*/2), - RETURN_NO_FAULT); - EXPECT_EQ( - dds::internal::deal_fanout_call_count() - before_multi, - expected_boards) - << "multi-worker CalcAllTablesX must fanout-sort every board once"; + InitializeStaticMemory(); + const DdTableDeal known = make_known_deal(); + constexpr int kNum = MAXNOOFTABLES + 1; + constexpr int kIncludedStrains = 1; + int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; + const int expected_boards = kNum * kIncludedStrains; + + std::vector deals(static_cast(kNum), known); + std::vector results(static_cast(kNum)); + + const int before_single = dds::internal::deal_fanout_call_count(); + ASSERT_EQ( + CalcAllTablesX( + kNum, deals.data(), -1, filter, results.data(), nullptr, + /*maxThreads=*/1), + RETURN_NO_FAULT); + EXPECT_EQ(dds::internal::deal_fanout_call_count(), before_single) + << "single-worker CalcAllTablesX must skip deal_fanout/sort"; + + const int before_multi = dds::internal::deal_fanout_call_count(); + ASSERT_EQ( + CalcAllTablesX( + kNum, deals.data(), -1, filter, results.data(), nullptr, + /*maxThreads=*/2), + RETURN_NO_FAULT); + EXPECT_EQ( + dds::internal::deal_fanout_call_count() - before_multi, + expected_boards) + << "multi-worker CalcAllTablesX must fanout-sort every board once"; } // The performance point of the X APIs: a batch larger than MAXNOOFTABLES must @@ -247,62 +247,62 @@ TEST(CalcAllTablesX, SingleWorkerSkipsFanoutSort) // chunked jobs of MAXNOOFBOARDS each. TEST(CalcAllTablesX, LargeBatchIsSingleParallelJob) { - InitializeStaticMemory(); - const DdTableDeal known = make_known_deal(); - constexpr int kNum = MAXNOOFTABLES + 1; - // One strain: still >MAXNOOFTABLES boards, without a 5× TSAN timeout. - constexpr int kIncludedStrains = 1; - int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; - const int expected_boards = kNum * kIncludedStrains; - - std::vector deals(static_cast(kNum), known); - std::vector results(static_cast(kNum)); - - (void)dds::internal::parallel_boards_last_job_board_count(); - ASSERT_EQ( - CalcAllTablesX( - kNum, deals.data(), -1, filter, results.data(), nullptr, - /*maxThreads=*/2), - RETURN_NO_FAULT); - - EXPECT_EQ( - dds::internal::parallel_boards_last_job_board_count(), - expected_boards) - << "unbounded calc must dispatch all boards in one parallel job"; + InitializeStaticMemory(); + const DdTableDeal known = make_known_deal(); + constexpr int kNum = MAXNOOFTABLES + 1; + // One strain: still >MAXNOOFTABLES boards, without a 5× TSAN timeout. + constexpr int kIncludedStrains = 1; + int filter[DDS_STRAINS] = {1, 1, 1, 1, 0}; + const int expected_boards = kNum * kIncludedStrains; + + std::vector deals(static_cast(kNum), known); + std::vector results(static_cast(kNum)); + + (void)dds::internal::parallel_boards_last_job_board_count(); + ASSERT_EQ( + CalcAllTablesX( + kNum, deals.data(), -1, filter, results.data(), nullptr, + /*maxThreads=*/2), + RETURN_NO_FAULT); + + EXPECT_EQ( + dds::internal::parallel_boards_last_job_board_count(), + expected_boards) + << "unbounded calc must dispatch all boards in one parallel job"; } TEST(CalcAllTablesX, PbnVariantMatchesBinary) { - InitializeStaticMemory(); - const DdTableDeal known = make_known_deal(); - constexpr int kNum = 3; - std::vector binary(static_cast(kNum), known); - std::vector binary_results(static_cast(kNum)); - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - ASSERT_EQ( - CalcAllTablesX( - kNum, binary.data(), -1, filter, binary_results.data(), nullptr, 1), - RETURN_NO_FAULT); - - // PBN for the known deal (examples/hands.cpp hand 0). - const char* pbn = - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; - std::vector pbn_deals(static_cast(kNum)); - for (int i = 0; i < kNum; i++) - { - copy_pbn_cards( - pbn_deals[static_cast(i)].cards, - sizeof(pbn_deals[0].cards), - pbn); - } - std::vector pbn_results(static_cast(kNum)); - ASSERT_EQ( - CalcAllTablesPBNX( - kNum, pbn_deals.data(), -1, filter, pbn_results.data(), nullptr, 1), - RETURN_NO_FAULT); - - for (int i = 0; i < kNum; i++) - expect_tables_equal( - binary_results[static_cast(i)], - pbn_results[static_cast(i)]); + InitializeStaticMemory(); + const DdTableDeal known = make_known_deal(); + constexpr int kNum = 3; + std::vector binary(static_cast(kNum), known); + std::vector binary_results(static_cast(kNum)); + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + ASSERT_EQ( + CalcAllTablesX( + kNum, binary.data(), -1, filter, binary_results.data(), nullptr, 1), + RETURN_NO_FAULT); + + // PBN for the known deal (examples/hands.cpp hand 0). + const char* pbn = + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + std::vector pbn_deals(static_cast(kNum)); + for (int i = 0; i < kNum; i++) + { + copy_pbn_cards( + pbn_deals[static_cast(i)].cards, + sizeof(pbn_deals[0].cards), + pbn); + } + std::vector pbn_results(static_cast(kNum)); + ASSERT_EQ( + CalcAllTablesPBNX( + kNum, pbn_deals.data(), -1, filter, pbn_results.data(), nullptr, 1), + RETURN_NO_FAULT); + + for (int i = 0; i < kNum; i++) + expect_tables_equal( + binary_results[static_cast(i)], + pbn_results[static_cast(i)]); } diff --git a/library/tests/system/calc_dd_table_partial_test.cpp b/library/tests/system/calc_dd_table_partial_test.cpp index aea2297e2..303eb9803 100644 --- a/library/tests/system/calc_dd_table_partial_test.cpp +++ b/library/tests/system/calc_dd_table_partial_test.cpp @@ -16,94 +16,94 @@ constexpr const char* kOneTrickSpadesPbn = "N:A... Q... K... J..."; void expect_ns_take_all_remaining(const DdTableResults& table, int tricks) { - for (int strain = 0; strain < DDS_STRAINS; strain++) - { - EXPECT_EQ(table.res_table[strain][0], tricks) << "strain=" << strain << " North"; - EXPECT_EQ(table.res_table[strain][1], 0) << "strain=" << strain << " East"; - EXPECT_EQ(table.res_table[strain][2], tricks) << "strain=" << strain << " South"; - EXPECT_EQ(table.res_table[strain][3], 0) << "strain=" << strain << " West"; - } + for (int strain = 0; strain < DDS_STRAINS; strain++) + { + EXPECT_EQ(table.res_table[strain][0], tricks) << "strain=" << strain << " North"; + EXPECT_EQ(table.res_table[strain][1], 0) << "strain=" << strain << " East"; + EXPECT_EQ(table.res_table[strain][2], tricks) << "strain=" << strain << " South"; + EXPECT_EQ(table.res_table[strain][3], 0) << "strain=" << strain << " West"; + } } } // namespace TEST(CalcDdTablePartial, OneCardPerHandUsesRemainingTricksNotThirteen) { - InitializeStaticMemory(); + InitializeStaticMemory(); - DdTableDealPBN deal{}; - std::strncpy(deal.cards, kOneTrickSpadesPbn, sizeof(deal.cards) - 1); - deal.cards[sizeof(deal.cards) - 1] = '\0'; + DdTableDealPBN deal{}; + std::strncpy(deal.cards, kOneTrickSpadesPbn, sizeof(deal.cards) - 1); + deal.cards[sizeof(deal.cards) - 1] = '\0'; - DdTableResults table{}; - ASSERT_EQ(CalcDDtablePBN(deal, &table), RETURN_NO_FAULT); + DdTableResults table{}; + ASSERT_EQ(CalcDDtablePBN(deal, &table), RETURN_NO_FAULT); - // Regression: before the fix, every entry was 13 or 12 (hardcoded 13 - score). - for (int strain = 0; strain < DDS_STRAINS; strain++) - for (int hand = 0; hand < DDS_HANDS; hand++) - { - EXPECT_GE(table.res_table[strain][hand], 0); - EXPECT_LE(table.res_table[strain][hand], 1) - << "strain=" << strain << " hand=" << hand; - } + // Regression: before the fix, every entry was 13 or 12 (hardcoded 13 - score). + for (int strain = 0; strain < DDS_STRAINS; strain++) + for (int hand = 0; hand < DDS_HANDS; hand++) + { + EXPECT_GE(table.res_table[strain][hand], 0); + EXPECT_LE(table.res_table[strain][hand], 1) + << "strain=" << strain << " hand=" << hand; + } - expect_ns_take_all_remaining(table, /*tricks=*/1); + expect_ns_take_all_remaining(table, /*tricks=*/1); } TEST(CalcAllTablesPartial, OneCardPerHandUsesRemainingTricksNotThirteen) { - InitializeStaticMemory(); + InitializeStaticMemory(); - DdTableDealsPBN deals{}; - deals.no_of_tables = 1; - std::strncpy(deals.deals[0].cards, kOneTrickSpadesPbn, sizeof(deals.deals[0].cards) - 1); - deals.deals[0].cards[sizeof(deals.deals[0].cards) - 1] = '\0'; + DdTableDealsPBN deals{}; + deals.no_of_tables = 1; + std::strncpy(deals.deals[0].cards, kOneTrickSpadesPbn, sizeof(deals.deals[0].cards) - 1); + deals.deals[0].cards[sizeof(deals.deals[0].cards) - 1] = '\0'; - int trump_filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - DdTablesRes resp{}; - AllParResults par{}; - ASSERT_EQ( - CalcAllTablesPBN(&deals, /*mode=*/-1, trump_filter, &resp, &par), - RETURN_NO_FAULT); + int trump_filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + DdTablesRes resp{}; + AllParResults par{}; + ASSERT_EQ( + CalcAllTablesPBN(&deals, /*mode=*/-1, trump_filter, &resp, &par), + RETURN_NO_FAULT); - expect_ns_take_all_remaining(resp.results[0], /*tricks=*/1); + expect_ns_take_all_remaining(resp.results[0], /*tricks=*/1); } TEST(CalcAllTablesXPartial, OneCardPerHandUsesRemainingTricksNotThirteen) { - InitializeStaticMemory(); + InitializeStaticMemory(); - DdTableDealPBN deal{}; - std::strncpy(deal.cards, kOneTrickSpadesPbn, sizeof(deal.cards) - 1); - deal.cards[sizeof(deal.cards) - 1] = '\0'; + DdTableDealPBN deal{}; + std::strncpy(deal.cards, kOneTrickSpadesPbn, sizeof(deal.cards) - 1); + deal.cards[sizeof(deal.cards) - 1] = '\0'; - int trump_filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - DdTableResults result{}; - ASSERT_EQ( - CalcAllTablesPBNX(1, &deal, /*mode=*/-1, trump_filter, &result, nullptr, 1), - RETURN_NO_FAULT); + int trump_filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; + DdTableResults result{}; + ASSERT_EQ( + CalcAllTablesPBNX(1, &deal, /*mode=*/-1, trump_filter, &result, nullptr, 1), + RETURN_NO_FAULT); - expect_ns_take_all_remaining(result, /*tricks=*/1); + expect_ns_take_all_remaining(result, /*tricks=*/1); } TEST(CalcDdTablePartialCpp, OneCardPerHandUsesRemainingTricksNotThirteen) { - InitializeStaticMemory(); + InitializeStaticMemory(); - DdTableDealPBN deal_pbn{}; - std::strncpy(deal_pbn.cards, kOneTrickSpadesPbn, sizeof(deal_pbn.cards) - 1); - deal_pbn.cards[sizeof(deal_pbn.cards) - 1] = '\0'; + DdTableDealPBN deal_pbn{}; + std::strncpy(deal_pbn.cards, kOneTrickSpadesPbn, sizeof(deal_pbn.cards) - 1); + deal_pbn.cards[sizeof(deal_pbn.cards) - 1] = '\0'; - DdTableResults table{}; - ASSERT_EQ(calc_dd_table_pbn(deal_pbn, &table), RETURN_NO_FAULT); + DdTableResults table{}; + ASSERT_EQ(calc_dd_table_pbn(deal_pbn, &table), RETURN_NO_FAULT); - for (int strain = 0; strain < DDS_STRAINS; strain++) - for (int hand = 0; hand < DDS_HANDS; hand++) - { - EXPECT_GE(table.res_table[strain][hand], 0); - EXPECT_LE(table.res_table[strain][hand], 1) - << "strain=" << strain << " hand=" << hand; - } + for (int strain = 0; strain < DDS_STRAINS; strain++) + for (int hand = 0; hand < DDS_HANDS; hand++) + { + EXPECT_GE(table.res_table[strain][hand], 0); + EXPECT_LE(table.res_table[strain][hand], 1) + << "strain=" << strain << " hand=" << hand; + } - expect_ns_take_all_remaining(table, /*tricks=*/1); + expect_ns_take_all_remaining(table, /*tricks=*/1); } diff --git a/library/tests/system/concurrency_validation_test.cpp b/library/tests/system/concurrency_validation_test.cpp index e66f47234..d747beaf1 100644 --- a/library/tests/system/concurrency_validation_test.cpp +++ b/library/tests/system/concurrency_validation_test.cpp @@ -21,89 +21,89 @@ extern Memory memory; // Ensure at least N thread slots are available in the legacy Memory static void ensure_threads(size_t n) { - if (memory.NumThreads() < n) - memory.Resize(static_cast(n), DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() < n) + memory.Resize(static_cast(n), DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } static Deal make_deal_from_pbn(const char* pbn, int trump = 0, int first = 0) { - Deal dl{}; - dl.trump = trump; - dl.first = first; - std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); - std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); - // Convert PBN distribution into remainCards bitmasks - const int rc = convert_from_pbn(pbn, dl.remainCards); - // If conversion fails, keep an empty Deal which should yield a deterministic error. - // Silent failure is intentional for this test: downstream code is expected to handle empty deals. - (void)rc; - return dl; + Deal dl{}; + dl.trump = trump; + dl.first = first; + std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); + std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); + // Convert PBN distribution into remainCards bitmasks + const int rc = convert_from_pbn(pbn, dl.remainCards); + // If conversion fails, keep an empty Deal which should yield a deterministic error. + // Silent failure is intentional for this test: downstream code is expected to handle empty deals. + (void)rc; + return dl; } static bool equal_future_tricks(const FutureTricks& a, const FutureTricks& b) { - if (a.cards != b.cards) return false; - for (int i = 0; i < a.cards; ++i) { - if (a.suit[i] != b.suit[i]) return false; - if (a.rank[i] != b.rank[i]) return false; - if (a.equals[i] != b.equals[i]) return false; - if (a.score[i] != b.score[i]) return false; - } - return true; + if (a.cards != b.cards) return false; + for (int i = 0; i < a.cards; ++i) { + if (a.suit[i] != b.suit[i]) return false; + if (a.rank[i] != b.rank[i]) return false; + if (a.equals[i] != b.equals[i]) return false; + if (a.score[i] != b.score[i]) return false; + } + return true; } TEST(ConcurrencyValidation, ParallelInstancesMatchSequentialBaseline) { - // A small set of distinct boards in PBN text form. - // Source: examples/hands.cpp (representative random deals) - const std::vector pbns = { - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3", - "E:QJT5432.T.6.QJ82 .J97543.K7532.94 87.A62.QJT4.AT75 AK96.KQ8.A98.K63", - "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93" - }; + // A small set of distinct boards in PBN text form. + // Source: examples/hands.cpp (representative random deals) + const std::vector pbns = { + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3", + "E:QJT5432.T.6.QJ82 .J97543.K7532.94 87.A62.QJT4.AT75 AK96.KQ8.A98.K63", + "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93" + }; - const size_t N = pbns.size(); - ensure_threads(N); + const size_t N = pbns.size(); + ensure_threads(N); - // Prepare deals and sequential baselines (single thread / thr 0) - std::vector deals; - deals.reserve(N); - for (const auto& s : pbns) { - deals.emplace_back(make_deal_from_pbn(s.c_str(), /*trump=*/0, /*first=*/0)); - } + // Prepare deals and sequential baselines (single thread / thr 0) + std::vector deals; + deals.reserve(N); + for (const auto& s : pbns) { + deals.emplace_back(make_deal_from_pbn(s.c_str(), /*trump=*/0, /*first=*/0)); + } - std::vector baseline_ft(N); - std::vector baseline_rc(N, 0); + std::vector baseline_ft(N); + std::vector baseline_rc(N, 0); - { - SolverContext ctx; - for (size_t i = 0; i < N; ++i) { - FutureTricks ft{}; - const int rc = solve_board(ctx, deals[i], /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft); - baseline_rc[i] = rc; - baseline_ft[i] = ft; // copy + { + SolverContext ctx; + for (size_t i = 0; i < N; ++i) { + FutureTricks ft{}; + const int rc = solve_board(ctx, deals[i], /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft); + baseline_rc[i] = rc; + baseline_ft[i] = ft; // copy + } } - } - // Run in parallel: one thread per board with its own ThreadData/Context - std::vector out_ft(N); - std::vector out_rc(N, 0); + // Run in parallel: one thread per board with its own ThreadData/Context + std::vector out_ft(N); + std::vector out_rc(N, 0); - std::vector threads; - threads.reserve(N); - for (size_t i = 0; i < N; ++i) { - threads.emplace_back([i, &deals, &out_ft, &out_rc]() { - SolverContext ctx; - FutureTricks ft{}; - const int rc = solve_board(ctx, deals[i], /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft); - out_rc[i] = rc; - out_ft[i] = ft; - }); - } - for (auto& t : threads) t.join(); + std::vector threads; + threads.reserve(N); + for (size_t i = 0; i < N; ++i) { + threads.emplace_back([i, &deals, &out_ft, &out_rc]() { + SolverContext ctx; + FutureTricks ft{}; + const int rc = solve_board(ctx, deals[i], /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft); + out_rc[i] = rc; + out_ft[i] = ft; + }); + } + for (auto& t : threads) t.join(); - for (size_t i = 0; i < N; ++i) { - EXPECT_EQ(out_rc[i], baseline_rc[i]) << "Return code mismatch for case " << i; - EXPECT_TRUE(equal_future_tricks(out_ft[i], baseline_ft[i])) << "FutureTricks mismatch for case " << i; - } + for (size_t i = 0; i < N; ++i) { + EXPECT_EQ(out_rc[i], baseline_rc[i]) << "Return code mismatch for case " << i; + EXPECT_TRUE(equal_future_tricks(out_ft[i], baseline_ft[i])) << "FutureTricks mismatch for case " << i; + } } diff --git a/library/tests/system/configure_tt_api_test.cpp b/library/tests/system/configure_tt_api_test.cpp index 57683994e..0712164d7 100644 --- a/library/tests/system/configure_tt_api_test.cpp +++ b/library/tests/system/configure_tt_api_test.cpp @@ -22,12 +22,12 @@ namespace { void set_env_var(const char* name, const char* value) { #ifdef _WIN32 - _putenv_s(name, value != nullptr ? value : ""); + _putenv_s(name, value != nullptr ? value : ""); #else - if (value == nullptr || value[0] == '\0') - unsetenv(name); - else - setenv(name, value, 1); + if (value == nullptr || value[0] == '\0') + unsetenv(name); + else + setenv(name, value, 1); #endif } @@ -35,108 +35,108 @@ void set_env_var(const char* name, const char* value) /// the lifetime of the guard and then restores whatever was there before. struct ScopedEnv { - ScopedEnv(const char* name, const char* value) : name_(name) - { - if (const char* old = std::getenv(name)) { - had_old_ = true; - old_ = old; + ScopedEnv(const char* name, const char* value) : name_(name) + { + if (const char* old = std::getenv(name)) { + had_old_ = true; + old_ = old; + } + set_env_var(name, value); } - set_env_var(name, value); - } - ~ScopedEnv() - { - set_env_var(name_, had_old_ ? old_.c_str() : nullptr); - } - const char* name_; - bool had_old_ = false; - std::string old_; + ~ScopedEnv() + { + set_env_var(name_, had_old_ ? old_.c_str() : nullptr); + } + const char* name_; + bool had_old_ = false; + std::string old_; }; auto kind_of(const TransTable* tt) -> TTKind { - if (dynamic_cast(tt) != nullptr) return TTKind::Small; - if (dynamic_cast(tt) != nullptr) return TTKind::Pattern; - return TTKind::Large; + if (dynamic_cast(tt) != nullptr) return TTKind::Small; + if (dynamic_cast(tt) != nullptr) return TTKind::Pattern; + return TTKind::Large; } TEST(ConfigureTtApiTest, ScopedEnvRestoresThePreviousValueAndAbsence) { - // Arrange: a known outer value, itself scoped so that whatever the runner - // supplied is put back when the test ends. - const char* name = "DDS_TEST_SCOPED_ENV"; - ScopedEnv outer(name, "before"); - - // Act & Assert: an override is undone, and so is a removal. - { - ScopedEnv overridden(name, "during"); - EXPECT_STREQ(std::getenv(name), "during"); - } - EXPECT_STREQ(std::getenv(name), "before"); - { - ScopedEnv removed(name, nullptr); - EXPECT_EQ(std::getenv(name), nullptr); - } - EXPECT_STREQ(std::getenv(name), "before"); + // Arrange: a known outer value, itself scoped so that whatever the runner + // supplied is put back when the test ends. + const char* name = "DDS_TEST_SCOPED_ENV"; + ScopedEnv outer(name, "before"); + + // Act & Assert: an override is undone, and so is a removal. + { + ScopedEnv overridden(name, "during"); + EXPECT_STREQ(std::getenv(name), "during"); + } + EXPECT_STREQ(std::getenv(name), "before"); + { + ScopedEnv removed(name, nullptr); + EXPECT_EQ(std::getenv(name), nullptr); + } + EXPECT_STREQ(std::getenv(name), "before"); } TEST(ConfigureTtApiTest, DefaultConfigurationUsesThePatternTable) { - // Arrange: no explicit kind anywhere (and no environment override). - ScopedEnv no_override("DDS_TT_KIND", nullptr); - SolverConfig cfg; - SolverContext configured(cfg); - SolverContext bare; - - // Act & Assert - EXPECT_EQ(cfg.tt_kind_, TTKind::Pattern); - EXPECT_NE(nullptr, dynamic_cast(configured.trans_table())); - EXPECT_NE(nullptr, dynamic_cast(bare.trans_table())); + // Arrange: no explicit kind anywhere (and no environment override). + ScopedEnv no_override("DDS_TT_KIND", nullptr); + SolverConfig cfg; + SolverContext configured(cfg); + SolverContext bare; + + // Act & Assert + EXPECT_EQ(cfg.tt_kind_, TTKind::Pattern); + EXPECT_NE(nullptr, dynamic_cast(configured.trans_table())); + EXPECT_NE(nullptr, dynamic_cast(bare.trans_table())); } TEST(ConfigureTtApiTest, PatternKindCreatesPatternTable) { - // Arrange: explicit kind, isolated from any ambient override. - ScopedEnv no_override("DDS_TT_KIND", nullptr); - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Pattern; - SolverContext ctx(cfg); - - // Act - auto* tt = ctx.trans_table(); - - // Assert - ASSERT_NE(tt, nullptr); - EXPECT_NE(nullptr, dynamic_cast(tt)); + // Arrange: explicit kind, isolated from any ambient override. + ScopedEnv no_override("DDS_TT_KIND", nullptr); + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Pattern; + SolverContext ctx(cfg); + + // Act + auto* tt = ctx.trans_table(); + + // Assert + ASSERT_NE(tt, nullptr); + EXPECT_NE(nullptr, dynamic_cast(tt)); } /// Prepares `tt` for a deal in which rank r of suit s belongs to seat (r + s) % 4. void init_rotating_deal(TransTable& tt) { - int hand_lookup[DDS_SUITS][15] = {}; - for (int s = 0; s < DDS_SUITS; ++s) - for (int r = 2; r <= 14; ++r) hand_lookup[s][r] = (r + s) % DDS_HANDS; - tt.init(hand_lookup); + int hand_lookup[DDS_SUITS][15] = {}; + for (int s = 0; s < DDS_SUITS; ++s) + for (int r = 2; r <= 14; ++r) hand_lookup[s][r] = (r + s) % DDS_HANDS; + tt.init(hand_lookup); } /// Fills a table with many distinct shapes and returns false as soon as its /// footprint exceeds `cap_kb`. auto stays_under(TransTable& tt, const double cap_kb) -> bool { - const unsigned short aggr[DDS_SUITS] = {0x1fff, 0x1fff, 0x1fff, 0x1fff}; - const unsigned short win_ranks[DDS_SUITS] = {1u << 12, 0, 0, 0}; - NodeCards cards{}; - cards.upper_bound = 13; - for (unsigned i = 0; i < 20000; ++i) { - int hand_dist[DDS_HANDS]; - for (int h = 0; h < DDS_HANDS; ++h) - hand_dist[h] = static_cast((i * 2654435761u * static_cast(h + 1)) & 0xfffu); - const int tricks = 1 + static_cast(i % 12); - bool lower_flag = false; - (void)tt.lookup(tricks, 0, aggr, hand_dist, -1, lower_flag); - tt.add(tricks, 0, aggr, win_ranks, cards, true); - if (tt.memory_in_use() > cap_kb) return false; - } - return true; + const unsigned short aggr[DDS_SUITS] = {0x1fff, 0x1fff, 0x1fff, 0x1fff}; + const unsigned short win_ranks[DDS_SUITS] = {1u << 12, 0, 0, 0}; + NodeCards cards{}; + cards.upper_bound = 13; + for (unsigned i = 0; i < 20000; ++i) { + int hand_dist[DDS_HANDS]; + for (int h = 0; h < DDS_HANDS; ++h) + hand_dist[h] = static_cast((i * 2654435761u * static_cast(h + 1)) & 0xfffu); + const int tricks = 1 + static_cast(i % 12); + bool lower_flag = false; + (void)tt.lookup(tricks, 0, aggr, hand_dist, -1, lower_flag); + tt.add(tricks, 0, aggr, win_ranks, cards, true); + if (tt.memory_in_use() > cap_kb) return false; + } + return true; } /// A configuration that sets only the maximum must yield a table capped at @@ -144,34 +144,34 @@ auto stays_under(TransTable& tt, const double cap_kb) -> bool /// unset value may not lift the cap. TEST(ConfigureTtApiTest, AMaximumOnlyConfigurationIsHonoredOnLazyCreation) { - // Arrange - ScopedEnv no_kind("DDS_TT_KIND", nullptr); - ScopedEnv no_default("DDS_TT_DEFAULT_MB", nullptr); - ScopedEnv no_limit("DDS_TT_LIMIT_MB", nullptr); - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Pattern; - cfg.tt_mem_default_mb_ = 0; - cfg.tt_mem_maximum_mb_ = 1; - SolverContext ctx(cfg); - - // Act - TransTable* tt = ctx.trans_table(); - ASSERT_NE(tt, nullptr); - init_rotating_deal(*tt); - const double cap_kb = tt->memory_in_use() + 1024.0; - - // Assert - EXPECT_TRUE(stays_under(*tt, cap_kb)); + // Arrange + ScopedEnv no_kind("DDS_TT_KIND", nullptr); + ScopedEnv no_default("DDS_TT_DEFAULT_MB", nullptr); + ScopedEnv no_limit("DDS_TT_LIMIT_MB", nullptr); + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Pattern; + cfg.tt_mem_default_mb_ = 0; + cfg.tt_mem_maximum_mb_ = 1; + SolverContext ctx(cfg); + + // Act + TransTable* tt = ctx.trans_table(); + ASSERT_NE(tt, nullptr); + init_rotating_deal(*tt); + const double cap_kb = tt->memory_in_use() + 1024.0; + + // Assert + EXPECT_TRUE(stays_under(*tt, cap_kb)); } /// Solves the known deal from examples/hands.cpp (hand 0) in notrump with `ctx`. auto solve_known_deal(SolverContext& ctx, FutureTricks& fut) -> int { - DealPBN dl{}; - dl.trump = 4; - dl.first = 0; - std::strcpy(dl.remainCards, "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); - return solve_board_pbn(ctx, dl, /*target=*/-1, /*solutions=*/1, /*mode=*/1, &fut); + DealPBN dl{}; + dl.trump = 4; + dl.first = 0; + std::strcpy(dl.remainCards, "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); + return solve_board_pbn(ctx, dl, /*target=*/-1, /*solutions=*/1, /*mode=*/1, &fut); } /// A table recreated between two solves of the same deal has not seen that @@ -179,160 +179,160 @@ auto solve_known_deal(SolverContext& ctx, FutureTricks& fut) -> int /// inert (never init()-ed) cache. TEST(ConfigureTtApiTest, ATableRecreatedBetweenSolvesOfTheSameDealIsInitializedAgain) { - // Arrange: one solve, then a kind change and back, which recreates the table. - ScopedEnv no_kind("DDS_TT_KIND", nullptr); - SolverContext ctx; - FutureTricks first{}; - ASSERT_EQ(solve_known_deal(ctx, first), RETURN_NO_FAULT); - ctx.configure_tt(TTKind::Large, 8, 16); - ctx.configure_tt(TTKind::Pattern, 8, 16); - auto* recreated = dynamic_cast(ctx.maybe_trans_table()); - ASSERT_NE(recreated, nullptr); - ASSERT_EQ(recreated->node_count(), 0u); - - // Act: the same deal again. - FutureTricks again{}; - ASSERT_EQ(solve_known_deal(ctx, again), RETURN_NO_FAULT); - - // Assert: same answer, and the cache was actually in use. - EXPECT_EQ(again.score[0], first.score[0]); - EXPECT_GT(recreated->node_count(), 0u); + // Arrange: one solve, then a kind change and back, which recreates the table. + ScopedEnv no_kind("DDS_TT_KIND", nullptr); + SolverContext ctx; + FutureTricks first{}; + ASSERT_EQ(solve_known_deal(ctx, first), RETURN_NO_FAULT); + ctx.configure_tt(TTKind::Large, 8, 16); + ctx.configure_tt(TTKind::Pattern, 8, 16); + auto* recreated = dynamic_cast(ctx.maybe_trans_table()); + ASSERT_NE(recreated, nullptr); + ASSERT_EQ(recreated->node_count(), 0u); + + // Act: the same deal again. + FutureTricks again{}; + ASSERT_EQ(solve_known_deal(ctx, again), RETURN_NO_FAULT); + + // Assert: same answer, and the cache was actually in use. + EXPECT_EQ(again.score[0], first.score[0]); + EXPECT_GT(recreated->node_count(), 0u); } /// Reconfiguring a live table with unset limits must resolve them the same /// way lazy creation does, not hand the table a zero maximum. TEST(ConfigureTtApiTest, ReconfiguringALiveTableWithUnsetLimitsResolvesThem) { - // Arrange - ScopedEnv no_kind("DDS_TT_KIND", nullptr); - ScopedEnv no_default("DDS_TT_DEFAULT_MB", nullptr); - ScopedEnv no_limit("DDS_TT_LIMIT_MB", nullptr); - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Pattern; - SolverContext ctx(cfg); - auto* tt = dynamic_cast(ctx.trans_table()); - ASSERT_NE(tt, nullptr); - init_rotating_deal(*tt); - - // Act - ctx.configure_tt(TTKind::Pattern, /*defMB=*/0, /*maxMB=*/0); - - // Assert: a few hundred shapes fit comfortably; a zero maximum would - // clear the table on every allocation and keep it near empty. - const unsigned short aggr[DDS_SUITS] = {0x1fff, 0x1fff, 0x1fff, 0x1fff}; - const unsigned short win_ranks[DDS_SUITS] = {1u << 12, 0, 0, 0}; - NodeCards cards{}; - cards.upper_bound = 13; - for (unsigned i = 0; i < 300; ++i) { - int hand_dist[DDS_HANDS]; - for (int h = 0; h < DDS_HANDS; ++h) - hand_dist[h] = static_cast((i * 2654435761u * static_cast(h + 1)) & 0xfffu); - bool lower_flag = false; - (void)tt->lookup(1 + static_cast(i % 12), 0, aggr, hand_dist, -1, lower_flag); - tt->add(1 + static_cast(i % 12), 0, aggr, win_ranks, cards, true); - } - EXPECT_EQ(tt->node_count(), 300u); + // Arrange + ScopedEnv no_kind("DDS_TT_KIND", nullptr); + ScopedEnv no_default("DDS_TT_DEFAULT_MB", nullptr); + ScopedEnv no_limit("DDS_TT_LIMIT_MB", nullptr); + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Pattern; + SolverContext ctx(cfg); + auto* tt = dynamic_cast(ctx.trans_table()); + ASSERT_NE(tt, nullptr); + init_rotating_deal(*tt); + + // Act + ctx.configure_tt(TTKind::Pattern, /*defMB=*/0, /*maxMB=*/0); + + // Assert: a few hundred shapes fit comfortably; a zero maximum would + // clear the table on every allocation and keep it near empty. + const unsigned short aggr[DDS_SUITS] = {0x1fff, 0x1fff, 0x1fff, 0x1fff}; + const unsigned short win_ranks[DDS_SUITS] = {1u << 12, 0, 0, 0}; + NodeCards cards{}; + cards.upper_bound = 13; + for (unsigned i = 0; i < 300; ++i) { + int hand_dist[DDS_HANDS]; + for (int h = 0; h < DDS_HANDS; ++h) + hand_dist[h] = static_cast((i * 2654435761u * static_cast(h + 1)) & 0xfffu); + bool lower_flag = false; + (void)tt->lookup(1 + static_cast(i % 12), 0, aggr, hand_dist, -1, lower_flag); + tt->add(1 + static_cast(i % 12), 0, aggr, win_ranks, cards, true); + } + EXPECT_EQ(tt->node_count(), 300u); } TEST(ConfigureTtApiTest, SwitchingToPatternRecreatesAndResizingKeepsInstance) { - // Arrange: start from the Large table, isolated from any ambient override. - ScopedEnv no_override("DDS_TT_KIND", nullptr); - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Large; - SolverContext ctx(cfg); - auto* large = ctx.trans_table(); - ASSERT_NE(nullptr, dynamic_cast(large)); - - // Act - ctx.configure_tt(TTKind::Pattern, /*defMB=*/8, /*maxMB=*/16); - auto* pattern = ctx.maybe_trans_table(); - ctx.configure_tt(TTKind::Pattern, /*defMB=*/16, /*maxMB=*/32); - auto* resized = ctx.maybe_trans_table(); - - // Assert - ASSERT_NE(pattern, nullptr); - EXPECT_NE(nullptr, dynamic_cast(pattern)); - EXPECT_EQ(pattern, resized) << "same kind: resize in place"; - ctx.configure_tt(TTKind::Large, 8, 16); - EXPECT_NE(nullptr, dynamic_cast(ctx.maybe_trans_table())); + // Arrange: start from the Large table, isolated from any ambient override. + ScopedEnv no_override("DDS_TT_KIND", nullptr); + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Large; + SolverContext ctx(cfg); + auto* large = ctx.trans_table(); + ASSERT_NE(nullptr, dynamic_cast(large)); + + // Act + ctx.configure_tt(TTKind::Pattern, /*defMB=*/8, /*maxMB=*/16); + auto* pattern = ctx.maybe_trans_table(); + ctx.configure_tt(TTKind::Pattern, /*defMB=*/16, /*maxMB=*/32); + auto* resized = ctx.maybe_trans_table(); + + // Assert + ASSERT_NE(pattern, nullptr); + EXPECT_NE(nullptr, dynamic_cast(pattern)); + EXPECT_EQ(pattern, resized) << "same kind: resize in place"; + ctx.configure_tt(TTKind::Large, 8, 16); + EXPECT_NE(nullptr, dynamic_cast(ctx.maybe_trans_table())); } TEST(ConfigureTtApiTest, EnvironmentOverridesTableKind) { - // Arrange - ScopedEnv env("DDS_TT_KIND", "pattern"); - SolverConfig cfg; - cfg.tt_kind_ = TTKind::Small; - SolverContext ctx(cfg); - - // Act & Assert - EXPECT_NE(nullptr, dynamic_cast(ctx.trans_table())); - ScopedEnv env2("DDS_TT_KIND", "large"); - ctx.dispose_trans_table(); - EXPECT_NE(nullptr, dynamic_cast(ctx.trans_table())); + // Arrange + ScopedEnv env("DDS_TT_KIND", "pattern"); + SolverConfig cfg; + cfg.tt_kind_ = TTKind::Small; + SolverContext ctx(cfg); + + // Act & Assert + EXPECT_NE(nullptr, dynamic_cast(ctx.trans_table())); + ScopedEnv env2("DDS_TT_KIND", "large"); + ctx.dispose_trans_table(); + EXPECT_NE(nullptr, dynamic_cast(ctx.trans_table())); } TEST(ConfigureTtApiTest, ConfigureTtComparesTheEnvironmentResolvedKind) { - // Arrange: the environment pins the effective kind to Pattern. - ScopedEnv env("DDS_TT_KIND", "pattern"); - SolverContext ctx; - auto* before = ctx.trans_table(); - ASSERT_NE(nullptr, dynamic_cast(before)); - // Give the live instance state a recreated one would not have (pointer - // equality alone is unreliable: a recreated object may reuse the address). - const int hand_lookup[DDS_SUITS][15] = {}; - before->init(hand_lookup); - const double marked_kb = before->memory_in_use(); - - // Act: asking for Small changes nothing effective, so the instance must - // survive (resized in place) rather than be destroyed and recreated. - ctx.configure_tt(TTKind::Small, /*defMB=*/8, /*maxMB=*/8); - - // Assert - ASSERT_NE(nullptr, ctx.maybe_trans_table()); - EXPECT_EQ(ctx.maybe_trans_table()->memory_in_use(), marked_kb); - - // Act: a new override that differs from the live table must recreate it, - // even though the configured kind (Small) has not changed. - ScopedEnv env2("DDS_TT_KIND", "small"); - ctx.configure_tt(TTKind::Small, /*defMB=*/8, /*maxMB=*/8); - - // Assert - EXPECT_NE(nullptr, dynamic_cast(ctx.maybe_trans_table())); + // Arrange: the environment pins the effective kind to Pattern. + ScopedEnv env("DDS_TT_KIND", "pattern"); + SolverContext ctx; + auto* before = ctx.trans_table(); + ASSERT_NE(nullptr, dynamic_cast(before)); + // Give the live instance state a recreated one would not have (pointer + // equality alone is unreliable: a recreated object may reuse the address). + const int hand_lookup[DDS_SUITS][15] = {}; + before->init(hand_lookup); + const double marked_kb = before->memory_in_use(); + + // Act: asking for Small changes nothing effective, so the instance must + // survive (resized in place) rather than be destroyed and recreated. + ctx.configure_tt(TTKind::Small, /*defMB=*/8, /*maxMB=*/8); + + // Assert + ASSERT_NE(nullptr, ctx.maybe_trans_table()); + EXPECT_EQ(ctx.maybe_trans_table()->memory_in_use(), marked_kb); + + // Act: a new override that differs from the live table must recreate it, + // even though the configured kind (Small) has not changed. + ScopedEnv env2("DDS_TT_KIND", "small"); + ctx.configure_tt(TTKind::Small, /*defMB=*/8, /*maxMB=*/8); + + // Assert + EXPECT_NE(nullptr, dynamic_cast(ctx.maybe_trans_table())); } TEST(ConfigureTtApiTest, SwitchKindRecreatesTable) { - // Default context, isolated from any ambient override (override behavior - // is covered by the Environment* tests). - ScopedEnv no_override("DDS_TT_KIND", nullptr); - SolverContext ctx; - auto* tt1 = ctx.trans_table(); - ASSERT_NE(tt1, nullptr); - - // Flip to a different kind - const TTKind new_kind = kind_of(tt1) == TTKind::Small ? TTKind::Large : TTKind::Small; - ctx.configure_tt(new_kind, /*defMB=*/8, /*maxMB=*/8); - - auto* tt2 = ctx.maybe_trans_table(); - ASSERT_NE(tt2, nullptr); - EXPECT_EQ(kind_of(tt2), new_kind); + // Default context, isolated from any ambient override (override behavior + // is covered by the Environment* tests). + ScopedEnv no_override("DDS_TT_KIND", nullptr); + SolverContext ctx; + auto* tt1 = ctx.trans_table(); + ASSERT_NE(tt1, nullptr); + + // Flip to a different kind + const TTKind new_kind = kind_of(tt1) == TTKind::Small ? TTKind::Large : TTKind::Small; + ctx.configure_tt(new_kind, /*defMB=*/8, /*maxMB=*/8); + + auto* tt2 = ctx.maybe_trans_table(); + ASSERT_NE(tt2, nullptr); + EXPECT_EQ(kind_of(tt2), new_kind); } TEST(ConfigureTtApiTest, ResizeInPlaceWhenKindUnchanged) { - SolverContext ctx; - auto* tt1 = ctx.trans_table(); - ASSERT_NE(tt1, nullptr); - const TTKind same_kind = kind_of(tt1); - - // Resize should not replace the instance when kind does not change - ctx.configure_tt(same_kind, /*defMB=*/16, /*maxMB=*/32); - auto* tt2 = ctx.maybe_trans_table(); - ASSERT_NE(tt2, nullptr); - EXPECT_EQ(tt1, tt2) << "Resize should keep the same TT instance"; + SolverContext ctx; + auto* tt1 = ctx.trans_table(); + ASSERT_NE(tt1, nullptr); + const TTKind same_kind = kind_of(tt1); + + // Resize should not replace the instance when kind does not change + ctx.configure_tt(same_kind, /*defMB=*/16, /*maxMB=*/32); + auto* tt2 = ctx.maybe_trans_table(); + ASSERT_NE(tt2, nullptr); + EXPECT_EQ(tt1, tt2) << "Resize should keep the same TT instance"; } } // namespace diff --git a/library/tests/system/context_equivalence_test.cpp b/library/tests/system/context_equivalence_test.cpp index fe586b29f..48d95c4c4 100644 --- a/library/tests/system/context_equivalence_test.cpp +++ b/library/tests/system/context_equivalence_test.cpp @@ -17,126 +17,126 @@ extern Memory memory; static Deal make_empty_deal() { - Deal dl{}; - dl.trump = 0; - dl.first = 0; - std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); - std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); - std::memset(dl.remainCards, 0, sizeof(dl.remainCards)); - return dl; + Deal dl{}; + dl.trump = 0; + dl.first = 0; + std::memset(dl.currentTrickSuit, 0, sizeof(dl.currentTrickSuit)); + std::memset(dl.currentTrickRank, 0, sizeof(dl.currentTrickRank)); + std::memset(dl.remainCards, 0, sizeof(dl.remainCards)); + return dl; } // Known deal from examples/hands.cpp (hand 0) // PBN: N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3 static DdTableDeal make_known_deal() { - DdTableDeal deal{}; - // Construct using the same rank bitmasks as calc_par_test.cpp. - // North: S=QJ6, H=K652, D=J85, C=T98 - deal.cards[0][0] = 0x1800 | 0x0040; // Spades: Q J 6 - deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; // Hearts: K 6 5 2 - deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; // Diamonds: J 8 5 - deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; // Clubs: T 9 8 - // East: S=873, H=J97, D=AT764, C=Q4 - deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; // Spades: 8 7 3 - deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; // Hearts: J 9 7 - deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; // Diamonds: A T 7 6 4 - deal.cards[1][3] = 0x1000 | 0x0010; // Clubs: Q 4 - // South: S=K5, H=T83, D=KQ9, C=A7652 - deal.cards[2][0] = 0x2000 | 0x0020; // Spades: K 5 - deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; // Hearts: T 8 3 - deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; // Diamonds: K Q 9 - deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; // Clubs: A 7 6 5 2 - // West: S=AT942, H=AQ4, D=32, C=KJ3 - deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; // Spades: A T 9 4 2 - deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; // Hearts: A Q 4 - deal.cards[3][2] = 0x0008 | 0x0004; // Diamonds: 3 2 - deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; // Clubs: K J 3 - return deal; + DdTableDeal deal{}; + // Construct using the same rank bitmasks as calc_par_test.cpp. + // North: S=QJ6, H=K652, D=J85, C=T98 + deal.cards[0][0] = 0x1800 | 0x0040; // Spades: Q J 6 + deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; // Hearts: K 6 5 2 + deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; // Diamonds: J 8 5 + deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; // Clubs: T 9 8 + // East: S=873, H=J97, D=AT764, C=Q4 + deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; // Spades: 8 7 3 + deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; // Hearts: J 9 7 + deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; // Diamonds: A T 7 6 4 + deal.cards[1][3] = 0x1000 | 0x0010; // Clubs: Q 4 + // South: S=K5, H=T83, D=KQ9, C=A7652 + deal.cards[2][0] = 0x2000 | 0x0020; // Spades: K 5 + deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; // Hearts: T 8 3 + deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; // Diamonds: K Q 9 + deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; // Clubs: A 7 6 5 2 + // West: S=AT942, H=AQ4, D=32, C=KJ3 + deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; // Spades: A T 9 4 2 + deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; // Hearts: A Q 4 + deal.cards[3][2] = 0x0008 | 0x0004; // Diamonds: 3 2 + deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; // Clubs: K J 3 + return deal; } TEST(SystemContextEquivalence, LegacyVsContextReturnCode) { - // Ensure DDS system and thread-local memory are initialized - InitializeStaticMemory(); - const int thr = 0; - FutureTricks ft_legacy{}; - FutureTricks ft_ctx{}; - Deal dl = make_empty_deal(); + // Ensure DDS system and thread-local memory are initialized + InitializeStaticMemory(); + const int thr = 0; + FutureTricks ft_legacy{}; + FutureTricks ft_ctx{}; + Deal dl = make_empty_deal(); - const int r_legacy = SolveBoard(dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft_legacy, thr); + const int r_legacy = SolveBoard(dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft_legacy, thr); - // Construct a SolverContext-owned ThreadData for the context-based call. - SolverContext ctx; - const int r_ctx = solve_board(ctx, dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft_ctx); + // Construct a SolverContext-owned ThreadData for the context-based call. + SolverContext ctx; + const int r_ctx = solve_board(ctx, dl, /*target=*/0, /*solutions=*/1, /*mode=*/0, &ft_ctx); - EXPECT_EQ(r_legacy, r_ctx) << "Legacy and context return codes should match"; + EXPECT_EQ(r_legacy, r_ctx) << "Legacy and context return codes should match"; } // Verify calc_dd_table context overload produces same results as non-context overload TEST(SystemContextEquivalence, CalcDDTableContextVsNonContext) { - DdTableDeal deal = make_known_deal(); - - DdTableResults table_no_ctx{}; - int res1 = calc_dd_table(deal, &table_no_ctx); - ASSERT_EQ(res1, RETURN_NO_FAULT) << "Non-context calc_dd_table failed"; - - SolverContext ctx; - DdTableResults table_with_ctx{}; - int res2 = calc_dd_table(ctx, deal, &table_with_ctx); - ASSERT_EQ(res2, RETURN_NO_FAULT) << "Context calc_dd_table failed"; - - // All trick counts must be identical - for (int strain = 0; strain < DDS_STRAINS; strain++) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - EXPECT_EQ(table_no_ctx.res_table[strain][hand], - table_with_ctx.res_table[strain][hand]) - << "Mismatch at strain=" << strain << " hand=" << hand; + DdTableDeal deal = make_known_deal(); + + DdTableResults table_no_ctx{}; + int res1 = calc_dd_table(deal, &table_no_ctx); + ASSERT_EQ(res1, RETURN_NO_FAULT) << "Non-context calc_dd_table failed"; + + SolverContext ctx; + DdTableResults table_with_ctx{}; + int res2 = calc_dd_table(ctx, deal, &table_with_ctx); + ASSERT_EQ(res2, RETURN_NO_FAULT) << "Context calc_dd_table failed"; + + // All trick counts must be identical + for (int strain = 0; strain < DDS_STRAINS; strain++) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + EXPECT_EQ(table_no_ctx.res_table[strain][hand], + table_with_ctx.res_table[strain][hand]) + << "Mismatch at strain=" << strain << " hand=" << hand; + } } - } } // Verify context reuse across multiple calc_dd_table calls produces consistent results TEST(SystemContextEquivalence, CalcDDTableContextReuse) { - DdTableDeal deal = make_known_deal(); - SolverContext ctx; - - DdTableResults first_result{}; - ASSERT_EQ(calc_dd_table(ctx, deal, &first_result), RETURN_NO_FAULT); - - // Call again with same context — must produce identical results - DdTableResults second_result{}; - ASSERT_EQ(calc_dd_table(ctx, deal, &second_result), RETURN_NO_FAULT); - - for (int strain = 0; strain < DDS_STRAINS; strain++) { - for (int hand = 0; hand < DDS_HANDS; hand++) { - EXPECT_EQ(first_result.res_table[strain][hand], - second_result.res_table[strain][hand]) - << "Context reuse changed result at strain=" << strain << " hand=" << hand; + DdTableDeal deal = make_known_deal(); + SolverContext ctx; + + DdTableResults first_result{}; + ASSERT_EQ(calc_dd_table(ctx, deal, &first_result), RETURN_NO_FAULT); + + // Call again with same context — must produce identical results + DdTableResults second_result{}; + ASSERT_EQ(calc_dd_table(ctx, deal, &second_result), RETURN_NO_FAULT); + + for (int strain = 0; strain < DDS_STRAINS; strain++) { + for (int hand = 0; hand < DDS_HANDS; hand++) { + EXPECT_EQ(first_result.res_table[strain][hand], + second_result.res_table[strain][hand]) + << "Context reuse changed result at strain=" << strain << " hand=" << hand; + } } - } } // Verify calc_par context overload produces same results as non-context overload TEST(SystemContextEquivalence, CalcParContextVsNonContext) { - DdTableDeal deal = make_known_deal(); - - DdTableResults table_no_ctx{}; - ParResults par_no_ctx{}; - int res1 = calc_par(deal, /*vulnerable=*/0, &table_no_ctx, &par_no_ctx); - ASSERT_EQ(res1, RETURN_NO_FAULT) << "Non-context calc_par failed"; - - SolverContext ctx; - DdTableResults table_with_ctx{}; - ParResults par_with_ctx{}; - int res2 = calc_par(ctx, deal, /*vulnerable=*/0, &table_with_ctx, &par_with_ctx); - ASSERT_EQ(res2, RETURN_NO_FAULT) << "Context calc_par failed"; - - EXPECT_STREQ(par_no_ctx.par_score[0], par_with_ctx.par_score[0]) - << "NS par scores differ between context and non-context paths"; - EXPECT_STREQ(par_no_ctx.par_score[1], par_with_ctx.par_score[1]) - << "EW par scores differ between context and non-context paths"; + DdTableDeal deal = make_known_deal(); + + DdTableResults table_no_ctx{}; + ParResults par_no_ctx{}; + int res1 = calc_par(deal, /*vulnerable=*/0, &table_no_ctx, &par_no_ctx); + ASSERT_EQ(res1, RETURN_NO_FAULT) << "Non-context calc_par failed"; + + SolverContext ctx; + DdTableResults table_with_ctx{}; + ParResults par_with_ctx{}; + int res2 = calc_par(ctx, deal, /*vulnerable=*/0, &table_with_ctx, &par_with_ctx); + ASSERT_EQ(res2, RETURN_NO_FAULT) << "Context calc_par failed"; + + EXPECT_STREQ(par_no_ctx.par_score[0], par_with_ctx.par_score[0]) + << "NS par scores differ between context and non-context paths"; + EXPECT_STREQ(par_no_ctx.par_score[1], par_with_ctx.par_score[1]) + << "EW par scores differ between context and non-context paths"; } diff --git a/library/tests/system/context_tt_facade_test.cpp b/library/tests/system/context_tt_facade_test.cpp index 0b6f9facc..071e51526 100644 --- a/library/tests/system/context_tt_facade_test.cpp +++ b/library/tests/system/context_tt_facade_test.cpp @@ -15,89 +15,89 @@ extern Memory memory; TEST(SystemContextTTFacades, ResetAndResizeAreNoopsWithoutTT) { - InitializeStaticMemory(); - // Some environments may compute 0 allowable threads (e.g., macOS sandbox), - // so ensure we have at least one thread allocated for the test. - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - // Create a context that owns its ThreadData for this test. - SolverContext ctx; - // Ensure no TT yet (construction is lazy until first use) - ASSERT_EQ(nullptr, ctx.maybe_trans_table()); - // Should not crash and should not create TT - ctx.reset_for_solve(); - ctx.clear_tt(); - ctx.resize_tt(8, 16); - - EXPECT_EQ(nullptr, ctx.maybe_trans_table()); + InitializeStaticMemory(); + // Some environments may compute 0 allowable threads (e.g., macOS sandbox), + // so ensure we have at least one thread allocated for the test. + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + // Create a context that owns its ThreadData for this test. + SolverContext ctx; + // Ensure no TT yet (construction is lazy until first use) + ASSERT_EQ(nullptr, ctx.maybe_trans_table()); + // Should not crash and should not create TT + ctx.reset_for_solve(); + ctx.clear_tt(); + ctx.resize_tt(8, 16); + + EXPECT_EQ(nullptr, ctx.maybe_trans_table()); } TEST(SystemContextTTFacades, ResizeCreatesWhenExisting) { - InitializeStaticMemory(); - // Ensure at least one thread exists; fall back to a small thread config. - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - // Use owned context for the test - SolverContext ctx; - // Force create via trans_table() - auto* tt = ctx.trans_table(); - ASSERT_NE(nullptr, tt); - - // Resize should apply immediately and keep TT alive - ctx.resize_tt(8, 16); - EXPECT_NE(nullptr, ctx.maybe_trans_table()); + InitializeStaticMemory(); + // Ensure at least one thread exists; fall back to a small thread config. + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + // Use owned context for the test + SolverContext ctx; + // Force create via trans_table() + auto* tt = ctx.trans_table(); + ASSERT_NE(nullptr, tt); + + // Resize should apply immediately and keep TT alive + ctx.resize_tt(8, 16); + EXPECT_NE(nullptr, ctx.maybe_trans_table()); } TEST(SystemContextTTFacades, Lifecycle_LookupAddClearDispose) { - InitializeStaticMemory(); - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - - SolverContext ctx; - - // Create TT and perform an initial lookup (expect miss) - auto* tt = ctx.trans_table(); - ASSERT_NE(nullptr, tt); - - // Ensure TT internal roots are initialized before Lookup/Add for the test. - // Production resets happen in SolverIF around new deals/trumps. - ctx.reset_for_solve(); - - // Minimal initialization for TT internals (aggr tables) - int handLookup[DDS_SUITS][15] = {}; - // Leave all zeros (map ranks to North=0) which is sufficient for basic TT wiring - tt->init(handLookup); - - const int trick = 11; // any valid trick index in [1..11] per implementation - const int hand = 0; // North - unsigned short aggrTarget[DDS_HANDS] = {0, 0, 0, 0}; - int hand_dist[DDS_HANDS] = {0, 0, 0, 0}; // 0 spades/hearts/diamonds; clubs inferred - bool lowerFlag = false; - - // Miss before any Add - auto* missNode = tt->lookup(trick, hand, aggrTarget, hand_dist, /*limit*/0, lowerFlag); - EXPECT_EQ(nullptr, missNode); - - // Add a minimal node for the same suit distribution so subsequent Lookup hits - NodeCards first{}; - first.lower_bound = 0; - first.upper_bound = 0; - first.best_move_suit = 0; - first.best_move_rank = 0; - std::memset(first.least_win, 0, sizeof(first.least_win)); - - unsigned short ourWinRanks[DDS_HANDS] = {0, 0, 0, 0}; - tt->add(trick, hand, aggrTarget, ourWinRanks, first, /*flag*/false); - - // Hit now (bounds allow returning the stored node) - auto* hitNode = tt->lookup(trick, hand, aggrTarget, hand_dist, /*limit*/0, lowerFlag); - ASSERT_NE(nullptr, hitNode); - EXPECT_EQ(0, static_cast(hitNode->lower_bound)); - EXPECT_EQ(0, static_cast(hitNode->upper_bound)); - - // Dispose destroys the TT instance from the registry - ctx.dispose_trans_table(); - EXPECT_EQ(nullptr, ctx.maybe_trans_table()); + InitializeStaticMemory(); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + + SolverContext ctx; + + // Create TT and perform an initial lookup (expect miss) + auto* tt = ctx.trans_table(); + ASSERT_NE(nullptr, tt); + + // Ensure TT internal roots are initialized before Lookup/Add for the test. + // Production resets happen in SolverIF around new deals/trumps. + ctx.reset_for_solve(); + + // Minimal initialization for TT internals (aggr tables) + int handLookup[DDS_SUITS][15] = {}; + // Leave all zeros (map ranks to North=0) which is sufficient for basic TT wiring + tt->init(handLookup); + + const int trick = 11; // any valid trick index in [1..11] per implementation + const int hand = 0; // North + unsigned short aggrTarget[DDS_HANDS] = {0, 0, 0, 0}; + int hand_dist[DDS_HANDS] = {0, 0, 0, 0}; // 0 spades/hearts/diamonds; clubs inferred + bool lowerFlag = false; + + // Miss before any Add + auto* missNode = tt->lookup(trick, hand, aggrTarget, hand_dist, /*limit*/0, lowerFlag); + EXPECT_EQ(nullptr, missNode); + + // Add a minimal node for the same suit distribution so subsequent Lookup hits + NodeCards first{}; + first.lower_bound = 0; + first.upper_bound = 0; + first.best_move_suit = 0; + first.best_move_rank = 0; + std::memset(first.least_win, 0, sizeof(first.least_win)); + + unsigned short ourWinRanks[DDS_HANDS] = {0, 0, 0, 0}; + tt->add(trick, hand, aggrTarget, ourWinRanks, first, /*flag*/false); + + // Hit now (bounds allow returning the stored node) + auto* hitNode = tt->lookup(trick, hand, aggrTarget, hand_dist, /*limit*/0, lowerFlag); + ASSERT_NE(nullptr, hitNode); + EXPECT_EQ(0, static_cast(hitNode->lower_bound)); + EXPECT_EQ(0, static_cast(hitNode->upper_bound)); + + // Dispose destroys the TT instance from the registry + ctx.dispose_trans_table(); + EXPECT_EQ(nullptr, ctx.maybe_trans_table()); } diff --git a/library/tests/system/deal_fanout_test.cpp b/library/tests/system/deal_fanout_test.cpp index a185a9b5b..550bcd217 100644 --- a/library/tests/system/deal_fanout_test.cpp +++ b/library/tests/system/deal_fanout_test.cpp @@ -14,67 +14,67 @@ namespace /// Pack suit holding bits into the remainCards encoding (aggregate << 2). auto holding(const unsigned aggregate) -> unsigned { - return aggregate << 2; + return aggregate << 2; } auto empty_deal() -> Deal { - Deal dl{}; - return dl; + Deal dl{}; + return dl; } } // namespace TEST(DealFanout, EmptyDealIsZero) { - // Arrange - const Deal dl = empty_deal(); + // Arrange + const Deal dl = empty_deal(); - // Act / Assert - EXPECT_EQ(dds::internal::deal_fanout(dl), 0); + // Act / Assert + EXPECT_EQ(dds::internal::deal_fanout(dl), 0); } TEST(DealFanout, SingleCardCountsAsOneGroupWithVoidBonus) { - // Arrange: North holds only the deuce of spades; all other holdings empty. - // That hand: 1 group + 3 voids * 1 = 4. Other hands: all voids → 0. - Deal dl = empty_deal(); - dl.remainCards[0][0] = holding(bit_map_rank[2]); + // Arrange: North holds only the deuce of spades; all other holdings empty. + // That hand: 1 group + 3 voids * 1 = 4. Other hands: all voids → 0. + Deal dl = empty_deal(); + dl.remainCards[0][0] = holding(bit_map_rank[2]); - // Act / Assert - EXPECT_EQ(dds::internal::deal_fanout(dl), 4); + // Act / Assert + EXPECT_EQ(dds::internal::deal_fanout(dl), 4); } TEST(DealFanout, ConsecutiveRanksAreOneGroup) { - // Arrange: North holds KT982 of spades (K | T98 | 2 → 3 groups). - // That hand: 3 groups + 3 voids * 3 = 12. Other hands: 0. - const unsigned kt982 = - bit_map_rank[13] | // K - bit_map_rank[10] | // T - bit_map_rank[9] | // 9 - bit_map_rank[8] | // 8 - bit_map_rank[2]; // 2 - - Deal dl = empty_deal(); - dl.remainCards[0][0] = holding(kt982); - - // Act / Assert - EXPECT_EQ(group_data[kt982].last_group_ + 1, 3); - EXPECT_EQ(dds::internal::deal_fanout(dl), 12); + // Arrange: North holds KT982 of spades (K | T98 | 2 → 3 groups). + // That hand: 3 groups + 3 voids * 3 = 12. Other hands: 0. + const unsigned kt982 = + bit_map_rank[13] | // K + bit_map_rank[10] | // T + bit_map_rank[9] | // 9 + bit_map_rank[8] | // 8 + bit_map_rank[2]; // 2 + + Deal dl = empty_deal(); + dl.remainCards[0][0] = holding(kt982); + + // Act / Assert + EXPECT_EQ(group_data[kt982].last_group_ + 1, 3); + EXPECT_EQ(dds::internal::deal_fanout(dl), 12); } TEST(DealFanout, SolidSuitIsOneGroup) { - // Arrange: North holds AKQ of hearts (one consecutive group), voids elsewhere. - // That hand: 1 + 3*1 = 4. - const unsigned akq = - bit_map_rank[14] | bit_map_rank[13] | bit_map_rank[12]; + // Arrange: North holds AKQ of hearts (one consecutive group), voids elsewhere. + // That hand: 1 + 3*1 = 4. + const unsigned akq = + bit_map_rank[14] | bit_map_rank[13] | bit_map_rank[12]; - Deal dl = empty_deal(); - dl.remainCards[0][1] = holding(akq); + Deal dl = empty_deal(); + dl.remainCards[0][1] = holding(akq); - // Act / Assert - EXPECT_EQ(group_data[akq].last_group_ + 1, 1); - EXPECT_EQ(dds::internal::deal_fanout(dl), 4); + // Act / Assert + EXPECT_EQ(group_data[akq].last_group_ + 1, 1); + EXPECT_EQ(dds::internal::deal_fanout(dl), 4); } diff --git a/library/tests/system/max_threads_equivalence_test.cpp b/library/tests/system/max_threads_equivalence_test.cpp index bee356caf..d7e77c002 100644 --- a/library/tests/system/max_threads_equivalence_test.cpp +++ b/library/tests/system/max_threads_equivalence_test.cpp @@ -16,32 +16,32 @@ namespace // PBN: N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3 DdTableDeal make_known_deal() { - DdTableDeal deal{}; - deal.cards[0][0] = 0x1800 | 0x0040; - deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; - deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; - deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; - deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; - deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; - deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; - deal.cards[1][3] = 0x1000 | 0x0010; - deal.cards[2][0] = 0x2000 | 0x0020; - deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; - deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; - deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; - deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; - deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; - deal.cards[3][2] = 0x0008 | 0x0004; - deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; - return deal; + DdTableDeal deal{}; + deal.cards[0][0] = 0x1800 | 0x0040; + deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; + deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; + deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; + deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; + deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; + deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; + deal.cards[1][3] = 0x1000 | 0x0010; + deal.cards[2][0] = 0x2000 | 0x0020; + deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; + deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; + deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; + deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; + deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; + deal.cards[3][2] = 0x0008 | 0x0004; + deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; + return deal; } void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) { - for (int strain = 0; strain < DDS_STRAINS; strain++) - for (int hand = 0; hand < DDS_HANDS; hand++) - EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) - << "Mismatch at strain=" << strain << " hand=" << hand; + for (int strain = 0; strain < DDS_STRAINS; strain++) + for (int hand = 0; hand < DDS_HANDS; hand++) + EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) + << "Mismatch at strain=" << strain << " hand=" << hand; } } // namespace @@ -49,84 +49,84 @@ void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) // CalcDDtableN with maxThreads=1 must match the auto CalcDDtable. TEST(MaxThreadsEquivalence, CalcDDtableNMatchesAuto) { - InitializeStaticMemory(); - DdTableDeal deal = make_known_deal(); - - DdTableResults table_auto{}; - ASSERT_EQ(CalcDDtable(deal, &table_auto), RETURN_NO_FAULT); - - DdTableResults table_one{}; - ASSERT_EQ(CalcDDtableN(deal, &table_one, /*maxThreads=*/1), RETURN_NO_FAULT); - expect_tables_equal(table_auto, table_one); - - if (std::thread::hardware_concurrency() > 2) - { - DdTableResults table_two{}; - ASSERT_EQ(CalcDDtableN(deal, &table_two, /*maxThreads=*/2), RETURN_NO_FAULT); - expect_tables_equal(table_auto, table_two); - } + InitializeStaticMemory(); + DdTableDeal deal = make_known_deal(); + + DdTableResults table_auto{}; + ASSERT_EQ(CalcDDtable(deal, &table_auto), RETURN_NO_FAULT); + + DdTableResults table_one{}; + ASSERT_EQ(CalcDDtableN(deal, &table_one, /*maxThreads=*/1), RETURN_NO_FAULT); + expect_tables_equal(table_auto, table_one); + + if (std::thread::hardware_concurrency() > 2) + { + DdTableResults table_two{}; + ASSERT_EQ(CalcDDtableN(deal, &table_two, /*maxThreads=*/2), RETURN_NO_FAULT); + expect_tables_equal(table_auto, table_two); + } } // SolveAllBoardsBinN with maxThreads=1 must match the auto SolveAllBoardsBin. TEST(MaxThreadsEquivalence, SolveAllBoardsBinNMatchesAuto) { - InitializeStaticMemory(); - DdTableDeal table_deal = make_known_deal(); - - // Solve all five strains as separate boards. - Boards bo{}; - bo.no_of_boards = DDS_STRAINS; - for (int tr = 0; tr < DDS_STRAINS; tr++) - { - Deal dl{}; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = table_deal.cards[h][s]; - dl.trump = tr; - dl.first = 0; - bo.deals[tr] = dl; - bo.target[tr] = -1; - bo.solutions[tr] = 1; - bo.mode[tr] = 1; - } - - SolvedBoards solved_auto{}; - ASSERT_EQ(SolveAllBoardsBin(&bo, &solved_auto), RETURN_NO_FAULT); - - SolvedBoards solved_one{}; - ASSERT_EQ(SolveAllBoardsBinN(&bo, &solved_one, /*maxThreads=*/1), RETURN_NO_FAULT); - - ASSERT_EQ(solved_auto.no_of_boards, solved_one.no_of_boards); - for (int b = 0; b < solved_auto.no_of_boards; b++) - { - const FutureTricks& fa = solved_auto.solved_board[b]; - const FutureTricks& fo = solved_one.solved_board[b]; - ASSERT_EQ(fa.cards, fo.cards) << "card count differs at board=" << b; - // Only the first `cards` entries are meaningful; the tail is uninitialized. - for (int c = 0; c < fa.cards; c++) + InitializeStaticMemory(); + DdTableDeal table_deal = make_known_deal(); + + // Solve all five strains as separate boards. + Boards bo{}; + bo.no_of_boards = DDS_STRAINS; + for (int tr = 0; tr < DDS_STRAINS; tr++) + { + Deal dl{}; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = table_deal.cards[h][s]; + dl.trump = tr; + dl.first = 0; + bo.deals[tr] = dl; + bo.target[tr] = -1; + bo.solutions[tr] = 1; + bo.mode[tr] = 1; + } + + SolvedBoards solved_auto{}; + ASSERT_EQ(SolveAllBoardsBin(&bo, &solved_auto), RETURN_NO_FAULT); + + SolvedBoards solved_one{}; + ASSERT_EQ(SolveAllBoardsBinN(&bo, &solved_one, /*maxThreads=*/1), RETURN_NO_FAULT); + + ASSERT_EQ(solved_auto.no_of_boards, solved_one.no_of_boards); + for (int b = 0; b < solved_auto.no_of_boards; b++) { - EXPECT_EQ(fa.suit[c], fo.suit[c]) << "suit at board=" << b << " c=" << c; - EXPECT_EQ(fa.rank[c], fo.rank[c]) << "rank at board=" << b << " c=" << c; - EXPECT_EQ(fa.equals[c], fo.equals[c]) << "equals at board=" << b << " c=" << c; - EXPECT_EQ(fa.score[c], fo.score[c]) << "score at board=" << b << " c=" << c; + const FutureTricks& fa = solved_auto.solved_board[b]; + const FutureTricks& fo = solved_one.solved_board[b]; + ASSERT_EQ(fa.cards, fo.cards) << "card count differs at board=" << b; + // Only the first `cards` entries are meaningful; the tail is uninitialized. + for (int c = 0; c < fa.cards; c++) + { + EXPECT_EQ(fa.suit[c], fo.suit[c]) << "suit at board=" << b << " c=" << c; + EXPECT_EQ(fa.rank[c], fo.rank[c]) << "rank at board=" << b << " c=" << c; + EXPECT_EQ(fa.equals[c], fo.equals[c]) << "equals at board=" << b << " c=" << c; + EXPECT_EQ(fa.score[c], fo.score[c]) << "score at board=" << b << " c=" << c; + } } - } } // InitializeStaticMemory leaves the library usable for a subsequent solve. TEST(MaxThreadsEquivalence, InitializeStaticMemoryThenSolve) { - InitializeStaticMemory(); - DdTableDeal deal = make_known_deal(); - DdTableResults table{}; - EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); + InitializeStaticMemory(); + DdTableDeal deal = make_known_deal(); + DdTableResults table{}; + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); } // The deprecated SetMaxThreads alias still initializes the library. TEST(MaxThreadsEquivalence, DeprecatedSetMaxThreadsAliasStillWorks) { - SetMaxThreads(1); - DdTableDeal deal = make_known_deal(); - DdTableResults table{}; - EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); + SetMaxThreads(1); + DdTableDeal deal = make_known_deal(); + DdTableResults table{}; + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); } diff --git a/library/tests/system/parallel_boards_test.cpp b/library/tests/system/parallel_boards_test.cpp index c5571ae36..89a9c6b42 100644 --- a/library/tests/system/parallel_boards_test.cpp +++ b/library/tests/system/parallel_boards_test.cpp @@ -46,24 +46,24 @@ std::atomic allocations{0}; void* operator new(const std::size_t size) { - if (allocation_tracking::enabled.load(std::memory_order_relaxed)) - allocation_tracking::allocations.fetch_add(1, std::memory_order_relaxed); - - // C++ requires successful zero-size allocations to return non-null; - // malloc(0) is allowed to return nullptr, so request at least one byte. - if (void* const memory = std::malloc(size == 0 ? 1 : size)) - return memory; - throw std::bad_alloc(); + if (allocation_tracking::enabled.load(std::memory_order_relaxed)) + allocation_tracking::allocations.fetch_add(1, std::memory_order_relaxed); + + // C++ requires successful zero-size allocations to return non-null; + // malloc(0) is allowed to return nullptr, so request at least one byte. + if (void* const memory = std::malloc(size == 0 ? 1 : size)) + return memory; + throw std::bad_alloc(); } void operator delete(void* const memory) noexcept { - std::free(memory); + std::free(memory); } void operator delete(void* const memory, std::size_t) noexcept { - std::free(memory); + std::free(memory); } #endif // !DDS_TEST_MEMORY_SANITIZER @@ -72,428 +72,428 @@ namespace { auto dispatched_boards( - const int count, - const std::vector& order) -> std::vector + const int count, + const std::vector& order) -> std::vector { - std::vector boards; - boards.reserve(static_cast(count)); - const std::function process_board = - [&](const int, const int board) { - boards.push_back(board); - return RETURN_NO_FAULT; - }; - - const int result = - parallel_all_boards_n(count, 1, process_board, &order); - - EXPECT_EQ(result, RETURN_NO_FAULT); - return boards; + std::vector boards; + boards.reserve(static_cast(count)); + const std::function process_board = + [&](const int, const int board) { + boards.push_back(board); + return RETURN_NO_FAULT; + }; + + const int result = + parallel_all_boards_n(count, 1, process_board, &order); + + EXPECT_EQ(result, RETURN_NO_FAULT); + return boards; } } // namespace TEST(ParallelAllBoards, ValidPermutationControlsDispatchOrder) { - // Arrange - const std::vector order{3, 1, 0, 2}; + // Arrange + const std::vector order{3, 1, 0, 2}; - // Act - const std::vector boards = dispatched_boards(4, order); + // Act + const std::vector boards = dispatched_boards(4, order); - // Assert - EXPECT_EQ(boards, order); + // Assert + EXPECT_EQ(boards, order); } TEST(ParallelAllBoards, DuplicateOrderFallsBackToIndexOrder) { - // Arrange - const std::vector order{0, 1, 1, 3}; + // Arrange + const std::vector order{0, 1, 1, 3}; - // Act - const std::vector boards = dispatched_boards(4, order); + // Act + const std::vector boards = dispatched_boards(4, order); - // Assert - EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); + // Assert + EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); } TEST(ParallelAllBoards, OutOfRangeOrderFallsBackToIndexOrder) { - // Arrange - const std::vector order{-1, 1, 2, 4}; + // Arrange + const std::vector order{-1, 1, 2, 4}; - // Act - const std::vector boards = dispatched_boards(4, order); + // Act + const std::vector boards = dispatched_boards(4, order); - // Assert - EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); + // Assert + EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); } TEST(ParallelAllBoards, WrongSizedOrderFallsBackToIndexOrder) { - // Arrange: valid values, but length does not match count. - const std::vector order{3, 1, 0}; + // Arrange: valid values, but length does not match count. + const std::vector order{3, 1, 0}; - // Act - const std::vector boards = dispatched_boards(4, order); + // Act + const std::vector boards = dispatched_boards(4, order); - // Assert - EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); + // Assert + EXPECT_EQ(boards, (std::vector{0, 1, 2, 3})); } TEST(ParallelAllBoards, PermutationValidationUsesAtMostOneAllocation) { #if DDS_TEST_MEMORY_SANITIZER - GTEST_SKIP() << "Allocation counting needs custom operator new; disabled under MSAN"; + GTEST_SKIP() << "Allocation counting needs custom operator new; disabled under MSAN"; #else - // Permutation checks may allocate a temporary "seen" bitmap; the board-slot - // mapping itself must stay allocation-free (plain data, not std::function). - // Arrange - const std::vector order{3, 1, 0, 2}; - std::vector boards; - boards.reserve(order.size()); - const std::function process_board = - [&](const int, const int board) { - boards.push_back(board); - return RETURN_NO_FAULT; - }; - allocation_tracking::allocations.store(0, std::memory_order_relaxed); - - // Act - allocation_tracking::enabled.store(true, std::memory_order_relaxed); - const int result = - parallel_all_boards_n(4, 1, process_board, &order); - allocation_tracking::enabled.store(false, std::memory_order_relaxed); - - // Assert - EXPECT_EQ(result, RETURN_NO_FAULT); - EXPECT_LE( - allocation_tracking::allocations.load(std::memory_order_relaxed), 1u); + // Permutation checks may allocate a temporary "seen" bitmap; the board-slot + // mapping itself must stay allocation-free (plain data, not std::function). + // Arrange + const std::vector order{3, 1, 0, 2}; + std::vector boards; + boards.reserve(order.size()); + const std::function process_board = + [&](const int, const int board) { + boards.push_back(board); + return RETURN_NO_FAULT; + }; + allocation_tracking::allocations.store(0, std::memory_order_relaxed); + + // Act + allocation_tracking::enabled.store(true, std::memory_order_relaxed); + const int result = + parallel_all_boards_n(4, 1, process_board, &order); + allocation_tracking::enabled.store(false, std::memory_order_relaxed); + + // Assert + EXPECT_EQ(result, RETURN_NO_FAULT); + EXPECT_LE( + allocation_tracking::allocations.load(std::memory_order_relaxed), 1u); #endif } TEST(ParallelAllBoards, ZeroSizeNewReturnsNonNull) { - // C++ requires a successful zero-size allocation to return a distinct - // non-null pointer; malloc(0) is allowed to return nullptr. - void* const memory = ::operator new(0); - EXPECT_NE(memory, nullptr); - ::operator delete(memory); + // C++ requires a successful zero-size allocation to return a distinct + // non-null pointer; malloc(0) is allowed to return nullptr. + void* const memory = ::operator new(0); + EXPECT_NE(memory, nullptr); + ::operator delete(memory); } TEST(ParallelAllBoards, MultiWorkerProcessesEachBoardOnce) { - // Arrange - constexpr int count = 32; - constexpr int workers = 4; - std::vector> hits(static_cast(count)); - for (auto& h : hits) - h.store(0, std::memory_order_relaxed); - - // Act - const int result = parallel_all_boards_n( - count, - workers, - [&](const int worker_id, const int bno) -> int { - // EXPECT does not abort; guard before indexing so a bad bno fails the - // test cleanly instead of crashing with an out-of-range access. - if (worker_id < 0 || worker_id >= workers || bno < 0 || bno >= count) - { - ADD_FAILURE() << "Invalid dispatch: worker_id=" << worker_id - << " bno=" << bno; - return RETURN_UNKNOWN_FAULT; - } - hits[static_cast(bno)].fetch_add(1, std::memory_order_relaxed); - return RETURN_NO_FAULT; - }); + // Arrange + constexpr int count = 32; + constexpr int workers = 4; + std::vector> hits(static_cast(count)); + for (auto& h : hits) + h.store(0, std::memory_order_relaxed); + + // Act + const int result = parallel_all_boards_n( + count, + workers, + [&](const int worker_id, const int bno) -> int { + // EXPECT does not abort; guard before indexing so a bad bno fails the + // test cleanly instead of crashing with an out-of-range access. + if (worker_id < 0 || worker_id >= workers || bno < 0 || bno >= count) + { + ADD_FAILURE() << "Invalid dispatch: worker_id=" << worker_id + << " bno=" << bno; + return RETURN_UNKNOWN_FAULT; + } + hits[static_cast(bno)].fetch_add(1, std::memory_order_relaxed); + return RETURN_NO_FAULT; + }); - // Assert - EXPECT_EQ(result, RETURN_NO_FAULT); - for (int i = 0; i < count; ++i) - EXPECT_EQ(hits[static_cast(i)].load(), 1) << "board " << i; + // Assert + EXPECT_EQ(result, RETURN_NO_FAULT); + for (int i = 0; i < count; ++i) + EXPECT_EQ(hits[static_cast(i)].load(), 1) << "board " << i; } TEST(ParallelAllBoards, CallerSeesNonAtomicWorkerWritesAfterReturn) { - // Completion must establish happens-before from each worker's process_board - // stores into the caller's buffers before parallel_all_boards_n returns. - // Plain (non-atomic) writes make missing mutex/condvar sync visible to TSan. - constexpr int count = 64; - constexpr int workers = 4; - std::vector results(static_cast(count), -1); - - // Act - const int result = parallel_all_boards_n( - count, - workers, - [&](const int, const int bno) { - results[static_cast(bno)] = bno * 10; - return RETURN_NO_FAULT; - }); + // Completion must establish happens-before from each worker's process_board + // stores into the caller's buffers before parallel_all_boards_n returns. + // Plain (non-atomic) writes make missing mutex/condvar sync visible to TSan. + constexpr int count = 64; + constexpr int workers = 4; + std::vector results(static_cast(count), -1); + + // Act + const int result = parallel_all_boards_n( + count, + workers, + [&](const int, const int bno) { + results[static_cast(bno)] = bno * 10; + return RETURN_NO_FAULT; + }); - // Assert: every slot is visible without further synchronization. - EXPECT_EQ(result, RETURN_NO_FAULT); - for (int i = 0; i < count; ++i) - EXPECT_EQ(results[static_cast(i)], i * 10) << "board " << i; + // Assert: every slot is visible without further synchronization. + EXPECT_EQ(result, RETURN_NO_FAULT); + for (int i = 0; i < count; ++i) + EXPECT_EQ(results[static_cast(i)], i * 10) << "board " << i; } TEST(ParallelAllBoards, FailFastReturnsFirstError) { - // Arrange / Act - const int result = parallel_all_boards_n( - 16, - 4, - [](const int, const int bno) { - return bno == 7 ? RETURN_TOO_MANY_BOARDS : RETURN_NO_FAULT; - }); + // Arrange / Act + const int result = parallel_all_boards_n( + 16, + 4, + [](const int, const int bno) { + return bno == 7 ? RETURN_TOO_MANY_BOARDS : RETURN_NO_FAULT; + }); - // Assert - EXPECT_EQ(result, RETURN_TOO_MANY_BOARDS); + // Assert + EXPECT_EQ(result, RETURN_TOO_MANY_BOARDS); } TEST(ParallelAllBoards, ProcessBoardExceptionDoesNotHangCaller) { - // If process_board throws on a pool worker, that worker must still account - // completion and wake cv_done_; otherwise the caller waits forever (or the - // process aborts via std::terminate when the exception leaves the thread). - if (std::thread::hardware_concurrency() < 2) - GTEST_SKIP() << "Need at least 2 hardware threads"; - - constexpr int count = 8; - constexpr int workers = 2; - constexpr auto deadline = std::chrono::seconds(10); - - // packaged_task owns the shared state; if wait times out we can detach the - // thread without UAF when locals here are destroyed (unlike a promise - // captured by reference). Define count/workers inside the lambda so MSVC - // does not require a capture (C3493) and clang does not warn about an - // unnecessary one (-Wunused-lambda-capture). - std::packaged_task task([] { - constexpr int board_count = 8; - constexpr int worker_count = 2; - return parallel_all_boards_n( - board_count, - worker_count, - [](const int, const int bno) -> int { - if (bno == 0) - throw std::runtime_error("process_board failed"); - return RETURN_NO_FAULT; - }); - }); - std::future fut = task.get_future(); - std::thread caller(std::move(task)); - - const bool ready = fut.wait_for(deadline) == std::future_status::ready; - if (!ready) - { - caller.detach(); - FAIL() << "caller hung after process_board threw on a pool worker"; - } - caller.join(); - - EXPECT_EQ(fut.get(), RETURN_UNKNOWN_FAULT); - - // Pool workers must remain usable after the exceptional run. - EXPECT_EQ( - parallel_all_boards_n( - count, workers, [](const int, const int) { return RETURN_NO_FAULT; }), - RETURN_NO_FAULT); + // If process_board throws on a pool worker, that worker must still account + // completion and wake cv_done_; otherwise the caller waits forever (or the + // process aborts via std::terminate when the exception leaves the thread). + if (std::thread::hardware_concurrency() < 2) + GTEST_SKIP() << "Need at least 2 hardware threads"; + + constexpr int count = 8; + constexpr int workers = 2; + constexpr auto deadline = std::chrono::seconds(10); + + // packaged_task owns the shared state; if wait times out we can detach the + // thread without UAF when locals here are destroyed (unlike a promise + // captured by reference). Define count/workers inside the lambda so MSVC + // does not require a capture (C3493) and clang does not warn about an + // unnecessary one (-Wunused-lambda-capture). + std::packaged_task task([] { + constexpr int board_count = 8; + constexpr int worker_count = 2; + return parallel_all_boards_n( + board_count, + worker_count, + [](const int, const int bno) -> int { + if (bno == 0) + throw std::runtime_error("process_board failed"); + return RETURN_NO_FAULT; + }); + }); + std::future fut = task.get_future(); + std::thread caller(std::move(task)); + + const bool ready = fut.wait_for(deadline) == std::future_status::ready; + if (!ready) + { + caller.detach(); + FAIL() << "caller hung after process_board threw on a pool worker"; + } + caller.join(); + + EXPECT_EQ(fut.get(), RETURN_UNKNOWN_FAULT); + + // Pool workers must remain usable after the exceptional run. + EXPECT_EQ( + parallel_all_boards_n( + count, workers, [](const int, const int) { return RETURN_NO_FAULT; }), + RETURN_NO_FAULT); } TEST(ParallelAllBoards, ProcessBoardExceptionOverridesPriorReturnCode) { - // Contract: any thrown exception maps the run to RETURN_UNKNOWN_FAULT, even - // when another worker has already recorded a non-success return code. - if (std::thread::hardware_concurrency() < 2) - GTEST_SKIP() << "Need at least 2 hardware threads"; - - constexpr auto deadline = std::chrono::seconds(10); - - // Heap-backed sync so a timeout detach cannot UAF stack atomics. - struct Sync - { - std::atomic board1_started{false}; - std::atomic board0_returned_error{false}; - }; - const auto sync = std::make_shared(); - - std::packaged_task task([sync] { - constexpr int board_count = 2; - constexpr int worker_count = 2; - return parallel_all_boards_n( - board_count, - worker_count, - [sync](const int, const int bno) -> int { - if (bno == 0) - { - while (!sync->board1_started.load(std::memory_order_acquire)) - std::this_thread::yield(); - sync->board0_returned_error.store(true, std::memory_order_release); - return RETURN_TOO_MANY_BOARDS; - } - sync->board1_started.store(true, std::memory_order_release); - while (!sync->board0_returned_error.load(std::memory_order_acquire)) - std::this_thread::yield(); - // Let the other worker's compare_exchange publish first_error first. - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - throw std::runtime_error("process_board failed after prior error"); - }); - }); - std::future fut = task.get_future(); - std::thread caller(std::move(task)); - - const bool ready = fut.wait_for(deadline) == std::future_status::ready; - if (!ready) - { - caller.detach(); - FAIL() << "caller hung when exception raced with a prior return code"; - } - caller.join(); - - EXPECT_EQ(fut.get(), RETURN_UNKNOWN_FAULT); + // Contract: any thrown exception maps the run to RETURN_UNKNOWN_FAULT, even + // when another worker has already recorded a non-success return code. + if (std::thread::hardware_concurrency() < 2) + GTEST_SKIP() << "Need at least 2 hardware threads"; + + constexpr auto deadline = std::chrono::seconds(10); + + // Heap-backed sync so a timeout detach cannot UAF stack atomics. + struct Sync + { + std::atomic board1_started{false}; + std::atomic board0_returned_error{false}; + }; + const auto sync = std::make_shared(); + + std::packaged_task task([sync] { + constexpr int board_count = 2; + constexpr int worker_count = 2; + return parallel_all_boards_n( + board_count, + worker_count, + [sync](const int, const int bno) -> int { + if (bno == 0) + { + while (!sync->board1_started.load(std::memory_order_acquire)) + std::this_thread::yield(); + sync->board0_returned_error.store(true, std::memory_order_release); + return RETURN_TOO_MANY_BOARDS; + } + sync->board1_started.store(true, std::memory_order_release); + while (!sync->board0_returned_error.load(std::memory_order_acquire)) + std::this_thread::yield(); + // Let the other worker's compare_exchange publish first_error first. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + throw std::runtime_error("process_board failed after prior error"); + }); + }); + std::future fut = task.get_future(); + std::thread caller(std::move(task)); + + const bool ready = fut.wait_for(deadline) == std::future_status::ready; + if (!ready) + { + caller.detach(); + FAIL() << "caller hung when exception raced with a prior return code"; + } + caller.join(); + + EXPECT_EQ(fut.get(), RETURN_UNKNOWN_FAULT); } TEST(ParallelAllBoards, ConcurrentCallersBothCompleteAndProcessAllBoards) { - // The pool is process-global; two threads dispatching batches at the same - // time must not clobber each other's job (which would drop boards or hang - // one caller forever waiting for workers that never picked its job up). - constexpr int callers = 2; - constexpr int count = 64; - constexpr int workers = 2; - constexpr auto deadline = std::chrono::seconds(20); - - // Arrange: per-caller hit counters and a start barrier so both callers - // enter the dispatcher at the same moment. - std::array>, callers> hits; - for (auto& caller_hits : hits) - { - caller_hits = std::vector>(count); - for (auto& h : caller_hits) - h.store(0, std::memory_order_relaxed); - } - std::atomic ready{0}; - std::array, callers> results; - std::array, callers> futures; - for (int c = 0; c < callers; ++c) - futures[static_cast(c)] = - results[static_cast(c)].get_future(); - - // Act - std::vector threads; - threads.reserve(callers); - for (int c = 0; c < callers; ++c) - { - threads.emplace_back([&, c] { - ready.fetch_add(1, std::memory_order_relaxed); - while (ready.load(std::memory_order_relaxed) < callers) - std::this_thread::yield(); - - const int rc = parallel_all_boards_n( - count, - workers, - [&, c](const int, const int bno) { - hits[static_cast(c)][static_cast(bno)] - .fetch_add(1, std::memory_order_relaxed); - std::this_thread::sleep_for(std::chrono::microseconds(200)); - return RETURN_NO_FAULT; + // The pool is process-global; two threads dispatching batches at the same + // time must not clobber each other's job (which would drop boards or hang + // one caller forever waiting for workers that never picked its job up). + constexpr int callers = 2; + constexpr int count = 64; + constexpr int workers = 2; + constexpr auto deadline = std::chrono::seconds(20); + + // Arrange: per-caller hit counters and a start barrier so both callers + // enter the dispatcher at the same moment. + std::array>, callers> hits; + for (auto& caller_hits : hits) + { + caller_hits = std::vector>(count); + for (auto& h : caller_hits) + h.store(0, std::memory_order_relaxed); + } + std::atomic ready{0}; + std::array, callers> results; + std::array, callers> futures; + for (int c = 0; c < callers; ++c) + futures[static_cast(c)] = + results[static_cast(c)].get_future(); + + // Act + std::vector threads; + threads.reserve(callers); + for (int c = 0; c < callers; ++c) + { + threads.emplace_back([&, c] { + ready.fetch_add(1, std::memory_order_relaxed); + while (ready.load(std::memory_order_relaxed) < callers) + std::this_thread::yield(); + + const int rc = parallel_all_boards_n( + count, + workers, + [&, c](const int, const int bno) { + hits[static_cast(c)][static_cast(bno)] + .fetch_add(1, std::memory_order_relaxed); + std::this_thread::sleep_for(std::chrono::microseconds(200)); + return RETURN_NO_FAULT; + }); + results[static_cast(c)].set_value(rc); }); - results[static_cast(c)].set_value(rc); - }); - } - - bool timed_out = false; - for (auto& fut : futures) - { - if (fut.wait_for(deadline) != std::future_status::ready) - timed_out = true; - } - - // A hung caller can never be joined; detach so the failure is reportable. - for (auto& th : threads) - { - if (timed_out) - th.detach(); - else - th.join(); - } - ASSERT_FALSE(timed_out) - << "a concurrent caller hung: its job was lost by the shared pool"; - - // Assert: both callers succeeded and every board was processed exactly once - // per caller. - for (int c = 0; c < callers; ++c) - { - EXPECT_EQ(futures[static_cast(c)].get(), RETURN_NO_FAULT) - << "caller " << c; - for (int i = 0; i < count; ++i) - EXPECT_EQ( - hits[static_cast(c)][static_cast(i)].load(), 1) - << "caller " << c << " board " << i; - } + } + + bool timed_out = false; + for (auto& fut : futures) + { + if (fut.wait_for(deadline) != std::future_status::ready) + timed_out = true; + } + + // A hung caller can never be joined; detach so the failure is reportable. + for (auto& th : threads) + { + if (timed_out) + th.detach(); + else + th.join(); + } + ASSERT_FALSE(timed_out) + << "a concurrent caller hung: its job was lost by the shared pool"; + + // Assert: both callers succeeded and every board was processed exactly once + // per caller. + for (int c = 0; c < callers; ++c) + { + EXPECT_EQ(futures[static_cast(c)].get(), RETURN_NO_FAULT) + << "caller " << c; + for (int i = 0; i < count; ++i) + EXPECT_EQ( + hits[static_cast(c)][static_cast(i)].load(), 1) + << "caller " << c << " board " << i; + } } TEST(ParallelAllBoards, ReusesWorkerThreadsAcrossConsecutiveCalls) { - // Persistent pool should create workers once and reuse them. Spawn-per-call - // creates a fresh set of threads on every multi-worker invocation. - if (std::thread::hardware_concurrency() < 2) - GTEST_SKIP() << "Need at least 2 hardware threads"; - - constexpr int count = 64; - constexpr int workers = 4; - const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; - - // Arrange: grow/warm the pool to the requested size. - ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); - const auto created_after_warm = - dds::internal::parallel_boards_worker_threads_created(); - ASSERT_GE(created_after_warm, static_cast(workers)); - - // Act: two more multi-worker runs at the same width. - ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); - ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); - const auto created_after_reuse = - dds::internal::parallel_boards_worker_threads_created(); - - // Assert: reuse must not create additional OS threads. - EXPECT_EQ(created_after_reuse, created_after_warm); + // Persistent pool should create workers once and reuse them. Spawn-per-call + // creates a fresh set of threads on every multi-worker invocation. + if (std::thread::hardware_concurrency() < 2) + GTEST_SKIP() << "Need at least 2 hardware threads"; + + constexpr int count = 64; + constexpr int workers = 4; + const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; + + // Arrange: grow/warm the pool to the requested size. + ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); + const auto created_after_warm = + dds::internal::parallel_boards_worker_threads_created(); + ASSERT_GE(created_after_warm, static_cast(workers)); + + // Act: two more multi-worker runs at the same width. + ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); + ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); + const auto created_after_reuse = + dds::internal::parallel_boards_worker_threads_created(); + + // Assert: reuse must not create additional OS threads. + EXPECT_EQ(created_after_reuse, created_after_warm); } TEST(ParallelAllBoards, LastJobBoardCountReflectsZeroBoardCall) { - // The test seam must record the most recent call's board count, including - // early-return paths (count <= 0), so a prior job cannot leave a stale value. - const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; + // The test seam must record the most recent call's board count, including + // early-return paths (count <= 0), so a prior job cannot leave a stale value. + const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; - ASSERT_EQ(parallel_all_boards_n(4, 1, noop), RETURN_NO_FAULT); - ASSERT_EQ(dds::internal::parallel_boards_last_job_board_count(), 4); + ASSERT_EQ(parallel_all_boards_n(4, 1, noop), RETURN_NO_FAULT); + ASSERT_EQ(dds::internal::parallel_boards_last_job_board_count(), 4); - ASSERT_EQ(parallel_all_boards_n(0, 1, noop), RETURN_NO_FAULT); - EXPECT_EQ(dds::internal::parallel_boards_last_job_board_count(), 0); + ASSERT_EQ(parallel_all_boards_n(0, 1, noop), RETURN_NO_FAULT); + EXPECT_EQ(dds::internal::parallel_boards_last_job_board_count(), 0); - ASSERT_EQ(parallel_all_boards_n(-3, 1, noop), RETURN_NO_FAULT); - EXPECT_EQ(dds::internal::parallel_boards_last_job_board_count(), -3); + ASSERT_EQ(parallel_all_boards_n(-3, 1, noop), RETURN_NO_FAULT); + EXPECT_EQ(dds::internal::parallel_boards_last_job_board_count(), -3); } TEST(ParallelAllBoards, ShutdownJoinsPoolAndAllowsRecreation) { - if (std::thread::hardware_concurrency() < 2) - GTEST_SKIP() << "Need at least 2 hardware threads"; + if (std::thread::hardware_concurrency() < 2) + GTEST_SKIP() << "Need at least 2 hardware threads"; - constexpr int count = 32; - constexpr int workers = 4; - const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; + constexpr int count = 32; + constexpr int workers = 4; + const auto noop = [](const int, const int) { return RETURN_NO_FAULT; }; - ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); - const auto created_before = - dds::internal::parallel_boards_worker_threads_created(); - ASSERT_GE(created_before, static_cast(workers)); + ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); + const auto created_before = + dds::internal::parallel_boards_worker_threads_created(); + ASSERT_GE(created_before, static_cast(workers)); - dds::internal::shutdown_parallel_boards_pool(); - dds::internal::shutdown_parallel_boards_pool(); // idempotent + dds::internal::shutdown_parallel_boards_pool(); + dds::internal::shutdown_parallel_boards_pool(); // idempotent - ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); - const auto created_after = - dds::internal::parallel_boards_worker_threads_created(); - EXPECT_GE(created_after, created_before + static_cast(workers)); + ASSERT_EQ(parallel_all_boards_n(count, workers, noop), RETURN_NO_FAULT); + const auto created_after = + dds::internal::parallel_boards_worker_threads_created(); + EXPECT_GE(created_after, created_before + static_cast(workers)); } diff --git a/library/tests/system/scheduler_board_time_test.cpp b/library/tests/system/scheduler_board_time_test.cpp index c02c84a8d..82c652890 100644 --- a/library/tests/system/scheduler_board_time_test.cpp +++ b/library/tests/system/scheduler_board_time_test.cpp @@ -9,25 +9,25 @@ TEST(SaturateBoardTimeUs, ClampsAboveIntMax) { - // Arrange - const long long too_large = - static_cast(std::numeric_limits::max()) + 1; + // Arrange + const long long too_large = + static_cast(std::numeric_limits::max()) + 1; - // Act / Assert - EXPECT_EQ(saturate_board_time_us(too_large), std::numeric_limits::max()); + // Act / Assert + EXPECT_EQ(saturate_board_time_us(too_large), std::numeric_limits::max()); } TEST(SaturateBoardTimeUs, ClampsNegativeToZero) { - EXPECT_EQ(saturate_board_time_us(-1), 0); - EXPECT_EQ(saturate_board_time_us(-5), 0); + EXPECT_EQ(saturate_board_time_us(-1), 0); + EXPECT_EQ(saturate_board_time_us(-5), 0); } TEST(SaturateBoardTimeUs, PreservesInRangeValues) { - EXPECT_EQ(saturate_board_time_us(0), 0); - EXPECT_EQ(saturate_board_time_us(12345), 12345); - EXPECT_EQ( - saturate_board_time_us(std::numeric_limits::max()), - std::numeric_limits::max()); + EXPECT_EQ(saturate_board_time_us(0), 0); + EXPECT_EQ(saturate_board_time_us(12345), 12345); + EXPECT_EQ( + saturate_board_time_us(std::numeric_limits::max()), + std::numeric_limits::max()); } diff --git a/library/tests/system/tt_sharing_test.cpp b/library/tests/system/tt_sharing_test.cpp index f989ddfd3..c5e63bd16 100644 --- a/library/tests/system/tt_sharing_test.cpp +++ b/library/tests/system/tt_sharing_test.cpp @@ -12,26 +12,26 @@ extern Memory memory; TEST(TransTableSharingTest, SameThreadSharesTT) { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); - // Create an owning context for this (simulates a thread-local owner) - SolverContext owner; - auto thr = owner.thread(); - SolverContext ctx1{thr}; - SolverContext ctx2{thr}; + // Create an owning context for this (simulates a thread-local owner) + SolverContext owner; + auto thr = owner.thread(); + SolverContext ctx1{thr}; + SolverContext ctx2{thr}; - // Initially no TT - EXPECT_EQ(ctx1.maybe_trans_table(), nullptr); - EXPECT_EQ(ctx2.maybe_trans_table(), nullptr); + // Initially no TT + EXPECT_EQ(ctx1.maybe_trans_table(), nullptr); + EXPECT_EQ(ctx2.maybe_trans_table(), nullptr); - TransTable* t1 = ctx1.trans_table(); - ASSERT_NE(t1, nullptr); - TransTable* t2 = ctx2.maybe_trans_table(); - ASSERT_NE(t2, nullptr); - EXPECT_EQ(t1, t2); + TransTable* t1 = ctx1.trans_table(); + ASSERT_NE(t1, nullptr); + TransTable* t2 = ctx2.maybe_trans_table(); + ASSERT_NE(t2, nullptr); + EXPECT_EQ(t1, t2); - // Dispose via one context should remove from registry - ctx1.dispose_trans_table(); - EXPECT_EQ(ctx2.maybe_trans_table(), nullptr); + // Dispose via one context should remove from registry + ctx1.dispose_trans_table(); + EXPECT_EQ(ctx2.maybe_trans_table(), nullptr); } diff --git a/library/tests/system/utilities_feature_flags_test.cpp b/library/tests/system/utilities_feature_flags_test.cpp index 087fe4181..cdb095b2e 100644 --- a/library/tests/system/utilities_feature_flags_test.cpp +++ b/library/tests/system/utilities_feature_flags_test.cpp @@ -11,13 +11,13 @@ namespace dds { TEST(UtilitiesFeatureFlags, LogDisabledByDefault) { - // Default build should not define DDS_UTILITIES_LOG. - EXPECT_FALSE(Utilities::log_enabled()); + // Default build should not define DDS_UTILITIES_LOG. + EXPECT_FALSE(Utilities::log_enabled()); } TEST(UtilitiesFeatureFlags, StatsDisabledByDefault) { - // Default build should not define DDS_UTILITIES_STATS. - EXPECT_FALSE(Utilities::stats_enabled()); + // Default build should not define DDS_UTILITIES_STATS. + EXPECT_FALSE(Utilities::stats_enabled()); } } // namespace dds diff --git a/library/tests/system/utilities_feature_flags_test_with_log.cpp b/library/tests/system/utilities_feature_flags_test_with_log.cpp index 412a59ac7..ce71e5ff2 100644 --- a/library/tests/system/utilities_feature_flags_test_with_log.cpp +++ b/library/tests/system/utilities_feature_flags_test_with_log.cpp @@ -16,7 +16,7 @@ namespace dds { TEST(UtilitiesFeatureFlagsWithLog, LogEnabledWithDefine) { - EXPECT_TRUE(Utilities::log_enabled()); + EXPECT_TRUE(Utilities::log_enabled()); } } // namespace dds diff --git a/library/tests/system/utilities_feature_flags_test_with_stats.cpp b/library/tests/system/utilities_feature_flags_test_with_stats.cpp index 008e3d8c3..c68fe2ff3 100644 --- a/library/tests/system/utilities_feature_flags_test_with_stats.cpp +++ b/library/tests/system/utilities_feature_flags_test_with_stats.cpp @@ -16,7 +16,7 @@ namespace dds { TEST(UtilitiesFeatureFlagsWithStats, StatsEnabledWithDefine) { - EXPECT_TRUE(Utilities::stats_enabled()); + EXPECT_TRUE(Utilities::stats_enabled()); } } // namespace dds diff --git a/library/tests/system/utilities_log_contains_test.cpp b/library/tests/system/utilities_log_contains_test.cpp index a89e1753b..4b3ba57c2 100644 --- a/library/tests/system/utilities_log_contains_test.cpp +++ b/library/tests/system/utilities_log_contains_test.cpp @@ -11,14 +11,14 @@ namespace dds { TEST(UtilitiesLogContains, PrefixMatchesWork) { - Utilities u; - u.log_clear(); - u.log_append("tt:create|K|1024|4096"); - u.log_append("ctx:reset_for_solve"); - EXPECT_EQ(u.log_size(), 2u); - EXPECT_TRUE(u.log_contains("tt:create")); - EXPECT_TRUE(u.log_contains("ctx:reset")); - EXPECT_FALSE(u.log_contains("not:there")); + Utilities u; + u.log_clear(); + u.log_append("tt:create|K|1024|4096"); + u.log_append("ctx:reset_for_solve"); + EXPECT_EQ(u.log_size(), 2u); + EXPECT_TRUE(u.log_contains("tt:create")); + EXPECT_TRUE(u.log_contains("ctx:reset")); + EXPECT_FALSE(u.log_contains("not:there")); } } // namespace dds diff --git a/library/tests/system/utilities_log_ctx_ops_test.cpp b/library/tests/system/utilities_log_ctx_ops_test.cpp index cb5187c17..57585e6b9 100644 --- a/library/tests/system/utilities_log_ctx_ops_test.cpp +++ b/library/tests/system/utilities_log_ctx_ops_test.cpp @@ -13,23 +13,23 @@ extern Memory memory; static void ensure_thread() { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } TEST(UtilitiesLogCtxOpsNoDefine, NoEntriesWhenDisabled) { - ensure_thread(); - SolverContext ctx; + ensure_thread(); + SolverContext ctx; - // Start from a clean log buffer - ctx.utilities().log_clear(); + // Start from a clean log buffer + ctx.utilities().log_clear(); - // Exercise the context operations (should not produce log entries by default) - ctx.reset_for_solve(); - ctx.reset_best_moves_lite(); - ctx.resize_tt(8, 16); - ctx.clear_tt(); + // Exercise the context operations (should not produce log entries by default) + ctx.reset_for_solve(); + ctx.reset_best_moves_lite(); + ctx.resize_tt(8, 16); + ctx.clear_tt(); - EXPECT_TRUE(ctx.utilities().log_buffer().empty()); + EXPECT_TRUE(ctx.utilities().log_buffer().empty()); } diff --git a/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp b/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp index bf9129d4a..5a4ea7d9a 100644 --- a/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp +++ b/library/tests/system/utilities_log_ctx_ops_test_with_define.cpp @@ -13,41 +13,41 @@ extern Memory memory; static void ensure_thread() { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } TEST(UtilitiesLogCtxOpsWithDefine, EmitsCtxAndTTOps) { - ensure_thread(); - SolverContext ctx; - - // Start from a clean log buffer - ctx.utilities().log_clear(); - - // Exercise the newly logged operations - ctx.reset_for_solve(); - ctx.reset_best_moves_lite(); - ctx.resize_tt(8, 16); - ctx.clear_tt(); - - const auto& logs = ctx.utilities().log_buffer(); - // We expect at least the four entries we just invoked - ASSERT_GE(logs.size(), 4u); - - bool sawResetForSolve = false; - bool sawResetBestMovesLite = false; - bool sawResize = false; - bool sawClear = false; - for (const auto& s : logs) { - if (s == "ctx:reset_for_solve") sawResetForSolve = true; - if (s == "ctx:reset_best_moves_lite") sawResetBestMovesLite = true; - if (s.rfind("tt:resize|", 0) == 0) sawResize = true; - if (s == "tt:clear") sawClear = true; - } - - EXPECT_TRUE(sawResetForSolve); - EXPECT_TRUE(sawResetBestMovesLite); - EXPECT_TRUE(sawResize); - EXPECT_TRUE(sawClear); + ensure_thread(); + SolverContext ctx; + + // Start from a clean log buffer + ctx.utilities().log_clear(); + + // Exercise the newly logged operations + ctx.reset_for_solve(); + ctx.reset_best_moves_lite(); + ctx.resize_tt(8, 16); + ctx.clear_tt(); + + const auto& logs = ctx.utilities().log_buffer(); + // We expect at least the four entries we just invoked + ASSERT_GE(logs.size(), 4u); + + bool sawResetForSolve = false; + bool sawResetBestMovesLite = false; + bool sawResize = false; + bool sawClear = false; + for (const auto& s : logs) { + if (s == "ctx:reset_for_solve") sawResetForSolve = true; + if (s == "ctx:reset_best_moves_lite") sawResetBestMovesLite = true; + if (s.rfind("tt:resize|", 0) == 0) sawResize = true; + if (s == "tt:clear") sawClear = true; + } + + EXPECT_TRUE(sawResetForSolve); + EXPECT_TRUE(sawResetBestMovesLite); + EXPECT_TRUE(sawResize); + EXPECT_TRUE(sawClear); } diff --git a/library/tests/system/utilities_log_snapshot_test.cpp b/library/tests/system/utilities_log_snapshot_test.cpp index b501eaa61..9bc8e3f91 100644 --- a/library/tests/system/utilities_log_snapshot_test.cpp +++ b/library/tests/system/utilities_log_snapshot_test.cpp @@ -11,18 +11,18 @@ namespace dds { TEST(UtilitiesLogSnapshot, CopiesAreIndependent) { - Utilities u; - u.log_clear(); - u.log_append("tt:create|K|1024|4096"); - u.log_append("ctx:reset_for_solve"); - auto snap = u.log_snapshot(); - ASSERT_EQ(snap.size(), 2u); - EXPECT_EQ(snap[0], "tt:create|K|1024|4096"); - EXPECT_EQ(snap[1], "ctx:reset_for_solve"); - // Mutate original; snapshot should remain unchanged - u.log_clear(); - EXPECT_EQ(u.log_size(), 0u); - ASSERT_EQ(snap.size(), 2u); + Utilities u; + u.log_clear(); + u.log_append("tt:create|K|1024|4096"); + u.log_append("ctx:reset_for_solve"); + auto snap = u.log_snapshot(); + ASSERT_EQ(snap.size(), 2u); + EXPECT_EQ(snap[0], "tt:create|K|1024|4096"); + EXPECT_EQ(snap[1], "ctx:reset_for_solve"); + // Mutate original; snapshot should remain unchanged + u.log_clear(); + EXPECT_EQ(u.log_size(), 0u); + ASSERT_EQ(snap.size(), 2u); } } // namespace dds diff --git a/library/tests/system/utilities_log_test.cpp b/library/tests/system/utilities_log_test.cpp index 47ce0c7aa..5182f217d 100644 --- a/library/tests/system/utilities_log_test.cpp +++ b/library/tests/system/utilities_log_test.cpp @@ -13,21 +13,21 @@ extern Memory memory; static void ensure_thread() { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } TEST(UtilitiesLogTest, NoLogWithoutDefine) { - ensure_thread(); - SolverContext ctx; + ensure_thread(); + SolverContext ctx; - // Ensure clean start - ctx.utilities().log_clear(); + // Ensure clean start + ctx.utilities().log_clear(); - // Create TT and dispose it; without define there should be no logs - (void)ctx.trans_table(); - ctx.dispose_trans_table(); + // Create TT and dispose it; without define there should be no logs + (void)ctx.trans_table(); + ctx.dispose_trans_table(); - EXPECT_TRUE(ctx.utilities().log_buffer().empty()); + EXPECT_TRUE(ctx.utilities().log_buffer().empty()); } diff --git a/library/tests/system/utilities_log_test_with_define.cpp b/library/tests/system/utilities_log_test_with_define.cpp index 9dd55c206..e2fa5e733 100644 --- a/library/tests/system/utilities_log_test_with_define.cpp +++ b/library/tests/system/utilities_log_test_with_define.cpp @@ -13,27 +13,27 @@ extern Memory memory; static void ensure_thread() { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } TEST(UtilitiesLogTestWithDefine, LogsPresentWhenEnabled) { - ensure_thread(); - SolverContext ctx; - ctx.utilities().log_clear(); + ensure_thread(); + SolverContext ctx; + ctx.utilities().log_clear(); - (void)ctx.trans_table(); - ctx.dispose_trans_table(); + (void)ctx.trans_table(); + ctx.dispose_trans_table(); - const auto& logs = ctx.utilities().log_buffer(); - ASSERT_GE(logs.size(), 1u); - // First log must be tt:create|K|def|max where K in {S,L} - EXPECT_TRUE(logs[0].rfind("tt:create|", 0) == 0); - // Last (or one of) should be dispose - bool sawDispose = false; - for (const auto& s : logs) { - if (s == "tt:dispose") { sawDispose = true; break; } - } - EXPECT_TRUE(sawDispose); + const auto& logs = ctx.utilities().log_buffer(); + ASSERT_GE(logs.size(), 1u); + // First log must be tt:create|K|def|max where K in {S,L} + EXPECT_TRUE(logs[0].rfind("tt:create|", 0) == 0); + // Last (or one of) should be dispose + bool sawDispose = false; + for (const auto& s : logs) { + if (s == "tt:dispose") { sawDispose = true; break; } + } + EXPECT_TRUE(sawDispose); } diff --git a/library/tests/system/utilities_stats_test.cpp b/library/tests/system/utilities_stats_test.cpp index 57c2bc0a2..133e9492a 100644 --- a/library/tests/system/utilities_stats_test.cpp +++ b/library/tests/system/utilities_stats_test.cpp @@ -13,20 +13,20 @@ extern Memory memory; static void ensure_thread() { - if (memory.NumThreads() == 0) - memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); + if (memory.NumThreads() == 0) + memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); } TEST(UtilitiesStatsTest, CountersRemainZeroWithoutDefine) { - ensure_thread(); - SolverContext ctx; - ctx.utilities().util().stats_reset(); + ensure_thread(); + SolverContext ctx; + ctx.utilities().util().stats_reset(); - (void)ctx.trans_table(); - ctx.dispose_trans_table(); + (void)ctx.trans_table(); + ctx.dispose_trans_table(); - const auto& st = ctx.utilities().util().stats(); - EXPECT_EQ(0u, st.tt_creates); - EXPECT_EQ(0u, st.tt_disposes); + const auto& st = ctx.utilities().util().stats(); + EXPECT_EQ(0u, st.tt_creates); + EXPECT_EQ(0u, st.tt_disposes); } diff --git a/library/tests/system/worker_context_reuse_test.cpp b/library/tests/system/worker_context_reuse_test.cpp index 7f83c8348..de220b7e7 100644 --- a/library/tests/system/worker_context_reuse_test.cpp +++ b/library/tests/system/worker_context_reuse_test.cpp @@ -19,32 +19,32 @@ namespace // PBN: N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3 DdTableDeal make_known_deal() { - DdTableDeal deal{}; - deal.cards[0][0] = 0x1800 | 0x0040; - deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; - deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; - deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; - deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; - deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; - deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; - deal.cards[1][3] = 0x1000 | 0x0010; - deal.cards[2][0] = 0x2000 | 0x0020; - deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; - deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; - deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; - deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; - deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; - deal.cards[3][2] = 0x0008 | 0x0004; - deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; - return deal; + DdTableDeal deal{}; + deal.cards[0][0] = 0x1800 | 0x0040; + deal.cards[0][1] = 0x2000 | 0x0060 | 0x0004; + deal.cards[0][2] = 0x0800 | 0x0100 | 0x0020; + deal.cards[0][3] = 0x0400 | 0x0200 | 0x0100; + deal.cards[1][0] = 0x0100 | 0x0080 | 0x0008; + deal.cards[1][1] = 0x0800 | 0x0200 | 0x0080; + deal.cards[1][2] = 0x4000 | 0x0400 | 0x0080 | 0x0040 | 0x0010; + deal.cards[1][3] = 0x1000 | 0x0010; + deal.cards[2][0] = 0x2000 | 0x0020; + deal.cards[2][1] = 0x0400 | 0x0100 | 0x0008; + deal.cards[2][2] = 0x2000 | 0x1000 | 0x0200; + deal.cards[2][3] = 0x4000 | 0x0080 | 0x0040 | 0x0020 | 0x0004; + deal.cards[3][0] = 0x4000 | 0x0400 | 0x0200 | 0x0010 | 0x0004; + deal.cards[3][1] = 0x4000 | 0x1000 | 0x0010; + deal.cards[3][2] = 0x0008 | 0x0004; + deal.cards[3][3] = 0x2000 | 0x0800 | 0x0008; + return deal; } void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) { - for (int strain = 0; strain < DDS_STRAINS; strain++) - for (int hand = 0; hand < DDS_HANDS; hand++) - EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) - << "Mismatch at strain=" << strain << " hand=" << hand; + for (int strain = 0; strain < DDS_STRAINS; strain++) + for (int hand = 0; hand < DDS_HANDS; hand++) + EXPECT_EQ(a.res_table[strain][hand], b.res_table[strain][hand]) + << "Mismatch at strain=" << strain << " hand=" << hand; } } // namespace @@ -52,31 +52,31 @@ void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) // The helper hands out one persistent context per calling thread. TEST(WorkerSolverContext, SameThreadReturnsSameInstance) { - SolverContext& first = dds::internal::worker_solver_context(); - SolverContext& second = dds::internal::worker_solver_context(); - EXPECT_EQ(&first, &second); + SolverContext& first = dds::internal::worker_solver_context(); + SolverContext& second = dds::internal::worker_solver_context(); + EXPECT_EQ(&first, &second); } // Distinct threads must not share a context (SolverContext is not thread-safe). TEST(WorkerSolverContext, DistinctThreadsGetDistinctInstances) { - SolverContext* main_ctx = &dds::internal::worker_solver_context(); - SolverContext* other_ctx = nullptr; - std::thread t([&] { other_ctx = &dds::internal::worker_solver_context(); }); - t.join(); - ASSERT_NE(other_ctx, nullptr); - EXPECT_NE(main_ctx, other_ctx); + SolverContext* main_ctx = &dds::internal::worker_solver_context(); + SolverContext* other_ctx = nullptr; + std::thread t([&] { other_ctx = &dds::internal::worker_solver_context(); }); + t.join(); + ASSERT_NE(other_ctx, nullptr); + EXPECT_NE(main_ctx, other_ctx); } // Repeated access on one thread creates exactly one context (counter seam). TEST(WorkerSolverContext, RepeatedCallsOnSameThreadCreateOneContext) { - (void)dds::internal::worker_solver_context(); - const std::uint64_t before = dds::internal::worker_solver_contexts_created(); - (void)dds::internal::worker_solver_context(); - (void)dds::internal::worker_solver_context(); - const std::uint64_t after = dds::internal::worker_solver_contexts_created(); - EXPECT_EQ(before, after); + (void)dds::internal::worker_solver_context(); + const std::uint64_t before = dds::internal::worker_solver_contexts_created(); + (void)dds::internal::worker_solver_context(); + (void)dds::internal::worker_solver_context(); + const std::uint64_t after = dds::internal::worker_solver_contexts_created(); + EXPECT_EQ(before, after); } // Consecutive batch calc calls must reuse worker contexts: since pool worker @@ -85,70 +85,70 @@ TEST(WorkerSolverContext, RepeatedCallsOnSameThreadCreateOneContext) // stay identical across calls (no stale transposition-table pollution). TEST(WorkerContextReuse, RepeatedCalcCallsAreBoundedByWorkerCountAndStayCorrect) { - InitializeStaticMemory(); - const DdTableDeal deal = make_known_deal(); - constexpr int kWorkers = 2; - constexpr int kCalls = 3; - - DdTableResults reference{}; - ASSERT_EQ(CalcDDtableN(deal, &reference, /*maxThreads=*/1), RETURN_NO_FAULT); - - const std::uint64_t before = dds::internal::worker_solver_contexts_created(); - - for (int call = 0; call < kCalls; call++) - { - DdTableResults table{}; - ASSERT_EQ(CalcDDtableN(deal, &table, kWorkers), RETURN_NO_FAULT) - << "call=" << call; - expect_tables_equal(reference, table); - } - - const std::uint64_t created = - dds::internal::worker_solver_contexts_created() - before; - EXPECT_LE(created, static_cast(kWorkers)) - << "worker contexts must be reused across consecutive batch calls"; + InitializeStaticMemory(); + const DdTableDeal deal = make_known_deal(); + constexpr int kWorkers = 2; + constexpr int kCalls = 3; + + DdTableResults reference{}; + ASSERT_EQ(CalcDDtableN(deal, &reference, /*maxThreads=*/1), RETURN_NO_FAULT); + + const std::uint64_t before = dds::internal::worker_solver_contexts_created(); + + for (int call = 0; call < kCalls; call++) + { + DdTableResults table{}; + ASSERT_EQ(CalcDDtableN(deal, &table, kWorkers), RETURN_NO_FAULT) + << "call=" << call; + expect_tables_equal(reference, table); + } + + const std::uint64_t created = + dds::internal::worker_solver_contexts_created() - before; + EXPECT_LE(created, static_cast(kWorkers)) + << "worker contexts must be reused across consecutive batch calls"; } // The batch solve path must also stay correct when worker contexts (and their // transposition tables) are reused across consecutive calls. TEST(WorkerContextReuse, RepeatedSolveCallsStayCorrect) { - InitializeStaticMemory(); - const DdTableDeal table_deal = make_known_deal(); - - Boards bo{}; - bo.no_of_boards = DDS_STRAINS; - for (int tr = 0; tr < DDS_STRAINS; tr++) - { - Deal dl{}; - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - dl.remainCards[h][s] = table_deal.cards[h][s]; - dl.trump = tr; - dl.first = 0; - bo.deals[tr] = dl; - bo.target[tr] = -1; - bo.solutions[tr] = 1; - bo.mode[tr] = 1; - } - - SolvedBoards first{}; - ASSERT_EQ(SolveAllBoardsBinN(&bo, &first, /*maxThreads=*/2), RETURN_NO_FAULT); - - SolvedBoards second{}; - ASSERT_EQ(SolveAllBoardsBinN(&bo, &second, /*maxThreads=*/2), RETURN_NO_FAULT); - - ASSERT_EQ(first.no_of_boards, second.no_of_boards); - for (int b = 0; b < first.no_of_boards; b++) - { - const FutureTricks& fa = first.solved_board[b]; - const FutureTricks& fb = second.solved_board[b]; - ASSERT_EQ(fa.cards, fb.cards) << "card count differs at board=" << b; - for (int c = 0; c < fa.cards; c++) + InitializeStaticMemory(); + const DdTableDeal table_deal = make_known_deal(); + + Boards bo{}; + bo.no_of_boards = DDS_STRAINS; + for (int tr = 0; tr < DDS_STRAINS; tr++) + { + Deal dl{}; + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + dl.remainCards[h][s] = table_deal.cards[h][s]; + dl.trump = tr; + dl.first = 0; + bo.deals[tr] = dl; + bo.target[tr] = -1; + bo.solutions[tr] = 1; + bo.mode[tr] = 1; + } + + SolvedBoards first{}; + ASSERT_EQ(SolveAllBoardsBinN(&bo, &first, /*maxThreads=*/2), RETURN_NO_FAULT); + + SolvedBoards second{}; + ASSERT_EQ(SolveAllBoardsBinN(&bo, &second, /*maxThreads=*/2), RETURN_NO_FAULT); + + ASSERT_EQ(first.no_of_boards, second.no_of_boards); + for (int b = 0; b < first.no_of_boards; b++) { - EXPECT_EQ(fa.suit[c], fb.suit[c]) << "suit at board=" << b << " c=" << c; - EXPECT_EQ(fa.rank[c], fb.rank[c]) << "rank at board=" << b << " c=" << c; - EXPECT_EQ(fa.score[c], fb.score[c]) << "score at board=" << b << " c=" << c; + const FutureTricks& fa = first.solved_board[b]; + const FutureTricks& fb = second.solved_board[b]; + ASSERT_EQ(fa.cards, fb.cards) << "card count differs at board=" << b; + for (int c = 0; c < fa.cards; c++) + { + EXPECT_EQ(fa.suit[c], fb.suit[c]) << "suit at board=" << b << " c=" << c; + EXPECT_EQ(fa.rank[c], fb.rank[c]) << "rank at board=" << b << " c=" << c; + EXPECT_EQ(fa.score[c], fb.score[c]) << "score at board=" << b << " c=" << c; + } } - } } diff --git a/library/tests/system/worker_count_test.cpp b/library/tests/system/worker_count_test.cpp index de751e51c..af750943d 100644 --- a/library/tests/system/worker_count_test.cpp +++ b/library/tests/system/worker_count_test.cpp @@ -18,76 +18,76 @@ namespace // Mirror the helper's "auto" computation so the test is host-independent. int auto_workers(const int count) { - const unsigned hw = std::thread::hardware_concurrency(); - const int hw_or_1 = hw > 0 ? static_cast(hw) : 1; - return std::max(1, std::min(hw_or_1, count)); + const unsigned hw = std::thread::hardware_concurrency(); + const int hw_or_1 = hw > 0 ? static_cast(hw) : 1; + return std::max(1, std::min(hw_or_1, count)); } } // namespace TEST(ResolveWorkerCount, NonPositiveCapUsesAuto) { - const int count = 8; - EXPECT_EQ(resolve_worker_count(0, count), auto_workers(count)); - EXPECT_EQ(resolve_worker_count(-4, count), auto_workers(count)); + const int count = 8; + EXPECT_EQ(resolve_worker_count(0, count), auto_workers(count)); + EXPECT_EQ(resolve_worker_count(-4, count), auto_workers(count)); } TEST(ResolveWorkerCount, CapLargerThanCountClampsToCount) { - EXPECT_EQ(resolve_worker_count(1000, 5), 5); + EXPECT_EQ(resolve_worker_count(1000, 5), 5); } TEST(ResolveWorkerCount, CapSmallerThanCountAndHardwareIsHonored) { - // A cap of 1 is always <= count and <= hardware_concurrency. - EXPECT_EQ(resolve_worker_count(1, 8), 1); + // A cap of 1 is always <= count and <= hardware_concurrency. + EXPECT_EQ(resolve_worker_count(1, 8), 1); } TEST(ResolveWorkerCount, SingleItemAlwaysOneWorker) { - EXPECT_EQ(resolve_worker_count(0, 1), 1); - EXPECT_EQ(resolve_worker_count(16, 1), 1); - EXPECT_EQ(resolve_worker_count(1, 1), 1); + EXPECT_EQ(resolve_worker_count(0, 1), 1); + EXPECT_EQ(resolve_worker_count(16, 1), 1); + EXPECT_EQ(resolve_worker_count(1, 1), 1); } TEST(ClampWorkersToMemoryBudget, CapsByBudgetPerWorker) { - constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; - EXPECT_EQ(clamp_workers_to_memory_budget(18, 1400, kPerWorkerMB), 11); - EXPECT_EQ(clamp_workers_to_memory_budget(4, 1400, kPerWorkerMB), 4); - EXPECT_EQ(clamp_workers_to_memory_budget(0, 1400, kPerWorkerMB), 1); - EXPECT_EQ(clamp_workers_to_memory_budget(8, 100, 200), 1); + constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; + EXPECT_EQ(clamp_workers_to_memory_budget(18, 1400, kPerWorkerMB), 11); + EXPECT_EQ(clamp_workers_to_memory_budget(4, 1400, kPerWorkerMB), 4); + EXPECT_EQ(clamp_workers_to_memory_budget(0, 1400, kPerWorkerMB), 1); + EXPECT_EQ(clamp_workers_to_memory_budget(8, 100, 200), 1); } TEST(ClampWorkersToMemoryBudget, PlatformCapMatchesWasmBudgetConstants) { - // Document the Emscripten budget used by resolve_worker_count so a quiet - // change to THREADMEM_* cannot silently re-OOM large WASM batches. - constexpr int kHeapBudgetMB = 1400; - constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; - constexpr int kExpectedCap = kHeapBudgetMB / kPerWorkerMB; - static_assert(kExpectedCap >= 1); - EXPECT_EQ(kExpectedCap, 11); - EXPECT_EQ( - clamp_workers_to_memory_budget(64, kHeapBudgetMB, kPerWorkerMB), - kExpectedCap); + // Document the Emscripten budget used by resolve_worker_count so a quiet + // change to THREADMEM_* cannot silently re-OOM large WASM batches. + constexpr int kHeapBudgetMB = 1400; + constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; + constexpr int kExpectedCap = kHeapBudgetMB / kPerWorkerMB; + static_assert(kExpectedCap >= 1); + EXPECT_EQ(kExpectedCap, 11); + EXPECT_EQ( + clamp_workers_to_memory_budget(64, kHeapBudgetMB, kPerWorkerMB), + kExpectedCap); } #if defined(__EMSCRIPTEN__) TEST(ResolveWorkerCount, WasmMemoryCapLimitsAutoWorkers) { - constexpr int kHeapBudgetMB = 1400; - constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; - const int mem_cap = kHeapBudgetMB / kPerWorkerMB; - EXPECT_EQ(resolve_worker_count(0, 5000), std::min(auto_workers(5000), mem_cap)); - EXPECT_EQ(resolve_worker_count(64, 5000), mem_cap); + constexpr int kHeapBudgetMB = 1400; + constexpr int kPerWorkerMB = THREADMEM_LARGE_DEF_MB + 24; + const int mem_cap = kHeapBudgetMB / kPerWorkerMB; + EXPECT_EQ(resolve_worker_count(0, 5000), std::min(auto_workers(5000), mem_cap)); + EXPECT_EQ(resolve_worker_count(64, 5000), mem_cap); } #else TEST(ResolveWorkerCount, NativeBuildsDoNotApplyWasmMemoryCap) { - // Explicit high caps must remain uncapped on native hosts; the WASM heap - // budget is Emscripten-only (#if defined(__EMSCRIPTEN__)). - EXPECT_EQ(resolve_worker_count(64, 5000), 64); - EXPECT_EQ(resolve_worker_count(0, 5000), auto_workers(5000)); + // Explicit high caps must remain uncapped on native hosts; the WASM heap + // budget is Emscripten-only (#if defined(__EMSCRIPTEN__)). + EXPECT_EQ(resolve_worker_count(64, 5000), 64); + EXPECT_EQ(resolve_worker_count(0, 5000), auto_workers(5000)); } #endif diff --git a/library/tests/test_timer_test.cpp b/library/tests/test_timer_test.cpp index 581b93843..cb446a835 100644 --- a/library/tests/test_timer_test.cpp +++ b/library/tests/test_timer_test.cpp @@ -18,198 +18,198 @@ namespace std::string capture_print_hands(const TestTimer& timer) { - std::ostringstream out; - timer.print_hands(out); - return out.str(); + std::ostringstream out; + timer.print_hands(out); + return out.str(); } /// Map a 64-bit value into signed int32 via explicit two's-complement wrap. /// Avoids implementation-defined narrowing of out-of-range values to int32. std::int32_t wrap_i64_to_i32(const std::int64_t value) { - constexpr auto kMod = std::uint64_t{1} << 32; - const auto bits = - static_cast(value) % kMod; // low 32 bits - if (bits > static_cast( - std::numeric_limits::max())) { - return static_cast( - static_cast(bits) - static_cast(kMod)); - } - return static_cast(bits); + constexpr auto kMod = std::uint64_t{1} << 32; + const auto bits = + static_cast(value) % kMod; // low 32 bits + if (bits > static_cast( + std::numeric_limits::max())) { + return static_cast( + static_cast(bits) - static_cast(kMod)); + } + return static_cast(bits); } /// What the old `1000 * delta` path produces when `long` is 32-bit (wasm32). long wrapped_i32_clock_delta_to_ms(const std::clock_t delta) { - const auto prod = - wrap_i64_to_i32(1000 * static_cast(delta)); - return static_cast(prod / static_cast(CLOCKS_PER_SEC)); + const auto prod = + wrap_i64_to_i32(1000 * static_cast(delta)); + return static_cast(prod / static_cast(CLOCKS_PER_SEC)); } /// Smallest tick count where `1000 * ticks` no longer fits in int32. /// Independent of CLOCKS_PER_SEC (1000 on Windows, 1e6 on POSIX/wasm). std::clock_t ticks_that_overflow_i32_multiply() { - constexpr auto kMaxI32 = std::numeric_limits::max(); - return static_cast( - static_cast(kMaxI32) / 1000 + 1); + constexpr auto kMaxI32 = std::numeric_limits::max(); + return static_cast( + static_cast(kMaxI32) / 1000 + 1); } } // namespace TEST(TestTimer, WrapI64ToI32UsesDefinedTwosComplement) { - constexpr auto kMaxI32 = std::numeric_limits::max(); - constexpr auto kMinI32 = std::numeric_limits::min(); - - EXPECT_EQ(wrap_i64_to_i32(0), 0); - EXPECT_EQ(wrap_i64_to_i32(kMaxI32), kMaxI32); - EXPECT_EQ(wrap_i64_to_i32(kMinI32), kMinI32); - EXPECT_EQ(wrap_i64_to_i32(static_cast(kMaxI32) + 1), kMinI32); - EXPECT_EQ( - wrap_i64_to_i32(static_cast(kMinI32) - 1), - kMaxI32); - // 1000 * ticks_that_overflow_i32_multiply() - EXPECT_EQ( - wrap_i64_to_i32( - 1000 * static_cast(ticks_that_overflow_i32_multiply())), - -2147483296); + constexpr auto kMaxI32 = std::numeric_limits::max(); + constexpr auto kMinI32 = std::numeric_limits::min(); + + EXPECT_EQ(wrap_i64_to_i32(0), 0); + EXPECT_EQ(wrap_i64_to_i32(kMaxI32), kMaxI32); + EXPECT_EQ(wrap_i64_to_i32(kMinI32), kMinI32); + EXPECT_EQ(wrap_i64_to_i32(static_cast(kMaxI32) + 1), kMinI32); + EXPECT_EQ( + wrap_i64_to_i32(static_cast(kMinI32) - 1), + kMaxI32); + // 1000 * ticks_that_overflow_i32_multiply() + EXPECT_EQ( + wrap_i64_to_i32( + 1000 * static_cast(ticks_that_overflow_i32_multiply())), + -2147483296); } TEST(TestTimer, ClockDeltaToMsAvoids32BitOverflowForMultiSecondBatches) { - const std::clock_t ticks = ticks_that_overflow_i32_multiply(); - const long expected_ms = static_cast( - (1000.0 * static_cast(ticks)) / - static_cast(CLOCKS_PER_SEC)); - const long wrapped_ms = wrapped_i32_clock_delta_to_ms(ticks); - - ASSERT_NE(wrapped_ms, expected_ms) - << "fixture requires a delta that wraps under 32-bit multiply"; - EXPECT_EQ(clock_delta_to_ms(ticks), expected_ms); - EXPECT_NE(clock_delta_to_ms(ticks), wrapped_ms); + const std::clock_t ticks = ticks_that_overflow_i32_multiply(); + const long expected_ms = static_cast( + (1000.0 * static_cast(ticks)) / + static_cast(CLOCKS_PER_SEC)); + const long wrapped_ms = wrapped_i32_clock_delta_to_ms(ticks); + + ASSERT_NE(wrapped_ms, expected_ms) + << "fixture requires a delta that wraps under 32-bit multiply"; + EXPECT_EQ(clock_delta_to_ms(ticks), expected_ms); + EXPECT_NE(clock_delta_to_ms(ticks), wrapped_ms); } TEST(TestTimer, RecordAccumulatesHandsAndTimes) { - TestTimer timer; - timer.record(10, 100, 50); - timer.record(5, 20, 10); - - const std::string out = capture_print_hands(timer); - // Anchor values to their labels so digits elsewhere in the report cannot - // satisfy the assertions (e.g. "15" matching inside "150"). - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Number of hands\s+15\s*(?:\n|$))"))); // 10 + 5 - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(User time \(ms\)\s+120\s*(?:\n|$))"))); // 100 + 20 - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Avg user time \(ms\)\s+8\.00\s*(?:\n|$))"))); // 120/15 - EXPECT_EQ(out.find("Min user time (ms)"), std::string::npos); - EXPECT_EQ(out.find("Max user time (ms)"), std::string::npos); + TestTimer timer; + timer.record(10, 100, 50); + timer.record(5, 20, 10); + + const std::string out = capture_print_hands(timer); + // Anchor values to their labels so digits elsewhere in the report cannot + // satisfy the assertions (e.g. "15" matching inside "150"). + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Number of hands\s+15\s*(?:\n|$))"))); // 10 + 5 + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(User time \(ms\)\s+120\s*(?:\n|$))"))); // 100 + 20 + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Avg user time \(ms\)\s+8\.00\s*(?:\n|$))"))); // 120/15 + EXPECT_EQ(out.find("Min user time (ms)"), std::string::npos); + EXPECT_EQ(out.find("Max user time (ms)"), std::string::npos); } TEST(TestTimer, RecordIgnoresNonPositiveHands) { - TestTimer timer; - timer.record(10, 100, 50); - timer.record(0, 999, 999); - timer.record(-3, 999, 999); - - const std::string out = capture_print_hands(timer); - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Number of hands\s+10\s*(?:\n|$))"))); - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(User time \(ms\)\s+100\s*(?:\n|$))"))); - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Avg user time \(ms\)\s+10\.00\s*(?:\n|$))"))); - EXPECT_EQ(out.find("999"), std::string::npos); + TestTimer timer; + timer.record(10, 100, 50); + timer.record(0, 999, 999); + timer.record(-3, 999, 999); + + const std::string out = capture_print_hands(timer); + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Number of hands\s+10\s*(?:\n|$))"))); + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(User time \(ms\)\s+100\s*(?:\n|$))"))); + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Avg user time \(ms\)\s+10\.00\s*(?:\n|$))"))); + EXPECT_EQ(out.find("999"), std::string::npos); } TEST(TestTimer, ResetClearsAccumulatedStats) { - TestTimer timer; - timer.record(1, 10, 2); - timer.reset(); - - const std::string out = capture_print_hands(timer); - // Require the hands count field itself to be 0 (not a substring match like - // "10", which also contains '0'). - EXPECT_TRUE(std::regex_search( - out, std::regex(R"(Number of hands\s+0\s*(?:\n|$))"))); - EXPECT_EQ(out.find("User time (ms)"), std::string::npos); + TestTimer timer; + timer.record(1, 10, 2); + timer.reset(); + + const std::string out = capture_print_hands(timer); + // Require the hands count field itself to be 0 (not a substring match like + // "10", which also contains '0'). + EXPECT_TRUE(std::regex_search( + out, std::regex(R"(Number of hands\s+0\s*(?:\n|$))"))); + EXPECT_EQ(out.find("User time (ms)"), std::string::npos); } TEST(TestTimer, PrintHandsShowsSysNaWhenClockUnavailable) { - // wasm32+pthread: clock() always returns -1 (process CPU clock is epoch-based - // and does not fit in 32-bit clock_t). That must not be printed as "zero". - TestTimer timer; - timer.mark_sys_time_unavailable(); - timer.record(10, 100, 0); + // wasm32+pthread: clock() always returns -1 (process CPU clock is epoch-based + // and does not fit in 32-bit clock_t). That must not be printed as "zero". + TestTimer timer; + timer.mark_sys_time_unavailable(); + timer.record(10, 100, 0); - const std::string out = capture_print_hands(timer); + const std::string out = capture_print_hands(timer); - EXPECT_NE(out.find("Sys time (ms)"), std::string::npos); - EXPECT_NE(out.find("n/a"), std::string::npos); - EXPECT_EQ(out.find("zero"), std::string::npos); + EXPECT_NE(out.find("Sys time (ms)"), std::string::npos); + EXPECT_NE(out.find("n/a"), std::string::npos); + EXPECT_EQ(out.find("zero"), std::string::npos); } TEST(TestTimer, PrintHandsRestoresStreamFormatState) { - TestTimer timer; - timer.record(2, 20, 4); + TestTimer timer; + timer.record(2, 20, 4); - std::ostringstream out; - out << std::scientific << std::setprecision(5); - const auto flags_before = out.flags(); - const auto precision_before = out.precision(); + std::ostringstream out; + out << std::scientific << std::setprecision(5); + const auto flags_before = out.flags(); + const auto precision_before = out.precision(); - timer.print_hands(out); + timer.print_hands(out); - EXPECT_EQ(out.flags(), flags_before); - EXPECT_EQ(out.precision(), precision_before); + EXPECT_EQ(out.flags(), flags_before); + EXPECT_EQ(out.precision(), precision_before); } TEST(TestTimer, PrintRunningOverwritesSingleLineWithAnsi) { - TestTimer timer; - timer.record(1, 10, 0); - testing::internal::CaptureStdout(); - timer.print_running(1, 10); - timer.record(1, 20, 0); - timer.print_running(2, 10); - const std::string out = testing::internal::GetCapturedStdout(); - - EXPECT_NE(out.find("\033[2K"), std::string::npos); - EXPECT_NE(out.find('\r'), std::string::npos); - EXPECT_NE(out.find("2 ("), std::string::npos); - // In-place updates must not end each progress tick with a newline. - EXPECT_EQ(out.find('\n'), std::string::npos); + TestTimer timer; + timer.record(1, 10, 0); + testing::internal::CaptureStdout(); + timer.print_running(1, 10); + timer.record(1, 20, 0); + timer.print_running(2, 10); + const std::string out = testing::internal::GetCapturedStdout(); + + EXPECT_NE(out.find("\033[2K"), std::string::npos); + EXPECT_NE(out.find('\r'), std::string::npos); + EXPECT_NE(out.find("2 ("), std::string::npos); + // In-place updates must not end each progress tick with a newline. + EXPECT_EQ(out.find('\n'), std::string::npos); } TEST(TestTimer, FinishRunningClearsProgressLine) { - TestTimer timer; - timer.record(1, 5, 0); - testing::internal::CaptureStdout(); - timer.print_running(1, 1); - timer.finish_running(); - const std::string out = testing::internal::GetCapturedStdout(); - - // After finish, the progress line is erased (clear + CR), not kept as a - // finalized "100%" row above the summary. - ASSERT_FALSE(out.empty()); - EXPECT_NE(out.find("\033[2K"), std::string::npos); - EXPECT_EQ(out.back(), '\r'); - EXPECT_EQ(out.find('\n'), std::string::npos); + TestTimer timer; + timer.record(1, 5, 0); + testing::internal::CaptureStdout(); + timer.print_running(1, 1); + timer.finish_running(); + const std::string out = testing::internal::GetCapturedStdout(); + + // After finish, the progress line is erased (clear + CR), not kept as a + // finalized "100%" row above the summary. + ASSERT_FALSE(out.empty()); + EXPECT_NE(out.find("\033[2K"), std::string::npos); + EXPECT_EQ(out.back(), '\r'); + EXPECT_EQ(out.find('\n'), std::string::npos); } TEST(TestTimer, FinishRunningIsNoOpWithoutPrintRunning) { - TestTimer timer; - testing::internal::CaptureStdout(); - timer.finish_running(); - const std::string out = testing::internal::GetCapturedStdout(); - EXPECT_TRUE(out.empty()); + TestTimer timer; + testing::internal::CaptureStdout(); + timer.finish_running(); + const std::string out = testing::internal::GetCapturedStdout(); + EXPECT_TRUE(out.empty()); } diff --git a/library/tests/testcommon.cpp b/library/tests/testcommon.cpp index 7b855c034..b666329ae 100644 --- a/library/tests/testcommon.cpp +++ b/library/tests/testcommon.cpp @@ -42,20 +42,20 @@ string GetCompiler(); const vector DDS_SYSTEM_PLATFORM = { - "", - "Windows", - "Cygwin", - "Linux", - "Apple" + "", + "Windows", + "Cygwin", + "Linux", + "Apple" }; const vector DDS_SYSTEM_COMPILER = { - "", - "Microsoft Visual C++", - "MinGW", - "GNU g++", - "clang" + "", + "Microsoft Visual C++", + "MinGW", + "GNU g++", + "clang" }; @@ -69,129 +69,129 @@ void main_identify(); int real_main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) { - bool GIBmode = false; - - int stepsize = 0; - if (options.solver_ == Solver::DTEST_SOLVER_SOLVE) - stepsize = MAXNOOFBOARDS; - else if (options.solver_ == Solver::DTEST_SOLVER_CALC) - stepsize = MAXNOOFBOARDS; - else if (options.solver_ == Solver::DTEST_SOLVER_PLAY) - stepsize = MAXNOOFBOARDS; - else if (options.solver_ == Solver::DTEST_SOLVER_PAR) - stepsize = 1; - else if (options.solver_ == Solver::DTEST_SOLVER_DEALERPAR) - stepsize = 1; - - set_constants(); - main_identify(); - - int number = 0; - int * dealer_list = nullptr; - int * vul_list = nullptr; - DealPBN * deal_list = nullptr; - FutureTricks * fut_list = nullptr; - DdTableResults * table_list = nullptr; - ParResults * par_list = nullptr; - ParResultsDealer * dealerpar_list = nullptr; - PlayTracePBN * play_list = nullptr; - SolvedPlay * trace_list = nullptr; - if (read_file(options.fname_, number, GIBmode, &dealer_list, &vul_list, - &deal_list, &fut_list, &table_list, &par_list, &dealerpar_list, - &play_list, &trace_list) == false) - { - cout << "read_file failed\n"; - return 1; - } - - if (GIBmode && options.solver_ != Solver::DTEST_SOLVER_CALC) - { - cout << "GIB file only works with calc\n"; - return 1; - } - - timer.reset(); - timer.set_name("Hand stats"); - - BoardsPBN bop; - SolvedBoards solvedbdp; - PlayTracesPBN playsp; - SolvedPlays solvedplp; - - std::vector> board_times; - if (options.report_slow_boards_) - board_times.reserve(static_cast(number)); - - bool ok = true; - if (options.solver_ == Solver::DTEST_SOLVER_SOLVE) - { - ok = loop_solve( - &bop, - &solvedbdp, - deal_list, - fut_list, - number, - stepsize, - options.report_slow_boards_ ? &board_times : nullptr); - } - else if (options.solver_ == Solver::DTEST_SOLVER_CALC) - { - ok = loop_calc( - deal_list, - table_list, - number, - stepsize, - options.report_slow_boards_ ? &board_times : nullptr); - } - else if (options.solver_ == Solver::DTEST_SOLVER_PLAY) - { - ok = loop_play(&bop, &playsp, &solvedplp, deal_list, play_list, trace_list, - number, stepsize); - } - else if (options.solver_ == Solver::DTEST_SOLVER_PAR) - { - ok = loop_par(vul_list, table_list, par_list, number, stepsize); - } - else if (options.solver_ == Solver::DTEST_SOLVER_DEALERPAR) - { - ok = loop_dealerpar(dealer_list, vul_list, table_list, dealerpar_list, - number, stepsize); - } - else - { - cout << "Unknown type " << - static_cast(options.solver_) << "\n"; - return 1; - } - - timer.print_hands(cout); - - if (options.report_slow_boards_) - { - if (board_times.empty()) + bool GIBmode = false; + + int stepsize = 0; + if (options.solver_ == Solver::DTEST_SOLVER_SOLVE) + stepsize = MAXNOOFBOARDS; + else if (options.solver_ == Solver::DTEST_SOLVER_CALC) + stepsize = MAXNOOFBOARDS; + else if (options.solver_ == Solver::DTEST_SOLVER_PLAY) + stepsize = MAXNOOFBOARDS; + else if (options.solver_ == Solver::DTEST_SOLVER_PAR) + stepsize = 1; + else if (options.solver_ == Solver::DTEST_SOLVER_DEALERPAR) + stepsize = 1; + + set_constants(); + main_identify(); + + int number = 0; + int * dealer_list = nullptr; + int * vul_list = nullptr; + DealPBN * deal_list = nullptr; + FutureTricks * fut_list = nullptr; + DdTableResults * table_list = nullptr; + ParResults * par_list = nullptr; + ParResultsDealer * dealerpar_list = nullptr; + PlayTracePBN * play_list = nullptr; + SolvedPlay * trace_list = nullptr; + if (read_file(options.fname_, number, GIBmode, &dealer_list, &vul_list, + &deal_list, &fut_list, &table_list, &par_list, &dealerpar_list, + &play_list, &trace_list) == false) { - cout << "Per-board timing data not available." << std::endl; + cout << "read_file failed\n"; + return 1; + } + + if (GIBmode && options.solver_ != Solver::DTEST_SOLVER_CALC) + { + cout << "GIB file only works with calc\n"; + return 1; + } + + timer.reset(); + timer.set_name("Hand stats"); + + BoardsPBN bop; + SolvedBoards solvedbdp; + PlayTracesPBN playsp; + SolvedPlays solvedplp; + + std::vector> board_times; + if (options.report_slow_boards_) + board_times.reserve(static_cast(number)); + + bool ok = true; + if (options.solver_ == Solver::DTEST_SOLVER_SOLVE) + { + ok = loop_solve( + &bop, + &solvedbdp, + deal_list, + fut_list, + number, + stepsize, + options.report_slow_boards_ ? &board_times : nullptr); + } + else if (options.solver_ == Solver::DTEST_SOLVER_CALC) + { + ok = loop_calc( + deal_list, + table_list, + number, + stepsize, + options.report_slow_boards_ ? &board_times : nullptr); + } + else if (options.solver_ == Solver::DTEST_SOLVER_PLAY) + { + ok = loop_play(&bop, &playsp, &solvedplp, deal_list, play_list, trace_list, + number, stepsize); + } + else if (options.solver_ == Solver::DTEST_SOLVER_PAR) + { + ok = loop_par(vul_list, table_list, par_list, number, stepsize); + } + else if (options.solver_ == Solver::DTEST_SOLVER_DEALERPAR) + { + ok = loop_dealerpar(dealer_list, vul_list, table_list, dealerpar_list, + number, stepsize); } else { - print_per_board_timings(cout, std::move(board_times)); + cout << "Unknown type " << + static_cast(options.solver_) << "\n"; + return 1; } - } - - free(dealer_list); - free(vul_list); - free(deal_list); - free(fut_list); - free(table_list); - free(par_list); - free(dealerpar_list); - free(play_list); - free(trace_list); - - // Release heavy timing storage before program exit to avoid long destructor work. - scheduler.ClearTiming(); - - return ok ? 0 : 1; + + timer.print_hands(cout); + + if (options.report_slow_boards_) + { + if (board_times.empty()) + { + cout << "Per-board timing data not available." << std::endl; + } + else + { + print_per_board_timings(cout, std::move(board_times)); + } + } + + free(dealer_list); + free(vul_list); + free(deal_list); + free(fut_list); + free(table_list); + free(par_list); + free(dealerpar_list); + free(play_list); + free(trace_list); + + // Release heavy timing storage before program exit to avoid long destructor work. + scheduler.ClearTiming(); + + return ok ? 0 : 1; } @@ -201,69 +201,69 @@ int real_main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) string GetSystem() { - unsigned sys; + unsigned sys; #if defined(_WIN32) - sys = 1; + sys = 1; #elif defined(__CYGWIN__) - sys = 2; + sys = 2; #elif defined(__linux) - sys = 3; + sys = 3; #elif defined(__APPLE__) - sys = 4; + sys = 4; #else - sys = 0; + sys = 0; #endif - - return DDS_SYSTEM_PLATFORM[sys]; + + return DDS_SYSTEM_PLATFORM[sys]; } string GetBits() { - if constexpr (sizeof(void *) == 4) - return "32 bits"; - else if constexpr (sizeof(void *) == 8) - return "64 bits"; - else - return "unknown"; + if constexpr (sizeof(void *) == 4) + return "32 bits"; + else if constexpr (sizeof(void *) == 8) + return "64 bits"; + else + return "unknown"; } string GetCompiler() { - unsigned comp; + unsigned comp; #if defined(_MSC_VER) - comp = 1; + comp = 1; #elif defined(__MINGW32__) - comp = 2; + comp = 2; #elif defined(__clang__) - comp = 4; // Out-of-order on purpose + comp = 4; // Out-of-order on purpose #elif defined(__GNUC__) - comp = 3; + comp = 3; #else - comp = 0; + comp = 0; #endif - return DDS_SYSTEM_COMPILER[comp]; + return DDS_SYSTEM_COMPILER[comp]; } void main_identify() { - cout << "test program\n"; - cout << string(13, '-') << "\n"; + cout << "test program\n"; + cout << string(13, '-') << "\n"; - const string strSystem = GetSystem(); - cout << left << setw(13) << "System" << - setw(20) << right << strSystem << "\n"; + const string strSystem = GetSystem(); + cout << left << setw(13) << "System" << + setw(20) << right << strSystem << "\n"; - const string strBits = GetBits(); - cout << left << setw(13) << "Word size" << - setw(20) << right << strBits << "\n"; + const string strBits = GetBits(); + cout << left << setw(13) << "Word size" << + setw(20) << right << strBits << "\n"; - const string strCompiler = GetCompiler(); - cout << left << setw(13) << "Compiler" << - setw(20) << right << strCompiler << "\n\n"; + const string strCompiler = GetCompiler(); + cout << left << setw(13) << "Compiler" << + setw(20) << right << strCompiler << "\n\n"; } diff --git a/library/tests/trans_table/mock_data_generators.cpp b/library/tests/trans_table/mock_data_generators.cpp index 84b39763c..1febac2bd 100644 --- a/library/tests/trans_table/mock_data_generators.cpp +++ b/library/tests/trans_table/mock_data_generators.cpp @@ -30,7 +30,7 @@ void MockHandGenerator::GenerateRandomDistribution(int hand_dist[DDS_HANDS]) remaining -= hand_dist[hand]; } hand_dist[DDS_HANDS - 1] = remaining; // Last hand gets remainder - + EnsureValidTotalCards(hand_dist); } @@ -49,7 +49,7 @@ void MockHandGenerator::GenerateUnbalancedDistribution(int hand_dist[DDS_HANDS]) hand_dist[1] = 15; hand_dist[2] = 12; hand_dist[3] = 15; - + EnsureValidTotalCards(hand_dist); } @@ -113,7 +113,7 @@ void MockHandGenerator::EnsureValidTotalCards(int hand_dist[DDS_HANDS], int targ for (int hand = 0; hand < DDS_HANDS; hand++) { total += hand_dist[hand]; } - + // Adjust if total doesn't match target if (total != targetTotal) { int diff = targetTotal - total; @@ -141,12 +141,12 @@ void MockPositionGenerator::GenerateEarlyGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS]) { - + trick = std::uniform_int_distribution(1, 4)(generator_); hand = hand_dist_(generator_); - + GenerateSimpleAggrTarget(aggrTarget); - + for (int h = 0; h < DDS_HANDS; h++) { hand_dist[h] = 13 - trick + 1; } @@ -156,12 +156,12 @@ void MockPositionGenerator::GenerateMiddleGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS]) { - + trick = std::uniform_int_distribution(5, 9)(generator_); hand = hand_dist_(generator_); - + GenerateAggrTarget(aggrTarget, 2); - + for (int h = 0; h < DDS_HANDS; h++) { hand_dist[h] = 13 - trick + 1; } @@ -171,12 +171,12 @@ void MockPositionGenerator::GenerateEndGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS]) { - + trick = std::uniform_int_distribution(10, 13)(generator_); hand = hand_dist_(generator_); - + GenerateComplexAggrTarget(aggrTarget); - + for (int h = 0; h < DDS_HANDS; h++) { hand_dist[h] = 13 - trick + 1; } @@ -211,7 +211,7 @@ void MockPositionGenerator::GenerateNodeCardsType(NodeCards& node, int tricksRem node.lower_bound = static_cast(std::max(0, tricksRemaining - 3)); node.best_move_suit = static_cast(std::uniform_int_distribution(0, DDS_SUITS - 1)(generator_)); node.best_move_rank = static_cast(std::uniform_int_distribution(2, 14)(generator_)); - + for (int suit = 0; suit < DDS_SUITS; suit++) { node.least_win[suit] = static_cast(std::uniform_int_distribution(2, 14)(generator_)); } @@ -220,22 +220,22 @@ void MockPositionGenerator::GenerateNodeCardsType(NodeCards& node, int tricksRem MockPositionGenerator::GameSequence MockPositionGenerator::GenerateGameSequence(int startTrick, int endTrick) { GameSequence sequence; - + for (int trick = startTrick; trick <= endTrick; trick++) { sequence.tricks.push_back(trick); sequence.hands.push_back(hand_dist_(generator_)); - + std::array aggrTarget; GenerateAggrTarget(aggrTarget.data(), 1); sequence.aggrTargets.push_back(aggrTarget); - + std::array hand_dist; for (int hand = 0; hand < DDS_HANDS; hand++) { hand_dist[hand] = 13 - trick + 1; } sequence.hand_dists.push_back(hand_dist); } - + return sequence; } @@ -269,10 +269,10 @@ void MockWinRankGenerator::GenerateComplexWinRanks(unsigned short win_ranks[DDS_ void MockWinRankGenerator::GenerateEquivalentWinRanks( unsigned short win_ranks1[DDS_SUITS], unsigned short win_ranks2[DDS_SUITS]) { - + // Generate first set GenerateSimpleWinRanks(win_ranks1); - + // Make second set equivalent in relative terms for (int suit = 0; suit < DDS_SUITS; suit++) { win_ranks2[suit] = win_ranks1[suit]; @@ -283,10 +283,10 @@ void MockWinRankGenerator::GenerateRelativeRankScenario( unsigned short absoluteRanks[DDS_SUITS], unsigned short relativeRanks[DDS_SUITS], unsigned short winMask[DDS_SUITS]) { - + GenerateSimpleWinRanks(absoluteRanks); ConvertToRelativeRanks(absoluteRanks, relativeRanks); - + for (int suit = 0; suit < DDS_SUITS; suit++) { winMask[suit] = 0x1FFF; // Full mask for simplicity } @@ -336,7 +336,7 @@ void MockWinRankGenerator::GenerateGappedPattern(unsigned short& suitRanks) bool MockWinRankGenerator::AreEquivalentRelativeRanks( const unsigned short ranks1[DDS_SUITS], const unsigned short ranks2[DDS_SUITS]) { - + // Simple equivalence check for now for (int suit = 0; suit < DDS_SUITS; suit++) { if (ranks1[suit] != ranks2[suit]) { @@ -349,7 +349,7 @@ bool MockWinRankGenerator::AreEquivalentRelativeRanks( void MockWinRankGenerator::ConvertToRelativeRanks( const unsigned short absolute[DDS_SUITS], unsigned short relative[DDS_SUITS]) { - + // Simple conversion - for testing purposes for (int suit = 0; suit < DDS_SUITS; suit++) { relative[suit] = absolute[suit]; @@ -387,15 +387,15 @@ MockDataFactory::MockDataFactory(unsigned int baseSeed) MockDataFactory::TestScenario MockDataFactory::CreateBasicScenario() { TestScenario scenario; - + posGen_.GenerateEarlyGamePosition( scenario.trick, scenario.hand, scenario.aggrTarget, scenario.hand_dist); - + rankGen_.GenerateSimpleWinRanks(scenario.win_ranks); posGen_.GenerateNodeCardsType(scenario.nodeData, 13 - scenario.trick); handGen_.GenerateStandardHandLookup(scenario.handLookup); - + IncrementSeed(); return scenario; } @@ -403,15 +403,15 @@ MockDataFactory::TestScenario MockDataFactory::CreateBasicScenario() MockDataFactory::TestScenario MockDataFactory::CreateComplexScenario() { TestScenario scenario; - + posGen_.GenerateMiddleGamePosition( scenario.trick, scenario.hand, scenario.aggrTarget, scenario.hand_dist); - + rankGen_.GenerateComplexWinRanks(scenario.win_ranks); posGen_.GenerateNodeCardsType(scenario.nodeData, 13 - scenario.trick); handGen_.GenerateRandomHandLookup(scenario.handLookup); - + IncrementSeed(); return scenario; } @@ -419,15 +419,15 @@ MockDataFactory::TestScenario MockDataFactory::CreateComplexScenario() MockDataFactory::TestScenario MockDataFactory::CreateEdgeCaseScenario() { TestScenario scenario; - + posGen_.GenerateEndGamePosition( scenario.trick, scenario.hand, scenario.aggrTarget, scenario.hand_dist); - + rankGen_.GenerateNoWinRanks(scenario.win_ranks); posGen_.GenerateNodeCardsType(scenario.nodeData, 13 - scenario.trick); handGen_.GenerateStandardHandLookup(scenario.handLookup); - + IncrementSeed(); return scenario; } @@ -435,15 +435,15 @@ MockDataFactory::TestScenario MockDataFactory::CreateEdgeCaseScenario() MockDataFactory::TestScenario MockDataFactory::CreatePerformanceScenario() { TestScenario scenario; - + posGen_.GenerateMiddleGamePosition( scenario.trick, scenario.hand, scenario.aggrTarget, scenario.hand_dist); - + rankGen_.GenerateMultiSuitWin(scenario.win_ranks); posGen_.GenerateNodeCardsType(scenario.nodeData, 13 - scenario.trick); handGen_.GenerateStandardHandLookup(scenario.handLookup); - + IncrementSeed(); return scenario; } @@ -453,10 +453,10 @@ MockDataFactory::CreateEquivalentScenarios() { TestScenario scenario1 = CreateBasicScenario(); TestScenario scenario2 = scenario1; // Copy for equivalence - + // Make them equivalent in relative rank terms rankGen_.GenerateEquivalentWinRanks(scenario1.win_ranks, scenario2.win_ranks); - + return std::make_pair(scenario1, scenario2); } @@ -465,7 +465,7 @@ MockDataFactory::CreateNonEquivalentScenarios() { TestScenario scenario1 = CreateBasicScenario(); TestScenario scenario2 = CreateComplexScenario(); - + return std::make_pair(scenario1, scenario2); } @@ -473,7 +473,7 @@ std::vector MockDataFactory::CreateTestSuite(int { std::vector scenarios; scenarios.reserve(count); - + for (int i = 0; i < count; i++) { if (i % 4 == 0) { scenarios.push_back(CreateBasicScenario()); @@ -485,19 +485,19 @@ std::vector MockDataFactory::CreateTestSuite(int scenarios.push_back(CreatePerformanceScenario()); } } - + return scenarios; } std::vector MockDataFactory::CreateRegressionTestSuite() { std::vector scenarios; - + // Add specific scenarios for regression testing scenarios.push_back(CreateBasicScenario()); scenarios.push_back(CreateComplexScenario()); scenarios.push_back(CreateEdgeCaseScenario()); - + return scenarios; } diff --git a/library/tests/trans_table/mock_data_generators.hpp b/library/tests/trans_table/mock_data_generators.hpp index 1781062ad..5bf4c43b4 100644 --- a/library/tests/trans_table/mock_data_generators.hpp +++ b/library/tests/trans_table/mock_data_generators.hpp @@ -19,29 +19,29 @@ namespace dds_test { class MockHandGenerator { public: MockHandGenerator(unsigned int seed = 12345); - + // Generate realistic hand distributions void GenerateRandomDistribution(int hand_dist[DDS_HANDS]); void GenerateBalancedDistribution(int hand_dist[DDS_HANDS]); void GenerateUnbalancedDistribution(int hand_dist[DDS_HANDS]); - + // Generate specific distribution patterns void GenerateVoidSuitDistribution(int hand_dist[DDS_HANDS], int suitToVoid); void GenerateLongSuitDistribution(int hand_dist[DDS_HANDS], int suitToExtend); - + // Generate hand lookup tables void GenerateStandardHandLookup(int handLookup[][15]); void GenerateRandomHandLookup(int handLookup[][15]); - + // Utilities bool IsValidDistribution(const int hand_dist[DDS_HANDS]) const; void PrintDistribution(const int hand_dist[DDS_HANDS]) const; - + private: std::mt19937 generator_; std::uniform_int_distribution cardDist_; std::uniform_int_distribution suitDist_; - + void EnsureValidTotalCards(int hand_dist[DDS_HANDS], int targetTotal = 13); }; @@ -49,34 +49,34 @@ class MockHandGenerator { class MockPositionGenerator { public: MockPositionGenerator(unsigned int seed = 54321); - + // Generate position data for different trick numbers void GenerateEarlyGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS] ); - + void GenerateMiddleGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS] ); - + void GenerateEndGamePosition( int& trick, int& hand, unsigned short aggrTarget[DDS_SUITS], int hand_dist[DDS_HANDS] ); - + // Generate aggregate target data void GenerateAggrTarget(unsigned short aggrTarget[DDS_SUITS], int complexity = 1); void GenerateSimpleAggrTarget(unsigned short aggrTarget[DDS_SUITS]); void GenerateComplexAggrTarget(unsigned short aggrTarget[DDS_SUITS]); - + // Generate node card data void GenerateNodeCardsType(NodeCards& node, int tricksRemaining); - + // Generate realistic game sequences struct GameSequence { std::vector tricks; @@ -84,15 +84,15 @@ class MockPositionGenerator { std::vector> aggrTargets; std::vector> hand_dists; }; - + GameSequence GenerateGameSequence(int startTrick, int endTrick); - + private: std::mt19937 generator_; std::uniform_int_distribution trickDist_; std::uniform_int_distribution hand_dist_; std::uniform_int_distribution aggrDist_; - + void AdjustForTrickNumber(int trick, int hand_dist[DDS_HANDS]); }; @@ -100,7 +100,7 @@ class MockPositionGenerator { class MockWinRankGenerator { public: MockWinRankGenerator(unsigned int seed = 98765); - + // Generate winning rank patterns void GenerateSimpleWinRanks(unsigned short win_ranks[DDS_SUITS]); void GenerateComplexWinRanks(unsigned short win_ranks[DDS_SUITS]); @@ -108,43 +108,43 @@ class MockWinRankGenerator { unsigned short win_ranks1[DDS_SUITS], unsigned short win_ranks2[DDS_SUITS] ); - + // Generate relative rank scenarios void GenerateRelativeRankScenario( unsigned short absoluteRanks[DDS_SUITS], unsigned short relativeRanks[DDS_SUITS], unsigned short winMask[DDS_SUITS] ); - + // Generate patterns for specific test scenarios void GenerateSingleSuitWin(unsigned short win_ranks[DDS_SUITS], int suit); void GenerateMultiSuitWin(unsigned short win_ranks[DDS_SUITS]); void GenerateNoWinRanks(unsigned short win_ranks[DDS_SUITS]); - + // Generate suit-specific patterns void GenerateHighCardPattern(unsigned short& suitRanks); void GenerateLowCardPattern(unsigned short& suitRanks); void GenerateSequencePattern(unsigned short& suitRanks); void GenerateGappedPattern(unsigned short& suitRanks); - + // Utilities for rank manipulation static bool AreEquivalentRelativeRanks( const unsigned short ranks1[DDS_SUITS], const unsigned short ranks2[DDS_SUITS] ); - + static void ConvertToRelativeRanks( const unsigned short absolute[DDS_SUITS], unsigned short relative[DDS_SUITS] ); - + void PrintWinRanks(const unsigned short win_ranks[DDS_SUITS]) const; - + private: std::mt19937 generator_; std::uniform_int_distribution rankDist_; std::bernoulli_distribution coinFlip_; - + // Helper to ensure valid rank patterns void EnsureValidRankPattern(unsigned short& ranks); unsigned short CreateRankSequence(int start, int length); @@ -154,7 +154,7 @@ class MockWinRankGenerator { class MockDataFactory { public: MockDataFactory(unsigned int baseSeed = 11111); - + // Create complete test scenarios struct TestScenario { int trick; @@ -165,26 +165,26 @@ class MockDataFactory { NodeCards nodeData; int handLookup[DDS_HANDS][15]; }; - + TestScenario CreateBasicScenario(); TestScenario CreateComplexScenario(); TestScenario CreateEdgeCaseScenario(); TestScenario CreatePerformanceScenario(); - + // Create matching scenarios for equivalence testing std::pair CreateEquivalentScenarios(); std::pair CreateNonEquivalentScenarios(); - + // Create test data sets std::vector CreateTestSuite(int count); std::vector CreateRegressionTestSuite(); - + private: MockHandGenerator handGen_; MockPositionGenerator posGen_; MockWinRankGenerator rankGen_; unsigned int currentSeed_; - + void IncrementSeed() { currentSeed_++; } }; diff --git a/library/tests/trans_table/test_utilities.cpp b/library/tests/trans_table/test_utilities.cpp index 560e7613b..f5a5e3ff2 100644 --- a/library/tests/trans_table/test_utilities.cpp +++ b/library/tests/trans_table/test_utilities.cpp @@ -45,23 +45,23 @@ void TransTableTestBase::CreateTestPositionData( int hand_dist[DDS_HANDS], unsigned short win_ranks[DDS_SUITS], NodeCards& nodeData) { - + // Create simple test position data for (int suit = 0; suit < DDS_SUITS; suit++) { aggrTarget[suit] = static_cast(0x1000 + suit * 0x100); win_ranks[suit] = static_cast(0x2000 + suit * 0x200); } - + for (int h = 0; h < DDS_HANDS; h++) { hand_dist[h] = 13 - trick; // Decreasing cards as tricks progress } - + // Initialize node data nodeData.upper_bound = static_cast(13 - trick); nodeData.lower_bound = static_cast(trick > 0 ? trick - 1 : 0); nodeData.best_move_suit = static_cast(0); nodeData.best_move_rank = static_cast(14); // Ace - + for (int suit = 0; suit < DDS_SUITS; suit++) { nodeData.least_win[suit] = static_cast(2 + suit); // 2, 3, 4, 5 } @@ -159,12 +159,12 @@ void PerformanceTimer::EndOperation(const std::string& name) if (isRunning_) { auto elapsed = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - startTime_); - + auto& stats = operations_[name]; stats.totalTime += elapsed; stats.minTime = std::min(stats.minTime, elapsed); stats.maxTime = std::max(stats.maxTime, elapsed); - + isRunning_ = false; } } @@ -172,24 +172,24 @@ void PerformanceTimer::EndOperation(const std::string& name) void PerformanceTimer::PrintResults() const { std::cout << "Performance Results:\n"; std::cout << std::setw(20) << "Operation" - << std::setw(10) << "Count" - << std::setw(12) << "Total(ms)" - << std::setw(12) << "Avg(ms)" - << std::setw(10) << "Min(ms)" - << std::setw(10) << "Max(ms)" << "\n"; + << std::setw(10) << "Count" + << std::setw(12) << "Total(ms)" + << std::setw(12) << "Avg(ms)" + << std::setw(10) << "Min(ms)" + << std::setw(10) << "Max(ms)" << "\n"; std::cout << std::string(74, '-') << "\n"; - + for (const auto& op : operations_) { const auto& stats = op.second; double avgTime = stats.count > 0 ? static_cast(stats.totalTime.count()) / stats.count : 0.0; - + std::cout << std::setw(20) << op.first - << std::setw(10) << stats.count - << std::setw(12) << stats.totalTime.count() - << std::setw(12) << std::fixed << std::setprecision(2) << avgTime - << std::setw(10) << stats.minTime.count() - << std::setw(10) << stats.maxTime.count() << "\n"; + << std::setw(10) << stats.count + << std::setw(12) << stats.totalTime.count() + << std::setw(12) << std::fixed << std::setprecision(2) << avgTime + << std::setw(10) << stats.minTime.count() + << std::setw(10) << stats.maxTime.count() << "\n"; } } @@ -199,17 +199,17 @@ bool PositionComparator::AreEqual(const NodeCards& a, const NodeCards& b) if (a.upper_bound != b.upper_bound || a.lower_bound != b.lower_bound) { return false; } - + if (a.best_move_suit != b.best_move_suit || a.best_move_rank != b.best_move_rank) { return false; } - + for (int suit = 0; suit < DDS_SUITS; suit++) { if (a.least_win[suit] != b.least_win[suit]) { return false; } } - + return true; } @@ -217,7 +217,7 @@ bool PositionComparator::BoundsAreEquivalent( const NodeCards& a, const NodeCards& b, int tolerance) { - + return (abs(a.upper_bound - b.upper_bound) <= tolerance) && (abs(a.lower_bound - b.lower_bound) <= tolerance); } @@ -226,17 +226,17 @@ bool PositionComparator::RelativeRanksMatch( const unsigned short ranks1[DDS_SUITS], const unsigned short ranks2[DDS_SUITS], const unsigned short winMask[DDS_SUITS]) { - + for (int suit = 0; suit < DDS_SUITS; suit++) { // Apply mask and compare unsigned short masked1 = ranks1[suit] & winMask[suit]; unsigned short masked2 = ranks2[suit] & winMask[suit]; - + if (masked1 != masked2) { return false; } } - + return true; } @@ -248,13 +248,13 @@ std::string PositionComparator::PositionToString(const NodeCards& node) << ", bestMove:" << static_cast(node.best_move_suit) << "/" << static_cast(node.best_move_rank) << ", least_win:["; - + for (int suit = 0; suit < DDS_SUITS; suit++) { if (suit > 0) oss << ","; oss << static_cast(node.least_win[suit]); } oss << "]}"; - + return oss.str(); } @@ -280,7 +280,7 @@ bool TestDataValidator::IsValidHandDistribution(const int hand_dist[DDS_HANDS]) } total += hand_dist[hand]; } - + // Total should be reasonable for remaining cards return total >= 0 && total <= 52; } @@ -315,20 +315,20 @@ bool TestDataValidator::IsValidNodeData(const NodeCards& node) node.lower_bound > node.upper_bound) { return false; } - + // Check suit/rank are in valid range if (node.best_move_suit < 0 || node.best_move_suit >= DDS_SUITS || node.best_move_rank < 2 || node.best_move_rank > 14) { return false; } - + // Check least_win values for (int suit = 0; suit < DDS_SUITS; suit++) { if (node.least_win[suit] < 0 || node.least_win[suit] > 14) { return false; } } - + return true; } diff --git a/library/tests/trans_table/test_utilities.hpp b/library/tests/trans_table/test_utilities.hpp index 9a51b9a55..ab8d11228 100644 --- a/library/tests/trans_table/test_utilities.hpp +++ b/library/tests/trans_table/test_utilities.hpp @@ -20,10 +20,10 @@ class TransTableTestBase { protected: virtual void SetUp(); virtual void TearDown(); - + // Helper to create standard hand lookup table void CreateStandardHandLookup(int handLookup[][15]); - + // Helper to create test position data void CreateTestPositionData( int trick, @@ -33,10 +33,10 @@ class TransTableTestBase { unsigned short win_ranks[DDS_SUITS], NodeCards& nodeData ); - + // Helper to verify memory usage patterns double GetInitialMemoryUsage(TransTable* table); - + // Standard test data int standardHandLookup[DDS_HANDS][15]; bool setupComplete; @@ -47,15 +47,15 @@ class MemoryTracker { public: MemoryTracker(TransTable* table); ~MemoryTracker(); - + double GetInitialUsage() const { return initialUsage_; } double GetCurrentUsage() const; double GetPeakUsage() const { return peakUsage_; } void UpdatePeak(); - + // Check for memory leaks bool HasMemoryLeak() const; - + private: TransTable* table_; double initialUsage_; @@ -66,31 +66,31 @@ class MemoryTracker { class PerformanceTimer { public: PerformanceTimer(); - + void Start(); void Stop(); void Reset(); - + std::chrono::milliseconds GetElapsed() const; double GetElapsedSeconds() const; - + // For benchmark operations void StartOperation(const std::string& name); void EndOperation(const std::string& name); void PrintResults() const; - + private: std::chrono::high_resolution_clock::time_point startTime_; std::chrono::high_resolution_clock::time_point endTime_; bool isRunning_; - + struct OperationStats { std::chrono::milliseconds totalTime{0}; int count = 0; std::chrono::milliseconds minTime{std::chrono::milliseconds::max()}; std::chrono::milliseconds maxTime{0}; }; - + std::map operations_; }; @@ -99,21 +99,21 @@ class PositionComparator { public: // Compare two NodeCards structures static bool AreEqual(const NodeCards& a, const NodeCards& b); - + // Compare bounds with tolerance static bool BoundsAreEquivalent( const NodeCards& a, const NodeCards& b, int tolerance = 0 ); - + // Verify relative rank equivalence static bool RelativeRanksMatch( const unsigned short ranks1[DDS_SUITS], const unsigned short ranks2[DDS_SUITS], const unsigned short winMask[DDS_SUITS] ); - + // Helper to print position data for debugging static std::string PositionToString(const NodeCards& node); static std::string RanksToString(const unsigned short ranks[DDS_SUITS]); @@ -124,16 +124,16 @@ class TestDataValidator { public: // Validate hand distribution is legal static bool IsValidHandDistribution(const int hand_dist[DDS_HANDS]); - + // Validate aggregate target data static bool IsValidAggrTarget(const unsigned short aggrTarget[DDS_SUITS]); - + // Validate winning ranks are consistent static bool IsValidWinRanks(const unsigned short win_ranks[DDS_SUITS]); - + // Validate node data is reasonable static bool IsValidNodeData(const NodeCards& node); - + // Check if trick/hand parameters are in valid range static bool IsValidTrickHand(int trick, int hand); }; diff --git a/library/tests/trans_table/trans_table_base_test.cpp b/library/tests/trans_table/trans_table_base_test.cpp index 420a4e258..c401fa77e 100644 --- a/library/tests/trans_table/trans_table_base_test.cpp +++ b/library/tests/trans_table/trans_table_base_test.cpp @@ -35,13 +35,13 @@ class TransTableBaseTest : public ::testing::Test { public: MockTransTable() : TransTable() {} - + ~MockTransTable() override = default; auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { - adds = 0; - overwrites = 0; - harvests = 0; + adds = 0; + overwrites = 0; + harvests = 0; } auto reset_op_stats() -> void override { @@ -175,7 +175,7 @@ class TransTableBaseTest : public ::testing::Test TEST_F(TransTableBaseTest, ConstructorCreatesValidObject) { EXPECT_NE(baseTable.get(), nullptr); - + // Verify initial state EXPECT_FALSE(baseTable->init_called_); EXPECT_FALSE(baseTable->tt_made_); @@ -189,7 +189,7 @@ TEST_F(TransTableBaseTest, VirtualDestructorWorks) // Create through base pointer to verify virtual destructor std::unique_ptr basePtr = std::make_unique(); EXPECT_NE(basePtr.get(), nullptr); - + // Destructor should work properly when called through base pointer // This test verifies that destructor is virtual basePtr.reset(); // Should call derived destructor properly @@ -200,7 +200,7 @@ TEST_F(TransTableBaseTest, VirtualDestructorWorks) TEST_F(TransTableBaseTest, InitMethodCallsOverride) { int handLookup[15][15] = {}; // Mock lookup table (zero-initialized) - + EXPECT_FALSE(baseTable->init_called_); baseTable->init(handLookup); EXPECT_TRUE(baseTable->init_called_); @@ -252,9 +252,9 @@ TEST_F(TransTableBaseTest, LookupMethodCallsOverride) const unsigned short aggrTarget[DDS_SUITS] = {0x1111, 0x2222, 0x3333, 0x4444}; const int hand_dist[4] = {13, 13, 13, 13}; bool lowerFlag = true; - + EXPECT_FALSE(baseTable->lookup_called_); - + NodeCards const* result = baseTable->lookup( 10, // trick 2, // hand @@ -263,7 +263,7 @@ TEST_F(TransTableBaseTest, LookupMethodCallsOverride) 8, // limit lowerFlag ); - + EXPECT_TRUE(baseTable->lookup_called_); EXPECT_EQ(baseTable->last_trick_, 10); EXPECT_EQ(baseTable->last_hand_, 2); @@ -277,9 +277,9 @@ TEST_F(TransTableBaseTest, AddMethodCallsOverride) const unsigned short aggrTarget[DDS_SUITS] = {0x1111, 0x2222, 0x3333, 0x4444}; const unsigned short win_ranks[DDS_SUITS] = {0x5555, 0x6666, 0x7777, 0x8888}; NodeCards nodeData; - + EXPECT_FALSE(baseTable->add_called_); - + baseTable->add( 8, // trick 1, // hand @@ -288,7 +288,7 @@ TEST_F(TransTableBaseTest, AddMethodCallsOverride) nodeData, true // flag ); - + EXPECT_TRUE(baseTable->add_called_); EXPECT_EQ(baseTable->add_trick_, 8); EXPECT_EQ(baseTable->add_hand_, 1); @@ -298,16 +298,16 @@ TEST_F(TransTableBaseTest, AddMethodCallsOverride) TEST_F(TransTableBaseTest, PrintMethodsCallOverride) { std::ofstream testFile("test_output.txt"); - + EXPECT_FALSE(baseTable->print_suits_called_); EXPECT_FALSE(baseTable->print_all_suits_called_); - + baseTable->print_suits(testFile, 5, 2); // trick=5, hand=2 EXPECT_TRUE(baseTable->print_suits_called_); - + baseTable->print_all_suits(testFile); EXPECT_TRUE(baseTable->print_all_suits_called_); - + testFile.close(); std::remove("test_output.txt"); // Cleanup } @@ -317,32 +317,32 @@ TEST_F(TransTableBaseTest, AllVirtualMethodsHaveExpectedSignatures) { // This test verifies that all expected virtual methods exist // and have the correct signatures by calling them through base pointer - + std::unique_ptr basePtr = std::make_unique(); - + // Test that we can call all virtual methods through base pointer int handLookup[15][15]; basePtr->init(handLookup); - + basePtr->set_memory_default(64); basePtr->set_memory_maximum(128); basePtr->make_tt(); basePtr->reset_memory(ResetReason::NewDeal); basePtr->return_all_memory(); - + double mem = basePtr->memory_in_use(); EXPECT_GE(mem, 0.0); // Should return non-negative value - + unsigned short aggrTarget[DDS_SUITS] = {0, 0, 0, 0}; unsigned short win_ranks[DDS_SUITS] = {0, 0, 0, 0}; int hand_dist[4] = {0, 0, 0, 0}; bool lowerFlag = false; NodeCards nodeData; - + // Should not crash when called through base pointer basePtr->lookup(0, 0, aggrTarget, hand_dist, 0, lowerFlag); basePtr->add(0, 0, aggrTarget, win_ranks, nodeData, false); - + std::ofstream nullFile("/dev/null"); basePtr->print_suits(nullFile, 0, 1); // trick=0, hand=1 basePtr->print_all_suits(nullFile); @@ -353,14 +353,14 @@ TEST_F(TransTableBaseTest, PolymorphicBehaviorWorks) { // Verify that virtual dispatch works correctly TransTable* basePtr = baseTable.get(); - + // Calls should go to derived class implementations basePtr->make_tt(); EXPECT_TRUE(baseTable->tt_made_); - + basePtr->return_all_memory(); EXPECT_TRUE(baseTable->memory_returned_); - + double memUsage = basePtr->memory_in_use(); EXPECT_EQ(memUsage, 42.5); // Should call derived implementation } diff --git a/library/tests/trans_table/trans_table_l_test.cpp b/library/tests/trans_table/trans_table_l_test.cpp index 1d0b98ac7..f857c490b 100644 --- a/library/tests/trans_table/trans_table_l_test.cpp +++ b/library/tests/trans_table/trans_table_l_test.cpp @@ -52,7 +52,7 @@ TEST(TransTableLBasicTest, BasicTypesWork) { for (int i = 0; i < DDS_SUITS; ++i) { testArray[i] = i * 0x1111; } - + EXPECT_EQ(testArray[0], 0x0000); EXPECT_EQ(testArray[1], 0x1111); EXPECT_EQ(testArray[2], 0x2222); @@ -63,18 +63,18 @@ TEST(TransTableLBasicTest, BasicTypesWork) { TEST(TransTableLBasicTest, HelperFunctionsWork) { int handLookup[15][15]; CreateBasicHandLookup(handLookup); - + // Check that lookup table has expected pattern EXPECT_EQ(handLookup[0][0], 0); EXPECT_EQ(handLookup[1][0], 1); EXPECT_EQ(handLookup[0][1], 1); EXPECT_EQ(handLookup[1][1], 2); - + unsigned short aggrTarget[DDS_SUITS]; CreateTestAggrTarget(aggrTarget); EXPECT_EQ(aggrTarget[0], 0x1111); EXPECT_EQ(aggrTarget[3], 0x4444); - + unsigned short win_ranks[DDS_SUITS]; CreateTestWinRanks(win_ranks); EXPECT_EQ(win_ranks[0], 0x5555); @@ -84,14 +84,14 @@ TEST(TransTableLBasicTest, HelperFunctionsWork) { // Test large memory management scenarios TEST(TransTableLAdvancedTest, LargeMemoryManagement) { // Test different large memory scenarios without actual allocation - + // Simulate large memory limits (TransTableL is for large memory scenarios) struct LargeMemoryLimits { int defaultMB; int maximumMB; bool shouldSucceed; }; - + LargeMemoryLimits testCases[] = { {64, 128, true}, // Normal large case {128, 256, true}, // Large case @@ -100,11 +100,11 @@ TEST(TransTableLAdvancedTest, LargeMemoryManagement) { {1024, 512, false}, // Invalid: default > maximum {0, 64, true}, // Zero default (should use defaults) }; - + for (const auto& test : testCases) { bool isValid = (test.defaultMB <= test.maximumMB) && - (test.defaultMB >= 0) && - (test.maximumMB >= 0); + (test.defaultMB >= 0) && + (test.maximumMB >= 0); EXPECT_EQ(isValid, test.shouldSucceed) << "Large memory limit check failed for default=" << test.defaultMB << " maximum=" << test.maximumMB; @@ -120,7 +120,7 @@ TEST(TransTableLAdvancedTest, PageBasedMemoryOrganization) { int entriesPerPage; bool valid; }; - + PageInfo testCases[] = { {4096, 256, 64, true}, // Standard page configuration {8192, 128, 128, true}, // Larger pages, fewer pages @@ -129,11 +129,11 @@ TEST(TransTableLAdvancedTest, PageBasedMemoryOrganization) { {4096, 0, 64, false}, // Invalid: zero pages {4096, 256, 0, false}, // Invalid: zero entries per page }; - + for (const auto& test : testCases) { bool isValid = (test.pageSize > 0) && - (test.totalPages > 0) && - (test.entriesPerPage > 0); + (test.totalPages > 0) && + (test.entriesPerPage > 0); EXPECT_EQ(isValid, test.valid) << "Page organization check failed for pageSize=" << test.pageSize << " totalPages=" << test.totalPages @@ -149,16 +149,16 @@ TEST(TransTableLAdvancedTest, HashTableDistribution) { int expectedBucket; int totalBuckets; }; - + int totalBuckets = 1024; // Large table has many buckets - + HashDistribution testCases[] = { {0x0000000000000000, 0, totalBuckets}, {0x0000000000000001, 1, totalBuckets}, {0x00000000000003FF, 1023, totalBuckets}, // Should map to last bucket {0x0000000000000400, 0, totalBuckets}, // Should wrap around }; - + for (const auto& test : testCases) { int actualBucket = test.hash % test.totalBuckets; EXPECT_EQ(actualBucket, test.expectedBucket) @@ -178,7 +178,7 @@ TEST(TransTableLAdvancedTest, BlockAllocationRecycling) { int freeBlocks; bool consistent; }; - + BlockAllocation testCases[] = { {64, 1000, 600, 400, true}, // Normal usage {128, 500, 300, 200, true}, // Larger blocks @@ -186,7 +186,7 @@ TEST(TransTableLAdvancedTest, BlockAllocationRecycling) { {64, 1000, 1200, -200, false}, // Invalid: more used than total {64, 1000, 600, 500, false}, // Invalid: used + free > total }; - + for (const auto& test : testCases) { bool isConsistent = (test.usedBlocks >= 0) && (test.freeBlocks >= 0) && @@ -209,7 +209,7 @@ TEST(TransTableLAdvancedTest, TimestampBasedAging) { uint32_t maxAge; bool shouldAge; }; - + TimestampEntry testCases[] = { {1000, 2000, 500, true}, // Old entry should age (age=1000 >= 500) {1500, 2000, 1000, false}, // Entry at age limit should not age (age=500 < 1000) @@ -217,11 +217,11 @@ TEST(TransTableLAdvancedTest, TimestampBasedAging) { {2000, 2000, 1000, false}, // Current entry should not age (age=0 < 1000) {2100, 2000, 1000, false}, // Future timestamp (edge case, age=0) }; - + for (const auto& test : testCases) { uint32_t age = (test.currentTime >= test.timestamp) - ? (test.currentTime - test.timestamp) - : 0; + ? (test.currentTime - test.timestamp) + : 0; bool shouldAge = (age >= test.maxAge); EXPECT_EQ(shouldAge, test.shouldAge) << "Timestamp aging check failed for" @@ -241,22 +241,22 @@ TEST(TransTableLAdvancedTest, LookupEfficiencyCharacteristics) { int totalLookups; double expectedEfficiency; }; - + LookupPattern patterns[] = { {8000, 2000, 10000, 0.85}, // Mostly sequential in large table {5000, 5000, 10000, 0.70}, // Mixed pattern {2000, 8000, 10000, 0.55}, // Mostly random {1000, 1000, 2000, 0.75}, // Small mixed workload }; - + for (const auto& pattern : patterns) { // Large tables should have better random access than small tables double totalLookups = pattern.totalLookups; double sequentialRatio = pattern.sequentialLookups / totalLookups; - + // Large tables have better base performance and less penalty for random access double simulatedEfficiency = 0.6 + (sequentialRatio * 0.35); // Better base than TransTableS - + EXPECT_GE(simulatedEfficiency, 0.0); EXPECT_LE(simulatedEfficiency, 1.0); EXPECT_TRUE(std::abs(simulatedEfficiency - pattern.expectedEfficiency) < 0.25) @@ -276,10 +276,10 @@ TEST(TransTableLAdvancedTest, MultiLevelHashLookup) { int level2Bucket; bool validLookup; }; - + int level1Buckets = 1024; int level2Buckets = 64; - + HashLookup testCases[] = { {0x1234567890ABCDEF, 0x12345678, (int)(0x1234567890ABCDEF % level1Buckets), @@ -289,11 +289,11 @@ TEST(TransTableLAdvancedTest, MultiLevelHashLookup) { (int)(0xFFFFFFFFFFFFFFFF % level1Buckets), (int)(0xFFFFFFFF % level2Buckets), true}, }; - + for (const auto& test : testCases) { int actualLevel1 = test.primaryHash % level1Buckets; int actualLevel2 = test.secondaryHash % level2Buckets; - + EXPECT_EQ(actualLevel1, test.level1Bucket); EXPECT_EQ(actualLevel2, test.level2Bucket); EXPECT_GE(actualLevel1, 0); @@ -315,7 +315,7 @@ TEST(TransTableLHarvestTest, HarvestMechanismForMemoryReclamation) { int expectedReclaimed; bool harvestTriggered; }; - + HarvestScenario testCases[] = { {1000, 800, 200, 800, true}, // High old entry ratio should trigger harvest {1000, 300, 700, 300, false}, // Low old entry ratio should not trigger @@ -323,19 +323,19 @@ TEST(TransTableLHarvestTest, HarvestMechanismForMemoryReclamation) { {100, 10, 90, 10, false}, // Low percentage old should not trigger {0, 0, 0, 0, false}, // Empty table should not trigger }; - + for (const auto& test : testCases) { double oldRatio = (test.totalEntries > 0) ? (double)test.oldEntries / test.totalEntries : 0.0; bool shouldHarvest = (oldRatio > 0.5) && (test.totalEntries > 50); - + EXPECT_EQ(shouldHarvest, test.harvestTriggered) << "Harvest trigger logic failed for" << " totalEntries=" << test.totalEntries << " oldEntries=" << test.oldEntries << " oldRatio=" << oldRatio; - + if (shouldHarvest) { EXPECT_EQ(test.expectedReclaimed, test.oldEntries) << "Expected to reclaim all old entries"; @@ -351,7 +351,7 @@ TEST(TransTableLHarvestTest, HarvestAgeThresholdHandling) { uint32_t harvestThreshold; bool shouldReclaim; }; - + AgeThreshold testCases[] = { {1000, 500, true}, // Age > threshold should reclaim {500, 500, false}, // Age = threshold should not reclaim @@ -359,7 +359,7 @@ TEST(TransTableLHarvestTest, HarvestAgeThresholdHandling) { {2000, 1000, true}, // Very old should definitely reclaim {50, 1000, false}, // Very recent should not reclaim }; - + for (const auto& test : testCases) { bool shouldReclaim = (test.entryAge > test.harvestThreshold); EXPECT_EQ(shouldReclaim, test.shouldReclaim) @@ -378,20 +378,20 @@ TEST(TransTableLHarvestTest, MemoryReclamationEfficiency) { int expectedReclaimed; double efficiency; }; - + ReclamationStats testCases[] = { {1000, 200, 800, 1.0}, // Perfect efficiency: reclaimed exactly expected {1000, 400, 600, 1.0}, // Perfect efficiency: reclaimed exactly expected {1000, 700, 300, 1.0}, // Perfect efficiency: reclaimed exactly expected {500, 100, 400, 1.0}, // Perfect efficiency: reclaimed exactly expected }; - + for (const auto& test : testCases) { int actualReclaimed = test.memoryBefore - test.memoryAfter; double actualEfficiency = (test.expectedReclaimed > 0) ? (double)actualReclaimed / test.expectedReclaimed : 0.0; - + EXPECT_GE(actualReclaimed, 0) << "Memory should not increase after reclamation"; EXPECT_LE(actualReclaimed, test.memoryBefore) @@ -410,7 +410,7 @@ TEST(TransTableLPageTest, PageAllocationAndDeallocation) { int freePages; bool validState; }; - + PageManagement testCases[] = { {1000, 600, 400, true}, // Normal allocation {500, 500, 0, true}, // Fully allocated @@ -418,11 +418,11 @@ TEST(TransTableLPageTest, PageAllocationAndDeallocation) { {1000, 1200, -200, false}, // Invalid: over-allocated {1000, 300, 800, false}, // Invalid: allocated + free > total }; - + for (const auto& test : testCases) { bool isValid = (test.allocatedPages >= 0) && - (test.freePages >= 0) && - (test.allocatedPages + test.freePages <= test.totalPages); + (test.freePages >= 0) && + (test.allocatedPages + test.freePages <= test.totalPages); EXPECT_EQ(isValid, test.validState) << "Page management validation failed for" << " totalPages=" << test.totalPages @@ -440,7 +440,7 @@ TEST(TransTableLPageTest, PageOverflowHandling) { int pagesNeeded; bool overflowHandled; }; - + PageOverflow testCases[] = { {64, 100, 2, true}, // Requires 2 pages for 100 entries {128, 128, 1, true}, // Exactly fits in 1 page @@ -448,13 +448,13 @@ TEST(TransTableLPageTest, PageOverflowHandling) { {64, 63, 1, true}, // Less than 1 page capacity {0, 100, 0, false}, // Invalid: zero capacity }; - + for (const auto& test : testCases) { int actualPages = (test.pageCapacity > 0) ? ((test.entriesRequested + test.pageCapacity - 1) / test.pageCapacity) : 0; bool handled = (test.pageCapacity > 0) && (actualPages > 0); - + EXPECT_EQ(handled, test.overflowHandled); if (handled) { EXPECT_EQ(actualPages, test.pagesNeeded) @@ -474,27 +474,27 @@ TEST(TransTableLComplexTest, ComplexCardPatternMatching) { bool shouldMatch; const char* description; }; - + CardPattern testCases[] = { {{0x1F00, 0x0F80, 0x07C0, 0x03E0}, {0x1000, 0x0800, 0x0400, 0x0200}, true, "High cards with wins"}, {{0x001F, 0x001F, 0x001F, 0x001F}, {0x0010, 0x0010, 0x0010, 0x0010}, true, "Low cards with wins"}, {{0x0000, 0x0000, 0x0000, 0x1FFF}, {0x0000, 0x0000, 0x0000, 0x1000}, true, "Single suit distribution"}, {{0xFFFF, 0x0000, 0x0000, 0x0000}, {0x8000, 0x0000, 0x0000, 0x0000}, true, "Void suits handled"}, }; - + for (const auto& test : testCases) { // Verify pattern consistency bool hasCards = false; - + for (int suit = 0; suit < DDS_SUITS; ++suit) { if (test.pattern[suit] > 0) hasCards = true; - + // Win ranks should be subset of pattern EXPECT_EQ((test.win_ranks[suit] & test.pattern[suit]), test.win_ranks[suit]) << "Win ranks not subset of pattern for suit " << suit << " in test: " << test.description; } - + EXPECT_TRUE(hasCards) << "Pattern should have some cards: " << test.description; } } @@ -508,22 +508,22 @@ TEST(TransTableLComplexTest, LargePositionDatasetLookup) { int expectedHits; double hitRatio; }; - + LargeDataset testCases[] = { {10000, 1000, 800, 0.8}, // Large dataset, good hit ratio {50000, 5000, 3500, 0.7}, // Very large dataset, decent hit ratio {100000, 2000, 1000, 0.5}, // Huge dataset, moderate hit ratio {1000, 100, 90, 0.9}, // Smaller dataset, high hit ratio }; - + for (const auto& test : testCases) { double actualHitRatio = (test.lookupQueries > 0) ? (double)test.expectedHits / test.lookupQueries : 0.0; - + EXPECT_DOUBLE_EQ(actualHitRatio, test.hitRatio) << "Hit ratio calculation incorrect for dataset size " << test.datasetSize; - + EXPECT_LE(test.expectedHits, test.lookupQueries) << "Cannot have more hits than queries"; EXPECT_LE(test.expectedHits, test.datasetSize) @@ -541,25 +541,25 @@ TEST(TransTableLMemoryTest, MemoryFragmentationHandling) { double fragmentationRatio; bool wellManaged; }; - + FragmentationScenario testCases[] = { {1000, 600, 350, 0.1, true}, // Low fragmentation, well managed {1000, 800, 150, 0.3, true}, // Moderate fragmentation, acceptable {1000, 900, 50, 0.5, false}, // High fragmentation, poorly managed {1000, 500, 500, 0.0, true}, // No fragmentation, perfect management }; - + for (const auto& test : testCases) { int freeMemory = test.totalMemory - test.usedMemory; double actualFragmentation = (freeMemory > 0) ? 1.0 - ((double)test.largestFreeBlock / freeMemory) : 0.0; - + EXPECT_GE(test.largestFreeBlock, 0); EXPECT_LE(test.largestFreeBlock, freeMemory); EXPECT_TRUE(std::abs(actualFragmentation - test.fragmentationRatio) < 0.1) << "Fragmentation ratio outside expected range"; - + bool isWellManaged = (actualFragmentation < 0.4); EXPECT_EQ(isWellManaged, test.wellManaged) << "Fragmentation management assessment incorrect"; @@ -575,7 +575,7 @@ TEST(TransTableLMemoryTest, LargeMemoryUsagePatterns) { int sustainedMB; bool efficientPattern; }; - + MemoryPattern testCases[] = { {64, 128, 96, true}, // Efficient: reasonable peak, good sustained {128, 1024, 200, false}, // Inefficient: huge peak, low sustained @@ -583,16 +583,16 @@ TEST(TransTableLMemoryTest, LargeMemoryUsagePatterns) { {512, 512, 512, true}, // Efficient: flat usage pattern {64, 64, 128, false}, // Invalid: sustained > peak }; - + for (const auto& test : testCases) { bool validPattern = (test.sustainedMB <= test.peakMB) && (test.baseMB <= test.peakMB); - + if (validPattern) { double peakRatio = (double)test.peakMB / test.baseMB; double sustainedRatio = (double)test.sustainedMB / test.peakMB; bool isEfficient = (peakRatio < 3.0) && (sustainedRatio > 0.6); - + EXPECT_EQ(isEfficient, test.efficientPattern) << "Memory efficiency assessment incorrect for" << " base=" << test.baseMB @@ -614,7 +614,7 @@ TEST(TransTableLEdgeTest, ExtremeMemoryPressure) { int expectedAllocation; bool gracefulDegradation; }; - + MemoryPressure testCases[] = { {100, 50, 50, true}, // Normal case: enough memory {100, 100, 100, true}, // Exact fit: use all available @@ -622,16 +622,16 @@ TEST(TransTableLEdgeTest, ExtremeMemoryPressure) { {100, 1000, 100, true}, // Extreme over request: graceful fallback {0, 100, 0, true}, // No memory: graceful failure }; - + for (const auto& test : testCases) { int actualAllocation = std::min(test.availableMemory, test.requestedMemory); actualAllocation = std::max(0, actualAllocation); - + EXPECT_EQ(actualAllocation, test.expectedAllocation) << "Memory allocation under pressure incorrect for" << " available=" << test.availableMemory << " requested=" << test.requestedMemory; - + bool graceful = (actualAllocation >= 0) && (actualAllocation <= test.availableMemory); EXPECT_EQ(graceful, test.gracefulDegradation) @@ -648,7 +648,7 @@ TEST(TransTableLEdgeTest, VeryLargePositionCounts) { double expectedLoadFactor; bool manageable; }; - + LargePositionTest testCases[] = { {1000000, 10000, 100.0, true}, // 1M positions, 10K buckets {10000000, 100000, 100.0, true}, // 10M positions, 100K buckets @@ -656,15 +656,15 @@ TEST(TransTableLEdgeTest, VeryLargePositionCounts) { {1000000, 1000, 1000.0, false}, // High load factor, not manageable {10000, 100000, 0.1, true}, // Very low load factor, over-provisioned but fine }; - + for (const auto& test : testCases) { double actualLoadFactor = (test.hashBuckets > 0) ? (double)test.positionCount / test.hashBuckets : 0.0; - + EXPECT_DOUBLE_EQ(actualLoadFactor, test.expectedLoadFactor) << "Load factor calculation incorrect"; - + bool isManageable = (actualLoadFactor < 500.0) && (actualLoadFactor > 0.01); EXPECT_EQ(isManageable, test.manageable) << "Manageability assessment incorrect for load factor " << actualLoadFactor; diff --git a/library/tests/trans_table/trans_table_p_test.cpp b/library/tests/trans_table/trans_table_p_test.cpp index 5480160b3..b72f6961a 100644 --- a/library/tests/trans_table/trans_table_p_test.cpp +++ b/library/tests/trans_table/trans_table_p_test.cpp @@ -1294,7 +1294,7 @@ TEST(TransTablePEquivalenceTest, CutDecisionsMatchTransTableLOnSmallWorkloads) held.emplace_back(s, r); // Prefer low cards so that shapes and top cards repeat often. std::sort(held.begin(), held.end(), - [](auto a, auto b) { return a.second < b.second; }); + [](auto a, auto b) { return a.second < b.second; }); const size_t idx = std::min(held.size() - 1, static_cast(std::uniform_int_distribution(0, 5)(rng))); const auto [s, r] = held[idx]; diff --git a/library/tests/trans_table/trans_table_s_test.cpp b/library/tests/trans_table/trans_table_s_test.cpp index fa226f080..8421383ef 100644 --- a/library/tests/trans_table/trans_table_s_test.cpp +++ b/library/tests/trans_table/trans_table_s_test.cpp @@ -83,7 +83,7 @@ TEST(TransTableSBasicTest, BasicTypesWork) { for (int i = 0; i < DDS_SUITS; ++i) { testArray[i] = i * 0x1111; } - + EXPECT_EQ(testArray[0], 0x0000); EXPECT_EQ(testArray[1], 0x1111); EXPECT_EQ(testArray[2], 0x2222); @@ -94,18 +94,18 @@ TEST(TransTableSBasicTest, BasicTypesWork) { TEST(TransTableSBasicTest, HelperFunctionsWork) { int handLookup[15][15]; CreateBasicHandLookup(handLookup); - + // Check that lookup table has expected pattern EXPECT_EQ(handLookup[0][0], 0); EXPECT_EQ(handLookup[1][0], 1); EXPECT_EQ(handLookup[0][1], 1); EXPECT_EQ(handLookup[1][1], 2); - + unsigned short aggrTarget[DDS_SUITS]; CreateTestAggrTarget(aggrTarget); EXPECT_EQ(aggrTarget[0], 0x1111); EXPECT_EQ(aggrTarget[3], 0x4444); - + unsigned short win_ranks[DDS_SUITS]; CreateTestWinRanks(win_ranks); EXPECT_EQ(win_ranks[0], 0x5555); @@ -117,11 +117,11 @@ TEST(TransTableSAdvancedTest, RelativeRankPatterns) { // Test that we can create relative rank patterns unsigned short absoluteRanks[DDS_SUITS] = {0x1F00, 0x0F80, 0x07C0, 0x03E0}; // High cards unsigned short relativeRanks[DDS_SUITS] = {0x001F, 0x001F, 0x001F, 0x001F}; // Relative equivalent - + // Verify patterns are different but could represent equivalent positions EXPECT_NE(absoluteRanks[0], relativeRanks[0]); EXPECT_NE(absoluteRanks[1], relativeRanks[1]); - + // Both should have same number of bits set (same number of cards) auto countBits = [](unsigned short value) -> int { int count = 0; @@ -131,7 +131,7 @@ TEST(TransTableSAdvancedTest, RelativeRankPatterns) { } return count; }; - + EXPECT_EQ(countBits(absoluteRanks[0]), countBits(relativeRanks[0])); EXPECT_EQ(countBits(absoluteRanks[1]), countBits(relativeRanks[1])); } @@ -141,13 +141,13 @@ TEST(TransTableSAdvancedTest, WinningRankTracking) { // Test different winning patterns unsigned short singleSuitWin[DDS_SUITS] = {0x1000, 0x0000, 0x0000, 0x0000}; // Only spades unsigned short multiSuitWin[DDS_SUITS] = {0x1000, 0x0800, 0x0400, 0x0200}; // One from each - + // Verify winning patterns are correctly formed EXPECT_GT(singleSuitWin[0], 0); EXPECT_EQ(singleSuitWin[1], 0); EXPECT_EQ(singleSuitWin[2], 0); EXPECT_EQ(singleSuitWin[3], 0); - + EXPECT_GT(multiSuitWin[0], 0); EXPECT_GT(multiSuitWin[1], 0); EXPECT_GT(multiSuitWin[2], 0); @@ -160,16 +160,16 @@ TEST(TransTableSAdvancedTest, HandDistributionPatterns) { int balanced[4] = {3, 3, 3, 4}; // 3-3-3-4 distribution int unbalanced[4] = {7, 3, 2, 1}; // 7-3-2-1 distribution int voidSuit[4] = {0, 5, 4, 4}; // Void in one suit - + // Verify distributions sum to 13 int sum1 = balanced[0] + balanced[1] + balanced[2] + balanced[3]; int sum2 = unbalanced[0] + unbalanced[1] + unbalanced[2] + unbalanced[3]; int sum3 = voidSuit[0] + voidSuit[1] + voidSuit[2] + voidSuit[3]; - + EXPECT_EQ(sum1, 13); EXPECT_EQ(sum2, 13); EXPECT_EQ(sum3, 13); - + // Test void handling EXPECT_EQ(voidSuit[0], 0); EXPECT_GT(voidSuit[1], 0); @@ -181,7 +181,7 @@ TEST(TransTableSAdvancedTest, EdgeCaseScenarios) { unsigned short maxRanks[DDS_SUITS] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; unsigned short minRanks[DDS_SUITS] = {0x0000, 0x0000, 0x0000, 0x0000}; unsigned short mixedRanks[DDS_SUITS] = {0xFFFF, 0x0000, 0xAAAA, 0x5555}; - + // Verify extreme values don't cause issues EXPECT_EQ(maxRanks[0], 0xFFFF); EXPECT_EQ(minRanks[0], 0x0000); @@ -197,7 +197,7 @@ TEST(TransTableSAdvancedTest, BoundsCheckingLogic) { int lbound; bool valid; }; - + TestBounds testCases[] = { {10, 8, true}, // Valid: ubound > lbound {13, 0, true}, // Valid: maximum range @@ -206,11 +206,11 @@ TEST(TransTableSAdvancedTest, BoundsCheckingLogic) { {14, 10, false}, // Invalid: ubound > 13 {10, -1, false} // Invalid: negative lbound }; - + for (const auto& test : testCases) { bool isValid = (test.ubound >= test.lbound) && - (test.ubound >= 0 && test.ubound <= 13) && - (test.lbound >= 0 && test.lbound <= 13); + (test.ubound >= 0 && test.ubound <= 13) && + (test.lbound >= 0 && test.lbound <= 13); EXPECT_EQ(isValid, test.valid) << "Bounds check failed for ubound=" << (int)test.ubound << " lbound=" << (int)test.lbound; @@ -220,14 +220,14 @@ TEST(TransTableSAdvancedTest, BoundsCheckingLogic) { // Test memory management scenarios TEST(TransTableSAdvancedTest, MemoryManagementScenarios) { // Test different memory scenarios without actual allocation - + // Simulate memory limits struct MemoryLimits { int defaultMB; int maximumMB; bool shouldSucceed; }; - + MemoryLimits testCases[] = { {16, 32, true}, // Normal case {1, 2, true}, // Very small @@ -235,11 +235,11 @@ TEST(TransTableSAdvancedTest, MemoryManagementScenarios) { {0, 0, true}, // Zero (should use defaults) {32, 16, false}, // Invalid: default > maximum }; - + for (const auto& test : testCases) { bool isValid = (test.defaultMB <= test.maximumMB) && - (test.defaultMB >= 0) && - (test.maximumMB >= 0); + (test.defaultMB >= 0) && + (test.maximumMB >= 0); EXPECT_EQ(isValid, test.shouldSucceed) << "Memory limit check failed for default=" << test.defaultMB << " maximum=" << test.maximumMB; @@ -254,7 +254,7 @@ TEST(TransTableSAdvancedTest, DataValidationLogic) { int bound_type; bool valid; }; - + TTEntryData testCases[] = { {0, 1, true}, // Valid: tricks 0-13, bound type 1-3 {13, 3, true}, // Valid: maximum tricks, maximum bound @@ -264,10 +264,10 @@ TEST(TransTableSAdvancedTest, DataValidationLogic) { {6, 0, false}, // Invalid: bound type 0 {6, 4, false}, // Invalid: bound type > 3 }; - + for (const auto& test : testCases) { bool isValid = (test.tricks >= 0 && test.tricks <= 13) && - (test.bound_type >= 1 && test.bound_type <= 3); + (test.bound_type >= 1 && test.bound_type <= 3); EXPECT_EQ(isValid, test.valid) << "Data validation failed for tricks=" << (int)test.tricks << " bound_type=" << (int)test.bound_type; @@ -277,27 +277,27 @@ TEST(TransTableSAdvancedTest, DataValidationLogic) { // Test performance characteristics without actual timing TEST(TransTableSAdvancedTest, PerformanceCharacteristics) { // Test that we can simulate performance scenarios - + // Simulate different access patterns struct AccessPattern { int sequential_accesses; int random_accesses; double expected_efficiency; // 0.0 to 1.0 }; - + AccessPattern patterns[] = { {1000, 0, 0.95}, // Highly sequential {500, 500, 0.70}, // Mixed {0, 1000, 0.50}, // Highly random {100, 100, 0.80}, // Small mixed }; - + for (const auto& pattern : patterns) { // Simulate efficiency calculation double total_accesses = pattern.sequential_accesses + pattern.random_accesses; double sequential_ratio = pattern.sequential_accesses / total_accesses; double simulated_efficiency = 0.5 + (sequential_ratio * 0.45); // Simple model - + // Check that our model produces reasonable results EXPECT_GE(simulated_efficiency, 0.0); EXPECT_LE(simulated_efficiency, 1.0); diff --git a/library/tests/utility/constants_test.cpp b/library/tests/utility/constants_test.cpp index 8c53fb8cd..2c336b813 100644 --- a/library/tests/utility/constants_test.cpp +++ b/library/tests/utility/constants_test.cpp @@ -49,10 +49,10 @@ TEST_F(ConstantsTest, HandRelationshipConsistency) { for (int i = 0; i < 4; i++) { // Partner of partner should be self EXPECT_EQ(partner[partner[i]], i); - + // LHO of RHO should be self EXPECT_EQ(lho[rho[i]], i); - + // RHO of LHO should be self EXPECT_EQ(rho[lho[i]], i); } diff --git a/library/tests/utility/lookup_tables_test.cpp b/library/tests/utility/lookup_tables_test.cpp index a4f0f8b10..9d49467ed 100644 --- a/library/tests/utility/lookup_tables_test.cpp +++ b/library/tests/utility/lookup_tables_test.cpp @@ -33,7 +33,7 @@ TEST_F(LookupTablesTest, InitLookupTablesIdempotent) { init_lookup_tables(); init_lookup_tables(); init_lookup_tables(); - + // Should complete without any issues SUCCEED(); } @@ -65,7 +65,7 @@ TEST_F(LookupTablesTest, CountTableArray) { actualCount++; } } - + EXPECT_EQ(count_table[i], actualCount) << "count_table[" << i << "] should equal actual bit count"; } @@ -164,7 +164,7 @@ TEST_F(LookupTablesTest, MoveGroupTypeStruct) { testGroup.sequence_[0] = 3; testGroup.fullseq_[0] = 1; testGroup.gap_[0] = 7; - + EXPECT_EQ(testGroup.last_group_, 3); EXPECT_EQ(testGroup.rank_[0], 5); EXPECT_EQ(testGroup.sequence_[0], 3); @@ -176,7 +176,7 @@ TEST_F(LookupTablesTest, GroupDataStructAccess) { // Test that we can access group_data array entries for (int i = 0; i < 10; i++) { // Test first 10 entries const MoveGroupType& group = group_data[i]; - + // Values should be in reasonable ranges // last_group_ can be -1 to indicate no groups EXPECT_GE(group.last_group_, -1); @@ -193,12 +193,12 @@ TEST_F(LookupTablesTest, GroupDataStructAccess) { TEST_F(LookupTablesTest, InitializationPerformance) { // Test that initialization completes in reasonable time auto start = std::chrono::high_resolution_clock::now(); - + init_lookup_tables(); - + auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end - start); - + // Should complete within 1 second (adjust if needed) EXPECT_LT(duration.count(), 1000) << "init_lookup_tables should complete within 1 second"; diff --git a/python/BUILD.bazel b/python/BUILD.bazel index 8ae15bfd7..4af33895b 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -102,6 +102,21 @@ py_test( ], ) +# Guard: C++ indent helpers + CI wiring for the workspace full-tree scan +# (.github/instructions/cpp.instructions.md). The scan itself runs in +# ci_linux.yml via `python3 python/tests/cpp_indentation_test.py`. +py_test( + name = "cpp_indentation_test", + size = "small", + main = "tests/cpp_indentation_test.py", + srcs = ["tests/cpp_indentation_test.py"], + data = [ + "//:.bazelrc", + "//:CPPVARIABLES.bzl", + "//.github/workflows:all_workflows", + ], +) + py_test( name = "python_interface_smoke_test", size = "small", @@ -219,3 +234,4 @@ py_wheel_dist( out = "dist", wheel = ":dds3_wheel", ) + diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index d231365c9..b111e2190 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -427,7 +427,7 @@ auto register_table_bindings(py::module_& module) -> void (wants_par && can_compute_par) ? MAXNOOFTABLES : ((included_strains > 0) ? ((MAXNOOFTABLES * DDS_STRAINS) / included_strains) - : MAXNOOFTABLES); + : MAXNOOFTABLES); // Convert list of PBN strings to DdTableDealsPBN const auto native_deals = dds3_python::list_to_dd_table_deals_pbn( @@ -456,7 +456,7 @@ auto register_table_bindings(py::module_& module) -> void py::dict result; result["no_of_boards"] = tables_res.no_of_boards; result["tables"] = dds3_python::dd_tables_res_to_list(tables_res, native_deals.no_of_tables); - + // Include par_results only if par was actually computed: // - Par computation requires mode != -1 AND all strains included // - This ensures AllParResults buffer (capacity MAXNOOFTABLES) won't be accessed out-of-bounds @@ -553,7 +553,7 @@ auto register_calc_par_bindings(py::module_& module) -> void &par_results); } throw_on_dds_error(code); - + // Return both DD table and par results py::dict result; result["dd_table"] = dds3_python::dd_table_results_to_dict(table_results); diff --git a/python/src/converters.cpp b/python/src/converters.cpp index ace27cbf1..c32c5215b 100644 --- a/python/src/converters.cpp +++ b/python/src/converters.cpp @@ -243,7 +243,7 @@ auto future_tricks_to_dict(const FutureTricks& future_tricks) -> py::dict py::dict result; result["nodes"] = future_tricks.nodes; result["cards"] = future_tricks.cards; - + // Convert arrays to tuples using loops for maintainability py::tuple suit(13); py::tuple rank(13); diff --git a/python/tests/cpp_indentation_test.py b/python/tests/cpp_indentation_test.py new file mode 100644 index 000000000..b4346f87e --- /dev/null +++ b/python/tests/cpp_indentation_test.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +"""Guard C++ sources use 4-space indentation and no hard tabs. + +Matches .github/instructions/cpp.instructions.md (Indentation): +- 4 spaces per indentation level +- No hard tabs + +Copyright block-comment lines that use a decorative 3-space indent are allowed +(leading length % 4 == 3). Continuation alignments that leave a residual +1-space indent (leading length % 4 == 1) are also allowed. What is not allowed +is a 2-space indent level (leading length % 4 == 2). + +Hermetic ``bazel test //python:cpp_indentation_test`` covers the helpers and +the CI wiring check. The full-tree scan runs against a real checkout via +``python3 python/tests/cpp_indentation_test.py`` (local or Linux CI), so we +do not need per-package Bazel filegroups of every C++ source. +""" + +from __future__ import annotations + +import re +import sys +import tempfile +import unittest +from pathlib import Path + + +_CPP_SUFFIXES = {".c", ".cc", ".cpp", ".h", ".hh", ".hpp"} +_SCAN_ROOTS = ( + "benchmarks", + "examples", + "include", + "jni", + "library", + "python", + "utilities", + "wasm", + "web", +) +_SKIP_DIR_NAMES = { + ".git", + "bazel-bin", + "bazel-out", + "bazel-testlogs", + "node_modules", + "third_party", + "external", +} +_MIN_REPO_CPP_FILES = 50 +_WORKSPACE_INDENT_CHECK = "python3 python/tests/cpp_indentation_test.py" + + +def _repo_root(start: Path | None = None) -> Path: + # Do not Path.resolve() — under `bazel test` this file is often a runfiles + # symlink into the execroot/source tree, and resolving leaves the runfiles + # tree where data deps live. + here = (start or Path(__file__)).absolute() + for parent in here.parents: + if (parent / ".bazelrc").is_file() and (parent / "CPPVARIABLES.bzl").is_file(): + return parent + raise AssertionError("could not locate repository root from test file path") + + +def iter_cpp_files(root: Path) -> list[Path]: + files: list[Path] = [] + for name in _SCAN_ROOTS: + base = root / name + if not base.is_dir(): + continue + for path in base.rglob("*"): + if not path.is_file(): + continue + if path.suffix.lower() not in _CPP_SUFFIXES: + continue + try: + relative_parts = path.relative_to(root).parts + except ValueError: + continue + if any(part in _SKIP_DIR_NAMES for part in relative_parts): + continue + files.append(path) + return sorted(files) + + +def expand_tabs(line: str, tab_width: int = 4) -> str: + """Expand tabs to spaces using column-aware tab stops.""" + out: list[str] = [] + col = 0 + for ch in line: + if ch == "\t": + spaces = tab_width - (col % tab_width) + out.append(" " * spaces) + col += spaces + else: + out.append(ch) + col += 1 + return "".join(out) + + +def leading_spaces(line: str) -> tuple[int, str]: + """Return (leading_space_count, remainder) after tab expansion.""" + expanded = expand_tabs(line) + match = re.match(r"^( *)", expanded) + assert match is not None + n = len(match.group(1)) + return n, expanded[n:] + + +def find_indent_violations(text: str, path: str = "") -> list[str]: + """Return human-readable violations for one file's contents.""" + violations: list[str] = [] + for lineno, raw in enumerate(text.splitlines(), 1): + if "\t" in raw: + violations.append(f"{path}:{lineno}: hard tab") + continue + if not raw.strip(): + continue + n, _ = leading_spaces(raw) + if n % 4 == 2: + violations.append( + f"{path}:{lineno}: leading indent {n} is not a multiple of 4 " + f"(2-space indent level)" + ) + return violations + + +def _count_exact_two_space_indents(text: str) -> int: + count = 0 + for raw in text.splitlines(): + if "\t" in raw or not raw.strip(): + continue + n, _ = leading_spaces(raw) + if n == 2: + count += 1 + return count + + +def reindent_text(text: str) -> str: + """Normalize a C++ source string to 4-space indentation and no tabs. + + Files that still use 2-space indent levels (many lines with exactly two + leading spaces) have every leading run of spaces doubled. Remaining lines + whose leading length is 2 mod 4 (for example half-indented ``public:``) + are padded up by two spaces to the next multiple of 4. + """ + newline = "\r\n" if "\r\n" in text else "\n" + body = text.splitlines() + + # Decide doubling from the tab-expanded view of the original text. + expanded_view = "\n".join(expand_tabs(line) for line in body) + double = _count_exact_two_space_indents(expanded_view) >= 3 + + out_lines: list[str] = [] + for raw in body: + if not raw.strip(): + out_lines.append("") + continue + expanded = expand_tabs(raw) + n, rest = leading_spaces(expanded) + if double and n % 2 == 0: + # Double even indents only. Odd decorative indents (copyright + # banners at 3 spaces, some aligned continuations) stay put. + n *= 2 + elif n % 4 == 2: + n += 2 + out_lines.append(" " * n + rest) + + result = newline.join(out_lines) + if text.endswith(("\n", "\r\n")): + result += newline + return result + + +class TestExpandTabs(unittest.TestCase): + def test_leading_tab_becomes_four_spaces(self) -> None: + self.assertEqual(expand_tabs("\tfoo"), " foo") + + def test_tab_after_spaces_aligns_to_stop(self) -> None: + self.assertEqual(expand_tabs(" \tfoo"), " foo") + + +class TestFindIndentViolations(unittest.TestCase): + def test_reports_hard_tab(self) -> None: + violations = find_indent_violations("int main() {\n\treturn 0;\n}\n") + self.assertEqual(len(violations), 1) + self.assertIn("hard tab", violations[0]) + + def test_reports_two_space_indent_level(self) -> None: + sample = "void f()\n{\n int x = 1;\n if (x) {\n return;\n }\n}\n" + violations = find_indent_violations(sample) + self.assertTrue(any("indent 2" in v for v in violations)) + self.assertTrue(any("indent 6" in v for v in violations) or any("indent 2" in v for v in violations)) + + def test_allows_four_space_indent_and_copyright_three_space(self) -> None: + sample = ( + "/*\n" + " Copyright\n" + "*/\n" + "void f()\n" + "{\n" + " int x = 1;\n" + " if (x) {\n" + " return;\n" + " }\n" + "}\n" + ) + self.assertEqual(find_indent_violations(sample), []) + + +class TestReindentText(unittest.TestCase): + def test_doubles_two_space_indent_levels(self) -> None: + sample = "void f()\n{\n int x = 1;\n if (x) {\n nested();\n }\n}\n" + fixed = reindent_text(sample) + self.assertEqual(find_indent_violations(fixed), []) + self.assertIn(" int x = 1;", fixed) + self.assertIn(" nested();", fixed) + + def test_expands_tabs_to_spaces(self) -> None: + sample = "void f()\n{\n\treturn;\n}\n" + fixed = reindent_text(sample) + self.assertEqual(find_indent_violations(fixed), []) + self.assertIn(" return;", fixed) + self.assertNotIn("\t", fixed) + + def test_pads_half_indented_access_specifier_in_four_space_file(self) -> None: + sample = ( + "class File\n" + "{\n" + " private:\n" + "\n" + " std::string fname_;\n" + "};\n" + ) + fixed = reindent_text(sample) + self.assertEqual(find_indent_violations(fixed), []) + self.assertIn(" private:", fixed) + self.assertIn(" std::string fname_;", fixed) + + def test_preserves_three_space_copyright_when_doubling(self) -> None: + sample = ( + "/*\n" + " Copyright\n" + "*/\n" + "void f()\n" + "{\n" + " int a;\n" + " int b;\n" + " int c;\n" + "}\n" + ) + fixed = reindent_text(sample) + self.assertEqual(find_indent_violations(fixed), []) + self.assertIn(" Copyright", fixed) + self.assertIn(" int a;", fixed) + + +class TestRepoCppIndentation(unittest.TestCase): + def test_all_cpp_sources_use_four_space_indent_without_tabs(self) -> None: + root = _repo_root() + files = iter_cpp_files(root) + if len(files) < _MIN_REPO_CPP_FILES: + self.skipTest( + "full-tree scan requires a workspace checkout " + f"(found {len(files)} C++ files; run " + f"`{_WORKSPACE_INDENT_CHECK}`)" + ) + + violations: list[str] = [] + for path in files: + text = path.read_text(encoding="utf-8", errors="replace") + rel = path.relative_to(root).as_posix() + violations.extend(find_indent_violations(text, rel)) + + if violations: + preview = "\n".join(violations[:40]) + more = "" if len(violations) <= 40 else f"\n... and {len(violations) - 40} more" + self.fail( + f"{len(violations)} C++ indentation violation(s); " + f"expected 4-space indent and no tabs:\n{preview}{more}" + ) + + +class TestCiWiresCppIndentationScan(unittest.TestCase): + def test_linux_ci_runs_workspace_indentation_check(self) -> None: + """Avoid reintroducing per-package filegroups: CI scans the checkout.""" + workflow = _repo_root() / ".github" / "workflows" / "ci_linux.yml" + self.assertTrue(workflow.is_file(), f"missing {workflow}") + text = workflow.read_text(encoding="utf-8") + self.assertIn( + _WORKSPACE_INDENT_CHECK, + text, + "ci_linux.yml must run the workspace C++ indentation check " + f"(`{_WORKSPACE_INDENT_CHECK}`) so the full tree is scanned " + "without Bazel filegroups of every source", + ) + + +class TestRepoRoot(unittest.TestCase): + def test_does_not_follow_symlink_out_of_runfiles_tree(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + source = tmp_path / "source" / "python" / "tests" + source.mkdir(parents=True) + (source / "cpp_indentation_test.py").write_text("# source\n", encoding="utf-8") + (tmp_path / "source" / ".bazelrc").write_text("#\n", encoding="utf-8") + (tmp_path / "source" / "CPPVARIABLES.bzl").write_text("#\n", encoding="utf-8") + + runfiles = tmp_path / "runfiles" / "_main" + tests_dir = runfiles / "python" / "tests" + tests_dir.mkdir(parents=True) + (tests_dir / "cpp_indentation_test.py").symlink_to( + source / "cpp_indentation_test.py" + ) + (runfiles / ".bazelrc").write_text("#\n", encoding="utf-8") + (runfiles / "CPPVARIABLES.bzl").write_text("#\n", encoding="utf-8") + + found = _repo_root(tests_dir / "cpp_indentation_test.py") + self.assertEqual(found, runfiles) + + +def _fix_repo(root: Path) -> int: + changed = 0 + for path in iter_cpp_files(root): + original = path.read_text(encoding="utf-8", errors="replace") + fixed = reindent_text(original) + if fixed != original: + path.write_text(fixed, encoding="utf-8") + changed += 1 + print(f"reindented {changed} file(s)") + return changed + + +if __name__ == "__main__": + if "--fix" in sys.argv: + _fix_repo(_repo_root()) + sys.exit(0) + unittest.main() diff --git a/utilities/src/dd_table_for_deal/dd_table_for_deal.cpp b/utilities/src/dd_table_for_deal/dd_table_for_deal.cpp index 37c6d74ee..29419b3a1 100644 --- a/utilities/src/dd_table_for_deal/dd_table_for_deal.cpp +++ b/utilities/src/dd_table_for_deal/dd_table_for_deal.cpp @@ -53,193 +53,193 @@ using dd_table_for_deal::unique_deals; static auto stdin_is_tty() -> bool { #if defined(_WIN32) - return _isatty(_fileno(stdin)) != 0; + return _isatty(_fileno(stdin)) != 0; #else - return isatty(STDIN_FILENO) != 0; + return isatty(STDIN_FILENO) != 0; #endif } auto read_pbn_file(const std::filesystem::path& path) -> std::optional { - std::ifstream file(path, std::ios::binary); - if (!file) - { - return std::nullopt; - } + std::ifstream file(path, std::ios::binary); + if (!file) + { + return std::nullopt; + } - return read_pbn_stream(file); + return read_pbn_stream(file); } auto read_pbn_file_workspace_relative(std::string_view path) - -> std::optional + -> std::optional { - if (auto text = read_pbn_file(std::filesystem::path(path))) - { - return text; - } - - // bazelisk run uses a runfiles cwd; BUILD_WORKSPACE_DIRECTORY is the repo root. - if (const char* workspace = std::getenv("BUILD_WORKSPACE_DIRECTORY")) - { - return read_pbn_file(std::filesystem::path(workspace) / path); - } - - return std::nullopt; + if (auto text = read_pbn_file(std::filesystem::path(path))) + { + return text; + } + + // bazelisk run uses a runfiles cwd; BUILD_WORKSPACE_DIRECTORY is the repo root. + if (const char* workspace = std::getenv("BUILD_WORKSPACE_DIRECTORY")) + { + return read_pbn_file(std::filesystem::path(workspace) / path); + } + + return std::nullopt; } auto path_openable_workspace_relative(std::string_view path) -> bool { - if (path_is_openable(path)) - return true; - if (const char* workspace = std::getenv("BUILD_WORKSPACE_DIRECTORY")) - { - return path_is_openable((std::filesystem::path(workspace) / path).string()); - } - return false; + if (path_is_openable(path)) + return true; + if (const char* workspace = std::getenv("BUILD_WORKSPACE_DIRECTORY")) + { + return path_is_openable((std::filesystem::path(workspace) / path).string()); + } + return false; } auto load_deals(std::string_view arg) -> std::optional> { - if (arg == "-") - { - const auto text = read_pbn_stream(std::cin); - if (!text) + if (arg == "-") { - // Oversized input is already reported by read_pbn_stream. - if (should_report_failed_stream_read(std::cin)) - std::cerr << "Cannot read PBN from stdin\n"; - return std::nullopt; + const auto text = read_pbn_stream(std::cin); + if (!text) + { + // Oversized input is already reported by read_pbn_stream. + if (should_report_failed_stream_read(std::cin)) + std::cerr << "Cannot read PBN from stdin\n"; + return std::nullopt; + } + + const auto deals = extract_deal_tags(*text); + if (deals.empty()) + { + std::cerr << "No [Deal \"...\"] tag found in stdin\n"; + return std::nullopt; + } + + return deals; } - const auto deals = extract_deal_tags(*text); - if (deals.empty()) + if (const auto text = read_pbn_file_workspace_relative(arg)) { - std::cerr << "No [Deal \"...\"] tag found in stdin\n"; - return std::nullopt; + const auto deals = extract_deal_tags(*text); + if (deals.empty()) + { + std::cerr << "No [Deal \"...\"] tag found in " << arg << "\n"; + return std::nullopt; + } + + return deals; } - return deals; - } - - if (const auto text = read_pbn_file_workspace_relative(arg)) - { - const auto deals = extract_deal_tags(*text); - if (deals.empty()) + if (looks_like_path(arg)) { - std::cerr << "No [Deal \"...\"] tag found in " << arg << "\n"; - return std::nullopt; + // Missing file vs openable-but-failed (e.g. oversize already reported). + if (!path_openable_workspace_relative(arg)) + std::cerr << "Cannot read file: " << arg << "\n"; + return std::nullopt; } - return deals; - } - - if (looks_like_path(arg)) - { - // Missing file vs openable-but-failed (e.g. oversize already reported). - if (!path_openable_workspace_relative(arg)) - std::cerr << "Cannot read file: " << arg << "\n"; - return std::nullopt; - } - - if (arg.size() >= PBN_DEAL_MAX) - { - std::cerr << "PBN deal too long (max " << (PBN_DEAL_MAX - 1) - << " characters)\n"; - return std::nullopt; - } + if (arg.size() >= PBN_DEAL_MAX) + { + std::cerr << "PBN deal too long (max " << (PBN_DEAL_MAX - 1) + << " characters)\n"; + return std::nullopt; + } - return std::vector{std::string(arg)}; + return std::vector{std::string(arg)}; } auto print_par_or_verbose( - DdTableResults const * table, - int vulnerable) -> bool + DdTableResults const * table, + int vulnerable) -> bool { - ParResultsMaster sidesRes[2]; - const int res = SidesParBin(table, sidesRes, vulnerable); - if (res != RETURN_NO_FAULT) - { - char line[80]; - ErrorMessage(res, line); - fprintf(stderr, "DDS error: %s\n", line); - return false; - } + ParResultsMaster sidesRes[2]; + const int res = SidesParBin(table, sidesRes, vulnerable); + if (res != RETURN_NO_FAULT) + { + char line[80]; + ErrorMessage(res, line); + fprintf(stderr, "DDS error: %s\n", line); + return false; + } - if (const auto line = format_par_line(sidesRes)) - { - printf("%s\n", line->c_str()); - return true; - } - - ParResults par; - char err[80]; - const int par_res = Par(table, &par, vulnerable); - if (par_res != RETURN_NO_FAULT) - { - ErrorMessage(par_res, err); - fprintf(stderr, "DDS error: %s\n", err); - return false; - } + if (const auto line = format_par_line(sidesRes)) + { + printf("%s\n", line->c_str()); + return true; + } + + ParResults par; + char err[80]; + const int par_res = Par(table, &par, vulnerable); + if (par_res != RETURN_NO_FAULT) + { + ErrorMessage(par_res, err); + fprintf(stderr, "DDS error: %s\n", err); + return false; + } - print_par(&par); - return true; + print_par(&par); + return true; } auto process_deal( - std::string const& deal, - std::size_t deal_no, - std::size_t deal_count, - int vulnerable, - int num_threads) -> bool + std::string const& deal, + std::size_t deal_no, + std::size_t deal_count, + int vulnerable, + int num_threads) -> bool { - DdTableDealPBN tableDealPBN{}; - if (deal.size() >= sizeof(tableDealPBN.cards)) - { - fprintf(stderr, - "PBN deal too long (max %zu characters)\n", - sizeof(tableDealPBN.cards) - 1); - return false; - } - - std::copy_n(deal.begin(), deal.size(), tableDealPBN.cards); - tableDealPBN.cards[deal.size()] = '\0'; - - DdTableResults table; - char line[80]; - - const int res = calc_dd_table_for_pbn_deal( - tableDealPBN, - num_threads, - &table, - [](DdTableDealPBN table_deal_pbn, DdTableResults * tablep, int threads) - { - return CalcDDtablePBNN(table_deal_pbn, tablep, threads); - }); - if (res != RETURN_NO_FAULT) - { - ErrorMessage(res, line); - fprintf(stderr, "DDS error: %s\n", line); - return false; - } + DdTableDealPBN tableDealPBN{}; + if (deal.size() >= sizeof(tableDealPBN.cards)) + { + fprintf(stderr, + "PBN deal too long (max %zu characters)\n", + sizeof(tableDealPBN.cards) - 1); + return false; + } - if (deal_count == 1) - std::snprintf(line, sizeof(line), "dd_table_for_deal:\n"); - else - std::snprintf(line, sizeof(line), "Deal %zu:\n", deal_no); + std::copy_n(deal.begin(), deal.size(), tableDealPBN.cards); + tableDealPBN.cards[deal.size()] = '\0'; - print_pbn_hand(line, tableDealPBN.cards); - print_table(&table); - if (!print_par_or_verbose(&table, vulnerable)) - return false; - if (deal_count > 1) - printf("\n"); - return true; + DdTableResults table; + char line[80]; + + const int res = calc_dd_table_for_pbn_deal( + tableDealPBN, + num_threads, + &table, + [](DdTableDealPBN table_deal_pbn, DdTableResults * tablep, int threads) + { + return CalcDDtablePBNN(table_deal_pbn, tablep, threads); + }); + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + fprintf(stderr, "DDS error: %s\n", line); + return false; + } + + if (deal_count == 1) + std::snprintf(line, sizeof(line), "dd_table_for_deal:\n"); + else + std::snprintf(line, sizeof(line), "Deal %zu:\n", deal_no); + + print_pbn_hand(line, tableDealPBN.cards); + print_table(&table); + if (!print_par_or_verbose(&table, vulnerable)) + return false; + if (deal_count > 1) + printf("\n"); + return true; } } // namespace @@ -247,152 +247,152 @@ auto process_deal( static auto print_usage(const char * prog) -> void { - fprintf(stderr, - "Usage: %s [--vul none|both|ns|ew|0|1|2|3] [--limit N] " - "[-n N|--numthr N] \n" - " %s -h | --help\n" - "\n" - "Calculate double-dummy tricks and par for all strains and leads.\n" - "\n" - "Arguments:\n" - " DDS PBN deal string, or path to a .pbn file\n" - " --vul Vulnerability: none|both|ns|ew or 0|1|2|3" - " (default: none)\n" - " --limit Solve only the first N unique deals\n" - " -n, --numthr Worker threads for each table solve.\n" - " 0 = auto (hardware concurrency), 1 = sequential.\n" - " (Default: 0)\n" - "\n" - "If stdin is not a terminal, PBN is read from stdin (all [Deal \"...\"] tags).\n" - "\n" - "Examples:\n" - " %s \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " - "5.A95432.7632.K6 AKJ9842.K.T8.J93\"\n" - " %s --vul ns hands/example.pbn\n" - " %s --limit 3 hands/multi_board.pbn\n" - " %s -n 1 hands/example.pbn\n" - " %s < hands/example.pbn\n", - prog, - prog, - prog, - prog, - prog, - prog, - prog); + fprintf(stderr, + "Usage: %s [--vul none|both|ns|ew|0|1|2|3] [--limit N] " + "[-n N|--numthr N] \n" + " %s -h | --help\n" + "\n" + "Calculate double-dummy tricks and par for all strains and leads.\n" + "\n" + "Arguments:\n" + " DDS PBN deal string, or path to a .pbn file\n" + " --vul Vulnerability: none|both|ns|ew or 0|1|2|3" + " (default: none)\n" + " --limit Solve only the first N unique deals\n" + " -n, --numthr Worker threads for each table solve.\n" + " 0 = auto (hardware concurrency), 1 = sequential.\n" + " (Default: 0)\n" + "\n" + "If stdin is not a terminal, PBN is read from stdin (all [Deal \"...\"] tags).\n" + "\n" + "Examples:\n" + " %s \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93\"\n" + " %s --vul ns hands/example.pbn\n" + " %s --limit 3 hands/multi_board.pbn\n" + " %s -n 1 hands/example.pbn\n" + " %s < hands/example.pbn\n", + prog, + prog, + prog, + prog, + prog, + prog, + prog); } auto main(int argc, char * argv[]) -> int { - const char * input = nullptr; - int vulnerable = 0; - int num_threads = 0; - std::optional limit; - - for (int i = 1; i < argc; ++i) - { - if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) - { - print_usage(argv[0]); - return 0; - } - if (strcmp(argv[i], "--vul") == 0) + const char * input = nullptr; + int vulnerable = 0; + int num_threads = 0; + std::optional limit; + + for (int i = 1; i < argc; ++i) { - if (i + 1 >= argc) - { - fprintf(stderr, "--vul requires a value (none|both|ns|ew or 0|1|2|3)\n"); - print_usage(argv[0]); - return 1; - } - const auto vul = parse_vulnerable(argv[++i]); - if (!vul) - { - fprintf(stderr, "Invalid --vul value (use none|both|ns|ew or 0|1|2|3)\n"); - print_usage(argv[0]); - return 1; - } - vulnerable = *vul; - continue; + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + print_usage(argv[0]); + return 0; + } + if (strcmp(argv[i], "--vul") == 0) + { + if (i + 1 >= argc) + { + fprintf(stderr, "--vul requires a value (none|both|ns|ew or 0|1|2|3)\n"); + print_usage(argv[0]); + return 1; + } + const auto vul = parse_vulnerable(argv[++i]); + if (!vul) + { + fprintf(stderr, "Invalid --vul value (use none|both|ns|ew or 0|1|2|3)\n"); + print_usage(argv[0]); + return 1; + } + vulnerable = *vul; + continue; + } + if (strcmp(argv[i], "--limit") == 0) + { + if (i + 1 >= argc) + { + fprintf(stderr, "--limit requires a positive integer\n"); + print_usage(argv[0]); + return 1; + } + const auto parsed_limit = parse_limit(argv[++i]); + if (!parsed_limit) + { + fprintf(stderr, "Invalid --limit value (use a positive integer)\n"); + print_usage(argv[0]); + return 1; + } + limit = parsed_limit; + continue; + } + if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--numthr") == 0) + { + if (i + 1 >= argc) + { + fprintf(stderr, "%s requires a non-negative integer\n", argv[i]); + print_usage(argv[0]); + return 1; + } + const char * flag = argv[i]; + const auto parsed_numthr = parse_numthr(argv[++i]); + if (!parsed_numthr) + { + fprintf(stderr, + "Invalid %s value (use a non-negative integer; " + "0 = auto)\n", + flag); + print_usage(argv[0]); + return 1; + } + num_threads = *parsed_numthr; + continue; + } + if (argv[i][0] == '-' && strcmp(argv[i], "-") != 0) + { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + print_usage(argv[0]); + return 1; + } + if (input != nullptr) + { + fprintf(stderr, "Only one deal argument is allowed\n"); + print_usage(argv[0]); + return 1; + } + input = argv[i]; } - if (strcmp(argv[i], "--limit") == 0) + + if (input == nullptr) { - if (i + 1 >= argc) - { - fprintf(stderr, "--limit requires a positive integer\n"); - print_usage(argv[0]); - return 1; - } - const auto parsed_limit = parse_limit(argv[++i]); - if (!parsed_limit) - { - fprintf(stderr, "Invalid --limit value (use a positive integer)\n"); - print_usage(argv[0]); - return 1; - } - limit = parsed_limit; - continue; + if (!stdin_is_tty()) + input = "-"; + else + { + print_usage(argv[0]); + return 1; + } } - if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--numthr") == 0) + + const auto loaded = load_deals(input); + if (!loaded) { - if (i + 1 >= argc) - { - fprintf(stderr, "%s requires a non-negative integer\n", argv[i]); - print_usage(argv[0]); - return 1; - } - const char * flag = argv[i]; - const auto parsed_numthr = parse_numthr(argv[++i]); - if (!parsed_numthr) - { - fprintf(stderr, - "Invalid %s value (use a non-negative integer; " - "0 = auto)\n", - flag); - print_usage(argv[0]); return 1; - } - num_threads = *parsed_numthr; - continue; } - if (argv[i][0] == '-' && strcmp(argv[i], "-") != 0) - { - fprintf(stderr, "Unknown option: %s\n", argv[i]); - print_usage(argv[0]); - return 1; - } - if (input != nullptr) - { - fprintf(stderr, "Only one deal argument is allowed\n"); - print_usage(argv[0]); - return 1; - } - input = argv[i]; - } - if (input == nullptr) - { - if (!stdin_is_tty()) - input = "-"; - else + const auto deals = apply_deal_limit(unique_deals(*loaded), limit); + + for (std::size_t i = 0; i < deals.size(); ++i) { - print_usage(argv[0]); - return 1; + if (!process_deal(deals[i], i + 1, deals.size(), vulnerable, num_threads)) + return 1; } - } - - const auto loaded = load_deals(input); - if (!loaded) - { - return 1; - } - - const auto deals = apply_deal_limit(unique_deals(*loaded), limit); - - for (std::size_t i = 0; i < deals.size(); ++i) - { - if (!process_deal(deals[i], i + 1, deals.size(), vulnerable, num_threads)) - return 1; - } - return 0; + return 0; } diff --git a/utilities/src/dd_table_for_deal/dd_table_for_deal_lib.cpp b/utilities/src/dd_table_for_deal/dd_table_for_deal_lib.cpp index 7aae1cade..5c17c4121 100644 --- a/utilities/src/dd_table_for_deal/dd_table_for_deal_lib.cpp +++ b/utilities/src/dd_table_for_deal/dd_table_for_deal_lib.cpp @@ -28,76 +28,76 @@ namespace dd_table_for_deal { namespace { const std::regex DEAL_TAG_RE{ - R"re(\[Deal\s*"([^"]*)")re", - std::regex::icase}; + R"re(\[Deal\s*"([^"]*)")re", + std::regex::icase}; auto denom_char(int denom) -> char { - static constexpr char kDenomChars[] = "NSHDC"; - if (denom < 0 || denom > 4) - return '?'; - return kDenomChars[denom]; + static constexpr char kDenomChars[] = "NSHDC"; + if (denom < 0 || denom > 4) + return '?'; + return kDenomChars[denom]; } auto seat_name(int seats) -> const char * { - static const char * kSeatNames[] = {"N", "E", "S", "W", "NS", "EW"}; - if (seats < 0 || seats > 5) - return "?"; - return kSeatNames[seats]; + static const char * kSeatNames[] = {"N", "E", "S", "W", "NS", "EW"}; + if (seats < 0 || seats > 5) + return "?"; + return kSeatNames[seats]; } auto format_contract( - const ContractType& contract, - bool include_seats) -> std::optional + const ContractType& contract, + bool include_seats) -> std::optional { - char out[16]; - const char doubled = contract.under_tricks > 0 ? 'x' : '\0'; - if (include_seats) - { - if (doubled) + char out[16]; + const char doubled = contract.under_tricks > 0 ? 'x' : '\0'; + if (include_seats) { - std::snprintf( - out, - sizeof(out), - "%s %d%cx", - seat_name(contract.seats), - contract.level, - denom_char(contract.denom)); + if (doubled) + { + std::snprintf( + out, + sizeof(out), + "%s %d%cx", + seat_name(contract.seats), + contract.level, + denom_char(contract.denom)); + } + else + { + std::snprintf( + out, + sizeof(out), + "%s %d%c", + seat_name(contract.seats), + contract.level, + denom_char(contract.denom)); + } + } + else if (doubled) + { + std::snprintf( + out, + sizeof(out), + "%d%cx", + contract.level, + denom_char(contract.denom)); } else { - std::snprintf( - out, - sizeof(out), - "%s %d%c", - seat_name(contract.seats), - contract.level, - denom_char(contract.denom)); + std::snprintf( + out, + sizeof(out), + "%d%c", + contract.level, + denom_char(contract.denom)); } - } - else if (doubled) - { - std::snprintf( - out, - sizeof(out), - "%d%cx", - contract.level, - denom_char(contract.denom)); - } - else - { - std::snprintf( - out, - sizeof(out), - "%d%c", - contract.level, - denom_char(contract.denom)); - } - return std::string(out); + return std::string(out); } } // namespace @@ -105,220 +105,220 @@ auto format_contract( auto parse_vulnerable(std::string_view text) -> std::optional { - std::string lower(text); - std::transform(lower.begin(), lower.end(), lower.begin(), + std::string lower(text); + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (lower == "none" || lower == "0") - return 0; - if (lower == "both" || lower == "1") - return 1; - if (lower == "ns" || lower == "2") - return 2; - if (lower == "ew" || lower == "3") - return 3; - return std::nullopt; + if (lower == "none" || lower == "0") + return 0; + if (lower == "both" || lower == "1") + return 1; + if (lower == "ns" || lower == "2") + return 2; + if (lower == "ew" || lower == "3") + return 3; + return std::nullopt; } auto parse_limit(std::string_view text) -> std::optional { - if (text.empty()) - return std::nullopt; + if (text.empty()) + return std::nullopt; - std::size_t value = 0; - for (char ch : text) - { - if (ch < '0' || ch > '9') - return std::nullopt; - const std::size_t digit = static_cast(ch - '0'); - if (value > (std::numeric_limits::max() - digit) / 10) - return std::nullopt; - value = value * 10 + digit; - } - if (value == 0) - return std::nullopt; - return value; + std::size_t value = 0; + for (char ch : text) + { + if (ch < '0' || ch > '9') + return std::nullopt; + const std::size_t digit = static_cast(ch - '0'); + if (value > (std::numeric_limits::max() - digit) / 10) + return std::nullopt; + value = value * 10 + digit; + } + if (value == 0) + return std::nullopt; + return value; } auto parse_numthr(std::string_view text) -> std::optional { - if (text.empty()) - return std::nullopt; + if (text.empty()) + return std::nullopt; - int value = 0; - for (char ch : text) - { - if (ch < '0' || ch > '9') - return std::nullopt; - const int digit = ch - '0'; - if (value > (std::numeric_limits::max() - digit) / 10) - return std::nullopt; - value = value * 10 + digit; - } - return value; + int value = 0; + for (char ch : text) + { + if (ch < '0' || ch > '9') + return std::nullopt; + const int digit = ch - '0'; + if (value > (std::numeric_limits::max() - digit) / 10) + return std::nullopt; + value = value * 10 + digit; + } + return value; } auto calc_dd_table_for_pbn_deal( - DdTableDealPBN table_deal, - int num_threads, - DdTableResults * table, - CalcDdTablePbnNFn const& calc) -> int + DdTableDealPBN table_deal, + int num_threads, + DdTableResults * table, + CalcDdTablePbnNFn const& calc) -> int { - return calc(table_deal, table, num_threads); + return calc(table_deal, table, num_threads); } auto apply_deal_limit( - std::vector deals, - std::optional limit) -> std::vector + std::vector deals, + std::optional limit) -> std::vector { - if (!limit.has_value() || *limit >= deals.size()) + if (!limit.has_value() || *limit >= deals.size()) + return deals; + deals.resize(*limit); return deals; - deals.resize(*limit); - return deals; } auto extract_deal_tags(std::string_view text) -> std::vector { - std::vector deals; - auto begin = text.cbegin(); - const auto end = text.cend(); - std::match_results match; - while (std::regex_search(begin, end, match, DEAL_TAG_RE) && match.size() > 1) - { - deals.emplace_back(match[1].first, match[1].second); - begin = match[0].second; - } - return deals; + std::vector deals; + auto begin = text.cbegin(); + const auto end = text.cend(); + std::match_results match; + while (std::regex_search(begin, end, match, DEAL_TAG_RE) && match.size() > 1) + { + deals.emplace_back(match[1].first, match[1].second); + begin = match[0].second; + } + return deals; } auto unique_deals(std::vector const& deals) - -> std::vector + -> std::vector { - std::vector unique; - std::unordered_set seen; - unique.reserve(deals.size()); - for (const auto& deal : deals) - { - if (seen.insert(deal).second) - unique.push_back(deal); - } - return unique; + std::vector unique; + std::unordered_set seen; + unique.reserve(deals.size()); + for (const auto& deal : deals) + { + if (seen.insert(deal).second) + unique.push_back(deal); + } + return unique; } auto looks_like_path(std::string_view arg) -> bool { - if (arg.find('/') != std::string_view::npos - || arg.find('\\') != std::string_view::npos) - { - return true; - } - - if (arg.size() >= 4) - { - std::string lower(arg.substr(arg.size() - 4)); - std::transform(lower.begin(), lower.end(), lower.begin(), + if (arg.find('/') != std::string_view::npos + || arg.find('\\') != std::string_view::npos) + { + return true; + } + + if (arg.size() >= 4) + { + std::string lower(arg.substr(arg.size() - 4)); + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (lower == ".pbn" || lower == ".txt") - return true; - } - return false; + if (lower == ".pbn" || lower == ".txt") + return true; + } + return false; } auto read_pbn_stream(std::istream& in) -> std::optional { - std::string text; - text.reserve(64 * 1024); - char buffer[4096]; - while (in) - { - in.read(buffer, static_cast(sizeof(buffer))); - const auto n = in.gcount(); - if (n > 0) - text.append(buffer, static_cast(n)); - if (text.size() > PBN_FILE_MAX) + std::string text; + text.reserve(64 * 1024); + char buffer[4096]; + while (in) { - std::cerr << "PBN input too large (max " << PBN_FILE_MAX << " characters)\n"; - return std::nullopt; + in.read(buffer, static_cast(sizeof(buffer))); + const auto n = in.gcount(); + if (n > 0) + text.append(buffer, static_cast(n)); + if (text.size() > PBN_FILE_MAX) + { + std::cerr << "PBN input too large (max " << PBN_FILE_MAX << " characters)\n"; + return std::nullopt; + } } - } - return text; + return text; } auto path_is_openable(std::string_view path) -> bool { - std::ifstream file(std::filesystem::path(path), std::ios::binary); - return static_cast(file); + std::ifstream file(std::filesystem::path(path), std::ios::binary); + return static_cast(file); } auto should_report_failed_stream_read(std::istream const& in) -> bool { - return in.eof() || in.bad(); + return in.eof() || in.bad(); } auto format_par_line(ParResultsMaster const sidesRes[2]) - -> std::optional + -> std::optional { - if (sidesRes[0].score == 0 && sidesRes[1].score == 0) - return std::string("Par: 0"); - - if (sidesRes[0].number <= 0 && sidesRes[1].number <= 0) - return std::nullopt; - - const ContractType& first = sidesRes[0].number > 0 - ? sidesRes[0].contracts[0] - : sidesRes[1].contracts[0]; - const int side = - (first.seats == 4 || first.seats == 0 || first.seats == 2) ? 0 : 1; - const ParResultsMaster& chosen = sidesRes[side]; - if (chosen.number <= 0) - return std::nullopt; + if (sidesRes[0].score == 0 && sidesRes[1].score == 0) + return std::string("Par: 0"); + + if (sidesRes[0].number <= 0 && sidesRes[1].number <= 0) + return std::nullopt; + + const ContractType& first = sidesRes[0].number > 0 + ? sidesRes[0].contracts[0] + : sidesRes[1].contracts[0]; + const int side = + (first.seats == 4 || first.seats == 0 || first.seats == 2) ? 0 : 1; + const ParResultsMaster& chosen = sidesRes[side]; + if (chosen.number <= 0) + return std::nullopt; + + std::string body; + for (int i = 0; i < chosen.number; ++i) + { + const bool include_seats = + i == 0 || chosen.contracts[i].seats != chosen.contracts[i - 1].seats; + const auto piece = format_contract(chosen.contracts[i], include_seats); + if (!piece) + return std::nullopt; + if (i > 0) + body += ", "; + body += *piece; + } - std::string body; - for (int i = 0; i < chosen.number; ++i) - { - const bool include_seats = - i == 0 || chosen.contracts[i].seats != chosen.contracts[i - 1].seats; - const auto piece = format_contract(chosen.contracts[i], include_seats); - if (!piece) - return std::nullopt; - if (i > 0) - body += ", "; - body += *piece; - } - - const ContractType& contract = chosen.contracts[0]; - char result[8]; - if (contract.under_tricks > 0) - std::snprintf(result, sizeof(result), "-%d", contract.under_tricks); - else if (contract.over_tricks > 0) - std::snprintf(result, sizeof(result), "+%d", contract.over_tricks); - else - std::snprintf(result, sizeof(result), "="); - - char line[256]; - const int written = std::snprintf( - line, - sizeof(line), - "Par: %s %s %d", - body.c_str(), - result, - chosen.score); - if (written <= 0 || static_cast(written) >= sizeof(line)) - return std::nullopt; - return std::string(line); + const ContractType& contract = chosen.contracts[0]; + char result[8]; + if (contract.under_tricks > 0) + std::snprintf(result, sizeof(result), "-%d", contract.under_tricks); + else if (contract.over_tricks > 0) + std::snprintf(result, sizeof(result), "+%d", contract.over_tricks); + else + std::snprintf(result, sizeof(result), "="); + + char line[256]; + const int written = std::snprintf( + line, + sizeof(line), + "Par: %s %s %d", + body.c_str(), + result, + chosen.score); + if (written <= 0 || static_cast(written) >= sizeof(line)) + return std::nullopt; + return std::string(line); } } // namespace dd_table_for_deal diff --git a/utilities/tests/dd_table_for_deal_test.cpp b/utilities/tests/dd_table_for_deal_test.cpp index cdb7503e8..ce50c395d 100644 --- a/utilities/tests/dd_table_for_deal_test.cpp +++ b/utilities/tests/dd_table_for_deal_test.cpp @@ -20,19 +20,19 @@ namespace { auto make_contract( - int seats, - int level, - int denom, - int under_tricks, - int over_tricks) -> ContractType + int seats, + int level, + int denom, + int under_tricks, + int over_tricks) -> ContractType { - ContractType c{}; - c.seats = seats; - c.level = level; - c.denom = denom; - c.under_tricks = under_tricks; - c.over_tricks = over_tricks; - return c; + ContractType c{}; + c.seats = seats; + c.level = level; + c.denom = denom; + c.under_tricks = under_tricks; + c.over_tricks = over_tricks; + return c; } } // namespace @@ -40,331 +40,331 @@ auto make_contract( TEST(ParseVulnerable, AcceptsAliasesAndCodes) { - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("none"), 0); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("None"), 0); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("0"), 0); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("both"), 1); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("1"), 1); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("ns"), 2); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("NS"), 2); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("2"), 2); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("ew"), 3); - EXPECT_EQ(dd_table_for_deal::parse_vulnerable("3"), 3); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("none"), 0); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("None"), 0); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("0"), 0); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("both"), 1); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("1"), 1); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("ns"), 2); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("NS"), 2); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("2"), 2); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("ew"), 3); + EXPECT_EQ(dd_table_for_deal::parse_vulnerable("3"), 3); } TEST(ParseVulnerable, RejectsUnknown) { - EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("maybe").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("4").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("maybe").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_vulnerable("4").has_value()); } TEST(ParseLimit, AcceptsPositiveIntegers) { - EXPECT_EQ(dd_table_for_deal::parse_limit("1"), 1u); - EXPECT_EQ(dd_table_for_deal::parse_limit("25"), 25u); + EXPECT_EQ(dd_table_for_deal::parse_limit("1"), 1u); + EXPECT_EQ(dd_table_for_deal::parse_limit("25"), 25u); } TEST(ParseLimit, RejectsNonPositiveAndNonNumeric) { - EXPECT_FALSE(dd_table_for_deal::parse_limit("").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_limit("0").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_limit("-1").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_limit("3x").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_limit("1.5").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_limit("").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_limit("0").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_limit("-1").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_limit("3x").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_limit("1.5").has_value()); } TEST(ParseNumthr, AcceptsZeroAndPositiveIntegers) { - EXPECT_EQ(dd_table_for_deal::parse_numthr("0"), 0); - EXPECT_EQ(dd_table_for_deal::parse_numthr("1"), 1); - EXPECT_EQ(dd_table_for_deal::parse_numthr("8"), 8); - EXPECT_EQ( - dd_table_for_deal::parse_numthr("2147483647"), - 2147483647); + EXPECT_EQ(dd_table_for_deal::parse_numthr("0"), 0); + EXPECT_EQ(dd_table_for_deal::parse_numthr("1"), 1); + EXPECT_EQ(dd_table_for_deal::parse_numthr("8"), 8); + EXPECT_EQ( + dd_table_for_deal::parse_numthr("2147483647"), + 2147483647); } TEST(ParseNumthr, RejectsNegativeAndNonNumeric) { - EXPECT_FALSE(dd_table_for_deal::parse_numthr("").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_numthr("-1").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_numthr("3x").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_numthr("1.5").has_value()); - EXPECT_FALSE(dd_table_for_deal::parse_numthr("2147483648").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_numthr("").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_numthr("-1").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_numthr("3x").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_numthr("1.5").has_value()); + EXPECT_FALSE(dd_table_for_deal::parse_numthr("2147483648").has_value()); } TEST(CalcDdTableForPbnDeal, ForwardsNumThreadsToSolver) { - int seen_threads = -1; - const auto fake_calc = - [&](DdTableDealPBN /*deal*/, DdTableResults * /*table*/, int num_threads) - { - seen_threads = num_threads; - return RETURN_NO_FAULT; - }; - - DdTableDealPBN deal{}; - DdTableResults table{}; - - EXPECT_EQ( - dd_table_for_deal::calc_dd_table_for_pbn_deal(deal, 1, &table, fake_calc), - RETURN_NO_FAULT); - EXPECT_EQ(seen_threads, 1); - - EXPECT_EQ( - dd_table_for_deal::calc_dd_table_for_pbn_deal(deal, 0, &table, fake_calc), - RETURN_NO_FAULT); - EXPECT_EQ(seen_threads, 0); + int seen_threads = -1; + const auto fake_calc = + [&](DdTableDealPBN /*deal*/, DdTableResults * /*table*/, int num_threads) + { + seen_threads = num_threads; + return RETURN_NO_FAULT; + }; + + DdTableDealPBN deal{}; + DdTableResults table{}; + + EXPECT_EQ( + dd_table_for_deal::calc_dd_table_for_pbn_deal(deal, 1, &table, fake_calc), + RETURN_NO_FAULT); + EXPECT_EQ(seen_threads, 1); + + EXPECT_EQ( + dd_table_for_deal::calc_dd_table_for_pbn_deal(deal, 0, &table, fake_calc), + RETURN_NO_FAULT); + EXPECT_EQ(seen_threads, 0); } TEST(ApplyDealLimit, KeepsPrefixWhenLimited) { - const std::vector deals{"a", "b", "c"}; - EXPECT_EQ( - dd_table_for_deal::apply_deal_limit(deals, 2), - (std::vector{"a", "b"})); - EXPECT_EQ( - dd_table_for_deal::apply_deal_limit(deals, std::nullopt), - deals); - EXPECT_EQ( - dd_table_for_deal::apply_deal_limit(deals, 10), - deals); + const std::vector deals{"a", "b", "c"}; + EXPECT_EQ( + dd_table_for_deal::apply_deal_limit(deals, 2), + (std::vector{"a", "b"})); + EXPECT_EQ( + dd_table_for_deal::apply_deal_limit(deals, std::nullopt), + deals); + EXPECT_EQ( + dd_table_for_deal::apply_deal_limit(deals, 10), + deals); } TEST(ReadPbnStream, EmptyInputReturnsEmptyString) { - std::istringstream in(""); - const auto text = dd_table_for_deal::read_pbn_stream(in); - ASSERT_TRUE(text.has_value()); - EXPECT_TRUE(text->empty()); + std::istringstream in(""); + const auto text = dd_table_for_deal::read_pbn_stream(in); + ASSERT_TRUE(text.has_value()); + EXPECT_TRUE(text->empty()); } TEST(ReadPbnStream, ReturnsContents) { - std::istringstream in("[Deal \"N:..\"]\n"); - const auto text = dd_table_for_deal::read_pbn_stream(in); - ASSERT_TRUE(text.has_value()); - EXPECT_EQ(*text, "[Deal \"N:..\"]\n"); + std::istringstream in("[Deal \"N:..\"]\n"); + const auto text = dd_table_for_deal::read_pbn_stream(in); + ASSERT_TRUE(text.has_value()); + EXPECT_EQ(*text, "[Deal \"N:..\"]\n"); } TEST(PathIsOpenable, TrueForExistingFile) { - const auto path = - std::filesystem::temp_directory_path() / "dds_dd_table_for_deal_openable.pbn"; - { - std::ofstream out(path); - out << "[Deal \"x\"]\n"; - } - EXPECT_TRUE(dd_table_for_deal::path_is_openable(path.string())); - std::filesystem::remove(path); + const auto path = + std::filesystem::temp_directory_path() / "dds_dd_table_for_deal_openable.pbn"; + { + std::ofstream out(path); + out << "[Deal \"x\"]\n"; + } + EXPECT_TRUE(dd_table_for_deal::path_is_openable(path.string())); + std::filesystem::remove(path); } TEST(PathIsOpenable, FalseForMissingFile) { - EXPECT_FALSE(dd_table_for_deal::path_is_openable( - "/definitely/missing/dds_dd_table_for_deal_no_such.pbn")); + EXPECT_FALSE(dd_table_for_deal::path_is_openable( + "/definitely/missing/dds_dd_table_for_deal_no_such.pbn")); } TEST(ShouldReportFailedStreamRead, TrueOnEofOrBad) { - std::istringstream empty(""); - empty.get(); - EXPECT_TRUE(empty.eof()); - EXPECT_TRUE(dd_table_for_deal::should_report_failed_stream_read(empty)); - - std::istringstream bad_stream("x"); - bad_stream.setstate(std::ios::badbit); - EXPECT_TRUE(dd_table_for_deal::should_report_failed_stream_read(bad_stream)); + std::istringstream empty(""); + empty.get(); + EXPECT_TRUE(empty.eof()); + EXPECT_TRUE(dd_table_for_deal::should_report_failed_stream_read(empty)); + + std::istringstream bad_stream("x"); + bad_stream.setstate(std::ios::badbit); + EXPECT_TRUE(dd_table_for_deal::should_report_failed_stream_read(bad_stream)); } TEST(ShouldReportFailedStreamRead, FalseWhenStreamStillReadable) { - // Oversized reads stop early without setting eof/bad; do not re-report. - std::istringstream mid("still-readable"); - EXPECT_FALSE(dd_table_for_deal::should_report_failed_stream_read(mid)); + // Oversized reads stop early without setting eof/bad; do not re-report. + std::istringstream mid("still-readable"); + EXPECT_FALSE(dd_table_for_deal::should_report_failed_stream_read(mid)); } TEST(ExtractDealTags, FindsAllTags) { - const char* text = - "{Board 1}\n" - "[Deal \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " - "5.A95432.7632.K6 AKJ9842.K.T8.J93\"]\n" - "\n" - "{Board 2}\n" - "[Deal \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 " - "K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3\"]\n"; - - const auto deals = dd_table_for_deal::extract_deal_tags(text); - ASSERT_EQ(deals.size(), 2u); - EXPECT_EQ( - deals[0], - "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " - "5.A95432.7632.K6 AKJ9842.K.T8.J93"); - EXPECT_EQ( - deals[1], - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 " - "K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); + const char* text = + "{Board 1}\n" + "[Deal \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93\"]\n" + "\n" + "{Board 2}\n" + "[Deal \"N:QJ6.K652.J85.T98 873.J97.AT764.Q4 " + "K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3\"]\n"; + + const auto deals = dd_table_for_deal::extract_deal_tags(text); + ASSERT_EQ(deals.size(), 2u); + EXPECT_EQ( + deals[0], + "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93"); + EXPECT_EQ( + deals[1], + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 " + "K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"); } TEST(ExtractDealTags, EmptyWhenNoTags) { - EXPECT_TRUE(dd_table_for_deal::extract_deal_tags("{comment only}").empty()); + EXPECT_TRUE(dd_table_for_deal::extract_deal_tags("{comment only}").empty()); } TEST(UniqueDeals, PreservesFirstSeenOrderAndDropsDuplicates) { - const std::vector deals = { - "deal-a", - "deal-b", - "deal-a", - "deal-c", - "deal-b", - "deal-a", - }; - const auto unique = dd_table_for_deal::unique_deals(deals); - ASSERT_EQ(unique.size(), 3u); - EXPECT_EQ(unique[0], "deal-a"); - EXPECT_EQ(unique[1], "deal-b"); - EXPECT_EQ(unique[2], "deal-c"); + const std::vector deals = { + "deal-a", + "deal-b", + "deal-a", + "deal-c", + "deal-b", + "deal-a", + }; + const auto unique = dd_table_for_deal::unique_deals(deals); + ASSERT_EQ(unique.size(), 3u); + EXPECT_EQ(unique[0], "deal-a"); + EXPECT_EQ(unique[1], "deal-b"); + EXPECT_EQ(unique[2], "deal-c"); } TEST(LooksLikePath, DetectsPathsAndExtensions) { - EXPECT_TRUE(dd_table_for_deal::looks_like_path("boards.pbn")); - EXPECT_TRUE(dd_table_for_deal::looks_like_path("hands/x.pbn")); - EXPECT_TRUE(dd_table_for_deal::looks_like_path("notes.txt")); - EXPECT_FALSE(dd_table_for_deal::looks_like_path( - "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " - "5.A95432.7632.K6 AKJ9842.K.T8.J93")); + EXPECT_TRUE(dd_table_for_deal::looks_like_path("boards.pbn")); + EXPECT_TRUE(dd_table_for_deal::looks_like_path("hands/x.pbn")); + EXPECT_TRUE(dd_table_for_deal::looks_like_path("notes.txt")); + EXPECT_FALSE(dd_table_for_deal::looks_like_path( + "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93")); } TEST(FormatParLine, SingleSacrifice) { - ParResultsMaster sides[2]{}; - sides[0].score = -300; - sides[0].number = 1; - sides[0].contracts[0] = make_contract(/*NS*/ 4, 5, /*H*/ 2, 2, 0); - sides[1].score = 300; - sides[1].number = 1; - sides[1].contracts[0] = make_contract(/*NS*/ 4, 5, /*H*/ 2, 2, 0); - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: NS 5Hx -2 -300"); + ParResultsMaster sides[2]{}; + sides[0].score = -300; + sides[0].number = 1; + sides[0].contracts[0] = make_contract(/*NS*/ 4, 5, /*H*/ 2, 2, 0); + sides[1].score = 300; + sides[1].number = 1; + sides[1].contracts[0] = make_contract(/*NS*/ 4, 5, /*H*/ 2, 2, 0); + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: NS 5Hx -2 -300"); } TEST(FormatParLine, SingleMakingUsesEqualsAndDeclaringScore) { - ParResultsMaster sides[2]{}; - sides[0].score = -110; - sides[0].number = 1; - sides[0].contracts[0] = make_contract(/*EW*/ 5, 2, /*S*/ 1, 0, 0); - sides[1].score = 110; - sides[1].number = 1; - sides[1].contracts[0] = make_contract(/*EW*/ 5, 2, /*S*/ 1, 0, 0); - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: EW 2S = 110"); + ParResultsMaster sides[2]{}; + sides[0].score = -110; + sides[0].number = 1; + sides[0].contracts[0] = make_contract(/*EW*/ 5, 2, /*S*/ 1, 0, 0); + sides[1].score = 110; + sides[1].number = 1; + sides[1].contracts[0] = make_contract(/*EW*/ 5, 2, /*S*/ 1, 0, 0); + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: EW 2S = 110"); } TEST(FormatParLine, MultipleSacrificesOnOneLine) { - ParResultsMaster sides[2]{}; - sides[0].score = 100; - sides[0].number = 2; - sides[0].contracts[0] = make_contract(/*EW*/ 5, 3, /*D*/ 3, 1, 0); - sides[0].contracts[1] = make_contract(/*EW*/ 5, 3, /*C*/ 4, 1, 0); - sides[1].score = -100; - sides[1].number = 2; - sides[1].contracts[0] = make_contract(/*EW*/ 5, 3, /*D*/ 3, 1, 0); - sides[1].contracts[1] = make_contract(/*EW*/ 5, 3, /*C*/ 4, 1, 0); - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: EW 3Dx, 3Cx -1 -100"); + ParResultsMaster sides[2]{}; + sides[0].score = 100; + sides[0].number = 2; + sides[0].contracts[0] = make_contract(/*EW*/ 5, 3, /*D*/ 3, 1, 0); + sides[0].contracts[1] = make_contract(/*EW*/ 5, 3, /*C*/ 4, 1, 0); + sides[1].score = -100; + sides[1].number = 2; + sides[1].contracts[0] = make_contract(/*EW*/ 5, 3, /*D*/ 3, 1, 0); + sides[1].contracts[1] = make_contract(/*EW*/ 5, 3, /*C*/ 4, 1, 0); + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: EW 3Dx, 3Cx -1 -100"); } TEST(FormatParLine, IncludesDeclaringSideWhenSeatsDiffer) { - // thomas1-style: W can sacrifice in hearts, E in clubs — both seats matter. - ParResultsMaster sides[2]{}; - sides[0].score = 100; - sides[0].number = 2; - sides[0].contracts[0] = make_contract(/*W*/ 3, 3, /*H*/ 2, 1, 0); - sides[0].contracts[1] = make_contract(/*E*/ 1, 3, /*C*/ 4, 1, 0); - sides[1].score = -100; - sides[1].number = 2; - sides[1].contracts[0] = make_contract(/*W*/ 3, 3, /*H*/ 2, 1, 0); - sides[1].contracts[1] = make_contract(/*E*/ 1, 3, /*C*/ 4, 1, 0); - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: W 3Hx, E 3Cx -1 -100"); + // thomas1-style: W can sacrifice in hearts, E in clubs — both seats matter. + ParResultsMaster sides[2]{}; + sides[0].score = 100; + sides[0].number = 2; + sides[0].contracts[0] = make_contract(/*W*/ 3, 3, /*H*/ 2, 1, 0); + sides[0].contracts[1] = make_contract(/*E*/ 1, 3, /*C*/ 4, 1, 0); + sides[1].score = -100; + sides[1].number = 2; + sides[1].contracts[0] = make_contract(/*W*/ 3, 3, /*H*/ 2, 1, 0); + sides[1].contracts[1] = make_contract(/*E*/ 1, 3, /*C*/ 4, 1, 0); + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: W 3Hx, E 3Cx -1 -100"); } TEST(FormatParLine, IncludesSeatWhenSecondContractNarrowsDeclaringSide) { - ParResultsMaster sides[2]{}; - sides[0].score = 100; - sides[0].number = 2; - sides[0].contracts[0] = make_contract(/*EW*/ 5, 4, /*H*/ 2, 1, 0); - sides[0].contracts[1] = make_contract(/*E*/ 1, 5, /*C*/ 4, 1, 0); - sides[1].score = -100; - sides[1].number = 2; - sides[1].contracts[0] = make_contract(/*EW*/ 5, 4, /*H*/ 2, 1, 0); - sides[1].contracts[1] = make_contract(/*E*/ 1, 5, /*C*/ 4, 1, 0); - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: EW 4Hx, E 5Cx -1 -100"); + ParResultsMaster sides[2]{}; + sides[0].score = 100; + sides[0].number = 2; + sides[0].contracts[0] = make_contract(/*EW*/ 5, 4, /*H*/ 2, 1, 0); + sides[0].contracts[1] = make_contract(/*E*/ 1, 5, /*C*/ 4, 1, 0); + sides[1].score = -100; + sides[1].number = 2; + sides[1].contracts[0] = make_contract(/*EW*/ 5, 4, /*H*/ 2, 1, 0); + sides[1].contracts[1] = make_contract(/*E*/ 1, 5, /*C*/ 4, 1, 0); + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: EW 4Hx, E 5Cx -1 -100"); } TEST(FormatParLine, PassedOut) { - ParResultsMaster sides[2]{}; - sides[0].score = 0; - sides[0].number = 1; - sides[1].score = 0; - sides[1].number = 1; - - const auto line = dd_table_for_deal::format_par_line(sides); - ASSERT_TRUE(line.has_value()); - EXPECT_EQ(*line, "Par: 0"); + ParResultsMaster sides[2]{}; + sides[0].score = 0; + sides[0].number = 1; + sides[1].score = 0; + sides[1].number = 1; + + const auto line = dd_table_for_deal::format_par_line(sides); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ(*line, "Par: 0"); } TEST(FormatParLine, ReturnsNulloptWhenNoContractsDespiteScores) { - ParResultsMaster sides[2]{}; - sides[0].score = -100; - sides[0].number = 0; - sides[1].score = 100; - sides[1].number = 0; + ParResultsMaster sides[2]{}; + sides[0].score = -100; + sides[0].number = 0; + sides[1].score = 100; + sides[1].number = 0; - EXPECT_FALSE(dd_table_for_deal::format_par_line(sides).has_value()); + EXPECT_FALSE(dd_table_for_deal::format_par_line(sides).has_value()); } diff --git a/wasm/calc_dd_table_pbn_test.cpp b/wasm/calc_dd_table_pbn_test.cpp index 0dfdc6e96..768d6ac97 100644 --- a/wasm/calc_dd_table_pbn_test.cpp +++ b/wasm/calc_dd_table_pbn_test.cpp @@ -14,12 +14,12 @@ TEST(CalcDdTablePbnWasmTest, MatchesReferenceTables) { - for (int handno = 0; handno < 3; ++handno) { + for (int handno = 0; handno < 3; ++handno) { DdTableDealPBN deal{}; std::strcpy(deal.cards, pbn_hands_[handno]); DdTableResults table{}; ASSERT_EQ(CalcDDtablePBN(deal, &table), RETURN_NO_FAULT) << "hand " << handno; EXPECT_TRUE(compare_table(&table, handno)) << "hand " << handno; - } + } } diff --git a/web/dds_web_wasm.cpp b/web/dds_web_wasm.cpp index 1c15d3cfd..199b7edb8 100644 --- a/web/dds_web_wasm.cpp +++ b/web/dds_web_wasm.cpp @@ -22,25 +22,25 @@ namespace { // Cap browser TT heaps: two session contexts at Large defaults would be ~190MB. auto web_solver_config() -> SolverConfig { - return SolverConfig{ - .tt_kind_ = TTKind::Small, - .tt_mem_default_mb_ = THREADMEM_SMALL_DEF_MB, - .tt_mem_maximum_mb_ = THREADMEM_SMALL_MAX_MB, - }; + return SolverConfig{ + .tt_kind_ = TTKind::Small, + .tt_mem_default_mb_ = THREADMEM_SMALL_DEF_MB, + .tt_mem_maximum_mb_ = THREADMEM_SMALL_MAX_MB, + }; } // Separate contexts so CalcDDtable worker pools cannot disturb SolveBoard // (and vice versa) when the UI auto-fills the table then analyzes leads. auto web_table_context() -> SolverContext& { - static SolverContext ctx(web_solver_config()); - return ctx; + static SolverContext ctx(web_solver_config()); + return ctx; } auto web_leads_context() -> SolverContext& { - static SolverContext ctx(web_solver_config()); - return ctx; + static SolverContext ctx(web_solver_config()); + return ctx; } // Caller buffer is out_leads[0] count plus at most 13 (suit,rank,score) triples. @@ -48,11 +48,11 @@ constexpr int kMaxExpandedLeads = 13; // Write one expanded lead at out_leads index `slot` (0-based among triples). void write_lead_triple( - int* out_leads, int slot, int suit, int rank, int score) + int* out_leads, int slot, int suit, int rank, int score) { - out_leads[1 + 3 * slot] = suit; - out_leads[1 + 3 * slot + 1] = rank; - out_leads[1 + 3 * slot + 2] = score; + out_leads[1 + 3 * slot] = suit; + out_leads[1 + 3 * slot + 1] = rank; + out_leads[1 + 3 * slot + 2] = score; } // True when Holding-encoded `equals` lists `rank` among lower equivalents. @@ -60,11 +60,11 @@ void write_lead_triple( // so rank r is present iff bit (r - 2) is set after >> 2. auto equals_lists_rank(int equals_holding, int rank) -> bool { - if (rank < 2 || rank > 14) { - return false; - } - const unsigned bits = static_cast(equals_holding) >> 2; - return (bits & (1u << (rank - 2))) != 0; + if (rank < 2 || rank > 14) { + return false; + } + const unsigned bits = static_cast(equals_holding) >> 2; + return (bits & (1u << (rank - 2))) != 0; } // FutureTricks packs leads: each of fut.cards is the highest card of an @@ -73,55 +73,55 @@ auto equals_lists_rank(int equals_holding, int rank) -> bool // card so the UI can badge every pip. Cap at 13 (one full hand on lead). void write_expanded_leads(const FutureTricks& fut, int* out_leads) { - int n = 0; - for (int i = 0; i < fut.cards && n < kMaxExpandedLeads; ++i) { - const int suit = fut.suit[i]; - const int score = fut.score[i]; - const int representative = fut.rank[i]; - - write_lead_triple(out_leads, n, suit, representative, score); - ++n; - - // Only ranks strictly below the representative are valid equals. - for (int rank = 2; rank < representative && n < kMaxExpandedLeads; ++rank) { - if (!equals_lists_rank(fut.equals[i], rank)) { - continue; - } - write_lead_triple(out_leads, n, suit, rank, score); - ++n; + int n = 0; + for (int i = 0; i < fut.cards && n < kMaxExpandedLeads; ++i) { + const int suit = fut.suit[i]; + const int score = fut.score[i]; + const int representative = fut.rank[i]; + + write_lead_triple(out_leads, n, suit, representative, score); + ++n; + + // Only ranks strictly below the representative are valid equals. + for (int rank = 2; rank < representative && n < kMaxExpandedLeads; ++rank) { + if (!equals_lists_rank(fut.equals[i], rank)) { + continue; + } + write_lead_triple(out_leads, n, suit, rank, score); + ++n; + } } - } - out_leads[0] = n; + out_leads[0] = n; } } // namespace #if !defined(__EMSCRIPTEN__) // Native unit-test hook for Holding-encoded equals expansion. extern "C" void dds_web_test_write_expanded_leads( - const FutureTricks* fut, int* out_leads) + const FutureTricks* fut, int* out_leads) { - if (fut == nullptr || out_leads == nullptr) { - return; - } - write_expanded_leads(*fut, out_leads); + if (fut == nullptr || out_leads == nullptr) { + return; + } + write_expanded_leads(*fut, out_leads); } // which: 0 = table context, 1 = leads context. extern "C" void dds_web_test_context_tt_config( - int which, int* kind_is_small, int* def_mb, int* max_mb) + int which, int* kind_is_small, int* def_mb, int* max_mb) { - SolverContext& ctx = - (which == 0) ? web_table_context() : web_leads_context(); - const SolverConfig& cfg = ctx.config(); - if (kind_is_small != nullptr) { - *kind_is_small = (cfg.tt_kind_ == TTKind::Small) ? 1 : 0; - } - if (def_mb != nullptr) { - *def_mb = cfg.tt_mem_default_mb_; - } - if (max_mb != nullptr) { - *max_mb = cfg.tt_mem_maximum_mb_; - } + SolverContext& ctx = + (which == 0) ? web_table_context() : web_leads_context(); + const SolverConfig& cfg = ctx.config(); + if (kind_is_small != nullptr) { + *kind_is_small = (cfg.tt_kind_ == TTKind::Small) ? 1 : 0; + } + if (def_mb != nullptr) { + *def_mb = cfg.tt_mem_default_mb_; + } + if (max_mb != nullptr) { + *max_mb = cfg.tt_mem_maximum_mb_; + } } #endif @@ -132,34 +132,34 @@ extern "C" { EMSCRIPTEN_KEEPALIVE auto dds_web_calc_table(const char* pbn, int* out_table) -> int { - if (pbn == nullptr || out_table == nullptr) { - return RETURN_UNKNOWN_FAULT; - } - - DdTableDealPBN deal{}; - const size_t pbn_len = std::strlen(pbn); - if (pbn_len >= sizeof(deal.cards)) { - return RETURN_PBN_FAULT; - } - std::memcpy(deal.cards, pbn, pbn_len + 1); - - SolverContext& ctx = web_table_context(); - ctx.reset_for_solve(); // recycle TT memory pool + search bookkeeping - // between deals; keeps the underlying allocation - - DdTableResults table{}; - const int res = calc_dd_table_pbn(ctx, deal, &table); - if (res != RETURN_NO_FAULT) { - return res; - } - - int k = 0; - for (int strain = 0; strain < DDS_STRAINS; ++strain) { - for (int hand = 0; hand < DDS_HANDS; ++hand) { - out_table[k++] = table.res_table[strain][hand]; + if (pbn == nullptr || out_table == nullptr) { + return RETURN_UNKNOWN_FAULT; + } + + DdTableDealPBN deal{}; + const size_t pbn_len = std::strlen(pbn); + if (pbn_len >= sizeof(deal.cards)) { + return RETURN_PBN_FAULT; + } + std::memcpy(deal.cards, pbn, pbn_len + 1); + + SolverContext& ctx = web_table_context(); + ctx.reset_for_solve(); // recycle TT memory pool + search bookkeeping + // between deals; keeps the underlying allocation + + DdTableResults table{}; + const int res = calc_dd_table_pbn(ctx, deal, &table); + if (res != RETURN_NO_FAULT) { + return res; } - } - return RETURN_NO_FAULT; + + int k = 0; + for (int strain = 0; strain < DDS_STRAINS; ++strain) { + for (int hand = 0; hand < DDS_HANDS; ++hand) { + out_table[k++] = table.res_table[strain][hand]; + } + } + return RETURN_NO_FAULT; } // Solves all opening leads from `first` in `trump`. @@ -169,41 +169,41 @@ auto dds_web_calc_table(const char* pbn, int* out_table) -> int // n is capped at 13. Caller must provide at least 1 + 13*3 ints. EMSCRIPTEN_KEEPALIVE auto dds_web_solve_leads( - const char* pbn, int trump, int first, int* out_leads) -> int + const char* pbn, int trump, int first, int* out_leads) -> int { - if (pbn == nullptr || out_leads == nullptr) { - return RETURN_UNKNOWN_FAULT; - } - if (trump < 0 || trump > 4 || first < 0 || first > 3) { - return RETURN_UNKNOWN_FAULT; - } - - Deal dl{}; - dl.trump = trump; - dl.first = first; - dl.currentTrickSuit[0] = 0; - dl.currentTrickSuit[1] = 0; - dl.currentTrickSuit[2] = 0; - dl.currentTrickRank[0] = 0; - dl.currentTrickRank[1] = 0; - dl.currentTrickRank[2] = 0; - - if (convert_from_pbn(pbn, dl.remainCards) != RETURN_NO_FAULT) { - return RETURN_PBN_FAULT; - } - - SolverContext& ctx = web_leads_context(); - ctx.reset_for_solve(); - - FutureTricks fut{}; - const int res = solve_board(ctx, dl, /*target=*/-1, /*solutions=*/3, - /*mode=*/0, &fut); - if (res != RETURN_NO_FAULT) { - return res; - } - - write_expanded_leads(fut, out_leads); - return RETURN_NO_FAULT; + if (pbn == nullptr || out_leads == nullptr) { + return RETURN_UNKNOWN_FAULT; + } + if (trump < 0 || trump > 4 || first < 0 || first > 3) { + return RETURN_UNKNOWN_FAULT; + } + + Deal dl{}; + dl.trump = trump; + dl.first = first; + dl.currentTrickSuit[0] = 0; + dl.currentTrickSuit[1] = 0; + dl.currentTrickSuit[2] = 0; + dl.currentTrickRank[0] = 0; + dl.currentTrickRank[1] = 0; + dl.currentTrickRank[2] = 0; + + if (convert_from_pbn(pbn, dl.remainCards) != RETURN_NO_FAULT) { + return RETURN_PBN_FAULT; + } + + SolverContext& ctx = web_leads_context(); + ctx.reset_for_solve(); + + FutureTricks fut{}; + const int res = solve_board(ctx, dl, /*target=*/-1, /*solutions=*/3, + /*mode=*/0, &fut); + if (res != RETURN_NO_FAULT) { + return res; + } + + write_expanded_leads(fut, out_leads); + return RETURN_NO_FAULT; } } // extern "C" @@ -211,6 +211,6 @@ auto dds_web_solve_leads( #if !defined(__EMSCRIPTEN__) && !defined(DDS_WEB_WASM_NO_MAIN) auto main() -> int { - return 0; + return 0; } #endif diff --git a/web/dds_web_wasm_test.cpp b/web/dds_web_wasm_test.cpp index a1622bd89..df1151773 100644 --- a/web/dds_web_wasm_test.cpp +++ b/web/dds_web_wasm_test.cpp @@ -15,234 +15,234 @@ extern "C" int dds_web_calc_table(const char* pbn, int* out_table); extern "C" int dds_web_solve_leads( - const char* pbn, int trump, int first, int* out_leads); + const char* pbn, int trump, int first, int* out_leads); extern "C" void dds_web_test_write_expanded_leads( - const FutureTricks* fut, int* out_leads); + const FutureTricks* fut, int* out_leads); extern "C" void dds_web_test_context_tt_config( - int which, int* kind_is_small, int* def_mb, int* max_mb); + int which, int* kind_is_small, int* def_mb, int* max_mb); namespace { constexpr int kExpectedHand0[20] = { - 5, 8, 5, 8, 6, 6, 6, 6, 5, 7, 5, 7, 7, 5, 7, 5, 6, 6, 6, 6, + 5, 8, 5, 8, 6, 6, 6, 6, 5, 7, 5, 7, 7, 5, 7, 5, 6, 6, 6, 6, }; constexpr char kPbnHand0[] = - "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; + "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"; constexpr int kExpectedHand1[20] = { - 3, 10, 3, 10, 9, 4, 9, 4, 8, 4, 8, 4, 3, 9, 3, 9, 4, 8, 4, 8, + 3, 10, 3, 10, 9, 4, 9, 4, 8, 4, 8, 4, 3, 9, 3, 9, 4, 8, 4, 8, }; constexpr char kPbnHand1[] = - "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93"; + "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93"; } // namespace TEST(DdsWebWasmTest, WebContextsUseSmallTranspositionTables) { - // Dual session contexts must not each default to Large (~95MB) TT heaps. - for (int which = 0; which < 2; ++which) { - int kind_is_small = 0; - int def_mb = -1; - int max_mb = -1; - dds_web_test_context_tt_config( - which, &kind_is_small, &def_mb, &max_mb); - EXPECT_EQ(kind_is_small, 1) << "context " << which; - EXPECT_EQ(def_mb, THREADMEM_SMALL_DEF_MB) << "context " << which; - EXPECT_EQ(max_mb, THREADMEM_SMALL_MAX_MB) << "context " << which; - } + // Dual session contexts must not each default to Large (~95MB) TT heaps. + for (int which = 0; which < 2; ++which) { + int kind_is_small = 0; + int def_mb = -1; + int max_mb = -1; + dds_web_test_context_tt_config( + which, &kind_is_small, &def_mb, &max_mb); + EXPECT_EQ(kind_is_small, 1) << "context " << which; + EXPECT_EQ(def_mb, THREADMEM_SMALL_DEF_MB) << "context " << which; + EXPECT_EQ(max_mb, THREADMEM_SMALL_MAX_MB) << "context " << which; + } } TEST(DdsWebWasmTest, RejectsNullPointers) { - int out[20]{}; - EXPECT_EQ(dds_web_calc_table(nullptr, out), RETURN_UNKNOWN_FAULT); - EXPECT_EQ(dds_web_calc_table(kPbnHand0, nullptr), RETURN_UNKNOWN_FAULT); + int out[20]{}; + EXPECT_EQ(dds_web_calc_table(nullptr, out), RETURN_UNKNOWN_FAULT); + EXPECT_EQ(dds_web_calc_table(kPbnHand0, nullptr), RETURN_UNKNOWN_FAULT); } TEST(DdsWebWasmTest, RejectsPbnTooLong) { - int out[20]{}; - const std::string too_long(80, 'A'); - EXPECT_EQ(dds_web_calc_table(too_long.c_str(), out), RETURN_PBN_FAULT); + int out[20]{}; + const std::string too_long(80, 'A'); + EXPECT_EQ(dds_web_calc_table(too_long.c_str(), out), RETURN_PBN_FAULT); } TEST(DdsWebWasmTest, RejectsInvalidPbn) { - int out[20]{}; - const int res = dds_web_calc_table("not-a-valid-pbn", out); - EXPECT_NE(res, RETURN_NO_FAULT); - EXPECT_LT(res, RETURN_NO_FAULT); + int out[20]{}; + const int res = dds_web_calc_table("not-a-valid-pbn", out); + EXPECT_NE(res, RETURN_NO_FAULT); + EXPECT_LT(res, RETURN_NO_FAULT); } TEST(DdsWebWasmTest, FillsFlatStrainHandTable) { - int out[20]{}; - ASSERT_EQ(dds_web_calc_table(kPbnHand0, out), RETURN_NO_FAULT); - for (int i = 0; i < 20; ++i) { - EXPECT_EQ(out[i], kExpectedHand0[i]) << "index " << i; - } + int out[20]{}; + ASSERT_EQ(dds_web_calc_table(kPbnHand0, out), RETURN_NO_FAULT); + for (int i = 0; i < 20; ++i) { + EXPECT_EQ(out[i], kExpectedHand0[i]) << "index " << i; + } } TEST(DdsWebWasmTest, FillsFlatStrainHandTableAcrossReuse) { - int out0[20]{}; - ASSERT_EQ(dds_web_calc_table(kPbnHand0, out0), RETURN_NO_FAULT); - for (int i = 0; i < 20; ++i) { - EXPECT_EQ(out0[i], kExpectedHand0[i]) << "hand0 index " << i; - } - - int out1[20]{}; - ASSERT_EQ(dds_web_calc_table(kPbnHand1, out1), RETURN_NO_FAULT); - for (int i = 0; i < 20; ++i) { - EXPECT_EQ(out1[i], kExpectedHand1[i]) << "hand1 index " << i; - } - - // Solve hand0 again through the same reused context to confirm the - // intervening different-deal solve didn't leave stale state behind. - int out0_again[20]{}; - ASSERT_EQ(dds_web_calc_table(kPbnHand0, out0_again), RETURN_NO_FAULT); - for (int i = 0; i < 20; ++i) { - EXPECT_EQ(out0_again[i], kExpectedHand0[i]) << "hand0 repeat index " << i; - } + int out0[20]{}; + ASSERT_EQ(dds_web_calc_table(kPbnHand0, out0), RETURN_NO_FAULT); + for (int i = 0; i < 20; ++i) { + EXPECT_EQ(out0[i], kExpectedHand0[i]) << "hand0 index " << i; + } + + int out1[20]{}; + ASSERT_EQ(dds_web_calc_table(kPbnHand1, out1), RETURN_NO_FAULT); + for (int i = 0; i < 20; ++i) { + EXPECT_EQ(out1[i], kExpectedHand1[i]) << "hand1 index " << i; + } + + // Solve hand0 again through the same reused context to confirm the + // intervening different-deal solve didn't leave stale state behind. + int out0_again[20]{}; + ASSERT_EQ(dds_web_calc_table(kPbnHand0, out0_again), RETURN_NO_FAULT); + for (int i = 0; i < 20; ++i) { + EXPECT_EQ(out0_again[i], kExpectedHand0[i]) << "hand0 repeat index " << i; + } } // Part-score deal from the DDS Web page (fillFormWithPartScoreTestData). constexpr char kPbnPartScore[] = - "N:AQ85.AK976.5.J87 JT.QJ5432.Q9.KQ9 972..JT863.A6432 K643.T8.AK742.T5"; + "N:AQ85.AK976.5.J87 JT.QJ5432.Q9.KQ9 972..JT863.A6432 K643.T8.AK742.T5"; TEST(DdsWebWasmTest, SolveLeadsRejectsNullPointers) { - int out[40]{}; - EXPECT_EQ(dds_web_solve_leads(nullptr, 4, 3, out), RETURN_UNKNOWN_FAULT); - EXPECT_EQ( - dds_web_solve_leads(kPbnPartScore, 4, 3, nullptr), RETURN_UNKNOWN_FAULT); + int out[40]{}; + EXPECT_EQ(dds_web_solve_leads(nullptr, 4, 3, out), RETURN_UNKNOWN_FAULT); + EXPECT_EQ( + dds_web_solve_leads(kPbnPartScore, 4, 3, nullptr), RETURN_UNKNOWN_FAULT); } TEST(DdsWebWasmTest, SolveLeadsReturnsOpeningLeadTricksForEachCard) { - // South declares NT → West leads. CalcDDtable says South takes 6 in NT, so - // the best EW opening lead scores 7 tricks for the side on lead. - int out[40]{}; - ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), - RETURN_NO_FAULT); - - const int n = out[0]; - ASSERT_EQ(n, 13); - - int max_score = -1; - bool saw_sk = false; - for (int i = 0; i < n; ++i) { - const int suit = out[1 + 3 * i]; - const int rank = out[1 + 3 * i + 1]; - const int score = out[1 + 3 * i + 2]; - EXPECT_GE(suit, 0); - EXPECT_LE(suit, 3); - EXPECT_GE(rank, 2); - EXPECT_LE(rank, 14); - EXPECT_GE(score, 0); - EXPECT_LE(score, 13); - if (score > max_score) { - max_score = score; - } - // West holds ♠K. - if (suit == 0 && rank == 13) { - saw_sk = true; + // South declares NT → West leads. CalcDDtable says South takes 6 in NT, so + // the best EW opening lead scores 7 tricks for the side on lead. + int out[40]{}; + ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), + RETURN_NO_FAULT); + + const int n = out[0]; + ASSERT_EQ(n, 13); + + int max_score = -1; + bool saw_sk = false; + for (int i = 0; i < n; ++i) { + const int suit = out[1 + 3 * i]; + const int rank = out[1 + 3 * i + 1]; + const int score = out[1 + 3 * i + 2]; + EXPECT_GE(suit, 0); + EXPECT_LE(suit, 3); + EXPECT_GE(rank, 2); + EXPECT_LE(rank, 14); + EXPECT_GE(score, 0); + EXPECT_LE(score, 13); + if (score > max_score) { + max_score = score; + } + // West holds ♠K. + if (suit == 0 && rank == 13) { + saw_sk = true; + } } - } - EXPECT_TRUE(saw_sk); - EXPECT_EQ(max_score, 7); + EXPECT_TRUE(saw_sk); + EXPECT_EQ(max_score, 7); } TEST(DdsWebWasmTest, SolveLeadsWorksAfterCalcTableOnSharedSession) { - // Auto-solve fills the DD table first, then a contract click runs leads on - // the same module session — leads must still return 13 cards. - int table[20]{}; - ASSERT_EQ(dds_web_calc_table(kPbnPartScore, table), RETURN_NO_FAULT); - - int out[40]{}; - ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), - RETURN_NO_FAULT); - ASSERT_EQ(out[0], 13); - - // A second table solve must not poison a following lead solve either. - ASSERT_EQ(dds_web_calc_table(kPbnPartScore, table), RETURN_NO_FAULT); - ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), - RETURN_NO_FAULT); - EXPECT_EQ(out[0], 13); + // Auto-solve fills the DD table first, then a contract click runs leads on + // the same module session — leads must still return 13 cards. + int table[20]{}; + ASSERT_EQ(dds_web_calc_table(kPbnPartScore, table), RETURN_NO_FAULT); + + int out[40]{}; + ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), + RETURN_NO_FAULT); + ASSERT_EQ(out[0], 13); + + // A second table solve must not poison a following lead solve either. + ASSERT_EQ(dds_web_calc_table(kPbnPartScore, table), RETURN_NO_FAULT); + ASSERT_EQ(dds_web_solve_leads(kPbnPartScore, /*trump=*/4, /*first=*/3, out), + RETURN_NO_FAULT); + EXPECT_EQ(out[0], 13); } TEST(DdsWebWasmTest, ExpandedLeadsUseHoldingEncodedEqualsBitmask) { - // FutureTricks packs equivalent leads (not one entry per card). Holding - // encoding (dll-description / equals_to_string): equals stores sequence << 2, - // so rank r is bit (r-2) after >> 2 — not (1 << r) on the shifted-down value. - // Queen alone in equals is sequence 0x0400 → 0x1000. Expansion writes both - // the representative king and the equal queen so the UI can badge each pip. - FutureTricks fut{}; - fut.cards = 1; - fut.suit[0] = 0; // spades - fut.rank[0] = 13; // king (representative) - fut.equals[0] = 0x0400 << 2; // queen equivalent - fut.score[0] = 7; - - int out[40]{}; - dds_web_test_write_expanded_leads(&fut, out); - - ASSERT_EQ(out[0], 2); - EXPECT_EQ(out[1], 0); - EXPECT_EQ(out[2], 13); - EXPECT_EQ(out[3], 7); - EXPECT_EQ(out[4], 0); - EXPECT_EQ(out[5], 12); // queen from equals - EXPECT_EQ(out[6], 7); + // FutureTricks packs equivalent leads (not one entry per card). Holding + // encoding (dll-description / equals_to_string): equals stores sequence << 2, + // so rank r is bit (r-2) after >> 2 — not (1 << r) on the shifted-down value. + // Queen alone in equals is sequence 0x0400 → 0x1000. Expansion writes both + // the representative king and the equal queen so the UI can badge each pip. + FutureTricks fut{}; + fut.cards = 1; + fut.suit[0] = 0; // spades + fut.rank[0] = 13; // king (representative) + fut.equals[0] = 0x0400 << 2; // queen equivalent + fut.score[0] = 7; + + int out[40]{}; + dds_web_test_write_expanded_leads(&fut, out); + + ASSERT_EQ(out[0], 2); + EXPECT_EQ(out[1], 0); + EXPECT_EQ(out[2], 13); + EXPECT_EQ(out[3], 7); + EXPECT_EQ(out[4], 0); + EXPECT_EQ(out[5], 12); // queen from equals + EXPECT_EQ(out[6], 7); } TEST(DdsWebWasmTest, ExpandedLeadsIgnoresEqualsBitsAtOrAboveRepresentative) { - // dll-description: equals are *lower*-ranked equivalents only. A bit for the - // representative (or higher) must not produce a duplicate out_leads triple. - FutureTricks fut{}; - fut.cards = 1; - fut.suit[0] = 0; - fut.rank[0] = 13; // king - // Queen (correct lower equal) | King (invalid self bit) | Ace (invalid higher). - const int queen = 1 << (12 - 2); - const int king = 1 << (13 - 2); - const int ace = 1 << (14 - 2); - fut.equals[0] = (queen | king | ace) << 2; - fut.score[0] = 5; - - int out[40]{}; - dds_web_test_write_expanded_leads(&fut, out); - - ASSERT_EQ(out[0], 2); - EXPECT_EQ(out[2], 13); // king once - EXPECT_EQ(out[5], 12); // queen only + // dll-description: equals are *lower*-ranked equivalents only. A bit for the + // representative (or higher) must not produce a duplicate out_leads triple. + FutureTricks fut{}; + fut.cards = 1; + fut.suit[0] = 0; + fut.rank[0] = 13; // king + // Queen (correct lower equal) | King (invalid self bit) | Ace (invalid higher). + const int queen = 1 << (12 - 2); + const int king = 1 << (13 - 2); + const int ace = 1 << (14 - 2); + fut.equals[0] = (queen | king | ace) << 2; + fut.score[0] = 5; + + int out[40]{}; + dds_web_test_write_expanded_leads(&fut, out); + + ASSERT_EQ(out[0], 2); + EXPECT_EQ(out[2], 13); // king once + EXPECT_EQ(out[5], 12); // queen only } TEST(DdsWebWasmTest, ExpandedLeadsHardCapsAtThirteenCards) { - // One representative with equals for ranks 2-13 expands to 13 cards; a second - // FutureTricks entry must not write past the caller buffer (1 + 13*3). - FutureTricks fut{}; - fut.cards = 2; - fut.suit[0] = 0; - fut.rank[0] = 14; // ace - int equals = 0; - for (int rank = 2; rank <= 13; ++rank) { - equals |= 1 << (rank - 2); - } - fut.equals[0] = equals << 2; - fut.score[0] = 7; - - fut.suit[1] = 1; - fut.rank[1] = 14; - fut.equals[1] = 0x0400 << 2; // would be a 14th/15th card without a cap - fut.score[1] = 6; - - constexpr int kCap = 13; - constexpr int kSlots = 1 + kCap * 3; - constexpr int kCanary = 0x7EFEFEFE; - int out[kSlots + 3]; - for (int i = 0; i < kSlots + 3; ++i) { - out[i] = kCanary; - } - - dds_web_test_write_expanded_leads(&fut, out); - - ASSERT_EQ(out[0], kCap); - EXPECT_EQ(out[kSlots], kCanary); - EXPECT_EQ(out[kSlots + 1], kCanary); - EXPECT_EQ(out[kSlots + 2], kCanary); + // One representative with equals for ranks 2-13 expands to 13 cards; a second + // FutureTricks entry must not write past the caller buffer (1 + 13*3). + FutureTricks fut{}; + fut.cards = 2; + fut.suit[0] = 0; + fut.rank[0] = 14; // ace + int equals = 0; + for (int rank = 2; rank <= 13; ++rank) { + equals |= 1 << (rank - 2); + } + fut.equals[0] = equals << 2; + fut.score[0] = 7; + + fut.suit[1] = 1; + fut.rank[1] = 14; + fut.equals[1] = 0x0400 << 2; // would be a 14th/15th card without a cap + fut.score[1] = 6; + + constexpr int kCap = 13; + constexpr int kSlots = 1 + kCap * 3; + constexpr int kCanary = 0x7EFEFEFE; + int out[kSlots + 3]; + for (int i = 0; i < kSlots + 3; ++i) { + out[i] = kCanary; + } + + dds_web_test_write_expanded_leads(&fut, out); + + ASSERT_EQ(out[0], kCap); + EXPECT_EQ(out[kSlots], kCanary); + EXPECT_EQ(out[kSlots + 1], kCanary); + EXPECT_EQ(out[kSlots + 2], kCanary); }