From 9a3e933da9174df5d80c8b544d9d4231bc981147 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sat, 23 May 2026 21:53:54 +0200 Subject: [PATCH 001/157] First commit for calc_dd.cpp and related. --- examples/BUILD.bazel | 14 ++++ examples/calc_dd.cpp | 162 ++++++++++++++++++++++++++++++++++++ hands/everyone_makes_3N.pbn | 2 + hands/example.pbn | 1 + hands/thomas1.pbn | 2 + hands/thomas2.pbn | 2 + 6 files changed, 183 insertions(+) create mode 100644 examples/calc_dd.cpp create mode 100644 hands/everyone_makes_3N.pbn create mode 100644 hands/example.pbn create mode 100644 hands/thomas1.pbn create mode 100644 hands/thomas2.pbn diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 8b0b67d3..d4266f3c 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -147,6 +147,19 @@ cc_binary( ], ) +cc_binary( + name = "calc_dds", + srcs = ["calc_dds.cpp"], + copts = EXAMPLES_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = EXAMPLES_LOCAL_DEFINES, + deps = [ + ":hands", + "//library/src:dds", + "//library/src/api:api_definitions", + ], +) + cc_binary( name = "dealer_par", srcs = ["dealer_par.cpp"], @@ -251,6 +264,7 @@ filegroup( ":calc_all_tables_pbn", ":calc_dd_table", ":calc_dd_table_pbn", + ":calc_dds", ":calc_par_context_example", ":dealer_par", ":par", diff --git a/examples/calc_dd.cpp b/examples/calc_dd.cpp new file mode 100644 index 00000000..511144e1 --- /dev/null +++ b/examples/calc_dd.cpp @@ -0,0 +1,162 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2016 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + + +// Calculate all 20 par results for a single deal from the command line or a PBN file. + +// Coded by Cursor, based on calc_dd_table.cpp + +#include +#include +#include +#include +#include +#include "hands.hpp" + + +#define PBN_FILE_MAX 8192 + + +static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> bool +{ + FILE * f = fopen(path, "rb"); + if (f == nullptr) + { + return false; + } + + const size_t n = fread(buf, 1, buf_size - 1, f); + fclose(f); + + if (n == 0) + { + return false; + } + + buf[n] = '\0'; + return true; +} + + +static auto extract_deal_tag(const char * text, char * deal, size_t deal_size) -> bool +{ + const char * p = text; + + while ((p = strstr(p, "[Deal")) != nullptr) + { + p += 5; + + while (*p != '\0' && isspace(static_cast(*p))) + { + ++p; + } + + if (*p != '"') + { + continue; + } + + ++p; + const char * start = p; + + while (*p != '\0' && *p != '"') + { + ++p; + } + + if (*p != '"') + { + continue; + } + + const size_t len = static_cast(p - start); + if (len >= deal_size) + { + return false; + } + + memcpy(deal, start, len); + deal[len] = '\0'; + return true; + } + + return false; +} + + +static auto load_deal(const char * arg, char * deal, size_t deal_size) -> int +{ + char file_buf[PBN_FILE_MAX]; + + if (read_pbn_file(arg, file_buf, sizeof(file_buf))) + { + if (!extract_deal_tag(file_buf, deal, deal_size)) + { + fprintf(stderr, "No [Deal \"...\"] tag found in %s\n", arg); + return 1; + } + } + else if (strlen(arg) >= deal_size) + { + fprintf(stderr, + "PBN deal too long (max %zu characters)\n", + deal_size - 1); + return 1; + } + else + { + strcpy(deal, arg); + } + + return 0; +} + + +auto main(int argc, char * argv[]) -> int +{ + if (argc != 2) + { + fprintf(stderr, + "Usage: %s \n" + "Example: %s \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93\"\n" + "Example: %s hand.pbn\n", + argv[0], + argv[0], + argv[0]); + return 1; + } + + DdTableDealPBN tableDealPBN; + DdTableResults table; + char line[80]; + + if (load_deal(argv[1], tableDealPBN.cards, sizeof(tableDealPBN.cards)) != 0) + { + return 1; + } + +#if defined(__linux) || defined(__APPLE__) + SetMaxThreads(0); +#endif + + const int res = CalcDDtablePBN(tableDealPBN, &table); + if (res != RETURN_NO_FAULT) + { + ErrorMessage(res, line); + fprintf(stderr, "DDS error: %s\n", line); + return 1; + } + + sprintf(line, "calc_dds:\n"); + print_pbn_hand(line, tableDealPBN.cards); + print_table(&table); + + return 0; +} diff --git a/hands/everyone_makes_3N.pbn b/hands/everyone_makes_3N.pbn new file mode 100644 index 00000000..91aa97eb --- /dev/null +++ b/hands/everyone_makes_3N.pbn @@ -0,0 +1,2 @@ +{Every direction can make 3N - a deal constructed by Richard Pavlicek} +[Deal "N:QT9.A8765432.KJ. KJ..A8765432.QT9 A8765432.QT9..KJ .KJ.QT9.A8765432"] diff --git a/hands/example.pbn b/hands/example.pbn new file mode 100644 index 00000000..d2c5571c --- /dev/null +++ b/hands/example.pbn @@ -0,0 +1 @@ +[Deal "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93"] \ No newline at end of file diff --git a/hands/thomas1.pbn b/hands/thomas1.pbn new file mode 100644 index 00000000..0b7db8df --- /dev/null +++ b/hands/thomas1.pbn @@ -0,0 +1,2 @@ +{Thomas 1 - time consuming to solve} +[Deal "S:Q853.AJ962.KT74. AJ962.KT74..Q853 KT74..Q853.AJ962 .Q853.AJ962.KT74"] diff --git a/hands/thomas2.pbn b/hands/thomas2.pbn new file mode 100644 index 00000000..973b0e91 --- /dev/null +++ b/hands/thomas2.pbn @@ -0,0 +1,2 @@ +{Thomas 2 - time consuming to solve} +[Deal "S:AQT8642.KJ9753.. KJ9753..AQT8642. ..KJ9753.AQT8642 .AQT8642..KJ9753"] \ No newline at end of file From 9d4c025931b7db1022bdea9d81eeece9a415db6b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sat, 23 May 2026 22:08:32 +0200 Subject: [PATCH 002/157] Read from stdin if available. Fix BUILD.bazel target name. --- examples/BUILD.bazel | 7 ++- examples/calc_dd.cpp | 114 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 100 insertions(+), 21 deletions(-) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index d4266f3c..642bb3f3 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -149,7 +149,7 @@ cc_binary( cc_binary( name = "calc_dds", - srcs = ["calc_dds.cpp"], + srcs = ["calc_dd.cpp"], copts = EXAMPLES_CPPOPTS, linkopts = DDS_LINKOPTS, local_defines = EXAMPLES_LOCAL_DEFINES, @@ -160,6 +160,11 @@ cc_binary( ], ) +alias( + name = "calc_dd", + actual = ":calc_dds", +) + cc_binary( name = "dealer_par", srcs = ["dealer_par.cpp"], diff --git a/examples/calc_dd.cpp b/examples/calc_dd.cpp index 511144e1..61e8ad05 100644 --- a/examples/calc_dd.cpp +++ b/examples/calc_dd.cpp @@ -16,6 +16,11 @@ #include #include #include +#if defined(_WIN32) +#include +#else +#include +#endif #include #include "hands.hpp" @@ -23,17 +28,19 @@ #define PBN_FILE_MAX 8192 -static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> bool +static auto stdin_is_tty() -> bool { - FILE * f = fopen(path, "rb"); - if (f == nullptr) - { - return false; - } +#if defined(_WIN32) + return _isatty(_fileno(stdin)) != 0; +#else + return isatty(STDIN_FILENO) != 0; +#endif +} - const size_t n = fread(buf, 1, buf_size - 1, f); - fclose(f); +static auto read_pbn_stream(FILE * stream, char * buf, size_t buf_size) -> bool +{ + const size_t n = fread(buf, 1, buf_size - 1, stream); if (n == 0) { return false; @@ -44,6 +51,20 @@ static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> boo } +static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> bool +{ + FILE * f = fopen(path, "rb"); + if (f == nullptr) + { + return false; + } + + const bool ok = read_pbn_stream(f, buf, buf_size); + fclose(f); + return ok; +} + + static auto extract_deal_tag(const char * text, char * deal, size_t deal_size) -> bool { const char * p = text; @@ -93,12 +114,31 @@ static auto extract_deal_tag(const char * text, char * deal, size_t deal_size) - static auto load_deal(const char * arg, char * deal, size_t deal_size) -> int { char file_buf[PBN_FILE_MAX]; + const char * source = arg; - if (read_pbn_file(arg, file_buf, sizeof(file_buf))) + if (strcmp(arg, "-") == 0) + { + source = "stdin"; + if (!read_pbn_stream(stdin, file_buf, sizeof(file_buf))) + { + fprintf(stderr, "No PBN input on stdin\n"); + return 1; + } + } + else if (read_pbn_file(arg, file_buf, sizeof(file_buf))) + { + source = arg; + } + else + { + source = nullptr; + } + + if (source != nullptr) { if (!extract_deal_tag(file_buf, deal, deal_size)) { - fprintf(stderr, "No [Deal \"...\"] tag found in %s\n", arg); + fprintf(stderr, "No [Deal \"...\"] tag found in %s\n", source); return 1; } } @@ -118,18 +158,52 @@ static auto load_deal(const char * arg, char * deal, size_t deal_size) -> int } +static auto print_usage(const char * prog) -> void +{ + fprintf(stderr, + "Usage: %s \n" + " %s -h | --help\n" + "\n" + "Calculate double-dummy tricks for all strains and leads.\n" + "\n" + "Arguments:\n" + " DDS PBN deal string, or path to a .pbn file\n" + "\n" + "If stdin is not a terminal, PBN is read from stdin (uses [Deal \"...\"]).\n" + "\n" + "Examples:\n" + " %s \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93\"\n" + " %s hands/example.pbn\n" + " %s < hands/example.pbn\n", + prog, + prog, + prog, + prog, + prog); +} + + auto main(int argc, char * argv[]) -> int { - if (argc != 2) + const char * input; + + if (argc == 2) { - fprintf(stderr, - "Usage: %s \n" - "Example: %s \"N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " - "5.A95432.7632.K6 AKJ9842.K.T8.J93\"\n" - "Example: %s hand.pbn\n", - argv[0], - argv[0], - argv[0]); + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) + { + print_usage(argv[0]); + return 0; + } + input = argv[1]; + } + else if (argc == 1 && !stdin_is_tty()) + { + input = "-"; + } + else + { + print_usage(argv[0]); return 1; } @@ -137,7 +211,7 @@ auto main(int argc, char * argv[]) -> int DdTableResults table; char line[80]; - if (load_deal(argv[1], tableDealPBN.cards, sizeof(tableDealPBN.cards)) != 0) + if (load_deal(input, tableDealPBN.cards, sizeof(tableDealPBN.cards)) != 0) { return 1; } From 7e6d338d64d7a40e09236b629ee23c4a6021e7e7 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 25 May 2026 15:47:36 +0200 Subject: [PATCH 003/157] Add two pbn files for manual testing. --- hands/12_cards.pbn | 1 + hands/all_solid.pbn | 1 + 2 files changed, 2 insertions(+) create mode 100644 hands/12_cards.pbn create mode 100644 hands/all_solid.pbn diff --git a/hands/12_cards.pbn b/hands/12_cards.pbn new file mode 100644 index 00000000..af0e40fa --- /dev/null +++ b/hands/12_cards.pbn @@ -0,0 +1 @@ +[Deal "N:KQJT98765432... .KQJT98765432.. ..KQJT98765432. ...KQJT98765432"] \ No newline at end of file diff --git a/hands/all_solid.pbn b/hands/all_solid.pbn new file mode 100644 index 00000000..af48e16d --- /dev/null +++ b/hands/all_solid.pbn @@ -0,0 +1 @@ +[Deal "N:AKQJT98765432... .AKQJT98765432.. ..AKQJT98765432. ...AKQJT98765432"] \ No newline at end of file From 075277b5f7563dea5d1ff886ca4959f6dfe2ed97 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 25 May 2026 15:53:38 +0200 Subject: [PATCH 004/157] renamed to calc_par --- examples/{calc_dd.cpp => calc_par.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{calc_dd.cpp => calc_par.cpp} (100%) diff --git a/examples/calc_dd.cpp b/examples/calc_par.cpp similarity index 100% rename from examples/calc_dd.cpp rename to examples/calc_par.cpp From 5a9a61ee3e2144458815491955bfe5326d8a7f36 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 25 May 2026 15:53:55 +0200 Subject: [PATCH 005/157] renamed to calc_par --- examples/BUILD.bazel | 11 +++-------- examples/README | 1 + examples/calc_par.cpp | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 642bb3f3..aa4c504c 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -148,8 +148,8 @@ cc_binary( ) cc_binary( - name = "calc_dds", - srcs = ["calc_dd.cpp"], + name = "calc_par", + srcs = ["calc_par.cpp"], copts = EXAMPLES_CPPOPTS, linkopts = DDS_LINKOPTS, local_defines = EXAMPLES_LOCAL_DEFINES, @@ -160,11 +160,6 @@ cc_binary( ], ) -alias( - name = "calc_dd", - actual = ":calc_dds", -) - cc_binary( name = "dealer_par", srcs = ["dealer_par.cpp"], @@ -269,7 +264,7 @@ filegroup( ":calc_all_tables_pbn", ":calc_dd_table", ":calc_dd_table_pbn", - ":calc_dds", + ":calc_par", ":calc_par_context_example", ":dealer_par", ":par", diff --git a/examples/README b/examples/README index 9541664d..e5a22423 100644 --- a/examples/README +++ b/examples/README @@ -16,5 +16,6 @@ Available examples: - analyse_play_bin, analyse_play_pbn - calc_all_tables, calc_all_tables_pbn - calc_dd_table, calc_dd_table_pbn +- calc_par - dealer_par, par - solve_all_boards, solve_board, solve_board_pbn diff --git a/examples/calc_par.cpp b/examples/calc_par.cpp index 61e8ad05..33f8a0bf 100644 --- a/examples/calc_par.cpp +++ b/examples/calc_par.cpp @@ -228,7 +228,7 @@ auto main(int argc, char * argv[]) -> int return 1; } - sprintf(line, "calc_dds:\n"); + sprintf(line, "calc_par:\n"); print_pbn_hand(line, tableDealPBN.cards); print_table(&table); From d8c697887787bc0175350ce672f96d544470ea2c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 31 May 2026 13:00:08 +0200 Subject: [PATCH 006/157] Renamed calc_par example to dd_table_for_deal --- examples/BUILD.bazel | 6 +++--- examples/README | 2 +- examples/{calc_par.cpp => dd_table_for_deal.cpp} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename examples/{calc_par.cpp => dd_table_for_deal.cpp} (97%) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index aa4c504c..e425c145 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -148,8 +148,8 @@ cc_binary( ) cc_binary( - name = "calc_par", - srcs = ["calc_par.cpp"], + name = "dd_table_for_deal", + srcs = ["dd_table_for_deal.cpp"], copts = EXAMPLES_CPPOPTS, linkopts = DDS_LINKOPTS, local_defines = EXAMPLES_LOCAL_DEFINES, @@ -264,7 +264,7 @@ filegroup( ":calc_all_tables_pbn", ":calc_dd_table", ":calc_dd_table_pbn", - ":calc_par", + ":dd_table_for_deal", ":calc_par_context_example", ":dealer_par", ":par", diff --git a/examples/README b/examples/README index e5a22423..d41f620c 100644 --- a/examples/README +++ b/examples/README @@ -16,6 +16,6 @@ Available examples: - analyse_play_bin, analyse_play_pbn - calc_all_tables, calc_all_tables_pbn - calc_dd_table, calc_dd_table_pbn -- calc_par +- dd_table_for_deal - dealer_par, par - solve_all_boards, solve_board, solve_board_pbn diff --git a/examples/calc_par.cpp b/examples/dd_table_for_deal.cpp similarity index 97% rename from examples/calc_par.cpp rename to examples/dd_table_for_deal.cpp index 33f8a0bf..cbde74e7 100644 --- a/examples/calc_par.cpp +++ b/examples/dd_table_for_deal.cpp @@ -8,7 +8,7 @@ */ -// Calculate all 20 par results for a single deal from the command line or a PBN file. +// Print the double-dummy table for a deal from the command line or a PBN file. // Coded by Cursor, based on calc_dd_table.cpp @@ -228,7 +228,7 @@ auto main(int argc, char * argv[]) -> int return 1; } - sprintf(line, "calc_par:\n"); + sprintf(line, "dd_table_for_deal:\n"); print_pbn_hand(line, tableDealPBN.cards); print_table(&table); From 901751d130db13901112e77fa812c4a4fef23dfc Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 31 May 2026 17:58:04 +0200 Subject: [PATCH 007/157] Added read_pbn_file_workspace_relative --- examples/dd_table_for_deal.cpp | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/examples/dd_table_for_deal.cpp b/examples/dd_table_for_deal.cpp index cbde74e7..53c0e28e 100644 --- a/examples/dd_table_for_deal.cpp +++ b/examples/dd_table_for_deal.cpp @@ -65,6 +65,39 @@ static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> boo } +static auto read_pbn_file_workspace_relative( + const char * path, + char * buf, + size_t buf_size) -> bool +{ + if (read_pbn_file(path, buf, buf_size)) + { + return true; + } + + // bazel run uses a runfiles cwd; BUILD_WORKSPACE_DIRECTORY is the repo root. + const char * workspace = getenv("BUILD_WORKSPACE_DIRECTORY"); + if (workspace == nullptr) + { + return false; + } + + char combined[PBN_FILE_MAX]; + const int n = snprintf( + combined, + sizeof(combined), + "%s/%s", + workspace, + path); + if (n < 0 || static_cast(n) >= sizeof(combined)) + { + return false; + } + + return read_pbn_file(combined, buf, buf_size); +} + + static auto extract_deal_tag(const char * text, char * deal, size_t deal_size) -> bool { const char * p = text; @@ -125,7 +158,7 @@ static auto load_deal(const char * arg, char * deal, size_t deal_size) -> int return 1; } } - else if (read_pbn_file(arg, file_buf, sizeof(file_buf))) + else if (read_pbn_file_workspace_relative(arg, file_buf, sizeof(file_buf))) { source = arg; } From 7e78900ee97e1957aed1aef1aab45602425d7d94 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 31 May 2026 18:00:23 +0200 Subject: [PATCH 008/157] Added dd_table_for_deal.py --- python/examples/BUILD.bazel | 8 + python/examples/README.md | 43 +++++ python/examples/dd_table_for_deal.py | 249 +++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 python/examples/BUILD.bazel create mode 100644 python/examples/README.md create mode 100644 python/examples/dd_table_for_deal.py diff --git a/python/examples/BUILD.bazel b/python/examples/BUILD.bazel new file mode 100644 index 00000000..39e7c152 --- /dev/null +++ b/python/examples/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +py_binary( + name = "dd_table_for_deal", + srcs = ["dd_table_for_deal.py"], + main = "dd_table_for_deal.py", + deps = ["//python:dds3_lib"], +) diff --git a/python/examples/README.md b/python/examples/README.md new file mode 100644 index 00000000..24e5b4f2 --- /dev/null +++ b/python/examples/README.md @@ -0,0 +1,43 @@ +**Python hints** + +See also `python/tests/README.md` and `docs/python_interface.md` + +*Run via Bazel* + +```bash +bazel run //python/examples:dd_table_for_deal "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93" +``` + +or + +```bash +bazel run //python/examples:dd_table_for_deal hands/example.pbn +``` + + +*Run without Bazel* + +To run the examples directly, without Bazel, you'll need to do one of these: + +```bash +export PYTHONPATH=python:bazel-bin/python +``` + +or + +```bash +bazel run //python:install_dds3_so +export PYTHONPATH=python +``` + +Direct commands: + +```bash +python python/examples/dd_table_for_deal.py "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93" +``` + +or + +```bash +python python/examples/dd_table_for_deal.py hands/example.pbn +``` \ No newline at end of file diff --git a/python/examples/dd_table_for_deal.py b/python/examples/dd_table_for_deal.py new file mode 100644 index 00000000..53652505 --- /dev/null +++ b/python/examples/dd_table_for_deal.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +"""Print the double-dummy table for a deal from PBN on the command line or a file. + +Python counterpart to examples/dd_table_for_deal.cpp. +""" + +from __future__ import annotations + +import os +import re +import sys +from pathlib import Path + +from dds3 import calc_all_tables_pbn + +PBN_FILE_MAX = 8192 + +# res_table[strain][hand]: strain 0-3 = S,H,D,C; 4 = NT. Columns match C++ print_table. +_STRAIN_ROWS = (("NT", 4), ("S", 0), ("H", 1), ("D", 2), ("C", 3)) +_HAND_COLUMNS = (("North", 0), ("South", 2), ("East", 1), ("West", 3)) + +_DDS_FULL_LINE = 80 +_DDS_HAND_OFFSET = 12 +_DDS_HAND_LINES = 12 + +_BIT_MAP_RANK = [ + 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, + 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, +] +_CARD_RANK_CHARS = "xx23456789TJQKA-" + +_DEAL_TAG_RE = re.compile(r'\[Deal\s*"([^"]*)"', re.IGNORECASE) + + +def _read_pbn_stream(stream) -> str | None: + data = stream.read(PBN_FILE_MAX - 1) + if not data: + return None + return data if isinstance(data, str) else data.decode("utf-8", errors="replace") + + +def _read_pbn_file(path: str) -> str | None: + candidates = [Path(path)] + workspace = os.environ.get("BUILD_WORKSPACE_DIRECTORY") + if workspace is not None: + candidates.append(Path(workspace) / path) + for candidate in candidates: + try: + return candidate.read_text(encoding="utf-8", errors="replace")[ + : PBN_FILE_MAX - 1 + ] + except OSError: + continue + return None + + +def _extract_deal_tag(text: str) -> str | None: + match = _DEAL_TAG_RE.search(text) + return match.group(1) if match else None + + +def _load_deal(arg: str) -> str: + if arg == "-": + text = _read_pbn_stream(sys.stdin) + if text is None: + raise ValueError("No PBN input on stdin") + source = "stdin" + else: + text = _read_pbn_file(arg) + source = arg if text is not None else None + + if source is not None: + deal = _extract_deal_tag(text) + if deal is None: + raise ValueError(f'No [Deal "..."] tag found in {source}') + return deal + + if len(arg) >= PBN_FILE_MAX: + raise ValueError(f"PBN deal too long (max {PBN_FILE_MAX - 1} characters)") + return arg + + +def _print_usage(prog: str) -> None: + print( + f"Usage: {prog} \n" + f" {prog} -h | --help\n" + "\n" + "Calculate double-dummy tricks for all strains and leads.\n" + "\n" + "Arguments:\n" + " DDS PBN deal string, or path to a .pbn file\n" + "\n" + 'If stdin is not a terminal, PBN is read from stdin (uses [Deal "..."]).\n' + "\n" + "Examples:\n" + f' {prog} "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 ' + f'5.A95432.7632.K6 AKJ9842.K.T8.J93"\n' + f" {prog} hands/example.pbn\n" + f" {prog} < hands/example.pbn\n", + file=sys.stderr, + ) + + +def _is_card(ch: str) -> int: + ch = ch.upper() + ranks = "23456789TJQKA" + return ranks.index(ch) + 2 if ch in ranks else 0 + + +def _convert_pbn(pbn_deal: str) -> list[list[int]]: + """Match examples/hands.cpp convert_pbn (4 hands x 4 suits bitmasks).""" + remain = [[0] * 4 for _ in range(4)] + bp = 0 + while ( + bp < 3 + and pbn_deal[bp] not in "NWESnwes" + ): + bp += 1 + if bp >= 3: + return remain + + first = {"N": 0, "E": 1, "S": 2, "W": 3}[pbn_deal[bp].upper()] + bp += 2 + hand_rel_first = 0 + suit_in_hand = 0 + + while bp < 80 and bp < len(pbn_deal): + ch = pbn_deal[bp] + card = _is_card(ch) + if card: + if first == 0: + hand = hand_rel_first + elif first == 1: + hand = 1 if hand_rel_first == 0 else 0 if hand_rel_first == 3 else hand_rel_first + 1 + elif first == 2: + hand = 2 if hand_rel_first == 0 else 3 if hand_rel_first == 1 else hand_rel_first - 2 + else: + hand = 3 if hand_rel_first == 0 else hand_rel_first - 1 + remain[hand][suit_in_hand] |= _BIT_MAP_RANK[card] << 2 + elif ch == ".": + suit_in_hand += 1 + elif ch == " ": + hand_rel_first += 1 + suit_in_hand = 0 + bp += 1 + return remain + + +def _print_pbn_hand(title: str, pbn_deal: str) -> None: + """Match examples/hands.cpp print_pbn_hand / print_hand.""" + remain_cards = _convert_pbn(pbn_deal) + text = [[" "] * _DDS_FULL_LINE for _ in range(_DDS_HAND_LINES)] + row_ends = [_DDS_FULL_LINE] * _DDS_HAND_LINES + + for h in range(4): + if h == 0: + offset, line = _DDS_HAND_OFFSET, 0 + elif h == 1: + offset, line = 2 * _DDS_HAND_OFFSET, 4 + elif h == 2: + offset, line = _DDS_HAND_OFFSET, 8 + else: + offset, line = 0, 4 + + for s in range(4): + row = line + s + c = offset + for r in range(14, 1, -1): + if (remain_cards[h][s] >> 2) & _BIT_MAP_RANK[r]: + text[row][c] = _CARD_RANK_CHARS[r] + c += 1 + if c == offset: + text[row][c] = "-" + c += 1 + if h != 3: + row_ends[row] = c + + sys.stdout.write(title) + dash_len = max(0, len(title) - 1) + print("-" * dash_len) + for i in range(_DDS_HAND_LINES): + print("".join(text[i][: row_ends[i]])) + print("\n") + + +def _print_table(res_table: list[list[int]]) -> None: + """Match examples/hands.cpp print_table (%5s %-5s ... / %5c %5d ...).""" + print(f"{'':>5} {'North':<5} {'South':<5} {'East':<5} {'West':<5}") + + _, nt_strain = _STRAIN_ROWS[0] + print( + f"{'NT':>5} " + f"{res_table[nt_strain][_HAND_COLUMNS[0][1]]:5d} " + f"{res_table[nt_strain][_HAND_COLUMNS[1][1]]:5d} " + f"{res_table[nt_strain][_HAND_COLUMNS[2][1]]:5d} " + f"{res_table[nt_strain][_HAND_COLUMNS[3][1]]:5d}" + ) + + for label, strain in _STRAIN_ROWS[1:]: + print( + f"{label:>5} " + f"{res_table[strain][_HAND_COLUMNS[0][1]]:5d} " + f"{res_table[strain][_HAND_COLUMNS[1][1]]:5d} " + f"{res_table[strain][_HAND_COLUMNS[2][1]]:5d} " + f"{res_table[strain][_HAND_COLUMNS[3][1]]:5d}" + ) + print() + + +def main(argv: list[str] | None = None) -> int: + argv = list(sys.argv if argv is None else argv) + prog = Path(argv[0]).name + + if len(argv) == 2: + if argv[1] in ("-h", "--help"): + _print_usage(prog) + return 0 + input_arg = argv[1] + elif len(argv) == 1 and not sys.stdin.isatty(): + input_arg = "-" + else: + _print_usage(prog) + return 1 + + try: + pbn_deal = _load_deal(input_arg) + except ValueError as exc: + print(exc, file=sys.stderr) + return 1 + + try: + result = calc_all_tables_pbn([pbn_deal]) + except (ValueError, RuntimeError) as exc: + print(f"DDS error: {exc}", file=sys.stderr) + return 1 + + tables = result.get("tables") + if not tables: + print("DDS error: no table returned", file=sys.stderr) + return 1 + + res_table = tables[0]["res_table"] + _print_pbn_hand("dd_table_for_deal:\n", pbn_deal) + _print_table(res_table) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d3f9b18f322f9992ca55a67d007aaad3763ea093 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 31 May 2026 18:01:03 +0200 Subject: [PATCH 009/157] Added usage for Python dd_table_for_deal --- examples/README | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/README b/examples/README index d41f620c..c1c472bb 100644 --- a/examples/README +++ b/examples/README @@ -11,11 +11,14 @@ Build a specific example: Run an example: bazel run //examples:calc_dd_table +Python `dd_table_for_deal` (see `python/examples/`): + bazel run //python/examples:dd_table_for_deal -- hands/example.pbn + Available examples: - analyse_all_plays_bin, analyse_all_plays_pbn - analyse_play_bin, analyse_play_pbn - calc_all_tables, calc_all_tables_pbn - calc_dd_table, calc_dd_table_pbn -- dd_table_for_deal +- dd_table_for_deal (C++; Python: //python/examples:dd_table_for_deal) - dealer_par, par - solve_all_boards, solve_board, solve_board_pbn From 0a83349911d2ce74acce54fd6d42942cc2fe1898 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 5 Jun 2026 07:31:43 +0200 Subject: [PATCH 010/157] Added newlines at the end of each file --- hands/12_cards.pbn | 2 +- hands/all_solid.pbn | 2 +- hands/example.pbn | 2 +- hands/thomas2.pbn | 2 +- python/examples/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hands/12_cards.pbn b/hands/12_cards.pbn index af0e40fa..07f4d8d0 100644 --- a/hands/12_cards.pbn +++ b/hands/12_cards.pbn @@ -1 +1 @@ -[Deal "N:KQJT98765432... .KQJT98765432.. ..KQJT98765432. ...KQJT98765432"] \ No newline at end of file +[Deal "N:KQJT98765432... .KQJT98765432.. ..KQJT98765432. ...KQJT98765432"] diff --git a/hands/all_solid.pbn b/hands/all_solid.pbn index af48e16d..a70fc311 100644 --- a/hands/all_solid.pbn +++ b/hands/all_solid.pbn @@ -1 +1 @@ -[Deal "N:AKQJT98765432... .AKQJT98765432.. ..AKQJT98765432. ...AKQJT98765432"] \ No newline at end of file +[Deal "N:AKQJT98765432... .AKQJT98765432.. ..AKQJT98765432. ...AKQJT98765432"] diff --git a/hands/example.pbn b/hands/example.pbn index d2c5571c..ae636b3c 100644 --- a/hands/example.pbn +++ b/hands/example.pbn @@ -1 +1 @@ -[Deal "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93"] \ No newline at end of file +[Deal "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 5.A95432.7632.K6 AKJ9842.K.T8.J93"] diff --git a/hands/thomas2.pbn b/hands/thomas2.pbn index 973b0e91..3c7f5f66 100644 --- a/hands/thomas2.pbn +++ b/hands/thomas2.pbn @@ -1,2 +1,2 @@ {Thomas 2 - time consuming to solve} -[Deal "S:AQT8642.KJ9753.. KJ9753..AQT8642. ..KJ9753.AQT8642 .AQT8642..KJ9753"] \ No newline at end of file +[Deal "S:AQT8642.KJ9753.. KJ9753..AQT8642. ..KJ9753.AQT8642 .AQT8642..KJ9753"] diff --git a/python/examples/README.md b/python/examples/README.md index 24e5b4f2..9de7faf9 100644 --- a/python/examples/README.md +++ b/python/examples/README.md @@ -40,4 +40,4 @@ or ```bash python python/examples/dd_table_for_deal.py hands/example.pbn -``` \ No newline at end of file +``` From 66b8ede4820ca04b97a7ae02f69e74e33cc21452 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 5 Jun 2026 14:58:38 +0100 Subject: [PATCH 011/157] Replace //python:install_dds3_so with //python:_dds3 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- python/examples/README.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/python/examples/README.md b/python/examples/README.md index 9de7faf9..eb7fe972 100644 --- a/python/examples/README.md +++ b/python/examples/README.md @@ -17,18 +17,10 @@ bazel run //python/examples:dd_table_for_deal hands/example.pbn *Run without Bazel* -To run the examples directly, without Bazel, you'll need to do one of these: +To run the examples directly, without Bazel, first build the native module and set `PYTHONPATH`: -```bash -export PYTHONPATH=python:bazel-bin/python -``` - -or - -```bash -bazel run //python:install_dds3_so -export PYTHONPATH=python -``` + bazel build //python:_dds3 + export PYTHONPATH=python:bazel-bin/python Direct commands: From c42179b37a8b6c917c412c313320d575d223dcdb Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 07:02:14 -0400 Subject: [PATCH 012/157] Use modern C++20 instead of C-style I/O and string handling. --- examples/dd_table_for_deal.cpp | 197 +++++++++++++++------------------ 1 file changed, 87 insertions(+), 110 deletions(-) diff --git a/examples/dd_table_for_deal.cpp b/examples/dd_table_for_deal.cpp index 53c0e28e..9f59adfc 100644 --- a/examples/dd_table_for_deal.cpp +++ b/examples/dd_table_for_deal.cpp @@ -12,10 +12,17 @@ // Coded by Cursor, based on calc_dd_table.cpp -#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include #if defined(_WIN32) #include #else @@ -25,7 +32,14 @@ #include "hands.hpp" -#define PBN_FILE_MAX 8192 +namespace { + +constexpr std::size_t PBN_FILE_MAX = 8192; +constexpr std::size_t PBN_DEAL_MAX = sizeof(DdTableDealPBN::cards); + +const std::regex DEAL_TAG_RE{ + R"re(\[Deal\s*"([^"]*)")re", + std::regex::icase}; static auto stdin_is_tty() -> bool @@ -38,158 +52,109 @@ static auto stdin_is_tty() -> bool } -static auto read_pbn_stream(FILE * stream, char * buf, size_t buf_size) -> bool +auto read_pbn_stream(std::istream& in) -> std::optional { - const size_t n = fread(buf, 1, buf_size - 1, stream); - if (n == 0) + std::string text(PBN_FILE_MAX - 1, '\0'); + in.read(text.data(), static_cast(PBN_FILE_MAX - 1)); + const auto n = in.gcount(); + if (n <= 0) { - return false; + return std::nullopt; } - buf[n] = '\0'; - return true; + text.resize(static_cast(n)); + return text; } -static auto read_pbn_file(const char * path, char * buf, size_t buf_size) -> bool +auto read_pbn_file(const std::filesystem::path& path) -> std::optional { - FILE * f = fopen(path, "rb"); - if (f == nullptr) + std::ifstream file(path, std::ios::binary); + if (!file) { - return false; + return std::nullopt; } - const bool ok = read_pbn_stream(f, buf, buf_size); - fclose(f); - return ok; + return read_pbn_stream(file); } -static auto read_pbn_file_workspace_relative( - const char * path, - char * buf, - size_t buf_size) -> bool +auto read_pbn_file_workspace_relative(std::string_view path) + -> std::optional { - if (read_pbn_file(path, buf, buf_size)) + if (auto text = read_pbn_file(std::filesystem::path(path))) { - return true; + return text; } // bazel run uses a runfiles cwd; BUILD_WORKSPACE_DIRECTORY is the repo root. - const char * workspace = getenv("BUILD_WORKSPACE_DIRECTORY"); - if (workspace == nullptr) + if (const char* workspace = std::getenv("BUILD_WORKSPACE_DIRECTORY")) { - return false; + return read_pbn_file(std::filesystem::path(workspace) / path); } - char combined[PBN_FILE_MAX]; - const int n = snprintf( - combined, - sizeof(combined), - "%s/%s", - workspace, - path); - if (n < 0 || static_cast(n) >= sizeof(combined)) - { - return false; - } - - return read_pbn_file(combined, buf, buf_size); + return std::nullopt; } -static auto extract_deal_tag(const char * text, char * deal, size_t deal_size) -> bool +auto extract_deal_tag(std::string_view text) -> std::optional { - const char * p = text; - - while ((p = strstr(p, "[Deal")) != nullptr) + std::match_results match; + if (std::regex_search(text.cbegin(), text.cend(), match, DEAL_TAG_RE) + && match.size() > 1) { - p += 5; - - while (*p != '\0' && isspace(static_cast(*p))) - { - ++p; - } - - if (*p != '"') - { - continue; - } + return std::string(match[1].first, match[1].second); + } - ++p; - const char * start = p; + return std::nullopt; +} - while (*p != '\0' && *p != '"') - { - ++p; - } - if (*p != '"') +auto load_deal(std::string_view arg) -> std::optional +{ + if (arg == "-") + { + const auto text = read_pbn_stream(std::cin); + if (!text) { - continue; + std::cerr << "No PBN input on stdin\n"; + return std::nullopt; } - const size_t len = static_cast(p - start); - if (len >= deal_size) + const auto deal = extract_deal_tag(*text); + if (!deal) { - return false; + std::cerr << "No [Deal \"...\"] tag found in stdin\n"; + return std::nullopt; } - memcpy(deal, start, len); - deal[len] = '\0'; - return true; + return deal; } - return false; -} - - -static auto load_deal(const char * arg, char * deal, size_t deal_size) -> int -{ - char file_buf[PBN_FILE_MAX]; - const char * source = arg; - - if (strcmp(arg, "-") == 0) + if (const auto text = read_pbn_file_workspace_relative(arg)) { - source = "stdin"; - if (!read_pbn_stream(stdin, file_buf, sizeof(file_buf))) + const auto deal = extract_deal_tag(*text); + if (!deal) { - fprintf(stderr, "No PBN input on stdin\n"); - return 1; + std::cerr << "No [Deal \"...\"] tag found in " << arg << "\n"; + return std::nullopt; } - } - else if (read_pbn_file_workspace_relative(arg, file_buf, sizeof(file_buf))) - { - source = arg; - } - else - { - source = nullptr; - } - if (source != nullptr) - { - if (!extract_deal_tag(file_buf, deal, deal_size)) - { - fprintf(stderr, "No [Deal \"...\"] tag found in %s\n", source); - return 1; - } + return deal; } - else if (strlen(arg) >= deal_size) - { - fprintf(stderr, - "PBN deal too long (max %zu characters)\n", - deal_size - 1); - return 1; - } - else + + if (arg.size() >= PBN_DEAL_MAX) { - strcpy(deal, arg); + std::cerr << "PBN deal too long (max " << (PBN_DEAL_MAX - 1) + << " characters)\n"; + return std::nullopt; } - return 0; + return std::string(arg); } +} // namespace + static auto print_usage(const char * prog) -> void { @@ -240,15 +205,27 @@ auto main(int argc, char * argv[]) -> int return 1; } - DdTableDealPBN tableDealPBN; - DdTableResults table; - char line[80]; + const auto deal = load_deal(input); + if (!deal) + { + return 1; + } - if (load_deal(input, tableDealPBN.cards, sizeof(tableDealPBN.cards)) != 0) + DdTableDealPBN tableDealPBN{}; + if (deal->size() >= sizeof(tableDealPBN.cards)) { + fprintf(stderr, + "PBN deal too long (max %zu characters)\n", + sizeof(tableDealPBN.cards) - 1); return 1; } + std::copy_n(deal->begin(), deal->size(), tableDealPBN.cards); + tableDealPBN.cards[deal->size()] = '\0'; + + DdTableResults table; + char line[80]; + #if defined(__linux) || defined(__APPLE__) SetMaxThreads(0); #endif From 8220c410e0c2e88f64cf023a1386078c9711216d Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 7 Jun 2026 07:41:25 -0400 Subject: [PATCH 013/157] Added tests and a test runner for the Javascript functions, per https://github.com/dds-bridge/dds/issues/171 --- web/BUILD.bazel | 13 ++ web/dds_mvp.js | 5 +- web/tests/dds_mvp_test.mjs | 235 +++++++++++++++++++++++++++++++++++ web/tests/test_dds_mvp_js.py | 53 ++++++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 web/tests/dds_mvp_test.mjs create mode 100644 web/tests/test_dds_mvp_js.py diff --git a/web/BUILD.bazel b/web/BUILD.bazel index 6a439057..9c2d2e8f 100644 --- a/web/BUILD.bazel +++ b/web/BUILD.bazel @@ -74,6 +74,18 @@ py_test( ], ) +py_test( + name = "dds_mvp_js_test", + size = "small", + timeout = "short", + main = "tests/test_dds_mvp_js.py", + srcs = ["tests/test_dds_mvp_js.py"], + data = [ + "dds_mvp.js", + "tests/dds_mvp_test.mjs", + ], +) + # Stages wasm artifacts and runs Node smoke (~1–2s; wasm build is a separate analysis action). py_test( name = "dds_mvp_wasm_system_test", @@ -123,6 +135,7 @@ py_test( test_suite( name = "web_tests", tests = [ + ":dds_mvp_js_test", ":dds_mvp_wasm_test", ":wasm_scripts_test", ], diff --git a/web/dds_mvp.js b/web/dds_mvp.js index 10de0f38..1a7220ca 100644 --- a/web/dds_mvp.js +++ b/web/dds_mvp.js @@ -4,7 +4,10 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT -// TODO: Add tests for the exported functions. +// Unit tests: web/tests/dds_mvp_test.mjs +// RUN WITH bazel test //web:dds_mvp_js_test) +// or python -m unittest web.tests.test_dds_mvp_js +// or node --test web/tests/dds_mvp_test.mjs // ESLint configuration // https://eslint.org/demo diff --git a/web/tests/dds_mvp_test.mjs b/web/tests/dds_mvp_test.mjs new file mode 100644 index 00000000..d19fba6c --- /dev/null +++ b/web/tests/dds_mvp_test.mjs @@ -0,0 +1,235 @@ +/** + * Unit tests for web/dds_mvp.js (Node built-in test runner). + * + * Run with: + * bazel test //web:dds_mvp_js_test + * or python -m unittest web.tests.test_dds_mvp_js + * or node --test web/tests/dds_mvp_test.mjs + */ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { createContext, runInContext } from "node:vm"; + +const DIRECTIONS = ["north", "east", "south", "west"]; +const SUITS = ["spades", "hearts", "diamonds", "clubs"]; + +function findWebRoot() { + const here = dirname(fileURLToPath(import.meta.url)); + const adjacent = join(here, ".."); + if (existsSync(join(adjacent, "dds_mvp.js"))) { + return adjacent; + } + + for (const base of [process.env.TEST_SRCDIR, process.env.RUNFILES_DIR]) { + if (!base) { + continue; + } + for (const sub of ["web", "_main/web"]) { + const candidate = join(base, sub); + if (existsSync(join(candidate, "dds_mvp.js"))) { + return candidate; + } + } + } + + throw new Error("dds_mvp.js not found"); +} + +function createMockDocument(initialValues = {}) { + const store = new Map(); + + const makeElement = (id) => { + const element = { + id, + value: initialValues[id] ?? "", + innerHTML: "", + }; + store.set(id, element); + return element; + }; + + for (const direction of DIRECTIONS) { + for (const suit of SUITS) { + makeElement(`${direction}_${suit}`); + } + } + makeElement("valid-pips"); + makeElement("result"); + + const rows = []; + for (let row = 0; row < 5; row++) { + const cells = []; + for (let column = 0; column < 6; column++) { + cells.push({ innerHTML: "" }); + } + rows.push({ cells }); + } + store.set("result-table", { rows }); + + return { + getElementById(id) { + return store.get(id) ?? null; + }, + element(id) { + return store.get(id); + }, + setValue(id, value) { + store.get(id).value = value; + }, + values() { + const out = {}; + for (const [id, element] of store) { + if (id.includes("_")) { + out[id] = element.value; + } + } + return out; + }, + }; +} + +function loadDdsMvp(document) { + const webRoot = findWebRoot(); + const code = readFileSync(join(webRoot, "dds_mvp.js"), "utf8"); + const sandbox = { + document, + console, + Promise, + Error, + }; + const context = createContext(sandbox); + runInContext(code, context, { filename: "dds_mvp.js" }); + return context; +} + +test("handsToPbn formats part-score deal", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.fillFormWithPartScoreTestData(); + const pbn = ctx.handsToPbn(ctx.collectHands()); + assert.equal( + pbn, + "N:AQ85.AK976.5.J87 JT.QJ5432.Q9.KQ9 972..JT863.A6432 K643.T8.AK742.T5" + ); +}); + +test("inputIsValid rejects incomplete deal", () => { + const ctx = loadDdsMvp(createMockDocument()); + assert.equal( + ctx.inputIsValid({ N: ["SA"], E: [], S: [], W: [] }), + "Please enter 13 cards per hand." + ); +}); + +test("inputIsValid rejects invalid pip", () => { + const ctx = loadDdsMvp(createMockDocument()); + const hands = { + N: ["S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", "ST", "SJ", "SQ", "SK"], + E: ["HA", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "HT", "HJ", "HQ", "HK"], + S: ["DA", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DT", "DJ", "DQ", "DK"], + W: ["CA", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CT", "CJ", "CQ", "CK"], + }; + assert.match(ctx.inputIsValid(hands), /^Please use only these pips:/); +}); + +test("inputIsValid rejects duplicate cards", () => { + const ctx = loadDdsMvp(createMockDocument()); + const hands = { + N: ["SA", "SA", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", "ST", "SJ", "SQ"], + E: ["HA", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "HT", "HJ", "HQ", "HK"], + S: ["DA", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DT", "DJ", "DQ", "DK"], + W: ["CA", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CT", "CJ", "CQ", "CK"], + }; + const message = ctx.inputIsValid(hands); + assert.match(message, /^Duplicated card/); + assert.match(message, /♠A/); +}); + +test("inputIsValid accepts part-score deal", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.fillFormWithPartScoreTestData(); + assert.equal(ctx.inputIsValid(ctx.collectHands()), ""); +}); + +test("collectHands reads suit holdings from inputs", () => { + const document = createMockDocument({ + north_spades: "AKQ", + north_hearts: "JT", + north_diamonds: "987", + north_clubs: "65432", + east_spades: "", + east_hearts: "AKQ", + east_diamonds: "", + east_clubs: "JT98765432", + south_spades: "JT98765432", + south_hearts: "", + south_diamonds: "AKQ", + south_clubs: "", + west_spades: "", + west_hearts: "98765432", + west_diamonds: "JT", + west_clubs: "AKQ", + }); + const ctx = loadDdsMvp(document); + const hands = ctx.collectHands(); + assert.equal(hands.N.length, 13); + assert.equal(hands.E.length, 13); + assert.equal(hands.S.length, 13); + assert.equal(hands.W.length, 13); + assert.ok(hands.N.includes("SA")); + assert.ok(hands.N.includes("SK")); + assert.ok(hands.E.includes("CJ")); +}); + +test("clearTestData clears all hand inputs", () => { + const document = createMockDocument({ north_spades: "AKQ", west_clubs: "JT" }); + const ctx = loadDdsMvp(document); + ctx.clearTestData(); + assert.equal(document.element("north_spades").value, ""); + assert.equal(document.element("west_clubs").value, ""); +}); + +test("rotateClockwise shifts holdings west to north", () => { + const document = createMockDocument(); + let index = 1; + for (const direction of DIRECTIONS) { + for (const suit of SUITS) { + document.setValue(`${direction}_${suit}`, String(index)); + index += 1; + } + } + const ctx = loadDdsMvp(document); + ctx.rotateClockwise(); + assert.equal(document.element("north_spades").value, "13"); + assert.equal(document.element("north_hearts").value, "14"); + assert.equal(document.element("north_diamonds").value, "15"); + assert.equal(document.element("north_clubs").value, "16"); + assert.equal(document.element("east_spades").value, "1"); +}); + +test("fillFormWithPartScoreTestData populates inputs", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.fillFormWithPartScoreTestData(); + assert.equal(document.element("north_spades").value, "AQ85"); + assert.equal(document.element("west_clubs").value, "T5"); +}); + +test("pageLoad shows valid pips", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.pageLoad(); + assert.equal(document.element("valid-pips").innerHTML, "AKQJT98765432"); +}); + +test("loadDdsModule rejects missing wasm globals", async () => { + const ctx = loadDdsMvp(createMockDocument()); + await assert.rejects( + () => ctx.loadDdsModule(), + /WASM module not found/ + ); +}); diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py new file mode 100644 index 00000000..05c224e9 --- /dev/null +++ b/web/tests/test_dds_mvp_js.py @@ -0,0 +1,53 @@ +"""Unit tests for web/dds_mvp.js via Node's built-in test runner. + +Run with:bazel test //web:dds_mvp_js_test +""" + +from __future__ import annotations + +import os +import shutil +import subprocess +import unittest +from pathlib import Path + +TESTS_ROOT = Path(__file__).resolve().parent + + +def _runfiles_root() -> Path: + for key in ("RUNFILES_DIR", "TEST_SRCDIR"): + if key in os.environ: + return Path(os.environ[key]) + return TESTS_ROOT.parent.parent + + +def rlocation(relpath: str) -> Path: + root = _runfiles_root() + for candidate in (root / relpath, root / "_main" / relpath): + if candidate.exists(): + return candidate + raise FileNotFoundError(relpath) + + +@unittest.skipUnless(shutil.which("node"), "node not found") +class DdsMvpJsTest(unittest.TestCase): + def test_dds_mvp_js(self) -> None: + node = shutil.which("node") + assert node is not None + + test_script = rlocation("web/tests/dds_mvp_test.mjs") + proc = subprocess.run( + [node, "--test", str(test_script)], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual( + proc.returncode, + 0, + msg=proc.stdout + proc.stderr, + ) + + +if __name__ == "__main__": + unittest.main() From da07b90eb65324e309cead4886ecf9d69f35c6fb Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 7 Jun 2026 09:36:47 -0400 Subject: [PATCH 014/157] Trigger CI workflow From aabf60c88915b58746f7502fd3411470fc6cbc67 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 7 Jun 2026 09:52:08 -0400 Subject: [PATCH 015/157] Trigger CI workflow - 2nd try From d3273565efff91bb257c1177a6429dceffdfcbb7 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 06:39:38 -0400 Subject: [PATCH 016/157] Updated comment to fix typo and add alternative test runners. --- web/tests/test_dds_mvp_js.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py index 05c224e9..32beed3c 100644 --- a/web/tests/test_dds_mvp_js.py +++ b/web/tests/test_dds_mvp_js.py @@ -1,6 +1,8 @@ """Unit tests for web/dds_mvp.js via Node's built-in test runner. -Run with:bazel test //web:dds_mvp_js_test +Run with: bazel test //web:dds_mvp_js_test +or: python -m unittest web.tests.test_dds_mvp_js +or: node --test web/tests/dds_mvp_test.mjs """ from __future__ import annotations From 2e3717425342ec01b44097e7cc7c4956d9a99424 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 11:36:27 +0100 Subject: [PATCH 017/157] Fix typos in comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- web/dds_mvp.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/dds_mvp.js b/web/dds_mvp.js index 1a7220ca..68813682 100644 --- a/web/dds_mvp.js +++ b/web/dds_mvp.js @@ -5,9 +5,9 @@ // https://opensource.org/licenses/MIT // Unit tests: web/tests/dds_mvp_test.mjs -// RUN WITH bazel test //web:dds_mvp_js_test) -// or python -m unittest web.tests.test_dds_mvp_js -// or node --test web/tests/dds_mvp_test.mjs +// Run with: bazel test //web:dds_mvp_js_test +// or: python -m unittest web.tests.test_dds_mvp_js +// or: node --test web/tests/dds_mvp_test.mjs // ESLint configuration // https://eslint.org/demo From 870b500966c681954c3ac36b77a3bb0d03604c9b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 21:47:42 -0400 Subject: [PATCH 018/157] Fix dds_mvp_js_test on Linux CI and with focusNorthSpades. Pass the resolved dds_mvp.js path via DDS_MVP_JS for Bazel runfiles, and add focus() to mock DOM elements so clearTestData and pageLoad work when merged with web changes. Co-authored-by: Cursor --- web/tests/dds_mvp_test.mjs | 18 +++++++++++------- web/tests/test_dds_mvp_js.py | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/web/tests/dds_mvp_test.mjs b/web/tests/dds_mvp_test.mjs index d19fba6c..03908415 100644 --- a/web/tests/dds_mvp_test.mjs +++ b/web/tests/dds_mvp_test.mjs @@ -16,10 +16,14 @@ import { createContext, runInContext } from "node:vm"; const DIRECTIONS = ["north", "east", "south", "west"]; const SUITS = ["spades", "hearts", "diamonds", "clubs"]; -function findWebRoot() { +function findDdsMvpJsPath() { + if (process.env.DDS_MVP_JS && existsSync(process.env.DDS_MVP_JS)) { + return process.env.DDS_MVP_JS; + } + const here = dirname(fileURLToPath(import.meta.url)); - const adjacent = join(here, ".."); - if (existsSync(join(adjacent, "dds_mvp.js"))) { + const adjacent = join(here, "..", "dds_mvp.js"); + if (existsSync(adjacent)) { return adjacent; } @@ -27,9 +31,9 @@ function findWebRoot() { if (!base) { continue; } - for (const sub of ["web", "_main/web"]) { + for (const sub of ["web/dds_mvp.js", "_main/web/dds_mvp.js"]) { const candidate = join(base, sub); - if (existsSync(join(candidate, "dds_mvp.js"))) { + if (existsSync(candidate)) { return candidate; } } @@ -46,6 +50,7 @@ function createMockDocument(initialValues = {}) { id, value: initialValues[id] ?? "", innerHTML: "", + focus() {}, }; store.set(id, element); return element; @@ -92,8 +97,7 @@ function createMockDocument(initialValues = {}) { } function loadDdsMvp(document) { - const webRoot = findWebRoot(); - const code = readFileSync(join(webRoot, "dds_mvp.js"), "utf8"); + const code = readFileSync(findDdsMvpJsPath(), "utf8"); const sandbox = { document, console, diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py index 32beed3c..ac2df6ae 100644 --- a/web/tests/test_dds_mvp_js.py +++ b/web/tests/test_dds_mvp_js.py @@ -38,11 +38,15 @@ def test_dds_mvp_js(self) -> None: assert node is not None test_script = rlocation("web/tests/dds_mvp_test.mjs") + dds_mvp_js = rlocation("web/dds_mvp.js") + env = os.environ.copy() + env["DDS_MVP_JS"] = str(dds_mvp_js) proc = subprocess.run( [node, "--test", str(test_script)], capture_output=True, text=True, check=False, + env=env, ) self.assertEqual( proc.returncode, From 06c470f495bb39cb2e82c6d1a3a6bd0454b470ab Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Tue, 9 Jun 2026 12:18:13 +0100 Subject: [PATCH 019/157] Applied Copilot's suggestions Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- web/tests/dds_mvp_test.mjs | 18 ++++++++++++++++++ web/tests/test_dds_mvp_js.py | 19 ++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/web/tests/dds_mvp_test.mjs b/web/tests/dds_mvp_test.mjs index 03908415..e80a5b24 100644 --- a/web/tests/dds_mvp_test.mjs +++ b/web/tests/dds_mvp_test.mjs @@ -237,3 +237,21 @@ test("loadDdsModule rejects missing wasm globals", async () => { /WASM module not found/ ); }); + +test("fillFormWithGrandSlamTestData populates inputs", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.fillFormWithGrandSlamTestData(); + assert.equal(document.element("north_spades").value, "AKQJ"); + assert.equal(document.element("east_clubs").value, "432"); + assert.equal(ctx.inputIsValid(ctx.collectHands()), ""); +}); + +test("fillFormWithEveryoneMakes3nTestData populates inputs", () => { + const document = createMockDocument(); + const ctx = loadDdsMvp(document); + ctx.fillFormWithEveryoneMakes3nTestData(); + assert.equal(document.element("north_hearts").value, "A8765432"); + assert.equal(document.element("west_spades").value, ""); + assert.equal(ctx.inputIsValid(ctx.collectHands()), ""); +}); diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py index ac2df6ae..f71e96cb 100644 --- a/web/tests/test_dds_mvp_js.py +++ b/web/tests/test_dds_mvp_js.py @@ -2,7 +2,7 @@ Run with: bazel test //web:dds_mvp_js_test or: python -m unittest web.tests.test_dds_mvp_js -or: node --test web/tests/dds_mvp_test.mjs +or: node --test web/tests/dds_mvp_test.mjs """ from __future__ import annotations @@ -31,11 +31,24 @@ def rlocation(relpath: str) -> Path: raise FileNotFoundError(relpath) -@unittest.skipUnless(shutil.which("node"), "node not found") class DdsMvpJsTest(unittest.TestCase): def test_dds_mvp_js(self) -> None: node = shutil.which("node") - assert node is not None + if not node: + raise unittest.SkipTest("node not found") + + version = subprocess.run( + [node, "--version"], + capture_output=True, + text=True, + check=False, + ).stdout.strip() + try: + major = int(version.lstrip("v").split(".", 1)[0]) + except ValueError: + raise unittest.SkipTest(f"could not parse node version: {version!r}") + if major < 18: + raise unittest.SkipTest(f"node >= 18 required for `node --test` (found {version})") test_script = rlocation("web/tests/dds_mvp_test.mjs") dds_mvp_js = rlocation("web/dds_mvp.js") From e5a80a1f529256b1618778b03202d21b00452f2a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 22:18:17 -0400 Subject: [PATCH 020/157] Upload Bazel test logs as CI artifacts on all platforms. Add upload-artifact@v6 steps to Linux, macOS, Windows, and WASM workflows using bazel-testlogs/ with if-no-files-found: ignore. Co-authored-by: Cursor --- .github/workflows/ci_linux.yml | 10 ++++++++++ .github/workflows/ci_macos.yml | 10 ++++++++++ .github/workflows/ci_wasm.yml | 12 +++++++++++- .github/workflows/ci_windows.yml | 10 ++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 471e2aeb..11dae6cd 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -31,3 +31,13 @@ jobs: # 6️⃣ Run all tests (including Python) - name: Run all tests run: bazel test --verbose_failures //... + + # 7️⃣ Upload test logs + - name: Upload test logs - Linux + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-linux + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index 7211123c..90a6d117 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -30,3 +30,13 @@ jobs: # Run all tests (including Python) - name: Run all tests run: bazelisk test --verbose_failures //... + + # Upload test logs + - name: Upload test logs - macOS + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-macos + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/ci_wasm.yml b/.github/workflows/ci_wasm.yml index 397285c8..8e5bf543 100644 --- a/.github/workflows/ci_wasm.yml +++ b/.github/workflows/ci_wasm.yml @@ -29,4 +29,14 @@ jobs: # 4️⃣ Smoke test: run solve_board under Node.js — pass if it does not crash - name: Smoke test solve_board_wasm - run: node bazel-bin/examples/wasm/solve_board.js \ No newline at end of file + run: node bazel-bin/examples/wasm/solve_board.js + + # 5️⃣ Upload test logs + - name: Upload test logs - WASM + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-wasm + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 9c309941..d228b98d 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -26,3 +26,13 @@ jobs: # Run all tests (including Python) - name: Run all tests run: bazel test --verbose_failures //... + + # Upload test logs + - name: Upload test logs - Windows + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-windows + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 \ No newline at end of file From 68397169bf9e930716f26269d8f49cfcb8dfaad9 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Tue, 26 May 2026 09:52:40 +0200 Subject: [PATCH 021/157] Initalt commit --- .gitignore | 1 + dotNet/AssemblyInfo.cpp | 20 +++++++ dotNet/DDS_CS.cpp | 4 ++ dotNet/DDS_CS.h | 10 ++++ dotNet/DDS_CS.vcxproj | 101 +++++++++++++++++++++++++++++++ dotNet/DDS_CS.vcxproj.filters | 52 ++++++++++++++++ dotNet/Directory.Build.props | 108 ++++++++++++++++++++++++++++++++++ dotNet/Resource.h | 3 + dotNet/app.aps | Bin 0 -> 43044 bytes dotNet/app.ico | Bin 0 -> 41395 bytes dotNet/app.rc | Bin 0 -> 2558 bytes dotNet/pch.cpp | 5 ++ dotNet/pch.h | 12 ++++ 13 files changed, 316 insertions(+) create mode 100644 dotNet/AssemblyInfo.cpp create mode 100644 dotNet/DDS_CS.cpp create mode 100644 dotNet/DDS_CS.h create mode 100644 dotNet/DDS_CS.vcxproj create mode 100644 dotNet/DDS_CS.vcxproj.filters create mode 100644 dotNet/Directory.Build.props create mode 100644 dotNet/Resource.h create mode 100644 dotNet/app.aps create mode 100644 dotNet/app.ico create mode 100644 dotNet/app.rc create mode 100644 dotNet/pch.cpp create mode 100644 dotNet/pch.h diff --git a/.gitignore b/.gitignore index 128d4cba..9835121c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.vcxproj.user Build/bin Build/int +obj # Prerequisites *.d diff --git a/dotNet/AssemblyInfo.cpp b/dotNet/AssemblyInfo.cpp new file mode 100644 index 00000000..75fe6e01 --- /dev/null +++ b/dotNet/AssemblyInfo.cpp @@ -0,0 +1,20 @@ +#include "pch.h" + +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +[assembly:AssemblyTitleAttribute(L"DDSCS")]; +[assembly:AssemblyDescriptionAttribute(L"")]; +[assembly:AssemblyConfigurationAttribute(L"")]; +[assembly:AssemblyCompanyAttribute(L"")]; +[assembly:AssemblyProductAttribute(L"DDSCS")]; +[assembly:AssemblyCopyrightAttribute(L"Copyright (c) 2026")]; +[assembly:AssemblyTrademarkAttribute(L"")]; +[assembly:AssemblyCultureAttribute(L"")]; + +[assembly:AssemblyVersionAttribute(L"1.0.*")]; + +[assembly:ComVisible(false)]; diff --git a/dotNet/DDS_CS.cpp b/dotNet/DDS_CS.cpp new file mode 100644 index 00000000..7615ce33 --- /dev/null +++ b/dotNet/DDS_CS.cpp @@ -0,0 +1,4 @@ +#include "pch.h" + +#include "DDS_CS.h" + diff --git a/dotNet/DDS_CS.h b/dotNet/DDS_CS.h new file mode 100644 index 00000000..22d51f14 --- /dev/null +++ b/dotNet/DDS_CS.h @@ -0,0 +1,10 @@ +#pragma once + +using namespace System; + +namespace DDSCS { + public ref class Class1 + { + // TODO: Add your methods for this class here. + }; +} diff --git a/dotNet/DDS_CS.vcxproj b/dotNet/DDS_CS.vcxproj new file mode 100644 index 00000000..36756cdc --- /dev/null +++ b/dotNet/DDS_CS.vcxproj @@ -0,0 +1,101 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + true + {A8C123BC-E0A4-1E10-60F5-A740E4FCCA67} + NetCoreCProj + DDSCS + 10.0 + net10.0 + 7.0 + + + + DynamicLibrary + true + v145 + NetCore + Unicode + + + DynamicLibrary + false + v145 + NetCore + Unicode + + + + + + + + + + + + + + + $(ProjectDir)..\Build\bin\$(platform)\$(Configuration)\ + $(ProjectDir)..\Build\bin\$(platform)\$(Configuration)\$(ProjectName)\ + + + + Use + pch.h + Level3 + _DEBUG;%(PreprocessorDefinitions) + + + dds.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Use + pch.h + Level3 + NDEBUG;%(PreprocessorDefinitions) + + + dds.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + \ No newline at end of file diff --git a/dotNet/DDS_CS.vcxproj.filters b/dotNet/DDS_CS.vcxproj.filters new file mode 100644 index 00000000..7346cba7 --- /dev/null +++ b/dotNet/DDS_CS.vcxproj.filters @@ -0,0 +1,52 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/dotNet/Directory.Build.props b/dotNet/Directory.Build.props new file mode 100644 index 00000000..d6ad3d22 --- /dev/null +++ b/dotNet/Directory.Build.props @@ -0,0 +1,108 @@ + + + + + 10.0 + + $(MSBuildThisFileDirectory)\..\Build\ + + + + + + v145 + + + + + + Debug + x64 + + + Release + x64 + + + MinSizeRel + x64 + + + RelWithDebInfo + x64 + + + + + + true + + + true + + + true + + + true + + + + + + true + + + Level3 + + + true + + + + + + + true + + + Level3 + + + true + true + true + + + + + + + true + + + Level3 + + + true + true + true + + + + + + + + true + + + Level3 + + + true + true + true + + + \ No newline at end of file diff --git a/dotNet/Resource.h b/dotNet/Resource.h new file mode 100644 index 00000000..d5ac7c42 --- /dev/null +++ b/dotNet/Resource.h @@ -0,0 +1,3 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by app.rc diff --git a/dotNet/app.aps b/dotNet/app.aps new file mode 100644 index 0000000000000000000000000000000000000000..7b9913c23aa28b9aec99c1cf4cd05b58aed32e5d GIT binary patch literal 43044 zcmeHQeQc9O7=PMzg#w#1L?uYz1*hzq--I&A@_u1T=*(y0Q(B)^5`+6P<)8 zMih*a%s=>uLb4d$AjT*r3quW{h7Y5OBpM|_MV)Nma3BuqbA5O1_3irhy_LDX+jHsl zy^rVap8MVJ?tOZ__5}b?&g|Hm*@fr+Tdh+eIo&dbLAHbFCb_IO? zHNG16!rDav-#V|Ss?wbws3yTBV{F$$UX=6Np zrg!@)PqwsQvO|eAHD1S_XghRes%KkE`&*Um=?`{1^sw)i*2B#mEql**oZr*ZUONYx zAnP7_^(O*PzxUzpx}RnqJ=3;(-uYFhJKH-uy8y1VjK%3We6-$SE|p3NNf*s*L1`D*1Tt~t$Y1joT5-vQ0!{TU%eTFjhW(lU}d== zt_M>vo`ZfhdRztY9i2nHgx=NVAr#Ed&xcDL7on@G3$9+hDhN@K5Fi8wD*{1g#F*UK zBwbqnJBwkb>S9>j(r0o*{7C3$_62p_^nL|`!S0sCUah1U}!!YD)X`aty! zAGK6Jy-VJH!}}GfVs@Yn6f%S2T~kF*v+EHL-$wjss$l1WLAI}jn_TF$pJNqFbD|G_ zjulxH=W{HIlOqHOfgylEXaVo%ekOnW)VsB_+7suC)}$C#4JV65OcTSDui`6WB8F21 zpZE+g#*cw5&vfj&OLqFI_64^xlC8~ndERb0eut)DG1ZPy4HT^%lZE+ACTzzr+l4!_ zV>l9YB?N{f0=?NWF)M2HB9CT6)SYYq;!z4|&6wD@rbF6+PUb5KR<w@hEc~3ElUVsjXL|Y87wcbH-&y_jqfN@KuHR0$|GM+|8=DF@YI_XOy0d-# z``cSyXefj6b!juE|Ld?hV2i=mt{W3t$9SfnZ9XyYO72mIv9G5Mev6%q7OWEeq7?Y%%W;fpyjh3_gS7+-pqZc z^`Gv4T0Rs1>He?B0WDu>{iFN8nK_{4vmW>9{!jLgmM>=JKG}a{|7rP5>mS*FdK}R5 zh1Nf^|IEw*EuZzcPxc?#e_Fnnnfqk_C2#*}`C#_A?D}W+{%ZB7{>$0FTKvo&r}3xp z*W-ZM`JG6Ay}q-&MCHw%PyMI)ug3wi^E;9LdVQz)uRk{uVMqCdA928R92nBS|DxXk z5(0$4&_#g%A5)_1&_&4HJx?^QjdzRxBQY%0S?&LDMV^bHg!1dL3^_u85QrdvcK>0M zd;0x%iu?yaqCK2c$&dG0H&ygYq{mRuKh@4Jh+|>|f71Wvo9}wecAmDS$VD}wI6L8a$IEbl$Gc05dMU}$N|m^Sw6|i^LE6xS(vBG)+Z~^+oAS} z{~`xCFJ$>7E6>{z+ZOrH^ZWZs?f1_A{>GixPxwdUuf=1i#8c|S-~cG2?l16NhW}3i z-fp=~+{bW)6wvNtux$$kJ>SQ0Vws!mZU*1^+9G@9ZL@NHxt~#abuAkV_{nmI$&2GZ zkmb1^eYqw(pV=OCUmYvE1q&Pv4FZdSztI^NMox}4*pP<#TqjK zte{}=Pz*O?({L|Vjg_YZsFkqQs{zZUgVV|K6kyMExA)Yd0Q+U`u%22lhUJI%)S>`e zB^@vdwVI87DHOm8d@q7xm=6nJ8MrWo>E5F^MDz}LD}B`gk9S%*!0idkTTucaM<{<+ z#YWGD>Qb*Vsbp;pRyM7CQpO0BVJ+$Ff?Y)?*2UtJ(PyJCgyo^f(+7jcSu)DmZEEWP z6GJHzLC_tW~X7P)V^aexI@1T#yTHL@EG2@WUE>ufh02sD(wLv~}PG z4^%-VrsiWx1(ZS=rm-?ESOT*!REnjl5OpuUyHUoA(mtrhkO#xl06`8j$YCqNcKn!H zfw?}+SrVh~mofUzm<=;RiP>1u(t;IBi;CwjSmr8-lRC3k*;(OKRts!EV54#3;~c@9 zjin2HWL($?GiSG{R|v7b0VC>n4r;!5uV(Y05vA39wT-BJoEiSnJYVI9n9t_QedBn( z+N_w*W=|{9K&>Bz(TVwNKGonXQuFOG`D})@mht@9-@dasxAj$?kIHK5XR~n8Zk``& zt878}GjFr}-q#Q|e|NkiH-FijJ*``Yk7Z=evymCMPp=&ndvR{EdFjG3k@4sAGEy&_ XZ#$x7_RZTH5(3+Y^qR}Q*QIUg61#Btj%&kM`7H)6nj+0&P zne4DLH}A~6^UO0d@9xgBL=JKjcMi&?At%w>EL>Qq#oKdt>C$N+(Rua|At$Lyk0H7#>diEi}F0wek;+HU7|b|XXU)xB+Bzp4Xf+HR-)G) zs@#~fX!#;mY)(aa>1JM9q{b|Es@mJmiXcbB=8Zn;=)3uM7IEz^*;GQ*b!7464yF^i z>&qW&Ajxqo~ z`o$M|S6|Ksp*!JynDLQBBox`-#bTKXNNLW6@ zgkx${f)e9J7HU zW>BoNwY0UbHnH&K*Qp-nz06Nvr^+meah=M<;eY@TNCN~S6LgB(nf&r-v~=^*=c0n&Ojh75tz+RnK5HVPvXohh+3)UAs(fW-b|Sp*ELAr(aAhg;CWNC zNg-csQaH8gOTI~A>mQ?;HLt$xf4z2e-!~6*cy1{Dt+(p06<@t|uzZicMMS~oQ0<3D z0x!1grj?D(wFOt)E;k)Ed9+Tg3NEWIIN#a3e!Td!+uXBZlaG*-5o1m2`SF#ewPVG* z`nqqrRCRT#YNfyP4BvY&bah{6bzfHSM$0IhOhc`lceYNT^Phg?sL%8lI?Ns?&V`*c zRdBocy$a9gPIeW|Bs$T^;GR!b6_sshE;@YY_gp~UPvB*7Kl6N3QpB;1NN_*^2mk>f zpdrBaQ2Eo`hgtjKfdM$cIKcK+E@&TbtfT$t|Aou}RsOSm)?qt)eP5#e#_0u9G5^8-Gva`rFPQ&e|Jj)XdOjO*ANC*YKRsXU%zfB@ z$=iQ=KG;33I{(?dy?Xu8el`17kDuM+=zsLT5eMwf?|AwfwVkKM%WwB|v>)T&hy!-# zcRc-#+K%yWJT~HChjicv4%m(ZY5o2e-U9>zKp=e)5dSYJ-Z^~{vUkhl^=tHZCp?kH z`q>9+;D7)SxP}0;>rdL;uu&S+@th^rgWXn^{AFF=V`xmP6 zLQm8mZTksaRPj`m7xe&t;4gDP@IsYOs`8>9xo#d7zpIv~Dlh6m{oudM0l^DZKB>x! zdgQug{)_ZfpHctp{7*IRqI}>VjlUj`bctK)VsIXmk>B_FKdTq@^7|D0wuFay@!ess zi$&(x%>|#u+@X3Fb@P0QtXIgd%~cBlKUMBwFX)@Ts-VrG-!fi7 zn=|^{lG%iLU}b4D-N4h)b3NmhZXDZC3wCq3uOe@&a=xRfT9Ml)+mSDe=PY|qCmQKg zXXO<`@Kh{u}P)77e+J`b^ZYfCz>(RWgv~!7P?33(2w$i>2#H#>Fc|_Qh z^$Vg|uu6y;F)Q)4&`b6oBW$sbY4hW1i<6cC#7@YRVoj;;1hOW_dS{Ze%gCRTQO?P0 zWC0DOPI5U zsW~oLw6^XIsan`<>ynio16ff0_sBq)>sz3jP1&!{CS&XIJM!nhN%CW}eNvC;0#Epr I_*Z2A2KzE2Bme*a literal 0 HcmV?d00001 diff --git a/dotNet/pch.cpp b/dotNet/pch.cpp new file mode 100644 index 00000000..64b7eef6 --- /dev/null +++ b/dotNet/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/dotNet/pch.h b/dotNet/pch.h new file mode 100644 index 00000000..9d715b06 --- /dev/null +++ b/dotNet/pch.h @@ -0,0 +1,12 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here + +#endif //PCH_H From 0a0fb9d88f88af44675592334ebaa51e1a3e13c0 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Wed, 27 May 2026 11:02:30 +0200 Subject: [PATCH 022/157] First take on a cross platfor dds wrapper --- dotNet/DDS_CS.cpp | 4 - dotNet/DDS_CS.h | 10 -- dotNet/Directory.Build.props | 108 ------------------ dotNet/app.aps | Bin 43044 -> 0 bytes dotnet.old/DDS_COre_Demo/DotNetDemo.csproj | 15 +++ dotnet.old/DDS_COre_Demo/Program.cs | 19 +++ .../Properties/launchSettings.json | 8 ++ dotnet.old/DDS_Core.slnx | 9 ++ .../DDS_Core}/AssemblyInfo.cpp | 4 +- dotnet.old/DDS_Core/DDS_Core.cpp | 53 +++++++++ dotnet.old/DDS_Core/DDS_Core.h | 32 ++++++ .../DDS_Core/DDS_Core.vcxproj | 30 +++-- .../DDS_Core/DDS_Core.vcxproj.filters | 27 ++++- dotnet.old/DDS_Core/ManagedDeal.cpp | 51 +++++++++ dotnet.old/DDS_Core/ManagedDeal.h | 20 ++++ dotnet.old/DDS_Core/ManagedSolverConfig.cpp | 31 +++++ dotnet.old/DDS_Core/ManagedSolverConfig.h | 19 +++ dotnet.old/DDS_Core/ManagedSolverContext.cpp | 37 ++++++ dotnet.old/DDS_Core/ManagedSolverContext.h | 25 ++++ dotnet.old/DDS_Core/ManagedTypes.cpp | 31 +++++ dotnet.old/DDS_Core/ManagedTypes.h | 29 +++++ {dotNet => dotnet.old/DDS_Core}/Resource.h | 0 {dotNet => dotnet.old/DDS_Core}/app.ico | Bin {dotNet => dotnet.old/DDS_Core}/app.rc | Bin {dotNet => dotnet.old/DDS_Core}/pch.cpp | 0 {dotNet => dotnet.old/DDS_Core}/pch.h | 0 .../examples/DotNetDemo/DotNetDemo.csproj | 11 ++ dotnet/DDS_Core/DDS.cs | 85 ++++++++++++++ dotnet/DDS_Core/DDS_Core.csproj | 8 ++ dotnet/DDS_Core/DDS_Core.slnx | 9 ++ dotnet/DDS_Core/DataModel/Boards.cs | 22 ++++ dotnet/DDS_Core/DataModel/Deal.cs | 27 +++++ dotnet/DDS_Core/DataModel/FutureTricks.cs | 37 ++++++ dotnet/DDS_Core/DataModel/Hand.cs | 3 + dotnet/DDS_Core/DataModel/SolvedBoards.cs | 10 ++ dotnet/DDS_Core/DataModel/SolvingMode.cs | 11 ++ dotnet/DDS_Core/DataModel/Suit.cs | 5 + dotnet/DDS_Core/DataModel/ddTableDeal.cs | 17 +++ dotnet/DDS_Core/DataModel/ddTableResults.cs | 8 ++ dotnet/DDS_Core/DataModel/parResults.cs | 11 ++ dotnet/DDS_Core/Directory.Build.props | 8 ++ dotnet/DDS_Core/Native/DdsNative.cs | 62 ++++++++++ dotnet/DDS_Core/Native/NativeLoader.cs | 72 ++++++++++++ solution/Solution.slnx | 1 - 44 files changed, 831 insertions(+), 138 deletions(-) delete mode 100644 dotNet/DDS_CS.cpp delete mode 100644 dotNet/DDS_CS.h delete mode 100644 dotNet/Directory.Build.props delete mode 100644 dotNet/app.aps create mode 100644 dotnet.old/DDS_COre_Demo/DotNetDemo.csproj create mode 100644 dotnet.old/DDS_COre_Demo/Program.cs create mode 100644 dotnet.old/DDS_COre_Demo/Properties/launchSettings.json create mode 100644 dotnet.old/DDS_Core.slnx rename {dotNet => dotnet.old/DDS_Core}/AssemblyInfo.cpp (86%) create mode 100644 dotnet.old/DDS_Core/DDS_Core.cpp create mode 100644 dotnet.old/DDS_Core/DDS_Core.h rename dotNet/DDS_CS.vcxproj => dotnet.old/DDS_Core/DDS_Core.vcxproj (78%) rename dotNet/DDS_CS.vcxproj.filters => dotnet.old/DDS_Core/DDS_Core.vcxproj.filters (67%) create mode 100644 dotnet.old/DDS_Core/ManagedDeal.cpp create mode 100644 dotnet.old/DDS_Core/ManagedDeal.h create mode 100644 dotnet.old/DDS_Core/ManagedSolverConfig.cpp create mode 100644 dotnet.old/DDS_Core/ManagedSolverConfig.h create mode 100644 dotnet.old/DDS_Core/ManagedSolverContext.cpp create mode 100644 dotnet.old/DDS_Core/ManagedSolverContext.h create mode 100644 dotnet.old/DDS_Core/ManagedTypes.cpp create mode 100644 dotnet.old/DDS_Core/ManagedTypes.h rename {dotNet => dotnet.old/DDS_Core}/Resource.h (100%) rename {dotNet => dotnet.old/DDS_Core}/app.ico (100%) rename {dotNet => dotnet.old/DDS_Core}/app.rc (100%) rename {dotNet => dotnet.old/DDS_Core}/pch.cpp (100%) rename {dotNet => dotnet.old/DDS_Core}/pch.h (100%) create mode 100644 dotnet.old/examples/DotNetDemo/DotNetDemo.csproj create mode 100644 dotnet/DDS_Core/DDS.cs create mode 100644 dotnet/DDS_Core/DDS_Core.csproj create mode 100644 dotnet/DDS_Core/DDS_Core.slnx create mode 100644 dotnet/DDS_Core/DataModel/Boards.cs create mode 100644 dotnet/DDS_Core/DataModel/Deal.cs create mode 100644 dotnet/DDS_Core/DataModel/FutureTricks.cs create mode 100644 dotnet/DDS_Core/DataModel/Hand.cs create mode 100644 dotnet/DDS_Core/DataModel/SolvedBoards.cs create mode 100644 dotnet/DDS_Core/DataModel/SolvingMode.cs create mode 100644 dotnet/DDS_Core/DataModel/Suit.cs create mode 100644 dotnet/DDS_Core/DataModel/ddTableDeal.cs create mode 100644 dotnet/DDS_Core/DataModel/ddTableResults.cs create mode 100644 dotnet/DDS_Core/DataModel/parResults.cs create mode 100644 dotnet/DDS_Core/Directory.Build.props create mode 100644 dotnet/DDS_Core/Native/DdsNative.cs create mode 100644 dotnet/DDS_Core/Native/NativeLoader.cs diff --git a/dotNet/DDS_CS.cpp b/dotNet/DDS_CS.cpp deleted file mode 100644 index 7615ce33..00000000 --- a/dotNet/DDS_CS.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include "pch.h" - -#include "DDS_CS.h" - diff --git a/dotNet/DDS_CS.h b/dotNet/DDS_CS.h deleted file mode 100644 index 22d51f14..00000000 --- a/dotNet/DDS_CS.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -using namespace System; - -namespace DDSCS { - public ref class Class1 - { - // TODO: Add your methods for this class here. - }; -} diff --git a/dotNet/Directory.Build.props b/dotNet/Directory.Build.props deleted file mode 100644 index d6ad3d22..00000000 --- a/dotNet/Directory.Build.props +++ /dev/null @@ -1,108 +0,0 @@ - - - - - 10.0 - - $(MSBuildThisFileDirectory)\..\Build\ - - - - - - v145 - - - - - - Debug - x64 - - - Release - x64 - - - MinSizeRel - x64 - - - RelWithDebInfo - x64 - - - - - - true - - - true - - - true - - - true - - - - - - true - - - Level3 - - - true - - - - - - - true - - - Level3 - - - true - true - true - - - - - - - true - - - Level3 - - - true - true - true - - - - - - - - true - - - Level3 - - - true - true - true - - - \ No newline at end of file diff --git a/dotNet/app.aps b/dotNet/app.aps deleted file mode 100644 index 7b9913c23aa28b9aec99c1cf4cd05b58aed32e5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43044 zcmeHQeQc9O7=PMzg#w#1L?uYz1*hzq--I&A@_u1T=*(y0Q(B)^5`+6P<)8 zMih*a%s=>uLb4d$AjT*r3quW{h7Y5OBpM|_MV)Nma3BuqbA5O1_3irhy_LDX+jHsl zy^rVap8MVJ?tOZ__5}b?&g|Hm*@fr+Tdh+eIo&dbLAHbFCb_IO? zHNG16!rDav-#V|Ss?wbws3yTBV{F$$UX=6Np zrg!@)PqwsQvO|eAHD1S_XghRes%KkE`&*Um=?`{1^sw)i*2B#mEql**oZr*ZUONYx zAnP7_^(O*PzxUzpx}RnqJ=3;(-uYFhJKH-uy8y1VjK%3We6-$SE|p3NNf*s*L1`D*1Tt~t$Y1joT5-vQ0!{TU%eTFjhW(lU}d== zt_M>vo`ZfhdRztY9i2nHgx=NVAr#Ed&xcDL7on@G3$9+hDhN@K5Fi8wD*{1g#F*UK zBwbqnJBwkb>S9>j(r0o*{7C3$_62p_^nL|`!S0sCUah1U}!!YD)X`aty! zAGK6Jy-VJH!}}GfVs@Yn6f%S2T~kF*v+EHL-$wjss$l1WLAI}jn_TF$pJNqFbD|G_ zjulxH=W{HIlOqHOfgylEXaVo%ekOnW)VsB_+7suC)}$C#4JV65OcTSDui`6WB8F21 zpZE+g#*cw5&vfj&OLqFI_64^xlC8~ndERb0eut)DG1ZPy4HT^%lZE+ACTzzr+l4!_ zV>l9YB?N{f0=?NWF)M2HB9CT6)SYYq;!z4|&6wD@rbF6+PUb5KR<w@hEc~3ElUVsjXL|Y87wcbH-&y_jqfN@KuHR0$|GM+|8=DF@YI_XOy0d-# z``cSyXefj6b!juE|Ld?hV2i=mt{W3t$9SfnZ9XyYO72mIv9G5Mev6%q7OWEeq7?Y%%W;fpyjh3_gS7+-pqZc z^`Gv4T0Rs1>He?B0WDu>{iFN8nK_{4vmW>9{!jLgmM>=JKG}a{|7rP5>mS*FdK}R5 zh1Nf^|IEw*EuZzcPxc?#e_Fnnnfqk_C2#*}`C#_A?D}W+{%ZB7{>$0FTKvo&r}3xp z*W-ZM`JG6Ay}q-&MCHw%PyMI)ug3wi^E;9LdVQz)uRk{uVMqCdA928R92nBS|DxXk z5(0$4&_#g%A5)_1&_&4HJx?^QjdzRxBQY%0S?&LDMV^bHg!1dL3^_u85QrdvcK>0M zd;0x%iu?yaqCK2c$&dG0H&ygYq{mRuKh@4Jh+|>|f71Wvo9}wecAmDS$VD}wI6L8a$IEbl$Gc05dMU}$N|m^Sw6|i^LE6xS(vBG)+Z~^+oAS} z{~`xCFJ$>7E6>{z+ZOrH^ZWZs?f1_A{>GixPxwdUuf=1i#8c|S-~cG2?l16NhW}3i z-fp=~+{bW)6wvNtux$$kJ>SQ0Vws!mZU*1^+9G@9ZL@NHxt~#abuAkV_{nmI$&2GZ zkmb1^eYqw(pV=OCUmYvE1q&Pv4FZdSztI^NMox}4*pP<#TqjK zte{}=Pz*O?({L|Vjg_YZsFkqQs{zZUgVV|K6kyMExA)Yd0Q+U`u%22lhUJI%)S>`e zB^@vdwVI87DHOm8d@q7xm=6nJ8MrWo>E5F^MDz}LD}B`gk9S%*!0idkTTucaM<{<+ z#YWGD>Qb*Vsbp;pRyM7CQpO0BVJ+$Ff?Y)?*2UtJ(PyJCgyo^f(+7jcSu)DmZEEWP z6GJHzLC_tW~X7P)V^aexI@1T#yTHL@EG2@WUE>ufh02sD(wLv~}PG z4^%-VrsiWx1(ZS=rm-?ESOT*!REnjl5OpuUyHUoA(mtrhkO#xl06`8j$YCqNcKn!H zfw?}+SrVh~mofUzm<=;RiP>1u(t;IBi;CwjSmr8-lRC3k*;(OKRts!EV54#3;~c@9 zjin2HWL($?GiSG{R|v7b0VC>n4r;!5uV(Y05vA39wT-BJoEiSnJYVI9n9t_QedBn( z+N_w*W=|{9K&>Bz(TVwNKGonXQuFOG`D})@mht@9-@dasxAj$?kIHK5XR~n8Zk``& zt878}GjFr}-q#Q|e|NkiH-FijJ*``Yk7Z=evymCMPp=&ndvR{EdFjG3k@4sAGEy&_ XZ#$x7_ + + + Exe + net8.0-windows10.0.22621 + enable + enable + x64 + + + + + + + diff --git a/dotnet.old/DDS_COre_Demo/Program.cs b/dotnet.old/DDS_COre_Demo/Program.cs new file mode 100644 index 00000000..d856b257 --- /dev/null +++ b/dotnet.old/DDS_COre_Demo/Program.cs @@ -0,0 +1,19 @@ +using DDSCore; + +namespace DotNetDemo; + +internal class Program +{ + static void Main(string[] args) + { + var dds = new DDSCore.dds(); + var deal = new DDSCore.ManagedDeal(); + var cfg = new DDSCore.ManagedSolverConfig(); + var ctx = new ManagedSolverContext(cfg); + var fut = new DDSCore.ManagedFutureTricks(); + + var rc = dds.SolveBoard(ctx, deal, 10, 1, 0, fut); + + Console.WriteLine("Hello, World!"); + } +} diff --git a/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json b/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json new file mode 100644 index 00000000..6c1a2314 --- /dev/null +++ b/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DotNetDemo": { + "commandName": "Project", + "debugEngines": "managed-dotnet,native" + } + } +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core.slnx b/dotnet.old/DDS_Core.slnx new file mode 100644 index 00000000..977311d2 --- /dev/null +++ b/dotnet.old/DDS_Core.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dotNet/AssemblyInfo.cpp b/dotnet.old/DDS_Core/AssemblyInfo.cpp similarity index 86% rename from dotNet/AssemblyInfo.cpp rename to dotnet.old/DDS_Core/AssemblyInfo.cpp index 75fe6e01..e1ef2875 100644 --- a/dotNet/AssemblyInfo.cpp +++ b/dotnet.old/DDS_Core/AssemblyInfo.cpp @@ -6,11 +6,11 @@ using namespace System::Runtime::CompilerServices; using namespace System::Runtime::InteropServices; using namespace System::Security::Permissions; -[assembly:AssemblyTitleAttribute(L"DDSCS")]; +[assembly:AssemblyTitleAttribute(L"DDSCore")]; [assembly:AssemblyDescriptionAttribute(L"")]; [assembly:AssemblyConfigurationAttribute(L"")]; [assembly:AssemblyCompanyAttribute(L"")]; -[assembly:AssemblyProductAttribute(L"DDSCS")]; +[assembly:AssemblyProductAttribute(L"DDSCore")]; [assembly:AssemblyCopyrightAttribute(L"Copyright (c) 2026")]; [assembly:AssemblyTrademarkAttribute(L"")]; [assembly:AssemblyCultureAttribute(L"")]; diff --git a/dotnet.old/DDS_Core/DDS_Core.cpp b/dotnet.old/DDS_Core/DDS_Core.cpp new file mode 100644 index 00000000..9cda6c7e --- /dev/null +++ b/dotnet.old/DDS_Core/DDS_Core.cpp @@ -0,0 +1,53 @@ +#include "pch.h" +#include "DDS_Core.h" + +namespace DDSCore { + + + dds::dds() + { + } + + dds::~dds() + { + } + + auto dds::SolveBoard(ManagedSolverContext^ ctx, + const ManagedDeal^ dl, + int target, + int solutions, + int mode, + ManagedFutureTricks^ futp) -> int + { + // 1. Set up native structures for the call + Deal nativeDeal = ManagedDeal::ToNative(dl); + FutureTricks nativeFt; + + // 2. Call native SolveBoard + int result = ::SolveBoard( + *ctx->native_, // SolverContext& + nativeDeal, // const Deal& + target, + solutions, + mode, + &nativeFt // FutureTricks* + ); + + // 3. Convert native → managed + ManagedFutureTricks^ mf = ManagedFutureTricks::FromNative(nativeFt); + + // 4. Copy back to output parameter + futp->Nodes = mf->Nodes; + futp->Cards = mf->Cards; + + for (int i = 0; i < mf->Cards; i++) + { + futp->Suit[i] = mf->Suit[i]; + futp->Rank[i] = mf->Rank[i]; + futp->Equals[i] = mf->Equals[i]; + futp->Score[i] = mf->Score[i]; + } + + return result; + } +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/DDS_Core.h b/dotnet.old/DDS_Core/DDS_Core.h new file mode 100644 index 00000000..fde8d608 --- /dev/null +++ b/dotnet.old/DDS_Core/DDS_Core.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include "ManagedTypes.h" +#include "ManagedSolverContext.h" +#include "ManagedDeal.h" + +using namespace System; + +namespace DDSCore { + public ref class dds + { + + public: + dds(); + ~dds(); + + auto SolveBoard( + ManagedSolverContext^ ctx, + const ManagedDeal^ dl, + int target, + int solutions, + int mode, + ManagedFutureTricks^ futp) -> int; + }; + + + + +} diff --git a/dotNet/DDS_CS.vcxproj b/dotnet.old/DDS_Core/DDS_Core.vcxproj similarity index 78% rename from dotNet/DDS_CS.vcxproj rename to dotnet.old/DDS_Core/DDS_Core.vcxproj index 36756cdc..5c5d4550 100644 --- a/dotNet/DDS_CS.vcxproj +++ b/dotnet.old/DDS_Core/DDS_Core.vcxproj @@ -13,18 +13,18 @@ 18.0 true - {A8C123BC-E0A4-1E10-60F5-A740E4FCCA67} + {CC2C338E-6224-0611-BE35-9DA0815F38DD} NetCoreCProj - DDSCS + DDSCore 10.0 - net10.0 + net8.0 7.0 DynamicLibrary true - v145 + v143 NetCore Unicode @@ -47,9 +47,9 @@ - - $(ProjectDir)..\Build\bin\$(platform)\$(Configuration)\ - $(ProjectDir)..\Build\bin\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\..\Build\bin\$(platform)\$(Configuration)\ + ..\..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ @@ -57,6 +57,8 @@ pch.h Level3 _DEBUG;%(PreprocessorDefinitions) + ../../library/src;%(AdditionalIncludeDirectories) + stdcpp20 dds.lib;%(AdditionalDependencies) @@ -69,6 +71,7 @@ pch.h Level3 NDEBUG;%(PreprocessorDefinitions) + ../../library/src;%(AdditionalIncludeDirectories) dds.lib;%(AdditionalDependencies) @@ -76,14 +79,21 @@ - + + + + + - - + + + + + Create Create diff --git a/dotNet/DDS_CS.vcxproj.filters b/dotnet.old/DDS_Core/DDS_Core.vcxproj.filters similarity index 67% rename from dotNet/DDS_CS.vcxproj.filters rename to dotnet.old/DDS_Core/DDS_Core.vcxproj.filters index 7346cba7..ebab145f 100644 --- a/dotNet/DDS_CS.vcxproj.filters +++ b/dotnet.old/DDS_Core/DDS_Core.vcxproj.filters @@ -15,7 +15,7 @@ - + Header Files @@ -24,12 +24,21 @@ Header Files - + + Header Files + + + Header Files + + + Header Files + + Header Files - + Source Files @@ -38,6 +47,18 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/dotnet.old/DDS_Core/ManagedDeal.cpp b/dotnet.old/DDS_Core/ManagedDeal.cpp new file mode 100644 index 00000000..d7e997c7 --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedDeal.cpp @@ -0,0 +1,51 @@ +#include "pch.h" +#include "ManagedDeal.h" + +namespace DDSCore { + ManagedDeal::ManagedDeal() + { + CurrentTrickSuit = gcnew array(3); + CurrentTrickRank = gcnew array(3); + RemainCards = gcnew array(DDS_HANDS, DDS_SUITS); + } + + ManagedDeal^ ManagedDeal::FromNative(const ::Deal& d) + { + ManagedDeal^ md = gcnew ManagedDeal(); + + md->Trump = d.trump; + md->First = d.first; + + for (int i = 0; i < 3; i++) + { + md->CurrentTrickSuit[i] = d.currentTrickSuit[i]; + md->CurrentTrickRank[i] = d.currentTrickRank[i]; + } + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + md->RemainCards[h, s] = d.remainCards[h][s]; + + return md; + } + + ::Deal ManagedDeal::ToNative(const ManagedDeal^ md) + { + ::Deal d; + + d.trump = md->Trump; + d.first = md->First; + + for (int i = 0; i < 3; i++) + { + d.currentTrickSuit[i] = md->CurrentTrickSuit[i]; + d.currentTrickRank[i] = md->CurrentTrickRank[i]; + } + + for (int h = 0; h < DDS_HANDS; h++) + for (int s = 0; s < DDS_SUITS; s++) + d.remainCards[h][s] = md->RemainCards[h, s]; + + return d; + } +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedDeal.h b/dotnet.old/DDS_Core/ManagedDeal.h new file mode 100644 index 00000000..894bd93b --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedDeal.h @@ -0,0 +1,20 @@ +#pragma once +#include + +namespace DDSCore { + public ref class ManagedDeal + { + public: + int Trump; + int First; + + array^ CurrentTrickSuit; + array^ CurrentTrickRank; + array^ RemainCards; + + ManagedDeal(); + internal: + static ManagedDeal^ FromNative(const ::Deal& d); + static ::Deal ToNative(const ManagedDeal^ md); + }; +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverConfig.cpp b/dotnet.old/DDS_Core/ManagedSolverConfig.cpp new file mode 100644 index 00000000..0925c7ae --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedSolverConfig.cpp @@ -0,0 +1,31 @@ +#pragma once +#include "pch.h" +#include "ManagedTypes.h" +#include "ManagedSolverConfig.h" + +namespace DDSCore { + inline ManagedSolverConfig::ManagedSolverConfig() + { + TTKind = ManagedTTKind::Large; + DefaultMemoryMB = 0; + MaximumMemoryMB = 0; + } + + inline ::SolverConfig ManagedSolverConfig::ToNative(ManagedSolverConfig^ cfg) + { + ::SolverConfig nc; + nc.tt_kind_ = static_cast<::TTKind>(cfg->TTKind); + nc.tt_mem_default_mb_ = cfg->DefaultMemoryMB; + nc.tt_mem_maximum_mb_ = cfg->MaximumMemoryMB; + return nc; + } + + inline ManagedSolverConfig^ ManagedSolverConfig::FromNative(const ::SolverConfig& cfg) + { + ManagedSolverConfig^ mc = gcnew ManagedSolverConfig(); + mc->TTKind = static_cast(cfg.tt_kind_); + mc->DefaultMemoryMB = cfg.tt_mem_default_mb_; + mc->MaximumMemoryMB = cfg.tt_mem_maximum_mb_; + return mc; + } +} diff --git a/dotnet.old/DDS_Core/ManagedSolverConfig.h b/dotnet.old/DDS_Core/ManagedSolverConfig.h new file mode 100644 index 00000000..902916f4 --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedSolverConfig.h @@ -0,0 +1,19 @@ +#pragma once +#include "ManagedTypes.h" + +namespace DDSCore { + public ref class ManagedSolverConfig + { + public: + ManagedTTKind TTKind; + int DefaultMemoryMB; + int MaximumMemoryMB; + + ManagedSolverConfig(); + + internal: + static ::SolverConfig ToNative(ManagedSolverConfig^ cfg); + + static ManagedSolverConfig^ FromNative(const ::SolverConfig& cfg); + }; +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverContext.cpp b/dotnet.old/DDS_Core/ManagedSolverContext.cpp new file mode 100644 index 00000000..3a6ddaae --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedSolverContext.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "ManagedSolverContext.h" + +namespace DDSCore { + inline DDSCore::ManagedSolverContext::ManagedSolverContext(ManagedSolverConfig^ cfg) + { + native_ = new SolverContext(ManagedSolverConfig::ToNative(cfg)); + } + + inline DDSCore::ManagedSolverContext::~ManagedSolverContext() + { + this->!ManagedSolverContext(); + } + + inline DDSCore::ManagedSolverContext::!ManagedSolverContext() + { + delete native_; + native_ = nullptr; + } + + inline array^ DDSCore::ManagedSolverContext::GetLog() + { + auto& buf = native_->utilities().log_buffer(); + int count = static_cast(buf.size()); + auto arr = gcnew array(count); + + for (int i = 0; i < count; i++) + arr[i] = gcnew System::String(buf[i].c_str()); + + return arr; + } + + inline void DDSCore::ManagedSolverContext::ClearLog() + { + native_->utilities().log_clear(); + } +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverContext.h b/dotnet.old/DDS_Core/ManagedSolverContext.h new file mode 100644 index 00000000..3515ca44 --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedSolverContext.h @@ -0,0 +1,25 @@ +#pragma once +//using namespace System; + +#include "ManagedSolverConfig.h" + +namespace DDSCore { + public ref class ManagedSolverContext + { + public: + ManagedSolverContext(ManagedSolverConfig^ cfg); + + ~ManagedSolverContext(); + + !ManagedSolverContext(); + + + array^ GetLog(); + + void ClearLog(); + + + internal: + SolverContext* native_; + }; +} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedTypes.cpp b/dotnet.old/DDS_Core/ManagedTypes.cpp new file mode 100644 index 00000000..64a0560b --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedTypes.cpp @@ -0,0 +1,31 @@ +#include "pch.h" +#include "ManagedTypes.h" + +namespace DDSCore { + inline ManagedFutureTricks::ManagedFutureTricks() + { + // DDS returnerer maks 13 entries + Suit = gcnew array(13); + Rank = gcnew array(13); + Equals = gcnew array(13); + Score = gcnew array(13); + } + + inline ManagedFutureTricks^ ManagedFutureTricks::FromNative(const ::FutureTricks& ft) + { + ManagedFutureTricks^ m = gcnew ManagedFutureTricks(); + + m->Nodes = ft.nodes; + m->Cards = ft.cards; + + for (int i = 0; i < ft.cards; i++) + { + m->Suit[i] = ft.suit[i]; + m->Rank[i] = ft.rank[i]; + m->Equals[i] = ft.equals[i]; + m->Score[i] = ft.score[i]; + } + + return m; + } +} diff --git a/dotnet.old/DDS_Core/ManagedTypes.h b/dotnet.old/DDS_Core/ManagedTypes.h new file mode 100644 index 00000000..fcdb268d --- /dev/null +++ b/dotnet.old/DDS_Core/ManagedTypes.h @@ -0,0 +1,29 @@ +#pragma once +#include + +namespace DDSCore { + public enum class ManagedTTKind + { + Small = (int)::TTKind::Small, + Large = (int)::TTKind::Large + }; + + public ref class ManagedFutureTricks + { + public: + int Nodes; + int Cards; + + array^ Suit; // length = Cards + array^ Rank; // length = Cards + array^ Equals; // length = Cards + array^ Score; // length = Cards + + ManagedFutureTricks(); + + internal: + static ManagedFutureTricks^ FromNative(const FutureTricks& ft); + + }; + +} \ No newline at end of file diff --git a/dotNet/Resource.h b/dotnet.old/DDS_Core/Resource.h similarity index 100% rename from dotNet/Resource.h rename to dotnet.old/DDS_Core/Resource.h diff --git a/dotNet/app.ico b/dotnet.old/DDS_Core/app.ico similarity index 100% rename from dotNet/app.ico rename to dotnet.old/DDS_Core/app.ico diff --git a/dotNet/app.rc b/dotnet.old/DDS_Core/app.rc similarity index 100% rename from dotNet/app.rc rename to dotnet.old/DDS_Core/app.rc diff --git a/dotNet/pch.cpp b/dotnet.old/DDS_Core/pch.cpp similarity index 100% rename from dotNet/pch.cpp rename to dotnet.old/DDS_Core/pch.cpp diff --git a/dotNet/pch.h b/dotnet.old/DDS_Core/pch.h similarity index 100% rename from dotNet/pch.h rename to dotnet.old/DDS_Core/pch.h diff --git a/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj b/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj new file mode 100644 index 00000000..57f19002 --- /dev/null +++ b/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj @@ -0,0 +1,11 @@ + + + + Exe + net8.0-windows10.0.22621 + enable + enable + x64 + + + diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs new file mode 100644 index 00000000..12228366 --- /dev/null +++ b/dotnet/DDS_Core/DDS.cs @@ -0,0 +1,85 @@ +using System.Runtime.InteropServices; +using DDS_Core.DataModel; +using DDS_Core.Native; + +namespace DDS_Core; + +public class DDS +{ + public int SolveBoard( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex = 0) + { + var rc = DdsNative.SolveBoard( in dl + , target + , solutions + , mode + , out fut + , threadIndex); + +#if DEBUG + if (rc != 1) // DDS return codes – check docs + throw new InvalidOperationException($"DDS SolveBoard failed with code {rc}"); +#endif + return rc; + } + + public SolvedBoards SolveAllBoards(Boards boards) + { + var rc = DdsNative.SolveAllBoards( in boards + , out var solved + , 0); + +#if DEBUG + if (rc != 1) + throw new InvalidOperationException($"DDS SolveAllBoards failed with code {rc}"); +#endif + return solved; + } + + public int SolvePBN( string pbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex) + { + var rc = DdsNative.SolvePBN( pbn + , target + , solutions + , mode + , out fut + , threadIndex); + +#if DEBUG + if (rc != 1) + throw new InvalidOperationException($"SolvePBN failed: {rc}"); +#endif + return rc; + } + + public ddTableResults CalcDDTable(ddTableDeal deal) + { + var rc = DdsNative.CalcDDtable(ref deal, out var table); + +#if DEBUG + if (rc != 1) + throw new InvalidOperationException($"CalcDDtable failed: {rc}"); +#endif + return table; + } + + public parResults CalcPar(ddTableResults table) + { + var rc = DdsNative.CalcPar(ref table, out var pres); + +#if DEBUG + if (rc != 1) + throw new InvalidOperationException($"CalcPar failed: {rc}"); +#endif + return pres; + } +} diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj new file mode 100644 index 00000000..9cc153ea --- /dev/null +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -0,0 +1,8 @@ + + + + net8.0 + enable + disable + + diff --git a/dotnet/DDS_Core/DDS_Core.slnx b/dotnet/DDS_Core/DDS_Core.slnx new file mode 100644 index 00000000..80ada029 --- /dev/null +++ b/dotnet/DDS_Core/DDS_Core.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dotnet/DDS_Core/DataModel/Boards.cs b/dotnet/DDS_Core/DataModel/Boards.cs new file mode 100644 index 00000000..28b6c807 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Boards.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; +using DDS_Core.DataModel; + +namespace DDS_Core.DataModel; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct Boards +{ + public int noOfBoards; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public Deal[] deals; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public int[] target; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public int[] solutions; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public int[] mode; +} diff --git a/dotnet/DDS_Core/DataModel/Deal.cs b/dotnet/DDS_Core/DataModel/Deal.cs new file mode 100644 index 00000000..4a007f26 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Deal.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace DDS_Core.DataModel; + +#region Native structures for DDS.dll + // Do not set [... , Pack = 1] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct Deal + { + public int trump; + public int first; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickSuit; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickRank; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public uint[] remainCards; + } +#endregion diff --git a/dotnet/DDS_Core/DataModel/FutureTricks.cs b/dotnet/DDS_Core/DataModel/FutureTricks.cs new file mode 100644 index 00000000..8508dbca --- /dev/null +++ b/dotnet/DDS_Core/DataModel/FutureTricks.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; + +namespace DDS_Core.DataModel; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct FutureTricks +{ + public long Nodes; + + public long TotalNodes; + public long TotalTrickNodes; + public long LeafNodes; + public long TTLookups; + public long TTHits; + public long TTInserts; + public long TTUpdates; + public long QuickTricks1; + public long QuickTricks2; + public long LaterTricks; + public long CheckSum; + + public int cards; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public int[] suit; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public int[] rank; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public int[] equals; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public int[] score; +} diff --git a/dotnet/DDS_Core/DataModel/Hand.cs b/dotnet/DDS_Core/DataModel/Hand.cs new file mode 100644 index 00000000..ef66f84c --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Hand.cs @@ -0,0 +1,3 @@ +namespace DDS_Core.DataModel; + +public enum Hand : int { North = 0, East = 1, South = 2, West = 3 } diff --git a/dotnet/DDS_Core/DataModel/SolvedBoards.cs b/dotnet/DDS_Core/DataModel/SolvedBoards.cs new file mode 100644 index 00000000..267358d7 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolvedBoards.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +using DDS_Core.DataModel; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SolvedBoards +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + public FutureTricks[] solvedBoard; +} diff --git a/dotnet/DDS_Core/DataModel/SolvingMode.cs b/dotnet/DDS_Core/DataModel/SolvingMode.cs new file mode 100644 index 00000000..197d6bc5 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolvingMode.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core.DataModel; + +public enum SolvingMode +{ + Auto = 0, + Par = 1, + SingleDummy = 2, + DoubleDummy = 3 +} diff --git a/dotnet/DDS_Core/DataModel/Suit.cs b/dotnet/DDS_Core/DataModel/Suit.cs new file mode 100644 index 00000000..b4683039 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Suit.cs @@ -0,0 +1,5 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core.DataModel; + +public enum Suit : int { Spades = 0, Hearts = 1, Diamonds = 2, Clubs = 3 } diff --git a/dotnet/DDS_Core/DataModel/ddTableDeal.cs b/dotnet/DDS_Core/DataModel/ddTableDeal.cs new file mode 100644 index 00000000..62db8180 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ddTableDeal.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core.DataModel +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ddTableDeal + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public int[] trump; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public int[] first; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public uint[] remainCards; + } +} diff --git a/dotnet/DDS_Core/DataModel/ddTableResults.cs b/dotnet/DDS_Core/DataModel/ddTableResults.cs new file mode 100644 index 00000000..fc311dc1 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ddTableResults.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ddTableResults +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public int[] resTable; +} diff --git a/dotnet/DDS_Core/DataModel/parResults.cs b/dotnet/DDS_Core/DataModel/parResults.cs new file mode 100644 index 00000000..fb3b713a --- /dev/null +++ b/dotnet/DDS_Core/DataModel/parResults.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct parResults +{ + public int score_NS; + public int score_EW; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public int[] parScore; +} diff --git a/dotnet/DDS_Core/Directory.Build.props b/dotnet/DDS_Core/Directory.Build.props new file mode 100644 index 00000000..1269d72a --- /dev/null +++ b/dotnet/DDS_Core/Directory.Build.props @@ -0,0 +1,8 @@ + + + latest + 4 + ..\..\Build\bin\$(platform)\ + ..\..\Build\int\$(platform)\$(MSBuildProjectName)\ + + \ No newline at end of file diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs new file mode 100644 index 00000000..a77486b9 --- /dev/null +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.InteropServices; +using DDS_Core.DataModel; +using static DDS_Core.Native.DdsNative; + +namespace DDS_Core.Native; + +internal static class DdsNative +{ + internal static readonly SolveBoard_t SolveBoard; + internal static readonly SolveAllBoards_t SolveAllBoards; + internal static readonly SolvePBN_t SolvePBN; + internal static readonly CalcDDtable_t CalcDDtable; + internal static readonly CalcPar_t CalcPar; + + static DdsNative() + { + var lib = NativeLoader.Handle; + + SolveBoard = LoadFunction(lib, "SolveBoard"); + SolveAllBoards = LoadFunction(lib, "SolveAllBoards"); + SolvePBN = LoadFunction(lib, "SolvePBN"); + CalcDDtable = LoadFunction(lib, "CalcDDtable"); + CalcPar = LoadFunction(lib, "CalcPar"); + + } + + private static T LoadFunction(IntPtr lib, string name) where T : Delegate + { + if (!NativeLibrary.TryGetExport(lib, name, out var fn)) + throw new MissingMethodException($"DDS function not found: {name}"); + + return Marshal.GetDelegateForFunctionPointer(fn); + } + + #region Define Delegate Types for DDS Functions + internal delegate int SolveBoard_t( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex); + + internal delegate int SolveAllBoards_t( in Boards boards + , out SolvedBoards solved + , int threadIndex); + + internal delegate int SolvePBN_t( [MarshalAs(UnmanagedType.LPStr)] string pbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex); + + internal delegate int CalcDDtable_t(ref ddTableDeal deal + , out ddTableResults table); + + internal delegate int CalcPar_t(ref ddTableResults table, + out parResults pres); + #endregion +} + diff --git a/dotnet/DDS_Core/Native/NativeLoader.cs b/dotnet/DDS_Core/Native/NativeLoader.cs new file mode 100644 index 00000000..1faad3a2 --- /dev/null +++ b/dotnet/DDS_Core/Native/NativeLoader.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace DDS_Core.Native; + +internal static class NativeLoader +{ + private static readonly IntPtr _handle; + + static NativeLoader() + { + var fileName = GetLibraryFileName(); + var fullPath = ResolveLibraryPath(fileName); + + if (!NativeLibrary.TryLoad(fullPath, out _handle)) + throw new DllNotFoundException($"Could not load native DDS library: {fullPath}"); + } + + public static IntPtr Handle => _handle; + + private static string GetLibraryFileName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "dds.dll"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "libdds.so"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "libdds.dylib"; + + throw new PlatformNotSupportedException(); + } + + private static string ResolveLibraryPath(string fileName) + { + var baseDir = AppContext.BaseDirectory; + + string[] search = + { + baseDir, + Path.Combine(baseDir, "runtimes", GetRid(), "native"), + Path.Combine(baseDir, "native") + }; + + foreach (var dir in search) + { + var path = Path.Combine(dir, fileName); + if (File.Exists(path)) + return path; + } + + return fileName; // fallback: let OS search PATH + } + + private static string GetRid() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "win-x64"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "linux-x64"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return RuntimeInformation.OSArchitecture == Architecture.Arm64 + ? "osx-arm64" + : "osx-x64"; + + throw new PlatformNotSupportedException(); + } +} diff --git a/solution/Solution.slnx b/solution/Solution.slnx index 50b563dd..efaf343b 100644 --- a/solution/Solution.slnx +++ b/solution/Solution.slnx @@ -6,7 +6,6 @@ - From 71a49a34665bb3a57ec004a73059a5469cfd56f6 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Wed, 27 May 2026 14:08:55 +0200 Subject: [PATCH 023/157] Create dynamic link libraries version of the static link libraries --- dotnet/DDS_Core/Native/DdsNative.cs | 109 ++++---- dotnet/DDS_Core/Native/NativeLoader.cs | 72 ----- library/src/BUILD.bazel | 23 ++ solution/DDS.vcxproj | 2 + solution/Solution.slnx | 1 + solution/dds_native.vcxproj | 153 +++++++++++ solution/dds_native.vcxproj.filters | 357 +++++++++++++++++++++++++ 7 files changed, 593 insertions(+), 124 deletions(-) delete mode 100644 dotnet/DDS_Core/Native/NativeLoader.cs create mode 100644 solution/dds_native.vcxproj create mode 100644 solution/dds_native.vcxproj.filters diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index a77486b9..951e5d29 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -1,62 +1,67 @@ using System; using System.Runtime.InteropServices; using DDS_Core.DataModel; -using static DDS_Core.Native.DdsNative; namespace DDS_Core.Native; internal static class DdsNative { - internal static readonly SolveBoard_t SolveBoard; - internal static readonly SolveAllBoards_t SolveAllBoards; - internal static readonly SolvePBN_t SolvePBN; - internal static readonly CalcDDtable_t CalcDDtable; - internal static readonly CalcPar_t CalcPar; - - static DdsNative() - { - var lib = NativeLoader.Handle; - - SolveBoard = LoadFunction(lib, "SolveBoard"); - SolveAllBoards = LoadFunction(lib, "SolveAllBoards"); - SolvePBN = LoadFunction(lib, "SolvePBN"); - CalcDDtable = LoadFunction(lib, "CalcDDtable"); - CalcPar = LoadFunction(lib, "CalcPar"); - - } - - private static T LoadFunction(IntPtr lib, string name) where T : Delegate - { - if (!NativeLibrary.TryGetExport(lib, name, out var fn)) - throw new MissingMethodException($"DDS function not found: {name}"); - - return Marshal.GetDelegateForFunctionPointer(fn); - } - - #region Define Delegate Types for DDS Functions - internal delegate int SolveBoard_t( in Deal dl - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex); - - internal delegate int SolveAllBoards_t( in Boards boards - , out SolvedBoards solved - , int threadIndex); - - internal delegate int SolvePBN_t( [MarshalAs(UnmanagedType.LPStr)] string pbn - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex); - - internal delegate int CalcDDtable_t(ref ddTableDeal deal - , out ddTableResults table); - - internal delegate int CalcPar_t(ref ddTableResults table, - out parResults pres); - #endregion + [DllImport("dds_native")] + public static extern int SolveBoard(in Deal dl, + int target, + int solutions, + int mode, + out FutureTricks fut, + int threadIndex); + + [DllImport("dds_native")] + public static extern int SolveAllBoards(in Boards boards, + out SolvedBoards solved, + int threadIndex); + + [DllImport("dds_native")] + public static extern int SolvePBN([MarshalAs(UnmanagedType.LPStr)] string pbn, + int target, + int solutions, + int mode, + out FutureTricks fut, + int threadIndex); + + [DllImport("dds_native")] + public static extern int CalcDDtable(ref ddTableDeal deal, + out ddTableResults table); + + [DllImport("dds_native")] + public static extern int CalcPar(ref ddTableResults table, + out parResults pres); + + internal static int SolveBoard_Wrapper(in Deal dl, + int target, + int solutions, + int mode, + out FutureTricks fut, + int threadIndex) + => SolveBoard(in dl, target, solutions, mode, out fut, threadIndex); + + internal static int SolveAllBoards_Wrapper(in Boards boards, + out SolvedBoards solved, + int threadIndex) + => SolveAllBoards(in boards, out solved, threadIndex); + + internal static int SolvePBN_Wrapper(string pbn, + int target, + int solutions, + int mode, + out FutureTricks fut, + int threadIndex) + => SolvePBN(pbn, target, solutions, mode, out fut, threadIndex); + + internal static int CalcDDtable_Wrapper(ref ddTableDeal deal, + out ddTableResults table) + => CalcDDtable(ref deal, out table); + + internal static int CalcPar_Wrapper(ref ddTableResults table, + out parResults pres) + => CalcPar(ref table, out pres); } diff --git a/dotnet/DDS_Core/Native/NativeLoader.cs b/dotnet/DDS_Core/Native/NativeLoader.cs deleted file mode 100644 index 1faad3a2..00000000 --- a/dotnet/DDS_Core/Native/NativeLoader.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace DDS_Core.Native; - -internal static class NativeLoader -{ - private static readonly IntPtr _handle; - - static NativeLoader() - { - var fileName = GetLibraryFileName(); - var fullPath = ResolveLibraryPath(fileName); - - if (!NativeLibrary.TryLoad(fullPath, out _handle)) - throw new DllNotFoundException($"Could not load native DDS library: {fullPath}"); - } - - public static IntPtr Handle => _handle; - - private static string GetLibraryFileName() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return "dds.dll"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return "libdds.so"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return "libdds.dylib"; - - throw new PlatformNotSupportedException(); - } - - private static string ResolveLibraryPath(string fileName) - { - var baseDir = AppContext.BaseDirectory; - - string[] search = - { - baseDir, - Path.Combine(baseDir, "runtimes", GetRid(), "native"), - Path.Combine(baseDir, "native") - }; - - foreach (var dir in search) - { - var path = Path.Combine(dir, fileName); - if (File.Exists(path)) - return path; - } - - return fileName; // fallback: let OS search PATH - } - - private static string GetRid() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return "win-x64"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return "linux-x64"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return RuntimeInformation.OSArchitecture == Architecture.Arm64 - ? "osx-arm64" - : "osx-x64"; - - throw new PlatformNotSupportedException(); - } -} diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 730bc3b7..72797025 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -44,6 +44,29 @@ cc_library( ], ) +cc_library( + name = "dds_native", + hdrs = ["//library/src:dds_headers"], + srcs = ["//library/src:dds_sources"], + includes = ["."], + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES, + visibility = ["//visibility:public"], + include_prefix = "dds", + linkshared = 1, + deps = [ + "//library/src/api:api_definitions", + "//library/src/utility:constants", + "//library/src/lookup_tables:lookup_tables", + "//library/src/heuristic_sorting", + "//library/src/trans_table", + "//library/src/moves:moves", + "//library/src/system", + "//library/src/solver_context:solver_context", + ], +) + filegroup( name = "testable_dds_sources", srcs = glob([ diff --git a/solution/DDS.vcxproj b/solution/DDS.vcxproj index 25a2615b..53def24f 100644 --- a/solution/DDS.vcxproj +++ b/solution/DDS.vcxproj @@ -32,6 +32,8 @@ StaticLibrary false + + diff --git a/solution/Solution.slnx b/solution/Solution.slnx index efaf343b..9c4a4476 100644 --- a/solution/Solution.slnx +++ b/solution/Solution.slnx @@ -35,6 +35,7 @@ + diff --git a/solution/dds_native.vcxproj b/solution/dds_native.vcxproj new file mode 100644 index 00000000..c58e703a --- /dev/null +++ b/solution/dds_native.vcxproj @@ -0,0 +1,153 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + true + {D38617A2-482A-1C00-7C88-02B17B693906} + NetCoreCProj + ddsnative + 10.0 + + + + + + + + DynamicLibrary + true + + + DynamicLibrary + false + + + + + + + + + + + + + + + + + + $(BuildDir)\bin\$(platform)\$(Configuration)\ + $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + + + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + ..\library\src;%(AdditionalIncludeDirectories) + stdcpp20 + + + $(IntDir)$(MSBuildProjectName).log + + + + + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + ..\library\src;%(AdditionalIncludeDirectories) + stdcpp20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/solution/dds_native.vcxproj.filters b/solution/dds_native.vcxproj.filters new file mode 100644 index 00000000..fce01c52 --- /dev/null +++ b/solution/dds_native.vcxproj.filters @@ -0,0 +1,357 @@ + + + + + + + + + + {F1000000-0000-4000-8000-000000000001} + + + + {F1000000-0000-4000-8000-000000000002} + + + + {F1000000-0000-4000-8000-000000000003} + + + + {F1000000-0000-4000-8000-000000000004} + + + + {F1000000-0000-4000-8000-000000000005} + + + + {F1000000-0000-4000-8000-000000000006} + + + + {F1000000-0000-4000-8000-000000000007} + + + + {F1000000-0000-4000-8000-000000000008} + + + + {F1000000-0000-4000-8000-000000000009} + + + + {F1000000-0000-4000-8000-00000000000A} + + + + + + + + + + + library\src + + + library\src + + + library\src + + + library\src + + + library\src + + + library\src + + + library\src + + + library\src + + + + library\src\heuristic_sorting + + + + library\src + + + + library\src + + + + library\src\lookup_tables + + + + library\src\moves + + + + library\src + + + + library\src + + + + library\src + + + + library\src + + + + library\src\solver_context + + + + library\src + + + + library\src + + + + library\src + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\trans_table + + + + library\src\trans_table + + + + library\src\utility + + + + + + + + + + + library\src + + + + library\src + + + + library\src\api + + + + library\src\api + + + + library\src\api + + + + library\src\api + + + + library\src\api + + + + library\src\api + + + + library\src\api + + + + library\src + + + + library\src + + + + library\src + + + + library\src + + + + library\src\heuristic_sorting + + + + library\src\heuristic_sorting + + + + library\src + + + + library\src + + + + library\src\lookup_tables + + + + library\src\moves + + + + library\src + + + + library\src + + + + library\src + + + + library\src\solver_context + + + + library\src + + + + library\src + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\system + + + + library\src\trans_table + + + + library\src\trans_table + + + + library\src\trans_table + + + + library\src\utility + + + + library\src\utility + + + + From fb7ace45a57653a03df7d9d18e7c583f1e78e523 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Thu, 28 May 2026 15:12:19 +0200 Subject: [PATCH 024/157] Load af native dll and a demo implemented --- dotnet/DDS_Core/DDS.cs | 4 +- dotnet/DDS_Core/DDS_Core.csproj | 3 + dotnet/DDS_Core/DDS_Core.slnx | 18 ++-- dotnet/DDS_Core/DataModel/Boards.cs | 4 +- dotnet/DDS_Core/DataModel/CardRanks.cs | 22 +++++ dotnet/DDS_Core/DataModel/Deal.cs | 2 +- dotnet/DDS_Core/DataModel/FutureTricks.cs | 19 +--- dotnet/DDS_Core/DataModel/Hand.cs | 2 +- dotnet/DDS_Core/DataModel/SolvedBoards.cs | 2 +- dotnet/DDS_Core/DataModel/SolvingMode.cs | 2 +- dotnet/DDS_Core/DataModel/Suit.cs | 2 +- dotnet/DDS_Core/DataModel/ddTableDeal.cs | 2 +- dotnet/DDS_Core/Directory.Build.props | 8 -- dotnet/DDS_Core/Native/DdsNative.cs | 88 +++++++++---------- dotnet/DDS_Core_Demo/DDS_Core_Demo.csproj | 17 ++++ dotnet/DDS_Core_Demo/Program.cs | 60 +++++++++++++ .../Properties/launchSettings.json | 8 ++ dotnet/Directory.Build.props | 21 +++++ 18 files changed, 201 insertions(+), 83 deletions(-) create mode 100644 dotnet/DDS_Core/DataModel/CardRanks.cs delete mode 100644 dotnet/DDS_Core/Directory.Build.props create mode 100644 dotnet/DDS_Core_Demo/DDS_Core_Demo.csproj create mode 100644 dotnet/DDS_Core_Demo/Program.cs create mode 100644 dotnet/DDS_Core_Demo/Properties/launchSettings.json create mode 100644 dotnet/Directory.Build.props diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 12228366..bc59cb5c 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -1,5 +1,5 @@ using System.Runtime.InteropServices; -using DDS_Core.DataModel; +using DDS_Core; using DDS_Core.Native; namespace DDS_Core; @@ -17,7 +17,7 @@ public int SolveBoard( in Deal dl , target , solutions , mode - , out fut + , out fut , threadIndex); #if DEBUG diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj index 9cc153ea..e6bc0879 100644 --- a/dotnet/DDS_Core/DDS_Core.csproj +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -4,5 +4,8 @@ net8.0 enable disable + x64 + false + ..\..\Build\bin\ diff --git a/dotnet/DDS_Core/DDS_Core.slnx b/dotnet/DDS_Core/DDS_Core.slnx index 80ada029..c30e54c4 100644 --- a/dotnet/DDS_Core/DDS_Core.slnx +++ b/dotnet/DDS_Core/DDS_Core.slnx @@ -1,9 +1,17 @@ - - - - + - + + + + + + + + + + + + diff --git a/dotnet/DDS_Core/DataModel/Boards.cs b/dotnet/DDS_Core/DataModel/Boards.cs index 28b6c807..bacb1a1d 100644 --- a/dotnet/DDS_Core/DataModel/Boards.cs +++ b/dotnet/DDS_Core/DataModel/Boards.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -using DDS_Core.DataModel; +using DDS_Core; -namespace DDS_Core.DataModel; +namespace DDS_Core; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Boards diff --git a/dotnet/DDS_Core/DataModel/CardRanks.cs b/dotnet/DDS_Core/DataModel/CardRanks.cs new file mode 100644 index 00000000..efb480cb --- /dev/null +++ b/dotnet/DDS_Core/DataModel/CardRanks.cs @@ -0,0 +1,22 @@ +namespace DDS_Core +{ + public enum CardRanks : uint + { +None + , Ace = 0x00000001 <<14 + , King = 0x00000001 <<13 + , Queen = 0x00000001 <<12 + , Jack = 0x00000001 <<11 + , Ten = 0x00000001 <<10 + , n9 = 0x00000001 <<9 + , n8 = 0x00000001 <<8 + , n7 = 0x00000001 <<7 + , n6 = 0x00000001 <<6 + , n5 = 0x00000001 <<5 + , n4 = 0x00000001 <<4 + , n3 = 0x00000001 <<3 + , n2 = 0x00000001 <<2 + + , All = 0x00007FFC // All ranks (except None) + }; +} diff --git a/dotnet/DDS_Core/DataModel/Deal.cs b/dotnet/DDS_Core/DataModel/Deal.cs index 4a007f26..ceed7947 100644 --- a/dotnet/DDS_Core/DataModel/Deal.cs +++ b/dotnet/DDS_Core/DataModel/Deal.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace DDS_Core.DataModel; +namespace DDS_Core; #region Native structures for DDS.dll // Do not set [... , Pack = 1] diff --git a/dotnet/DDS_Core/DataModel/FutureTricks.cs b/dotnet/DDS_Core/DataModel/FutureTricks.cs index 8508dbca..aa788737 100644 --- a/dotnet/DDS_Core/DataModel/FutureTricks.cs +++ b/dotnet/DDS_Core/DataModel/FutureTricks.cs @@ -2,26 +2,13 @@ using System.Linq; using System.Runtime.InteropServices; -namespace DDS_Core.DataModel; +namespace DDS_Core; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FutureTricks { - public long Nodes; - - public long TotalNodes; - public long TotalTrickNodes; - public long LeafNodes; - public long TTLookups; - public long TTHits; - public long TTInserts; - public long TTUpdates; - public long QuickTricks1; - public long QuickTricks2; - public long LaterTricks; - public long CheckSum; - - public int cards; + public int Nodes; + public int cards; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public int[] suit; diff --git a/dotnet/DDS_Core/DataModel/Hand.cs b/dotnet/DDS_Core/DataModel/Hand.cs index ef66f84c..d276ad32 100644 --- a/dotnet/DDS_Core/DataModel/Hand.cs +++ b/dotnet/DDS_Core/DataModel/Hand.cs @@ -1,3 +1,3 @@ -namespace DDS_Core.DataModel; +namespace DDS_Core; public enum Hand : int { North = 0, East = 1, South = 2, West = 3 } diff --git a/dotnet/DDS_Core/DataModel/SolvedBoards.cs b/dotnet/DDS_Core/DataModel/SolvedBoards.cs index 267358d7..a9a74dcf 100644 --- a/dotnet/DDS_Core/DataModel/SolvedBoards.cs +++ b/dotnet/DDS_Core/DataModel/SolvedBoards.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -using DDS_Core.DataModel; +using DDS_Core; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SolvedBoards diff --git a/dotnet/DDS_Core/DataModel/SolvingMode.cs b/dotnet/DDS_Core/DataModel/SolvingMode.cs index 197d6bc5..66d74e37 100644 --- a/dotnet/DDS_Core/DataModel/SolvingMode.cs +++ b/dotnet/DDS_Core/DataModel/SolvingMode.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace DDS_Core.DataModel; +namespace DDS_Core; public enum SolvingMode { diff --git a/dotnet/DDS_Core/DataModel/Suit.cs b/dotnet/DDS_Core/DataModel/Suit.cs index b4683039..ad23c32e 100644 --- a/dotnet/DDS_Core/DataModel/Suit.cs +++ b/dotnet/DDS_Core/DataModel/Suit.cs @@ -1,5 +1,5 @@ using System.Runtime.InteropServices; -namespace DDS_Core.DataModel; +namespace DDS_Core; public enum Suit : int { Spades = 0, Hearts = 1, Diamonds = 2, Clubs = 3 } diff --git a/dotnet/DDS_Core/DataModel/ddTableDeal.cs b/dotnet/DDS_Core/DataModel/ddTableDeal.cs index 62db8180..6b04a6a6 100644 --- a/dotnet/DDS_Core/DataModel/ddTableDeal.cs +++ b/dotnet/DDS_Core/DataModel/ddTableDeal.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace DDS_Core.DataModel +namespace DDS_Core { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ddTableDeal diff --git a/dotnet/DDS_Core/Directory.Build.props b/dotnet/DDS_Core/Directory.Build.props deleted file mode 100644 index 1269d72a..00000000 --- a/dotnet/DDS_Core/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - latest - 4 - ..\..\Build\bin\$(platform)\ - ..\..\Build\int\$(platform)\$(MSBuildProjectName)\ - - \ No newline at end of file diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 951e5d29..dc0433e3 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -1,67 +1,67 @@ using System; using System.Runtime.InteropServices; -using DDS_Core.DataModel; +using DDS_Core; namespace DDS_Core.Native; internal static class DdsNative { [DllImport("dds_native")] - public static extern int SolveBoard(in Deal dl, - int target, - int solutions, - int mode, - out FutureTricks fut, - int threadIndex); + public static extern int SolveBoard( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex); [DllImport("dds_native")] - public static extern int SolveAllBoards(in Boards boards, - out SolvedBoards solved, - int threadIndex); + public static extern int SolveAllBoards( in Boards boards + , out SolvedBoards solved + , int threadIndex); [DllImport("dds_native")] - public static extern int SolvePBN([MarshalAs(UnmanagedType.LPStr)] string pbn, - int target, - int solutions, - int mode, - out FutureTricks fut, - int threadIndex); + public static extern int SolvePBN( [MarshalAs(UnmanagedType.LPStr)] string pbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex); [DllImport("dds_native")] - public static extern int CalcDDtable(ref ddTableDeal deal, - out ddTableResults table); + public static extern int CalcDDtable( ref ddTableDeal deal + , out ddTableResults table); [DllImport("dds_native")] - public static extern int CalcPar(ref ddTableResults table, - out parResults pres); + public static extern int CalcPar( ref ddTableResults table + , out parResults pres); - internal static int SolveBoard_Wrapper(in Deal dl, - int target, - int solutions, - int mode, - out FutureTricks fut, - int threadIndex) - => SolveBoard(in dl, target, solutions, mode, out fut, threadIndex); + internal static int SolveBoard_Wrapper( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex) + => SolveBoard(in dl, target, solutions, mode, out fut, threadIndex); - internal static int SolveAllBoards_Wrapper(in Boards boards, - out SolvedBoards solved, - int threadIndex) - => SolveAllBoards(in boards, out solved, threadIndex); + internal static int SolveAllBoards_Wrapper( in Boards boards + , out SolvedBoards solved + , int threadIndex) + => SolveAllBoards(in boards, out solved, threadIndex); - internal static int SolvePBN_Wrapper(string pbn, - int target, - int solutions, - int mode, - out FutureTricks fut, - int threadIndex) - => SolvePBN(pbn, target, solutions, mode, out fut, threadIndex); + internal static int SolvePBN_Wrapper( string pbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex) + => SolvePBN(pbn, target, solutions, mode, out fut, threadIndex); - internal static int CalcDDtable_Wrapper(ref ddTableDeal deal, - out ddTableResults table) - => CalcDDtable(ref deal, out table); + internal static int CalcDDtable_Wrapper( ref ddTableDeal deal + , out ddTableResults table) + => CalcDDtable(ref deal, out table); - internal static int CalcPar_Wrapper(ref ddTableResults table, - out parResults pres) - => CalcPar(ref table, out pres); + internal static int CalcPar_Wrapper( ref ddTableResults table + , out parResults pres) + => CalcPar(ref table, out pres); } diff --git a/dotnet/DDS_Core_Demo/DDS_Core_Demo.csproj b/dotnet/DDS_Core_Demo/DDS_Core_Demo.csproj new file mode 100644 index 00000000..ee83a962 --- /dev/null +++ b/dotnet/DDS_Core_Demo/DDS_Core_Demo.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + x64 + false + ..\..\Build\bin\ + + + + + + + diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs new file mode 100644 index 00000000..2bda7ece --- /dev/null +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -0,0 +1,60 @@ +using DDS_Core; +using static DDS_Core.CardRanks; + +namespace DDS_Core_Demo; + +internal class Program +{ + + static string[] contracts = ["spades","hearts", "diamonds", "clubs", "no trump" ]; + + static void Main(string[] args) + { + var dds = new DDS(); + var deal = new Deal() + { + trump = (int)Suit.Hearts + , first = 0 + , currentTrickSuit = new int[3] {0, 0, 0 } + , currentTrickRank = new int[3] {0, 0, 0 } + , remainCards = new uint[16] + { + (uint)(Jack|n6|n5|n2) + , (uint)(Ace|n7|n4) + , (uint)(None) + , (uint)(King|Ten|n9|n6|n4|n2) + // + , (uint)(Ace|Ten|n9|n8|n7|n3) + , (uint)(King|Jack|n5) + , (uint)(Ten|n8|n2) + , (uint)(n8) + // + , (uint)(n4) + , (uint)(Queen|Ten|n9|n8|n2) + , (uint)(Ace|King|Queen|n9|n3) + , (uint)(Ace|Jack) + // + , (uint)(King|Queen) + , (uint)(n6|n3) + , (uint)(Jack|n7|n6|n5|n4) + , (uint)(Queen|n7|n5|n3) + } + }; + + + for (deal.trump = 0; deal.trump < 5; deal.trump++) + for (deal.first = 0; deal.first < 4; deal.first++) + { + var rc=dds.SolveBoard(deal, -1, 1, 2, out FutureTricks fut); + var deci = (deal.first+3)&3; + var decl = "NESW"[deci]; + var cont = contracts[deal.trump]; + + Console.WriteLine($"Tricks in {cont} for {decl}: {13 - fut.score[0]} "); + } + Console.WriteLine($"Press any key to continue..."); + + + Console.ReadKey(); + } +} diff --git a/dotnet/DDS_Core_Demo/Properties/launchSettings.json b/dotnet/DDS_Core_Demo/Properties/launchSettings.json new file mode 100644 index 00000000..1779d31d --- /dev/null +++ b/dotnet/DDS_Core_Demo/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DDS_Core_Demo": { + "commandName": "Project", + "debugEngines": "managed-dotnet,native" + } + } +} \ No newline at end of file diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props new file mode 100644 index 00000000..4d87bb66 --- /dev/null +++ b/dotnet/Directory.Build.props @@ -0,0 +1,21 @@ + + + latest + 4 + ..\..\Build\bin\$(platform)\ + ..\..\Build\int\$(platform)\$(MSBuildProjectName)\ + + + + $(DefineConstants);DEBUG; + false + full + + + + + $(DefineConstants); RELEASE; + true + none + + \ No newline at end of file From d93619026b60af07454b3d04ad913bc8f180edbf Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 29 May 2026 15:15:01 +0200 Subject: [PATCH 025/157] All 2.9 interfaces added --- .../.cr/personal/Navigation/Code Places.xml | 6 + dotnet/DDS_Core/DDS.cs | 414 +++++++++++++++--- dotnet/DDS_Core/DDS_Core.csproj | 7 + dotnet/DDS_Core/DataModel/AllParResults.cs | 15 + dotnet/DDS_Core/DataModel/Boards.cs | 21 +- dotnet/DDS_Core/DataModel/BoardsPBN.cs | 32 ++ dotnet/DDS_Core/DataModel/CardRanks.cs | 26 +- dotnet/DDS_Core/DataModel/ContractType.cs | 26 ++ dotnet/DDS_Core/DataModel/DDSInfo.cs | 55 +++ dotnet/DDS_Core/DataModel/DdTableDealPBN.cs | 16 + dotnet/DDS_Core/DataModel/DdTableDeals.cs | 17 + dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs | 17 + dotnet/DDS_Core/DataModel/DdTablesRes.cs | 42 ++ dotnet/DDS_Core/DataModel/DdsConstants.cs | 28 ++ dotnet/DDS_Core/DataModel/Deal.cs | 37 +- dotnet/DDS_Core/DataModel/DealPBN.cs | 30 ++ dotnet/DDS_Core/DataModel/FutureTricks.cs | 32 +- dotnet/DDS_Core/DataModel/Hands.cs | 37 ++ dotnet/DDS_Core/DataModel/ParResultsDealer.cs | 24 + dotnet/DDS_Core/DataModel/ParResultsMaster.cs | 23 + dotnet/DDS_Core/DataModel/ParTextResults.cs | 22 + dotnet/DDS_Core/DataModel/PlayTraceBin.cs | 23 + dotnet/DDS_Core/DataModel/PlayTracePBN.cs | 19 + dotnet/DDS_Core/DataModel/PlayTracesBin.cs | 19 + dotnet/DDS_Core/DataModel/PlayTracesPBN.cs | 17 + dotnet/DDS_Core/DataModel/SolveBoardResult.cs | 129 ++++++ .../DataModel/SolveBoardResultExtensions.cs | 40 ++ dotnet/DDS_Core/DataModel/SolvedBoards.cs | 18 +- dotnet/DDS_Core/DataModel/SolvedPlay.cs | 20 + dotnet/DDS_Core/DataModel/SolvedPlays.cs | 18 + dotnet/DDS_Core/DataModel/ddTableDeal.cs | 33 +- dotnet/DDS_Core/DataModel/ddTableResults.cs | 19 +- dotnet/DDS_Core/DataModel/parResults.cs | 26 +- dotnet/DDS_Core/Helpers/ByteBuffer100.cs | 25 ++ dotnet/DDS_Core/Helpers/ByteBuffer106.cs | 26 ++ dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs | 41 ++ dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs | 39 ++ dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs | 40 ++ dotnet/DDS_Core/Helpers/ByteBuffer32.cs | 25 ++ dotnet/DDS_Core/Helpers/ByteBuffer80.cs | 25 ++ dotnet/DDS_Core/Helpers/Grid1D.cs | 42 ++ dotnet/DDS_Core/Helpers/IntBuffer13.cs | 26 ++ dotnet/DDS_Core/Helpers/IntBuffer3.cs | 25 ++ dotnet/DDS_Core/Helpers/IntBuffer52.cs | 25 ++ dotnet/DDS_Core/Helpers/IntBuffer53.cs | 26 ++ dotnet/DDS_Core/Helpers/IntBuffer5x4.cs | 41 ++ dotnet/DDS_Core/Helpers/ParResultsBuffer.cs | 17 + dotnet/DDS_Core/Helpers/UintBuffer4x4.cs | 64 +++ dotnet/DDS_Core/Native/DdsNative.cs | 223 +++++++--- dotnet/DDS_Core_Demo/Program.cs | 84 ++-- dotnet/Directory.Build.props | 2 +- 51 files changed, 1871 insertions(+), 233 deletions(-) create mode 100644 dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml create mode 100644 dotnet/DDS_Core/DataModel/AllParResults.cs create mode 100644 dotnet/DDS_Core/DataModel/BoardsPBN.cs create mode 100644 dotnet/DDS_Core/DataModel/ContractType.cs create mode 100644 dotnet/DDS_Core/DataModel/DDSInfo.cs create mode 100644 dotnet/DDS_Core/DataModel/DdTableDealPBN.cs create mode 100644 dotnet/DDS_Core/DataModel/DdTableDeals.cs create mode 100644 dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs create mode 100644 dotnet/DDS_Core/DataModel/DdTablesRes.cs create mode 100644 dotnet/DDS_Core/DataModel/DdsConstants.cs create mode 100644 dotnet/DDS_Core/DataModel/DealPBN.cs create mode 100644 dotnet/DDS_Core/DataModel/Hands.cs create mode 100644 dotnet/DDS_Core/DataModel/ParResultsDealer.cs create mode 100644 dotnet/DDS_Core/DataModel/ParResultsMaster.cs create mode 100644 dotnet/DDS_Core/DataModel/ParTextResults.cs create mode 100644 dotnet/DDS_Core/DataModel/PlayTraceBin.cs create mode 100644 dotnet/DDS_Core/DataModel/PlayTracePBN.cs create mode 100644 dotnet/DDS_Core/DataModel/PlayTracesBin.cs create mode 100644 dotnet/DDS_Core/DataModel/PlayTracesPBN.cs create mode 100644 dotnet/DDS_Core/DataModel/SolveBoardResult.cs create mode 100644 dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs create mode 100644 dotnet/DDS_Core/DataModel/SolvedPlay.cs create mode 100644 dotnet/DDS_Core/DataModel/SolvedPlays.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer100.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer106.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer32.cs create mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer80.cs create mode 100644 dotnet/DDS_Core/Helpers/Grid1D.cs create mode 100644 dotnet/DDS_Core/Helpers/IntBuffer13.cs create mode 100644 dotnet/DDS_Core/Helpers/IntBuffer3.cs create mode 100644 dotnet/DDS_Core/Helpers/IntBuffer52.cs create mode 100644 dotnet/DDS_Core/Helpers/IntBuffer53.cs create mode 100644 dotnet/DDS_Core/Helpers/IntBuffer5x4.cs create mode 100644 dotnet/DDS_Core/Helpers/ParResultsBuffer.cs create mode 100644 dotnet/DDS_Core/Helpers/UintBuffer4x4.cs diff --git a/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml b/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml new file mode 100644 index 00000000..f9335fb6 --- /dev/null +++ b/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml @@ -0,0 +1,6 @@ + + + + True + + \ No newline at end of file diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index bc59cb5c..d340980a 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Text; using DDS_Core; using DDS_Core.Native; @@ -6,80 +7,361 @@ namespace DDS_Core; public class DDS { - public int SolveBoard( in Deal dl - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex = 0) - { - var rc = DdsNative.SolveBoard( in dl - , target - , solutions - , mode - , out fut - , threadIndex); + #region ====== Configuration and Resource Management ====== + public void SetMaxThreads(int userThreads) + => DdsNative.SetMaxThreads(userThreads); -#if DEBUG - if (rc != 1) // DDS return codes – check docs - throw new InvalidOperationException($"DDS SolveBoard failed with code {rc}"); -#endif - return rc; - } + public int SetThreading(int code) + { + var rc = DdsNative.SetThreading( code); - public SolvedBoards SolveAllBoards(Boards boards) - { - var rc = DdsNative.SolveAllBoards( in boards - , out var solved - , 0); + ThrowIfError(rc, nameof(SetThreading)); + return rc; + } -#if DEBUG - if (rc != 1) - throw new InvalidOperationException($"DDS SolveAllBoards failed with code {rc}"); -#endif - return solved; - } - - public int SolvePBN( string pbn - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex) - { - var rc = DdsNative.SolvePBN( pbn - , target - , solutions - , mode - , out fut - , threadIndex); + public void SetResources(int maxMemoryMB, int maxThreads) + => DdsNative.SetResources(maxMemoryMB, maxThreads); -#if DEBUG - if (rc != 1) - throw new InvalidOperationException($"SolvePBN failed: {rc}"); -#endif - return rc; - } + public void FreeMemory() + => DdsNative.FreeMemory(); + #endregion - public ddTableResults CalcDDTable(ddTableDeal deal) - { - var rc = DdsNative.CalcDDtable(ref deal, out var table); + #region ====== Single Board Solving ====== + public int SolveBoard( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex = 0) + { + var rc = DdsNative.SolveBoard( in dl + , target + , solutions + , mode + , out fut + , threadIndex); -#if DEBUG - if (rc != 1) - throw new InvalidOperationException($"CalcDDtable failed: {rc}"); -#endif - return table; - } + ThrowIfError(rc, nameof(SolveBoard)); + return rc; + } + + public int SolveBoardPBN( DealPBN pbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int threadIndex) + { + var rc = DdsNative.SolveBoardPBN( pbn + , target + , solutions + , mode + , out fut + , threadIndex); + + ThrowIfError(rc, nameof(SolveBoardPBN)); + return rc; + } + #endregion + + #region ====== Multiple Board Solving ====== + public int SolveAllBoards( Boards boards + , out SolvedBoards solved) + { + var rc = DdsNative.SolveAllBoards( in boards + , out solved); + + ThrowIfError(rc, nameof(SolveAllBoards)); + return rc; + } + + public int SolveAllBoardsBin( in Boards bop + , out SolvedBoards solved + ) + { + var rc = DdsNative.SolveAllBoardsBin( in bop + , out solved); + + ThrowIfError(rc, nameof(SolveAllBoardsBin)); + return rc; + } + + public int SolveAllChunks( in BoardsPBN bop + , out SolvedBoards solved + , int chunkSize) + { + var rc = DdsNative.SolveAllChunks( in bop + , out solved + , chunkSize); + + ThrowIfError(rc, nameof(SolveAllChunks)); + return rc; + } + + public int SolveAllChunksBin( in Boards bop + , out SolvedBoards solved + , int chunkSize) + { + var rc = DdsNative.SolveAllChunksBin( in bop + , out solved + , chunkSize); + ThrowIfError(rc, nameof(SolveAllChunksBin)); + return rc; + } + + public int SolveAllChunksPBN( in BoardsPBN bop + , out SolvedBoards solved + , int chunkSize) + { + var rc = DdsNative.SolveAllChunksPBN( in bop + , out solved + , chunkSize); + ThrowIfError(rc, nameof(SolveAllChunksPBN)); + return rc; + } + #endregion + + #region ====== Double Dummy Table Calculation ====== + public int CalcDDTable( DdTableDeal deal + , out DdTableResults table) + { + var rc = DdsNative.CalcDDtable( in deal + , out table); + + ThrowIfError(rc, nameof(CalcDDTable)); + return rc; + } + + public int CalcDDtablePBN( in DdTableDealPBN tableDealPBN + , out DdTableResults table) + { + var rc = DdsNative.CalcDDtablePBN( in tableDealPBN + , out table); + + ThrowIfError(rc, nameof(CalcDDtablePBN)); + return rc; + } + + public int CalcAllTables( in DdTableDeals dealsp + , int mode + , int[] trumpFilter //TODO: Tjekke denne + , out DdTablesRes resp + , out AllParResults presp) + { + var rc = DdsNative.CalcAllTables( in dealsp + , mode + , trumpFilter + , out resp + , out presp); + + ThrowIfError(rc, nameof(CalcAllTables)); + return rc; + } + + public int CalcAllTablesPBN( in DdTableDealsPBN dealsp + , int mode + , int[] trumpFilter + , out DdTablesRes resp + , out AllParResults presp) + { + var rc = DdsNative.CalcAllTablesPBN( in dealsp + , mode + , trumpFilter + , out resp + , out presp); + + ThrowIfError(rc, nameof(CalcAllTablesPBN)); + return rc; + } + #endregion + + #region ====== Par Score Calculation ====== + public int Par( in DdTableResults table + , out ParResults pres + , int vulnerable) + { + var rc = DdsNative.Par( in table + , out pres + , vulnerable); + + ThrowIfError(rc, nameof(Par)); + return rc; + } + + public int CalcPar( in DdTableDeal tableDeal + , int vulnerable + , out DdTableResults table + , out ParResults pres) + { + var rc = DdsNative.CalcPar( in tableDeal + , vulnerable + , out table + , out pres); + ThrowIfError(rc, nameof(CalcPar)); + return rc; + } + + public int CalcParPBN( in DdTableDealPBN tableDealPBN + , out DdTableResults table + , int vulnerable + , out ParResults pres) + { + var rc = DdsNative.CalcParPBN( in tableDealPBN + , out table + , vulnerable + , out pres); + + ThrowIfError(rc, nameof(CalcParPBN)); + return rc; + } + + public int SidesPar( in DdTableResults table + , [Out] ParResultsDealer[] sidesRes + , int vulnerable) + { + var rc = DdsNative.SidesPar( in table + , sidesRes + , vulnerable); + + ThrowIfError(rc, nameof(SidesPar)); + return rc; + } + + public int DealerPar( in DdTableResults table + , out ParResultsDealer pres + , int dealer + , int vulnerable) + { + var rc = DdsNative.DealerPar( in table + , out pres + , dealer + , vulnerable); + + ThrowIfError(rc, nameof(DealerPar)); + return rc; + } + + public int DealerParBin( in DdTableResults table + , out ParResultsMaster pres + , int dealer + , int vulnerable) + { + var rc = DdsNative.DealerParBin( in table + , out pres + , dealer + , vulnerable); + + ThrowIfError(rc, nameof(DealerParBin)); + return rc; + } + + public int SidesParBin( in DdTableResults table + , [Out] ParResultsMaster[] sidesRes + , int vulnerable) + { + var rc = DdsNative.SidesParBin( in table + , sidesRes + , vulnerable); + + ThrowIfError(rc, nameof(SidesParBin)); + return rc; + } + #endregion + + #region ====== Par Text Conversion ====== + public int ConvertToDealerTextFormat( in ParResultsMaster pres + , StringBuilder resp) + { + var rc = DdsNative.ConvertToDealerTextFormat( in pres + , resp); + ThrowIfError(rc, nameof(ConvertToDealerTextFormat)); + return rc; + } + + public int ConvertToSidesTextFormat( in ParResultsMaster pres + , out ParTextResults resp) + { + var rc = DdsNative.ConvertToSidesTextFormat( in pres + , out resp); + ThrowIfError(rc, nameof(ConvertToSidesTextFormat)); + return rc; + } + #endregion + + #region ====== Play Analysis ====== + public int AnalysePlayBin( in Deal dl + , in PlayTraceBin play + , out SolvedPlay solved + , int thrId) + { + var rc = DdsNative.AnalysePlayBin( in dl + , in play + , out solved + , thrId); + ThrowIfError(rc, nameof(AnalysePlayBin)); + return rc; + } + + public int AnalysePlayPBN( in DealPBN dlPBN + , in PlayTracePBN playPBN + , out SolvedPlay solved + , int thrId) + { + var rc = DdsNative.AnalysePlayPBN( in dlPBN + , in playPBN + , out solved + , thrId); + ThrowIfError(rc, nameof(AnalysePlayPBN)); + return rc; + } + + public int AnalyseAllPlaysBin( in Boards bop + , in PlayTracesBin plp + , out SolvedPlays solved + , int chunkSize) + { + var rc = DdsNative.AnalyseAllPlaysBin( in bop + , in plp + , out solved + , chunkSize); + ThrowIfError(rc, nameof(AnalyseAllPlaysBin)); + return rc; + } + + public int AnalyseAllPlaysPBN( in BoardsPBN bopPBN + , in PlayTracesPBN plpPBN + , out SolvedPlays solved + , int chunkSize) + { + var rc = DdsNative.AnalyseAllPlaysPBN( in bopPBN + , in plpPBN + , out solved + , chunkSize); + ThrowIfError(rc, nameof(AnalyseAllPlaysPBN)); + return rc; + } + #endregion + + #region ====== Utility Functions ====== + public void GetDDSInfo(out DDSInfo info) + { + DdsNative.GetDDSInfo(out info); + } - public parResults CalcPar(ddTableResults table) - { - var rc = DdsNative.CalcPar(ref table, out var pres); + public void ErrorMessage( int code + , StringBuilder line) + { + DdsNative.ErrorMessage(code, line); + } + #endregion + #region private methods + private static void ThrowIfError(int result, string functionName) + { #if DEBUG - if (rc != 1) - throw new InvalidOperationException($"CalcPar failed: {rc}"); + if (result != (int)SolveBoardResult.NoFault) + throw new InvalidOperationException($"{functionName} failed with code {result}: {result.GetRCErrorMessage()}"); #endif - return pres; - } + } + #endregion } diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj index e6bc0879..c0b582be 100644 --- a/dotnet/DDS_Core/DDS_Core.csproj +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -7,5 +7,12 @@ x64 false ..\..\Build\bin\ + True + + + + + + diff --git a/dotnet/DDS_Core/DataModel/AllParResults.cs b/dotnet/DDS_Core/DataModel/AllParResults.cs new file mode 100644 index 00000000..5da2fa9b --- /dev/null +++ b/dotnet/DDS_Core/DataModel/AllParResults.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Par results for all declarer/strain combinations (up to 40). +/// +[StructLayout(LayoutKind.Sequential)] +public struct AllParResults +{ + /// Array of par results (up to MAXNOOFTABLES entries). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES)] + public ParResults[] par_results; +} diff --git a/dotnet/DDS_Core/DataModel/Boards.cs b/dotnet/DDS_Core/DataModel/Boards.cs index bacb1a1d..8a5f1c4c 100644 --- a/dotnet/DDS_Core/DataModel/Boards.cs +++ b/dotnet/DDS_Core/DataModel/Boards.cs @@ -1,22 +1,29 @@ using System.Runtime.InteropServices; -using DDS_Core; namespace DDS_Core; -[StructLayout(LayoutKind.Sequential, Pack = 1)] +/// +/// Represents multiple bridge deals for batch analysis. +/// +[StructLayout(LayoutKind.Sequential)] public struct Boards { - public int noOfBoards; + /// Number of deals in this batch. + public int no_of_boards; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + /// Array of deals. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] public Deal[] deals; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + /// Target tricks for each board. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] public int[] target; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + /// Solution mode for each board (1=best, 2=all, 3=all+par). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] public int[] solutions; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] + /// Solve mode for each board. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] public int[] mode; } diff --git a/dotnet/DDS_Core/DataModel/BoardsPBN.cs b/dotnet/DDS_Core/DataModel/BoardsPBN.cs new file mode 100644 index 00000000..3f72c690 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/BoardsPBN.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Multiple boards in PBN format for batch solving. +/// +/// Similar to Boards but uses PBN (Portable Bridge Notation) format +/// for deal representation. Used for solving multiple boards efficiently. +/// +[StructLayout(LayoutKind.Sequential)] +public struct BoardsPBN +{ + /// Number of boards to solve. + public int no_of_boards; + + /// Array of deals in PBN format. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public DealPBN[] deals; + + /// Target tricks for each board. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public int[] target; + + /// Solution mode for each board. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public int[] solutions; + + /// Solve mode for each board. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public int[] mode; +} diff --git a/dotnet/DDS_Core/DataModel/CardRanks.cs b/dotnet/DDS_Core/DataModel/CardRanks.cs index efb480cb..644bd000 100644 --- a/dotnet/DDS_Core/DataModel/CardRanks.cs +++ b/dotnet/DDS_Core/DataModel/CardRanks.cs @@ -3,19 +3,19 @@ public enum CardRanks : uint { None - , Ace = 0x00000001 <<14 - , King = 0x00000001 <<13 - , Queen = 0x00000001 <<12 - , Jack = 0x00000001 <<11 - , Ten = 0x00000001 <<10 - , n9 = 0x00000001 <<9 - , n8 = 0x00000001 <<8 - , n7 = 0x00000001 <<7 - , n6 = 0x00000001 <<6 - , n5 = 0x00000001 <<5 - , n4 = 0x00000001 <<4 - , n3 = 0x00000001 <<3 - , n2 = 0x00000001 <<2 + , rA = 0x00000001 <<14 + , rK = 0x00000001 <<13 + , rQ = 0x00000001 <<12 + , rJ = 0x00000001 <<11 + , rT = 0x00000001 <<10 + , r9 = 0x00000001 <<9 + , r8 = 0x00000001 <<8 + , r7 = 0x00000001 <<7 + , r6 = 0x00000001 <<6 + , r5 = 0x00000001 <<5 + , r4 = 0x00000001 <<4 + , r3 = 0x00000001 <<3 + , r2 = 0x00000001 <<2 , All = 0x00007FFC // All ranks (except None) }; diff --git a/dotnet/DDS_Core/DataModel/ContractType.cs b/dotnet/DDS_Core/DataModel/ContractType.cs new file mode 100644 index 00000000..bdf9883b --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ContractType.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Contract details (level, strain, tricks, seats). +/// +[StructLayout(LayoutKind.Sequential)] +public struct ContractType +{ + /// Under tricks (0 = make, 1-13 = sacrifice). + public int under_tricks; + + /// Over tricks (0-3, e.g., 1 for 4S + 1). + public int over_tricks; + + /// Contract level (1-7). + public int level; + + /// Denomination (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). + public int denom; + + /// Seats playing contract (0 = N, 1 = E, 2 = S, 3 = W, 4 = NS, 5 = EW). + public int seats; +} diff --git a/dotnet/DDS_Core/DataModel/DDSInfo.cs b/dotnet/DDS_Core/DataModel/DDSInfo.cs new file mode 100644 index 00000000..c524a099 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DDSInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// DDS library information and configuration details. +/// +/// Contains version, platform, compiler, threading, and resource information. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct DDSInfo +{ + /// Major version number (e.g., 3 for 3.0.0). + public int major; + + /// Minor version number. + public int minor; + + /// Patch version number. + public int patch; + + /// Version string (e.g., "3.0.0"), max 10 characters. + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] + public string version_string; + + /// Platform (0 = unknown, 1 = Windows, 2 = Cygwin, 3 = Linux, 4 = macOS). + public int system; + + /// Bit width (32 or 64). + public int numBits; + + /// Compiler (0 = unknown, 1 = MSVC, 2 = mingw, 3 = GCC, 4 = clang). + public int compiler; + + /// Constructor type (0 = none, 1 = DllMain, 2 = Unix-style). + public int constructor; + + /// Number of processor cores available. + public int numCores; + + /// Threading system (0 = none, 1 = Windows, 2 = OpenMP, 3 = GCD, 4 = Boost, 5 = STL, 6 = TBB, etc.). + public int threading; + + /// Actual number of threads configured. + public int noOfThreads; + + /// Thread memory sizes configuration string (max 128 characters). + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string threadSizes; + + /// Detailed system information string (max 1024 characters). + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] + public string systemString; +} diff --git a/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs b/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs new file mode 100644 index 00000000..f92fc4af --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// A single deal in PBN format for double dummy table calculation. +/// +/// Uses PBN (Portable Bridge Notation) string representation. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct DdTableDealPBN +{ + /// Cards in PBN format (max 80 characters). + + public ByteBuffer80 cards; +} diff --git a/dotnet/DDS_Core/DataModel/DdTableDeals.cs b/dotnet/DDS_Core/DataModel/DdTableDeals.cs new file mode 100644 index 00000000..d1c2f235 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdTableDeals.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Multiple deals for batch double dummy table calculation. +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTableDeals +{ + /// Number of tables. + public int no_of_tables; + + /// Array of deals (up to MAXNOOFTABLES * DDS_STRAINS). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] + public DdTableDeal[] deals; +} diff --git a/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs new file mode 100644 index 00000000..bb8da951 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Multiple deals in PBN format for batch double dummy table calculation. +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTableDealsPBN +{ + /// Number of tables. + public int no_of_tables; + + /// Array of PBN deals (up to MAXNOOFTABLES * DDS_STRAINS). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] + public DdTableDealPBN[] deals; +} diff --git a/dotnet/DDS_Core/DataModel/DdTablesRes.cs b/dotnet/DDS_Core/DataModel/DdTablesRes.cs new file mode 100644 index 00000000..8d880c4e --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdTablesRes.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +//TODO: Tjek denne + +/// +/// Multiple double dummy table results. +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTablesRes +{ + /// Number of boards. + public int no_of_boards; + + /// Array of results (up to MAXNOOFTABLES * DDS_STRAINS = 200). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] + public DdTableResults[] results; + + /// + /// Safe indexer with bounds checking against actual no_of_boards. + /// + public DdTableResults this[int index] + { + get + { + if (index < 0 || index >= no_of_boards) + throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); + + return results[index]; + } + + set + { + if (index < 0 || index >= no_of_boards) + throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); + + results[index] = value; + } + } +} diff --git a/dotnet/DDS_Core/DataModel/DdsConstants.cs b/dotnet/DDS_Core/DataModel/DdsConstants.cs new file mode 100644 index 00000000..4f6ef007 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdsConstants.cs @@ -0,0 +1,28 @@ +using System; + +namespace DDS_Core +{ + /// + /// Constants for DDS bridge analysis. + /// + public static class DdsConstants + { + /// Number of bridge strains (4 suits + no trump). + public const int DDS_STRAINS = 5; + + /// Number of hands (N/E/S/W). + public const int DDS_HANDS = 4; + + /// Number of suits (S/H/D/C). + public const int DDS_SUITS = 4; + + /// No trump strain index. + public const int DDS_NOTRUMP = 4; + + /// Maximum number of boards in batch operations. + public const int MAXNOOFBOARDS = 200; + + /// Maximum number of DD tables. + public const int MAXNOOFTABLES = 40; + } +} diff --git a/dotnet/DDS_Core/DataModel/Deal.cs b/dotnet/DDS_Core/DataModel/Deal.cs index ceed7947..481f3eba 100644 --- a/dotnet/DDS_Core/DataModel/Deal.cs +++ b/dotnet/DDS_Core/DataModel/Deal.cs @@ -4,24 +4,31 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using static DDS_Core.ByteBuffer10x10; namespace DDS_Core; -#region Native structures for DDS.dll - // Do not set [... , Pack = 1] - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct Deal - { - public int trump; - public int first; +/// +/// Represents a bridge Deal for double dummy analysis. +/// +[StructLayout(LayoutKind.Sequential)] +public struct Deal +{ + /// Trump suit (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). + public int trump; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickSuit; + /// Hand to play first (0 = N, 1 = E, 2 = S, 3 = W). + public int first; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickRank; + /// Suits of cards played in the current trick (3 entries). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickSuit; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public uint[] remainCards; - } -#endregion + /// Ranks of cards played in the current trick (3 entries). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickRank; + + /// Remaining cards for each hand and suit (4 hands × 4 suits = 16 elements). + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public UintBuffer4x4 remainCards; +} diff --git a/dotnet/DDS_Core/DataModel/DealPBN.cs b/dotnet/DDS_Core/DataModel/DealPBN.cs new file mode 100644 index 00000000..f84133b7 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DealPBN.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Represents a bridge Deal in PBN (Portable Bridge Notation) format. +/// +/// PBN format is a standard text representation for bridge hands. +/// Example: "N:AKQ.K.AKQ.AKQ8 J976.QJT.J42.Q2 T842.A542.T83.T3 53.9876.965.J976" +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct DealPBN +{ + /// Trump suit (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). + public int trump; + + /// Hand to play first (0 = N, 1 = E, 2 = S, 3 = W). + public int first; + + /// Suits of cards played in the current trick (3 entries). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickSuit; + + /// Ranks of cards played in the current trick (3 entries). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public int[] currentTrickRank; + + /// PBN string describing remaining cards (max 80 characters). + public ByteBuffer80 remainCards; +} diff --git a/dotnet/DDS_Core/DataModel/FutureTricks.cs b/dotnet/DDS_Core/DataModel/FutureTricks.cs index aa788737..8c608ef8 100644 --- a/dotnet/DDS_Core/DataModel/FutureTricks.cs +++ b/dotnet/DDS_Core/DataModel/FutureTricks.cs @@ -1,24 +1,32 @@ using System; -using System.Linq; using System.Runtime.InteropServices; namespace DDS_Core; -[StructLayout(LayoutKind.Sequential, Pack = 1)] +/// +/// Stores the result of a double dummy analysis for a single position. +/// +/// Contains the number of nodes searched, the number of cards in the result, +/// and arrays for each card's suit, rank, equality group, and score. +/// +[StructLayout(LayoutKind.Sequential)] public struct FutureTricks { - public int Nodes; - public int cards; + /// Number of nodes searched in the analysis. + public int nodes; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public int[] suit; + /// Number of cards in the results. + public int cards; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public int[] rank; + /// Suit of each card (13 entries). + public IntBuffer13 suit; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public int[] equals; + /// Rank of each card (13 entries). + public IntBuffer13 rank; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - public int[] score; + /// Equality group for each card (13 entries). + public IntBuffer13 equals; + + /// Score for each card (13 entries). + public IntBuffer13 score; } diff --git a/dotnet/DDS_Core/DataModel/Hands.cs b/dotnet/DDS_Core/DataModel/Hands.cs new file mode 100644 index 00000000..dadc9b94 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Hands.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +//TODO: Tjek denne + +/// +/// 2D view of the four hands (ie cards) as [hand, suit] indexing. +/// +[StructLayout(LayoutKind.Sequential)] +public struct Hands +{ + /// + /// Cards for each hand and suit (4 hands × 4 suits = 16 elements). + /// Stored as flattened array but accessed via 2D indexer. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + private uint[] cards; + + /// Gets or sets card by [hand, suit] indexing. + /// Hand index (0=N, 1=E, 2=S, 3=W) + /// Suit index (0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs) + public ref uint this[int hand, int suit] + { + get + { + if (hand < 0 || hand >= 4) + throw new ArgumentOutOfRangeException(nameof(hand), "Hand must be 0-3"); + + if (suit < 0 || suit >= 4) + throw new ArgumentOutOfRangeException(nameof(suit), "Suit must be 0-3"); + + return ref cards[hand * 4 + suit]; + } + } +} diff --git a/dotnet/DDS_Core/DataModel/ParResultsDealer.cs b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs new file mode 100644 index 00000000..cafda75f --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + /// + /// Par result for a specific dealer. + /// + /// Contains number of par contracts and their details. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct ParResultsDealer + { + /// Number of contracts yielding the par score. + public int number; + + /// Par score for the specified dealer hand. + public int score; + + /// Par contract text strings (10 entries, max 10 chars each). + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public ByteBuffer100 contracts; + } +} diff --git a/dotnet/DDS_Core/DataModel/ParResultsMaster.cs b/dotnet/DDS_Core/DataModel/ParResultsMaster.cs new file mode 100644 index 00000000..9adbbd3c --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ParResultsMaster.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + + /// + /// Par contracts for both dealer and vulnerable combinations. + /// + [StructLayout(LayoutKind.Sequential)] + public struct ParResultsMaster + { + /// Par score (sign according to NS view). + public int score; + + /// Number of contracts giving the par score. + public int number; + + /// Array of par contracts (up to 10). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public ContractType[] contracts; + } +} diff --git a/dotnet/DDS_Core/DataModel/ParTextResults.cs b/dotnet/DDS_Core/DataModel/ParTextResults.cs new file mode 100644 index 00000000..7670ae20 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ParTextResults.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Text representation of par results. +/// +/// Includes short text summary and information about equality. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct ParTextResults +{ + /// + /// Short par text for NS and EW using 2D indexing (2 x 128 chars). + /// Access: par_text[side, index] where side=0(NS) or 1(EW) + /// + public ByteBuffer2x128 par_text; + + /// True if equal (doesn't matter who starts bidding), false otherwise. + [MarshalAs(UnmanagedType.I1)] + public bool equal; +} diff --git a/dotnet/DDS_Core/DataModel/PlayTraceBin.cs b/dotnet/DDS_Core/DataModel/PlayTraceBin.cs new file mode 100644 index 00000000..6149b8dc --- /dev/null +++ b/dotnet/DDS_Core/DataModel/PlayTraceBin.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + + +/// +/// Binary play trace (cards played in sequence). +/// +/// Contains card suits and ranks for cards played during the hand. +/// +[StructLayout(LayoutKind.Sequential)] +public struct PlayTraceBin +{ + /// Number of cards played (1-52). + public int number; + + /// Suit of each played card (52 entries). + public IntBuffer52 suit; + + /// Rank of each played card (52 entries). + public IntBuffer52 rank; +} diff --git a/dotnet/DDS_Core/DataModel/PlayTracePBN.cs b/dotnet/DDS_Core/DataModel/PlayTracePBN.cs new file mode 100644 index 00000000..410a4b87 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/PlayTracePBN.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// PBN format play trace (cards played in sequence). +/// +/// Uses PBN string representation for played cards. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct PlayTracePBN +{ + /// Number of cards played. + public int number; + + /// Cards in PBN format (max 106 characters). + //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 106)] + public ByteBuffer106 cards; +} diff --git a/dotnet/DDS_Core/DataModel/PlayTracesBin.cs b/dotnet/DDS_Core/DataModel/PlayTracesBin.cs new file mode 100644 index 00000000..014c97e9 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/PlayTracesBin.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + + +/// +/// Multiple binary play traces for batch analysis. +/// +[StructLayout(LayoutKind.Sequential)] +public struct PlayTracesBin +{ + /// Number of boards. + public int no_of_boards; + + /// Array of play traces (up to MAXNOOFBOARDS). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public PlayTraceBin[] plays; +} diff --git a/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs b/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs new file mode 100644 index 00000000..db628626 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Multiple PBN play traces for batch analysis. +/// +[StructLayout(LayoutKind.Sequential)] +public struct PlayTracesPBN +{ + /// Number of boards. + public int no_of_boards; + + /// Array of PBN play traces (up to MAXNOOFBOARDS). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public PlayTracePBN[] plays; +} diff --git a/dotnet/DDS_Core/DataModel/SolveBoardResult.cs b/dotnet/DDS_Core/DataModel/SolveBoardResult.cs new file mode 100644 index 00000000..932365ab --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolveBoardResult.cs @@ -0,0 +1,129 @@ +namespace DDS_Core; + +/// +/// DDS return codes for SolveBoard() and related functions. +/// +public enum SolveBoardResult +{ + /// + /// Success - no fault detected. + /// + NoFault = 1, + + /// + /// General error. Currently happens when fopen() fails or when AnalyseAllPlaysBin() + /// gets a different number of Boards in its first two arguments. + /// + UnknownFault = -1, + + /// + /// Zero cards supplied. + /// + ZeroCards = -2, + + /// + /// Target exceeds number of tricks remaining. + /// + TargetTooHigh = -3, + + /// + /// Cards duplicated. + /// + DuplicateCards = -4, + + /// + /// Target is less than -1. + /// + TargetWrongLo = -5, + + /// + /// Target is higher than 13. + /// + TargetWrongHi = -7, + + /// + /// Solutions parameter is less than 1. + /// + SolutionsWrongLo = -8, + + /// + /// Solutions parameter is higher than 3. + /// + SolutionsWrongHi = -9, + + /// + /// Too many cards supplied. + /// + TooManyCards = -10, + + /// + /// currentTrickSuit or currentTrickRank has wrong data. + /// + SuitOrRank = -12, + + /// + /// Played card also remains in a hand. + /// + PlayedCard = -13, + + /// + /// Wrong number of remaining cards in a hand. + /// + CardCount = -14, + + /// + /// Thread index is not 0 .. maximum. + /// + ThreadIndex = -15, + + /// + /// Mode parameter is less than 0. + /// + ModeWrongLo = -16, + + /// + /// Mode parameter is higher than 2. + /// + ModeWrongHi = -17, + + /// + /// Trump is not in 0 .. 4. + /// + TrumpWrong = -18, + + /// + /// First is not in 0 .. 2. + /// + FirstWrong = -19, + + /// + /// AnalysePlay input error (less than 0 or more than 52 cards, + /// invalid suit or rank, or played card is not held by the right player). + /// + PlayFault = -98, + + /// + /// PBN string error. + /// + PbnFault = -99, + + /// + /// Too many Boards requested. + /// + TooManyBoards = -101, + + /// + /// Could not create threads. + /// + ThreadCreate = -102, + + /// + /// Something failed waiting for thread to end. + /// + ThreadWait = -103, + + /// + /// Tried to set a multi-threading system that is not present in DLL. + /// + ThreadMissing = -104 +} diff --git a/dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs b/dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs new file mode 100644 index 00000000..fc777186 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs @@ -0,0 +1,40 @@ +namespace DDS_Core +{ + public static class SolveBoardResultExtensions + { + public static string GetRCErrorMessage(this int result) + { + return GetRcErrorMessage((SolveBoardResult)result); + } + + public static string GetRcErrorMessage(this SolveBoardResult result) + => result switch + { + SolveBoardResult.NoFault => "No fault", + SolveBoardResult.UnknownFault => "General error", + SolveBoardResult.ZeroCards => "Zero cards", + SolveBoardResult.TargetTooHigh => "Target exceeds number of tricks", + SolveBoardResult.DuplicateCards => "Cards duplicated", + SolveBoardResult.TargetWrongLo => "Target is less than -1", + SolveBoardResult.TargetWrongHi => "Target is higher than 13", + SolveBoardResult.SolutionsWrongLo => "Solutions parameter is less than 1", + SolveBoardResult.SolutionsWrongHi => "Solutions parameter is higher than 3", + SolveBoardResult.TooManyCards => "Too many cards", + SolveBoardResult.SuitOrRank => "currentTrickSuit or currentTrickRank has wrong data", + SolveBoardResult.PlayedCard => "Played card also remains in a hand", + SolveBoardResult.CardCount => "Wrong number of remaining cards in a hand", + SolveBoardResult.ThreadIndex => "Thread index is not 0 .. maximum", + SolveBoardResult.ModeWrongLo => "Mode parameter is less than 0", + SolveBoardResult.ModeWrongHi => "Mode parameter is higher than 2", + SolveBoardResult.TrumpWrong => "Trump is not in 0 .. 4", + SolveBoardResult.FirstWrong => "First is not in 0 .. 2", + SolveBoardResult.PlayFault => "AnalysePlay input error", + SolveBoardResult.PbnFault => "PBN string error", + SolveBoardResult.TooManyBoards => "Too many Boards requested", + SolveBoardResult.ThreadCreate => "Could not create threads", + SolveBoardResult.ThreadWait => "Something failed waiting for thread to end", + SolveBoardResult.ThreadMissing => "Tried to set a multi-threading system that is not present", + _ => "Unknown error" + }; + } +} diff --git a/dotnet/DDS_Core/DataModel/SolvedBoards.cs b/dotnet/DDS_Core/DataModel/SolvedBoards.cs index a9a74dcf..e4ea1f70 100644 --- a/dotnet/DDS_Core/DataModel/SolvedBoards.cs +++ b/dotnet/DDS_Core/DataModel/SolvedBoards.cs @@ -1,10 +1,20 @@ using System.Runtime.InteropServices; -using DDS_Core; +namespace DDS_Core; -[StructLayout(LayoutKind.Sequential, Pack = 1)] +/// +/// Solutions for multiple boards. +/// +/// Container for results from batch board solving operations. +/// Each entry contains the complete future tricks analysis for one board. +/// +[StructLayout(LayoutKind.Sequential)] public struct SolvedBoards { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] - public FutureTricks[] solvedBoard; + /// Number of solved boards. + public int no_of_boards; + + /// Array of solutions (future tricks for each board). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public FutureTricks[] solved_board; } diff --git a/dotnet/DDS_Core/DataModel/SolvedPlay.cs b/dotnet/DDS_Core/DataModel/SolvedPlay.cs new file mode 100644 index 00000000..563d4204 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolvedPlay.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + + +/// +/// Analyzed result of a play sequence. +/// +/// Contains tricks won for each possible play continuation. +/// +[StructLayout(LayoutKind.Sequential)] +public struct SolvedPlay +{ + /// Number of results. + public int number; + + /// Tricks possible after each play (53 entries). + public IntBuffer53 tricks; +} diff --git a/dotnet/DDS_Core/DataModel/SolvedPlays.cs b/dotnet/DDS_Core/DataModel/SolvedPlays.cs new file mode 100644 index 00000000..da85f923 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolvedPlays.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Analyzed results of multiple play sequences. +/// +[StructLayout(LayoutKind.Sequential)] +public struct SolvedPlays +{ + /// Number of solved plays. + public int no_of_boards; + + /// Array of solved play results (up to MAXNOOFBOARDS). + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] + public SolvedPlay[] solved; +} diff --git a/dotnet/DDS_Core/DataModel/ddTableDeal.cs b/dotnet/DDS_Core/DataModel/ddTableDeal.cs index 6b04a6a6..e6ad2afe 100644 --- a/dotnet/DDS_Core/DataModel/ddTableDeal.cs +++ b/dotnet/DDS_Core/DataModel/ddTableDeal.cs @@ -1,17 +1,22 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; +using static DDS_Core.ByteBuffer10x10; -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ddTableDeal - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public int[] trump; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public int[] first; +namespace DDS_Core; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public uint[] remainCards; - } +/// +/// A single deal for double dummy table calculation. +/// +/// Contains card distribution as bitmasks for efficient computation. +/// C++ type: unsigned int cards[DDS_HANDS][DDS_SUITS] = unsigned int cards[4][4] +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTableDeal +{ + /// + /// Cards for each hand and suit using 2D indexing. + /// Access: cards[hand, suit] where hand=0-3 (N/E/S/W), suit=0-3 (S/H/D/C) + /// Each uint is a bitmask of cards (bit 0 = Deuce, bit 12 = Ace) + /// + public UintBuffer4x4 cards; } diff --git a/dotnet/DDS_Core/DataModel/ddTableResults.cs b/dotnet/DDS_Core/DataModel/ddTableResults.cs index fc311dc1..791dff26 100644 --- a/dotnet/DDS_Core/DataModel/ddTableResults.cs +++ b/dotnet/DDS_Core/DataModel/ddTableResults.cs @@ -1,8 +1,19 @@ using System.Runtime.InteropServices; -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct ddTableResults +namespace DDS_Core; + +/// +/// Double dummy results for all strains and hands. +/// +/// Results indexed by strain (0-4: S/H/D/C/NT) and hand (0-3: N/E/S/W). +/// Each entry contains the number of tricks that hand can take in that strain. +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTableResults { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public int[] resTable; + /// + /// Tricks per strain and hand using 2D indexing. + /// Access: res_table[strain, hand] where strain=0-4, hand=0-3 + /// + public IntBuffer5x4 res_table; } diff --git a/dotnet/DDS_Core/DataModel/parResults.cs b/dotnet/DDS_Core/DataModel/parResults.cs index fb3b713a..fe7c0872 100644 --- a/dotnet/DDS_Core/DataModel/parResults.cs +++ b/dotnet/DDS_Core/DataModel/parResults.cs @@ -1,11 +1,25 @@ using System.Runtime.InteropServices; -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct parResults +namespace DDS_Core; + +/// +/// Par score and contracts for a single declarer/strain combination. +/// +/// Includes both NS and EW perspectives. +/// Index 0 = NS view, Index 1 = EW view. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct ParResults { - public int score_NS; - public int score_EW; + /// + /// Par score strings for NS and EW (2 entries, max 16 chars each). + /// Access: par_score[side, index] where side=0(NS) or 1(EW) + /// + public ByteBuffer2x16 par_score; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public int[] parScore; + /// + /// Par contract strings for NS and EW (2 entries, max 128 chars each). + /// Access: par_contracts_string[side, index] where side=0(NS) or 1(EW) + /// + public ByteBuffer2x128 par_contracts_string; } diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer100.cs b/dotnet/DDS_Core/Helpers/ByteBuffer100.cs new file mode 100644 index 00000000..5e79e7a1 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer100.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ByteBuffer100 +{ + public const int SIZE = 100; + + private fixed byte data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer106.cs b/dotnet/DDS_Core/Helpers/ByteBuffer106.cs new file mode 100644 index 00000000..766c3d01 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer106.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ByteBuffer106 + { + public const int SIZE = 106; + + private fixed byte data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs b/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs new file mode 100644 index 00000000..90bbae35 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace DDS_Core; + + +[StructLayout(LayoutKind.Sequential)] +public unsafe partial struct ByteBuffer10x10 +{ + public const int ROWS = 10; + public const int COLS = 10; + public const int SIZE = ROWS * COLS; + + public fixed byte data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs b/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs new file mode 100644 index 00000000..2d01bfaf --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ByteBuffer2x128 +{ + public const int ROWS = 2; + public const int COLS = 128; + public const int SIZE = ROWS * COLS; + + public fixed byte data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs b/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs new file mode 100644 index 00000000..adab0126 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ByteBuffer2x16 + { + public const int ROWS = 2; + public const int COLS = 16; + public const int SIZE = ROWS * COLS; + + public fixed byte data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer32.cs b/dotnet/DDS_Core/Helpers/ByteBuffer32.cs new file mode 100644 index 00000000..938e3532 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer32.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ByteBuffer32 +{ + public const int SIZE = 32; + + public fixed byte data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer80.cs b/dotnet/DDS_Core/Helpers/ByteBuffer80.cs new file mode 100644 index 00000000..b9e3c401 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ByteBuffer80.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ByteBuffer80 +{ + public const int SIZE = 80; + + private fixed byte data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/Grid1D.cs b/dotnet/DDS_Core/Helpers/Grid1D.cs new file mode 100644 index 00000000..e4eb76b9 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/Grid1D.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Generic 1D array wrapper for marshalling with bounds checking. +/// +public struct Grid1D where T : unmanaged +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0)] // Placeholder, will be overridden + private T[] data; + + private readonly int size; + + public Grid1D(int size) + { + if (size<=0) + throw new ArgumentException("Size must be positive", nameof(size)); + + this.size =size; + this.data =new T[size]; + } + + /// Gets or sets an element with bounds checking. + public ref T this[int index] + { + get + { + if (index<0||index>=size) + throw new IndexOutOfRangeException($"Index {index} out of range [0, {size-1}]"); + + return ref data[index]; + } + } + + /// Number of elements. + public int Length => size; + + /// Gets the underlying array as a span. + public Span AsSpan() => new(data, 0, size); +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer13.cs b/dotnet/DDS_Core/Helpers/IntBuffer13.cs new file mode 100644 index 00000000..648ad64d --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IntBuffer13.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IntBuffer13 + { + public const int SIZE = 13; + + public fixed int data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref int this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } + } +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer3.cs b/dotnet/DDS_Core/Helpers/IntBuffer3.cs new file mode 100644 index 00000000..55a85b00 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IntBuffer3.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct IntBuffer3 +{ + public const int SIZE = 3; + + public fixed int data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref int this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer52.cs b/dotnet/DDS_Core/Helpers/IntBuffer52.cs new file mode 100644 index 00000000..51964f36 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IntBuffer52.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct IntBuffer52 +{ + public const int SIZE = 52; + + public fixed int data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref int this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer53.cs b/dotnet/DDS_Core/Helpers/IntBuffer53.cs new file mode 100644 index 00000000..987df3cd --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IntBuffer53.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IntBuffer53 + { + public const int SIZE = 53; + + public fixed int data[SIZE]; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public ref int this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } + } +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs b/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs new file mode 100644 index 00000000..c5f7a70d --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IntBuffer5x4 + { + public const int ROWS = 5; + public const int COLS = 4; + public const int SIZE = ROWS * COLS; + + public fixed int data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref int start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public ref int this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + } +} + + diff --git a/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs b/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs new file mode 100644 index 00000000..5f57c0dd --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public struct ParResultsBuffer +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES)] + public ParResults[] Items; + + public void Init() + { + Items = new ParResults[DdsConstants.MAXNOOFTABLES]; + } +} + diff --git a/dotnet/DDS_Core/Helpers/UintBuffer4x4.cs b/dotnet/DDS_Core/Helpers/UintBuffer4x4.cs new file mode 100644 index 00000000..ce02d000 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/UintBuffer4x4.cs @@ -0,0 +1,64 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct UintBuffer4x4 +{ + public const int ROWS = 4; + public const int COLS = 4; + public const int SIZE = ROWS * COLS; + + private fixed uint data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref uint start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public ref uint this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + + + public static implicit operator UintBuffer4x4(uint[] src) + { + var buf = new UintBuffer4x4(); + + for (int c = 0; c < 16; c++) + buf.data[c] = src[c]; + + return buf; + } + + + public static implicit operator UintBuffer4x4(uint[][] src) + { + var buf = new UintBuffer4x4(); + + int k = 0; + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) + buf.data[k++] = src[r][c]; + + return buf; + } + + +} diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index dc0433e3..1981b587 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -1,67 +1,180 @@ using System; using System.Runtime.InteropServices; +using System.Text; using DDS_Core; namespace DDS_Core.Native; internal static class DdsNative { - [DllImport("dds_native")] - public static extern int SolveBoard( in Deal dl - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex); - - [DllImport("dds_native")] - public static extern int SolveAllBoards( in Boards boards - , out SolvedBoards solved + private const string DllName = "dds_native"; + + #region ====== Configuration and Resource Management ====== + [DllImport(DllName)] + public static extern void SetMaxThreads(int userThreads); + + [DllImport(DllName)] + public static extern int SetThreading(int code); + + [DllImport(DllName)] + public static extern void SetResources( int maxMemoryMB + , int maxThreads); + + [DllImport(DllName)] + public static extern void FreeMemory(); + #endregion + + #region ====== Single Board Solving ====== + [DllImport(DllName)] + public static extern int SolveBoard( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut , int threadIndex); - [DllImport("dds_native")] - public static extern int SolvePBN( [MarshalAs(UnmanagedType.LPStr)] string pbn - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex); - - [DllImport("dds_native")] - public static extern int CalcDDtable( ref ddTableDeal deal - , out ddTableResults table); - - [DllImport("dds_native")] - public static extern int CalcPar( ref ddTableResults table - , out parResults pres); - - internal static int SolveBoard_Wrapper( in Deal dl - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex) - => SolveBoard(in dl, target, solutions, mode, out fut, threadIndex); - - internal static int SolveAllBoards_Wrapper( in Boards boards - , out SolvedBoards solved - , int threadIndex) - => SolveAllBoards(in boards, out solved, threadIndex); - - internal static int SolvePBN_Wrapper( string pbn - , int target - , int solutions - , int mode - , out FutureTricks fut - , int threadIndex) - => SolvePBN(pbn, target, solutions, mode, out fut, threadIndex); - - internal static int CalcDDtable_Wrapper( ref ddTableDeal deal - , out ddTableResults table) - => CalcDDtable(ref deal, out table); - - internal static int CalcPar_Wrapper( ref ddTableResults table - , out parResults pres) - => CalcPar(ref table, out pres); + [DllImport(DllName)] + public static extern int SolveBoardPBN( in DealPBN dlpbn + , int target + , int solutions + , int mode + , out FutureTricks fut + , int thrId); + #endregion + + #region ====== Multiple Board Solving ====== + [DllImport(DllName)] + public static extern int SolveAllBoards( in Boards bop + , out SolvedBoards solved); + + [DllImport(DllName)] + public static extern int SolveAllBoardsBin( in Boards bop + , out SolvedBoards solved); + + [DllImport(DllName)] + public static extern int SolveAllChunks( in BoardsPBN bop + , out SolvedBoards solved + , int chunkSize); + + [DllImport(DllName)] + public static extern int SolveAllChunksBin( in Boards bop + , out SolvedBoards solved + , int chunkSize); + + [DllImport(DllName)] + public static extern int SolveAllChunksPBN( in BoardsPBN bop + , out SolvedBoards solved + , int chunkSize); + #endregion + + #region ====== Double Dummy Table Calculation ====== + [DllImport(DllName)] + public static extern int CalcDDtable( in DdTableDeal tableDeal + , out DdTableResults table); + + [DllImport(DllName)] + public static extern int CalcDDtablePBN( in DdTableDealPBN tableDealPBN + , out DdTableResults table); + + [DllImport(DllName)] + public static extern int CalcAllTables( in DdTableDeals dealsp + , int mode + , int[] trumpFilter //TODO: Tjekke denne + , out DdTablesRes resp + , out AllParResults presp); + + [DllImport(DllName)] + public static extern int CalcAllTablesPBN( in DdTableDealsPBN dealsp + , int mode + , int[] trumpFilter + , out DdTablesRes resp + , out AllParResults presp); + #endregion + + #region ====== Par Score Calculation ====== + [DllImport(DllName)] + public static extern int Par( in DdTableResults table + , out ParResults pres + , int vulnerable); + + [DllImport(DllName)] + public static extern int CalcPar( in DdTableDeal tableDeal + , int vulnerable + , out DdTableResults table + , out ParResults pres); + + [DllImport(DllName)] + public static extern int CalcParPBN( in DdTableDealPBN tableDealPBN + , out DdTableResults table + , int vulnerable + , out ParResults pres); + + [DllImport(DllName)] + public static extern int SidesPar( in DdTableResults table + , [Out] ParResultsDealer[] sidesRes + , int vulnerable); + + [DllImport(DllName)] + public static extern int DealerPar( in DdTableResults table + , out ParResultsDealer pres + , int dealer + , int vulnerable); + + [DllImport(DllName)] + public static extern int DealerParBin( in DdTableResults table + , out ParResultsMaster pres + , int dealer + , int vulnerable); + + [DllImport(DllName)] + public static extern int SidesParBin( in DdTableResults table + , [Out] ParResultsMaster[] sidesRes + , int vulnerable); + #endregion + + #region ====== Par Text Conversion ====== + [DllImport(DllName, CharSet = CharSet.Ansi)] + public static extern int ConvertToDealerTextFormat( in ParResultsMaster pres + , StringBuilder resp); //TODO: Tjek alle StringBuilder + + [DllImport(DllName)] + public static extern int ConvertToSidesTextFormat( in ParResultsMaster pres + , out ParTextResults resp); + #endregion + + #region ====== Play Analysis ====== + [DllImport(DllName)] + public static extern int AnalysePlayBin( in Deal dl + , in PlayTraceBin play + , out SolvedPlay solved + , int thrId); + + [DllImport(DllName, CharSet = CharSet.Ansi)] + public static extern int AnalysePlayPBN( in DealPBN dlPBN + , in PlayTracePBN playPBN + , out SolvedPlay solved + , int thrId); + + [DllImport(DllName)] + public static extern int AnalyseAllPlaysBin( in Boards bop + , in PlayTracesBin plp + , out SolvedPlays solved + , int chunkSize); + + [DllImport(DllName, CharSet = CharSet.Ansi)] + public static extern int AnalyseAllPlaysPBN( in BoardsPBN bopPBN + , in PlayTracesPBN plpPBN + , out SolvedPlays solved + , int chunkSize); + #endregion + + #region ====== Utility Functions ====== + [DllImport(DllName)] + public static extern void GetDDSInfo(out DDSInfo info); + + [DllImport(DllName, CharSet = CharSet.Ansi)] + public static extern void ErrorMessage( int code + , StringBuilder line); + #endregion } diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 2bda7ece..5163e7a8 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -5,56 +5,68 @@ namespace DDS_Core_Demo; internal class Program { - - static string[] contracts = ["spades","hearts", "diamonds", "clubs", "no trump" ]; + private static string[] contracts = ["spades","hearts", "diamonds", "clubs", "no trump" ]; static void Main(string[] args) { + uint[][] hands = [ new uint[] + { + (uint)(rJ|r6|r5|r2) + , (uint)(rA|r7|r4) + , (uint)None + , (uint)(rK|rT|r9|r6|r4|r2) + } + + , new uint[] + { + (uint)(rA|rT|r9|r8|r7|r3) + , (uint)(rK|rJ|r5) + , (uint)(rT|r8|r2) + , (uint)r8 + } + + , new uint[] + { + (uint)r4 + , (uint)(rQ|rT|r9|r8|r2) + , (uint)(rA|rK|rQ|r9|r3) + , (uint)(rA|rJ) + } + + , new uint[] + { + (uint)(rK|rQ) + , (uint)(r6|r3) + , (uint)(rJ|r7|r6|r5|r4) + , (uint)(rQ|r7|r5|r3) + } + + ]; + var dds = new DDS(); - var deal = new Deal() + var deal = new Deal { trump = (int)Suit.Hearts , first = 0 , currentTrickSuit = new int[3] {0, 0, 0 } , currentTrickRank = new int[3] {0, 0, 0 } - , remainCards = new uint[16] - { - (uint)(Jack|n6|n5|n2) - , (uint)(Ace|n7|n4) - , (uint)(None) - , (uint)(King|Ten|n9|n6|n4|n2) - // - , (uint)(Ace|Ten|n9|n8|n7|n3) - , (uint)(King|Jack|n5) - , (uint)(Ten|n8|n2) - , (uint)(n8) - // - , (uint)(n4) - , (uint)(Queen|Ten|n9|n8|n2) - , (uint)(Ace|King|Queen|n9|n3) - , (uint)(Ace|Jack) - // - , (uint)(King|Queen) - , (uint)(n6|n3) - , (uint)(Jack|n7|n6|n5|n4) - , (uint)(Queen|n7|n5|n3) - } + , remainCards = hands }; - - for (deal.trump = 0; deal.trump < 5; deal.trump++) - for (deal.first = 0; deal.first < 4; deal.first++) + // SolveBoard: Loop through all possible contracts and first players + for (deal.trump = 0; deal.trump < 5; deal.trump++) + for (deal.first = 0; deal.first < 4; deal.first++) { - var rc=dds.SolveBoard(deal, -1, 1, 2, out FutureTricks fut); - var deci = (deal.first+3)&3; - var decl = "NESW"[deci]; - var cont = contracts[deal.trump]; - - Console.WriteLine($"Tricks in {cont} for {decl}: {13 - fut.score[0]} "); - } - Console.WriteLine($"Press any key to continue..."); + var rc =dds.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); + var deci = (deal.first+3)&3; + var decl = "NESW"[deci]; + var cont = contracts[deal.trump]; + Console.WriteLine($"Tricks in {cont} for {decl}: {13 - fut.score[0]} "); + } + Console.WriteLine($"Press any key to continue..."); Console.ReadKey(); + return; } } diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props index 4d87bb66..eaaf5149 100644 --- a/dotnet/Directory.Build.props +++ b/dotnet/Directory.Build.props @@ -13,7 +13,7 @@ - + $(DefineConstants); RELEASE; true none From 386a1bd5c8c954ce0baecf5656311bb35d5cfd97 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:19:33 +0200 Subject: [PATCH 026/157] All relevant methods implemented and tested. --- .gitignore | 2 + dotnet/DDS_Core/DDS.cs | 484 +++++++--- dotnet/DDS_Core/DDS_Core.csproj | 14 + dotnet/DDS_Core/DataModel/AllParResults.cs | 13 +- dotnet/DDS_Core/DataModel/Boards.cs | 47 +- dotnet/DDS_Core/DataModel/BoardsPBN.cs | 46 +- dotnet/DDS_Core/DataModel/CardRanks.cs | 22 - dotnet/DDS_Core/DataModel/ContractType.cs | 10 +- dotnet/DDS_Core/DataModel/DDSInfo.cs | 28 +- dotnet/DDS_Core/DataModel/DdTableDealPBN.cs | 3 +- dotnet/DDS_Core/DataModel/DdTableDeals.cs | 39 +- dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs | 37 +- dotnet/DDS_Core/DataModel/DdTablesRes.cs | 42 - dotnet/DDS_Core/DataModel/DdTablesResult.cs | 69 ++ dotnet/DDS_Core/DataModel/Deal.cs | 18 +- dotnet/DDS_Core/DataModel/DealPBN.cs | 12 +- .../Enums and Constants/CardRanks.cs | 21 + .../{ => Enums and Constants}/DdsConstants.cs | 12 +- .../{ => Enums and Constants}/Hand.cs | 0 .../SolveBoardResult.cs | 0 .../{ => Enums and Constants}/SolvingMode.cs | 0 .../{ => Enums and Constants}/Suit.cs | 0 .../DataModel/Enums and Constants/TTKind.cs | 7 + dotnet/DDS_Core/DataModel/FutureTricks.cs | 12 +- dotnet/DDS_Core/DataModel/Hands.cs | 37 - dotnet/DDS_Core/DataModel/ParResultsDealer.cs | 33 +- .../DDS_Core/DataModel/ParResultsDealers.cs | 43 + dotnet/DDS_Core/DataModel/ParResultsMaster.cs | 36 +- .../DDS_Core/DataModel/ParResultsMasters.cs | 43 + dotnet/DDS_Core/DataModel/ParTextResults.cs | 4 +- dotnet/DDS_Core/DataModel/PlayTraceBin.cs | 13 +- dotnet/DDS_Core/DataModel/PlayTracePBN.cs | 9 +- dotnet/DDS_Core/DataModel/PlayTracesBin.cs | 13 +- dotnet/DDS_Core/DataModel/PlayTracesPBN.cs | 14 +- dotnet/DDS_Core/DataModel/SolvedBoards.cs | 37 +- dotnet/DDS_Core/DataModel/SolvedPlay.cs | 4 +- dotnet/DDS_Core/DataModel/SolvedPlays.cs | 37 +- dotnet/DDS_Core/DataModel/SolverConfig.cs | 23 + dotnet/DDS_Core/DataModel/SolverContext.cs | 155 ++++ dotnet/DDS_Core/DataModel/ddTableDeal.cs | 3 +- dotnet/DDS_Core/DataModel/ddTableResults.cs | 2 +- dotnet/DDS_Core/DataModel/parResults.cs | 5 +- dotnet/DDS_Core/Helpers/ByteBuffer100.cs | 25 - dotnet/DDS_Core/Helpers/ByteBuffer106.cs | 26 - dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs | 41 - dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs | 39 - dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs | 40 - dotnet/DDS_Core/Helpers/ByteBuffer32.cs | 25 - dotnet/DDS_Core/Helpers/ByteBuffer80.cs | 25 - .../{UintBuffer4x4.cs => FourHands.cs} | 44 +- dotnet/DDS_Core/Helpers/Grid1D.cs | 42 - dotnet/DDS_Core/Helpers/IBuffer.cs | 7 + dotnet/DDS_Core/Helpers/IntBuffer13.cs | 26 - dotnet/DDS_Core/Helpers/IntBuffer3.cs | 25 - dotnet/DDS_Core/Helpers/IntBuffer52.cs | 25 - dotnet/DDS_Core/Helpers/IntBuffer53.cs | 26 - dotnet/DDS_Core/Helpers/IntBuffer5x4.cs | 41 - dotnet/DDS_Core/Helpers/ParResultsBuffer.cs | 17 - dotnet/DDS_Core/Helpers/ResultsTable.cs | 58 ++ .../SolveBoardResultExtensions.cs | 2 +- .../DDS_Core/Helpers/SolverContextHandle.cs | 23 + dotnet/DDS_Core/Helpers/intArray10x10.cs | 92 ++ .../DDS_Core/Helpers/intArray10xDebugView.cs | 28 + dotnet/DDS_Core/Helpers/intArray13.cs | 31 + dotnet/DDS_Core/Helpers/intArray200.cs | 31 + dotnet/DDS_Core/Helpers/intArray2x128.cs | 91 ++ dotnet/DDS_Core/Helpers/intArray2x16.cs | 92 ++ .../DDS_Core/Helpers/intArray2xDebugView.cs | 16 + dotnet/DDS_Core/Helpers/intArray3.cs | 31 + dotnet/DDS_Core/Helpers/intArray5.cs | 32 + dotnet/DDS_Core/Helpers/intArray52.cs | 31 + dotnet/DDS_Core/Helpers/intArray53.cs | 32 + dotnet/DDS_Core/Helpers/string106.cs | 86 ++ dotnet/DDS_Core/Helpers/string80.cs | 86 ++ dotnet/DDS_Core/Native/DdsNative.cs | 171 +++- .../DDS_Core/Properties/launchSettings.json | 8 + dotnet/DDS_Core_Demo/Program.cs | 864 ++++++++++++++++-- dotnet/DDS_Core_Demo/TestData.cs | 224 +++++ library/src/api/calc_dd_table.hpp | 4 +- library/src/api/calc_par.hpp | 2 +- library/src/api/dds_api.hpp | 89 ++ library/src/solver_context/solver_context.cpp | 3 +- solution/dds_native.vcxproj | 9 +- solution/dds_native.vcxproj.filters | 89 +- 84 files changed, 3169 insertions(+), 979 deletions(-) delete mode 100644 dotnet/DDS_Core/DataModel/CardRanks.cs delete mode 100644 dotnet/DDS_Core/DataModel/DdTablesRes.cs create mode 100644 dotnet/DDS_Core/DataModel/DdTablesResult.cs create mode 100644 dotnet/DDS_Core/DataModel/Enums and Constants/CardRanks.cs rename dotnet/DDS_Core/DataModel/{ => Enums and Constants}/DdsConstants.cs (68%) rename dotnet/DDS_Core/DataModel/{ => Enums and Constants}/Hand.cs (100%) rename dotnet/DDS_Core/DataModel/{ => Enums and Constants}/SolveBoardResult.cs (100%) rename dotnet/DDS_Core/DataModel/{ => Enums and Constants}/SolvingMode.cs (100%) rename dotnet/DDS_Core/DataModel/{ => Enums and Constants}/Suit.cs (100%) create mode 100644 dotnet/DDS_Core/DataModel/Enums and Constants/TTKind.cs delete mode 100644 dotnet/DDS_Core/DataModel/Hands.cs create mode 100644 dotnet/DDS_Core/DataModel/ParResultsDealers.cs create mode 100644 dotnet/DDS_Core/DataModel/ParResultsMasters.cs create mode 100644 dotnet/DDS_Core/DataModel/SolverConfig.cs create mode 100644 dotnet/DDS_Core/DataModel/SolverContext.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer100.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer106.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer32.cs delete mode 100644 dotnet/DDS_Core/Helpers/ByteBuffer80.cs rename dotnet/DDS_Core/Helpers/{UintBuffer4x4.cs => FourHands.cs} (51%) delete mode 100644 dotnet/DDS_Core/Helpers/Grid1D.cs create mode 100644 dotnet/DDS_Core/Helpers/IBuffer.cs delete mode 100644 dotnet/DDS_Core/Helpers/IntBuffer13.cs delete mode 100644 dotnet/DDS_Core/Helpers/IntBuffer3.cs delete mode 100644 dotnet/DDS_Core/Helpers/IntBuffer52.cs delete mode 100644 dotnet/DDS_Core/Helpers/IntBuffer53.cs delete mode 100644 dotnet/DDS_Core/Helpers/IntBuffer5x4.cs delete mode 100644 dotnet/DDS_Core/Helpers/ParResultsBuffer.cs create mode 100644 dotnet/DDS_Core/Helpers/ResultsTable.cs rename dotnet/DDS_Core/{DataModel => Helpers}/SolveBoardResultExtensions.cs (98%) create mode 100644 dotnet/DDS_Core/Helpers/SolverContextHandle.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray10x10.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray10xDebugView.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray13.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray200.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray2x128.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray2x16.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray2xDebugView.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray3.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray5.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray52.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray53.cs create mode 100644 dotnet/DDS_Core/Helpers/string106.cs create mode 100644 dotnet/DDS_Core/Helpers/string80.cs create mode 100644 dotnet/DDS_Core/Properties/launchSettings.json create mode 100644 dotnet/DDS_Core_Demo/TestData.cs create mode 100644 library/src/api/dds_api.hpp diff --git a/.gitignore b/.gitignore index 9835121c..a990e18a 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,5 @@ doxygen_output/ .cce/ # Claude Code local settings written by cce init .claude/settings.local.json + +/dotnet/DDS_Core/.github/copilot-instructions.md diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index d340980a..ec278835 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -1,6 +1,7 @@ -using System.Runtime.InteropServices; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Text; -using DDS_Core; +using DDS_Core.Helpers; using DDS_Core.Native; namespace DDS_Core; @@ -8,10 +9,50 @@ namespace DDS_Core; public class DDS { #region ====== Configuration and Resource Management ====== + /// + /// Sets the maximum number of threads used by the solver. + /// + /// + /// + /// Deprecated. In the modern C++ API, thread count is controlled by the + /// embedding application — typically by creating one SolverContext + /// instance per worker thread. + /// + /// + /// New code should create and destroy SolverContext instances in the + /// application rather than calling this function. See docs/api_migration.md + /// for examples of the modern API. + /// + /// + /// This function is part of the legacy C API and is maintained for backward + /// compatibility. It has no direct equivalent in the modern API, where both + /// threading and transposition‑table memory limits are configured per instance + /// via SolverContext and SolverConfig. + /// + /// + /// Maximum number of threads to use. + [Obsolete("Use SolverContext instead.")] public void SetMaxThreads(int userThreads) - => DdsNative.SetMaxThreads(userThreads); - - public int SetThreading(int code) + => DdsNative.SetMaxThreads(userThreads); + + /// + /// Sets the threading backend used by the solver. + /// + /// + /// + /// Deprecated. Use SolverContext instead — threading is implicit + /// (one context per thread). See docs/api_migration.md for modern C++ API examples. + /// + /// + /// This function is part of the legacy C API and is maintained for backward + /// compatibility. The modern C++ API does not require explicit threading + /// configuration; instead, create one SolverContext instance per thread. + /// + /// + /// Threading backend code (see documentation). + /// 1 on success, error code otherwise. + [Obsolete("Use SolverContext instead.")] + public int SetThreading(in int code) { var rc = DdsNative.SetThreading( code); @@ -19,22 +60,65 @@ public int SetThreading(int code) return rc; } - public void SetResources(int maxMemoryMB, int maxThreads) - => DdsNative.SetResources(maxMemoryMB, maxThreads); - - public void FreeMemory() - => DdsNative.FreeMemory(); + /// + /// Sets memory and thread resources for the solver. + /// + /// + /// + /// Deprecated. Use SolverContext with SolverConfig instead. + /// See docs/api_migration.md for modern C++ API examples. + /// + /// + /// This function is part of the legacy C API and is maintained for backward + /// compatibility. New code should use the modern C++ API with SolverContext, + /// which provides per‑instance configuration through SolverConfig. + /// + /// + /// Maximum memory in megabytes. + /// Maximum number of threads. + [Obsolete("Use SolverContext instead.")] + public void SetResources(in int maxMemoryMB, in int maxThreads) + + => DdsNative.SetResources(maxMemoryMB, maxThreads); + + /// + /// Frees memory used by the solver. + /// + /// + /// + /// Deprecated. Use SolverContext RAII instead — cleanup is automatic. + /// See docs/api_migration.md for modern C++ API examples. + /// + /// + /// This function is part of the legacy C API and is maintained for backward + /// compatibility. The modern C++ API uses RAII (Resource Acquisition Is + /// Initialization) through SolverContext, which automatically cleans up + /// resources when the context goes out of scope. + /// + /// + //[Obsolete("Use SolverContext instead.")] + public void FreeMemory() => DdsNative.FreeMemory(); #endregion #region ====== Single Board Solving ====== + /// + /// Solves a single bridge Deal using double dummy analysis. + /// + /// The deal to analyze. + /// Target number of tricks. + /// Solution mode (1 = best, 2 = all, etc.). + /// Analysis mode. + /// The result. + /// Index of the thread to use. + /// 1 on success, error code otherwise. public int SolveBoard( in Deal dl - , int target - , int solutions - , int mode + , in int target + , in int solutions + , in int mode , out FutureTricks fut , int threadIndex = 0) { - var rc = DdsNative.SolveBoard( in dl + var rc = DdsNative.SolveBoard( dl , target , solutions , mode @@ -45,14 +129,25 @@ public int SolveBoard( in Deal dl return rc; } - public int SolveBoardPBN( DealPBN pbn - , int target - , int solutions - , int mode + /// + /// Solves a single bridge deal in PBN format using double dummy analysis. + /// + /// The PBN deal to analyze. + /// Target number of tricks. + /// Solution mode. + /// Analysis mode. + /// The result. + /// Index of the thread to use. + /// 1 on success, error code otherwise. + [Obsolete("Use SolverContext instead.")] + public int SolveBoardPBN( in DealPBN pbn + , in int target + , in int solutions + , in int mode , out FutureTricks fut - , int threadIndex) + , int threadIndex = 0) { - var rc = DdsNative.SolveBoardPBN( pbn + var rc = DdsNative.SolveBoardPBN( pbn , target , solutions , mode @@ -65,21 +160,36 @@ public int SolveBoardPBN( DealPBN pbn #endregion #region ====== Multiple Board Solving ====== - public int SolveAllBoards( Boards boards + /// + /// Solves multiple bridge deals in PBN format. + /// + /// Multiple PBN deals. + /// The results for solved boards. + /// 1 on success, error code otherwise. + public int SolveAllBoards( in BoardsPBN boards , out SolvedBoards solved) { - var rc = DdsNative.SolveAllBoards( in boards + solved = default; + //Note: To step into c++ code you must set a break in c++? + var rc = DdsNative.SolveAllBoards( boards , out solved); ThrowIfError(rc, nameof(SolveAllBoards)); return rc; } + /// + /// Solves multiple bridge deals in binary format. + /// + /// Multiple deals. + /// The results for solved boards. + /// 1 on success, error code otherwise. + [Obsolete("Use SolverContext instead.")] public int SolveAllBoardsBin( in Boards bop - , out SolvedBoards solved - ) + , out SolvedBoards solved) { - var rc = DdsNative.SolveAllBoardsBin( in bop + //Note: To step into c++ code you must set a break in c++? + var rc = DdsNative.SolveAllBoardsBin( bop , out solved); ThrowIfError(rc, nameof(SolveAllBoardsBin)); @@ -88,9 +198,10 @@ public int SolveAllBoardsBin( in Boards bop public int SolveAllChunks( in BoardsPBN bop , out SolvedBoards solved - , int chunkSize) + , in int chunkSize) { - var rc = DdsNative.SolveAllChunks( in bop + //Note: To step into c++ code you must set a break in c++? + var rc = DdsNative.SolveAllChunks( bop , out solved , chunkSize); @@ -100,9 +211,10 @@ public int SolveAllChunks( in BoardsPBN bop public int SolveAllChunksBin( in Boards bop , out SolvedBoards solved - , int chunkSize) + , in int chunkSize) { - var rc = DdsNative.SolveAllChunksBin( in bop + //Note: To step into c++ code you must set a break in c++? + var rc = DdsNative.SolveAllChunksBin( bop , out solved , chunkSize); ThrowIfError(rc, nameof(SolveAllChunksBin)); @@ -111,9 +223,10 @@ public int SolveAllChunksBin( in Boards bop public int SolveAllChunksPBN( in BoardsPBN bop , out SolvedBoards solved - , int chunkSize) + , in int chunkSize) { - var rc = DdsNative.SolveAllChunksPBN( in bop + //Note: To step into c++ code you must set a break in c++? + var rc = DdsNative.SolveAllChunksPBN( bop , out solved , chunkSize); ThrowIfError(rc, nameof(SolveAllChunksPBN)); @@ -122,53 +235,83 @@ public int SolveAllChunksPBN( in BoardsPBN bop #endregion #region ====== Double Dummy Table Calculation ====== - public int CalcDDTable( DdTableDeal deal + /// + /// Calculates the double dummy table for a given deal. + /// + /// Deal for which to calculate the table. + /// The result table. + /// 1 on success, error code otherwise. + public int CalcDdTable( in DdTableDeal deal , out DdTableResults table) { - var rc = DdsNative.CalcDDtable( in deal + var rc = DdsNative.CalcDDtable( deal , out table); - ThrowIfError(rc, nameof(CalcDDTable)); + ThrowIfError(rc, nameof(CalcDdTable)); return rc; } - public int CalcDDtablePBN( in DdTableDealPBN tableDealPBN + /// + /// Calculates the double dummy table for a PBN deal. + /// + /// PBN deal for which to calculate the table. + /// The result table. + /// 1 on success, error code otherwise. + public int CalcDdTablePBN( in DdTableDealPBN tableDealPBN , out DdTableResults table) { - var rc = DdsNative.CalcDDtablePBN( in tableDealPBN + var rc = DdsNative.CalcDDtablePBN( tableDealPBN , out table); - ThrowIfError(rc, nameof(CalcDDtablePBN)); + ThrowIfError(rc, nameof(CalcDdTablePBN)); return rc; } - public int CalcAllTables( in DdTableDeals dealsp - , int mode - , int[] trumpFilter //TODO: Tjekke denne - , out DdTablesRes resp - , out AllParResults presp) + /// + /// Calculates double dummy tables for multiple deals. + /// + /// Multiple deals. + /// Analysis mode. + /// Array of trump suit filters. + /// The result tables. + /// The par results. + /// 1 on success, error code otherwise. + public int CalcAllTables( in DdTableDeals deals + , in int mode + , in intArray5 trumpFilter + , out DdTablesResult resTables + , out AllParResults parResults) { - var rc = DdsNative.CalcAllTables( in dealsp + var rc = DdsNative.CalcAllTables( deals , mode , trumpFilter - , out resp - , out presp); + , out resTables + , out parResults); ThrowIfError(rc, nameof(CalcAllTables)); return rc; } + /// + /// Calculates double dummy tables for multiple PBN deals. + /// + /// Multiple PBN deals. + /// Analysis mode. + /// Array of trump suit filters. + /// The result tables. + /// The par results. + /// 1 on success, error code otherwise. public int CalcAllTablesPBN( in DdTableDealsPBN dealsp - , int mode - , int[] trumpFilter - , out DdTablesRes resp - , out AllParResults presp) + , in int mode + , in intArray5 trumpFilter + , out DdTablesResult ResTables + , out AllParResults parResults) { - var rc = DdsNative.CalcAllTablesPBN( in dealsp + var rc = DdsNative.CalcAllTablesPBN( dealsp , mode , trumpFilter - , out resp - , out presp); + , out ResTables + , out parResults); ThrowIfError(rc, nameof(CalcAllTablesPBN)); return rc; @@ -178,9 +321,9 @@ public int CalcAllTablesPBN( in DdTableDealsPBN dealsp #region ====== Par Score Calculation ====== public int Par( in DdTableResults table , out ParResults pres - , int vulnerable) + , in int vulnerable) { - var rc = DdsNative.Par( in table + var rc = DdsNative.Par( table , out pres , vulnerable); @@ -188,39 +331,97 @@ public int Par( in DdTableResults table return rc; } + /// + /// Calculates par score and contracts for a deal table. + /// + /// + /// + /// Computes the double dummy table for the given deal, then calculates par score + /// and contracts based on vulnerability. This overload creates a temporary + /// SolverContext internally. + /// + /// + /// This function is equivalent to calling CalcDDtable followed by + /// Par in the legacy C API. + /// + /// + /// + /// Deal represented as card holdings for each hand. + /// + /// + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// + /// + /// Output: double dummy table results. + /// + /// + /// Output: par score and contract strings. + /// + /// + /// Error code (RETURN_NO_FAULT on success). + /// public int CalcPar( in DdTableDeal tableDeal - , int vulnerable - , out DdTableResults table - , out ParResults pres) + , in int vulnerable + , out DdTableResults tableResults + , out ParResults parResults) { - var rc = DdsNative.CalcPar( in tableDeal + var rc = DdsNative.CalcPar( tableDeal , vulnerable - , out table - , out pres); + , out tableResults + , out parResults); ThrowIfError(rc, nameof(CalcPar)); return rc; } + /// + /// Calculates par score and contracts for a deal table in PBN format. + /// + /// + /// + /// Computes the double dummy table for the given deal, then calculates par score + /// and contracts based on vulnerability. This overload creates a temporary + /// SolverContext internally. + /// + /// + /// This function is equivalent to calling CalcDDtable followed by + /// Par in the legacy C API. + /// + /// + /// + /// Deal represented as card holdings for each hand. + /// + /// + /// Output: double dummy table results. + /// + /// /// + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// + /// + /// Output: par score and contract strings. + /// + /// + /// Error code (RETURN_NO_FAULT on success). + /// public int CalcParPBN( in DdTableDealPBN tableDealPBN - , out DdTableResults table - , int vulnerable - , out ParResults pres) + , out DdTableResults tableResults + , in int vulnerable + , out ParResults parResults) { - var rc = DdsNative.CalcParPBN( in tableDealPBN - , out table + var rc = DdsNative.CalcParPBN( tableDealPBN + , out tableResults , vulnerable - , out pres); + , out parResults); ThrowIfError(rc, nameof(CalcParPBN)); return rc; } public int SidesPar( in DdTableResults table - , [Out] ParResultsDealer[] sidesRes - , int vulnerable) + , out ParResultsDealers sidesRes + , in int vulnerable) { - var rc = DdsNative.SidesPar( in table - , sidesRes + var rc = DdsNative.SidesPar( table + , out sidesRes , vulnerable); ThrowIfError(rc, nameof(SidesPar)); @@ -229,10 +430,10 @@ public int SidesPar( in DdTableResults table public int DealerPar( in DdTableResults table , out ParResultsDealer pres - , int dealer - , int vulnerable) + , in int dealer + , in int vulnerable) { - var rc = DdsNative.DealerPar( in table + var rc = DdsNative.DealerPar( table , out pres , dealer , vulnerable); @@ -243,10 +444,10 @@ public int DealerPar( in DdTableResults table public int DealerParBin( in DdTableResults table , out ParResultsMaster pres - , int dealer - , int vulnerable) + , in int dealer + , in int vulnerable) { - var rc = DdsNative.DealerParBin( in table + var rc = DdsNative.DealerParBin( table , out pres , dealer , vulnerable); @@ -256,11 +457,11 @@ public int DealerParBin( in DdTableResults table } public int SidesParBin( in DdTableResults table - , [Out] ParResultsMaster[] sidesRes - , int vulnerable) + , out ParResultsMasters sidesRes + , in int vulnerable) { - var rc = DdsNative.SidesParBin( in table - , sidesRes + var rc = DdsNative.SidesParBin( table + , out sidesRes , vulnerable); ThrowIfError(rc, nameof(SidesParBin)); @@ -270,18 +471,20 @@ public int SidesParBin( in DdTableResults table #region ====== Par Text Conversion ====== public int ConvertToDealerTextFormat( in ParResultsMaster pres - , StringBuilder resp) + , out string resp) { - var rc = DdsNative.ConvertToDealerTextFormat( in pres - , resp); + var str = new StringBuilder(80); + var rc = DdsNative.ConvertToDealerTextFormat( pres + , str); ThrowIfError(rc, nameof(ConvertToDealerTextFormat)); + resp = str.ToString(); return rc; } - public int ConvertToSidesTextFormat( in ParResultsMaster pres + public int ConvertToSidesTextFormat( in ParResultsMasters pres , out ParTextResults resp) { - var rc = DdsNative.ConvertToSidesTextFormat( in pres + var rc = DdsNative.ConvertToSidesTextFormat( pres , out resp); ThrowIfError(rc, nameof(ConvertToSidesTextFormat)); return rc; @@ -292,9 +495,9 @@ public int ConvertToSidesTextFormat( in ParResultsMaster pres public int AnalysePlayBin( in Deal dl , in PlayTraceBin play , out SolvedPlay solved - , int thrId) + , in int thrId) { - var rc = DdsNative.AnalysePlayBin( in dl + var rc = DdsNative.AnalysePlayBin( dl , in play , out solved , thrId); @@ -305,9 +508,9 @@ public int AnalysePlayBin( in Deal dl public int AnalysePlayPBN( in DealPBN dlPBN , in PlayTracePBN playPBN , out SolvedPlay solved - , int thrId) + , in int thrId) { - var rc = DdsNative.AnalysePlayPBN( in dlPBN + var rc = DdsNative.AnalysePlayPBN( dlPBN , in playPBN , out solved , thrId); @@ -318,45 +521,118 @@ public int AnalysePlayPBN( in DealPBN dlPBN public int AnalyseAllPlaysBin( in Boards bop , in PlayTracesBin plp , out SolvedPlays solved - , int chunkSize) + , in int chunkSize) { - var rc = DdsNative.AnalyseAllPlaysBin( in bop - , in plp - , out solved - , chunkSize); - ThrowIfError(rc, nameof(AnalyseAllPlaysBin)); - return rc; + try + { + solved = new(); + + var rc = DdsNative.AnalyseAllPlaysBin( bop + , plp + , out solved + , chunkSize); + + ThrowIfError(rc, nameof(AnalyseAllPlaysBin)); + return rc; + } + + catch (Exception ex) + { + Debug.WriteLine(ex); + throw ex; + } } public int AnalyseAllPlaysPBN( in BoardsPBN bopPBN , in PlayTracesPBN plpPBN , out SolvedPlays solved - , int chunkSize) + , in int chunkSize) { - var rc = DdsNative.AnalyseAllPlaysPBN( in bopPBN - , in plpPBN + solved = new(); + var rc = DdsNative.AnalyseAllPlaysPBN( bopPBN + , plpPBN , out solved , chunkSize); ThrowIfError(rc, nameof(AnalyseAllPlaysPBN)); return rc; } + + ///// + ///// Benchmark variant: AnalyseAllPlaysBin with ref parameters for performance testing. + ///// Used to compare marshalling overhead between 'in' and 'ref' parameter passing. + ///// + //public int AnalyseAllPlaysBin_BenchRef( ref Boards bop + // , in PlayTracesBin plp + // , out SolvedPlays solved + // , int chunkSize) + //{ + // try + // { + // solved = new(); + + // var rc = DdsNative.AnalyseAllPlaysBin_Ref( ref bop + // , in plp + // , in solved + // , chunkSize); + + // ThrowIfError(rc, nameof(AnalyseAllPlaysBin_BenchRef)); + // return rc; + // } + + // catch (Exception ex) + // { + // Debug.WriteLine(ex); + // throw ex; + // } + //} + + ///// + ///// Benchmark variant: AnalyseAllPlaysBin with ref parameters for performance testing. + ///// Used to compare marshalling overhead between 'in' and 'ref' parameter passing. + ///// + //public int AnalyseAllPlaysBin_Bench3( Boards bop + // , PlayTracesBin plp + // , out SolvedPlays solved + // , int chunkSize) + //{ + // try + // { + // solved = new(); + + // var rc = DdsNative.AnalyseAllPlaysBin_Ref( ref bop + // , in plp + // , in solved + // , chunkSize); + + // ThrowIfError(rc, nameof(AnalyseAllPlaysBin_BenchRef)); + // return rc; + // } + + // catch (Exception ex) + // { + // Debug.WriteLine(ex); + // throw ex; + // } + //} #endregion #region ====== Utility Functions ====== - public void GetDDSInfo(out DDSInfo info) + public void GetDDSInfo(out DdsInfo info) { DdsNative.GetDDSInfo(out info); } - public void ErrorMessage( int code - , StringBuilder line) + public void ErrorMessage( in int code + , out string line) { - DdsNative.ErrorMessage(code, line); + var str = new StringBuilder(80); + DdsNative.ErrorMessage(code, str); + line = str.ToString(); } #endregion #region private methods - private static void ThrowIfError(int result, string functionName) + private static void ThrowIfError(in int result, in string functionName) { #if DEBUG if (result != (int)SolveBoardResult.NoFault) diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj index c0b582be..2f35b124 100644 --- a/dotnet/DDS_Core/DDS_Core.csproj +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -12,7 +12,21 @@ + + + + + + + + + + + + + + diff --git a/dotnet/DDS_Core/DataModel/AllParResults.cs b/dotnet/DDS_Core/DataModel/AllParResults.cs index 5da2fa9b..e80eb242 100644 --- a/dotnet/DDS_Core/DataModel/AllParResults.cs +++ b/dotnet/DDS_Core/DataModel/AllParResults.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace DDS_Core; @@ -10,6 +11,14 @@ namespace DDS_Core; public struct AllParResults { /// Array of par results (up to MAXNOOFTABLES entries). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES)] - public ParResults[] par_results; + public ParResultsArray ParResults; + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfTables)] + public struct ParResultsArray + { + private ParResults item; + } + #endregion + } diff --git a/dotnet/DDS_Core/DataModel/Boards.cs b/dotnet/DDS_Core/DataModel/Boards.cs index 8a5f1c4c..a0ec5852 100644 --- a/dotnet/DDS_Core/DataModel/Boards.cs +++ b/dotnet/DDS_Core/DataModel/Boards.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -9,21 +10,47 @@ namespace DDS_Core; public struct Boards { /// Number of deals in this batch. - public int no_of_boards; + public int NumberOfBoards; /// Array of deals. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public Deal[] deals; + public DealsArray Deals; /// Target tricks for each board. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] target; + public intArray200 Target; /// Solution mode for each board (1=best, 2=all, 3=all+par). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] solutions; + + public intArray200 Solutions; /// Solve mode for each board. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] mode; + public intArray200 Modes; + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfBoards)] + public struct DealsArray + { + private Deal item; + + public static implicit operator DealsArray(Deal[] array) + { + var result = new DealsArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from DealsArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfBoards); + } + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/BoardsPBN.cs b/dotnet/DDS_Core/DataModel/BoardsPBN.cs index 3f72c690..9c0b5088 100644 --- a/dotnet/DDS_Core/DataModel/BoardsPBN.cs +++ b/dotnet/DDS_Core/DataModel/BoardsPBN.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -12,21 +13,46 @@ namespace DDS_Core; public struct BoardsPBN { /// Number of boards to solve. - public int no_of_boards; + public int NumberOfBoards; /// Array of deals in PBN format. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public DealPBN[] deals; + public DealsPBNArray Deals; /// Target tricks for each board. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] target; + public intArray200 Target; /// Solution mode for each board. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] solutions; + public intArray200 Solutions; /// Solve mode for each board. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public int[] mode; + public intArray200 Modes; + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfBoards)] + public struct DealsPBNArray + { + private DealPBN item; + + public static implicit operator DealsPBNArray(DealPBN[] array) + { + var result = new DealsPBNArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from DealsPBNArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfBoards); + } + #endregion + } } diff --git a/dotnet/DDS_Core/DataModel/CardRanks.cs b/dotnet/DDS_Core/DataModel/CardRanks.cs deleted file mode 100644 index 644bd000..00000000 --- a/dotnet/DDS_Core/DataModel/CardRanks.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace DDS_Core -{ - public enum CardRanks : uint - { -None - , rA = 0x00000001 <<14 - , rK = 0x00000001 <<13 - , rQ = 0x00000001 <<12 - , rJ = 0x00000001 <<11 - , rT = 0x00000001 <<10 - , r9 = 0x00000001 <<9 - , r8 = 0x00000001 <<8 - , r7 = 0x00000001 <<7 - , r6 = 0x00000001 <<6 - , r5 = 0x00000001 <<5 - , r4 = 0x00000001 <<4 - , r3 = 0x00000001 <<3 - , r2 = 0x00000001 <<2 - - , All = 0x00007FFC // All ranks (except None) - }; -} diff --git a/dotnet/DDS_Core/DataModel/ContractType.cs b/dotnet/DDS_Core/DataModel/ContractType.cs index bdf9883b..0b341ddf 100644 --- a/dotnet/DDS_Core/DataModel/ContractType.cs +++ b/dotnet/DDS_Core/DataModel/ContractType.cs @@ -10,17 +10,17 @@ namespace DDS_Core; public struct ContractType { /// Under tricks (0 = make, 1-13 = sacrifice). - public int under_tricks; + public int UnderTricks; /// Over tricks (0-3, e.g., 1 for 4S + 1). - public int over_tricks; + public int OverTricks; /// Contract level (1-7). - public int level; + public int Level; /// Denomination (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). - public int denom; + public int Denomination; /// Seats playing contract (0 = N, 1 = E, 2 = S, 3 = W, 4 = NS, 5 = EW). - public int seats; + public int Seats; } diff --git a/dotnet/DDS_Core/DataModel/DDSInfo.cs b/dotnet/DDS_Core/DataModel/DDSInfo.cs index c524a099..b654b7b5 100644 --- a/dotnet/DDS_Core/DataModel/DDSInfo.cs +++ b/dotnet/DDS_Core/DataModel/DDSInfo.cs @@ -9,47 +9,49 @@ namespace DDS_Core; /// Contains version, platform, compiler, threading, and resource information. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] -public struct DDSInfo +public struct DdsInfo { /// Major version number (e.g., 3 for 3.0.0). - public int major; + public int Major; /// Minor version number. - public int minor; + public int Minor; /// Patch version number. - public int patch; + public int Patch; /// Version string (e.g., "3.0.0"), max 10 characters. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] - public string version_string; + public readonly string VersionString; /// Platform (0 = unknown, 1 = Windows, 2 = Cygwin, 3 = Linux, 4 = macOS). - public int system; + public int System; /// Bit width (32 or 64). - public int numBits; + public int NumberOfBits; /// Compiler (0 = unknown, 1 = MSVC, 2 = mingw, 3 = GCC, 4 = clang). - public int compiler; + public int Compiler; /// Constructor type (0 = none, 1 = DllMain, 2 = Unix-style). public int constructor; /// Number of processor cores available. - public int numCores; + public int NumberOfCores; /// Threading system (0 = none, 1 = Windows, 2 = OpenMP, 3 = GCD, 4 = Boost, 5 = STL, 6 = TBB, etc.). - public int threading; + public int Threading; /// Actual number of threads configured. - public int noOfThreads; + public int NumberOfThreads; /// Thread memory sizes configuration string (max 128 characters). [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string threadSizes; + public readonly string ThreadSizes; /// Detailed system information string (max 1024 characters). [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] - public string systemString; + public readonly string SystemString; + + } diff --git a/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs b/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs index f92fc4af..7aa217fc 100644 --- a/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs +++ b/dotnet/DDS_Core/DataModel/DdTableDealPBN.cs @@ -11,6 +11,5 @@ namespace DDS_Core; public struct DdTableDealPBN { /// Cards in PBN format (max 80 characters). - - public ByteBuffer80 cards; + public string80 Cards; } diff --git a/dotnet/DDS_Core/DataModel/DdTableDeals.cs b/dotnet/DDS_Core/DataModel/DdTableDeals.cs index d1c2f235..cfaaffd5 100644 --- a/dotnet/DDS_Core/DataModel/DdTableDeals.cs +++ b/dotnet/DDS_Core/DataModel/DdTableDeals.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -9,9 +10,39 @@ namespace DDS_Core; public struct DdTableDeals { /// Number of tables. - public int no_of_tables; + public int NumberOfTables; /// Array of deals (up to MAXNOOFTABLES * DDS_STRAINS). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] - public DdTableDeal[] deals; + public DdTableDealsArray Deals; + + + #region Nested Types + + [InlineArray(DdsConstants.DdsStrains)] + public struct DdTableDealsArray + { + private DdTableDeal item; + + public static implicit operator DdTableDealsArray(DdTableDeal[] array) + { + var result = new DdTableDealsArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from DdTableDealsArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.DdsStrains); + } + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs index bb8da951..a8bfee15 100644 --- a/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs +++ b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -9,9 +10,37 @@ namespace DDS_Core; public struct DdTableDealsPBN { /// Number of tables. - public int no_of_tables; + public int NumberOfTables; /// Array of PBN deals (up to MAXNOOFTABLES * DDS_STRAINS). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] - public DdTableDealPBN[] deals; + public DdTableDealsPBNArray Deals; + #region Nested Types + + [InlineArray(DdsConstants.DdsStrains)] + public struct DdTableDealsPBNArray + { + private DdTableDealPBN item; + + public static implicit operator DdTableDealsPBNArray(DdTableDealPBN[] array) + { + var result = new DdTableDealsPBNArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from DdTableDealsPBNArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.DdsStrains); + } + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/DdTablesRes.cs b/dotnet/DDS_Core/DataModel/DdTablesRes.cs deleted file mode 100644 index 8d880c4e..00000000 --- a/dotnet/DDS_Core/DataModel/DdTablesRes.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -//TODO: Tjek denne - -/// -/// Multiple double dummy table results. -/// -[StructLayout(LayoutKind.Sequential)] -public struct DdTablesRes -{ - /// Number of boards. - public int no_of_boards; - - /// Array of results (up to MAXNOOFTABLES * DDS_STRAINS = 200). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES * DdsConstants.DDS_STRAINS)] - public DdTableResults[] results; - - /// - /// Safe indexer with bounds checking against actual no_of_boards. - /// - public DdTableResults this[int index] - { - get - { - if (index < 0 || index >= no_of_boards) - throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); - - return results[index]; - } - - set - { - if (index < 0 || index >= no_of_boards) - throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); - - results[index] = value; - } - } -} diff --git a/dotnet/DDS_Core/DataModel/DdTablesResult.cs b/dotnet/DDS_Core/DataModel/DdTablesResult.cs new file mode 100644 index 00000000..800897c8 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/DdTablesResult.cs @@ -0,0 +1,69 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Multiple double dummy table results. +/// +[StructLayout(LayoutKind.Sequential)] +public struct DdTablesResult +{ + /// Number of boards. + public int no_of_boards; + + /// Array of results (up to MAXNOOFTABLES * DDS_STRAINS = 200). + public DdTableResultsArray Results; + + /// + /// Safe indexer with bounds checking against actual no_of_boards. + /// + public DdTableResults this[int index] + { + get + { + if (index < 0 || index >= no_of_boards) + throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); + + return Results[index]; + } + + set + { + if (index < 0 || index >= no_of_boards) + throw new IndexOutOfRangeException($"Index {index} out of range [0, {no_of_boards - 1}]"); + + Results[index] = value; + } + } + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfTables * DdsConstants.DdsStrains)] + public struct DdTableResultsArray + { + private DdTableResults item; + + public static implicit operator DdTableResultsArray(DdTableResults[] array) + { + var result = new DdTableResultsArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from DdTableResultsArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfTables * DdsConstants.DdsStrains); + } + } + #endregion +} diff --git a/dotnet/DDS_Core/DataModel/Deal.cs b/dotnet/DDS_Core/DataModel/Deal.cs index 481f3eba..d84d7fc1 100644 --- a/dotnet/DDS_Core/DataModel/Deal.cs +++ b/dotnet/DDS_Core/DataModel/Deal.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using static DDS_Core.ByteBuffer10x10; +using DDS_Core.Helpers; namespace DDS_Core; @@ -15,20 +16,19 @@ namespace DDS_Core; public struct Deal { /// Trump suit (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). - public int trump; + public int Trump; /// Hand to play first (0 = N, 1 = E, 2 = S, 3 = W). - public int first; + public int First; /// Suits of cards played in the current trick (3 entries). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickSuit; + public intArray3 CurrentTrickSuit; /// Ranks of cards played in the current trick (3 entries). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickRank; + public intArray3 CurrentTrickRank; /// Remaining cards for each hand and suit (4 hands × 4 suits = 16 elements). - //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public UintBuffer4x4 remainCards; + public FourHands RemainingCards; + + } diff --git a/dotnet/DDS_Core/DataModel/DealPBN.cs b/dotnet/DDS_Core/DataModel/DealPBN.cs index f84133b7..73841432 100644 --- a/dotnet/DDS_Core/DataModel/DealPBN.cs +++ b/dotnet/DDS_Core/DataModel/DealPBN.cs @@ -12,19 +12,17 @@ namespace DDS_Core; public struct DealPBN { /// Trump suit (0 = NT, 1 = Spades, 2 = Hearts, 3 = Diamonds, 4 = Clubs). - public int trump; + public int Trump; /// Hand to play first (0 = N, 1 = E, 2 = S, 3 = W). - public int first; + public int First; /// Suits of cards played in the current trick (3 entries). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickSuit; + public intArray3 CurrentTrickSuit; /// Ranks of cards played in the current trick (3 entries). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public int[] currentTrickRank; + public intArray3 CurrentTrickRank; /// PBN string describing remaining cards (max 80 characters). - public ByteBuffer80 remainCards; + public string80 RemainingCards; } diff --git a/dotnet/DDS_Core/DataModel/Enums and Constants/CardRanks.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/CardRanks.cs new file mode 100644 index 00000000..bc9a9e67 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Enums and Constants/CardRanks.cs @@ -0,0 +1,21 @@ +namespace DDS_Core; + +public enum CardRanks : uint +{ + None = 0 + , rA = 0x00000001 <<14 + , rK = 0x00000001 <<13 + , rQ = 0x00000001 <<12 + , rJ = 0x00000001 <<11 + , rT = 0x00000001 <<10 + , r9 = 0x00000001 <<9 + , r8 = 0x00000001 <<8 + , r7 = 0x00000001 <<7 + , r6 = 0x00000001 <<6 + , r5 = 0x00000001 <<5 + , r4 = 0x00000001 <<4 + , r3 = 0x00000001 <<3 + , r2 = 0x00000001 <<2 + , All = 0x00007FFC // All ranks (except None) +}; + diff --git a/dotnet/DDS_Core/DataModel/DdsConstants.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/DdsConstants.cs similarity index 68% rename from dotnet/DDS_Core/DataModel/DdsConstants.cs rename to dotnet/DDS_Core/DataModel/Enums and Constants/DdsConstants.cs index 4f6ef007..7fefc915 100644 --- a/dotnet/DDS_Core/DataModel/DdsConstants.cs +++ b/dotnet/DDS_Core/DataModel/Enums and Constants/DdsConstants.cs @@ -8,21 +8,21 @@ namespace DDS_Core public static class DdsConstants { /// Number of bridge strains (4 suits + no trump). - public const int DDS_STRAINS = 5; + public const int DdsStrains = 5; /// Number of hands (N/E/S/W). - public const int DDS_HANDS = 4; + public const int DdsHands = 4; /// Number of suits (S/H/D/C). - public const int DDS_SUITS = 4; + public const int DdsSuits = 4; /// No trump strain index. - public const int DDS_NOTRUMP = 4; + public const int DdsNoTrump = 4; /// Maximum number of boards in batch operations. - public const int MAXNOOFBOARDS = 200; + public const int MaxNumberOfBoards = 200; /// Maximum number of DD tables. - public const int MAXNOOFTABLES = 40; + public const int MaxNumberOfTables = 40; } } diff --git a/dotnet/DDS_Core/DataModel/Hand.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/Hand.cs similarity index 100% rename from dotnet/DDS_Core/DataModel/Hand.cs rename to dotnet/DDS_Core/DataModel/Enums and Constants/Hand.cs diff --git a/dotnet/DDS_Core/DataModel/SolveBoardResult.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/SolveBoardResult.cs similarity index 100% rename from dotnet/DDS_Core/DataModel/SolveBoardResult.cs rename to dotnet/DDS_Core/DataModel/Enums and Constants/SolveBoardResult.cs diff --git a/dotnet/DDS_Core/DataModel/SolvingMode.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/SolvingMode.cs similarity index 100% rename from dotnet/DDS_Core/DataModel/SolvingMode.cs rename to dotnet/DDS_Core/DataModel/Enums and Constants/SolvingMode.cs diff --git a/dotnet/DDS_Core/DataModel/Suit.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/Suit.cs similarity index 100% rename from dotnet/DDS_Core/DataModel/Suit.cs rename to dotnet/DDS_Core/DataModel/Enums and Constants/Suit.cs diff --git a/dotnet/DDS_Core/DataModel/Enums and Constants/TTKind.cs b/dotnet/DDS_Core/DataModel/Enums and Constants/TTKind.cs new file mode 100644 index 00000000..80e6e0ba --- /dev/null +++ b/dotnet/DDS_Core/DataModel/Enums and Constants/TTKind.cs @@ -0,0 +1,7 @@ +namespace DDS_Core; + +public enum TTKind : int +{ + Small = 0, + Large = 1 +} diff --git a/dotnet/DDS_Core/DataModel/FutureTricks.cs b/dotnet/DDS_Core/DataModel/FutureTricks.cs index 8c608ef8..bdadfa8d 100644 --- a/dotnet/DDS_Core/DataModel/FutureTricks.cs +++ b/dotnet/DDS_Core/DataModel/FutureTricks.cs @@ -13,20 +13,20 @@ namespace DDS_Core; public struct FutureTricks { /// Number of nodes searched in the analysis. - public int nodes; + public int Nodes; /// Number of cards in the results. - public int cards; + public int NumberOfCards; /// Suit of each card (13 entries). - public IntBuffer13 suit; + public intArray13 Suit; /// Rank of each card (13 entries). - public IntBuffer13 rank; + public intArray13 Ranks; /// Equality group for each card (13 entries). - public IntBuffer13 equals; + public intArray13 EqualGroups; /// Score for each card (13 entries). - public IntBuffer13 score; + public intArray13 Score; } diff --git a/dotnet/DDS_Core/DataModel/Hands.cs b/dotnet/DDS_Core/DataModel/Hands.cs deleted file mode 100644 index dadc9b94..00000000 --- a/dotnet/DDS_Core/DataModel/Hands.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -//TODO: Tjek denne - -/// -/// 2D view of the four hands (ie cards) as [hand, suit] indexing. -/// -[StructLayout(LayoutKind.Sequential)] -public struct Hands -{ - /// - /// Cards for each hand and suit (4 hands × 4 suits = 16 elements). - /// Stored as flattened array but accessed via 2D indexer. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - private uint[] cards; - - /// Gets or sets card by [hand, suit] indexing. - /// Hand index (0=N, 1=E, 2=S, 3=W) - /// Suit index (0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs) - public ref uint this[int hand, int suit] - { - get - { - if (hand < 0 || hand >= 4) - throw new ArgumentOutOfRangeException(nameof(hand), "Hand must be 0-3"); - - if (suit < 0 || suit >= 4) - throw new ArgumentOutOfRangeException(nameof(suit), "Suit must be 0-3"); - - return ref cards[hand * 4 + suit]; - } - } -} diff --git a/dotnet/DDS_Core/DataModel/ParResultsDealer.cs b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs index cafda75f..b5d0a584 100644 --- a/dotnet/DDS_Core/DataModel/ParResultsDealer.cs +++ b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs @@ -1,24 +1,23 @@ using System; +using System.Drawing; using System.Runtime.InteropServices; -namespace DDS_Core +namespace DDS_Core; + +/// +/// Par result for a specific dealer. +/// +/// Contains number of par contracts and their details. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct ParResultsDealer { - /// - /// Par result for a specific dealer. - /// - /// Contains number of par contracts and their details. - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct ParResultsDealer - { - /// Number of contracts yielding the par score. - public int number; + /// Number of contracts yielding the par score. + public int NumberOfContracts; - /// Par score for the specified dealer hand. - public int score; + /// Par score for the specified dealer hand. + public int Score; - /// Par contract text strings (10 entries, max 10 chars each). - //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] - public ByteBuffer100 contracts; - } + /// Par contract text strings (10 entries, max 10 chars each). + public intArray10x10 Contracts; } diff --git a/dotnet/DDS_Core/DataModel/ParResultsDealers.cs b/dotnet/DDS_Core/DataModel/ParResultsDealers.cs new file mode 100644 index 00000000..519b9151 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ParResultsDealers.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + + /// + /// ParResultsDealer[2] + /// + /// Contains number of par contracts and their details. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct ParResultsDealers + { + private ParResultsDealer parResultsDealersNS; + private ParResultsDealer parResultsDealersEW; + + public ParResultsDealer this[int index] + { + get + { + if ((uint)index >= 2) + throw new IndexOutOfRangeException(); + + if (index == 0) + return parResultsDealersNS; + else + return parResultsDealersEW; + } + + set + { + if ((uint)index >= 2) + throw new IndexOutOfRangeException(); + + if (index == 0) + parResultsDealersNS = value; + else + parResultsDealersEW = value; + } + } + } +} diff --git a/dotnet/DDS_Core/DataModel/ParResultsMaster.cs b/dotnet/DDS_Core/DataModel/ParResultsMaster.cs index 9adbbd3c..bd44019e 100644 --- a/dotnet/DDS_Core/DataModel/ParResultsMaster.cs +++ b/dotnet/DDS_Core/DataModel/ParResultsMaster.cs @@ -1,23 +1,29 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace DDS_Core +namespace DDS_Core; + +/// +/// Par contracts for both dealer and vulnerable combinations. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ParResultsMaster { + /// Par score (sign according to NS view). + public int Score; - /// - /// Par contracts for both dealer and vulnerable combinations. - /// - [StructLayout(LayoutKind.Sequential)] - public struct ParResultsMaster - { - /// Par score (sign according to NS view). - public int score; + /// Number of contracts giving the par score. + public int Number; - /// Number of contracts giving the par score. - public int number; + /// InlineArray of 10 par contracts. + public ContractTypes Contracts; - /// Array of par contracts (up to 10). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] - public ContractType[] contracts; - } + #region Nested Types + [InlineArray(10)] + public struct ContractTypes + { + private ContractType Contract; + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/ParResultsMasters.cs b/dotnet/DDS_Core/DataModel/ParResultsMasters.cs new file mode 100644 index 00000000..98bec397 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/ParResultsMasters.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace DDS_Core +{ + + /// + /// Par contracts for both dealer and vulnerable combinations. + /// + [StructLayout(LayoutKind.Sequential)] + + public struct ParResultsMasters + { + /// InlineArray of 10 par contracts. + private ParResultsMaster parResultsMaster0; + private ParResultsMaster parResultsMaster1; + + public ParResultsMaster this[int index] + { + get + { + if ((uint)index >= 2) + throw new IndexOutOfRangeException(); + + if (index == 0) + return parResultsMaster0; + else + return parResultsMaster1; + } + + set + { + if ((uint)index >= 2) + throw new IndexOutOfRangeException(); + + if (index == 0) + parResultsMaster0 = value; + else + parResultsMaster1 = value; + } + } + } +} diff --git a/dotnet/DDS_Core/DataModel/ParTextResults.cs b/dotnet/DDS_Core/DataModel/ParTextResults.cs index 7670ae20..08d5e340 100644 --- a/dotnet/DDS_Core/DataModel/ParTextResults.cs +++ b/dotnet/DDS_Core/DataModel/ParTextResults.cs @@ -14,9 +14,9 @@ public struct ParTextResults /// Short par text for NS and EW using 2D indexing (2 x 128 chars). /// Access: par_text[side, index] where side=0(NS) or 1(EW) /// - public ByteBuffer2x128 par_text; + public intArray2x128 ParTextStrings; /// True if equal (doesn't matter who starts bidding), false otherwise. [MarshalAs(UnmanagedType.I1)] - public bool equal; + public bool IsEqual; } diff --git a/dotnet/DDS_Core/DataModel/PlayTraceBin.cs b/dotnet/DDS_Core/DataModel/PlayTraceBin.cs index 6149b8dc..44a30efb 100644 --- a/dotnet/DDS_Core/DataModel/PlayTraceBin.cs +++ b/dotnet/DDS_Core/DataModel/PlayTraceBin.cs @@ -1,9 +1,9 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace DDS_Core; - /// /// Binary play trace (cards played in sequence). /// @@ -13,11 +13,12 @@ namespace DDS_Core; public struct PlayTraceBin { /// Number of cards played (1-52). - public int number; + public int NumberOfCards; + + /// Suit of each card played (52 plays max). + public intArray52 Suits; - /// Suit of each played card (52 entries). - public IntBuffer52 suit; + /// Rank of each card played (52 plays max). + public intArray52 Ranks; - /// Rank of each played card (52 entries). - public IntBuffer52 rank; } diff --git a/dotnet/DDS_Core/DataModel/PlayTracePBN.cs b/dotnet/DDS_Core/DataModel/PlayTracePBN.cs index 410a4b87..9a196627 100644 --- a/dotnet/DDS_Core/DataModel/PlayTracePBN.cs +++ b/dotnet/DDS_Core/DataModel/PlayTracePBN.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -11,9 +12,9 @@ namespace DDS_Core; public struct PlayTracePBN { /// Number of cards played. - public int number; + public int NumberOfPlayedCards; /// Cards in PBN format (max 106 characters). - //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 106)] - public ByteBuffer106 cards; + public string106 Cards; + } diff --git a/dotnet/DDS_Core/DataModel/PlayTracesBin.cs b/dotnet/DDS_Core/DataModel/PlayTracesBin.cs index 014c97e9..4652c8ea 100644 --- a/dotnet/DDS_Core/DataModel/PlayTracesBin.cs +++ b/dotnet/DDS_Core/DataModel/PlayTracesBin.cs @@ -1,9 +1,9 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace DDS_Core; - /// /// Multiple binary play traces for batch analysis. /// @@ -11,9 +11,14 @@ namespace DDS_Core; public struct PlayTracesBin { /// Number of boards. - public int no_of_boards; + public int NumberOfBoards; /// Array of play traces (up to MAXNOOFBOARDS). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public PlayTraceBin[] plays; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MaxNumberOfBoards)] + public PlayTraceBin[] Plays; + + public PlayTracesBin() + { + Plays = new PlayTraceBin[DdsConstants.MaxNumberOfBoards]; + } } diff --git a/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs b/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs index db628626..daaaa7f3 100644 --- a/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs +++ b/dotnet/DDS_Core/DataModel/PlayTracesPBN.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -9,9 +10,14 @@ namespace DDS_Core; public struct PlayTracesPBN { /// Number of boards. - public int no_of_boards; + public int NumberOfBoards; /// Array of PBN play traces (up to MAXNOOFBOARDS). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public PlayTracePBN[] plays; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MaxNumberOfBoards)] + public PlayTracePBN[] Plays; + + public PlayTracesPBN() + { + Plays = new PlayTracePBN[DdsConstants.MaxNumberOfBoards]; + } } diff --git a/dotnet/DDS_Core/DataModel/SolvedBoards.cs b/dotnet/DDS_Core/DataModel/SolvedBoards.cs index e4ea1f70..096e2c8f 100644 --- a/dotnet/DDS_Core/DataModel/SolvedBoards.cs +++ b/dotnet/DDS_Core/DataModel/SolvedBoards.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace DDS_Core; @@ -12,9 +13,37 @@ namespace DDS_Core; public struct SolvedBoards { /// Number of solved boards. - public int no_of_boards; + public int NumberOfBoards; /// Array of solutions (future tricks for each board). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public FutureTricks[] solved_board; + public FutureTricksArray Tricks; + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfBoards)] + public struct FutureTricksArray + { + private FutureTricks item; + + public static implicit operator FutureTricksArray(FutureTricks[] array) + { + var result = new FutureTricksArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from FutureTricksArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfBoards); + } + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/SolvedPlay.cs b/dotnet/DDS_Core/DataModel/SolvedPlay.cs index 563d4204..4fbc9045 100644 --- a/dotnet/DDS_Core/DataModel/SolvedPlay.cs +++ b/dotnet/DDS_Core/DataModel/SolvedPlay.cs @@ -13,8 +13,8 @@ namespace DDS_Core; public struct SolvedPlay { /// Number of results. - public int number; + public int NumberOfResults; /// Tricks possible after each play (53 entries). - public IntBuffer53 tricks; + public IntArray53 Tricks; } diff --git a/dotnet/DDS_Core/DataModel/SolvedPlays.cs b/dotnet/DDS_Core/DataModel/SolvedPlays.cs index da85f923..42a42eb3 100644 --- a/dotnet/DDS_Core/DataModel/SolvedPlays.cs +++ b/dotnet/DDS_Core/DataModel/SolvedPlays.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace DDS_Core; @@ -10,9 +11,39 @@ namespace DDS_Core; public struct SolvedPlays { /// Number of solved plays. - public int no_of_boards; + public int NumberOfPlayedBoards; /// Array of solved play results (up to MAXNOOFBOARDS). - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFBOARDS)] - public SolvedPlay[] solved; + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MaxNumberOfBoards)] + //public SolvedPlay[] Solved; + public SolvedPlayArray Solved; + + #region Nested Types + [InlineArray(DdsConstants.MaxNumberOfBoards)] + public struct SolvedPlayArray + { + private SolvedPlay item; + + public static implicit operator SolvedPlayArray(SolvedPlay[] array) + { + var result = new SolvedPlayArray(); + + if (array != null) + { + var span = result.AsSpan(); + + for (int i = 0; i < Math.Min(array.Length, span.Length); i++) + span[i] = array[i]; + } + + return result; + } + + // Implicit conversion from SolvedPlayArray to Span + private Span AsSpan() + { + return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfBoards); + } + } + #endregion } diff --git a/dotnet/DDS_Core/DataModel/SolverConfig.cs b/dotnet/DDS_Core/DataModel/SolverConfig.cs new file mode 100644 index 00000000..5aa94353 --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolverConfig.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +[StructLayout(LayoutKind.Sequential)] +public struct SolverConfig +{ + public TTKind TTKind; + public int DefaultMemoryMB; + public int MaximumMemoryMB; + + public SolverConfig() + { + TTKind = TTKind.Large; + } + + public SolverConfig(TTKind tTKind, int defaultMemoryMB, int maximumMemoryMB) + { + TTKind = tTKind; + DefaultMemoryMB = defaultMemoryMB; + MaximumMemoryMB = maximumMemoryMB; + } +} diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs new file mode 100644 index 00000000..881e506c --- /dev/null +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using DDS_Core.Helpers; +using DDS_Core.Native; + +namespace DDS_Core; + +public sealed class SolverContext : IDisposable +{ + public SolverContextHandle Handle { get; } + + #region Constructors and destructors + public SolverContext() + { + Handle = DdsNative.dds_create_solvercontext_default() + ?? throw new InvalidOperationException("Failed to create SolverContext."); + } + + public SolverContext(SolverConfig config) + { + Handle = DdsNative.dds_create_solvercontext(config) + ?? throw new InvalidOperationException("Failed to create SolverContext."); + } + + public void Dispose() + { + Handle?.Dispose(); + GC.SuppressFinalize(this); + } + #endregion + + #region TT Management + public void ConfigureTT(TTKind kind, int defaultMb, int maxMb) + => DdsNative.dds_configure_tt(Handle, kind, defaultMb, maxMb); + + public void ResizeTT(int defaultMb, int maxMb) + => DdsNative.dds_resize_tt(Handle, defaultMb, maxMb); + + public void ClearTT() + => DdsNative.dds_clear_tt(Handle); + + public void ResetForSolve() + => DdsNative.dds_reset_for_solve(Handle); + + public void ResetBestMovesLite() + => DdsNative.dds_reset_best_moves_lite(Handle); + #endregion + + #region Solving + public int SolveBoard( in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut) + { + var rc = DdsNative.dds_solve_board( Handle + , dl + , target + , solutions + , mode + , out fut); + + ThrowIfError(rc, nameof(SolveBoard)); + return rc; + } + + public int CalcDdTable( in DdTableDeal table_deal + , out DdTableResults table_results) + { + var rc = DdsNative.calc_dd_table( Handle + , in table_deal + , out table_results); + + ThrowIfError(rc, nameof(SolveBoard)); + return rc; + } + + public int CalcDdTablePBN( in DdTableDealPBN table_deal_pbn + , out DdTableResults table_results) + { + var rc = DdsNative.calc_dd_table_pbn( Handle + , in table_deal_pbn + , out table_results); + + ThrowIfError(rc, nameof(CalcDdTablePBN)); + return rc; + } + + /// +/// Calculates par score and contracts for a deal table. +/// +/// +/// +/// Computes the double dummy table for the given deal, then calculates par score +/// and contracts based on vulnerability. +/// +/// +/// This function is equivalent to calling CalcDDtable followed by +/// Par in the legacy C API. +/// +/// +/// +/// Deal represented as card holdings for each hand. +/// +/// +/// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). +/// +/// +/// Output: double dummy table results. +/// +/// +/// Output: par score and contract strings. +/// +/// +/// Error code (RETURN_NO_FAULT on success). +/// + + public int CalcPar( in DdTableDeal table_deal + , int vulnerable + , out DdTableResults table_results + , out ParResults par_results) + { + var rc = DdsNative.calc_par( Handle + , in table_deal + , vulnerable + , out table_results + , out par_results); + ThrowIfError(rc, nameof(CalcPar)); + return rc; + } + #endregion + + #region Logging + public void LogAppend(string message) + => DdsNative.dds_log_append(Handle, message); + + public void LogClear() + => DdsNative.dds_log_clear(Handle); + #endregion + + #region private methods + private static void ThrowIfError(int result, string functionName) + { +#if DEBUG + if (result != (int)SolveBoardResult.NoFault) + throw new InvalidOperationException($"{functionName} failed with code {result}: {result.GetRCErrorMessage()}"); +#endif + } + #endregion +} + diff --git a/dotnet/DDS_Core/DataModel/ddTableDeal.cs b/dotnet/DDS_Core/DataModel/ddTableDeal.cs index e6ad2afe..d983e13f 100644 --- a/dotnet/DDS_Core/DataModel/ddTableDeal.cs +++ b/dotnet/DDS_Core/DataModel/ddTableDeal.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using static DDS_Core.ByteBuffer10x10; namespace DDS_Core; @@ -18,5 +17,5 @@ public struct DdTableDeal /// Access: cards[hand, suit] where hand=0-3 (N/E/S/W), suit=0-3 (S/H/D/C) /// Each uint is a bitmask of cards (bit 0 = Deuce, bit 12 = Ace) /// - public UintBuffer4x4 cards; + public FourHands Cards; } diff --git a/dotnet/DDS_Core/DataModel/ddTableResults.cs b/dotnet/DDS_Core/DataModel/ddTableResults.cs index 791dff26..1750d54e 100644 --- a/dotnet/DDS_Core/DataModel/ddTableResults.cs +++ b/dotnet/DDS_Core/DataModel/ddTableResults.cs @@ -15,5 +15,5 @@ public struct DdTableResults /// Tricks per strain and hand using 2D indexing. /// Access: res_table[strain, hand] where strain=0-4, hand=0-3 /// - public IntBuffer5x4 res_table; + public ResultsTable ResultsTable; } diff --git a/dotnet/DDS_Core/DataModel/parResults.cs b/dotnet/DDS_Core/DataModel/parResults.cs index fe7c0872..8d8cafa5 100644 --- a/dotnet/DDS_Core/DataModel/parResults.cs +++ b/dotnet/DDS_Core/DataModel/parResults.cs @@ -15,11 +15,12 @@ public struct ParResults /// Par score strings for NS and EW (2 entries, max 16 chars each). /// Access: par_score[side, index] where side=0(NS) or 1(EW) /// - public ByteBuffer2x16 par_score; + public intArray2x16 ParScores; + /// /// Par contract strings for NS and EW (2 entries, max 128 chars each). /// Access: par_contracts_string[side, index] where side=0(NS) or 1(EW) /// - public ByteBuffer2x128 par_contracts_string; + public intArray2x128 ParContractStrings; } diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer100.cs b/dotnet/DDS_Core/Helpers/ByteBuffer100.cs deleted file mode 100644 index 5e79e7a1..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer100.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ByteBuffer100 -{ - public const int SIZE = 100; - - private fixed byte data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref byte this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer106.cs b/dotnet/DDS_Core/Helpers/ByteBuffer106.cs deleted file mode 100644 index 766c3d01..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer106.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct ByteBuffer106 - { - public const int SIZE = 106; - - private fixed byte data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref byte this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs b/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs deleted file mode 100644 index 90bbae35..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer10x10.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; - -namespace DDS_Core; - - -[StructLayout(LayoutKind.Sequential)] -public unsafe partial struct ByteBuffer10x10 -{ - public const int ROWS = 10; - public const int COLS = 10; - public const int SIZE = ROWS * COLS; - - public fixed byte data[SIZE]; - - public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public Span RowAsSpan(int row) - { - if ((uint)row >= ROWS) - throw new ArgumentOutOfRangeException(nameof(row)); - - // ref to first byte in the array for the specified row - ref byte start = ref data[row * COLS]; - - return MemoryMarshal.CreateSpan(ref start, COLS); - } - - public ref byte this[int row, int col] - { - get - { - if ((uint)row >= ROWS || (uint)col >= COLS) - throw new IndexOutOfRangeException(); - - return ref data[row * COLS + col]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs b/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs deleted file mode 100644 index 2d01bfaf..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer2x128.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ByteBuffer2x128 -{ - public const int ROWS = 2; - public const int COLS = 128; - public const int SIZE = ROWS * COLS; - - public fixed byte data[SIZE]; - - public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public Span RowAsSpan(int row) - { - if ((uint)row >= ROWS) - throw new ArgumentOutOfRangeException(nameof(row)); - - // ref to first byte in the array for the specified row - ref byte start = ref data[row * COLS]; - - return MemoryMarshal.CreateSpan(ref start, COLS); - } - - public ref byte this[int row, int col] - { - get - { - if ((uint)row >= ROWS || (uint)col >= COLS) - throw new IndexOutOfRangeException(); - - return ref data[row * COLS + col]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs b/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs deleted file mode 100644 index adab0126..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer2x16.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct ByteBuffer2x16 - { - public const int ROWS = 2; - public const int COLS = 16; - public const int SIZE = ROWS * COLS; - - public fixed byte data[SIZE]; - - public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public Span RowAsSpan(int row) - { - if ((uint)row >= ROWS) - throw new ArgumentOutOfRangeException(nameof(row)); - - // ref to first byte in the array for the specified row - ref byte start = ref data[row * COLS]; - - return MemoryMarshal.CreateSpan(ref start, COLS); - } - - public ref byte this[int row, int col] - { - get - { - if ((uint)row >= ROWS || (uint)col >= COLS) - throw new IndexOutOfRangeException(); - - return ref data[row * COLS + col]; - } - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer32.cs b/dotnet/DDS_Core/Helpers/ByteBuffer32.cs deleted file mode 100644 index 938e3532..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer32.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ByteBuffer32 -{ - public const int SIZE = 32; - - public fixed byte data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref byte this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/ByteBuffer80.cs b/dotnet/DDS_Core/Helpers/ByteBuffer80.cs deleted file mode 100644 index b9e3c401..00000000 --- a/dotnet/DDS_Core/Helpers/ByteBuffer80.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ByteBuffer80 -{ - public const int SIZE = 80; - - private fixed byte data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref byte this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/UintBuffer4x4.cs b/dotnet/DDS_Core/Helpers/FourHands.cs similarity index 51% rename from dotnet/DDS_Core/Helpers/UintBuffer4x4.cs rename to dotnet/DDS_Core/Helpers/FourHands.cs index ce02d000..1d27543a 100644 --- a/dotnet/DDS_Core/Helpers/UintBuffer4x4.cs +++ b/dotnet/DDS_Core/Helpers/FourHands.cs @@ -3,16 +3,16 @@ namespace DDS_Core; [StructLayout(LayoutKind.Sequential)] -public unsafe struct UintBuffer4x4 +public unsafe struct FourHands { public const int ROWS = 4; public const int COLS = 4; public const int SIZE = ROWS * COLS; - private fixed uint data[SIZE]; + private fixed uint data[SIZE]; public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); + => MemoryMarshal.CreateSpan(ref data[0], SIZE); public Span RowAsSpan(int row) { @@ -25,6 +25,21 @@ public Span RowAsSpan(int row) return MemoryMarshal.CreateSpan(ref start, COLS); } + public Span this[int row] + { + get + { + if ((uint)row >= ROWS) + throw new IndexOutOfRangeException(); + + // ref to the first element in the specified row + ref uint start = ref data[row * COLS]; + + // correct way to create a span over the fixed buffer + return MemoryMarshal.CreateSpan(ref start, COLS); + } + } + public ref uint this[int row, int col] { get @@ -36,29 +51,26 @@ public Span RowAsSpan(int row) } } - - public static implicit operator UintBuffer4x4(uint[] src) + public static implicit operator FourHands(uint[] src) { - var buf = new UintBuffer4x4(); + var buf = new FourHands(); - for (int c = 0; c < 16; c++) - buf.data[c] = src[c]; + for (int c = 0; c < 16; c++) + buf.data[c] = src[c]; return buf; } - - - public static implicit operator UintBuffer4x4(uint[][] src) + + public static implicit operator FourHands(uint[][] src) { - var buf = new UintBuffer4x4(); + var buf = new FourHands(); int k = 0; - for (int r = 0; r < 4; r++) - for (int c = 0; c < 4; c++) + + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) buf.data[k++] = src[r][c]; return buf; } - - } diff --git a/dotnet/DDS_Core/Helpers/Grid1D.cs b/dotnet/DDS_Core/Helpers/Grid1D.cs deleted file mode 100644 index e4eb76b9..00000000 --- a/dotnet/DDS_Core/Helpers/Grid1D.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -/// -/// Generic 1D array wrapper for marshalling with bounds checking. -/// -public struct Grid1D where T : unmanaged -{ - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0)] // Placeholder, will be overridden - private T[] data; - - private readonly int size; - - public Grid1D(int size) - { - if (size<=0) - throw new ArgumentException("Size must be positive", nameof(size)); - - this.size =size; - this.data =new T[size]; - } - - /// Gets or sets an element with bounds checking. - public ref T this[int index] - { - get - { - if (index<0||index>=size) - throw new IndexOutOfRangeException($"Index {index} out of range [0, {size-1}]"); - - return ref data[index]; - } - } - - /// Number of elements. - public int Length => size; - - /// Gets the underlying array as a span. - public Span AsSpan() => new(data, 0, size); -} diff --git a/dotnet/DDS_Core/Helpers/IBuffer.cs b/dotnet/DDS_Core/Helpers/IBuffer.cs new file mode 100644 index 00000000..c363c8b7 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/IBuffer.cs @@ -0,0 +1,7 @@ +namespace DDS_Core +{ +public interface IBuffer + { + public string GetString(int row); + } +} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer13.cs b/dotnet/DDS_Core/Helpers/IntBuffer13.cs deleted file mode 100644 index 648ad64d..00000000 --- a/dotnet/DDS_Core/Helpers/IntBuffer13.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct IntBuffer13 - { - public const int SIZE = 13; - - public fixed int data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref int this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } - } -} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer3.cs b/dotnet/DDS_Core/Helpers/IntBuffer3.cs deleted file mode 100644 index 55a85b00..00000000 --- a/dotnet/DDS_Core/Helpers/IntBuffer3.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct IntBuffer3 -{ - public const int SIZE = 3; - - public fixed int data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref int this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer52.cs b/dotnet/DDS_Core/Helpers/IntBuffer52.cs deleted file mode 100644 index 51964f36..00000000 --- a/dotnet/DDS_Core/Helpers/IntBuffer52.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public unsafe struct IntBuffer52 -{ - public const int SIZE = 52; - - public fixed int data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref int this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } -} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer53.cs b/dotnet/DDS_Core/Helpers/IntBuffer53.cs deleted file mode 100644 index 987df3cd..00000000 --- a/dotnet/DDS_Core/Helpers/IntBuffer53.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct IntBuffer53 - { - public const int SIZE = 53; - - public fixed int data[SIZE]; - - public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public ref int this[int index] - { - get - { - if ((uint)index >= SIZE) - throw new IndexOutOfRangeException(); - - return ref data[index]; - } - } - } -} diff --git a/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs b/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs deleted file mode 100644 index c5f7a70d..00000000 --- a/dotnet/DDS_Core/Helpers/IntBuffer5x4.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Runtime.InteropServices; - -namespace DDS_Core -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct IntBuffer5x4 - { - public const int ROWS = 5; - public const int COLS = 4; - public const int SIZE = ROWS * COLS; - - public fixed int data[SIZE]; - - public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public Span RowAsSpan(int row) - { - if ((uint)row >= ROWS) - throw new ArgumentOutOfRangeException(nameof(row)); - - // ref to first byte in the array for the specified row - ref int start = ref data[row * COLS]; - - return MemoryMarshal.CreateSpan(ref start, COLS); - } - - public ref int this[int row, int col] - { - get - { - if ((uint)row >= ROWS || (uint)col >= COLS) - throw new IndexOutOfRangeException(); - - return ref data[row * COLS + col]; - } - } - } -} - - diff --git a/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs b/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs deleted file mode 100644 index 5f57c0dd..00000000 --- a/dotnet/DDS_Core/Helpers/ParResultsBuffer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DDS_Core; - -[StructLayout(LayoutKind.Sequential)] -public struct ParResultsBuffer -{ - [MarshalAs(UnmanagedType.ByValArray, SizeConst = DdsConstants.MAXNOOFTABLES)] - public ParResults[] Items; - - public void Init() - { - Items = new ParResults[DdsConstants.MAXNOOFTABLES]; - } -} - diff --git a/dotnet/DDS_Core/Helpers/ResultsTable.cs b/dotnet/DDS_Core/Helpers/ResultsTable.cs new file mode 100644 index 00000000..4dbb588f --- /dev/null +++ b/dotnet/DDS_Core/Helpers/ResultsTable.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +namespace DDS_Core; + +/// +/// Tricks per strain and hand using 2D indexing. +/// Access: res_table[strain, hand] where strain=0-4, hand=0-3 +/// +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ResultsTable +{ + public const int ROWS = 5; + public const int COLS = 4; + public const int SIZE = ROWS * COLS; + + private fixed int data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref int start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public Span this[int row] +{ + get + { + if ((uint)row >= ROWS) + throw new IndexOutOfRangeException(); + + // ref to the first element in the specified row + ref int start = ref data[row * COLS]; + + // correct way to create a span over the fixed buffer + return MemoryMarshal.CreateSpan(ref start, COLS); + } +} + public ref int this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } +} + + diff --git a/dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs b/dotnet/DDS_Core/Helpers/SolveBoardResultExtensions.cs similarity index 98% rename from dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs rename to dotnet/DDS_Core/Helpers/SolveBoardResultExtensions.cs index fc777186..c8b9ffff 100644 --- a/dotnet/DDS_Core/DataModel/SolveBoardResultExtensions.cs +++ b/dotnet/DDS_Core/Helpers/SolveBoardResultExtensions.cs @@ -1,4 +1,4 @@ -namespace DDS_Core +namespace DDS_Core.Helpers { public static class SolveBoardResultExtensions { diff --git a/dotnet/DDS_Core/Helpers/SolverContextHandle.cs b/dotnet/DDS_Core/Helpers/SolverContextHandle.cs new file mode 100644 index 00000000..20011ceb --- /dev/null +++ b/dotnet/DDS_Core/Helpers/SolverContextHandle.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using DDS_Core.Native; + +namespace DDS_Core; +public sealed class SolverContextHandle : SafeHandle +{ + // Required by marshaling infrastructure, not intended to be used directly. + private SolverContextHandle() : base(IntPtr.Zero, ownsHandle: true) { } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + DdsNative.dds_destroy_solvercontext(handle); + return true; + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray10x10.cs b/dotnet/DDS_Core/Helpers/intArray10x10.cs new file mode 100644 index 00000000..081650f9 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray10x10.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + + +/// +/// Byte buffer struct is suitable for interop with native code, but in c# it acts +/// like a 2D string array with fixed max length per string. Strings are ASCII-encoded +/// and padded with nulls if shorter than max length. +/// +[DebuggerDisplay("{ToString()}")] +[DebuggerTypeProxy(typeof(intArray10xDebugView))] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct intArray10x10 : IBuffer +{ + public const int ROWS = 10; + public const int COLS = 10; + public const int SIZE = ROWS * COLS; + + private fixed byte data[SIZE]; + + #region Indexers + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + + public string this[int row] + { + get => GetString(row); + set => SetString(row, value); + } + #endregion + + #region String assignment API + public void SetString(int row, string value) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + if (value is null) + throw new ArgumentNullException(nameof(value)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + + // encode to ASCII + int written = Encoding.ASCII.GetBytes(value, span); + + if (written < COLS) + span.Slice(written).Clear(); + } + + public string GetString(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + return Encoding.ASCII.GetString(span).TrimEnd('\0'); + } + #endregion + + #region Spans + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + #endregion + + public override string ToString() + => "["+string.Join("],[", Enumerable.Range(0, ROWS).Select(GetString)) +"]"; + +} diff --git a/dotnet/DDS_Core/Helpers/intArray10xDebugView.cs b/dotnet/DDS_Core/Helpers/intArray10xDebugView.cs new file mode 100644 index 00000000..8b23c22e --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray10xDebugView.cs @@ -0,0 +1,28 @@ +using System; + +namespace DDS_Core +{ + internal sealed class intArray10xDebugView where T : IBuffer + { + private readonly T _buffer; + + public intArray10xDebugView(T buffer) + { + _buffer = buffer; + } + + public string Row0 => _buffer.GetString(0); + public string Row1 => _buffer.GetString(1); + public string Row2 => _buffer.GetString(2); + public string Row3 => _buffer.GetString(3); + + public string Row4 => _buffer.GetString(4); + public string Row5 => _buffer.GetString(5); + + public string Row6 => _buffer.GetString(6); + public string Row7 => _buffer.GetString(7); + + public string Row8 => _buffer.GetString(8); + public string Row9 => _buffer.GetString(9); + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray13.cs b/dotnet/DDS_Core/Helpers/intArray13.cs new file mode 100644 index 00000000..7828d3a4 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray13.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core; + +[InlineArray(13)] +public struct intArray13 +{ + private int item; + + public static implicit operator intArray13(int[] src) + { + var buf = new intArray13(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, 13); + +} diff --git a/dotnet/DDS_Core/Helpers/intArray200.cs b/dotnet/DDS_Core/Helpers/intArray200.cs new file mode 100644 index 00000000..ea7310fe --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray200.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core +{ + [InlineArray(DdsConstants.MaxNumberOfBoards)] + public struct intArray200 + { + private int item; + + public static implicit operator intArray200(int[] src) + { + var buf = new intArray200(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, DdsConstants.MaxNumberOfBoards); + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray2x128.cs b/dotnet/DDS_Core/Helpers/intArray2x128.cs new file mode 100644 index 00000000..48c3522c --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray2x128.cs @@ -0,0 +1,91 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + + +/// +/// Byte buffer struct is suitable for interop with native code, but in c# it acts +/// like a 2D string array with fixed max length per string. Strings are ASCII-encoded +/// and padded with nulls if shorter than max length. +/// +[DebuggerDisplay("{ToString()}")] +[DebuggerTypeProxy(typeof(intArray2xDebugView))] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct intArray2x128 : IBuffer +{ + public const int ROWS = 2; + public const int COLS = 128; + public const int SIZE = ROWS * COLS; + + private fixed byte data[SIZE]; + + #region Indexers + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + + public string this[int row] + { + get => GetString(row); + set => SetString(row, value); + } + #endregion + + #region String assignment API + public void SetString(int row, string value) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + if (value is null) + throw new ArgumentNullException(nameof(value)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + + // encode to ASCII + int written = Encoding.ASCII.GetBytes(value, span); + + if (written < COLS) + span.Slice(written).Clear(); + } + + public string GetString(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + return Encoding.ASCII.GetString(span).TrimEnd('\0'); + } + #endregion + + #region Spans + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + #endregion + + public override string ToString() + => "["+string.Join("],[", Enumerable.Range(0, ROWS).Select(GetString)) +"]"; +} diff --git a/dotnet/DDS_Core/Helpers/intArray2x16.cs b/dotnet/DDS_Core/Helpers/intArray2x16.cs new file mode 100644 index 00000000..f3aa7b41 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray2x16.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + + +/// +/// Byte buffer struct is suitable for interop with native code, but in c# it acts +/// like a 2D string array with fixed max length per string. Strings are ASCII-encoded +/// and padded with nulls if shorter than max length. +/// +[DebuggerDisplay("{ToString()}")] +[DebuggerTypeProxy(typeof(intArray2xDebugView))] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct intArray2x16 : IBuffer +{ + public const int ROWS = 2; + public const int COLS = 16; + public const int SIZE = ROWS * COLS; + + private fixed byte data[SIZE]; + + #region Indexers + public ref byte this[int row, int col] + { + get + { + if ((uint)row >= ROWS || (uint)col >= COLS) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + + public string this[int row] + { + get => GetString(row); + set => SetString(row, value); + } + #endregion + + #region String assignment API + public void SetString(int row, string value) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + if (value is null) + throw new ArgumentNullException(nameof(value)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + + // encode to ASCII + int written = Encoding.ASCII.GetBytes(value, span); + + if (written < COLS) + span.Slice(written).Clear(); + } + + public string GetString(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + ref byte start = ref data[row * COLS]; + var span = MemoryMarshal.CreateSpan(ref start, COLS); + return Encoding.ASCII.GetString(span).TrimEnd('\0'); + } + #endregion + + #region Spans + public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if ((uint)row >= ROWS) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref byte start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + #endregion + + public override string ToString() + => "["+string.Join("],[", Enumerable.Range(0, ROWS).Select(GetString)) +"]"; + +} diff --git a/dotnet/DDS_Core/Helpers/intArray2xDebugView.cs b/dotnet/DDS_Core/Helpers/intArray2xDebugView.cs new file mode 100644 index 00000000..0fcaab46 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray2xDebugView.cs @@ -0,0 +1,16 @@ +using System; + +namespace DDS_Core; + +internal sealed class intArray2xDebugView where T : IBuffer +{ + private readonly T _buffer; + + public intArray2xDebugView(T buffer) + { + _buffer = buffer; + } + + public string Row0 => _buffer.GetString(0); + public string Row1 => _buffer.GetString(1); +} diff --git a/dotnet/DDS_Core/Helpers/intArray3.cs b/dotnet/DDS_Core/Helpers/intArray3.cs new file mode 100644 index 00000000..fe9ea9cc --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray3.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core; + +[InlineArray(3)] +public struct intArray3 +{ + private int item; + + public static implicit operator intArray3(int[] src) + { + var buf = new intArray3(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, 3); + +} diff --git a/dotnet/DDS_Core/Helpers/intArray5.cs b/dotnet/DDS_Core/Helpers/intArray5.cs new file mode 100644 index 00000000..f2eaf9fa --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray5.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core +{ + [InlineArray(5)] + public struct intArray5 + { + private int item; + + public static implicit operator intArray5(int[] src) + { + var buf = new intArray5(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, 5); + + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray52.cs b/dotnet/DDS_Core/Helpers/intArray52.cs new file mode 100644 index 00000000..866ec967 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray52.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core; + +[InlineArray(52)] +public struct intArray52 +{ + private int item; + + public static implicit operator intArray52(int[] src) + { + var buf = new intArray52(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, 52); + +} diff --git a/dotnet/DDS_Core/Helpers/intArray53.cs b/dotnet/DDS_Core/Helpers/intArray53.cs new file mode 100644 index 00000000..3583146b --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray53.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DDS_Core +{ + [InlineArray(53)] + public struct IntArray53 + { + private int item; + + public static implicit operator IntArray53(int[] src) + { + var buf = new IntArray53(); + var span = buf.AsSpan(); + + int count = Math.Min(span.Length, src.Length); + + for (int i = 0; i < count; i++) + span[i] = src[i]; + + for (int i = count; i < span.Length; i++) + span[i] = 0; + + return buf; + } + + public Span AsSpan() + => System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref item, 53); + + } +} diff --git a/dotnet/DDS_Core/Helpers/string106.cs b/dotnet/DDS_Core/Helpers/string106.cs new file mode 100644 index 00000000..534c66ef --- /dev/null +++ b/dotnet/DDS_Core/Helpers/string106.cs @@ -0,0 +1,86 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + +/// +/// Byte buffer struct is suitable for interop with native code, but in c# it acts +/// like a simple string with fixed max length per string. Strings are ASCII-encoded +/// and padded with nulls if shorter than max length. +/// +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct string106 +{ + public const int SIZE = 106; + + private fixed byte data[SIZE]; + + // ------------------------------- + // Indexers + // ------------------------------- + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } + + public string Value + { + get => GetString(); + set => SetString(value); + } + + // ------------------------------- + // String assignment API + // ------------------------------- + public void SetString(string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value.Length > SIZE) + throw new ArgumentOutOfRangeException(nameof(value)); + + ref byte start = ref data[0]; + var span = MemoryMarshal.CreateSpan(ref start, SIZE); + + // ASCII encode + int written = Encoding.ASCII.GetBytes(value, span); + + if (written < SIZE) + span.Slice(written).Clear(); + } + + public string GetString() + { + ref byte start = ref data[0]; + var span = MemoryMarshal.CreateSpan(ref start, SIZE); + return Encoding.ASCII.GetString(span).TrimEnd('\0'); + } + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + // ------------------------------- + // Implicit conversion + // ------------------------------- + public static implicit operator string106(string src) + { + var buf = new string106(); + buf.SetString(src); + return buf; + } + + // ------------------------------- + // Debugger / ToString + // ------------------------------- + public override string ToString() => GetString(); +} diff --git a/dotnet/DDS_Core/Helpers/string80.cs b/dotnet/DDS_Core/Helpers/string80.cs new file mode 100644 index 00000000..fd69871c --- /dev/null +++ b/dotnet/DDS_Core/Helpers/string80.cs @@ -0,0 +1,86 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + +/// +/// Byte buffer struct is suitable for interop with native code, but in c# it acts +/// like a simple string with fixed max length per string. Strings are ASCII-encoded +/// and padded with nulls if shorter than max length. +/// +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct string80 +{ + public const int SIZE = 80; + + private fixed byte data[SIZE]; + + // ------------------------------- + // Indexers + // ------------------------------- + public ref byte this[int index] + { + get + { + if ((uint)index >= SIZE) + throw new IndexOutOfRangeException(); + + return ref data[index]; + } + } + + public string Value + { + get => GetString(); + set => SetString(value); + } + + // ------------------------------- + // String assignment API + // ------------------------------- + public void SetString(string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value.Length > SIZE) + throw new ArgumentOutOfRangeException(nameof(value)); + + ref byte start = ref data[0]; + var span = MemoryMarshal.CreateSpan(ref start, SIZE); + + // ASCII encode + int written = Encoding.ASCII.GetBytes(value, span); + + if (written < SIZE) + span.Slice(written).Clear(); + } + + public string GetString() + { + ref byte start = ref data[0]; + var span = MemoryMarshal.CreateSpan(ref start, SIZE); + return Encoding.ASCII.GetString(span).TrimEnd('\0'); + } + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + // ------------------------------- + // Implicit conversion + // ------------------------------- + public static implicit operator string80(string src) + { + var buf = new string80(); + buf.SetString(src); + return buf; + } + + // ------------------------------- + // Debugger / ToString + // ------------------------------- + public override string ToString() => GetString(); +} diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 1981b587..c17eab9d 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; using System.Text; -using DDS_Core; +using static System.Net.WebRequestMethods; namespace DDS_Core.Native; @@ -9,23 +9,110 @@ internal static class DdsNative { private const string DllName = "dds_native"; + #region ====== Version 3 specific methods ====== + #region ===== Solver Context Management ====== + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + + internal static extern SolverContextHandle dds_create_solvercontext_default(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern SolverContextHandle dds_create_solvercontext(SolverConfig cfg); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_destroy_solvercontext(IntPtr ctx); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_configure_tt( SolverContextHandle ctx + , TTKind kind + , int defMB + , int maxMB); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_resize_tt( SolverContextHandle ctx + , int defMB + , int maxMB); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_clear_tt(SolverContextHandle ctx); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_reset_for_solve(SolverContextHandle ctx); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_reset_best_moves_lite(in SolverContextHandle ctx); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern void dds_log_append(in SolverContextHandle ctx, in string msg); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void dds_log_clear(in SolverContextHandle ctx); + #endregion + + #region ====== SolverContext methods ====== + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int dds_solve_board( SolverContextHandle ctx + , in Deal dl + , int target + , int solutions + , int mode + , out FutureTricks fut); + + #region Call_dd + // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int calc_dd_table( in DdTableDeal table_deal + // , out DdTableResults table_results); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int calc_dd_table( SolverContextHandle ctx + , in DdTableDeal table_deal + , out DdTableResults table_results); + + // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int calc_dd_table_pbn( in DdTableDealPBN table_deal_pbn + // , out DdTableResults table_results); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int calc_dd_table_pbn( SolverContextHandle ctx + , in DdTableDealPBN table_deal_pbn + , out DdTableResults table_results); + #endregion + + #region Call_par + // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int calc_par( in DdTableDeal table_deal + // , int vulnerable + // , out DdTableResults table_results + // , out ParResults par_results); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int calc_par( SolverContextHandle ctx + , in DdTableDeal table_deal + , int vulnerable + , out DdTableResults table_results + , out ParResults par_results); + + // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int calc_par_from_table( in DdTableResults table_results + // , int vulnerable + // , out ParResults par_results); + #endregion + #endregion + #endregion + #region ====== Configuration and Resource Management ====== - [DllImport(DllName)] - public static extern void SetMaxThreads(int userThreads); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern void SetMaxThreads( int userThreads); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SetThreading(int code); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern void SetResources( int maxMemoryMB , int maxThreads); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern void FreeMemory(); #endregion #region ====== Single Board Solving ====== - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveBoard( in Deal dl , int target , int solutions @@ -33,7 +120,7 @@ public static extern int SolveBoard( in Deal dl , out FutureTricks fut , int threadIndex); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveBoardPBN( in DealPBN dlpbn , int target , int solutions @@ -43,125 +130,125 @@ public static extern int SolveBoardPBN( in DealPBN dlpbn #endregion #region ====== Multiple Board Solving ====== - [DllImport(DllName)] - public static extern int SolveAllBoards( in Boards bop + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int SolveAllBoards( in BoardsPBN bop , out SolvedBoards solved); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveAllBoardsBin( in Boards bop , out SolvedBoards solved); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveAllChunks( in BoardsPBN bop , out SolvedBoards solved , int chunkSize); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveAllChunksBin( in Boards bop , out SolvedBoards solved , int chunkSize); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SolveAllChunksPBN( in BoardsPBN bop , out SolvedBoards solved , int chunkSize); #endregion #region ====== Double Dummy Table Calculation ====== - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcDDtable( in DdTableDeal tableDeal , out DdTableResults table); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcDDtablePBN( in DdTableDealPBN tableDealPBN , out DdTableResults table); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcAllTables( in DdTableDeals dealsp , int mode - , int[] trumpFilter //TODO: Tjekke denne - , out DdTablesRes resp + , intArray5 trumpFilter + , out DdTablesResult resp , out AllParResults presp); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcAllTablesPBN( in DdTableDealsPBN dealsp , int mode - , int[] trumpFilter - , out DdTablesRes resp + , in intArray5 trumpFilter + , out DdTablesResult resp , out AllParResults presp); #endregion #region ====== Par Score Calculation ====== - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int Par( in DdTableResults table , out ParResults pres , int vulnerable); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcPar( in DdTableDeal tableDeal , int vulnerable , out DdTableResults table , out ParResults pres); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int CalcParPBN( in DdTableDealPBN tableDealPBN , out DdTableResults table , int vulnerable , out ParResults pres); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SidesPar( in DdTableResults table - , [Out] ParResultsDealer[] sidesRes + , out ParResultsDealers sidesRes , int vulnerable); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int DealerPar( in DdTableResults table , out ParResultsDealer pres , int dealer , int vulnerable); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int DealerParBin( in DdTableResults table , out ParResultsMaster pres , int dealer , int vulnerable); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int SidesParBin( in DdTableResults table - , [Out] ParResultsMaster[] sidesRes + , out ParResultsMasters sidesRes , int vulnerable); #endregion #region ====== Par Text Conversion ====== - [DllImport(DllName, CharSet = CharSet.Ansi)] + [DllImport("dds_native", CharSet = CharSet.Ansi)] public static extern int ConvertToDealerTextFormat( in ParResultsMaster pres - , StringBuilder resp); //TODO: Tjek alle StringBuilder + , StringBuilder resp); - [DllImport(DllName)] - public static extern int ConvertToSidesTextFormat( in ParResultsMaster pres + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern int ConvertToSidesTextFormat( in ParResultsMasters pres , out ParTextResults resp); #endregion #region ====== Play Analysis ====== - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int AnalysePlayBin( in Deal dl , in PlayTraceBin play , out SolvedPlay solved , int thrId); - [DllImport(DllName, CharSet = CharSet.Ansi)] + [DllImport("dds_native", CharSet = CharSet.Ansi)] public static extern int AnalysePlayPBN( in DealPBN dlPBN , in PlayTracePBN playPBN , out SolvedPlay solved , int thrId); - [DllImport(DllName)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int AnalyseAllPlaysBin( in Boards bop , in PlayTracesBin plp , out SolvedPlays solved , int chunkSize); - [DllImport(DllName, CharSet = CharSet.Ansi)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern int AnalyseAllPlaysPBN( in BoardsPBN bopPBN , in PlayTracesPBN plpPBN , out SolvedPlays solved @@ -169,10 +256,10 @@ public static extern int AnalyseAllPlaysPBN( in BoardsPBN bopPBN #endregion #region ====== Utility Functions ====== - [DllImport(DllName)] - public static extern void GetDDSInfo(out DDSInfo info); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern void GetDDSInfo(out DdsInfo info); - [DllImport(DllName, CharSet = CharSet.Ansi)] + [DllImport("dds_native", CharSet = CharSet.Ansi)] public static extern void ErrorMessage( int code , StringBuilder line); #endregion diff --git a/dotnet/DDS_Core/Properties/launchSettings.json b/dotnet/DDS_Core/Properties/launchSettings.json new file mode 100644 index 00000000..03f7be23 --- /dev/null +++ b/dotnet/DDS_Core/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DDS_Core": { + "commandName": "Project", + "debugEngines": "managed-dotnet,native" + } + } +} \ No newline at end of file diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 5163e7a8..ce707c2c 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -1,72 +1,830 @@ -using DDS_Core; +using System.ComponentModel; +using System.Diagnostics; +using System.Net; +using System.Text; +using System.Transactions; +using DDS_Core; +using Microsoft.Win32.SafeHandles; +using static System.Net.Mime.MediaTypeNames; using static DDS_Core.CardRanks; namespace DDS_Core_Demo; internal class Program { - private static string[] contracts = ["spades","hearts", "diamonds", "clubs", "no trump" ]; + private static string[] contracts = ["spades","hearts", "diamonds", "clubs", "no trump" ]; + private static string[] c = ["S","H", "D", "C", "NT" ]; + private static int[,] tricks = new int[5, 4]; + private static bool isPerformanceTest ; + private static int iterations = 100; static void Main(string[] args) { - uint[][] hands = [ new uint[] - { - (uint)(rJ|r6|r5|r2) - , (uint)(rA|r7|r4) - , (uint)None - , (uint)(rK|rT|r9|r6|r4|r2) - } - - , new uint[] - { - (uint)(rA|rT|r9|r8|r7|r3) - , (uint)(rK|rJ|r5) - , (uint)(rT|r8|r2) - , (uint)r8 - } - - , new uint[] - { - (uint)r4 - , (uint)(rQ|rT|r9|r8|r2) - , (uint)(rA|rK|rQ|r9|r3) - , (uint)(rA|rJ) - } - - , new uint[] - { - (uint)(rK|rQ) - , (uint)(r6|r3) - , (uint)(rJ|r7|r6|r5|r4) - , (uint)(rQ|r7|r5|r3) - } - - ]; - - var dds = new DDS(); - var deal = new Deal - { - trump = (int)Suit.Hearts - , first = 0 - , currentTrickSuit = new int[3] {0, 0, 0 } - , currentTrickRank = new int[3] {0, 0, 0 } - , remainCards = hands - }; + var dds = new DDS(); + var tst =TestData.deals; // Only to initialize TestData - actually not needed! - // SolveBoard: Loop through all possible contracts and first players - for (deal.trump = 0; deal.trump < 5; deal.trump++) - for (deal.first = 0; deal.first < 4; deal.first++) + // All samples display the number of tricks for each contract + // and each declarer even though most of the dds methods returns + // the number of tricks for the hand at lead. + // + // PERFORMANCE TEST: Measure AnalyseAllPlaysBin P/Invoke performance + // Run in Release mode for accurate results + bool isPerformanceTest = args.Length > 0 && args[0] == "benchmark"; + + if (isPerformanceTest) + { + BenchmarkAnalyseAllPlaysBin(dds); + Console.WriteLine($"Press any key to continue..."); + Console.ReadKey(); + return; + } + + // + dds.SetMaxThreads(1); + doSolveBoard(dds, TestData.deals[0]); + doSolveBoardPBN(dds, TestData.dealsPBN[0]); + doSolveAllBoards(dds, TestData.boardsPBN); + doSolveAllBoardsBin(dds, TestData.boards); + doSolveAllChunks(dds, TestData.boardsPBN, 10); + doSolveAllChunksBin(dds, TestData.boards, 10); + + doSolveAllChunksPBN(dds, TestData.boardsPBN, 10); + doCalcDdTable(dds, TestData.ddTableDeal); + doCalcDdTablePBN(dds, TestData.ddTableDealPBN); + doCalcAllTables(dds, TestData.ddTableDeals); + doCalcAllTablesPBN(dds, TestData.ddTableDealsPBN); + doPar(dds, TestData.ddTableResults, 0); + + doCalcPar(dds, TestData.ddTableDeal); + doCalcParPBN(dds, TestData.ddTableDealPBN); + doDealerPar(dds, TestData.ddTableResults); + doDealerParBin(dds, TestData.ddTableResults); + doConvertToDealerTextFormat(dds, TestData.parResultsMaster); + doConvertToSidesTextFormat(dds, TestData.parResultsMasters); + // + // These two methods should be tested together + doSidesParBin(dds, TestData.ddTableResults); + doAnalysePlayBin(dds, TestData.deals[0], TestData.parResultsMasters, TestData.playTraceBin); + // + // These two methods should be tested together + doSidesPar(dds, TestData.ddTableResults); + doAnalysePlayPBN(dds, TestData.dealsPBN[0], TestData.parResultsMasters, TestData.playTracePBN); + // + doAnalyseAllPlayBin(dds, TestData.boards, TestData.playTracesBin); + doAnalyseAllPlayPBN(dds, TestData.boardsPBN, TestData.playTracesPBN); + doGetDDSInfo(dds); + doErrorMessage(dds, -14); + + #region Version 3.0.0 samples + var cfg = new SolverConfig() + { + TTKind = TTKind.Large + , DefaultMemoryMB = 256 + , MaximumMemoryMB = 1024 + }; + + using (var ctx = new SolverContext(cfg)) { - var rc =dds.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); - var deci = (deal.first+3)&3; - var decl = "NESW"[deci]; - var cont = contracts[deal.trump]; + ctx?.ConfigureTT(TTKind.Small, 256, 1024); - Console.WriteLine($"Tricks in {cont} for {decl}: {13 - fut.score[0]} "); + doSolveBoardV3(dds, ctx, TestData.deals[0]); + doCalcDdTableV3(dds, ctx, TestData.ddTableDeal); + doCalcDdTablePBNV3(dds, ctx, TestData.ddTableDealPBN); + doCalcPar(dds, TestData.ddTableDeal); } + #endregion Console.WriteLine($"Press any key to continue..."); Console.ReadKey(); return; } + + private static void doSolveBoard(DDS dds, Deal deal) + { + Console.WriteLine($"SolveBoard"); + + tricks = new int[5, 4]; + + for (deal.Trump = 0; deal.Trump < 5; deal.Trump++) + for (deal.First = 0; deal.First < 4; deal.First++) + { + var decl =(deal.First + 3) & 3; + var rc = dds.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); + + // record the number of tricks for declarer + tricks[deal.Trump, decl] = 13 - fut.Score[0]; + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveBoardV3(DDS dds, SolverContext ctx, Deal deal) + { + Console.WriteLine($"SolveBoard v3"); + + tricks = new int[5, 4]; + + for (deal.Trump = 0; deal.Trump < 5; deal.Trump++) + for (deal.First = 0; deal.First < 4; deal.First++) + { + var decl =(deal.First + 3) & 3; + var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); + + // record the number of tricks for declarer + tricks[deal.Trump, decl] = 13 - fut.Score[0]; + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveBoardPBN(DDS dds, DealPBN deal) + { + // SolveBoard: Loop through all possible contracts and first players + Console.WriteLine($"SolveBoardPBN"); + tricks = new int[5, 4]; + + for (deal.Trump = 0; deal.Trump < 5; deal.Trump++) + + for (deal.First = 0; deal.First < 4; deal.First++) + { + var decl =(deal.First + 3) & 3; + var rc = dds.SolveBoardPBN(deal, -1, 1, 0, out FutureTricks fut); + + // record the number of tricks for declarer + tricks[deal.Trump, decl] = 13 - fut.Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveAllBoards(DDS dds, BoardsPBN boards) + { + Console.WriteLine($"SolveAllBoards"); + tricks = new int[5, 4]; + + for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + { + var decl =(boards.Deals[0].First + 3) & 3; + var rc = dds.SolveAllBoards(boards, out SolvedBoards solved); + + // record the number of tricks for declarer + tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveAllBoardsBin(DDS dds, Boards boards) + { + Console.WriteLine($"SolveAllBoardsBin"); + tricks = new int[5, 4]; + + // Here we loop over all trump suits and dealers which is't the normal thing to do! + // This is done as we want to display the full tricks table + for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + { + var decl =(boards.Deals[0].First + 3) & 3; + var rc = dds.SolveAllBoardsBin(boards, out SolvedBoards solved); + + // record the number of tricks for declarer + tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveAllChunks(DDS dds, BoardsPBN boards, int chunkSize) + { + Console.WriteLine($"SolveAllChunks"); + tricks = new int[5, 4]; + + for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + { + var decl =(boards.Deals[0].First + 3) & 3; + var rc = dds.SolveAllChunks(boards, out SolvedBoards solved, chunkSize); + + // record the number of tricks for declarer + tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveAllChunksBin(DDS dds, Boards boards, int chunkSize) + { + Console.WriteLine($"SolveAllChunksBin"); + tricks = new int[5, 4]; + + for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + { + var decl =(boards.Deals[0].First + 3) & 3; + var rc = dds.SolveAllChunksBin(boards, out SolvedBoards solved, chunkSize); + + // record the number of tricks for declarer + tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doSolveAllChunksPBN(DDS dds, BoardsPBN boards, int chunkSize) + { + Console.WriteLine($"SolveAllChunksPBN"); + tricks = new int[5, 4]; + + for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + { + var decl =(boards.Deals[0].First + 3) & 3; + var rc = dds.SolveAllChunksPBN(boards, out SolvedBoards solved, chunkSize); + + // record the number of tricks for declarer + tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + + dds.FreeMemory(); + } + + DisplayTricks(); + } + + private static void doCalcDdTable(DDS dds, DdTableDeal ddTableDeal) + { + Console.WriteLine($"CalcDdTable"); + tricks = new int[5, 4]; + + var rc = dds.CalcDdTable(ddTableDeal, out DdTableResults results); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results.ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + + private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal) + { + Console.WriteLine($"CalcDdTable V3"); + tricks = new int[5, 4]; + + var rc = ctx.CalcDdTable(ddTableDeal, out DdTableResults results); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results.ResultsTable[trump, decl]; + } + + ctx.ResetForSolve(); + //dds.FreeMemory(); + DisplayTricks(); + } + + private static void doCalcDdTablePBN( + DDS dds, DdTableDealPBN ddTableDeal) + { + Console.WriteLine($"CalcDdTablePBN"); + tricks = new int[5, 4]; + + var rc = dds.CalcDdTablePBN(ddTableDeal, out DdTableResults results); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results.ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + + TestData.ddTableResults = results; + } + + private static void doCalcDdTablePBNV3(DDS dds, SolverContext ctx, DdTableDealPBN ddTableDeal) + { + Console.WriteLine($"CalcDdTablePBN V3"); + tricks = new int[5, 4]; + + var rc = ctx.CalcDdTablePBN(ddTableDeal, out DdTableResults results); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results.ResultsTable[trump, decl]; + } + + ctx.ResetForSolve(); + //dds.FreeMemory(); + DisplayTricks(); + + TestData.ddTableResults = results; + } + + private static void doCalcAllTables(DDS dds, DdTableDeals ddTableDeals) + { + Console.WriteLine($"CalcAllTables"); + tricks = new int[5, 4]; + intArray5 trumpFilter = new(); + + var rc = dds.CalcAllTables( in ddTableDeals + , 0 + , trumpFilter + , out DdTablesResult results + , out AllParResults presp); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results[0].ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + + private static void doCalcAllTablesPBN(DDS dds, DdTableDealsPBN ddTableDeals) + { + Console.WriteLine($"CalcAllTablesPBN"); + tricks = new int[5, 4]; + intArray5 trumpFilter = new(); + + var rc = dds.CalcAllTablesPBN( in ddTableDeals + , 0 + , trumpFilter + , out DdTablesResult results + , out AllParResults presp); + + for (var trump = 0; trump < 5; trump++) + + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = results[0].ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + + private static void doPar(DDS dds, DdTableResults tableResults, int vulnerable) + { + Console.WriteLine($"Par"); + tricks = new int[5, 4]; + + var rc = dds.Par( in tableResults + , out ParResults results + , vulnerable); + + Console.WriteLine(results.ParContractStrings); + Console.WriteLine(results.ParScores); + + dds.FreeMemory(); + Console.WriteLine(); + } + + private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal) + { + Console.WriteLine($"CalcPar"); + tricks = new int[5, 4]; + + var rc = dds.CalcPar( in ddTableDeal + , 0 + , out DdTableResults tResults + , out ParResults results ); + + Console.WriteLine(results.ParContractStrings); + Console.WriteLine(results.ParScores); + + for (var trump = 0; trump < 5; trump++) + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = tResults.ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + + private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal) + { + Console.WriteLine($"CalcParV3"); + tricks = new int[5, 4]; + + var rc = ctx.CalcPar( in ddTableDeal + , 0 + , out DdTableResults tResults + , out ParResults results ); + + Console.WriteLine(results.ParContractStrings); + Console.WriteLine(results.ParScores); + + for (var trump = 0; trump < 5; trump++) + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = tResults.ResultsTable[trump, decl]; + } + + ctx.ResetForSolve(); + //dds.FreeMemory(); + DisplayTricks(); + } + + private static void doCalcParPBN(DDS dds, DdTableDealPBN ddTableDeal) + { + Console.WriteLine($"CalcParPBN"); + tricks = new int[5, 4]; + + var rc = dds.CalcParPBN( in ddTableDeal + , out DdTableResults tResults + , 0 + , out ParResults results ); + + Console.WriteLine(results.ParContractStrings); + Console.WriteLine(results.ParScores); + + for (var trump = 0; trump < 5; trump++) + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = tResults.ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + + private static void doSidesPar(DDS dds, DdTableResults tResults) + { + Console.WriteLine($"SidesPar"); + tricks = new int[5, 4]; + + var rc = dds.SidesPar( in tResults + , out ParResultsDealers results + , 0); + + Console.WriteLine(results[0].NumberOfContracts); + Console.WriteLine(results[0].Score); + Console.WriteLine(results[0].Contracts); + + Console.WriteLine(); + Console.WriteLine(results[1].NumberOfContracts); + Console.WriteLine(results[1].Score); + Console.WriteLine(results[1].Contracts); + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doDealerPar(DDS dds, DdTableResults tResults) + { + Console.WriteLine($"DealerPar"); + tricks = new int[5, 4]; + + var rc = dds.DealerPar( in tResults + , out ParResultsDealer results + , 0 + , 3); + + Console.WriteLine(results.NumberOfContracts); + Console.WriteLine(results.Score); + Console.WriteLine(results.Contracts); + Console.WriteLine(""); + + dds.FreeMemory(); + } + + private static void doDealerParBin(DDS dds, DdTableResults tResults) + { + Console.WriteLine($"DealerParBin"); + tricks = new int[5, 4]; + + var rc = dds.DealerParBin( in tResults + , out ParResultsMaster results + , 0 + , 3); + + Console.WriteLine(results.Number); + Console.WriteLine(results.Score); + + for (int i = 0; i < results.Number; i++) + { + var contract =results.Contracts[i]; + var d = c[contract.Denomination]; + + if (contract.UnderTricks > 0) + Console.WriteLine($"{contract.Level}{d}(-{contract.UnderTricks})"); + else + if (contract.OverTricks > 0) + Console.WriteLine($"{contract.Level}{d}(+{contract.OverTricks})"); + else + Console.WriteLine($"{contract.Level}{d}(=)"); + } + + Console.WriteLine(""); + TestData.parResultsMaster = results; + dds.FreeMemory(); + } + + private static void doSidesParBin(DDS dds, DdTableResults tResults) + { + Console.WriteLine($"SidesParBin"); + tricks = new int[5, 4]; + + var rc = dds.SidesParBin( in tResults + , out ParResultsMasters results + , 1); + + for (int s = 0; s < 2; s++) + { + Console.WriteLine($"Side {(s == 0 ? "N/S" : "E/W")}"); + Console.WriteLine(results[s].Number); + Console.WriteLine(results[s].Score); + + for (int i = 0; i < results[s].Number; i++) + { + var contract =results[s].Contracts[i]; + var d = c[contract.Denomination]; + + if (contract.UnderTricks > 0) + Console.WriteLine($"{contract.Level}{d}(-{contract.UnderTricks})"); + else + if (contract.OverTricks > 0) + Console.WriteLine($"{contract.Level}{d}(+{contract.OverTricks})"); + else + Console.WriteLine($"{contract.Level}{d}(=)"); + } + + Console.WriteLine(""); + } + + TestData.parResultsMasters = results; + dds.FreeMemory(); + } + + private static void doConvertToDealerTextFormat(DDS dds, ParResultsMaster tResults) + { + Console.WriteLine($"ConvertToDealerTextFormat"); + + var rc = dds.ConvertToDealerTextFormat( in tResults + , out string str ); + + Console.WriteLine(str); + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doConvertToSidesTextFormat(DDS dds, ParResultsMasters tResults) + { + Console.WriteLine($"ConvertToSidesTextFormat"); + + var rc = dds.ConvertToSidesTextFormat( in tResults + , out ParTextResults str ); + + Console.WriteLine(str.ParTextStrings); + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doAnalysePlayBin(DDS dds, Deal deal, ParResultsMasters tResults, PlayTraceBin ptrace) + { + Console.WriteLine($"AnalysePlayBin"); + + var rc = dds.AnalysePlayBin( in deal + , in ptrace + , out SolvedPlay solved + , 0); + + for (int i = 0; i <= ptrace.NumberOfCards; i++) + Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); + + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doAnalysePlayPBN(DDS dds, DealPBN deal, ParResultsMasters tResults, PlayTracePBN ptrace) + { + Console.WriteLine($"AnalysePlayPBN"); + + var rc = dds.AnalysePlayPBN( in deal + , in ptrace + , out SolvedPlay solved + , 0); + + for (int i = 0; i <= ptrace.NumberOfPlayedCards; i++) + Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); + + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doAnalyseAllPlayBin(DDS dds, Boards boards, PlayTracesBin ptrace) + { + if (!isPerformanceTest) + Console.WriteLine($"AnalysePlayBin"); + + var rc = dds.AnalyseAllPlaysBin( in boards + , in ptrace + , out SolvedPlays solved + , 0); + + if (isPerformanceTest) + { + for (int i = 0; i <= ptrace.Plays[0].NumberOfCards; i++) + Console.WriteLine($"{i,2}: {solved.Solved[0].Tricks[i]}"); + + Console.WriteLine(""); + } + + dds.FreeMemory(); + } + + private static void doAnalyseAllPlayPBN(DDS dds, BoardsPBN boards, PlayTracesPBN ptrace) + { + Console.WriteLine($"AnalyseAllPlayPBN"); + + var rc = dds.AnalyseAllPlaysPBN( in boards + , in ptrace + , out SolvedPlays solved + , 0); + + for (int i = 0; i <= ptrace.Plays[0].NumberOfPlayedCards; i++) + Console.WriteLine($"{i,2}: {solved.Solved[0].Tricks[i]}"); + + Console.WriteLine(""); + dds.FreeMemory(); + } + + private static void doGetDDSInfo(DDS dds) + { + Console.WriteLine($"GetDDSInfo"); + + dds.GetDDSInfo(out DdsInfo info); + + Console.WriteLine($"DDS Info:"); + Console.WriteLine($" Version: {info.VersionString}"); + Console.WriteLine($" System: {info.System}"); + Console.WriteLine($" Bits: {info.NumberOfBits}"); + Console.WriteLine($" Threading: {info.Threading}"); + Console.WriteLine($" Threads: {info.NumberOfThreads}"); + Console.WriteLine(""); + } + + private static void doErrorMessage(DDS dds, int rc) + { + Console.WriteLine($"Error Message"); + + dds.ErrorMessage(rc, out string error); + + Console.WriteLine($"Error: {error}"); + Console.WriteLine(""); + } + + private static void DisplayTricks() + { + Console.WriteLine($" N E S E"); + Console.WriteLine($" __ __ __ __"); + + for (var denonination = 0; denonination < 5; denonination++) + { + Console.Write($"Tricks in {contracts[denonination],-10}: "); + + for (var declarer = 0; declarer < 4; declarer++) + + Console.Write($"{tricks[denonination, declarer],2} "); + + Console.WriteLine(""); + } + + Console.WriteLine(); + } + + private static void BenchmarkAnalyseAllPlaysBin(DDS dds) + { + Console.WriteLine("=== AnalyseAllPlaysBin P/Invoke Performance Benchmark ==="); + Console.WriteLine("(Run this in Release configuration for accurate results)\n"); + + var boards = TestData.boards; + var playTracesBin = TestData.playTracesBin; + try + { + var time1 = 0d; + + // Variant 1: Baseline - Cdecl with 'in' parameters (read-only reference) + time1+= BenchmarkVariant1(dds, boards, playTracesBin); + + Console.WriteLine("\n=== Benchmark Complete ==="); + Console.Out.Flush(); + } + + catch (Exception ex) + { + Console.WriteLine($"\nERROR: {ex.GetType().Name}: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + Console.Out.Flush(); + } + } + + private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin playTracesBin) + { + try + { + // Warmup iterations + Console.WriteLine("\n--- Variant 1: in parameters all the way ---"); + Console.WriteLine("Warming up..."); + Console.Out.Flush(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + for (int i = 0; i < 5; i++) + dds.AnalyseAllPlaysBin(in boards, in playTracesBin, out SolvedPlays solved, 0); + + dds.FreeMemory(); + + // Measure iterations + Console.WriteLine($"Running {iterations} iterations..."); + + var sw = Stopwatch.StartNew(); + + for (int i = 0; i < iterations; i++) + { + dds.AnalyseAllPlaysBin(in boards, in playTracesBin, out SolvedPlays solved, 0); + dds.FreeMemory(); + } + + sw.Stop(); + + double avgMs = sw.Elapsed.TotalMilliseconds / iterations; + double opsPerSecond = 1000.0 / avgMs; + + Console.WriteLine($"Total time: {sw.Elapsed.TotalMilliseconds:F2} ms"); + Console.WriteLine($"Average time: {avgMs:F4} ms per call"); + Console.WriteLine($"Throughput: {opsPerSecond:F2} calls/second"); + + return opsPerSecond; + } + catch (Exception ex) + { + Console.WriteLine($"ERROR in Variant1: {ex.Message}"); + + if (ex.InnerException != null) + Console.WriteLine($"Inner: {ex.InnerException.Message}"); + } + + return 0d; + } } + diff --git a/dotnet/DDS_Core_Demo/TestData.cs b/dotnet/DDS_Core_Demo/TestData.cs new file mode 100644 index 00000000..1f70800b --- /dev/null +++ b/dotnet/DDS_Core_Demo/TestData.cs @@ -0,0 +1,224 @@ +using DDS_Core; +using static DDS_Core.CardRanks; + +namespace DDS_Core_Demo +{ + public static class TestData + { + public static string[] pbn; + public static uint[][][] hands; + public static Deal[] deals; + public static DealPBN[] dealsPBN; + public static BoardsPBN boardsPBN; + public static Boards boards; + public static DdTableDeal ddTableDeal; + public static DdTableDealPBN ddTableDealPBN; + public static DdTableDeals ddTableDeals; + public static DdTableDealsPBN ddTableDealsPBN; + public static DdTableResults ddTableResults; + public static ParResultsMaster parResultsMaster; + public static ParResultsMasters parResultsMasters; + public static PlayTraceBin playTraceBin; + public static PlayTracePBN playTracePBN; + public static PlayTracesBin playTracesBin; + public static PlayTracesPBN playTracesPBN; + + static TestData() + { + hands = new uint[4][][]; + + hands[0] = [ + [ (uint)(rJ|r6|r5|r2) + , (uint)(rA|r7|r4) + , (uint)None + , (uint)(rK|rT|r9|r6|r4|r2) + ] + , [ (uint)(rA|rT|r9|r8|r7|r3) + , (uint)(rK|rJ|r5) + , (uint)(rT|r8|r2) + , (uint)r8 + ] + , [ (uint)r4 + , (uint)(rQ|rT|r9|r8|r2) + , (uint)(rA|rK|rQ|r9|r3) + , (uint)(rA|rJ) + ] + , [ (uint)(rK|rQ) + , (uint)(r6|r3) + , (uint)(rJ|r7|r6|r5|r4) + , (uint)(rQ|r7|r5|r3) + ] + ]; + + hands[1] = [ + [ (uint)(rA|rK|r9|r6) + , (uint)(rK|rQ|r8) + , (uint)(rA|r9|r8) + , (uint)(rK|r6|r3) + ] + + , [ (uint)(rQ|rJ|rT|r5|r4|r3|r2) + , (uint)(rT) + , (uint)(r6) + , (uint)(rQ|rJ|r8|r2) + ] + + , [ (uint)None + , (uint)(rJ|r9|r7|r5|r4|r3) + , (uint)(rK|r7|r5|r3|r2) + , (uint)(r9|r4) + ] + + , [ (uint)(r8|r7) + , (uint)(rA|r6|r2) + , (uint)(rQ|rJ|rT|r4) + , (uint)(rA|rT|r7|r5) + ] + ]; + + hands[2] = [ + + [ (uint)(r7|r3) + , (uint)(rQ|rJ|rT) + , (uint)(rA|rQ|r5|r4) + , (uint)(rT|r7|r5|r2) + ] + , [ (uint)(rQ|rT|r6) + , (uint)(r8|r7|r6) + , (uint)(rK|rJ|r9) + , (uint)(rA|rQ|r8|r4) + ] + , [ (uint)r5 + , (uint)(rA|r9|r5|r4|r3|r2) + , (uint)(r7|r6|r3|r2) + , (uint)(rK|r6) + ] + , [ (uint)(rA|rK|rJ|r9|r8|r4|r2) + , (uint)(rK) + , (uint)(rT|r8) + , (uint)(rJ|r9|r3) + ] + ]; + + pbn = [ "N:J652.A74..KT9642 AT9873.KJ5.T82.8 4.QT982.AKQ93.AJ KQ.63.J7654.Q753" + , "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" + ]; + + deals = new Deal[3]; + + for (int i = 0; i < deals.Length; i++) + deals[i] = new Deal + { + Trump = (int)Suit.Hearts + , First = 0 + , CurrentTrickSuit = new int[3] {0, 0, 0 } + , CurrentTrickRank = new int[3] {0, 0, 0 } + , RemainingCards = TestData.hands[i] + }; + + // dealPBN + dealsPBN = new DealPBN[3]; + + for (int i = 0; i < dealsPBN.Length; i++) + dealsPBN[i] = new DealPBN + { + Trump = (int)Suit.Hearts + , First = 0 + , CurrentTrickSuit = new int[3] {0, 0, 0 } + , CurrentTrickRank = new int[3] {0, 0, 0 } + , RemainingCards = TestData.pbn[i] + }; + + // boards and boardsPBN + boards = new Boards { NumberOfBoards = 3 }; + boardsPBN = new BoardsPBN {NumberOfBoards = 3 }; + + for (int i = 0; i < boardsPBN.NumberOfBoards; i++) + { + boards.Deals[i] = deals[i]; + boards.Target[i] = -1; + boards.Solutions[i] = 1; + boards.Modes[i] = 0; + + boardsPBN.Deals[i] = dealsPBN[i]; + boardsPBN.Target[i] = -1; + boardsPBN.Solutions[i] = 1; + boardsPBN.Modes[i] = 0; + } + + // ddTableDeal(PBN) + ddTableDeal = new() { Cards = hands[0] }; + ddTableDealPBN = new() {Cards = pbn[0] }; + + // ddTableDeals + ddTableDeals = new DdTableDeals + { + NumberOfTables = 3 + , Deals = new DdTableDeal[200] + }; + + for (int i = 0; i < ddTableDeals.NumberOfTables; i++) + ddTableDeals.Deals[i] = new DdTableDeal { Cards = hands[i] }; + + // ddTableDealsPBN + ddTableDealsPBN = new DdTableDealsPBN + { + NumberOfTables = 3 + , Deals = new DdTableDealPBN[200] + }; + + for (int i = 0; i < ddTableDealsPBN.NumberOfTables; i++) + ddTableDealsPBN.Deals[i] = new DdTableDealPBN { Cards = pbn[i] }; + + // playTraceBin and PBN + playTraceBin = new(); + playTraceBin.NumberOfCards = 3; + playTraceBin.Suits[0] = 3; + playTraceBin.Ranks[0] = 2; + + playTraceBin.Suits[1] = 3; + playTraceBin.Ranks[1] = 8; + + playTraceBin.Suits[2] = 3; + playTraceBin.Ranks[2] = 14; + + var playTraceBin1 = new PlayTraceBin(); + playTraceBin1.NumberOfCards = 1; + playTraceBin1.Suits[0] = 0; + playTraceBin1.Ranks[0] = 14; + + var playTraceBin2 = new PlayTraceBin(); + playTraceBin2.NumberOfCards = 4; + playTraceBin2.Suits[0] = 1; + playTraceBin2.Ranks[0] = 12; + + playTraceBin2.Suits[1] = 1; + playTraceBin2.Ranks[1] = 8; + + playTraceBin2.Suits[2] = 1; + playTraceBin2.Ranks[2] = 2; + + playTraceBin2.Suits[3] = 1; + playTraceBin2.Ranks[3] = 13; + + playTracePBN = new PlayTracePBN { NumberOfPlayedCards = 3, Cards = "C2C8CA" }; + var playTracePBN2 = new PlayTracePBN {NumberOfPlayedCards = 1, Cards = "SA" }; + var playTracePBN3 = new PlayTracePBN {NumberOfPlayedCards = 4, Cards = "HQH8H2HK" }; + + // playTracesBin and PBN + playTracesBin = new(); + playTracesBin.NumberOfBoards = 3; + playTracesBin.Plays[0] = playTraceBin; + playTracesBin.Plays[1] = playTraceBin1; + playTracesBin.Plays[2] = playTraceBin2; + + playTracesPBN = new(); + + playTracesPBN.NumberOfBoards = 3; + playTracesPBN.Plays[0] = playTracePBN; + playTracesPBN.Plays[1] = playTracePBN2; + playTracesPBN.Plays[2] = playTracePBN3; + } + } +} diff --git a/library/src/api/calc_dd_table.hpp b/library/src/api/calc_dd_table.hpp index 9782f259..9c904b2e 100644 --- a/library/src/api/calc_dd_table.hpp +++ b/library/src/api/calc_dd_table.hpp @@ -50,7 +50,7 @@ auto calc_dd_table( * DD-table calculations iterate all strains, so TT entry reuse across * calls may be limited depending on call patterns. */ -auto calc_dd_table( +EXTERN_C DLLEXPORT auto calc_dd_table( SolverContext& ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int; @@ -80,7 +80,7 @@ auto calc_dd_table_pbn( * @param table_results Output: double dummy table results * @return Error code (RETURN_NO_FAULT on success, RETURN_PBN_FAULT on parse error) */ -auto calc_dd_table_pbn( +EXTERN_C DLLEXPORT auto calc_dd_table_pbn( SolverContext& ctx, const DdTableDealPBN& table_deal_pbn, DdTableResults* table_results) -> int; diff --git a/library/src/api/calc_par.hpp b/library/src/api/calc_par.hpp index faeca2c9..064c1585 100644 --- a/library/src/api/calc_par.hpp +++ b/library/src/api/calc_par.hpp @@ -59,7 +59,7 @@ auto calc_par( * @param par_results Output: par score and contract strings * @return Error code (RETURN_NO_FAULT on success) */ -auto calc_par( +EXTERN_C DLLEXPORT auto calc_par( SolverContext& ctx, const DdTableDeal& table_deal, int vulnerable, diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp new file mode 100644 index 00000000..f7d89e59 --- /dev/null +++ b/library/src/api/dds_api.hpp @@ -0,0 +1,89 @@ +// dds_api.hpp +#pragma once +#include +#include +#include + +extern "C" { + + // Opaque handle type for C#/PInvoke + typedef SolverContext* DDS_SOLVER_CTX; + + // Creation + EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default() + { + SolverConfig cfg{}; + return new SolverContext(cfg); + } + + EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg) + { + return new SolverContext(cfg); + } + + // SolverContext Destruction + EXTERN_C DLLEXPORT void dds_destroy_solvercontext(DDS_SOLVER_CTX ctx) + { + delete ctx; + } + + // TT Configuration + EXTERN_C DLLEXPORT void dds_configure_tt(DDS_SOLVER_CTX ctx, + TTKind kind, + int defMB, int maxMB) + { + ctx->configure_tt(kind, defMB, maxMB); + } + + EXTERN_C DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, + int defMB, + int maxMB) + { + ctx->resize_tt(defMB, maxMB); + } + + EXTERN_C DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx) + { + ctx->clear_tt(); + } + + // Resets + EXTERN_C DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx) + { + ctx->reset_for_solve(); + } + + EXTERN_C DLLEXPORT void dds_reset_best_moves_lite(DDS_SOLVER_CTX ctx) + { + ctx->reset_best_moves_lite(); + } + + // Utilities – simple logging passthrough + EXTERN_C DLLEXPORT void dds_log_append(DDS_SOLVER_CTX ctx, + const char* msg) + { + ctx->utilities().log_append(std::string(msg)); + } + + EXTERN_C DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx) + { + ctx->utilities().log_clear(); + } + + EXTERN_C 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) ; + } + + +} diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index ceca3280..d5b5daa3 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -12,7 +13,7 @@ // 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) - : thr_(nullptr), cfg_(cfg) + : thr_(nullptr), cfg_(cfg) //TODO: remove thr_(nullptr) which is redundant { // Create an owned ThreadData instance and keep it in thr_. thr_ = std::make_shared(); diff --git a/solution/dds_native.vcxproj b/solution/dds_native.vcxproj index c58e703a..675ba727 100644 --- a/solution/dds_native.vcxproj +++ b/solution/dds_native.vcxproj @@ -17,12 +17,10 @@ NetCoreCProj ddsnative 10.0 - - + - DynamicLibrary true @@ -31,8 +29,6 @@ DynamicLibrary false - - @@ -46,7 +42,7 @@ - + $(BuildDir)\bin\$(platform)\$(Configuration)\ $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ @@ -106,6 +102,7 @@ + diff --git a/solution/dds_native.vcxproj.filters b/solution/dds_native.vcxproj.filters index fce01c52..af07ca4a 100644 --- a/solution/dds_native.vcxproj.filters +++ b/solution/dds_native.vcxproj.filters @@ -1,57 +1,43 @@ - - {F1000000-0000-4000-8000-000000000001} - {F1000000-0000-4000-8000-000000000002} - {F1000000-0000-4000-8000-000000000003} - {F1000000-0000-4000-8000-000000000004} - {F1000000-0000-4000-8000-000000000005} - {F1000000-0000-4000-8000-000000000006} - {F1000000-0000-4000-8000-000000000007} - {F1000000-0000-4000-8000-000000000008} - {F1000000-0000-4000-8000-000000000009} - {F1000000-0000-4000-8000-00000000000A} - - - library\src @@ -77,281 +63,214 @@ library\src - library\src\heuristic_sorting - library\src - library\src - library\src\lookup_tables - library\src\moves - library\src - library\src - library\src - library\src - library\src\solver_context - library\src - library\src - library\src - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\trans_table - library\src\trans_table - library\src\utility - - - + + library\src\api + library\src - library\src - library\src\api - library\src\api - library\src\api - library\src\api - library\src\api - library\src\api - library\src\api - library\src - library\src - library\src - library\src - library\src\heuristic_sorting - library\src\heuristic_sorting - library\src - library\src - library\src\lookup_tables - library\src\moves - library\src - library\src - library\src - library\src\solver_context - library\src - library\src - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\system - library\src\trans_table - library\src\trans_table - library\src\trans_table - library\src\utility - library\src\utility - - + \ No newline at end of file From d83b0c85ecfc68423659ac0da695bdcbb4d16897 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:13:58 +0200 Subject: [PATCH 027/157] DDS.Core is now fullt implemengted --- Build | 1 - dotnet.old/DDS_COre_Demo/DotNetDemo.csproj | 15 - dotnet.old/DDS_COre_Demo/Program.cs | 19 - .../Properties/launchSettings.json | 8 - dotnet.old/DDS_Core.slnx | 9 - dotnet.old/DDS_Core/AssemblyInfo.cpp | 20 - dotnet.old/DDS_Core/DDS_Core.cpp | 53 --- dotnet.old/DDS_Core/DDS_Core.h | 32 -- dotnet.old/DDS_Core/DDS_Core.vcxproj | 111 ----- dotnet.old/DDS_Core/DDS_Core.vcxproj.filters | 73 ---- dotnet.old/DDS_Core/ManagedDeal.cpp | 51 --- dotnet.old/DDS_Core/ManagedDeal.h | 20 - dotnet.old/DDS_Core/ManagedSolverConfig.cpp | 31 -- dotnet.old/DDS_Core/ManagedSolverConfig.h | 19 - dotnet.old/DDS_Core/ManagedSolverContext.cpp | 37 -- dotnet.old/DDS_Core/ManagedSolverContext.h | 25 -- dotnet.old/DDS_Core/ManagedTypes.cpp | 31 -- dotnet.old/DDS_Core/ManagedTypes.h | 29 -- dotnet.old/DDS_Core/Resource.h | 3 - dotnet.old/DDS_Core/app.ico | Bin 41395 -> 0 bytes dotnet.old/DDS_Core/app.rc | Bin 2558 -> 0 bytes dotnet.old/DDS_Core/pch.cpp | 5 - dotnet.old/DDS_Core/pch.h | 12 - .../examples/DotNetDemo/DotNetDemo.csproj | 11 - dotnet/DDS_Core/DDS.cs | 214 +++++----- dotnet/DDS_Core/DDS_Core.csproj | 1 + dotnet/DDS_Core/DataModel/ContractType.cs | 12 + dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs | 2 +- dotnet/DDS_Core/DataModel/Deal.cs | 2 - dotnet/DDS_Core/DataModel/ParResultsDealer.cs | 2 +- dotnet/DDS_Core/DataModel/ParTextResults.cs | 2 +- dotnet/DDS_Core/DataModel/SolverConfig.cs | 6 +- dotnet/DDS_Core/DataModel/SolverContext.cs | 4 +- dotnet/DDS_Core/DataModel/ddTableResults.cs | 2 +- dotnet/DDS_Core/DataModel/parResults.cs | 4 +- dotnet/DDS_Core/Helpers/Global.cs | 14 + dotnet/DDS_Core/Helpers/intArray5x4.cs | 99 +++++ .../DDS_Core/Helpers/intArray5xDebugView.cs | 23 + .../{intArray10x10.cs => stringArray10x10.cs} | 4 +- ...ebugView.cs => stringArray10xDebugView.cs} | 10 +- .../{intArray2x128.cs => stringArray2x128.cs} | 4 +- .../{intArray2x16.cs => stringArray2x16.cs} | 4 +- ...DebugView.cs => stringArray2xDebugView.cs} | 4 +- dotnet/DDS_Core/Native/DdsNative.cs | 28 +- dotnet/DDS_Core_Demo/Program.cs | 397 +++++++++--------- dotnet/DDS_Core_Demo/TestData.cs | 152 +++++-- 46 files changed, 621 insertions(+), 984 deletions(-) delete mode 120000 Build delete mode 100644 dotnet.old/DDS_COre_Demo/DotNetDemo.csproj delete mode 100644 dotnet.old/DDS_COre_Demo/Program.cs delete mode 100644 dotnet.old/DDS_COre_Demo/Properties/launchSettings.json delete mode 100644 dotnet.old/DDS_Core.slnx delete mode 100644 dotnet.old/DDS_Core/AssemblyInfo.cpp delete mode 100644 dotnet.old/DDS_Core/DDS_Core.cpp delete mode 100644 dotnet.old/DDS_Core/DDS_Core.h delete mode 100644 dotnet.old/DDS_Core/DDS_Core.vcxproj delete mode 100644 dotnet.old/DDS_Core/DDS_Core.vcxproj.filters delete mode 100644 dotnet.old/DDS_Core/ManagedDeal.cpp delete mode 100644 dotnet.old/DDS_Core/ManagedDeal.h delete mode 100644 dotnet.old/DDS_Core/ManagedSolverConfig.cpp delete mode 100644 dotnet.old/DDS_Core/ManagedSolverConfig.h delete mode 100644 dotnet.old/DDS_Core/ManagedSolverContext.cpp delete mode 100644 dotnet.old/DDS_Core/ManagedSolverContext.h delete mode 100644 dotnet.old/DDS_Core/ManagedTypes.cpp delete mode 100644 dotnet.old/DDS_Core/ManagedTypes.h delete mode 100644 dotnet.old/DDS_Core/Resource.h delete mode 100644 dotnet.old/DDS_Core/app.ico delete mode 100644 dotnet.old/DDS_Core/app.rc delete mode 100644 dotnet.old/DDS_Core/pch.cpp delete mode 100644 dotnet.old/DDS_Core/pch.h delete mode 100644 dotnet.old/examples/DotNetDemo/DotNetDemo.csproj create mode 100644 dotnet/DDS_Core/Helpers/Global.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray5x4.cs create mode 100644 dotnet/DDS_Core/Helpers/intArray5xDebugView.cs rename dotnet/DDS_Core/Helpers/{intArray10x10.cs => stringArray10x10.cs} (95%) rename dotnet/DDS_Core/Helpers/{intArray10xDebugView.cs => stringArray10xDebugView.cs} (81%) rename dotnet/DDS_Core/Helpers/{intArray2x128.cs => stringArray2x128.cs} (95%) rename dotnet/DDS_Core/Helpers/{intArray2x16.cs => stringArray2x16.cs} (95%) rename dotnet/DDS_Core/Helpers/{intArray2xDebugView.cs => stringArray2xDebugView.cs} (65%) diff --git a/Build b/Build deleted file mode 120000 index 16ce6a9b..00000000 --- a/Build +++ /dev/null @@ -1 +0,0 @@ -../../../../Build/DDS3 \ No newline at end of file diff --git a/dotnet.old/DDS_COre_Demo/DotNetDemo.csproj b/dotnet.old/DDS_COre_Demo/DotNetDemo.csproj deleted file mode 100644 index 675281c8..00000000 --- a/dotnet.old/DDS_COre_Demo/DotNetDemo.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net8.0-windows10.0.22621 - enable - enable - x64 - - - - - - - diff --git a/dotnet.old/DDS_COre_Demo/Program.cs b/dotnet.old/DDS_COre_Demo/Program.cs deleted file mode 100644 index d856b257..00000000 --- a/dotnet.old/DDS_COre_Demo/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using DDSCore; - -namespace DotNetDemo; - -internal class Program -{ - static void Main(string[] args) - { - var dds = new DDSCore.dds(); - var deal = new DDSCore.ManagedDeal(); - var cfg = new DDSCore.ManagedSolverConfig(); - var ctx = new ManagedSolverContext(cfg); - var fut = new DDSCore.ManagedFutureTricks(); - - var rc = dds.SolveBoard(ctx, deal, 10, 1, 0, fut); - - Console.WriteLine("Hello, World!"); - } -} diff --git a/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json b/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json deleted file mode 100644 index 6c1a2314..00000000 --- a/dotnet.old/DDS_COre_Demo/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "DotNetDemo": { - "commandName": "Project", - "debugEngines": "managed-dotnet,native" - } - } -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core.slnx b/dotnet.old/DDS_Core.slnx deleted file mode 100644 index 977311d2..00000000 --- a/dotnet.old/DDS_Core.slnx +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dotnet.old/DDS_Core/AssemblyInfo.cpp b/dotnet.old/DDS_Core/AssemblyInfo.cpp deleted file mode 100644 index e1ef2875..00000000 --- a/dotnet.old/DDS_Core/AssemblyInfo.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "pch.h" - -using namespace System; -using namespace System::Reflection; -using namespace System::Runtime::CompilerServices; -using namespace System::Runtime::InteropServices; -using namespace System::Security::Permissions; - -[assembly:AssemblyTitleAttribute(L"DDSCore")]; -[assembly:AssemblyDescriptionAttribute(L"")]; -[assembly:AssemblyConfigurationAttribute(L"")]; -[assembly:AssemblyCompanyAttribute(L"")]; -[assembly:AssemblyProductAttribute(L"DDSCore")]; -[assembly:AssemblyCopyrightAttribute(L"Copyright (c) 2026")]; -[assembly:AssemblyTrademarkAttribute(L"")]; -[assembly:AssemblyCultureAttribute(L"")]; - -[assembly:AssemblyVersionAttribute(L"1.0.*")]; - -[assembly:ComVisible(false)]; diff --git a/dotnet.old/DDS_Core/DDS_Core.cpp b/dotnet.old/DDS_Core/DDS_Core.cpp deleted file mode 100644 index 9cda6c7e..00000000 --- a/dotnet.old/DDS_Core/DDS_Core.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "pch.h" -#include "DDS_Core.h" - -namespace DDSCore { - - - dds::dds() - { - } - - dds::~dds() - { - } - - auto dds::SolveBoard(ManagedSolverContext^ ctx, - const ManagedDeal^ dl, - int target, - int solutions, - int mode, - ManagedFutureTricks^ futp) -> int - { - // 1. Set up native structures for the call - Deal nativeDeal = ManagedDeal::ToNative(dl); - FutureTricks nativeFt; - - // 2. Call native SolveBoard - int result = ::SolveBoard( - *ctx->native_, // SolverContext& - nativeDeal, // const Deal& - target, - solutions, - mode, - &nativeFt // FutureTricks* - ); - - // 3. Convert native → managed - ManagedFutureTricks^ mf = ManagedFutureTricks::FromNative(nativeFt); - - // 4. Copy back to output parameter - futp->Nodes = mf->Nodes; - futp->Cards = mf->Cards; - - for (int i = 0; i < mf->Cards; i++) - { - futp->Suit[i] = mf->Suit[i]; - futp->Rank[i] = mf->Rank[i]; - futp->Equals[i] = mf->Equals[i]; - futp->Score[i] = mf->Score[i]; - } - - return result; - } -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/DDS_Core.h b/dotnet.old/DDS_Core/DDS_Core.h deleted file mode 100644 index fde8d608..00000000 --- a/dotnet.old/DDS_Core/DDS_Core.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include "ManagedTypes.h" -#include "ManagedSolverContext.h" -#include "ManagedDeal.h" - -using namespace System; - -namespace DDSCore { - public ref class dds - { - - public: - dds(); - ~dds(); - - auto SolveBoard( - ManagedSolverContext^ ctx, - const ManagedDeal^ dl, - int target, - int solutions, - int mode, - ManagedFutureTricks^ futp) -> int; - }; - - - - -} diff --git a/dotnet.old/DDS_Core/DDS_Core.vcxproj b/dotnet.old/DDS_Core/DDS_Core.vcxproj deleted file mode 100644 index 5c5d4550..00000000 --- a/dotnet.old/DDS_Core/DDS_Core.vcxproj +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 18.0 - true - {CC2C338E-6224-0611-BE35-9DA0815F38DD} - NetCoreCProj - DDSCore - 10.0 - net8.0 - 7.0 - - - - DynamicLibrary - true - v143 - NetCore - Unicode - - - DynamicLibrary - false - v145 - NetCore - Unicode - - - - - - - - - - - - - - - ..\..\Build\bin\$(platform)\$(Configuration)\ - ..\..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ - - - - Use - pch.h - Level3 - _DEBUG;%(PreprocessorDefinitions) - ../../library/src;%(AdditionalIncludeDirectories) - stdcpp20 - - - dds.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - Use - pch.h - Level3 - NDEBUG;%(PreprocessorDefinitions) - ../../library/src;%(AdditionalIncludeDirectories) - - - dds.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - - - - - - - - - - - - - - - - Create - Create - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet.old/DDS_Core/DDS_Core.vcxproj.filters b/dotnet.old/DDS_Core/DDS_Core.vcxproj.filters deleted file mode 100644 index ebab145f..00000000 --- a/dotnet.old/DDS_Core/DDS_Core.vcxproj.filters +++ /dev/null @@ -1,73 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Resource Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedDeal.cpp b/dotnet.old/DDS_Core/ManagedDeal.cpp deleted file mode 100644 index d7e997c7..00000000 --- a/dotnet.old/DDS_Core/ManagedDeal.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "pch.h" -#include "ManagedDeal.h" - -namespace DDSCore { - ManagedDeal::ManagedDeal() - { - CurrentTrickSuit = gcnew array(3); - CurrentTrickRank = gcnew array(3); - RemainCards = gcnew array(DDS_HANDS, DDS_SUITS); - } - - ManagedDeal^ ManagedDeal::FromNative(const ::Deal& d) - { - ManagedDeal^ md = gcnew ManagedDeal(); - - md->Trump = d.trump; - md->First = d.first; - - for (int i = 0; i < 3; i++) - { - md->CurrentTrickSuit[i] = d.currentTrickSuit[i]; - md->CurrentTrickRank[i] = d.currentTrickRank[i]; - } - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - md->RemainCards[h, s] = d.remainCards[h][s]; - - return md; - } - - ::Deal ManagedDeal::ToNative(const ManagedDeal^ md) - { - ::Deal d; - - d.trump = md->Trump; - d.first = md->First; - - for (int i = 0; i < 3; i++) - { - d.currentTrickSuit[i] = md->CurrentTrickSuit[i]; - d.currentTrickRank[i] = md->CurrentTrickRank[i]; - } - - for (int h = 0; h < DDS_HANDS; h++) - for (int s = 0; s < DDS_SUITS; s++) - d.remainCards[h][s] = md->RemainCards[h, s]; - - return d; - } -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedDeal.h b/dotnet.old/DDS_Core/ManagedDeal.h deleted file mode 100644 index 894bd93b..00000000 --- a/dotnet.old/DDS_Core/ManagedDeal.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include - -namespace DDSCore { - public ref class ManagedDeal - { - public: - int Trump; - int First; - - array^ CurrentTrickSuit; - array^ CurrentTrickRank; - array^ RemainCards; - - ManagedDeal(); - internal: - static ManagedDeal^ FromNative(const ::Deal& d); - static ::Deal ToNative(const ManagedDeal^ md); - }; -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverConfig.cpp b/dotnet.old/DDS_Core/ManagedSolverConfig.cpp deleted file mode 100644 index 0925c7ae..00000000 --- a/dotnet.old/DDS_Core/ManagedSolverConfig.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "pch.h" -#include "ManagedTypes.h" -#include "ManagedSolverConfig.h" - -namespace DDSCore { - inline ManagedSolverConfig::ManagedSolverConfig() - { - TTKind = ManagedTTKind::Large; - DefaultMemoryMB = 0; - MaximumMemoryMB = 0; - } - - inline ::SolverConfig ManagedSolverConfig::ToNative(ManagedSolverConfig^ cfg) - { - ::SolverConfig nc; - nc.tt_kind_ = static_cast<::TTKind>(cfg->TTKind); - nc.tt_mem_default_mb_ = cfg->DefaultMemoryMB; - nc.tt_mem_maximum_mb_ = cfg->MaximumMemoryMB; - return nc; - } - - inline ManagedSolverConfig^ ManagedSolverConfig::FromNative(const ::SolverConfig& cfg) - { - ManagedSolverConfig^ mc = gcnew ManagedSolverConfig(); - mc->TTKind = static_cast(cfg.tt_kind_); - mc->DefaultMemoryMB = cfg.tt_mem_default_mb_; - mc->MaximumMemoryMB = cfg.tt_mem_maximum_mb_; - return mc; - } -} diff --git a/dotnet.old/DDS_Core/ManagedSolverConfig.h b/dotnet.old/DDS_Core/ManagedSolverConfig.h deleted file mode 100644 index 902916f4..00000000 --- a/dotnet.old/DDS_Core/ManagedSolverConfig.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "ManagedTypes.h" - -namespace DDSCore { - public ref class ManagedSolverConfig - { - public: - ManagedTTKind TTKind; - int DefaultMemoryMB; - int MaximumMemoryMB; - - ManagedSolverConfig(); - - internal: - static ::SolverConfig ToNative(ManagedSolverConfig^ cfg); - - static ManagedSolverConfig^ FromNative(const ::SolverConfig& cfg); - }; -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverContext.cpp b/dotnet.old/DDS_Core/ManagedSolverContext.cpp deleted file mode 100644 index 3a6ddaae..00000000 --- a/dotnet.old/DDS_Core/ManagedSolverContext.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "pch.h" -#include "ManagedSolverContext.h" - -namespace DDSCore { - inline DDSCore::ManagedSolverContext::ManagedSolverContext(ManagedSolverConfig^ cfg) - { - native_ = new SolverContext(ManagedSolverConfig::ToNative(cfg)); - } - - inline DDSCore::ManagedSolverContext::~ManagedSolverContext() - { - this->!ManagedSolverContext(); - } - - inline DDSCore::ManagedSolverContext::!ManagedSolverContext() - { - delete native_; - native_ = nullptr; - } - - inline array^ DDSCore::ManagedSolverContext::GetLog() - { - auto& buf = native_->utilities().log_buffer(); - int count = static_cast(buf.size()); - auto arr = gcnew array(count); - - for (int i = 0; i < count; i++) - arr[i] = gcnew System::String(buf[i].c_str()); - - return arr; - } - - inline void DDSCore::ManagedSolverContext::ClearLog() - { - native_->utilities().log_clear(); - } -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedSolverContext.h b/dotnet.old/DDS_Core/ManagedSolverContext.h deleted file mode 100644 index 3515ca44..00000000 --- a/dotnet.old/DDS_Core/ManagedSolverContext.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -//using namespace System; - -#include "ManagedSolverConfig.h" - -namespace DDSCore { - public ref class ManagedSolverContext - { - public: - ManagedSolverContext(ManagedSolverConfig^ cfg); - - ~ManagedSolverContext(); - - !ManagedSolverContext(); - - - array^ GetLog(); - - void ClearLog(); - - - internal: - SolverContext* native_; - }; -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/ManagedTypes.cpp b/dotnet.old/DDS_Core/ManagedTypes.cpp deleted file mode 100644 index 64a0560b..00000000 --- a/dotnet.old/DDS_Core/ManagedTypes.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "pch.h" -#include "ManagedTypes.h" - -namespace DDSCore { - inline ManagedFutureTricks::ManagedFutureTricks() - { - // DDS returnerer maks 13 entries - Suit = gcnew array(13); - Rank = gcnew array(13); - Equals = gcnew array(13); - Score = gcnew array(13); - } - - inline ManagedFutureTricks^ ManagedFutureTricks::FromNative(const ::FutureTricks& ft) - { - ManagedFutureTricks^ m = gcnew ManagedFutureTricks(); - - m->Nodes = ft.nodes; - m->Cards = ft.cards; - - for (int i = 0; i < ft.cards; i++) - { - m->Suit[i] = ft.suit[i]; - m->Rank[i] = ft.rank[i]; - m->Equals[i] = ft.equals[i]; - m->Score[i] = ft.score[i]; - } - - return m; - } -} diff --git a/dotnet.old/DDS_Core/ManagedTypes.h b/dotnet.old/DDS_Core/ManagedTypes.h deleted file mode 100644 index fcdb268d..00000000 --- a/dotnet.old/DDS_Core/ManagedTypes.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include - -namespace DDSCore { - public enum class ManagedTTKind - { - Small = (int)::TTKind::Small, - Large = (int)::TTKind::Large - }; - - public ref class ManagedFutureTricks - { - public: - int Nodes; - int Cards; - - array^ Suit; // length = Cards - array^ Rank; // length = Cards - array^ Equals; // length = Cards - array^ Score; // length = Cards - - ManagedFutureTricks(); - - internal: - static ManagedFutureTricks^ FromNative(const FutureTricks& ft); - - }; - -} \ No newline at end of file diff --git a/dotnet.old/DDS_Core/Resource.h b/dotnet.old/DDS_Core/Resource.h deleted file mode 100644 index d5ac7c42..00000000 --- a/dotnet.old/DDS_Core/Resource.h +++ /dev/null @@ -1,3 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by app.rc diff --git a/dotnet.old/DDS_Core/app.ico b/dotnet.old/DDS_Core/app.ico deleted file mode 100644 index 789d7ccbb56ed92f1bb005054ec7e67257ddc37e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41395 zcmeHQZA=_R7=DjCkZTKK8e`L%_DrfaQ35q-NU0yk2`Okwv@x+7+O*QNL}I~`Y8$Mc zweceoTVpWz1KLD?5Tj6HjAC->RZTH5(3+Y^qR}Q*QIUg61#Btj%&kM`7H)6nj+0&P zne4DLH}A~6^UO0d@9xgBL=JKjcMi&?At%w>EL>Qq#oKdt>C$N+(Rua|At$Lyk0H7#>diEi}F0wek;+HU7|b|XXU)xB+Bzp4Xf+HR-)G) zs@#~fX!#;mY)(aa>1JM9q{b|Es@mJmiXcbB=8Zn;=)3uM7IEz^*;GQ*b!7464yF^i z>&qW&Ajxqo~ z`o$M|S6|Ksp*!JynDLQBBox`-#bTKXNNLW6@ zgkx${f)e9J7HU zW>BoNwY0UbHnH&K*Qp-nz06Nvr^+meah=M<;eY@TNCN~S6LgB(nf&r-v~=^*=c0n&Ojh75tz+RnK5HVPvXohh+3)UAs(fW-b|Sp*ELAr(aAhg;CWNC zNg-csQaH8gOTI~A>mQ?;HLt$xf4z2e-!~6*cy1{Dt+(p06<@t|uzZicMMS~oQ0<3D z0x!1grj?D(wFOt)E;k)Ed9+Tg3NEWIIN#a3e!Td!+uXBZlaG*-5o1m2`SF#ewPVG* z`nqqrRCRT#YNfyP4BvY&bah{6bzfHSM$0IhOhc`lceYNT^Phg?sL%8lI?Ns?&V`*c zRdBocy$a9gPIeW|Bs$T^;GR!b6_sshE;@YY_gp~UPvB*7Kl6N3QpB;1NN_*^2mk>f zpdrBaQ2Eo`hgtjKfdM$cIKcK+E@&TbtfT$t|Aou}RsOSm)?qt)eP5#e#_0u9G5^8-Gva`rFPQ&e|Jj)XdOjO*ANC*YKRsXU%zfB@ z$=iQ=KG;33I{(?dy?Xu8el`17kDuM+=zsLT5eMwf?|AwfwVkKM%WwB|v>)T&hy!-# zcRc-#+K%yWJT~HChjicv4%m(ZY5o2e-U9>zKp=e)5dSYJ-Z^~{vUkhl^=tHZCp?kH z`q>9+;D7)SxP}0;>rdL;uu&S+@th^rgWXn^{AFF=V`xmP6 zLQm8mZTksaRPj`m7xe&t;4gDP@IsYOs`8>9xo#d7zpIv~Dlh6m{oudM0l^DZKB>x! zdgQug{)_ZfpHctp{7*IRqI}>VjlUj`bctK)VsIXmk>B_FKdTq@^7|D0wuFay@!ess zi$&(x%>|#u+@X3Fb@P0QtXIgd%~cBlKUMBwFX)@Ts-VrG-!fi7 zn=|^{lG%iLU}b4D-N4h)b3NmhZXDZC3wCq3uOe@&a=xRfT9Ml)+mSDe=PY|qCmQKg zXXO<`@Kh{u}P)77e+J`b^ZYfCz>(RWgv~!7P?33(2w$i>2#H#>Fc|_Qh z^$Vg|uu6y;F)Q)4&`b6oBW$sbY4hW1i<6cC#7@YRVoj;;1hOW_dS{Ze%gCRTQO?P0 zWC0DOPI5U zsW~oLw6^XIsan`<>ynio16ff0_sBq)>sz3jP1&!{CS&XIJM!nhN%CW}eNvC;0#Epr I_*Z2A2KzE2Bme*a diff --git a/dotnet.old/DDS_Core/pch.cpp b/dotnet.old/DDS_Core/pch.cpp deleted file mode 100644 index 64b7eef6..00000000 --- a/dotnet.old/DDS_Core/pch.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/dotnet.old/DDS_Core/pch.h b/dotnet.old/DDS_Core/pch.h deleted file mode 100644 index 9d715b06..00000000 --- a/dotnet.old/DDS_Core/pch.h +++ /dev/null @@ -1,12 +0,0 @@ -// pch.h: This is a precompiled header file. -// Files listed below are compiled only once, improving build performance for future builds. -// This also affects IntelliSense performance, including code completion and many code browsing features. -// However, files listed here are ALL re-compiled if any one of them is updated between builds. -// Do not add files here that you will be updating frequently as this negates the performance advantage. - -#ifndef PCH_H -#define PCH_H - -// add headers that you want to pre-compile here - -#endif //PCH_H diff --git a/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj b/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj deleted file mode 100644 index 57f19002..00000000 --- a/dotnet.old/examples/DotNetDemo/DotNetDemo.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - Exe - net8.0-windows10.0.22621 - enable - enable - x64 - - - diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index ec278835..6f5ba3f9 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -3,6 +3,7 @@ using System.Text; using DDS_Core.Helpers; using DDS_Core.Native; +using static System.Formats.Asn1.AsnWriter; namespace DDS_Core; @@ -33,7 +34,7 @@ public class DDS /// Maximum number of threads to use. [Obsolete("Use SolverContext instead.")] public void SetMaxThreads(int userThreads) - => DdsNative.SetMaxThreads(userThreads); + => DdsNative.SetMaxThreads(userThreads); /// /// Sets the threading backend used by the solver. @@ -79,7 +80,7 @@ public int SetThreading(in int code) [Obsolete("Use SolverContext instead.")] public void SetResources(in int maxMemoryMB, in int maxThreads) - => DdsNative.SetResources(maxMemoryMB, maxThreads); + => DdsNative.SetResources(maxMemoryMB, maxThreads); /// /// Frees memory used by the solver. @@ -97,7 +98,7 @@ public void SetResources(in int maxMemoryMB, in int maxThreads) /// /// //[Obsolete("Use SolverContext instead.")] - public void FreeMemory() => DdsNative.FreeMemory(); + public void FreeMemory() => DdsNative.FreeMemory(); #endregion #region ====== Single Board Solving ====== @@ -140,21 +141,21 @@ public int SolveBoard( in Deal dl /// Index of the thread to use. /// 1 on success, error code otherwise. [Obsolete("Use SolverContext instead.")] - public int SolveBoardPBN( in DealPBN pbn + public int SolveBoard( in DealPBN pbn , in int target , in int solutions , in int mode , out FutureTricks fut , int threadIndex = 0) { - var rc = DdsNative.SolveBoardPBN( pbn + var rc = DdsNative.SolveBoardPBN( pbn , target , solutions , mode , out fut , threadIndex); - ThrowIfError(rc, nameof(SolveBoardPBN)); + ThrowIfError(rc, nameof(SolveBoard)); return rc; } #endregion @@ -196,42 +197,42 @@ public int SolveAllBoardsBin( in Boards bop return rc; } - public int SolveAllChunks( in BoardsPBN bop - , out SolvedBoards solved - , in int chunkSize) - { - //Note: To step into c++ code you must set a break in c++? - var rc = DdsNative.SolveAllChunks( bop - , out solved - , chunkSize); + //public int SolveAllChunks( in BoardsPBN bop + // , out SolvedBoards solved + // , in int chunkSize) + //{ + // //Note: To step into c++ code you must set a break in c++? + // var rc = DdsNative.SolveAllChunks( bop + // , out solved + // , chunkSize); - ThrowIfError(rc, nameof(SolveAllChunks)); - return rc; - } + // ThrowIfError(rc, nameof(SolveAllChunks)); + // return rc; + //} - public int SolveAllChunksBin( in Boards bop - , out SolvedBoards solved - , in int chunkSize) - { - //Note: To step into c++ code you must set a break in c++? - var rc = DdsNative.SolveAllChunksBin( bop - , out solved - , chunkSize); - ThrowIfError(rc, nameof(SolveAllChunksBin)); - return rc; - } + //public int SolveAllChunksBin( in Boards bop + // , out SolvedBoards solved + // , in int chunkSize) + //{ + // //Note: To step into c++ code you must set a break in c++? + // var rc = DdsNative.SolveAllChunksBin( bop + // , out solved + // , chunkSize); + // ThrowIfError(rc, nameof(SolveAllChunksBin)); + // return rc; + //} - public int SolveAllChunksPBN( in BoardsPBN bop - , out SolvedBoards solved - , in int chunkSize) - { - //Note: To step into c++ code you must set a break in c++? - var rc = DdsNative.SolveAllChunksPBN( bop - , out solved - , chunkSize); - ThrowIfError(rc, nameof(SolveAllChunksPBN)); - return rc; - } + //public int SolveAllChunkN( in BoardsPBN bop + // , out SolvedBoards solved + // , in int chunkSize) + //{ + // //Note: To step into c++ code you must set a break in c++? + // var rc = DdsNative.SolveAllChunksPBN( bop + // , out solved + // , chunkSize); + // ThrowIfError(rc, nameof(SolveAllChunksPBN)); + // return rc; + //} #endregion #region ====== Double Dummy Table Calculation ====== @@ -257,18 +258,18 @@ public int CalcDdTable( in DdTableDeal deal /// PBN deal for which to calculate the table. /// The result table. /// 1 on success, error code otherwise. - public int CalcDdTablePBN( in DdTableDealPBN tableDealPBN + public int CalcDdTable( in DdTableDealPBN tableDealPBN , out DdTableResults table) { var rc = DdsNative.CalcDDtablePBN( tableDealPBN , out table); - ThrowIfError(rc, nameof(CalcDdTablePBN)); + ThrowIfError(rc, nameof(CalcDdTable)); return rc; } /// - /// Calculates double dummy tables for multiple deals. + /// Calculates double dummy tables and par contracts for multiple deals. /// /// Multiple deals. /// Analysis mode. @@ -293,7 +294,7 @@ public int CalcAllTables( in DdTableDeals deals } /// - /// Calculates double dummy tables for multiple PBN deals. + /// Calculates double dummy tables and par contracts for multiple PBN deals. /// /// Multiple PBN deals. /// Analysis mode. @@ -301,29 +302,39 @@ public int CalcAllTables( in DdTableDeals deals /// The result tables. /// The par results. /// 1 on success, error code otherwise. - public int CalcAllTablesPBN( in DdTableDealsPBN dealsp + public int CalcAllTables( in DdTableDealsPBN dealsp , in int mode , in intArray5 trumpFilter , out DdTablesResult ResTables , out AllParResults parResults) { - var rc = DdsNative.CalcAllTablesPBN( dealsp + var rc = DdsNative.CalcAllTablesPBN( dealsp , mode , trumpFilter , out ResTables , out parResults); - ThrowIfError(rc, nameof(CalcAllTablesPBN)); + ThrowIfError(rc, nameof(CalcAllTables)); return rc; } #endregion #region ====== Par Score Calculation ====== + /// + /// Computes the par score and contracts for both sides for a given double dummy results. + /// + /// This function analyzes the double dummy table results and determines the par score + /// and contracts for both North-South and East-West, based on vulnerability. + /// + /// + /// + /// + /// public int Par( in DdTableResults table , out ParResults pres , in int vulnerable) { - var rc = DdsNative.Par( table + var rc = DdsNative.Par( table , out pres , vulnerable); @@ -365,7 +376,7 @@ public int CalcPar( in DdTableDeal tableDeal , out DdTableResults tableResults , out ParResults parResults) { - var rc = DdsNative.CalcPar( tableDeal + var rc = DdsNative.CalcPar( tableDeal , vulnerable , out tableResults , out parResults); @@ -402,110 +413,125 @@ public int CalcPar( in DdTableDeal tableDeal /// /// Error code (RETURN_NO_FAULT on success). /// - public int CalcParPBN( in DdTableDealPBN tableDealPBN + public int CalcPar( in DdTableDealPBN tableDealPBN , out DdTableResults tableResults , in int vulnerable , out ParResults parResults) { - var rc = DdsNative.CalcParPBN( tableDealPBN + var rc = DdsNative.CalcParPBN( tableDealPBN , out tableResults , vulnerable , out parResults); - ThrowIfError(rc, nameof(CalcParPBN)); + ThrowIfError(rc, nameof(CalcPar)); return rc; } - public int SidesPar( in DdTableResults table - , out ParResultsDealers sidesRes - , in int vulnerable) + public int ParSide( in DdTableResults table + , out ParResultsDealers sidesRes + , in int vulnerable) { - var rc = DdsNative.SidesPar( table + var rc = DdsNative.SidesPar( table , out sidesRes , vulnerable); - ThrowIfError(rc, nameof(SidesPar)); + ThrowIfError(rc, nameof(ParSide)); return rc; } - public int DealerPar( in DdTableResults table + /// + /// Calculates par score and contracts for a specific dealer and vulnerability. + /// + /// + /// + /// + /// + /// + public int ParDealer( in DdTableResults table , out ParResultsDealer pres , in int dealer , in int vulnerable) { - var rc = DdsNative.DealerPar( table + var rc = DdsNative.DealerPar( table , out pres , dealer , vulnerable); - ThrowIfError(rc, nameof(DealerPar)); + ThrowIfError(rc, nameof(ParDealer)); return rc; } - - public int DealerParBin( in DdTableResults table - , out ParResultsMaster pres - , in int dealer - , in int vulnerable) + /// + /// Calculates par score and contract types for both sides for a specific dealer and vulnerability. + /// + /// + /// + /// + /// + /// + public int DealerParBothSides( in DdTableResults table + , out ParResultsMaster pres + , in int dealer + , in int vulnerable) { var rc = DdsNative.DealerParBin( table , out pres , dealer , vulnerable); - ThrowIfError(rc, nameof(DealerParBin)); + ThrowIfError(rc, nameof(DealerParBothSides)); return rc; } - public int SidesParBin( in DdTableResults table - , out ParResultsMasters sidesRes - , in int vulnerable) + public int ParAll( in DdTableResults table + , out ParResultsMasters sidesRes + , in int vulnerable) { var rc = DdsNative.SidesParBin( table , out sidesRes , vulnerable); - ThrowIfError(rc, nameof(SidesParBin)); + ThrowIfError(rc, nameof(ParAll)); return rc; } #endregion #region ====== Par Text Conversion ====== - public int ConvertToDealerTextFormat( in ParResultsMaster pres - , out string resp) + public int ConvertToTextFormat( in ParResultsMaster pres + , out string resp) { var str = new StringBuilder(80); var rc = DdsNative.ConvertToDealerTextFormat( pres , str); - ThrowIfError(rc, nameof(ConvertToDealerTextFormat)); + ThrowIfError(rc, nameof(ConvertToTextFormat)); resp = str.ToString(); return rc; } - public int ConvertToSidesTextFormat( in ParResultsMasters pres - , out ParTextResults resp) + public int ConvertToTextFormat( in ParResultsMasters pres + , out ParTextResults resp) { var rc = DdsNative.ConvertToSidesTextFormat( pres , out resp); - ThrowIfError(rc, nameof(ConvertToSidesTextFormat)); + ThrowIfError(rc, nameof(ConvertToTextFormat)); return rc; } #endregion #region ====== Play Analysis ====== - public int AnalysePlayBin( in Deal dl - , in PlayTraceBin play - , out SolvedPlay solved - , in int thrId) + public int AnalysePlay( in Deal dl + , in PlayTraceBin play + , out SolvedPlay solved + , in int thrId) { var rc = DdsNative.AnalysePlayBin( dl , in play , out solved , thrId); - ThrowIfError(rc, nameof(AnalysePlayBin)); + ThrowIfError(rc, nameof(AnalysePlay)); return rc; } - public int AnalysePlayPBN( in DealPBN dlPBN + public int AnalysePlay( in DealPBN dlPBN , in PlayTracePBN playPBN , out SolvedPlay solved , in int thrId) @@ -514,25 +540,25 @@ public int AnalysePlayPBN( in DealPBN dlPBN , in playPBN , out solved , thrId); - ThrowIfError(rc, nameof(AnalysePlayPBN)); + ThrowIfError(rc, nameof(AnalysePlay)); return rc; } - public int AnalyseAllPlaysBin( in Boards bop - , in PlayTracesBin plp - , out SolvedPlays solved - , in int chunkSize) + public int AnalyseAllPlays( in Boards bop + , in PlayTracesBin plp + , out SolvedPlays solved + , in int chunkSize) { try { solved = new(); var rc = DdsNative.AnalyseAllPlaysBin( bop - , plp + , plp , out solved , chunkSize); - ThrowIfError(rc, nameof(AnalyseAllPlaysBin)); + ThrowIfError(rc, nameof(AnalyseAllPlays)); return rc; } @@ -543,17 +569,17 @@ public int AnalyseAllPlaysBin( in Boards bop } } - public int AnalyseAllPlaysPBN( in BoardsPBN bopPBN - , in PlayTracesPBN plpPBN - , out SolvedPlays solved - , in int chunkSize) + public int AnalyseAllPlays( in BoardsPBN bopPBN + , in PlayTracesPBN plpPBN + , out SolvedPlays solved + , in int chunkSize) { solved = new(); var rc = DdsNative.AnalyseAllPlaysPBN( bopPBN - , plpPBN + , plpPBN , out solved , chunkSize); - ThrowIfError(rc, nameof(AnalyseAllPlaysPBN)); + ThrowIfError(rc, nameof(AnalyseAllPlays)); return rc; } diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj index 2f35b124..b6e7c17a 100644 --- a/dotnet/DDS_Core/DDS_Core.csproj +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -23,6 +23,7 @@ + diff --git a/dotnet/DDS_Core/DataModel/ContractType.cs b/dotnet/DDS_Core/DataModel/ContractType.cs index 0b341ddf..8f02d573 100644 --- a/dotnet/DDS_Core/DataModel/ContractType.cs +++ b/dotnet/DDS_Core/DataModel/ContractType.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using DDS_Core.Helpers; namespace DDS_Core; @@ -23,4 +24,15 @@ public struct ContractType /// Seats playing contract (0 = N, 1 = E, 2 = S, 3 = W, 4 = NS, 5 = EW). public int Seats; + + public override string ToString() + { + if (UnderTricks > 0) + return $"{Global.Seats[Seats]}:{Level}{Global.Denoms[Denomination]}(-{UnderTricks})"; + else + if (OverTricks > 0) + return $"{Global.Seats[Seats]}:{Level}{Global.Denoms[Denomination]}(+{OverTricks})"; + else + return $"{Global.Seats[Seats]}:{Level}{Global.Denoms[Denomination]}(=)"; + } } diff --git a/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs index a8bfee15..2b044dea 100644 --- a/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs +++ b/dotnet/DDS_Core/DataModel/DdTableDealsPBN.cs @@ -14,8 +14,8 @@ public struct DdTableDealsPBN /// Array of PBN deals (up to MAXNOOFTABLES * DDS_STRAINS). public DdTableDealsPBNArray Deals; - #region Nested Types + #region Nested Types [InlineArray(DdsConstants.DdsStrains)] public struct DdTableDealsPBNArray { diff --git a/dotnet/DDS_Core/DataModel/Deal.cs b/dotnet/DDS_Core/DataModel/Deal.cs index d84d7fc1..6ede0840 100644 --- a/dotnet/DDS_Core/DataModel/Deal.cs +++ b/dotnet/DDS_Core/DataModel/Deal.cs @@ -29,6 +29,4 @@ public struct Deal /// Remaining cards for each hand and suit (4 hands × 4 suits = 16 elements). public FourHands RemainingCards; - - } diff --git a/dotnet/DDS_Core/DataModel/ParResultsDealer.cs b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs index b5d0a584..e54e8334 100644 --- a/dotnet/DDS_Core/DataModel/ParResultsDealer.cs +++ b/dotnet/DDS_Core/DataModel/ParResultsDealer.cs @@ -19,5 +19,5 @@ public struct ParResultsDealer public int Score; /// Par contract text strings (10 entries, max 10 chars each). - public intArray10x10 Contracts; + public stringArray10x10 Contracts; } diff --git a/dotnet/DDS_Core/DataModel/ParTextResults.cs b/dotnet/DDS_Core/DataModel/ParTextResults.cs index 08d5e340..c37e7671 100644 --- a/dotnet/DDS_Core/DataModel/ParTextResults.cs +++ b/dotnet/DDS_Core/DataModel/ParTextResults.cs @@ -14,7 +14,7 @@ public struct ParTextResults /// Short par text for NS and EW using 2D indexing (2 x 128 chars). /// Access: par_text[side, index] where side=0(NS) or 1(EW) /// - public intArray2x128 ParTextStrings; + public stringArray2x128 ParTextStrings; /// True if equal (doesn't matter who starts bidding), false otherwise. [MarshalAs(UnmanagedType.I1)] diff --git a/dotnet/DDS_Core/DataModel/SolverConfig.cs b/dotnet/DDS_Core/DataModel/SolverConfig.cs index 5aa94353..1d05a509 100644 --- a/dotnet/DDS_Core/DataModel/SolverConfig.cs +++ b/dotnet/DDS_Core/DataModel/SolverConfig.cs @@ -6,8 +6,8 @@ namespace DDS_Core; public struct SolverConfig { public TTKind TTKind; - public int DefaultMemoryMB; - public int MaximumMemoryMB; + public int DefaultMemoryMB; + public int MaximumMemoryMB; public SolverConfig() { @@ -16,7 +16,7 @@ public SolverConfig() public SolverConfig(TTKind tTKind, int defaultMemoryMB, int maximumMemoryMB) { - TTKind = tTKind; + TTKind = tTKind; DefaultMemoryMB = defaultMemoryMB; MaximumMemoryMB = maximumMemoryMB; } diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index 881e506c..47f85731 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -79,14 +79,14 @@ public int CalcDdTable( in DdTableDeal table_deal return rc; } - public int CalcDdTablePBN( in DdTableDealPBN table_deal_pbn + public int CalcDdTable( in DdTableDealPBN table_deal_pbn , out DdTableResults table_results) { var rc = DdsNative.calc_dd_table_pbn( Handle , in table_deal_pbn , out table_results); - ThrowIfError(rc, nameof(CalcDdTablePBN)); + ThrowIfError(rc, nameof(CalcDdTable)); return rc; } diff --git a/dotnet/DDS_Core/DataModel/ddTableResults.cs b/dotnet/DDS_Core/DataModel/ddTableResults.cs index 1750d54e..7915ceca 100644 --- a/dotnet/DDS_Core/DataModel/ddTableResults.cs +++ b/dotnet/DDS_Core/DataModel/ddTableResults.cs @@ -15,5 +15,5 @@ public struct DdTableResults /// Tricks per strain and hand using 2D indexing. /// Access: res_table[strain, hand] where strain=0-4, hand=0-3 /// - public ResultsTable ResultsTable; + public intArray5x4 ResultsTable; } diff --git a/dotnet/DDS_Core/DataModel/parResults.cs b/dotnet/DDS_Core/DataModel/parResults.cs index 8d8cafa5..13bd5157 100644 --- a/dotnet/DDS_Core/DataModel/parResults.cs +++ b/dotnet/DDS_Core/DataModel/parResults.cs @@ -15,12 +15,12 @@ public struct ParResults /// Par score strings for NS and EW (2 entries, max 16 chars each). /// Access: par_score[side, index] where side=0(NS) or 1(EW) /// - public intArray2x16 ParScores; + public stringArray2x16 ParScores; /// /// Par contract strings for NS and EW (2 entries, max 128 chars each). /// Access: par_contracts_string[side, index] where side=0(NS) or 1(EW) /// - public intArray2x128 ParContractStrings; + public stringArray2x128 ParContractStrings; } diff --git a/dotnet/DDS_Core/Helpers/Global.cs b/dotnet/DDS_Core/Helpers/Global.cs new file mode 100644 index 00000000..57def295 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/Global.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DDS_Core.Helpers +{ + internal static class Global + { + internal static string[] Denoms = ["S","H", "D", "C", "NT" ]; + internal static string[] Seats = ["N","E", "S", "W", "NS", "EW" ]; + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray5x4.cs b/dotnet/DDS_Core/Helpers/intArray5x4.cs new file mode 100644 index 00000000..1b1b6107 --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray5x4.cs @@ -0,0 +1,99 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace DDS_Core; + +[DebuggerDisplay("{ToString(),nq}")] +[DebuggerTypeProxy(typeof(intArray5xDebugView))] +[StructLayout(LayoutKind.Sequential)] +public unsafe struct intArray5x4 : IBuffer +{ + public const int ROWS = 5; + public const int COLS = 4; + public const int SIZE = ROWS * COLS; + + private fixed int data[SIZE]; + + public Span AsSpan() + => MemoryMarshal.CreateSpan(ref data[0], SIZE); + + public Span RowAsSpan(int row) + { + if (row >= ROWS || row < 0) + throw new ArgumentOutOfRangeException(nameof(row)); + + // ref to first byte in the array for the specified row + ref int start = ref data[row * COLS]; + + return MemoryMarshal.CreateSpan(ref start, COLS); + } + + public Span this[int row] + { + get + { + if ((uint)row >= ROWS) + throw new IndexOutOfRangeException(); + + // ref to the first element in the specified row + ref int start = ref data[row * COLS]; + + // correct way to create a span over the fixed buffer + return MemoryMarshal.CreateSpan(ref start, COLS); + } + } + + public ref int this[int row, int col] + { + get + { + if (row >= ROWS || row < 0 + || col >= COLS || col < 0) + throw new IndexOutOfRangeException(); + + return ref data[row * COLS + col]; + } + } + + public static implicit operator intArray5x4(int[] src) + { + var buf = new intArray5x4(); + + for (int c = 0; c < 16; c++) + buf.data[c] = src[c]; + + return buf; + } + + public static implicit operator intArray5x4(int[][] src) + { + var buf = new intArray5x4(); + + int k = 0; + + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) + buf.data[k++] = src[r][c]; + + return buf; + } + + public string GetString(int row) + => string.Join(",", RowAsSpan(row).ToArray()); + + //public override string ToString() + // => "["+string.Join("],[", Enumerable.Range(0, ROWS).Select(GetString)) +"]"; + public override string ToString() + { + var parts = new string[ROWS]; + + for (int r = 0; r < ROWS; r++) + { + var row = RowAsSpan(r); + parts[r] = "[" + string.Join(",", row.ToArray()) + "]"; + } + + return string.Join(" | ", parts); + } +} diff --git a/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs b/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs new file mode 100644 index 00000000..9b05cd7d --- /dev/null +++ b/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs @@ -0,0 +1,23 @@ +using System; + +namespace DDS_Core +{ + internal sealed class intArray5xDebugView where T : IBuffer + { + private readonly T _buffer; + + public intArray5xDebugView(T buffer) + { + _buffer = buffer; + } + + public string Row0 => _buffer.GetString(0); + public string Row1 => _buffer.GetString(1); + public string Row2 => _buffer.GetString(2); + public string Row3 => _buffer.GetString(3); + + public string Row4 => _buffer.GetString(4); + + } +} + diff --git a/dotnet/DDS_Core/Helpers/intArray10x10.cs b/dotnet/DDS_Core/Helpers/stringArray10x10.cs similarity index 95% rename from dotnet/DDS_Core/Helpers/intArray10x10.cs rename to dotnet/DDS_Core/Helpers/stringArray10x10.cs index 081650f9..ee8022eb 100644 --- a/dotnet/DDS_Core/Helpers/intArray10x10.cs +++ b/dotnet/DDS_Core/Helpers/stringArray10x10.cs @@ -12,9 +12,9 @@ namespace DDS_Core; /// and padded with nulls if shorter than max length. /// [DebuggerDisplay("{ToString()}")] -[DebuggerTypeProxy(typeof(intArray10xDebugView))] +[DebuggerTypeProxy(typeof(stringArray10xDebugView))] [StructLayout(LayoutKind.Sequential)] -public unsafe struct intArray10x10 : IBuffer +public unsafe struct stringArray10x10 : IBuffer { public const int ROWS = 10; public const int COLS = 10; diff --git a/dotnet/DDS_Core/Helpers/intArray10xDebugView.cs b/dotnet/DDS_Core/Helpers/stringArray10xDebugView.cs similarity index 81% rename from dotnet/DDS_Core/Helpers/intArray10xDebugView.cs rename to dotnet/DDS_Core/Helpers/stringArray10xDebugView.cs index 8b23c22e..3ed078c0 100644 --- a/dotnet/DDS_Core/Helpers/intArray10xDebugView.cs +++ b/dotnet/DDS_Core/Helpers/stringArray10xDebugView.cs @@ -1,12 +1,12 @@ using System; -namespace DDS_Core -{ - internal sealed class intArray10xDebugView where T : IBuffer +namespace DDS_Core; + + internal sealed class stringArray10xDebugView where T : IBuffer { private readonly T _buffer; - public intArray10xDebugView(T buffer) + public stringArray10xDebugView(T buffer) { _buffer = buffer; } @@ -25,4 +25,4 @@ public intArray10xDebugView(T buffer) public string Row8 => _buffer.GetString(8); public string Row9 => _buffer.GetString(9); } -} + diff --git a/dotnet/DDS_Core/Helpers/intArray2x128.cs b/dotnet/DDS_Core/Helpers/stringArray2x128.cs similarity index 95% rename from dotnet/DDS_Core/Helpers/intArray2x128.cs rename to dotnet/DDS_Core/Helpers/stringArray2x128.cs index 48c3522c..979da2f6 100644 --- a/dotnet/DDS_Core/Helpers/intArray2x128.cs +++ b/dotnet/DDS_Core/Helpers/stringArray2x128.cs @@ -12,9 +12,9 @@ namespace DDS_Core; /// and padded with nulls if shorter than max length. /// [DebuggerDisplay("{ToString()}")] -[DebuggerTypeProxy(typeof(intArray2xDebugView))] +[DebuggerTypeProxy(typeof(stringArray2xDebugView))] [StructLayout(LayoutKind.Sequential)] -public unsafe struct intArray2x128 : IBuffer +public unsafe struct stringArray2x128 : IBuffer { public const int ROWS = 2; public const int COLS = 128; diff --git a/dotnet/DDS_Core/Helpers/intArray2x16.cs b/dotnet/DDS_Core/Helpers/stringArray2x16.cs similarity index 95% rename from dotnet/DDS_Core/Helpers/intArray2x16.cs rename to dotnet/DDS_Core/Helpers/stringArray2x16.cs index f3aa7b41..88eb1a50 100644 --- a/dotnet/DDS_Core/Helpers/intArray2x16.cs +++ b/dotnet/DDS_Core/Helpers/stringArray2x16.cs @@ -12,9 +12,9 @@ namespace DDS_Core; /// and padded with nulls if shorter than max length. /// [DebuggerDisplay("{ToString()}")] -[DebuggerTypeProxy(typeof(intArray2xDebugView))] +[DebuggerTypeProxy(typeof(stringArray2xDebugView))] [StructLayout(LayoutKind.Sequential)] -public unsafe struct intArray2x16 : IBuffer +public unsafe struct stringArray2x16 : IBuffer { public const int ROWS = 2; public const int COLS = 16; diff --git a/dotnet/DDS_Core/Helpers/intArray2xDebugView.cs b/dotnet/DDS_Core/Helpers/stringArray2xDebugView.cs similarity index 65% rename from dotnet/DDS_Core/Helpers/intArray2xDebugView.cs rename to dotnet/DDS_Core/Helpers/stringArray2xDebugView.cs index 0fcaab46..d2543245 100644 --- a/dotnet/DDS_Core/Helpers/intArray2xDebugView.cs +++ b/dotnet/DDS_Core/Helpers/stringArray2xDebugView.cs @@ -2,11 +2,11 @@ namespace DDS_Core; -internal sealed class intArray2xDebugView where T : IBuffer +internal sealed class stringArray2xDebugView where T : IBuffer { private readonly T _buffer; - public intArray2xDebugView(T buffer) + public stringArray2xDebugView(T buffer) { _buffer = buffer; } diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index c17eab9d..1929c651 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -138,20 +138,20 @@ public static extern int SolveAllBoards( in BoardsPBN bop public static extern int SolveAllBoardsBin( in Boards bop , out SolvedBoards solved); - [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int SolveAllChunks( in BoardsPBN bop - , out SolvedBoards solved - , int chunkSize); - - [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int SolveAllChunksBin( in Boards bop - , out SolvedBoards solved - , int chunkSize); - - [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int SolveAllChunksPBN( in BoardsPBN bop - , out SolvedBoards solved - , int chunkSize); + //[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int SolveAllChunks( in BoardsPBN bop + // , out SolvedBoards solved + // , int chunkSize); + + //[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int SolveAllChunksBin( in Boards bop + // , out SolvedBoards solved + // , int chunkSize); + + //[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + //public static extern int SolveAllChunksPBN( in BoardsPBN bop + // , out SolvedBoards solved + // , int chunkSize); #endregion #region ====== Double Dummy Table Calculation ====== diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index ce707c2c..cbbbe3c0 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -39,39 +39,46 @@ static void Main(string[] args) return; } + int vulnability =2; // 0=none, 1=both, 2=NS, 3=EW + int dealer = 1; // 0=N, 1=E, 2=S, 3=W + // - dds.SetMaxThreads(1); doSolveBoard(dds, TestData.deals[0]); - doSolveBoardPBN(dds, TestData.dealsPBN[0]); + doSolveBoard(dds, TestData.dealsPBN[0]); + + doSolveAllBoards(dds, TestData.boards); doSolveAllBoards(dds, TestData.boardsPBN); - doSolveAllBoardsBin(dds, TestData.boards); - doSolveAllChunks(dds, TestData.boardsPBN, 10); - doSolveAllChunksBin(dds, TestData.boards, 10); - doSolveAllChunksPBN(dds, TestData.boardsPBN, 10); + // Obsolete methods - not recommended for use as they are not optimized and will be removed in future versions + //doSolveAllChunks(dds, TestData.boards, 10); + //doSolveAllChunks(dds, TestData.boardsPBN, 10); + //doSolveAllChunksPBN(dds, TestData.boardsPBN, 10); + //// doCalcDdTable(dds, TestData.ddTableDeal); - doCalcDdTablePBN(dds, TestData.ddTableDealPBN); + doCalcDdTable(dds, TestData.ddTableDealPBN); + doCalcAllTables(dds, TestData.ddTableDeals); - doCalcAllTablesPBN(dds, TestData.ddTableDealsPBN); - doPar(dds, TestData.ddTableResults, 0); - - doCalcPar(dds, TestData.ddTableDeal); - doCalcParPBN(dds, TestData.ddTableDealPBN); - doDealerPar(dds, TestData.ddTableResults); - doDealerParBin(dds, TestData.ddTableResults); - doConvertToDealerTextFormat(dds, TestData.parResultsMaster); - doConvertToSidesTextFormat(dds, TestData.parResultsMasters); + doCalcAllTables(dds, TestData.ddTableDealsPBN); // - // These two methods should be tested together - doSidesParBin(dds, TestData.ddTableResults); - doAnalysePlayBin(dds, TestData.deals[0], TestData.parResultsMasters, TestData.playTraceBin); + doCalcPar(dds, TestData.ddTableDeal, vulnability); + doCalcPar(dds, TestData.ddTableDealPBN, vulnability); + doPar(dds, TestData.ddTableResults, vulnability); + + doParDealer(dds, TestData.ddTableResults, dealer, vulnability); + doParDealerBothSides(dds, TestData.ddTableResults, dealer, vulnability); + + doParAll(dds, TestData.ddTableResults, vulnability); + doParSide(dds, TestData.ddTableResults, vulnability); + + doConvertToTextFormat(dds, TestData.parResultsMaster); + doConvertToTextFormat(dds, TestData.parResultsMasters); + + doAnalysePlay(dds, TestData.deals[0], TestData.parResultsMasters, TestData.playTraceBin); + doAnalysePlay(dds, TestData.dealsPBN[0], TestData.parResultsMasters, TestData.playTracePBN); // - // These two methods should be tested together - doSidesPar(dds, TestData.ddTableResults); - doAnalysePlayPBN(dds, TestData.dealsPBN[0], TestData.parResultsMasters, TestData.playTracePBN); + doAnalyseAllPlays(dds, TestData.boards, TestData.playTracesBin); + doAnalyseAllPlays(dds, TestData.boardsPBN, TestData.playTracesPBN); // - doAnalyseAllPlayBin(dds, TestData.boards, TestData.playTracesBin); - doAnalyseAllPlayPBN(dds, TestData.boardsPBN, TestData.playTracesPBN); doGetDDSInfo(dds); doErrorMessage(dds, -14); @@ -89,8 +96,7 @@ static void Main(string[] args) doSolveBoardV3(dds, ctx, TestData.deals[0]); doCalcDdTableV3(dds, ctx, TestData.ddTableDeal); - doCalcDdTablePBNV3(dds, ctx, TestData.ddTableDealPBN); - doCalcPar(dds, TestData.ddTableDeal); + doCalcDdTableV3(dds, ctx, TestData.ddTableDealPBN); } #endregion @@ -139,10 +145,10 @@ private static void doSolveBoardV3(DDS dds, SolverContext ctx, Deal deal) DisplayTricks(); } - private static void doSolveBoardPBN(DDS dds, DealPBN deal) + private static void doSolveBoard(DDS dds, DealPBN deal) { // SolveBoard: Loop through all possible contracts and first players - Console.WriteLine($"SolveBoardPBN"); + Console.WriteLine($"SolveBoard PBN"); tricks = new int[5, 4]; for (deal.Trump = 0; deal.Trump < 5; deal.Trump++) @@ -150,7 +156,7 @@ private static void doSolveBoardPBN(DDS dds, DealPBN deal) for (deal.First = 0; deal.First < 4; deal.First++) { var decl =(deal.First + 3) & 3; - var rc = dds.SolveBoardPBN(deal, -1, 1, 0, out FutureTricks fut); + var rc = dds.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); // record the number of tricks for declarer tricks[deal.Trump, decl] = 13 - fut.Score[0]; @@ -163,7 +169,7 @@ private static void doSolveBoardPBN(DDS dds, DealPBN deal) private static void doSolveAllBoards(DDS dds, BoardsPBN boards) { - Console.WriteLine($"SolveAllBoards"); + Console.WriteLine($"SolveAllBoards PBN"); tricks = new int[5, 4]; for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) @@ -181,9 +187,9 @@ private static void doSolveAllBoards(DDS dds, BoardsPBN boards) DisplayTricks(); } - private static void doSolveAllBoardsBin(DDS dds, Boards boards) + private static void doSolveAllBoards(DDS dds, Boards boards) { - Console.WriteLine($"SolveAllBoardsBin"); + Console.WriteLine($"SolveAllBoards"); tricks = new int[5, 4]; // Here we loop over all trump suits and dealers which is't the normal thing to do! @@ -203,66 +209,65 @@ private static void doSolveAllBoardsBin(DDS dds, Boards boards) DisplayTricks(); } - private static void doSolveAllChunks(DDS dds, BoardsPBN boards, int chunkSize) - { - Console.WriteLine($"SolveAllChunks"); - tricks = new int[5, 4]; + //private static void doSolveAllChunks(DDS dds, BoardsPBN boards, int chunkSize) + //{ + // Console.WriteLine($"SolveAllChunks PBN"); + // tricks = new int[5, 4]; - for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) - for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) - { - var decl =(boards.Deals[0].First + 3) & 3; - var rc = dds.SolveAllChunks(boards, out SolvedBoards solved, chunkSize); + // for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + // for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + // { + // var decl =(boards.Deals[0].First + 3) & 3; + // var rc = dds.SolveAllChunks(boards, out SolvedBoards solved, chunkSize); - // record the number of tricks for declarer - tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + // // record the number of tricks for declarer + // tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; - dds.FreeMemory(); - } + // dds.FreeMemory(); + // } - DisplayTricks(); - } + // DisplayTricks(); + //} - private static void doSolveAllChunksBin(DDS dds, Boards boards, int chunkSize) - { - Console.WriteLine($"SolveAllChunksBin"); - tricks = new int[5, 4]; + //private static void doSolveAllChunks(DDS dds, Boards boards, int chunkSize) + //{ + // Console.WriteLine($"SolveAllChunks"); + // tricks = new int[5, 4]; - for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) - for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) - { - var decl =(boards.Deals[0].First + 3) & 3; - var rc = dds.SolveAllChunksBin(boards, out SolvedBoards solved, chunkSize); + // for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + // for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + // { + // var decl =(boards.Deals[0].First + 3) & 3; + // var rc = dds.SolveAllChunksBin(boards, out SolvedBoards solved, chunkSize); - // record the number of tricks for declarer - tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + // // record the number of tricks for declarer + // tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; - dds.FreeMemory(); - } + // dds.FreeMemory(); + // } - DisplayTricks(); - } - - private static void doSolveAllChunksPBN(DDS dds, BoardsPBN boards, int chunkSize) - { - Console.WriteLine($"SolveAllChunksPBN"); - tricks = new int[5, 4]; + // DisplayTricks(); + //} - for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) - for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) - { - var decl =(boards.Deals[0].First + 3) & 3; - var rc = dds.SolveAllChunksPBN(boards, out SolvedBoards solved, chunkSize); + //private static void doSolveAllChunksPBN(DDS dds, BoardsPBN boards, int chunkSize) + //{ + // Console.WriteLine($"SolveAllChunks PBN"); + // tricks = new int[5, 4]; - // record the number of tricks for declarer - tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; + // for (boards.Deals[0].Trump = 0; boards.Deals[0].Trump < 5; boards.Deals[0].Trump++) + // for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) + // { + // var decl =(boards.Deals[0].First + 3) & 3; + // var rc = dds.SolveAllChunksPBN(boards, out SolvedBoards solved, chunkSize); - dds.FreeMemory(); - } + // // record the number of tricks for declarer + // tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; - DisplayTricks(); - } + // dds.FreeMemory(); + // } + // DisplayTricks(); + //} private static void doCalcDdTable(DDS dds, DdTableDeal ddTableDeal) { Console.WriteLine($"CalcDdTable"); @@ -283,6 +288,8 @@ private static void doCalcDdTable(DDS dds, DdTableDeal ddTableDeal) dds.FreeMemory(); DisplayTricks(); + + TestData.ddTableResults = results; } private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal) @@ -307,13 +314,13 @@ private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDeal ddTa DisplayTricks(); } - private static void doCalcDdTablePBN( - DDS dds, DdTableDealPBN ddTableDeal) + private static void doCalcDdTable( + DDS dds, DdTableDealPBN ddTableDeal) { - Console.WriteLine($"CalcDdTablePBN"); + Console.WriteLine($"CalcDdTable PBN"); tricks = new int[5, 4]; - var rc = dds.CalcDdTablePBN(ddTableDeal, out DdTableResults results); + var rc = dds.CalcDdTable(ddTableDeal, out DdTableResults results); for (var trump = 0; trump < 5; trump++) @@ -332,12 +339,12 @@ private static void doCalcDdTablePBN( TestData.ddTableResults = results; } - private static void doCalcDdTablePBNV3(DDS dds, SolverContext ctx, DdTableDealPBN ddTableDeal) + private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDealPBN ddTableDeal) { - Console.WriteLine($"CalcDdTablePBN V3"); + Console.WriteLine($"CalcDdTable PBN V3"); tricks = new int[5, 4]; - var rc = ctx.CalcDdTablePBN(ddTableDeal, out DdTableResults results); + var rc = ctx.CalcDdTable(ddTableDeal, out DdTableResults results); for (var trump = 0; trump < 5; trump++) @@ -383,13 +390,13 @@ private static void doCalcAllTables(DDS dds, DdTableDeals ddTableDeals) DisplayTricks(); } - private static void doCalcAllTablesPBN(DDS dds, DdTableDealsPBN ddTableDeals) + private static void doCalcAllTables(DDS dds, DdTableDealsPBN ddTableDeals) { - Console.WriteLine($"CalcAllTablesPBN"); + Console.WriteLine($"CalcAllTables PBN"); tricks = new int[5, 4]; intArray5 trumpFilter = new(); - var rc = dds.CalcAllTablesPBN( in ddTableDeals + var rc = dds.CalcAllTables( in ddTableDeals , 0 , trumpFilter , out DdTablesResult results @@ -410,14 +417,14 @@ private static void doCalcAllTablesPBN(DDS dds, DdTableDealsPBN ddTableDeals) DisplayTricks(); } - private static void doPar(DDS dds, DdTableResults tableResults, int vulnerable) + private static void doPar(DDS dds, DdTableResults tableResults, int vulnability) { Console.WriteLine($"Par"); tricks = new int[5, 4]; var rc = dds.Par( in tableResults , out ParResults results - , vulnerable); + , vulnability); Console.WriteLine(results.ParContractStrings); Console.WriteLine(results.ParScores); @@ -426,13 +433,13 @@ private static void doPar(DDS dds, DdTableResults tableResults, int vulnerable) Console.WriteLine(); } - private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal) + private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal, int vulnability) { Console.WriteLine($"CalcPar"); tricks = new int[5, 4]; var rc = dds.CalcPar( in ddTableDeal - , 0 + , vulnability , out DdTableResults tResults , out ParResults results ); @@ -451,6 +458,7 @@ private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal) dds.FreeMemory(); DisplayTricks(); + //Console.WriteLine(); } private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal) @@ -466,28 +474,28 @@ private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableD Console.WriteLine(results.ParContractStrings); Console.WriteLine(results.ParScores); - for (var trump = 0; trump < 5; trump++) - for (var first = 0; first < 4; first++) - { - var decl =(first + 3) & 3; - - // record the number of tricks for declarer - tricks[trump, decl] = tResults.ResultsTable[trump, decl]; - } + //for (var trump = 0; trump < 5; trump++) + // for (var first = 0; first < 4; first++) + // { + // var decl =(first + 3) & 3; + // // record the number of tricks for declarer + // tricks[trump, decl] = tResults.ResultsTable[trump, decl]; + // } ctx.ResetForSolve(); //dds.FreeMemory(); - DisplayTricks(); + //DisplayTricks(); + Console.WriteLine(); } - private static void doCalcParPBN(DDS dds, DdTableDealPBN ddTableDeal) + private static void doCalcPar(DDS dds, DdTableDealPBN ddTableDeal, int vulnability) { - Console.WriteLine($"CalcParPBN"); + Console.WriteLine($"CalcPar PBN"); tricks = new int[5, 4]; - var rc = dds.CalcParPBN( in ddTableDeal + var rc = dds.CalcPar( in ddTableDeal , out DdTableResults tResults - , 0 + , vulnability , out ParResults results ); Console.WriteLine(results.ParContractStrings); @@ -505,87 +513,39 @@ private static void doCalcParPBN(DDS dds, DdTableDealPBN ddTableDeal) dds.FreeMemory(); DisplayTricks(); + //Console.WriteLine(); } - private static void doSidesPar(DDS dds, DdTableResults tResults) + private static void doParSide(DDS dds, DdTableResults tResults, int vulnability) { - Console.WriteLine($"SidesPar"); + Console.WriteLine($"ParSide -> ParResultsDealers"); tricks = new int[5, 4]; - var rc = dds.SidesPar( in tResults - , out ParResultsDealers results - , 0); + var rc = dds.ParSide( in tResults + , out ParResultsDealers results + , vulnability); Console.WriteLine(results[0].NumberOfContracts); Console.WriteLine(results[0].Score); Console.WriteLine(results[0].Contracts); - Console.WriteLine(); + Console.WriteLine(results[1].NumberOfContracts); Console.WriteLine(results[1].Score); Console.WriteLine(results[1].Contracts); - Console.WriteLine(""); - dds.FreeMemory(); - } - - private static void doDealerPar(DDS dds, DdTableResults tResults) - { - Console.WriteLine($"DealerPar"); - tricks = new int[5, 4]; - - var rc = dds.DealerPar( in tResults - , out ParResultsDealer results - , 0 - , 3); - - Console.WriteLine(results.NumberOfContracts); - Console.WriteLine(results.Score); - Console.WriteLine(results.Contracts); - Console.WriteLine(""); - - dds.FreeMemory(); - } - - private static void doDealerParBin(DDS dds, DdTableResults tResults) - { - Console.WriteLine($"DealerParBin"); - tricks = new int[5, 4]; - - var rc = dds.DealerParBin( in tResults - , out ParResultsMaster results - , 0 - , 3); - - Console.WriteLine(results.Number); - Console.WriteLine(results.Score); - - for (int i = 0; i < results.Number; i++) - { - var contract =results.Contracts[i]; - var d = c[contract.Denomination]; - - if (contract.UnderTricks > 0) - Console.WriteLine($"{contract.Level}{d}(-{contract.UnderTricks})"); - else - if (contract.OverTricks > 0) - Console.WriteLine($"{contract.Level}{d}(+{contract.OverTricks})"); - else - Console.WriteLine($"{contract.Level}{d}(=)"); - } + Console.WriteLine(); - Console.WriteLine(""); - TestData.parResultsMaster = results; dds.FreeMemory(); } - private static void doSidesParBin(DDS dds, DdTableResults tResults) + private static void doParAll(DDS dds, DdTableResults tResults, int vulnability) { - Console.WriteLine($"SidesParBin"); + Console.WriteLine($"ParAll -> ParResultsMasters "); tricks = new int[5, 4]; - var rc = dds.SidesParBin( in tResults - , out ParResultsMasters results - , 1); + var rc = dds.ParAll( in tResults + , out ParResultsMasters results + , vulnability); for (int s = 0; s < 2; s++) { @@ -614,38 +574,79 @@ private static void doSidesParBin(DDS dds, DdTableResults tResults) dds.FreeMemory(); } - private static void doConvertToDealerTextFormat(DDS dds, ParResultsMaster tResults) + private static void doParDealer(DDS dds, DdTableResults tResults, int dealer, int vulnability) { - Console.WriteLine($"ConvertToDealerTextFormat"); + Console.WriteLine($"ParDealer"); + tricks = new int[5, 4]; + + var rc = dds.ParDealer( in tResults + , out ParResultsDealer results + , dealer + , vulnability + ); + + Console.WriteLine(results.NumberOfContracts); + Console.WriteLine(results.Score); + Console.WriteLine(results.Contracts); + Console.WriteLine(""); + + dds.FreeMemory(); + } + + private static void doParDealerBothSides( DDS dds, DdTableResults tResults, int dealer + , int vulnability) + { + Console.WriteLine($"DealerParBothSides"); + tricks = new int[5, 4]; + + var rc = dds.DealerParBothSides( in tResults + , out ParResultsMaster results + , dealer + , vulnability); + + Console.WriteLine(results.Number); + Console.WriteLine(results.Score); + + for (int i = 0; i < results.Number; i++) + Console.WriteLine(results.Contracts[i]); + + Console.WriteLine(""); + TestData.parResultsMaster = results; + dds.FreeMemory(); + } + + private static void doConvertToTextFormat(DDS dds, ParResultsMaster tResults) + { + Console.WriteLine($"ConvertToTextFormat ParResultsMaster"); - var rc = dds.ConvertToDealerTextFormat( in tResults - , out string str ); + var rc = dds.ConvertToTextFormat( in tResults + , out string str ); Console.WriteLine(str); Console.WriteLine(""); dds.FreeMemory(); } - private static void doConvertToSidesTextFormat(DDS dds, ParResultsMasters tResults) + private static void doConvertToTextFormat(DDS dds, ParResultsMasters tResults) { - Console.WriteLine($"ConvertToSidesTextFormat"); + Console.WriteLine($"ConvertToTextFormat ParResultsMasters"); - var rc = dds.ConvertToSidesTextFormat( in tResults - , out ParTextResults str ); + var rc = dds.ConvertToTextFormat( in tResults + , out ParTextResults str ); Console.WriteLine(str.ParTextStrings); Console.WriteLine(""); dds.FreeMemory(); } - private static void doAnalysePlayBin(DDS dds, Deal deal, ParResultsMasters tResults, PlayTraceBin ptrace) + private static void doAnalysePlay(DDS dds, Deal deal, ParResultsMasters tResults, PlayTraceBin ptrace) { - Console.WriteLine($"AnalysePlayBin"); + Console.WriteLine($"AnalysePlay PlayTracesBin"); - var rc = dds.AnalysePlayBin( in deal - , in ptrace - , out SolvedPlay solved - , 0); + var rc = dds.AnalysePlay( in deal + , in ptrace + , out SolvedPlay solved + , 0); for (int i = 0; i <= ptrace.NumberOfCards; i++) Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); @@ -654,11 +655,11 @@ private static void doAnalysePlayBin(DDS dds, Deal deal, ParResultsMasters tResu dds.FreeMemory(); } - private static void doAnalysePlayPBN(DDS dds, DealPBN deal, ParResultsMasters tResults, PlayTracePBN ptrace) + private static void doAnalysePlay(DDS dds, DealPBN deal, ParResultsMasters tResults, PlayTracePBN ptrace) { - Console.WriteLine($"AnalysePlayPBN"); + Console.WriteLine($"AnalysePlay PlayTracePBN"); - var rc = dds.AnalysePlayPBN( in deal + var rc = dds.AnalysePlay( in deal , in ptrace , out SolvedPlay solved , 0); @@ -670,15 +671,15 @@ private static void doAnalysePlayPBN(DDS dds, DealPBN deal, ParResultsMasters tR dds.FreeMemory(); } - private static void doAnalyseAllPlayBin(DDS dds, Boards boards, PlayTracesBin ptrace) + private static void doAnalyseAllPlays(DDS dds, Boards boards, PlayTracesBin ptrace) { if (!isPerformanceTest) - Console.WriteLine($"AnalysePlayBin"); + Console.WriteLine($"AnalysePlay PlayTracesBin"); - var rc = dds.AnalyseAllPlaysBin( in boards - , in ptrace - , out SolvedPlays solved - , 0); + var rc = dds.AnalyseAllPlays( in boards + , in ptrace + , out SolvedPlays solved + , 0); if (isPerformanceTest) { @@ -691,14 +692,14 @@ private static void doAnalyseAllPlayBin(DDS dds, Boards boards, PlayTracesBin pt dds.FreeMemory(); } - private static void doAnalyseAllPlayPBN(DDS dds, BoardsPBN boards, PlayTracesPBN ptrace) + private static void doAnalyseAllPlays(DDS dds, BoardsPBN boards, PlayTracesPBN ptrace) { - Console.WriteLine($"AnalyseAllPlayPBN"); + Console.WriteLine($"AnalyseAllPlay PlayTracesPBN"); - var rc = dds.AnalyseAllPlaysPBN( in boards - , in ptrace - , out SolvedPlays solved - , 0); + var rc = dds.AnalyseAllPlays( in boards + , in ptrace + , out SolvedPlays solved + , 0); for (int i = 0; i <= ptrace.Plays[0].NumberOfPlayedCards; i++) Console.WriteLine($"{i,2}: {solved.Solved[0].Tricks[i]}"); @@ -768,7 +769,6 @@ private static void BenchmarkAnalyseAllPlaysBin(DDS dds) Console.WriteLine("\n=== Benchmark Complete ==="); Console.Out.Flush(); } - catch (Exception ex) { Console.WriteLine($"\nERROR: {ex.GetType().Name}: {ex.Message}"); @@ -790,7 +790,7 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl GC.Collect(); for (int i = 0; i < 5; i++) - dds.AnalyseAllPlaysBin(in boards, in playTracesBin, out SolvedPlays solved, 0); + dds.AnalyseAllPlays(in boards, in playTracesBin, out SolvedPlays solved, 0); dds.FreeMemory(); @@ -801,7 +801,7 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl for (int i = 0; i < iterations; i++) { - dds.AnalyseAllPlaysBin(in boards, in playTracesBin, out SolvedPlays solved, 0); + dds.AnalyseAllPlays(in boards, in playTracesBin, out SolvedPlays solved, 0); dds.FreeMemory(); } @@ -816,6 +816,7 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl return opsPerSecond; } + catch (Exception ex) { Console.WriteLine($"ERROR in Variant1: {ex.Message}"); diff --git a/dotnet/DDS_Core_Demo/TestData.cs b/dotnet/DDS_Core_Demo/TestData.cs index 1f70800b..83cba311 100644 --- a/dotnet/DDS_Core_Demo/TestData.cs +++ b/dotnet/DDS_Core_Demo/TestData.cs @@ -1,4 +1,7 @@ -using DDS_Core; +using System.Numerics; +using System.Runtime.Intrinsics.X86; +using System.Text; +using DDS_Core; using static DDS_Core.CardRanks; namespace DDS_Core_Demo @@ -25,28 +28,28 @@ public static class TestData static TestData() { - hands = new uint[4][][]; + hands = new uint[3][][]; hands[0] = [ - [ (uint)(rJ|r6|r5|r2) - , (uint)(rA|r7|r4) - , (uint)None - , (uint)(rK|rT|r9|r6|r4|r2) + [ (uint)(rT|r8|r5) + , (uint)(rA|rT|r7|r2) + , (uint)(rK|rQ|r8) + , (uint)(rA|r3|r2) ] - , [ (uint)(rA|rT|r9|r8|r7|r3) - , (uint)(rK|rJ|r5) - , (uint)(rT|r8|r2) - , (uint)r8 + , [ (uint)(rJ|r2) + , (uint)(rK|r9) + , (uint)(rA|rJ|r7|r4|r3|r2) + , (uint)(rJ|r8|r7) ] - , [ (uint)r4 - , (uint)(rQ|rT|r9|r8|r2) - , (uint)(rA|rK|rQ|r9|r3) - , (uint)(rA|rJ) + , [ (uint)(rK|rQ|r3) + , (uint)(rJ|r8|r6|r5|r4|r3) + , (uint)(r5) + , (uint)(rK|r9|r6) ] - , [ (uint)(rK|rQ) - , (uint)(r6|r3) - , (uint)(rJ|r7|r6|r5|r4) - , (uint)(rQ|r7|r5|r3) + , [ (uint)(rA|r9|r7|r6|r4) + , (uint)(rQ) + , (uint)(rT|r9|r6) + , (uint)(rQ|rT|r5|r4) ] ]; @@ -100,7 +103,7 @@ static TestData() ] ]; - pbn = [ "N:J652.A74..KT9642 AT9873.KJ5.T82.8 4.QT982.AKQ93.AJ KQ.63.J7654.Q753" + pbn = [ "N:T85.AT72.KQ8.A32 J2.K9.AJ7432.J87 KQ3.J86543.5.K96 A9764.Q.T96.QT54" , "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" ]; @@ -139,12 +142,12 @@ static TestData() boards.Deals[i] = deals[i]; boards.Target[i] = -1; boards.Solutions[i] = 1; - boards.Modes[i] = 0; + boards.Modes[i] = 0; boardsPBN.Deals[i] = dealsPBN[i]; boardsPBN.Target[i] = -1; boardsPBN.Solutions[i] = 1; - boardsPBN.Modes[i] = 0; + boardsPBN.Modes[i] = 0; } // ddTableDeal(PBN) @@ -175,13 +178,12 @@ static TestData() playTraceBin = new(); playTraceBin.NumberOfCards = 3; playTraceBin.Suits[0] = 3; - playTraceBin.Ranks[0] = 2; - - playTraceBin.Suits[1] = 3; - playTraceBin.Ranks[1] = 8; + playTraceBin.Suits[1] = 3; + playTraceBin.Suits[2] = 3; - playTraceBin.Suits[2] = 3; - playTraceBin.Ranks[2] = 14; + playTraceBin.Ranks[0] = 2; + playTraceBin.Ranks[1] = 7; + playTraceBin.Ranks[2] = 6; var playTraceBin1 = new PlayTraceBin(); playTraceBin1.NumberOfCards = 1; @@ -191,34 +193,110 @@ static TestData() var playTraceBin2 = new PlayTraceBin(); playTraceBin2.NumberOfCards = 4; playTraceBin2.Suits[0] = 1; - playTraceBin2.Ranks[0] = 12; + playTraceBin2.Suits[1] = 1; + playTraceBin2.Suits[2] = 1; + playTraceBin2.Suits[3] = 1; - playTraceBin2.Suits[1] = 1; + playTraceBin2.Ranks[0] = 12; playTraceBin2.Ranks[1] = 8; - - playTraceBin2.Suits[2] = 1; playTraceBin2.Ranks[2] = 2; + playTraceBin2.Ranks[3] = 13; - playTraceBin2.Suits[3] = 1; - playTraceBin2.Ranks[3] = 13; - - playTracePBN = new PlayTracePBN { NumberOfPlayedCards = 3, Cards = "C2C8CA" }; + playTracePBN = new PlayTracePBN { NumberOfPlayedCards = 3, Cards = "C2C7C6" }; var playTracePBN2 = new PlayTracePBN {NumberOfPlayedCards = 1, Cards = "SA" }; var playTracePBN3 = new PlayTracePBN {NumberOfPlayedCards = 4, Cards = "HQH8H2HK" }; // playTracesBin and PBN - playTracesBin = new(); + playTracesBin = new(); playTracesBin.NumberOfBoards = 3; playTracesBin.Plays[0] = playTraceBin; playTracesBin.Plays[1] = playTraceBin1; playTracesBin.Plays[2] = playTraceBin2; - playTracesPBN = new(); + playTracesPBN = new(); playTracesPBN.NumberOfBoards = 3; playTracesPBN.Plays[0] = playTracePBN; playTracesPBN.Plays[1] = playTracePBN2; playTracesPBN.Plays[2] = playTracePBN3; + + validate(); + } + + private static void validate() + { + // Validation + Console.WriteLine("Validating TestData..."); + var err =false; + + for (int i = 0; i < hands.Count(); i++) + { + //var arr = pbn[i][2..].Split(' ','.'); + for (int s = 0; s < 4; s++) + { + uint ranks =0; + string str = ""; + + for (int p = 0; p < 4; p++) + { + var r = hands[i][p][s]>>2; + var d = ranks & r; + + if (d == 0) + { + ranks |= r; + //Console.WriteLine($"TestData: valid rank(s) in hands[{i}] suit[{s}] player: {p} rank(s):{Convert.ToString(r , 2).PadLeft(13,'0')}"); + } + else + { + err = true; + Console.WriteLine($"TestData: valid rank(s) in prior hands - rank(s):{Convert.ToString(ranks, 2).PadLeft(13, '0')}"); + Console.WriteLine($"TestData: Duplicate rank(s) in hands[{i}] suit[{s}] player: {p} rank(s):{Convert.ToString(d, 2).PadLeft(13, '0')}"); + continue; + } + } + } + } + + for (int i = 0; i < hands.Count(); i++) + for (int p = 0; p < 4; p++) + { + var cnt = BitOperations.PopCount(hands[i][p][0]) + + BitOperations.PopCount(hands[i][p][1]) + + BitOperations.PopCount(hands[i][p][2]) + + BitOperations.PopCount(hands[i][p][3]); + + //int cnt = 0; + if (cnt > 13 || cnt < 13) + { + err = true; + Console.WriteLine($"TestData: hands[{i}] player:{p} :contains {cnt} cards"); + continue; + } + } + + for (int i = 0; i < hands.Count(); i++) + for (int s = 0; s < 4; s++) + { + var cnt = BitOperations.PopCount(hands[i][0][s]) + + BitOperations.PopCount(hands[i][1][s]) + + BitOperations.PopCount(hands[i][2][s]) + + BitOperations.PopCount(hands[i][3][s]); + + if (cnt > 13 || cnt < 13) + { + err = true; + Console.WriteLine($"TestData: hands[{i}] suit:{s} contains {cnt} cards"); + continue; + } + } + + if (err) + { + Console.WriteLine("Validation failed."); + Console.ReadKey(); + Environment.Exit(0); + } } } } From cb59baa6e0af881c10d7a586d52c79d6817f7ac5 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:09:17 +0200 Subject: [PATCH 028/157] First take on md files --- docs/DDS_Core.md | 31 ++++ docs/DDS_Core1.md | 284 ++++++++++++++++++++++++++++++++ docs/dotnet_solvercontext.md | 136 +++++++++++++++ dotnet/DDS_Core/DDS.cs | 4 +- dotnet/DDS_Core_Demo/Program.cs | 2 +- 5 files changed, 454 insertions(+), 3 deletions(-) create mode 100644 docs/DDS_Core.md create mode 100644 docs/DDS_Core1.md create mode 100644 docs/dotnet_solvercontext.md diff --git a/docs/DDS_Core.md b/docs/DDS_Core.md new file mode 100644 index 00000000..b972710c --- /dev/null +++ b/docs/DDS_Core.md @@ -0,0 +1,31 @@ +# DDS_Core System Documentation +DDS_Core is a .NET class library that provides a high-level API for using DDS. + +## Usages + +DDS_Core can be used in .NET applications with .NET 8+ to interact with the DDS. + +A simple example of using DDS_Core to solve a board is shown below: +```c# +using DDS_Core; +... + +var cfg = new SolverConfig() + { + TTKind = TTKind.Large + , DefaultMemoryMB = 256 + , MaximumMemoryMB = 1024 + }; + +using (var ctx = new SolverContext(cfg)) +{ + var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); +} +``` + + +## API Reference + + + + diff --git a/docs/DDS_Core1.md b/docs/DDS_Core1.md new file mode 100644 index 00000000..154b5dc0 --- /dev/null +++ b/docs/DDS_Core1.md @@ -0,0 +1,284 @@ +# DDS_Core – .NET API for the Double Dummy Solver + +`DDS_Core` is a modern, type‑safe .NET wrapper around Bo Haglund’s **Double Dummy Solver (DDS)**. +The library exposes the full DDS functionality through idiomatic C# structures and methods, supporting both: + +- The **legacy C API** (`SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay`, …) +- The **modern C++ API** (via `SolverContext` and `SolverConfig`) + +The goal is to provide a stable, fast, and fully documented .NET interface to the DDS engine. + +--- + +## Table of Contents +1. [Introduction](#introduction) +2. [Legacy vs. Modern DDS API](#legacy-vs-modern-dds-api) +3. [Basic Usage](#basic-usage) +4. [API Overview](#api-overview) + - [Configuration & Resources](#configuration--resources) + - [Single Board Solving](#single-board-solving) + - [Multiple Board Solving](#multiple-board-solving) + - [Double Dummy Table Calculation](#double-dummy-table-calculation) + - [Par Score Calculation](#par-score-calculation) + - [Par Text Conversion](#par-text-conversion) + - [Play Analysis](#play-analysis) + - [Utility Functions](#utility-functions) +5. [Error Handling](#error-handling) +6. [Threading & Performance](#threading--performance) +7. [Examples](#examples) + +--- + +# Introduction + +`DDS_Core` provides a managed .NET interface to the DDS engine. +All data structures (`Deal`, `FutureTricks`, `DdTableResults`, etc.) are blittable and optimized for interoparation with the DDS library. + +The wrapper is designed for: + +- high performance +- minimal marshalling overhead +- strong type safety +- full compatibility with both DDS C and C++ APIs + +--- + +# Legacy vs. Modern DDS API + +DDS exposes two API layers: + +| API | Description | Status | +|-----|-------------|--------| +| **Legacy C API** | Global functions such as `SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay` | Supported but deprecated | +| **Modern C++ API** | RAII‑based `SolverContext` and `SolverConfig` | Recommended | + +In `DDS_Core`, all legacy functions are marked with: + +```csharp +[Obsolete("Use SolverContext instead.")] +``` + +# Basic Usage + +Legacy API usage example: +```csharp +var dds = new DDS(); + +var deal = new Deal +{ + // fill in card holdings... +}; + +dds.SolveBoard( + dl: deal, + target: 0, + solutions: 1, + mode: 0, + out var fut +); + +Console.WriteLine($"Best trick count: {fut.score[0]}"); +``` + + +The same sample but using the modern API: +```csharp +using DDS_Core; +... + +var dds = new DDS(); + +var deal = new Deal +{ + // fill in card holdings... +}; + +var cfg = new SolverConfig() + { + TTKind = TTKind.Large + , DefaultMemoryMB = 256 + , MaximumMemoryMB = 1024 + }; + +using (var ctx = new SolverContext(cfg)) +{ + var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); +} +``` + +# API Overview + +This document provides a structured overview of the public API exposed by the `DDS_Core` .NET wrapper. +The API is grouped into functional areas that mirror the underlying DDS engine capabilities. +The API is divided into two layers: + +1. **Modern API** — based on `SolverContext` (recommended) +2. **Legacy API** — thin wrappers around the original C API (maintained for compatibility) + +--- + +# Modern API (Recommended) + +The modern API is built around the RAII‑style `SolverContext` object, mirroring the C++ DDS API. +Each context instance owns its own transposition table, configuration, and solver state. + +--- + +## SolverContext + +### Construction & Lifetime + +```csharp +var ctx = new SolverContext(); +var ctx = new SolverContext(new SolverConfig { ... }); + +--- + +## Configuration & Resources + +These functions belong to the **legacy C API** and are preserved for backward compatibility. +Modern applications should prefer `SolverContext` and `SolverConfig`. + +### Methods +- **SetMaxThreads(int userThreads)** + Sets the maximum number of threads used by the legacy solver backend. + +- **SetThreading(int code)** + Selects the threading backend. Returns `1` on success. + +- **SetResources(int maxMemoryMB, int maxThreads)** + Configures memory and thread limits for the legacy solver. + +- **FreeMemory()** + Frees memory allocated by the legacy solver. + +--- + +## Single Board Solving + +Functions for analyzing a single bridge deal using double‑dummy analysis. + +### Methods +- **SolveBoard(Deal dl, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** + Solves a binary `Deal` and returns trick results. + +- **SolveBoard(DealPBN pbn, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** + PBN‑formatted variant (legacy). + +--- + +## Multiple Board Solving + +Batch‑oriented solving of multiple deals, significantly faster than repeated single‑deal calls. + +### Methods +- **SolveAllBoards(Boards bop, out SolvedBoards solved)** + Solves multiple PBN deals. + +- **SolveAllBoards(BoardsPBN boards, out SolvedBoards solved)** + Binary variant (legacy). + +--- + +## Double Dummy Table Calculation + +Computes DDS tables for one or more deals, including optional par calculations. + +### Methods +- **CalcDdTable(DdTableDeal deal, out DdTableResults table)** + Computes the double‑dummy table for a binary deal. + +- **CalcDdTable(DdTableDealPBN deal, out DdTableResults table)** + PBN variant. + +- **CalcAllTables(DdTableDeals deals, int mode, intArray5 trumpFilter, out DdTablesResult resTables, out AllParResults parResults)** + Computes tables and par results for multiple deals. + +- **CalcAllTables(DdTableDealsPBN deals, int mode, intArray5 trumpFilter, out DdTablesResult resTables, out AllParResults parResults)** + PBN variant. + +--- + +## Par Score Calculation + +Computes par contracts and scores based on DDS table results. + +### Methods +- **Par(DdTableResults table, out ParResults pres, int vulnerable)** + Computes par score for both sides. + +- **CalcPar(DdTableDeal deal, int vulnerable, out DdTableResults tableResults, out ParResults parResults)** + Computes DDS table and par score in one call. + +- **CalcPar(DdTableDealPBN deal, out DdTableResults tableResults, int vulnerable, out ParResults parResults)** + PBN variant. + +- **ParSide(DdTableResults table, out ParResultsDealers sidesRes, int vulnerable)** + Computes par results for both sides separately. + +- **ParDealer(DdTableResults table, out ParResultsDealer pres, int dealer, int vulnerable)** + Computes par score for a specific dealer. + +- **DealerParBothSides(DdTableResults table, out ParResultsMaster pres, int dealer, int vulnerable)** + Computes par contracts for both sides for a specific dealer. + +- **ParAll(DdTableResults table, out ParResultsMasters sidesRes, int vulnerable)** + Computes par results for all sides and dealers. + +--- + +## Par Text Conversion + +Converts par results into human‑readable text formats. + +### Methods +- **ConvertToTextFormat(ParResultsMaster pres, out string resp)** + Converts dealer‑based par results to a text string. + +- **ConvertToTextFormat(ParResultsMasters pres, out ParTextResults resp)** + Converts multi‑side par results to structured text output. + +--- + +## Play Analysis + +Analyzes play sequences (traces) and computes optimal lines of play. + +### Methods +- **AnalysePlay(Deal dl, PlayTraceBin play, out SolvedPlay solved, int thrId)** + Analyzes a binary deal and play trace. + +- **AnalysePlay(DealPBN dl, PlayTracePBN play, out SolvedPlay solved, int thrId)** + PBN variant. + +- **AnalyseAllPlays(Boards bop, PlayTracesBin plp, out SolvedPlays solved, int chunkSize)** + Batch analysis of multiple deals and play traces. + +- **AnalyseAllPlays(BoardsPBN bop, PlayTracesPBN plp, out SolvedPlays solved, int chunkSize)** + PBN variant. + +--- + +## Utility Functions + +General helper functions for diagnostics and error reporting. + +### Methods +- **GetDDSInfo(out DdsInfo info)** + Retrieves version and build information from the DDS engine. + +- **ErrorMessage(int code, out string line)** + Returns a human‑readable description of a DDS error code. + +--- + +## Error Handling + +Most public methods returns an error code that should be checked: + +```csharp + var rc = dds.SolveAllBoards( bop, out solved); + + if (rc != (int)SolveBoardResult.NoFault) + throw new InvalidOperationException($"DDS_Core failed with code: {rc}"); +``` \ No newline at end of file diff --git a/docs/dotnet_solvercontext.md b/docs/dotnet_solvercontext.md new file mode 100644 index 00000000..c7688dfa --- /dev/null +++ b/docs/dotnet_solvercontext.md @@ -0,0 +1,136 @@ +# SolverContext API Reference + +`SolverContext` is the modern, RAII‑style API for interacting with the DDS engine. +Each instance owns its own solver state, transposition table, configuration, and logging buffer. + +This API is **recommended for all new development** and replaces the legacy global C API. + +--- + +## Table of Contents + +- [Overview](#overview) +- [Construction & Lifetime](#construction--lifetime) +- [Transposition Table Management](#transposition-table-management) +- [Solving Methods](#solving-methods) +- [Par Calculation](#par-calculation) +- [Logging](#logging) +- [Error Handling](#error-handling) +- [Summary Table](#summary-table) + +--- + +# Overview + +`SolverContext` encapsulates a complete DDS solver instance. +It provides: + +- isolated solver state +- per‑context transposition table +- configurable memory limits +- thread‑safe usage (one context per thread) +- modern API parity with the C++ DDS interface + +A context must be disposed when no longer needed. + +--- + +# Construction & Lifetime + +### **Default constructor** + +```csharp +var ctx = new SolverContext(); + +or + +var cfg = new SolverConfig() + { + TTKind = TTKind.Large + , DefaultMemoryMB = 256 + , MaximumMemoryMB = 1024 + }; + +using var ctx = new SolverContext(cfg); + +or + +using var ctx = new SolverContext(new new SolverConfig(TTKind.Large,256,1024)); +```` + +Please note that the `using` statement ensures that the context is properly disposed, freeing any allocated resources. +As an alternative one might also call `ctx.Dispose()` explicitly when the context is no longer needed. + + +### Transposition Table Management +The transposition table (TT) is a key performance component of DDS. +SolverContext exposes full control over TT configuration and lifecycle. + +- **void ConfigureTT(TTKind kind, int defaultMb, int maxMb)** + Configures the transposition table settings. + +- **void ResizeTT(int defaultMb, int maxMb)** + Resizes the transposition table according to new memory limits. + +- **void ClearTT()** + Clears all entries from the transposition table. + Note: This is not necessary in normal usage. + +- **void ResetForSolve()** + Resets the solver state and clears the transposition table in preparation for a new solve. + Note: This is not necessary in normal usage. + +- **void ResetBestMovesLite()** + Clears lightweight best‑move caches used by the solver. + +### Solving Methods +- **int SolveBoard(in Deal dl, + int target, + int solutions, + int mode, + out FutureTricks fut)** + + Solves a single board with the specified parameters. + +- **int CalcDdTable(in DdTableDeal deal, + out DdTableResults table)** + Solves a single board returning a full double dummy table. + +- **int CalcDdTable(in DdTableDealPBN dealPbn, + out DdTableResults table)** + PBN variant. + + +### Par Calculation +- **int CalcPar(in DdTableDeal deal, + int vulnerable, + out DdTableResults table, + out ParResults par)** + + Computes the double‑dummy table, the par score and contracts in a single optimized call. + +### Logging +Each SolverContext maintains its own log buffer. + +- **void LogAppend(string message)** + Appends a message to the internal log. +- **void LogClear()** + Clears the internal log. + +### Error Handling +- - **** +- - **** +- - **** +- - **** + +### Summary Table +# SolverContext Summary + +| Category | Methods | +|:---------|:---------| +| **Construction** | `SolverContext()`, `SolverContext(SolverConfig)` | +| **Transposition Table Management** | `ConfigureTT`, `ResizeTT`, `ClearTT`, `ResetForSolve`, `ResetBestMovesLite` | +| **Solving** | `SolveBoard`, `CalcDdTable` (binary), `CalcDdTable` (PBN) | +| **Par Calculation** | `CalcPar` | +| **Logging** | `LogAppend`, `LogClear` | + diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 6f5ba3f9..95246af4 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -186,14 +186,14 @@ public int SolveAllBoards( in BoardsPBN boards /// The results for solved boards. /// 1 on success, error code otherwise. [Obsolete("Use SolverContext instead.")] - public int SolveAllBoardsBin( in Boards bop + public int SolveAllBoards( in Boards bop , out SolvedBoards solved) { //Note: To step into c++ code you must set a break in c++? var rc = DdsNative.SolveAllBoardsBin( bop , out solved); - ThrowIfError(rc, nameof(SolveAllBoardsBin)); + ThrowIfError(rc, nameof(SolveAllBoards)); return rc; } diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index cbbbe3c0..47503477 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -198,7 +198,7 @@ private static void doSolveAllBoards(DDS dds, Boards boards) for (boards.Deals[0].First = 0; boards.Deals[0].First < 4; boards.Deals[0].First++) { var decl =(boards.Deals[0].First + 3) & 3; - var rc = dds.SolveAllBoardsBin(boards, out SolvedBoards solved); + var rc = dds.SolveAllBoards(boards, out SolvedBoards solved); // record the number of tricks for declarer tricks[boards.Deals[0].Trump, decl] = 13 - solved.Tricks[0].Score[0]; From 70d9f9268a031148a21575eccc46046d119865e3 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:10:27 +0200 Subject: [PATCH 029/157] extra pbn test string --- dotnet/DDS_Core_Demo/TestData.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/DDS_Core_Demo/TestData.cs b/dotnet/DDS_Core_Demo/TestData.cs index 83cba311..103d4796 100644 --- a/dotnet/DDS_Core_Demo/TestData.cs +++ b/dotnet/DDS_Core_Demo/TestData.cs @@ -103,6 +103,7 @@ static TestData() ] ]; + // "N:J652.A74..KT9642 AT9873.KJ5.T82.8 4.QT982.AKQ93.AJ KQ.63.J7654.Q753" pbn = [ "N:T85.AT72.KQ8.A32 J2.K9.AJ7432.J87 KQ3.J86543.5.K96 A9764.Q.T96.QT54" , "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" From 941d3681af363ea84449fece20dcf7c4384df042 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:42:51 +0200 Subject: [PATCH 030/157] Markdown added --- .gitignore | 1 + README.md | 2 + docs/DDS_Core.md | 31 -- docs/DDS_Core1.md | 284 --------------- docs/dotnet_interface.md | 389 +++++++++++++++++++++ docs/dotnet_solvercontext.md | 136 ------- dotnet/DDS_Core/DDS.cs | 314 ++++++++--------- dotnet/DDS_Core/DataModel/SolverContext.cs | 153 ++++---- dotnet/DDS_Core/Helpers/FourHands.cs | 16 +- library/src/api/BUILD.bazel | 1 + library/src/api/dds_api.hpp | 2 +- 11 files changed, 628 insertions(+), 701 deletions(-) delete mode 100644 docs/DDS_Core.md delete mode 100644 docs/DDS_Core1.md create mode 100644 docs/dotnet_interface.md delete mode 100644 docs/dotnet_solvercontext.md diff --git a/.gitignore b/.gitignore index a990e18a..5daf9358 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Build/bin Build/int obj +.cr # Prerequisites *.d diff --git a/README.md b/README.md index 73fcfa91..afc39493 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Martin Nygren, May 2026 [Python Interface](docs/python_interface.md) +[DotNet Interface](docs/dotnet_interface.md) + [Legacy C Interface](docs/legacy_c_api.md) [Migrating to the modern API](docs/api_migration.md) diff --git a/docs/DDS_Core.md b/docs/DDS_Core.md deleted file mode 100644 index b972710c..00000000 --- a/docs/DDS_Core.md +++ /dev/null @@ -1,31 +0,0 @@ -# DDS_Core System Documentation -DDS_Core is a .NET class library that provides a high-level API for using DDS. - -## Usages - -DDS_Core can be used in .NET applications with .NET 8+ to interact with the DDS. - -A simple example of using DDS_Core to solve a board is shown below: -```c# -using DDS_Core; -... - -var cfg = new SolverConfig() - { - TTKind = TTKind.Large - , DefaultMemoryMB = 256 - , MaximumMemoryMB = 1024 - }; - -using (var ctx = new SolverContext(cfg)) -{ - var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); -} -``` - - -## API Reference - - - - diff --git a/docs/DDS_Core1.md b/docs/DDS_Core1.md deleted file mode 100644 index 154b5dc0..00000000 --- a/docs/DDS_Core1.md +++ /dev/null @@ -1,284 +0,0 @@ -# DDS_Core – .NET API for the Double Dummy Solver - -`DDS_Core` is a modern, type‑safe .NET wrapper around Bo Haglund’s **Double Dummy Solver (DDS)**. -The library exposes the full DDS functionality through idiomatic C# structures and methods, supporting both: - -- The **legacy C API** (`SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay`, …) -- The **modern C++ API** (via `SolverContext` and `SolverConfig`) - -The goal is to provide a stable, fast, and fully documented .NET interface to the DDS engine. - ---- - -## Table of Contents -1. [Introduction](#introduction) -2. [Legacy vs. Modern DDS API](#legacy-vs-modern-dds-api) -3. [Basic Usage](#basic-usage) -4. [API Overview](#api-overview) - - [Configuration & Resources](#configuration--resources) - - [Single Board Solving](#single-board-solving) - - [Multiple Board Solving](#multiple-board-solving) - - [Double Dummy Table Calculation](#double-dummy-table-calculation) - - [Par Score Calculation](#par-score-calculation) - - [Par Text Conversion](#par-text-conversion) - - [Play Analysis](#play-analysis) - - [Utility Functions](#utility-functions) -5. [Error Handling](#error-handling) -6. [Threading & Performance](#threading--performance) -7. [Examples](#examples) - ---- - -# Introduction - -`DDS_Core` provides a managed .NET interface to the DDS engine. -All data structures (`Deal`, `FutureTricks`, `DdTableResults`, etc.) are blittable and optimized for interoparation with the DDS library. - -The wrapper is designed for: - -- high performance -- minimal marshalling overhead -- strong type safety -- full compatibility with both DDS C and C++ APIs - ---- - -# Legacy vs. Modern DDS API - -DDS exposes two API layers: - -| API | Description | Status | -|-----|-------------|--------| -| **Legacy C API** | Global functions such as `SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay` | Supported but deprecated | -| **Modern C++ API** | RAII‑based `SolverContext` and `SolverConfig` | Recommended | - -In `DDS_Core`, all legacy functions are marked with: - -```csharp -[Obsolete("Use SolverContext instead.")] -``` - -# Basic Usage - -Legacy API usage example: -```csharp -var dds = new DDS(); - -var deal = new Deal -{ - // fill in card holdings... -}; - -dds.SolveBoard( - dl: deal, - target: 0, - solutions: 1, - mode: 0, - out var fut -); - -Console.WriteLine($"Best trick count: {fut.score[0]}"); -``` - - -The same sample but using the modern API: -```csharp -using DDS_Core; -... - -var dds = new DDS(); - -var deal = new Deal -{ - // fill in card holdings... -}; - -var cfg = new SolverConfig() - { - TTKind = TTKind.Large - , DefaultMemoryMB = 256 - , MaximumMemoryMB = 1024 - }; - -using (var ctx = new SolverContext(cfg)) -{ - var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); -} -``` - -# API Overview - -This document provides a structured overview of the public API exposed by the `DDS_Core` .NET wrapper. -The API is grouped into functional areas that mirror the underlying DDS engine capabilities. -The API is divided into two layers: - -1. **Modern API** — based on `SolverContext` (recommended) -2. **Legacy API** — thin wrappers around the original C API (maintained for compatibility) - ---- - -# Modern API (Recommended) - -The modern API is built around the RAII‑style `SolverContext` object, mirroring the C++ DDS API. -Each context instance owns its own transposition table, configuration, and solver state. - ---- - -## SolverContext - -### Construction & Lifetime - -```csharp -var ctx = new SolverContext(); -var ctx = new SolverContext(new SolverConfig { ... }); - ---- - -## Configuration & Resources - -These functions belong to the **legacy C API** and are preserved for backward compatibility. -Modern applications should prefer `SolverContext` and `SolverConfig`. - -### Methods -- **SetMaxThreads(int userThreads)** - Sets the maximum number of threads used by the legacy solver backend. - -- **SetThreading(int code)** - Selects the threading backend. Returns `1` on success. - -- **SetResources(int maxMemoryMB, int maxThreads)** - Configures memory and thread limits for the legacy solver. - -- **FreeMemory()** - Frees memory allocated by the legacy solver. - ---- - -## Single Board Solving - -Functions for analyzing a single bridge deal using double‑dummy analysis. - -### Methods -- **SolveBoard(Deal dl, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** - Solves a binary `Deal` and returns trick results. - -- **SolveBoard(DealPBN pbn, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** - PBN‑formatted variant (legacy). - ---- - -## Multiple Board Solving - -Batch‑oriented solving of multiple deals, significantly faster than repeated single‑deal calls. - -### Methods -- **SolveAllBoards(Boards bop, out SolvedBoards solved)** - Solves multiple PBN deals. - -- **SolveAllBoards(BoardsPBN boards, out SolvedBoards solved)** - Binary variant (legacy). - ---- - -## Double Dummy Table Calculation - -Computes DDS tables for one or more deals, including optional par calculations. - -### Methods -- **CalcDdTable(DdTableDeal deal, out DdTableResults table)** - Computes the double‑dummy table for a binary deal. - -- **CalcDdTable(DdTableDealPBN deal, out DdTableResults table)** - PBN variant. - -- **CalcAllTables(DdTableDeals deals, int mode, intArray5 trumpFilter, out DdTablesResult resTables, out AllParResults parResults)** - Computes tables and par results for multiple deals. - -- **CalcAllTables(DdTableDealsPBN deals, int mode, intArray5 trumpFilter, out DdTablesResult resTables, out AllParResults parResults)** - PBN variant. - ---- - -## Par Score Calculation - -Computes par contracts and scores based on DDS table results. - -### Methods -- **Par(DdTableResults table, out ParResults pres, int vulnerable)** - Computes par score for both sides. - -- **CalcPar(DdTableDeal deal, int vulnerable, out DdTableResults tableResults, out ParResults parResults)** - Computes DDS table and par score in one call. - -- **CalcPar(DdTableDealPBN deal, out DdTableResults tableResults, int vulnerable, out ParResults parResults)** - PBN variant. - -- **ParSide(DdTableResults table, out ParResultsDealers sidesRes, int vulnerable)** - Computes par results for both sides separately. - -- **ParDealer(DdTableResults table, out ParResultsDealer pres, int dealer, int vulnerable)** - Computes par score for a specific dealer. - -- **DealerParBothSides(DdTableResults table, out ParResultsMaster pres, int dealer, int vulnerable)** - Computes par contracts for both sides for a specific dealer. - -- **ParAll(DdTableResults table, out ParResultsMasters sidesRes, int vulnerable)** - Computes par results for all sides and dealers. - ---- - -## Par Text Conversion - -Converts par results into human‑readable text formats. - -### Methods -- **ConvertToTextFormat(ParResultsMaster pres, out string resp)** - Converts dealer‑based par results to a text string. - -- **ConvertToTextFormat(ParResultsMasters pres, out ParTextResults resp)** - Converts multi‑side par results to structured text output. - ---- - -## Play Analysis - -Analyzes play sequences (traces) and computes optimal lines of play. - -### Methods -- **AnalysePlay(Deal dl, PlayTraceBin play, out SolvedPlay solved, int thrId)** - Analyzes a binary deal and play trace. - -- **AnalysePlay(DealPBN dl, PlayTracePBN play, out SolvedPlay solved, int thrId)** - PBN variant. - -- **AnalyseAllPlays(Boards bop, PlayTracesBin plp, out SolvedPlays solved, int chunkSize)** - Batch analysis of multiple deals and play traces. - -- **AnalyseAllPlays(BoardsPBN bop, PlayTracesPBN plp, out SolvedPlays solved, int chunkSize)** - PBN variant. - ---- - -## Utility Functions - -General helper functions for diagnostics and error reporting. - -### Methods -- **GetDDSInfo(out DdsInfo info)** - Retrieves version and build information from the DDS engine. - -- **ErrorMessage(int code, out string line)** - Returns a human‑readable description of a DDS error code. - ---- - -## Error Handling - -Most public methods returns an error code that should be checked: - -```csharp - var rc = dds.SolveAllBoards( bop, out solved); - - if (rc != (int)SolveBoardResult.NoFault) - throw new InvalidOperationException($"DDS_Core failed with code: {rc}"); -``` \ No newline at end of file diff --git a/docs/dotnet_interface.md b/docs/dotnet_interface.md new file mode 100644 index 00000000..64e19751 --- /dev/null +++ b/docs/dotnet_interface.md @@ -0,0 +1,389 @@ +# DDS_Core – .NET API for the Double Dummy Solver + +`DDS_Core` is a modern, type‑safe .NET wrapper around Bo Haglund’s **Double Dummy Solver (DDS)** . +Please note that this library does not support `.Net Framework` and requires `.Net 8.0` or later. +The library exposes the full DDS functionality through idiomatic `.Net` structures and methods, supporting both: + +- The **legacy C API** (`SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay`, …) +- The **modern C++ API** (via `SolverContext` and `SolverConfig`) + +The goal is to provide a stable, fast, and fully documented .NET interface to the DDS engine. + +--- + +## Table of Contents +1. [Introduction](#introduction) +2. [Legacy vs. Modern DDS API](#legacy-vs-modern-dds-api) +3. [Basic Usage](#basic-usage) +4. [API Overview](#api-overview) + a. [Modern API (Recommended)](#modern-api-recommended) + - [Construction & Lifetime](#construction-lifetime) + - [Single Board Solving Modern](#single-board-solving-modern) + - [Double Dummy Table Calculation Modern](#double-dummy-table-calculation-modern) + - [Par Score Calculation Modern](#par-score-calculation-modern) + - [Logging](#logging) + b. [Legacy API Overview](#legacy-api) + - [Configuration & Resources](#configuration-resources) + - [Single Board Solving](#single-board-solving) + - [Multiple Board Solving](#multiple-board-solving) + - [Double Dummy Table Calculation](#double-dummy-table-calculation) + - [Par Score Calculation](#par-score-calculation) + - [Par Text Conversion](#par-text-conversion) + - [Play Analysis](#play-analysis) + - [Utility Functions](#utility-functions) + c. [Conversion and error handling](#conversion-and-error-handling) + - [Error Handling](#error-handling) + - [Internal Error Handling](#internal-error-handling) + d. [Threading & Performance](#threading-performance) + e. [Examples](#examples) + +--- + +# Introduction + +`DDS_Core` provides a managed .NET interface to the DDS engine. +All data structures (`Deal`, `FutureTricks`, `DdTableResults`, etc.) are blittable and optimized for interoperation with the DDS library. + +The wrapper is designed for: + +- high performance +- minimal marshalling overhead +- strong type safety +- full compatibility with both DDS C and C++ APIs +- the project follows the .NET Framework Design Guidelines, including naming conventions, and leverages method overloading to provide a clear, expressive, and flexible API surface. +- one-dimensional InlineArrays and two-dimensional array-like types are used interop arrays to avoid unnecessary heap allocations and improve performance while keeping `.Net` bounds checking +- the modern `SolverContext` class encapsulates solver state and resources, allowing for efficient reuse across multiple calls and handles the resource housekeeping and guard against memory leaks. + +--- + +# Legacy vs. Modern DDS API + +DDS exposes two API layers: + +| API | Description | Status | +|-----|-------------|--------| +| **Legacy C API** | Global functions such as `SolveBoard`, `CalcDDtable`, `Par`, `AnalysePlay` | Supported but deprecated | +| **Modern C++ API** | RAII‑based `SolverContext` and `SolverConfig` | Recommended | + +In `DDS_Core`, all legacy functions are marked with: + +```csharp +[Obsolete("Use SolverContext instead.")] +``` + +# Basic Usage +Using some common declarations: +```csharp +using DDS_Core; +... +var dds = new DDS() +var hands +{ + // fill in card holdings... +}; + +``` + +Legacy API usage example: +```csharp +var rc = dds.SolveBoard(dl: deal, + target: 0, + solutions: 1, + mode: 0, + out var fut, + threadIndex: 0 // use 0 for single-threaded, or thread ID for multi-threaded contexts); +``` + + +The same sample but using the modern API: +```csharp + +var ctx = new SolverContext(); // create one context per thread, and reuse for multiple calls + rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); +``` + +# API Overview + +This document provides a structured overview of the public API exposed by the `DDS_Core` .NET wrapper. +The API is grouped into functional areas that mirror the underlying DDS engine capabilities. +The API is divided into two layers: + +1. **Modern API** — based on `SolverContext` (recommended) +2. **Legacy API** — thin wrappers around the original C API (maintained for compatibility) + +--- + +## Modern API (Recommended) + +The modern API is built around the RAII‑style `SolverContext` object, mirroring the C++ DDS API. +Each context instance owns its own transposition table, configuration, and solver state. + +--- + +### Construction & Lifetime + +```csharp +var ctx = new SolverContext(); +var ctx = new SolverContext(new SolverConfig { ... }); +``` + +--- + +### Configuration & Resources + +- **SetMaxThreads(int userThreads)** + Sets the maximum number of threads used by the legacy solver backend. + +- **SetThreading(int code)** + Selects the threading backend. Returns `1` on success. + +- **SetResources(int maxMemoryMB, int maxThreads)** + Configures memory and thread limits for the legacy solver. + +- **FreeMemory()** + Frees memory allocated by the legacy solver. + +- **ResetBestMovesLite()** + Resets the internal cache of best moves. + +--- + +### Single Board Solving Modern + +Functions for analyzing a single bridge deal using double‑dummy analysis. + +- **int SolveBoard(Deal dl, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** + Solves a binary `Deal` and returns trick results. + +- **int SolveBoard(DealPBN pbn, int target, int solutions, int mode, out FutureTricks fut, int threadIndex = 0)** + PBN‑formatted variant (legacy). + +--- + +### Double Dummy Table Calculation Modern + +Computes DDS table for one deal, including optional par calculations. + + +- **int CalcDdTable(DdTableDeal deal, out DdTableResults table)** + Computes the double‑dummy table for a binary deal. + +- **int CalcDdTable(DdTableDealPBN deal, out DdTableResults table)** + PBN variant. + +--- + +### Par Score Calculation Modern + +Computes par contracts and scores based on DDS table results. + +- **int CalcPar(DdTableDeal table_deal + , int vulnerable + , out DdTableResults table_results + , out ParResults par_results)** + +--- + +### Logging +- **void LogAppend(string message)** + Appends a log message to the per thread log file. + +- **void LogClear()** + Clears the per thread log file. + +--- + +--- + +## Legacy API + +The legacy API provides direct access to the original C functions. Many if these are marked as `[Obsolete]` +and should be used with caution, as they may not manage resources as efficiently as the modern API. + +### Configuration & Resources + +- **SetMaxThreads(int userThreads)** + Deprecated. This sets the maximum number of threads used by the legacy solver backend. + The modern API manages threading implicitly via `SolverContext` and does not require manual configuration. + +- **SetThreading(int code)** + Deprecated. configures the threading backend for the legacy API. + The modern API uses a more efficient and flexible threading model that does not require manual selection. + +- **SetResources(int maxMemoryMB, int maxThreads)** + Deprecated. Sets memory and thread resources for the legacy API. The modern API uses `SolverConfig`. + +- **FreeMemory()** + Deprecated. Frees memory allocated by the legacy API. The modern API uses RAII via `SolverContext`. + +### Single Board Solving +- **int SolveBoard(Deal dl, ...)** + Solves a single deal via double dummy analysis . Deprecated – use SolverContext. + +- **int SolveBoard(DealPBN pbn, ...)** + The same only with PBN-format. Deprecated. + +### Multiple Board Solving +- **int SolveAllBoards(Board boards, out SolvedBoards solved)** + Solves multiple boards. Deprecated, but not yet implemented in the modern api. + +- **int SolveAllBoards(BoardsPBN boards, out SolvedBoards solved)** + The same only with PBN-format. Deprecated, but not yet implemented in the modern api. + +### Double Dummy Table Calculation +- ** int CalcDdTable(DdTableDeal deal, out DdTableResults table)** + Calculates the double‑dummy table for a single deal. Deprecated – use SolverContext. + +- **int CalcDdTable(DdTableDealPBN deal, out DdTableResults table)** + The same only with PBN-format. Deprecated – use SolverContext. + +- **int CalcAllTables(...)** + As above but for multiple deals. Deprecated, but not yet implemented in the modern api. + +### Par Score Calculation +- **int Par(DdTableResults table, out ParResults pres, int vulnerable)** + Calculate par scores based on the double‑dummy table results. Deprecated – use SolverContext.CalcPar. + +- **int CalcPar(DdTableDeal deal, ...)** + Combines DD-table calculation and par-score in one function. Deprecated – use SolverContext + +- **int CalcPar(DdTableDealPBN deal, ...)** + The same only with PBN-format. Deprecated – use SolverContext. + +- **int ParSide()** + Calculates par score for a specific side. Deprecated, but not yet implemented in the modern api. + Exists in both a variant with binary and without PBN-format. + +- **int ParDealer()** + Calculates par score for the dealer. Deprecated, but not yet implemented in the modern api. + +- **int DealerParBothSides()** + Calculates par score for both sides. Deprecated, but not yet implemented in the modern api. + +- **int ParAll()** + Calculates par score for all sides. Deprecated, but not yet implemented in the modern api. + +### Play Analysis +- **int AnalysePlay(Deal dl, PlayTraceBin play, ...)** + analyses a play sequence for a single deal. Deprecated, but not yet implemented in the modern api. + +- **int AnalysePlay(DealPBN dl, PlayTracePBN play, ...)** + PBN-version. Deprecated, but not yet implemented in the modern api. + +- **int AnalyseAllPlays(...)** + analyses multiple play sequences across multiple deals. Deprecated, but not yet implemented in the modern api. + +--- + +## Conversion and error handling. +### Par Text Conversion +- **int ConvertToTextFormat(ParResultsMaster pres, out string resp)** + Converts par results to a human‑readable text format. + +- **int ConvertToTextFormat(ParResultsMasters pres, out ParTextResults resp)** + Converts multi‑side par results to structured text output. + +--- + + +### Error Handling + +Most public methods returns an error code that should be checked: + +```csharp +var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); + +if (rc != (int)SolveBoardResult.NoFault) + throw new InvalidOperationException($"DDS_Core failed with code: {rc}"); +``` +### Internal Error Handling + +In DEBUG mode, the library will throw exceptions for any DDS error code returned by the underlying functions. +This allows for easier debugging and error tracking during development. + +--- + +## Threading & Performance +The main advantages of the modern API is that it allows for better control over threading and resource management. +Each thread must create their own SolverContext and configure it. Then the same context can be used for multiple calls +to SolveBoard, CalcDdTable, etc., without the overhead of repeated initialization. If the context is created with a using +statement, it will automatically free resources when disposed. This will guard against memory leaks and ensure that all resources +are properly released. + +## Examples + +```csharp +using DDS_Core; +... + +public void sample() +{ + var dds = new DDS(); + + var hands = new FourHands(new uint[4][] // C# 12 syntax! + { + [ (uint)(rT | r8 | r5) + , (uint)(rA | rT | r7 | r2) + , (uint)(rK | rQ | r8) + , (uint)(rA | r3 | r2)] + , + [ (uint)(rJ | r2) + , (uint)(rK | r9) + , (uint)(rA | rJ | r7 | r4 | r3 | r2) + , (uint)(rJ | r8 | r7)] + , + [ (uint)(rK | rQ | r3) + , (uint)(rJ | r8 | r6 | r5 | r4 | r3) + , (uint)(r5) + , (uint)(rK | r9 | r6)] + , [ (uint)(rA | r9 | r7 | r6 | r4) + , (uint)(rQ) + , (uint)(rT | r9 | r6) + , (uint)(rQ | rT | r5 | r4)] + }); + + var cfg = new SolverConfig(){ + TTKind = TTKind.Small + , DefaultMemoryMB = 256 + , MaximumMemoryMB = 1024 + }; + + using var ctx = new SolverContext(cfg); + + ... + + var deal = new Deal { Trump = (int)Suit.Hearts + , First = 0 // North was to lead - ie. West is declarer + , CurrentTrickSuit = new int[3] {2, 0, 0 } // Diamonds + , CurrentTrickRank = new int[3] {8, 0, 0 } // 8 of Diamonds already lead + , RemainingCards = hands + }; + + var rc = ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut); + + if (rc != (int)SolveBoardResult.NoFault) + { + ErrorMessage( rc , out string error); + throw new InvalidOperationException(error); + } + + if (!(int)ctx.SolveBoard(deal, -1, 1, 0, out FutureTricks fut)) + throw new InvalidOperationException("DDS_Core failed to solve the board."); + + + ... + + var ddTableDeal = new() {Cards = hands }; + + rc = ctx.CalcDdTable( ddTableDeal , out DdTableResults table_results); + + if (rc != (int)SolveBoardResult.NoFault) + { + ErrorMessage( rc , out string error); + throw new InvalidOperationException(error); + } + + ... +} + +``` \ No newline at end of file diff --git a/docs/dotnet_solvercontext.md b/docs/dotnet_solvercontext.md deleted file mode 100644 index c7688dfa..00000000 --- a/docs/dotnet_solvercontext.md +++ /dev/null @@ -1,136 +0,0 @@ -# SolverContext API Reference - -`SolverContext` is the modern, RAII‑style API for interacting with the DDS engine. -Each instance owns its own solver state, transposition table, configuration, and logging buffer. - -This API is **recommended for all new development** and replaces the legacy global C API. - ---- - -## Table of Contents - -- [Overview](#overview) -- [Construction & Lifetime](#construction--lifetime) -- [Transposition Table Management](#transposition-table-management) -- [Solving Methods](#solving-methods) -- [Par Calculation](#par-calculation) -- [Logging](#logging) -- [Error Handling](#error-handling) -- [Summary Table](#summary-table) - ---- - -# Overview - -`SolverContext` encapsulates a complete DDS solver instance. -It provides: - -- isolated solver state -- per‑context transposition table -- configurable memory limits -- thread‑safe usage (one context per thread) -- modern API parity with the C++ DDS interface - -A context must be disposed when no longer needed. - ---- - -# Construction & Lifetime - -### **Default constructor** - -```csharp -var ctx = new SolverContext(); - -or - -var cfg = new SolverConfig() - { - TTKind = TTKind.Large - , DefaultMemoryMB = 256 - , MaximumMemoryMB = 1024 - }; - -using var ctx = new SolverContext(cfg); - -or - -using var ctx = new SolverContext(new new SolverConfig(TTKind.Large,256,1024)); -```` - -Please note that the `using` statement ensures that the context is properly disposed, freeing any allocated resources. -As an alternative one might also call `ctx.Dispose()` explicitly when the context is no longer needed. - - -### Transposition Table Management -The transposition table (TT) is a key performance component of DDS. -SolverContext exposes full control over TT configuration and lifecycle. - -- **void ConfigureTT(TTKind kind, int defaultMb, int maxMb)** - Configures the transposition table settings. - -- **void ResizeTT(int defaultMb, int maxMb)** - Resizes the transposition table according to new memory limits. - -- **void ClearTT()** - Clears all entries from the transposition table. - Note: This is not necessary in normal usage. - -- **void ResetForSolve()** - Resets the solver state and clears the transposition table in preparation for a new solve. - Note: This is not necessary in normal usage. - -- **void ResetBestMovesLite()** - Clears lightweight best‑move caches used by the solver. - -### Solving Methods -- **int SolveBoard(in Deal dl, - int target, - int solutions, - int mode, - out FutureTricks fut)** - - Solves a single board with the specified parameters. - -- **int CalcDdTable(in DdTableDeal deal, - out DdTableResults table)** - Solves a single board returning a full double dummy table. - -- **int CalcDdTable(in DdTableDealPBN dealPbn, - out DdTableResults table)** - PBN variant. - - -### Par Calculation -- **int CalcPar(in DdTableDeal deal, - int vulnerable, - out DdTableResults table, - out ParResults par)** - - Computes the double‑dummy table, the par score and contracts in a single optimized call. - -### Logging -Each SolverContext maintains its own log buffer. - -- **void LogAppend(string message)** - Appends a message to the internal log. -- **void LogClear()** - Clears the internal log. - -### Error Handling -- - **** -- - **** -- - **** -- - **** - -### Summary Table -# SolverContext Summary - -| Category | Methods | -|:---------|:---------| -| **Construction** | `SolverContext()`, `SolverContext(SolverConfig)` | -| **Transposition Table Management** | `ConfigureTT`, `ResizeTT`, `ClearTT`, `ResetForSolve`, `ResetBestMovesLite` | -| **Solving** | `SolveBoard`, `CalcDdTable` (binary), `CalcDdTable` (PBN) | -| **Par Calculation** | `CalcPar` | -| **Logging** | `LogAppend`, `LogClear` | - diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 95246af4..485216dc 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -33,8 +33,7 @@ public class DDS /// /// Maximum number of threads to use. [Obsolete("Use SolverContext instead.")] - public void SetMaxThreads(int userThreads) - => DdsNative.SetMaxThreads(userThreads); + public void SetMaxThreads(int userThreads) => DdsNative.SetMaxThreads(userThreads); /// /// Sets the threading backend used by the solver. @@ -51,11 +50,11 @@ public void SetMaxThreads(int userThreads) /// /// /// Threading backend code (see documentation). - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). [Obsolete("Use SolverContext instead.")] public int SetThreading(in int code) { - var rc = DdsNative.SetThreading( code); + var rc = DdsNative.SetThreading(code); ThrowIfError(rc, nameof(SetThreading)); return rc; @@ -80,7 +79,7 @@ public int SetThreading(in int code) [Obsolete("Use SolverContext instead.")] public void SetResources(in int maxMemoryMB, in int maxThreads) - => DdsNative.SetResources(maxMemoryMB, maxThreads); + => DdsNative.SetResources(maxMemoryMB, maxThreads); /// /// Frees memory used by the solver. @@ -97,7 +96,7 @@ public void SetResources(in int maxMemoryMB, in int maxThreads) /// resources when the context goes out of scope. /// /// - //[Obsolete("Use SolverContext instead.")] + [Obsolete("Use SolverContext instead.")] public void FreeMemory() => DdsNative.FreeMemory(); #endregion @@ -111,7 +110,8 @@ public void SetResources(in int maxMemoryMB, in int maxThreads) /// Analysis mode. /// The result. /// Index of the thread to use. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). + [Obsolete("Use SolverContext instead.")] public int SolveBoard( in Deal dl , in int target , in int solutions @@ -139,20 +139,20 @@ public int SolveBoard( in Deal dl /// Analysis mode. /// The result. /// Index of the thread to use. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). [Obsolete("Use SolverContext instead.")] public int SolveBoard( in DealPBN pbn - , in int target - , in int solutions - , in int mode - , out FutureTricks fut - , int threadIndex = 0) + , in int target + , in int solutions + , in int mode + , out FutureTricks fut + , int threadIndex = 0) { var rc = DdsNative.SolveBoardPBN( pbn , target , solutions , mode - , out fut + , out fut , threadIndex); ThrowIfError(rc, nameof(SolveBoard)); @@ -166,14 +166,14 @@ public int SolveBoard( in DealPBN pbn /// /// Multiple PBN deals. /// The results for solved boards. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). public int SolveAllBoards( in BoardsPBN boards , out SolvedBoards solved) { solved = default; //Note: To step into c++ code you must set a break in c++? var rc = DdsNative.SolveAllBoards( boards - , out solved); + , out solved); ThrowIfError(rc, nameof(SolveAllBoards)); return rc; @@ -184,55 +184,18 @@ public int SolveAllBoards( in BoardsPBN boards /// /// Multiple deals. /// The results for solved boards. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). [Obsolete("Use SolverContext instead.")] public int SolveAllBoards( in Boards bop - , out SolvedBoards solved) + , out SolvedBoards solved) { //Note: To step into c++ code you must set a break in c++? var rc = DdsNative.SolveAllBoardsBin( bop - , out solved); + , out solved); ThrowIfError(rc, nameof(SolveAllBoards)); return rc; } - - //public int SolveAllChunks( in BoardsPBN bop - // , out SolvedBoards solved - // , in int chunkSize) - //{ - // //Note: To step into c++ code you must set a break in c++? - // var rc = DdsNative.SolveAllChunks( bop - // , out solved - // , chunkSize); - - // ThrowIfError(rc, nameof(SolveAllChunks)); - // return rc; - //} - - //public int SolveAllChunksBin( in Boards bop - // , out SolvedBoards solved - // , in int chunkSize) - //{ - // //Note: To step into c++ code you must set a break in c++? - // var rc = DdsNative.SolveAllChunksBin( bop - // , out solved - // , chunkSize); - // ThrowIfError(rc, nameof(SolveAllChunksBin)); - // return rc; - //} - - //public int SolveAllChunkN( in BoardsPBN bop - // , out SolvedBoards solved - // , in int chunkSize) - //{ - // //Note: To step into c++ code you must set a break in c++? - // var rc = DdsNative.SolveAllChunksPBN( bop - // , out solved - // , chunkSize); - // ThrowIfError(rc, nameof(SolveAllChunksPBN)); - // return rc; - //} #endregion #region ====== Double Dummy Table Calculation ====== @@ -241,7 +204,7 @@ public int SolveAllBoards( in Boards bop /// /// Deal for which to calculate the table. /// The result table. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). public int CalcDdTable( in DdTableDeal deal , out DdTableResults table) { @@ -257,9 +220,9 @@ public int CalcDdTable( in DdTableDeal deal /// /// PBN deal for which to calculate the table. /// The result table. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). public int CalcDdTable( in DdTableDealPBN tableDealPBN - , out DdTableResults table) + , out DdTableResults table) { var rc = DdsNative.CalcDDtablePBN( tableDealPBN , out table); @@ -276,7 +239,7 @@ public int CalcDdTable( in DdTableDealPBN tableDealPBN /// Array of trump suit filters. /// The result tables. /// The par results. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). public int CalcAllTables( in DdTableDeals deals , in int mode , in intArray5 trumpFilter @@ -301,12 +264,12 @@ public int CalcAllTables( in DdTableDeals deals /// Array of trump suit filters. /// The result tables. /// The par results. - /// 1 on success, error code otherwise. + /// Error code (RETURN_NO_FAULT on success). public int CalcAllTables( in DdTableDealsPBN dealsp - , in int mode - , in intArray5 trumpFilter - , out DdTablesResult ResTables - , out AllParResults parResults) + , in int mode + , in intArray5 trumpFilter + , out DdTablesResult ResTables + , out AllParResults parResults) { var rc = DdsNative.CalcAllTablesPBN( dealsp , mode @@ -414,9 +377,9 @@ public int CalcPar( in DdTableDeal tableDeal /// Error code (RETURN_NO_FAULT on success). /// public int CalcPar( in DdTableDealPBN tableDealPBN - , out DdTableResults tableResults - , in int vulnerable - , out ParResults parResults) + , out DdTableResults tableResults + , in int vulnerable + , out ParResults parResults) { var rc = DdsNative.CalcParPBN( tableDealPBN , out tableResults @@ -427,6 +390,13 @@ public int CalcPar( in DdTableDealPBN tableDealPBN return rc; } + /// + /// Calculates par score and contracts for both sides based on the double dummy table results. + /// + /// Double dummy table results. + /// Output: par results for both sides. + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// Error code (RETURN_NO_FAULT on success). public int ParSide( in DdTableResults table , out ParResultsDealers sidesRes , in int vulnerable) @@ -439,15 +409,15 @@ public int ParSide( in DdTableResults table return rc; } - /// - /// Calculates par score and contracts for a specific dealer and vulnerability. - /// - /// - /// - /// - /// - /// - public int ParDealer( in DdTableResults table + /// + /// Calculates par score and contracts for a specific dealer and vulnerability. + /// + /// + /// + /// + /// + /// + public int ParDealer( in DdTableResults table , out ParResultsDealer pres , in int dealer , in int vulnerable) @@ -460,15 +430,16 @@ public int ParDealer( in DdTableResults table ThrowIfError(rc, nameof(ParDealer)); return rc; } - /// - /// Calculates par score and contract types for both sides for a specific dealer and vulnerability. - /// - /// - /// - /// - /// - /// - public int DealerParBothSides( in DdTableResults table + + /// + /// Calculates par score and contract types for both sides for a specific dealer and vulnerability. + /// + /// + /// + /// + /// + /// + public int DealerParBothSides( in DdTableResults table , out ParResultsMaster pres , in int dealer , in int vulnerable) @@ -482,6 +453,13 @@ public int DealerParBothSides( in DdTableResults table return rc; } + /// + /// calculates par score and contract types for both sides based on the double dummy table results. + /// + /// Double dummy table results. + /// Output: par results for both sides. + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// Error code (RETURN_NO_FAULT on success). public int ParAll( in DdTableResults table , out ParResultsMasters sidesRes , in int vulnerable) @@ -495,29 +473,15 @@ public int ParAll( in DdTableResults table } #endregion - #region ====== Par Text Conversion ====== - public int ConvertToTextFormat( in ParResultsMaster pres - , out string resp) - { - var str = new StringBuilder(80); - var rc = DdsNative.ConvertToDealerTextFormat( pres - , str); - ThrowIfError(rc, nameof(ConvertToTextFormat)); - resp = str.ToString(); - return rc; - } - - public int ConvertToTextFormat( in ParResultsMasters pres - , out ParTextResults resp) - { - var rc = DdsNative.ConvertToSidesTextFormat( pres - , out resp); - ThrowIfError(rc, nameof(ConvertToTextFormat)); - return rc; - } - #endregion - #region ====== Play Analysis ====== + /// + /// Analyzes a play trace for a given deal and determines the optimal line of play. + /// + /// The deal to analyze. + /// The play trace to analyze. + /// The result of the analysis, including optimal line and tricks. + /// The thread ID for parallel processing. + /// Error code (RETURN_NO_FAULT on success). public int AnalysePlay( in Deal dl , in PlayTraceBin play , out SolvedPlay solved @@ -531,10 +495,18 @@ public int AnalysePlay( in Deal dl return rc; } + /// + /// analyzes a play trace for a given deal in PBN format and determines the optimal line of play. + /// + /// The PBN deal to analyze. + /// The play trace in PBN format to analyze. + /// The result of the analysis, including optimal line and tricks. + /// The thread ID for parallel processing. + /// Error code (RETURN_NO_FAULT on success). public int AnalysePlay( in DealPBN dlPBN - , in PlayTracePBN playPBN - , out SolvedPlay solved - , in int thrId) + , in PlayTracePBN playPBN + , out SolvedPlay solved + , in int thrId) { var rc = DdsNative.AnalysePlayPBN( dlPBN , in playPBN @@ -544,6 +516,14 @@ public int AnalysePlay( in DealPBN dlPBN return rc; } + /// + /// Analyzes all play traces for a given set of boards and determines the optimal lines of play. + /// + /// The boards to analyze. + /// The play traces to analyze. + /// The result of the analysis, including optimal lines and tricks. + /// The chunk size for parallel processing. + /// Error code (RETURN_NO_FAULT on success). public int AnalyseAllPlays( in Boards bop , in PlayTracesBin plp , out SolvedPlays solved @@ -561,7 +541,6 @@ public int AnalyseAllPlays( in Boards bop ThrowIfError(rc, nameof(AnalyseAllPlays)); return rc; } - catch (Exception ex) { Debug.WriteLine(ex); @@ -569,6 +548,14 @@ public int AnalyseAllPlays( in Boards bop } } + /// + /// Analyzes all play traces for a given set of boards in PBN format and determines the optimal lines of play. + /// + /// The boards in PBN format to analyze. + /// The play traces in PBN format to analyze. + /// The result of the analysis, including optimal lines and tricks. + /// The chunk size for parallel processing. + /// Error code (RETURN_NO_FAULT on success). public int AnalyseAllPlays( in BoardsPBN bopPBN , in PlayTracesPBN plpPBN , out SolvedPlays solved @@ -582,72 +569,57 @@ public int AnalyseAllPlays( in BoardsPBN bopPBN ThrowIfError(rc, nameof(AnalyseAllPlays)); return rc; } - - ///// - ///// Benchmark variant: AnalyseAllPlaysBin with ref parameters for performance testing. - ///// Used to compare marshalling overhead between 'in' and 'ref' parameter passing. - ///// - //public int AnalyseAllPlaysBin_BenchRef( ref Boards bop - // , in PlayTracesBin plp - // , out SolvedPlays solved - // , int chunkSize) - //{ - // try - // { - // solved = new(); - - // var rc = DdsNative.AnalyseAllPlaysBin_Ref( ref bop - // , in plp - // , in solved - // , chunkSize); - - // ThrowIfError(rc, nameof(AnalyseAllPlaysBin_BenchRef)); - // return rc; - // } - - // catch (Exception ex) - // { - // Debug.WriteLine(ex); - // throw ex; - // } - //} - - ///// - ///// Benchmark variant: AnalyseAllPlaysBin with ref parameters for performance testing. - ///// Used to compare marshalling overhead between 'in' and 'ref' parameter passing. - ///// - //public int AnalyseAllPlaysBin_Bench3( Boards bop - // , PlayTracesBin plp - // , out SolvedPlays solved - // , int chunkSize) - //{ - // try - // { - // solved = new(); - - // var rc = DdsNative.AnalyseAllPlaysBin_Ref( ref bop - // , in plp - // , in solved - // , chunkSize); - - // ThrowIfError(rc, nameof(AnalyseAllPlaysBin_BenchRef)); - // return rc; - // } - - // catch (Exception ex) - // { - // Debug.WriteLine(ex); - // throw ex; - // } - //} #endregion #region ====== Utility Functions ====== + #region ====== Par Text Conversion ====== + /// + /// Converts par results to a human-readable text format. + /// + /// Par results to convert. + /// Output: human-readable par results. + /// Error code (RETURN_NO_FAULT on success). + public int ConvertToTextFormat( in ParResultsMaster pres + , out string resp) + { + var str = new StringBuilder(80); + var rc = DdsNative.ConvertToDealerTextFormat( pres + , str); + ThrowIfError(rc, nameof(ConvertToTextFormat)); + resp = str.ToString(); + return rc; + } + + /// + /// converts par results for both sides to a human-readable text format. + /// + /// Par results for both sides to convert. + /// Output: human-readable par results for both sides. + /// Error code (RETURN_NO_FAULT on success). + public int ConvertToTextFormat( in ParResultsMasters pres + , out ParTextResults resp) + { + var rc = DdsNative.ConvertToSidesTextFormat( pres + , out resp); + ThrowIfError(rc, nameof(ConvertToTextFormat)); + return rc; + } + #endregion + + /// + /// Retrieves information about the DDS library. + /// + /// The DDS information. public void GetDDSInfo(out DdsInfo info) { DdsNative.GetDDSInfo(out info); } + /// + /// Retrieves the error message corresponding to a given error code. + /// + /// The error code for which to retrieve the message. + /// Output: the error message corresponding to the provided code. public void ErrorMessage( in int code , out string line) { @@ -655,6 +627,8 @@ public void ErrorMessage( in int code DdsNative.ErrorMessage(code, str); line = str.ToString(); } + + #endregion #region private methods diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index 47f85731..7d38d9b8 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -34,20 +34,15 @@ public void Dispose() #endregion #region TT Management - public void ConfigureTT(TTKind kind, int defaultMb, int maxMb) - => DdsNative.dds_configure_tt(Handle, kind, defaultMb, maxMb); + public void ConfigureTT(TTKind kind, int defaultMb, int maxMb) => DdsNative.dds_configure_tt(Handle, kind, defaultMb, maxMb); - public void ResizeTT(int defaultMb, int maxMb) - => DdsNative.dds_resize_tt(Handle, defaultMb, maxMb); + public void ResizeTT(int defaultMb, int maxMb) => DdsNative.dds_resize_tt(Handle, defaultMb, maxMb); - public void ClearTT() - => DdsNative.dds_clear_tt(Handle); + public void ClearTT() => DdsNative.dds_clear_tt(Handle); - public void ResetForSolve() - => DdsNative.dds_reset_for_solve(Handle); + public void ResetForSolve() => DdsNative.dds_reset_for_solve(Handle); - public void ResetBestMovesLite() - => DdsNative.dds_reset_best_moves_lite(Handle); + public void ResetBestMovesLite() => DdsNative.dds_reset_best_moves_lite(Handle); #endregion #region Solving @@ -68,78 +63,82 @@ public int SolveBoard( in Deal dl return rc; } - public int CalcDdTable( in DdTableDeal table_deal - , out DdTableResults table_results) - { - var rc = DdsNative.calc_dd_table( Handle - , in table_deal - , out table_results); - - ThrowIfError(rc, nameof(SolveBoard)); - return rc; - } - - public int CalcDdTable( in DdTableDealPBN table_deal_pbn - , out DdTableResults table_results) - { - var rc = DdsNative.calc_dd_table_pbn( Handle - , in table_deal_pbn + #region CalcDdTable - Double Dummy Table and Par + public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_results) + { + var rc = DdsNative.calc_dd_table( Handle + , in table_deal , out table_results); - ThrowIfError(rc, nameof(CalcDdTable)); - return rc; - } - - /// -/// Calculates par score and contracts for a deal table. -/// -/// -/// -/// Computes the double dummy table for the given deal, then calculates par score -/// and contracts based on vulnerability. -/// -/// -/// This function is equivalent to calling CalcDDtable followed by -/// Par in the legacy C API. -/// -/// -/// -/// Deal represented as card holdings for each hand. -/// -/// -/// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). -/// -/// -/// Output: double dummy table results. -/// -/// -/// Output: par score and contract strings. -/// -/// -/// Error code (RETURN_NO_FAULT on success). -/// - - public int CalcPar( in DdTableDeal table_deal - , int vulnerable - , out DdTableResults table_results - , out ParResults par_results) - { - var rc = DdsNative.calc_par( Handle - , in table_deal - , vulnerable - , out table_results - , out par_results); - ThrowIfError(rc, nameof(CalcPar)); - return rc; - } + ThrowIfError(rc, nameof(SolveBoard)); + return rc; + } + + public int CalcDdTable(in DdTableDealPBN table_deal_pbn, out DdTableResults table_results) + { + var rc = DdsNative.calc_dd_table_pbn( Handle + , in table_deal_pbn + , out table_results); + + ThrowIfError(rc, nameof(CalcDdTable)); + return rc; + } + + /// + /// Calculates par score and contracts for a deal table. + /// + /// + /// + /// Computes the double dummy table for the given deal, then calculates par score + /// and contracts based on vulnerability. + /// + /// + /// This function is equivalent to calling CalcDDtable followed by + /// Par in the legacy C API. + /// + /// + /// + /// Deal represented as card holdings for each hand. + /// + /// + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// + /// + /// Output: double dummy table results. + /// + /// + /// Output: par score and contract strings. + /// + /// + /// Error code (RETURN_NO_FAULT on success). + /// + public int CalcPar( in DdTableDeal table_deal + , int vulnerable + , out DdTableResults table_results + , out ParResults par_results) + { + var rc = DdsNative.calc_par( Handle + , in table_deal + , vulnerable + , out table_results + , out par_results); + ThrowIfError(rc, nameof(CalcPar)); + return rc; + } + #endregion #endregion #region Logging - public void LogAppend(string message) - => DdsNative.dds_log_append(Handle, message); - - public void LogClear() - => DdsNative.dds_log_clear(Handle); + /// + /// Appends a message to the DDS log. + /// + /// The message to append to the log. + public void LogAppend(string message) => DdsNative.dds_log_append(Handle, message); + + /// + /// Clears the DDS log. + /// + public void LogClear() => DdsNative.dds_log_clear(Handle); #endregion #region private methods diff --git a/dotnet/DDS_Core/Helpers/FourHands.cs b/dotnet/DDS_Core/Helpers/FourHands.cs index 1d27543a..f458378b 100644 --- a/dotnet/DDS_Core/Helpers/FourHands.cs +++ b/dotnet/DDS_Core/Helpers/FourHands.cs @@ -11,6 +11,18 @@ public unsafe struct FourHands private fixed uint data[SIZE]; + public FourHands() + { + } + + public FourHands(uint[][] src) + { + int k = 0; + + for (int r = 0; r < Math.Min(ROWS ,src.Length); r++) + for (int c = 0; c < Math.Min(COLS, src[r].Length); c++) + data[k++] = src[r][c]; + } public Span AsSpan() => MemoryMarshal.CreateSpan(ref data[0], SIZE); @@ -67,8 +79,8 @@ public static implicit operator FourHands(uint[][] src) int k = 0; - for (int r = 0; r < 4; r++) - for (int c = 0; c < 4; c++) + for (int r = 0; r < Math.Min(ROWS ,src.Length); r++) + for (int c = 0; c < Math.Min(COLS, src[r].Length); c++) buf.data[k++] = src[r][c]; return buf; diff --git a/library/src/api/BUILD.bazel b/library/src/api/BUILD.bazel index 268a0b74..958b2388 100644 --- a/library/src/api/BUILD.bazel +++ b/library/src/api/BUILD.bazel @@ -8,6 +8,7 @@ cc_library( "calc_par.hpp", "dds.h", "dll.h", + "dds_api.h", "portab.h", "PBN.h", # Case-sensitive on consumers; cannot add pbn.h on macOS CI "solve_board.hpp", diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp index f7d89e59..4af0b6d1 100644 --- a/library/src/api/dds_api.hpp +++ b/library/src/api/dds_api.hpp @@ -1,4 +1,4 @@ -// dds_api.hpp +// File: dds_api.hpp #pragma once #include #include From 0d545630654f0ef0291dee0ca17876ce0ebfbfb0 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:04:55 +0200 Subject: [PATCH 031/157] Warning on Directory.Build.props eliminated --- solution/DDS.vcxproj | 9 +++------ solution/analyse_all_plays_bin.vcxproj | 5 ++--- solution/analyse_play_bin.vcxproj | 9 +++------ solution/analyse_play_pbn.vcxproj | 10 ++++------ solution/calc_all_tables.vcxproj | 11 ++++------- solution/calc_all_tables_pbn.vcxproj | 11 ++++------- solution/calc_dd_table.vcxproj | 10 ++++------ solution/calc_par_context_example.vcxproj | 12 ++++-------- solution/dds_native.vcxproj | 8 +++----- solution/dealer_par.vcxproj | 10 ++++------ solution/hands.vcxproj | 8 +++----- solution/migration_example.vcxproj | 10 ++++------ solution/par.vcxproj | 11 ++++------- solution/solve_all_boards.vcxproj | 10 ++++------ solution/solve_board.vcxproj | 8 +++----- solution/solve_board_pbn.vcxproj | 11 ++++------- 16 files changed, 57 insertions(+), 96 deletions(-) diff --git a/solution/DDS.vcxproj b/solution/DDS.vcxproj index 53def24f..b41de484 100644 --- a/solution/DDS.vcxproj +++ b/solution/DDS.vcxproj @@ -20,9 +20,6 @@ - - - StaticLibrary @@ -48,9 +45,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/analyse_all_plays_bin.vcxproj b/solution/analyse_all_plays_bin.vcxproj index 43f4b8a0..6d99d7d7 100644 --- a/solution/analyse_all_plays_bin.vcxproj +++ b/solution/analyse_all_plays_bin.vcxproj @@ -18,7 +18,6 @@ - @@ -29,8 +28,8 @@ - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/analyse_play_bin.vcxproj b/solution/analyse_play_bin.vcxproj index 084139b4..9d76ff4d 100644 --- a/solution/analyse_play_bin.vcxproj +++ b/solution/analyse_play_bin.vcxproj @@ -19,9 +19,6 @@ - - - @@ -32,9 +29,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/analyse_play_pbn.vcxproj b/solution/analyse_play_pbn.vcxproj index c64dee6f..d94e3dff 100644 --- a/solution/analyse_play_pbn.vcxproj +++ b/solution/analyse_play_pbn.vcxproj @@ -17,9 +17,7 @@ false - - - + @@ -30,9 +28,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/calc_all_tables.vcxproj b/solution/calc_all_tables.vcxproj index c1f9d949..fe9c3061 100644 --- a/solution/calc_all_tables.vcxproj +++ b/solution/calc_all_tables.vcxproj @@ -17,10 +17,7 @@ false - - - - + @@ -30,9 +27,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/calc_all_tables_pbn.vcxproj b/solution/calc_all_tables_pbn.vcxproj index 3347854b..edcc78bd 100644 --- a/solution/calc_all_tables_pbn.vcxproj +++ b/solution/calc_all_tables_pbn.vcxproj @@ -16,10 +16,7 @@ false - - - - + @@ -29,9 +26,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/calc_dd_table.vcxproj b/solution/calc_dd_table.vcxproj index 5b4f83ce..2ec6afe0 100644 --- a/solution/calc_dd_table.vcxproj +++ b/solution/calc_dd_table.vcxproj @@ -16,9 +16,7 @@ false - - - + @@ -28,9 +26,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/calc_par_context_example.vcxproj b/solution/calc_par_context_example.vcxproj index 4ca4320e..36c1c6e6 100644 --- a/solution/calc_par_context_example.vcxproj +++ b/solution/calc_par_context_example.vcxproj @@ -23,10 +23,6 @@ - - - @@ -46,10 +42,10 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ - + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ + diff --git a/solution/dds_native.vcxproj b/solution/dds_native.vcxproj index 675ba727..ab85d7f7 100644 --- a/solution/dds_native.vcxproj +++ b/solution/dds_native.vcxproj @@ -19,8 +19,6 @@ 10.0 - - DynamicLibrary true @@ -42,9 +40,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/dealer_par.vcxproj b/solution/dealer_par.vcxproj index 80c488db..c769e8e0 100644 --- a/solution/dealer_par.vcxproj +++ b/solution/dealer_par.vcxproj @@ -17,9 +17,7 @@ false - - - + @@ -30,9 +28,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/hands.vcxproj b/solution/hands.vcxproj index 1a743094..1192b6fc 100644 --- a/solution/hands.vcxproj +++ b/solution/hands.vcxproj @@ -16,8 +16,6 @@ false - - @@ -27,9 +25,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/migration_example.vcxproj b/solution/migration_example.vcxproj index dfcc6b32..9c6da4bf 100644 --- a/solution/migration_example.vcxproj +++ b/solution/migration_example.vcxproj @@ -16,9 +16,7 @@ false - - - + @@ -28,9 +26,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/par.vcxproj b/solution/par.vcxproj index a699bc92..62d2b6bb 100644 --- a/solution/par.vcxproj +++ b/solution/par.vcxproj @@ -16,10 +16,7 @@ false - - - - + @@ -29,9 +26,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/solve_all_boards.vcxproj b/solution/solve_all_boards.vcxproj index 238ca532..d373a9b2 100644 --- a/solution/solve_all_boards.vcxproj +++ b/solution/solve_all_boards.vcxproj @@ -16,9 +16,7 @@ false - - - + @@ -29,9 +27,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/solve_board.vcxproj b/solution/solve_board.vcxproj index 1bd0b9cc..36068883 100644 --- a/solution/solve_board.vcxproj +++ b/solution/solve_board.vcxproj @@ -16,8 +16,6 @@ false - - @@ -27,9 +25,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ diff --git a/solution/solve_board_pbn.vcxproj b/solution/solve_board_pbn.vcxproj index 8bd53286..dd7580a0 100644 --- a/solution/solve_board_pbn.vcxproj +++ b/solution/solve_board_pbn.vcxproj @@ -16,10 +16,7 @@ false - - - - + @@ -28,9 +25,9 @@ - - $(BuildDir)\bin\$(platform)\$(Configuration)\ - $(BuildDir)\int\$(platform)\$(Configuration)\$(ProjectName)\ + + ..\Build\bin\$(platform)\$(Configuration)\ + ..\Build\int\$(platform)\$(Configuration)\$(ProjectName)\ From cf605ef92f6d1c7d0f33ae3becd7d7365fd3e3dd Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:34:08 +0200 Subject: [PATCH 032/157] BUILD.bazel changed to fix error --- library/src/BUILD.bazel | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 72797025..f578af04 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -44,17 +44,14 @@ cc_library( ], ) -cc_library( +cc_binary( name = "dds_native", - hdrs = ["//library/src:dds_headers"], srcs = ["//library/src:dds_sources"], - includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, local_defines = DDS_LOCAL_DEFINES, - visibility = ["//visibility:public"], - include_prefix = "dds", linkshared = 1, + visibility = ["//visibility:public"], deps = [ "//library/src/api:api_definitions", "//library/src/utility:constants", From 2c364d42e914d308e983244aa9c862ef1898e087 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:37:04 +0200 Subject: [PATCH 033/157] dds_native removed from BUILD.bazel --- library/src/BUILD.bazel | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index f578af04..730bc3b7 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -44,26 +44,6 @@ cc_library( ], ) -cc_binary( - name = "dds_native", - srcs = ["//library/src:dds_sources"], - copts = DDS_CPPOPTS, - linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, - linkshared = 1, - visibility = ["//visibility:public"], - deps = [ - "//library/src/api:api_definitions", - "//library/src/utility:constants", - "//library/src/lookup_tables:lookup_tables", - "//library/src/heuristic_sorting", - "//library/src/trans_table", - "//library/src/moves:moves", - "//library/src/system", - "//library/src/solver_context:solver_context", - ], -) - filegroup( name = "testable_dds_sources", srcs = glob([ From 0f7ff294422efaa7b8e0c2d1b2c6d7aa42f4b585 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:47:24 +0200 Subject: [PATCH 034/157] The file is named dds_api.hpp and not dds_api.h -hanged in BUILD.bazel --- library/src/api/BUILD.bazel | 2 +- library/src/api/dds_api.hpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/library/src/api/BUILD.bazel b/library/src/api/BUILD.bazel index 958b2388..b3aacc6b 100644 --- a/library/src/api/BUILD.bazel +++ b/library/src/api/BUILD.bazel @@ -8,7 +8,7 @@ cc_library( "calc_par.hpp", "dds.h", "dll.h", - "dds_api.h", + "dds_api.hpp", "portab.h", "PBN.h", # Case-sensitive on consumers; cannot add pbn.h on macOS CI "solve_board.hpp", diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp index 4af0b6d1..183ca030 100644 --- a/library/src/api/dds_api.hpp +++ b/library/src/api/dds_api.hpp @@ -2,7 +2,6 @@ #pragma once #include #include -#include extern "C" { From 8062a0dd9c076737e8530057197acaec99d04dc9 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sun, 7 Jun 2026 20:16:11 +0200 Subject: [PATCH 035/157] api/dds_api.hpp removed from solver_context.cpp --- library/src/solver_context/solver_context.cpp | 2 +- solution/Solution.slnx | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index d5b5daa3..fd85df64 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -5,7 +5,7 @@ #include #include -#include +//#include #include #include diff --git a/solution/Solution.slnx b/solution/Solution.slnx index 9c4a4476..68626a69 100644 --- a/solution/Solution.slnx +++ b/solution/Solution.slnx @@ -8,57 +8,72 @@ + + + + + + + - + + + + + + + + + From 4f7cd058b50bbe7b75f567b13af3cf8fccb66531 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:56:25 +0200 Subject: [PATCH 036/157] Changes suggested by Copilots review --- .gitignore | 3 ++ dotnet/DDS_Core/DDS.cs | 10 +---- dotnet/DDS_Core/DDS_Core.csproj | 21 --------- dotnet/DDS_Core/Helpers/ResultsTable.cs | 58 ------------------------- dotnet/DDS_Core/Native/DdsNative.cs | 6 +-- dotnet/DDS_Core_Demo/Program.cs | 8 ++-- library/src/api/calc_par.hpp | 2 +- library/src/dds_api.cpp | 10 +++++ solution/dds_native.vcxproj | 1 + solution/dds_native.vcxproj.filters | 3 ++ 10 files changed, 28 insertions(+), 94 deletions(-) delete mode 100644 dotnet/DDS_Core/Helpers/ResultsTable.cs create mode 100644 library/src/dds_api.cpp diff --git a/.gitignore b/.gitignore index 5daf9358..d4ecf154 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ Build/int obj .cr +# Ignore miscellaneous files +.github + # Prerequisites *.d diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 485216dc..94c4403f 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -529,8 +529,7 @@ public int AnalyseAllPlays( in Boards bop , out SolvedPlays solved , in int chunkSize) { - try - { + solved = new(); var rc = DdsNative.AnalyseAllPlaysBin( bop @@ -540,12 +539,7 @@ public int AnalyseAllPlays( in Boards bop ThrowIfError(rc, nameof(AnalyseAllPlays)); return rc; - } - catch (Exception ex) - { - Debug.WriteLine(ex); - throw ex; - } + } /// diff --git a/dotnet/DDS_Core/DDS_Core.csproj b/dotnet/DDS_Core/DDS_Core.csproj index b6e7c17a..35151565 100644 --- a/dotnet/DDS_Core/DDS_Core.csproj +++ b/dotnet/DDS_Core/DDS_Core.csproj @@ -9,25 +9,4 @@ ..\..\Build\bin\ True - - - - - - - - - - - - - - - - - - - - - diff --git a/dotnet/DDS_Core/Helpers/ResultsTable.cs b/dotnet/DDS_Core/Helpers/ResultsTable.cs deleted file mode 100644 index 4dbb588f..00000000 --- a/dotnet/DDS_Core/Helpers/ResultsTable.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Runtime.InteropServices; - -namespace DDS_Core; - -/// -/// Tricks per strain and hand using 2D indexing. -/// Access: res_table[strain, hand] where strain=0-4, hand=0-3 -/// -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ResultsTable -{ - public const int ROWS = 5; - public const int COLS = 4; - public const int SIZE = ROWS * COLS; - - private fixed int data[SIZE]; - - public Span AsSpan() - => MemoryMarshal.CreateSpan(ref data[0], SIZE); - - public Span RowAsSpan(int row) - { - if ((uint)row >= ROWS) - throw new ArgumentOutOfRangeException(nameof(row)); - - // ref to first byte in the array for the specified row - ref int start = ref data[row * COLS]; - - return MemoryMarshal.CreateSpan(ref start, COLS); - } - - public Span this[int row] -{ - get - { - if ((uint)row >= ROWS) - throw new IndexOutOfRangeException(); - - // ref to the first element in the specified row - ref int start = ref data[row * COLS]; - - // correct way to create a span over the fixed buffer - return MemoryMarshal.CreateSpan(ref start, COLS); - } -} - public ref int this[int row, int col] - { - get - { - if ((uint)row >= ROWS || (uint)col >= COLS) - throw new IndexOutOfRangeException(); - - return ref data[row * COLS + col]; - } - } -} - - diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 1929c651..2d3a2492 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -39,13 +39,13 @@ internal static extern void dds_resize_tt( SolverContextHandle ctx internal static extern void dds_reset_for_solve(SolverContextHandle ctx); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void dds_reset_best_moves_lite(in SolverContextHandle ctx); + internal static extern void dds_reset_best_moves_lite(SolverContextHandle ctx); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - internal static extern void dds_log_append(in SolverContextHandle ctx, in string msg); + internal static extern void dds_log_append( SolverContextHandle ctx, string msg); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void dds_log_clear(in SolverContextHandle ctx); + internal static extern void dds_log_clear( SolverContextHandle ctx); #endregion #region ====== SolverContext methods ====== diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 47503477..851487a7 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -29,7 +29,7 @@ static void Main(string[] args) // // PERFORMANCE TEST: Measure AnalyseAllPlaysBin P/Invoke performance // Run in Release mode for accurate results - bool isPerformanceTest = args.Length > 0 && args[0] == "benchmark"; + isPerformanceTest = args.Length > 0 && args[0] == "benchmark"; if (isPerformanceTest) { @@ -97,7 +97,9 @@ static void Main(string[] args) doSolveBoardV3(dds, ctx, TestData.deals[0]); doCalcDdTableV3(dds, ctx, TestData.ddTableDeal); doCalcDdTableV3(dds, ctx, TestData.ddTableDealPBN); - } + ctx.LogAppend("Completed V3.0.0 samples"); + //ctx.LogClear(); + } #endregion Console.WriteLine($"Press any key to continue..."); @@ -735,7 +737,7 @@ private static void doErrorMessage(DDS dds, int rc) private static void DisplayTricks() { - Console.WriteLine($" N E S E"); + Console.WriteLine($" N E S W"); Console.WriteLine($" __ __ __ __"); for (var denonination = 0; denonination < 5; denonination++) diff --git a/library/src/api/calc_par.hpp b/library/src/api/calc_par.hpp index 064c1585..6a2aeda3 100644 --- a/library/src/api/calc_par.hpp +++ b/library/src/api/calc_par.hpp @@ -59,7 +59,7 @@ auto calc_par( * @param par_results Output: par score and contract strings * @return Error code (RETURN_NO_FAULT on success) */ -EXTERN_C DLLEXPORT auto calc_par( +DLLEXPORT auto calc_par( SolverContext& ctx, const DdTableDeal& table_deal, int vulnerable, diff --git a/library/src/dds_api.cpp b/library/src/dds_api.cpp new file mode 100644 index 00000000..457c5e48 --- /dev/null +++ b/library/src/dds_api.cpp @@ -0,0 +1,10 @@ +/* + DDS, a bridge double dummy solver. + + Version 3 API implementation - Solver Context Management + This file ensures the dds_api.hpp functions are compiled into the DLL. +*/ + +#include + + diff --git a/solution/dds_native.vcxproj b/solution/dds_native.vcxproj index ab85d7f7..2b1a6677 100644 --- a/solution/dds_native.vcxproj +++ b/solution/dds_native.vcxproj @@ -69,6 +69,7 @@ + diff --git a/solution/dds_native.vcxproj.filters b/solution/dds_native.vcxproj.filters index af07ca4a..74d08c55 100644 --- a/solution/dds_native.vcxproj.filters +++ b/solution/dds_native.vcxproj.filters @@ -60,6 +60,9 @@ library\src + + library\src + library\src From 4481722a1570b6b184ba3e325c0a399be5a998ab Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:57:45 +0200 Subject: [PATCH 037/157] CodeRush deleted --- dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml diff --git a/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml b/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml deleted file mode 100644 index f9335fb6..00000000 --- a/dotnet/DDS_Core/.cr/personal/Navigation/Code Places.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - True - - \ No newline at end of file From 649bb47d8c10b0f405ab6443dda1e01761ec9a81 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:15:53 +0200 Subject: [PATCH 038/157] Copilot suggestions implemented --- dotnet/DDS_Core/DataModel/SolverContext.cs | 4 +- dotnet/DDS_Core/Helpers/FourHands.cs | 2 +- dotnet/DDS_Core/Helpers/intArray5x4.cs | 16 ++-- .../DDS_Core/Helpers/intArray5xDebugView.cs | 1 - dotnet/DDS_Core/Native/DdsNative.cs | 12 +-- library/src/api/calc_dd_table.hpp | 4 +- library/src/api/dds_api.hpp | 72 ++++++------------ library/src/dds_api.cpp | 75 +++++++++++++++++++ solution/dds_native.vcxproj | 2 +- 9 files changed, 117 insertions(+), 71 deletions(-) diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index 7d38d9b8..9c2c2f14 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -66,7 +66,7 @@ public int SolveBoard( in Deal dl #region CalcDdTable - Double Dummy Table and Par public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_results) { - var rc = DdsNative.calc_dd_table( Handle + var rc = DdsNative.calc_ddtable( Handle , in table_deal , out table_results); @@ -76,7 +76,7 @@ public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_resul public int CalcDdTable(in DdTableDealPBN table_deal_pbn, out DdTableResults table_results) { - var rc = DdsNative.calc_dd_table_pbn( Handle + var rc = DdsNative.calc_ddtable_pbn( Handle , in table_deal_pbn , out table_results); diff --git a/dotnet/DDS_Core/Helpers/FourHands.cs b/dotnet/DDS_Core/Helpers/FourHands.cs index f458378b..3c2e18e6 100644 --- a/dotnet/DDS_Core/Helpers/FourHands.cs +++ b/dotnet/DDS_Core/Helpers/FourHands.cs @@ -67,7 +67,7 @@ public static implicit operator FourHands(uint[] src) { var buf = new FourHands(); - for (int c = 0; c < 16; c++) + for (int c = 0; c < SIZE; c++) buf.data[c] = src[c]; return buf; diff --git a/dotnet/DDS_Core/Helpers/intArray5x4.cs b/dotnet/DDS_Core/Helpers/intArray5x4.cs index 1b1b6107..2a0948e1 100644 --- a/dotnet/DDS_Core/Helpers/intArray5x4.cs +++ b/dotnet/DDS_Core/Helpers/intArray5x4.cs @@ -20,7 +20,7 @@ public Span AsSpan() public Span RowAsSpan(int row) { - if (row >= ROWS || row < 0) + if ((uint)row >= ROWS ) throw new ArgumentOutOfRangeException(nameof(row)); // ref to first byte in the array for the specified row @@ -48,8 +48,7 @@ public Span this[int row] { get { - if (row >= ROWS || row < 0 - || col >= COLS || col < 0) + if ((uint)row >= ROWS || (uint)col >= COLS) throw new IndexOutOfRangeException(); return ref data[row * COLS + col]; @@ -59,8 +58,9 @@ public Span this[int row] public static implicit operator intArray5x4(int[] src) { var buf = new intArray5x4(); + int count = Math.Min(src.Length, SIZE); - for (int c = 0; c < 16; c++) + for (int c = 0; c < count; c++) buf.data[c] = src[c]; return buf; @@ -71,9 +71,11 @@ public static implicit operator intArray5x4(int[][] src) var buf = new intArray5x4(); int k = 0; + int rows = Math.Min(src.Length, SIZE); + int cols = Math.Min(src[0].Length, COLS); - for (int r = 0; r < 4; r++) - for (int c = 0; c < 4; c++) + for (int r = 0; r < rows; r++) + for (int c = 0; c < cols; c++) buf.data[k++] = src[r][c]; return buf; @@ -82,8 +84,6 @@ public static implicit operator intArray5x4(int[][] src) public string GetString(int row) => string.Join(",", RowAsSpan(row).ToArray()); - //public override string ToString() - // => "["+string.Join("],[", Enumerable.Range(0, ROWS).Select(GetString)) +"]"; public override string ToString() { var parts = new string[ROWS]; diff --git a/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs b/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs index 9b05cd7d..d3a50c7a 100644 --- a/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs +++ b/dotnet/DDS_Core/Helpers/intArray5xDebugView.cs @@ -15,7 +15,6 @@ public intArray5xDebugView(T buffer) public string Row1 => _buffer.GetString(1); public string Row2 => _buffer.GetString(2); public string Row3 => _buffer.GetString(3); - public string Row4 => _buffer.GetString(4); } diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 2d3a2492..68910a82 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -59,10 +59,10 @@ public static extern int dds_solve_board( SolverContextHandle ctx #region Call_dd // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - //public static extern int calc_dd_table( in DdTableDeal table_deal + //public static extern int calc_ddtable( in DdTableDeal table_deal // , out DdTableResults table_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_dd_table( SolverContextHandle ctx + public static extern int calc_ddtable( SolverContextHandle ctx , in DdTableDeal table_deal , out DdTableResults table_results); @@ -70,7 +70,7 @@ public static extern int calc_dd_table( SolverContextHandle ctx //public static extern int calc_dd_table_pbn( in DdTableDealPBN table_deal_pbn // , out DdTableResults table_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_dd_table_pbn( SolverContextHandle ctx + public static extern int calc_ddtable_pbn( SolverContextHandle ctx , in DdTableDealPBN table_deal_pbn , out DdTableResults table_results); #endregion @@ -220,7 +220,7 @@ public static extern int SidesParBin( in DdTableResults table #endregion #region ====== Par Text Conversion ====== - [DllImport("dds_native", CharSet = CharSet.Ansi)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern int ConvertToDealerTextFormat( in ParResultsMaster pres , StringBuilder resp); @@ -236,7 +236,7 @@ public static extern int AnalysePlayBin( in Deal dl , out SolvedPlay solved , int thrId); - [DllImport("dds_native", CharSet = CharSet.Ansi)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern int AnalysePlayPBN( in DealPBN dlPBN , in PlayTracePBN playPBN , out SolvedPlay solved @@ -259,7 +259,7 @@ public static extern int AnalyseAllPlaysPBN( in BoardsPBN bopPBN [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern void GetDDSInfo(out DdsInfo info); - [DllImport("dds_native", CharSet = CharSet.Ansi)] + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern void ErrorMessage( int code , StringBuilder line); #endregion diff --git a/library/src/api/calc_dd_table.hpp b/library/src/api/calc_dd_table.hpp index 9c904b2e..51e1cb73 100644 --- a/library/src/api/calc_dd_table.hpp +++ b/library/src/api/calc_dd_table.hpp @@ -50,7 +50,7 @@ auto calc_dd_table( * DD-table calculations iterate all strains, so TT entry reuse across * calls may be limited depending on call patterns. */ -EXTERN_C DLLEXPORT auto calc_dd_table( +auto calc_dd_table( SolverContext& ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int; @@ -80,7 +80,7 @@ auto calc_dd_table_pbn( * @param table_results Output: double dummy table results * @return Error code (RETURN_NO_FAULT on success, RETURN_PBN_FAULT on parse error) */ -EXTERN_C DLLEXPORT auto calc_dd_table_pbn( + auto calc_dd_table_pbn( SolverContext& ctx, const DdTableDealPBN& table_deal_pbn, DdTableResults* table_results) -> int; diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp index 183ca030..ef0226f2 100644 --- a/library/src/api/dds_api.hpp +++ b/library/src/api/dds_api.hpp @@ -2,6 +2,7 @@ #pragma once #include #include +#include extern "C" { @@ -9,80 +10,51 @@ extern "C" { typedef SolverContext* DDS_SOLVER_CTX; // Creation - EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default() - { - SolverConfig cfg{}; - return new SolverContext(cfg); - } + EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default(); - EXTERN_C DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg) - { - return new SolverContext(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) - { - delete ctx; - } + 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) - { - ctx->configure_tt(kind, defMB, maxMB); - } + int defMB, int maxMB); EXTERN_C DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, int defMB, - int maxMB) - { - ctx->resize_tt(defMB, maxMB); - } + int maxMB); - EXTERN_C DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx) - { - ctx->clear_tt(); - } + EXTERN_C DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx); // Resets - EXTERN_C DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx) - { - ctx->reset_for_solve(); - } + 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) - { - ctx->reset_best_moves_lite(); - } + 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) - { - ctx->utilities().log_append(std::string(msg)); - } + const char* msg); - EXTERN_C DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx) - { - ctx->utilities().log_clear(); - } + 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 - { - return SolveBoard(*ctx, - dl, - target, - solutions, - mode, - futp) ; - } + FutureTricks* futp) -> int; + EXTERN_C DLLEXPORT auto calc_ddtable( + DDS_SOLVER_CTX ctx, + const DdTableDeal& table_deal, + DdTableResults* table_results) -> int; + + + EXTERN_C DLLEXPORT auto calc_ddtable_pbn( + DDS_SOLVER_CTX ctx, + const DdTableDealPBN& table_deal, + DdTableResults* table_results) -> int; } diff --git a/library/src/dds_api.cpp b/library/src/dds_api.cpp index 457c5e48..a4a8cc82 100644 --- a/library/src/dds_api.cpp +++ b/library/src/dds_api.cpp @@ -7,4 +7,79 @@ #include +// Creation +DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default() +{ + SolverConfig cfg{}; + return new SolverContext(cfg); +} +DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext(SolverConfig cfg) +{ + return new SolverContext(cfg); +} + +// SolverContext Destruction +DLLEXPORT void dds_destroy_solvercontext(DDS_SOLVER_CTX 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); +} + +DLLEXPORT void dds_resize_tt(DDS_SOLVER_CTX ctx, int defMB, int maxMB) +{ + ctx->resize_tt(defMB, maxMB); +} + +DLLEXPORT void dds_clear_tt(DDS_SOLVER_CTX ctx) +{ + ctx->clear_tt(); +} + +// Resets +DLLEXPORT void dds_reset_for_solve(DDS_SOLVER_CTX ctx) +{ + ctx->reset_for_solve(); +} + +DLLEXPORT void dds_reset_best_moves_lite(DDS_SOLVER_CTX ctx) +{ + 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 : "")); +} + +DLLEXPORT void dds_log_clear(DDS_SOLVER_CTX ctx) +{ + 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); +} + +DLLEXPORT auto calc_ddtable(DDS_SOLVER_CTX ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int +{ + return calc_dd_table(*ctx, table_deal, table_results); +} + +DLLEXPORT auto calc_ddtable_pbn(DDS_SOLVER_CTX ctx, const DdTableDealPBN& table_deal, DdTableResults* table_results) -> int +{ + + return calc_dd_table_pbn(*ctx, table_deal, table_results); +} diff --git a/solution/dds_native.vcxproj b/solution/dds_native.vcxproj index 2b1a6677..1c4f7a36 100644 --- a/solution/dds_native.vcxproj +++ b/solution/dds_native.vcxproj @@ -69,7 +69,7 @@ - + From 734ff97a018a95e52c86e76b23fe12daa40fe3ca Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 20:09:09 -0400 Subject: [PATCH 039/157] Ignore local Cursor and VS Code workspace files. Add *.code-workspace to .gitignore so editor-specific workspace configs stay out of the repo. Co-authored-by: Cursor --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d4ecf154..5a1351bb 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,6 @@ doxygen_output/ .claude/settings.local.json /dotnet/DDS_Core/.github/copilot-instructions.md + +# Cursor or VS Code workspace files +*.code-workspace From cb84a4625c7590d24395ceb790115118be43a750 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 22:12:28 -0400 Subject: [PATCH 040/157] Ignore Cursor or VS Code workspace files --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5a1351bb..4bd4cbc7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ !.vscode/mcp.json !.vscode/tasks.json +# Ignore Cursor or VS Code workspace files +*.code-workspace + # Ignore Visual Studio directory and files .vs #*/.vs/* @@ -110,6 +113,3 @@ doxygen_output/ .claude/settings.local.json /dotnet/DDS_Core/.github/copilot-instructions.md - -# Cursor or VS Code workspace files -*.code-workspace From 977d2073a6d65ccb6fb8831b77187ddc029625d1 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 22:27:53 -0400 Subject: [PATCH 041/157] Do not ignore .github. That would ignore github's Continuous Integration workflows. It's useful to be able to update them. --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4bd4cbc7..87b45633 100644 --- a/.gitignore +++ b/.gitignore @@ -18,9 +18,6 @@ Build/int obj .cr -# Ignore miscellaneous files -.github - # Prerequisites *.d From 3ee97e2215ae4edd33b3fb2e624486c01ecf928a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 22:56:28 -0400 Subject: [PATCH 042/157] Fix CalcDdTable error message naming in SolverContext. Use nameof(CalcDdTable) instead of nameof(SolveBoard) so DEBUG exceptions identify the failing method correctly. Co-authored-by: Cursor --- dotnet/DDS_Core/DataModel/SolverContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index 9c2c2f14..b5c4ec08 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -70,7 +70,7 @@ public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_resul , in table_deal , out table_results); - ThrowIfError(rc, nameof(SolveBoard)); + ThrowIfError(rc, nameof(CalcDdTable)); return rc; } From 8eda4d8adaf83f0556e0a8039dde237251edd2e4 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:18:10 +0200 Subject: [PATCH 043/157] Changes acording to Cursor suggestions --- dotnet/DDS_Core/DDS.cs | 19 +++++++++---------- dotnet/DDS_Core/DataModel/SolverContext.cs | 7 ++++--- dotnet/DDS_Core/Native/DdsNative.cs | 2 +- dotnet/DDS_Core_Demo/Program.cs | 2 +- library/src/api/calc_par.hpp | 2 +- library/src/dds_api.cpp | 12 ++++++++++++ 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 94c4403f..9757a25e 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -362,25 +362,25 @@ public int CalcPar( in DdTableDeal tableDeal /// /// /// - /// Deal represented as card holdings for each hand. + /// Deal represented as a PBN string for each hand. /// + /// + /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// /// /// Output: double dummy table results. /// - /// /// - /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). - /// /// /// Output: par score and contract strings. /// /// /// Error code (RETURN_NO_FAULT on success). /// - public int CalcPar( in DdTableDealPBN tableDealPBN - , out DdTableResults tableResults + public int CalcPar(in DdTableDealPBN tableDealPBN , in int vulnerable + , out DdTableResults tableResults , out ParResults parResults) - { + { var rc = DdsNative.CalcParPBN( tableDealPBN , out tableResults , vulnerable @@ -626,12 +626,11 @@ public void ErrorMessage( in int code #endregion #region private methods - private static void ThrowIfError(in int result, in string functionName) + [Conditional("DEBUG")] + private static void ThrowIfError(in int result, in string functionName) { -#if DEBUG if (result != (int)SolveBoardResult.NoFault) throw new InvalidOperationException($"{functionName} failed with code {result}: {result.GetRCErrorMessage()}"); -#endif } #endregion } diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index b5c4ec08..81ddfc10 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -117,7 +118,7 @@ public int CalcPar( in DdTableDeal table_deal , out DdTableResults table_results , out ParResults par_results) { - var rc = DdsNative.calc_par( Handle + var rc = DdsNative.calc_par_dll( Handle , in table_deal , vulnerable , out table_results @@ -142,12 +143,12 @@ public int CalcPar( in DdTableDeal table_deal #endregion #region private methods + [Conditional("DEBUG")] + private static void ThrowIfError(int result, string functionName) { -#if DEBUG if (result != (int)SolveBoardResult.NoFault) throw new InvalidOperationException($"{functionName} failed with code {result}: {result.GetRCErrorMessage()}"); -#endif } #endregion } diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 68910a82..2144f131 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -82,7 +82,7 @@ public static extern int calc_ddtable_pbn( SolverContextHandle ctx // , out DdTableResults table_results // , out ParResults par_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_par( SolverContextHandle ctx + public static extern int calc_par_dll( SolverContextHandle ctx , in DdTableDeal table_deal , int vulnerable , out DdTableResults table_results diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 851487a7..afd6d2b2 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -496,8 +496,8 @@ private static void doCalcPar(DDS dds, DdTableDealPBN ddTableDeal, int vulnabili tricks = new int[5, 4]; var rc = dds.CalcPar( in ddTableDeal - , out DdTableResults tResults , vulnability + , out DdTableResults tResults , out ParResults results ); Console.WriteLine(results.ParContractStrings); diff --git a/library/src/api/calc_par.hpp b/library/src/api/calc_par.hpp index 6a2aeda3..5dc3ebb5 100644 --- a/library/src/api/calc_par.hpp +++ b/library/src/api/calc_par.hpp @@ -59,7 +59,7 @@ auto calc_par( * @param par_results Output: par score and contract strings * @return Error code (RETURN_NO_FAULT on success) */ -DLLEXPORT auto calc_par( + auto calc_par( SolverContext& ctx, const DdTableDeal& table_deal, int vulnerable, diff --git a/library/src/dds_api.cpp b/library/src/dds_api.cpp index a4a8cc82..dddbda72 100644 --- a/library/src/dds_api.cpp +++ b/library/src/dds_api.cpp @@ -6,6 +6,7 @@ */ #include +#include // Creation DLLEXPORT DDS_SOLVER_CTX dds_create_solvercontext_default() @@ -83,3 +84,14 @@ DLLEXPORT auto calc_ddtable_pbn(DDS_SOLVER_CTX ctx, const DdTableDealPBN& table_ return calc_dd_table_pbn(*ctx, table_deal, table_results); } + +DLLEXPORT auto calc_par_dll( + SolverContext& 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); +} + From 2457b70def252ec04821c6a8f2bbff7215497ac1 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:06:13 +0200 Subject: [PATCH 044/157] calc_par and calc_dd_table exports renamed and C linkage ensured. SolverContext.Calc_Par added to demo --- dotnet/DDS_Core/DDS.cs | 32 +++++++--------- dotnet/DDS_Core/DataModel/SolverContext.cs | 15 ++------ .../DDS_Core/Helpers/SolverContextHandle.cs | 8 +--- dotnet/DDS_Core/Native/DdsNative.cs | 14 +++---- dotnet/DDS_Core_Demo/Program.cs | 37 +++++++++++++++---- library/src/api/dds_api.hpp | 12 +++++- library/src/dds_api.cpp | 10 ++--- 7 files changed, 69 insertions(+), 59 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 9757a25e..6ebab880 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -1,9 +1,7 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using System.Text; using DDS_Core.Helpers; using DDS_Core.Native; -using static System.Formats.Asn1.AsnWriter; namespace DDS_Core; @@ -79,7 +77,7 @@ public int SetThreading(in int code) [Obsolete("Use SolverContext instead.")] public void SetResources(in int maxMemoryMB, in int maxThreads) - => DdsNative.SetResources(maxMemoryMB, maxThreads); + => DdsNative.SetResources(maxMemoryMB, maxThreads); /// /// Frees memory used by the solver. @@ -189,6 +187,8 @@ public int SolveAllBoards( in BoardsPBN boards public int SolveAllBoards( in Boards bop , out SolvedBoards solved) { + solved = default; + //Note: To step into c++ code you must set a break in c++? var rc = DdsNative.SolveAllBoardsBin( bop , out solved); @@ -376,11 +376,11 @@ public int CalcPar( in DdTableDeal tableDeal /// /// Error code (RETURN_NO_FAULT on success). /// - public int CalcPar(in DdTableDealPBN tableDealPBN + public int CalcPar( in DdTableDealPBN tableDealPBN , in int vulnerable , out DdTableResults tableResults , out ParResults parResults) - { + { var rc = DdsNative.CalcParPBN( tableDealPBN , out tableResults , vulnerable @@ -529,17 +529,15 @@ public int AnalyseAllPlays( in Boards bop , out SolvedPlays solved , in int chunkSize) { - - solved = new(); + solved = new(); - var rc = DdsNative.AnalyseAllPlaysBin( bop - , plp - , out solved - , chunkSize); + var rc = DdsNative.AnalyseAllPlaysBin( bop + , plp + , out solved + , chunkSize); - ThrowIfError(rc, nameof(AnalyseAllPlays)); - return rc; - + ThrowIfError(rc, nameof(AnalyseAllPlays)); + return rc; } /// @@ -621,13 +619,11 @@ public void ErrorMessage( in int code DdsNative.ErrorMessage(code, str); line = str.ToString(); } - - #endregion #region private methods - [Conditional("DEBUG")] - private static void ThrowIfError(in int result, in string functionName) + [Conditional("DEBUG")] + private static void ThrowIfError(in int result, in string functionName) { if (result != (int)SolveBoardResult.NoFault) throw new InvalidOperationException($"{functionName} failed with code {result}: {result.GetRCErrorMessage()}"); diff --git a/dotnet/DDS_Core/DataModel/SolverContext.cs b/dotnet/DDS_Core/DataModel/SolverContext.cs index 81ddfc10..bdf29cc7 100644 --- a/dotnet/DDS_Core/DataModel/SolverContext.cs +++ b/dotnet/DDS_Core/DataModel/SolverContext.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; using DDS_Core.Helpers; using DDS_Core.Native; @@ -67,7 +61,7 @@ public int SolveBoard( in Deal dl #region CalcDdTable - Double Dummy Table and Par public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_results) { - var rc = DdsNative.calc_ddtable( Handle + var rc = DdsNative.dds_calc_dd_table( Handle , in table_deal , out table_results); @@ -77,7 +71,7 @@ public int CalcDdTable(in DdTableDeal table_deal, out DdTableResults table_resul public int CalcDdTable(in DdTableDealPBN table_deal_pbn, out DdTableResults table_results) { - var rc = DdsNative.calc_ddtable_pbn( Handle + var rc = DdsNative.dds_calc_dd_table_pbn( Handle , in table_deal_pbn , out table_results); @@ -118,7 +112,7 @@ public int CalcPar( in DdTableDeal table_deal , out DdTableResults table_results , out ParResults par_results) { - var rc = DdsNative.calc_par_dll( Handle + var rc = DdsNative.dds_calc_par( Handle , in table_deal , vulnerable , out table_results @@ -144,7 +138,6 @@ public int CalcPar( in DdTableDeal table_deal #region private methods [Conditional("DEBUG")] - private static void ThrowIfError(int result, string functionName) { if (result != (int)SolveBoardResult.NoFault) diff --git a/dotnet/DDS_Core/Helpers/SolverContextHandle.cs b/dotnet/DDS_Core/Helpers/SolverContextHandle.cs index 20011ceb..f2f09dd5 100644 --- a/dotnet/DDS_Core/Helpers/SolverContextHandle.cs +++ b/dotnet/DDS_Core/Helpers/SolverContextHandle.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using DDS_Core.Native; namespace DDS_Core; diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 2144f131..9ee2c487 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -1,7 +1,5 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using System.Text; -using static System.Net.WebRequestMethods; namespace DDS_Core.Native; @@ -59,18 +57,18 @@ public static extern int dds_solve_board( SolverContextHandle ctx #region Call_dd // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - //public static extern int calc_ddtable( in DdTableDeal table_deal + //public static extern int dds_calc_dd_table( in DdTableDeal table_deal // , out DdTableResults table_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_ddtable( SolverContextHandle ctx + public static extern int dds_calc_dd_table( SolverContextHandle ctx , in DdTableDeal table_deal , out DdTableResults table_results); // [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - //public static extern int calc_dd_table_pbn( in DdTableDealPBN table_deal_pbn + //public static extern int dds_calc_dd_table_pbn( in DdTableDealPBN table_deal_pbn // , out DdTableResults table_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_ddtable_pbn( SolverContextHandle ctx + public static extern int dds_calc_dd_table_pbn( SolverContextHandle ctx , in DdTableDealPBN table_deal_pbn , out DdTableResults table_results); #endregion @@ -82,7 +80,7 @@ public static extern int calc_ddtable_pbn( SolverContextHandle ctx // , out DdTableResults table_results // , out ParResults par_results); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int calc_par_dll( SolverContextHandle ctx + public static extern int dds_calc_par( SolverContextHandle ctx , in DdTableDeal table_deal , int vulnerable , out DdTableResults table_results diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index afd6d2b2..567526eb 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -1,12 +1,5 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Net; -using System.Text; -using System.Transactions; +using System.Diagnostics; using DDS_Core; -using Microsoft.Win32.SafeHandles; -using static System.Net.Mime.MediaTypeNames; -using static DDS_Core.CardRanks; namespace DDS_Core_Demo; @@ -97,6 +90,7 @@ static void Main(string[] args) doSolveBoardV3(dds, ctx, TestData.deals[0]); doCalcDdTableV3(dds, ctx, TestData.ddTableDeal); doCalcDdTableV3(dds, ctx, TestData.ddTableDealPBN); + doCalcParV3(dds, ctx, TestData.ddTableDeal, vulnability); ctx.LogAppend("Completed V3.0.0 samples"); //ctx.LogClear(); } @@ -435,6 +429,33 @@ private static void doPar(DDS dds, DdTableResults tableResults, int vulnability) Console.WriteLine(); } + private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal, int vulnability) + { + Console.WriteLine($"CalcPar V3"); + tricks = new int[5, 4]; + + var rc = ctx.CalcPar( in ddTableDeal + , vulnability + , out DdTableResults tResults + , out ParResults results ); + + Console.WriteLine(results.ParContractStrings); + Console.WriteLine(results.ParScores); + + for (var trump = 0; trump < 5; trump++) + for (var first = 0; first < 4; first++) + { + var decl =(first + 3) & 3; + + // record the number of tricks for declarer + tricks[trump, decl] = tResults.ResultsTable[trump, decl]; + } + + dds.FreeMemory(); + + DisplayTricks(); + } + private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal, int vulnability) { Console.WriteLine($"CalcPar"); diff --git a/library/src/api/dds_api.hpp b/library/src/api/dds_api.hpp index ef0226f2..92c6589f 100644 --- a/library/src/api/dds_api.hpp +++ b/library/src/api/dds_api.hpp @@ -46,15 +46,23 @@ extern "C" { int mode, FutureTricks* futp) -> int; - EXTERN_C DLLEXPORT auto calc_ddtable( + EXTERN_C DLLEXPORT auto dds_calc_dd_table( DDS_SOLVER_CTX ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int; - EXTERN_C DLLEXPORT auto calc_ddtable_pbn( + 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; + + } diff --git a/library/src/dds_api.cpp b/library/src/dds_api.cpp index dddbda72..a10973a6 100644 --- a/library/src/dds_api.cpp +++ b/library/src/dds_api.cpp @@ -74,24 +74,24 @@ DLLEXPORT auto dds_solve_board(DDS_SOLVER_CTX ctx, const Deal& dl, int target, i futp); } -DLLEXPORT auto calc_ddtable(DDS_SOLVER_CTX ctx, const DdTableDeal& table_deal, DdTableResults* table_results) -> int +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); } -DLLEXPORT auto calc_ddtable_pbn(DDS_SOLVER_CTX ctx, const DdTableDealPBN& table_deal, DdTableResults* table_results) -> int +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); } -DLLEXPORT auto calc_par_dll( - SolverContext& ctx, +DLLEXPORT auto dds_calc_par( + 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); } From b683df9b64ba476e553ff7a8a27c099e8f4ddd97 Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 12 Jun 2026 19:21:47 +0200 Subject: [PATCH 045/157] Indention fix, and change dds.FreeMemory to ctx.ResetForSolve(); --- dotnet/DDS_Core/DDS.cs | 4 +- dotnet/DDS_Core_Demo/Program.cs | 65 +++++++++------------------------ 2 files changed, 19 insertions(+), 50 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 6ebab880..e0e4d5e5 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -75,9 +75,7 @@ public int SetThreading(in int code) /// Maximum memory in megabytes. /// Maximum number of threads. [Obsolete("Use SolverContext instead.")] - public void SetResources(in int maxMemoryMB, in int maxThreads) - - => DdsNative.SetResources(maxMemoryMB, maxThreads); + public void SetResources(in int maxMemoryMB, in int maxThreads) => DdsNative.SetResources(maxMemoryMB, maxThreads); /// /// Frees memory used by the solver. diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 567526eb..4a2ceb49 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -90,10 +90,10 @@ static void Main(string[] args) doSolveBoardV3(dds, ctx, TestData.deals[0]); doCalcDdTableV3(dds, ctx, TestData.ddTableDeal); doCalcDdTableV3(dds, ctx, TestData.ddTableDealPBN); - doCalcParV3(dds, ctx, TestData.ddTableDeal, vulnability); - ctx.LogAppend("Completed V3.0.0 samples"); - //ctx.LogClear(); - } + doCalcParV3(dds, ctx, TestData.ddTableDeal, vulnability); + ctx.LogAppend("Completed V3.0.0 samples"); + //ctx.LogClear(); + } #endregion Console.WriteLine($"Press any key to continue..."); @@ -135,7 +135,7 @@ private static void doSolveBoardV3(DDS dds, SolverContext ctx, Deal deal) // record the number of tricks for declarer tricks[deal.Trump, decl] = 13 - fut.Score[0]; - dds.FreeMemory(); + ctx.ResetForSolve(); } DisplayTricks(); @@ -306,7 +306,6 @@ private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDeal ddTa } ctx.ResetForSolve(); - //dds.FreeMemory(); DisplayTricks(); } @@ -353,7 +352,6 @@ private static void doCalcDdTableV3(DDS dds, SolverContext ctx, DdTableDealPBN d } ctx.ResetForSolve(); - //dds.FreeMemory(); DisplayTricks(); TestData.ddTableResults = results; @@ -393,10 +391,10 @@ private static void doCalcAllTables(DDS dds, DdTableDealsPBN ddTableDeals) intArray5 trumpFilter = new(); var rc = dds.CalcAllTables( in ddTableDeals - , 0 - , trumpFilter - , out DdTablesResult results - , out AllParResults presp); + , 0 + , trumpFilter + , out DdTablesResult results + , out AllParResults presp); for (var trump = 0; trump < 5; trump++) @@ -451,11 +449,10 @@ private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableD tricks[trump, decl] = tResults.ResultsTable[trump, decl]; } - dds.FreeMemory(); - + ctx.ResetForSolve(); DisplayTricks(); } - + private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal, int vulnability) { Console.WriteLine($"CalcPar"); @@ -484,32 +481,6 @@ private static void doCalcPar(DDS dds, DdTableDeal ddTableDeal, int vulnability) //Console.WriteLine(); } - private static void doCalcParV3(DDS dds, SolverContext ctx, DdTableDeal ddTableDeal) - { - Console.WriteLine($"CalcParV3"); - tricks = new int[5, 4]; - - var rc = ctx.CalcPar( in ddTableDeal - , 0 - , out DdTableResults tResults - , out ParResults results ); - - Console.WriteLine(results.ParContractStrings); - Console.WriteLine(results.ParScores); - - //for (var trump = 0; trump < 5; trump++) - // for (var first = 0; first < 4; first++) - // { - // var decl =(first + 3) & 3; - - // // record the number of tricks for declarer - // tricks[trump, decl] = tResults.ResultsTable[trump, decl]; - // } - ctx.ResetForSolve(); - //dds.FreeMemory(); - //DisplayTricks(); - Console.WriteLine(); - } private static void doCalcPar(DDS dds, DdTableDealPBN ddTableDeal, int vulnability) { @@ -517,9 +488,9 @@ private static void doCalcPar(DDS dds, DdTableDealPBN ddTableDeal, int vulnabili tricks = new int[5, 4]; var rc = dds.CalcPar( in ddTableDeal - , vulnability - , out DdTableResults tResults - , out ParResults results ); + , vulnability + , out DdTableResults tResults + , out ParResults results ); Console.WriteLine(results.ParContractStrings); Console.WriteLine(results.ParScores); @@ -683,9 +654,9 @@ private static void doAnalysePlay(DDS dds, DealPBN deal, ParResultsMasters tResu Console.WriteLine($"AnalysePlay PlayTracePBN"); var rc = dds.AnalysePlay( in deal - , in ptrace - , out SolvedPlay solved - , 0); + , in ptrace + , out SolvedPlay solved + , 0); for (int i = 0; i <= ptrace.NumberOfPlayedCards; i++) Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); @@ -792,6 +763,7 @@ private static void BenchmarkAnalyseAllPlaysBin(DDS dds) Console.WriteLine("\n=== Benchmark Complete ==="); Console.Out.Flush(); } + catch (Exception ex) { Console.WriteLine($"\nERROR: {ex.GetType().Name}: {ex.Message}"); @@ -839,7 +811,6 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl return opsPerSecond; } - catch (Exception ex) { Console.WriteLine($"ERROR in Variant1: {ex.Message}"); From dad027dca65a966cbda30552d0169eb8ca11ba5a Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Fri, 12 Jun 2026 19:37:42 +0200 Subject: [PATCH 046/157] empty commit --- dotnet/DDS_Core_Demo/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index 4a2ceb49..f40750ef 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -138,6 +138,7 @@ private static void doSolveBoardV3(DDS dds, SolverContext ctx, Deal deal) ctx.ResetForSolve(); } + DisplayTricks(); } From 0107dbb8e345d40bfdc67386bd47154d6da4c24a Mon Sep 17 00:00:00 2001 From: mortensp <2102113+mortensp@users.noreply.github.com> Date: Sat, 13 Jun 2026 07:57:20 +0200 Subject: [PATCH 047/157] Out parameters is now last where posible. --- dotnet/DDS_Core/DDS.cs | 49 +++++++++++++++++++-------------- dotnet/DDS_Core_Demo/Program.cs | 38 ++++++++++++------------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index e0e4d5e5..adbaa594 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -288,12 +288,13 @@ public int CalcAllTables( in DdTableDealsPBN dealsp /// and contracts for both North-South and East-West, based on vulnerability. /// /// - /// /// + /// /// public int Par( in DdTableResults table + , in int vulnerable , out ParResults pres - , in int vulnerable) + ) { var rc = DdsNative.Par( table , out pres @@ -392,12 +393,13 @@ public int CalcPar( in DdTableDealPBN tableDealPBN /// Calculates par score and contracts for both sides based on the double dummy table results. /// /// Double dummy table results. - /// Output: par results for both sides. /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// Output: par results for both sides. /// Error code (RETURN_NO_FAULT on success). public int ParSide( in DdTableResults table + , in int vulnerable , out ParResultsDealers sidesRes - , in int vulnerable) + ) { var rc = DdsNative.SidesPar( table , out sidesRes @@ -411,14 +413,15 @@ public int ParSide( in DdTableResults table /// Calculates par score and contracts for a specific dealer and vulnerability. /// /// - /// /// /// + /// /// public int ParDealer( in DdTableResults table - , out ParResultsDealer pres , in int dealer - , in int vulnerable) + , in int vulnerable + , out ParResultsDealer pres + ) { var rc = DdsNative.DealerPar( table , out pres @@ -433,14 +436,15 @@ public int ParDealer( in DdTableResults table /// Calculates par score and contract types for both sides for a specific dealer and vulnerability. /// /// - /// /// /// + /// /// public int DealerParBothSides( in DdTableResults table - , out ParResultsMaster pres , in int dealer - , in int vulnerable) + , in int vulnerable + , out ParResultsMaster pres + ) { var rc = DdsNative.DealerParBin( table , out pres @@ -455,12 +459,13 @@ public int DealerParBothSides( in DdTableResults table /// calculates par score and contract types for both sides based on the double dummy table results. /// /// Double dummy table results. - /// Output: par results for both sides. /// Vulnerability (0=None, 1=Both, 2=NS, 3=EW). + /// Output: par results for both sides. /// Error code (RETURN_NO_FAULT on success). public int ParAll( in DdTableResults table + , in int vulnerable , out ParResultsMasters sidesRes - , in int vulnerable) + ) { var rc = DdsNative.SidesParBin( table , out sidesRes @@ -477,13 +482,14 @@ public int ParAll( in DdTableResults table /// /// The deal to analyze. /// The play trace to analyze. - /// The result of the analysis, including optimal line and tricks. /// The thread ID for parallel processing. + /// The result of the analysis, including optimal line and tricks. /// Error code (RETURN_NO_FAULT on success). public int AnalysePlay( in Deal dl , in PlayTraceBin play + , in int thrId , out SolvedPlay solved - , in int thrId) + ) { var rc = DdsNative.AnalysePlayBin( dl , in play @@ -498,13 +504,14 @@ public int AnalysePlay( in Deal dl /// /// The PBN deal to analyze. /// The play trace in PBN format to analyze. - /// The result of the analysis, including optimal line and tricks. /// The thread ID for parallel processing. + /// The result of the analysis, including optimal line and tricks. /// Error code (RETURN_NO_FAULT on success). public int AnalysePlay( in DealPBN dlPBN , in PlayTracePBN playPBN + , in int thrId , out SolvedPlay solved - , in int thrId) + ) { var rc = DdsNative.AnalysePlayPBN( dlPBN , in playPBN @@ -519,13 +526,14 @@ public int AnalysePlay( in DealPBN dlPBN /// /// The boards to analyze. /// The play traces to analyze. - /// The result of the analysis, including optimal lines and tricks. /// The chunk size for parallel processing. + /// The result of the analysis, including optimal lines and tricks. /// Error code (RETURN_NO_FAULT on success). public int AnalyseAllPlays( in Boards bop , in PlayTracesBin plp + , in int chunkSize , out SolvedPlays solved - , in int chunkSize) + ) { solved = new(); @@ -543,13 +551,14 @@ public int AnalyseAllPlays( in Boards bop /// /// The boards in PBN format to analyze. /// The play traces in PBN format to analyze. - /// The result of the analysis, including optimal lines and tricks. /// The chunk size for parallel processing. + /// The result of the analysis, including optimal lines and tricks. /// Error code (RETURN_NO_FAULT on success). public int AnalyseAllPlays( in BoardsPBN bopPBN , in PlayTracesPBN plpPBN + , in int chunkSize , out SolvedPlays solved - , in int chunkSize) + ) { solved = new(); var rc = DdsNative.AnalyseAllPlaysPBN( bopPBN diff --git a/dotnet/DDS_Core_Demo/Program.cs b/dotnet/DDS_Core_Demo/Program.cs index f40750ef..166091ce 100644 --- a/dotnet/DDS_Core_Demo/Program.cs +++ b/dotnet/DDS_Core_Demo/Program.cs @@ -418,8 +418,8 @@ private static void doPar(DDS dds, DdTableResults tableResults, int vulnability) tricks = new int[5, 4]; var rc = dds.Par( in tableResults - , out ParResults results - , vulnability); + , vulnability , out ParResults results +); Console.WriteLine(results.ParContractStrings); Console.WriteLine(results.ParScores); @@ -517,8 +517,8 @@ private static void doParSide(DDS dds, DdTableResults tResults, int vulnability) tricks = new int[5, 4]; var rc = dds.ParSide( in tResults - , out ParResultsDealers results - , vulnability); + , vulnability , out ParResultsDealers results +); Console.WriteLine(results[0].NumberOfContracts); Console.WriteLine(results[0].Score); @@ -539,8 +539,8 @@ private static void doParAll(DDS dds, DdTableResults tResults, int vulnability) tricks = new int[5, 4]; var rc = dds.ParAll( in tResults - , out ParResultsMasters results - , vulnability); + , vulnability , out ParResultsMasters results +); for (int s = 0; s < 2; s++) { @@ -575,9 +575,9 @@ private static void doParDealer(DDS dds, DdTableResults tResults, int dealer, in tricks = new int[5, 4]; var rc = dds.ParDealer( in tResults - , out ParResultsDealer results , dealer , vulnability + , out ParResultsDealer results ); Console.WriteLine(results.NumberOfContracts); @@ -595,9 +595,9 @@ private static void doParDealerBothSides( DDS dds, DdTableResults tResults, int tricks = new int[5, 4]; var rc = dds.DealerParBothSides( in tResults - , out ParResultsMaster results , dealer - , vulnability); + , vulnability , out ParResultsMaster results +); Console.WriteLine(results.Number); Console.WriteLine(results.Score); @@ -640,8 +640,8 @@ private static void doAnalysePlay(DDS dds, Deal deal, ParResultsMasters tResults var rc = dds.AnalysePlay( in deal , in ptrace - , out SolvedPlay solved - , 0); + , 0 , out SolvedPlay solved +); for (int i = 0; i <= ptrace.NumberOfCards; i++) Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); @@ -656,8 +656,8 @@ private static void doAnalysePlay(DDS dds, DealPBN deal, ParResultsMasters tResu var rc = dds.AnalysePlay( in deal , in ptrace - , out SolvedPlay solved - , 0); + , 0 , out SolvedPlay solved +); for (int i = 0; i <= ptrace.NumberOfPlayedCards; i++) Console.WriteLine($"{i,2}: {solved.Tricks[i]}"); @@ -673,8 +673,8 @@ private static void doAnalyseAllPlays(DDS dds, Boards boards, PlayTracesBin ptra var rc = dds.AnalyseAllPlays( in boards , in ptrace - , out SolvedPlays solved - , 0); + , 0 , out SolvedPlays solved +); if (isPerformanceTest) { @@ -693,8 +693,8 @@ private static void doAnalyseAllPlays(DDS dds, BoardsPBN boards, PlayTracesPBN p var rc = dds.AnalyseAllPlays( in boards , in ptrace - , out SolvedPlays solved - , 0); + , 0 , out SolvedPlays solved +); for (int i = 0; i <= ptrace.Plays[0].NumberOfPlayedCards; i++) Console.WriteLine($"{i,2}: {solved.Solved[0].Tricks[i]}"); @@ -786,7 +786,7 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl GC.Collect(); for (int i = 0; i < 5; i++) - dds.AnalyseAllPlays(in boards, in playTracesBin, out SolvedPlays solved, 0); + dds.AnalyseAllPlays(in boards, in playTracesBin, 0, out SolvedPlays solved); dds.FreeMemory(); @@ -797,7 +797,7 @@ private static double BenchmarkVariant1(DDS dds, Boards boards, PlayTracesBin pl for (int i = 0; i < iterations; i++) { - dds.AnalyseAllPlays(in boards, in playTracesBin, out SolvedPlays solved, 0); + dds.AnalyseAllPlays(in boards, in playTracesBin, 0, out SolvedPlays solved); dds.FreeMemory(); } From 90d296ca13a0b9f2099f2ffa01e03ac352341c92 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Fri, 5 Jun 2026 15:11:21 +0100 Subject: [PATCH 048/157] work in progress on advice. --- .github/instructions/git.instructions.md | 2 +- .github/instructions/github.instructions.md | 14 +------- docs/coding_standards_and_ai_advice.md | 36 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 docs/coding_standards_and_ai_advice.md diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index e12da055..264fdc00 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -21,6 +21,6 @@ alwaysApply: false 2. **Make changes** locally. 3. **Commit** with a clear message. 4. **Push** the branch. -5. **Open a PR** via the `github` MCP server (see `github.instructions.md`). +5. **Open a PR** via the `github` command line interface. --- \ No newline at end of file diff --git a/.github/instructions/github.instructions.md b/.github/instructions/github.instructions.md index 5bbff444..d372d0ea 100644 --- a/.github/instructions/github.instructions.md +++ b/.github/instructions/github.instructions.md @@ -7,7 +7,6 @@ alwaysApply: false ## Overview This project uses GitHub as its primary version control and collaboration platform. All code contributions, fixes, and features must go through a pull request (PR) workflow. -You have access to an MCP server named **`github`** (running `mcp-github`) which can create branches, push commits, and open PRs directly from the Continue.dev environment. See `.vscode/mcp.json` for configuration. ## Branching Strategy - **Default branch:** `main` @@ -32,25 +31,14 @@ You have access to an MCP server named **`github`** (running `mcp-github`) which 5. Small PRs are preferred — keep them focused on one logical change. ## Tooling -Use the **`github`** MCP server (configured in `.vscode/mcp.json`) to: +Use the github command line interface to: - Create branches - Commit and push changes - Open pull requests - Check PR status -## Example MCP Server Commands -You can instruct Continue.dev to: -- `Use the github MCP server to create a new branch called fix/memory-leak` -- `Push the current branch and open a pull request titled "Fix memory leak in cache manager"` -- `List open pull requests for this repo` -- `Merge PR #45 after approval` ## Automation - CI/CD runs automatically for all PRs. - Approved PRs can be merged by maintainers. -- Use **squash merging** to keep history clean. -## Notes for Continue.dev -- You are allowed to automate branch creation and PR submission using the `github` MCP server. -- If multiple PRs are required, ensure each is isolated to its own branch. -- You may request human review before merging. ``` diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md new file mode 100644 index 00000000..cc20471a --- /dev/null +++ b/docs/coding_standards_and_ai_advice.md @@ -0,0 +1,36 @@ +# Coding agents and coding standards + +Coding agents are becoming increasingly powerful and which tool and model that scores best in test changes if not by the week at least on a monthly basis. These recommendations and instructions for coding were collected in the first quarter of 2026 when the bulk of the work to modernise the codebase for release 3.0.0 was carried out. + +Configurations for MCP servers are not included. MCP servers add a lot of power, but can also expose severe vulnerabilities. How they should be deployed is definitely not one size fits all. + +## Recommended tools for coding agents + +### clangd +https://clangd.llvm.org + +Language servers are well known by most integrated development environments but coding agents are typically not able to interact with them directly. There are several MCP wrappers that can surface a language server to a coding agent. This is, however, surfacing raw JSON responses that the coding agent has to interpret and reason around. If - as is highly recommended - you run serena then prefer to let serena handle the integration with language servers. + +### Serena +https://github.com/oraios/serena + +Probably the most important tool at the time of writing. + +### Code Context Engine + +## Coding Standards + +Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory` + +1. [C++](../.github/instructions/cpp.instructions.md) +2. [Bazel](../.github/instructions/bazel.instructions.md) +3. [Git](../.github/instructions/git.instructions.md) +4. [GitHub](../.github/instructions/github.instructions.md) + +## Documentation and code completion + +### Extracting compile_commands.json + +https://github.com/kiron1/bazel-compile-commands + +https://github.com/hedronvision/bazel-compile-commands-extractor \ No newline at end of file From 797022070b57a337ff99a38e5ad4456acd8435ed Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Wed, 10 Jun 2026 11:25:10 +0100 Subject: [PATCH 049/157] work in progress on documents --- docs/coding_standards_and_ai_advice.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md index cc20471a..8010f7f8 100644 --- a/docs/coding_standards_and_ai_advice.md +++ b/docs/coding_standards_and_ai_advice.md @@ -14,13 +14,19 @@ Language servers are well known by most integrated development environments but ### Serena https://github.com/oraios/serena -Probably the most important tool at the time of writing. +Probably the most important tool at the time of writing. Serena provides semantic analysis and instructions to help coding agents stay on target. ### Code Context Engine +https://github.com/elara-labs/code-context-engine + +Creates and maintains an index of the codebase. This means that a coding agent often can read only the relevant part of a file instead of searching all the content of several files. + +As a side note, I find it interesting that vector embeddings were removed from Claude Code. My amatuer understanding is that Serena tells the agent what the code is doing and code context enginer where the interesting code. This differs from vector embeddnings which tells the coding agent which parts of the codebase looks similar. Knowing that calls to write to the database looks similar is not useful, but the information where they are and what they write is. + ## Coding Standards -Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory` +Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory`, which is where Github Copilot looks for its permanent instructions. 1. [C++](../.github/instructions/cpp.instructions.md) 2. [Bazel](../.github/instructions/bazel.instructions.md) @@ -29,6 +35,8 @@ Keeping a consistent coding style is more important than ever as it helps both h ## Documentation and code completion +DDS3 + ### Extracting compile_commands.json https://github.com/kiron1/bazel-compile-commands From 70e0ad64e8fc9af192a1fc9029b0f8ab11e07695 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Fri, 12 Jun 2026 15:05:08 +0100 Subject: [PATCH 050/157] Fix doxygen_docs genrule PATH to include platform-specific tool directories Bazel constructs a minimal PATH that excludes Homebrew on macOS, causing the doxygen availability check to fail even when doxygen is installed. Co-Authored-By: Claude Sonnet 4.6 --- BUILD.bazel | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index 50327287..d3263ea2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -93,6 +93,11 @@ genrule( outs = ["doxygen_docs.zip"], cmd = """ set -e + case "$$(uname -s)" in + Darwin) PATH="/opt/homebrew/bin:/usr/local/bin:$$PATH" ;; + Linux) PATH="/usr/local/bin:$$PATH" ;; + MINGW*|MSYS*|CYGWIN*) PATH="/usr/bin:/usr/local/bin:$$PATH" ;; + esac DOXYFILE_GEN="$(@D)/Doxyfile.generated" OUT_DIR="$(@D)/doxygen_output" OUT_ZIP="$$(pwd)/$@" From 9f2c490b888451652dcf2e9ca57459cb4f43cfe9 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Fri, 12 Jun 2026 18:13:48 +0100 Subject: [PATCH 051/157] first draft of ai and coding guidelines. --- MODULE.bazel.lock | 2 +- docs/coding_standards_and_ai_advice.md | 31 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 800cc995..c9c77ee2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -260,7 +260,7 @@ }, "@@emsdk+//:emscripten_deps.bzl%emscripten_deps": { "general": { - "bzlTransitiveDigest": "Dt5IF0PG0xjU5iMLeU6FQ1/xWvBqUgUognNk3r5pJXY=", + "bzlTransitiveDigest": "reX42Ca3PEP5mFXph0pqMqqqwHJbvykl5X4FHfXD6qg=", "usagesDigest": "lqS0hMGr6MqmX63BGZOskMFcqzqO53K0UlYFxuE3QSU=", "recordedInputs": [ "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md index 8010f7f8..8ede8f56 100644 --- a/docs/coding_standards_and_ai_advice.md +++ b/docs/coding_standards_and_ai_advice.md @@ -2,14 +2,23 @@ Coding agents are becoming increasingly powerful and which tool and model that scores best in test changes if not by the week at least on a monthly basis. These recommendations and instructions for coding were collected in the first quarter of 2026 when the bulk of the work to modernise the codebase for release 3.0.0 was carried out. -Configurations for MCP servers are not included. MCP servers add a lot of power, but can also expose severe vulnerabilities. How they should be deployed is definitely not one size fits all. +Configurations for MCP (Model Content Protocol) servers are not included. MCP servers add a lot of power, but can also expose severe vulnerabilities. How they should be deployed is definitely not one size fits all. + +## Coding Standards + +Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory`, which is where Github Copilot looks for its permanent instructions. + +1. [C++](../.github/instructions/cpp.instructions.md) +2. [Bazel](../.github/instructions/bazel.instructions.md) +3. [Git](../.github/instructions/git.instructions.md) +4. [GitHub](../.github/instructions/github.instructions.md) ## Recommended tools for coding agents ### clangd https://clangd.llvm.org -Language servers are well known by most integrated development environments but coding agents are typically not able to interact with them directly. There are several MCP wrappers that can surface a language server to a coding agent. This is, however, surfacing raw JSON responses that the coding agent has to interpret and reason around. If - as is highly recommended - you run serena then prefer to let serena handle the integration with language servers. +Language servers are well known to most integrated development environments but coding agents are typically not able to interact with them directly. There are several MCP wrappers that can surface a language server to a coding agent. This is, however, serving raw JSON responses that the coding agent has to interpret and reason around. If - as is highly recommended - you run serena then prefer to let serena handle the integration with language servers. ### Serena https://github.com/oraios/serena @@ -23,22 +32,20 @@ Creates and maintains an index of the codebase. This means that a coding agent o As a side note, I find it interesting that vector embeddings were removed from Claude Code. My amatuer understanding is that Serena tells the agent what the code is doing and code context enginer where the interesting code. This differs from vector embeddnings which tells the coding agent which parts of the codebase looks similar. Knowing that calls to write to the database looks similar is not useful, but the information where they are and what they write is. +## Documentation and code completion -## Coding Standards - -Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory`, which is where Github Copilot looks for its permanent instructions. +Code documentation for DDS3 is generated through doxygen, which extracts formatted comments from the source code. The +build command -1. [C++](../.github/instructions/cpp.instructions.md) -2. [Bazel](../.github/instructions/bazel.instructions.md) -3. [Git](../.github/instructions/git.instructions.md) -4. [GitHub](../.github/instructions/github.instructions.md) - -## Documentation and code completion +``` +bazelisk build //:doxygen_docs +``` -DDS3 +generates a stack of local html pages. Open `doxygen_output/html/pages.html` to read the documentation. ### Extracting compile_commands.json +Language servers, such as clangd, rely on a file called `compile_commands.json` which contains information about how project artefacts are build. https://github.com/kiron1/bazel-compile-commands https://github.com/hedronvision/bazel-compile-commands-extractor \ No newline at end of file From 7de76319df9a46f24ce9250c27198c7663beaf33 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Fri, 12 Jun 2026 18:56:59 +0100 Subject: [PATCH 052/157] updates the coding standards and ai advice. --- docs/coding_standards_and_ai_advice.md | 37 +++++++++++++++++--------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md index 8ede8f56..9c51378f 100644 --- a/docs/coding_standards_and_ai_advice.md +++ b/docs/coding_standards_and_ai_advice.md @@ -1,51 +1,62 @@ # Coding agents and coding standards -Coding agents are becoming increasingly powerful and which tool and model that scores best in test changes if not by the week at least on a monthly basis. These recommendations and instructions for coding were collected in the first quarter of 2026 when the bulk of the work to modernise the codebase for release 3.0.0 was carried out. +Coding agents are improving quickly, and the best tool or model for a task can change from one month to the next. This note collects the coding guidance and tooling recommendations that were assembled during the first quarter of 2026, when most of the modernisation work for release 3.0.0 was completed. -Configurations for MCP (Model Content Protocol) servers are not included. MCP servers add a lot of power, but can also expose severe vulnerabilities. How they should be deployed is definitely not one size fits all. +This document does not prescribe a specific MCP server setup. MCP servers can be powerful, but they also introduce security risks, so the right deployment strategy depends on the environment. ## Coding Standards -Keeping a consistent coding style is more important than ever as it helps both humans and coding agents. Preferred styles are document in the `.github/instructions directory`, which is where Github Copilot looks for its permanent instructions. +Consistent style matters even more when both humans and coding agents are reading and editing the same code. The preferred conventions are documented in the `.github/instructions` directory, which is where GitHub Copilot looks for its persistent instructions. 1. [C++](../.github/instructions/cpp.instructions.md) 2. [Bazel](../.github/instructions/bazel.instructions.md) 3. [Git](../.github/instructions/git.instructions.md) 4. [GitHub](../.github/instructions/github.instructions.md) -## Recommended tools for coding agents +## Recommended Tools for Coding Agents ### clangd + https://clangd.llvm.org -Language servers are well known to most integrated development environments but coding agents are typically not able to interact with them directly. There are several MCP wrappers that can surface a language server to a coding agent. This is, however, serving raw JSON responses that the coding agent has to interpret and reason around. If - as is highly recommended - you run serena then prefer to let serena handle the integration with language servers. +Language servers are familiar to most IDE users, but coding agents usually cannot interact with them directly. MCP wrappers can expose a language server to an agent, but they often do so by forwarding raw JSON responses that still need to be interpreted. If you also run Serena, prefer to let Serena handle the language-server integration. ### Serena + https://github.com/oraios/serena -Probably the most important tool at the time of writing. Serena provides semantic analysis and instructions to help coding agents stay on target. +Serena is the most useful tool in this workflow. It provides semantic analysis and retrieval features that help coding agents stay focused on the right parts of the codebase and understand the structure of the language they are working in. ### Code Context Engine + https://github.com/elara-labs/code-context-engine -Creates and maintains an index of the codebase. This means that a coding agent often can read only the relevant part of a file instead of searching all the content of several files. +Code Context Engine builds and maintains an index of the codebase, which lets a coding agent inspect the relevant parts of a file without scanning unrelated content across many files. -As a side note, I find it interesting that vector embeddings were removed from Claude Code. My amatuer understanding is that Serena tells the agent what the code is doing and code context enginer where the interesting code. This differs from vector embeddnings which tells the coding agent which parts of the codebase looks similar. Knowing that calls to write to the database looks similar is not useful, but the information where they are and what they write is. +One observation from this tooling landscape is that different systems solve different problems. Serena helps explain what the code is doing, while a code index helps locate where the interesting code lives. That is more useful than simply surfacing syntactically similar code, which is often not enough to guide a change. -## Documentation and code completion +## Documentation and Code Completion -Code documentation for DDS3 is generated through doxygen, which extracts formatted comments from the source code. The -build command +Code documentation for DDS3 is generated with Doxygen, which extracts formatted comments from the source code. Run the following command to generate the local documentation: ``` bazelisk build //:doxygen_docs ``` -generates a stack of local html pages. Open `doxygen_output/html/pages.html` to read the documentation. +The generated HTML pages are available under `doxygen_output/html/`. Open `doxygen_output/html/pages.html` to read the documentation. ### Extracting compile_commands.json -Language servers, such as clangd, rely on a file called `compile_commands.json` which contains information about how project artefacts are build. +Language servers such as clangd rely on a `compile_commands.json` file, which describes how the project is built. + +On macOS or Linux, the following command generates that file when the `bazel-compile-commands` utility is installed: + +``` +bazel-compile-commands //... +``` + https://github.com/kiron1/bazel-compile-commands +An alternative is Hedron Compile Commands, which can be integrated into the Bazel build. It appears to be less actively maintained, but it is still worth knowing about: + https://github.com/hedronvision/bazel-compile-commands-extractor \ No newline at end of file From 4e01e8d0e83e620eadc99b60a97c0af90725dc78 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 13 Jun 2026 12:24:48 +0100 Subject: [PATCH 053/157] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/instructions/github.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/instructions/github.instructions.md b/.github/instructions/github.instructions.md index d372d0ea..5a9bb712 100644 --- a/.github/instructions/github.instructions.md +++ b/.github/instructions/github.instructions.md @@ -31,7 +31,7 @@ This project uses GitHub as its primary version control and collaboration platfo 5. Small PRs are preferred — keep them focused on one logical change. ## Tooling -Use the github command line interface to: +Use `git` for local version control, and the GitHub CLI (`gh`) for GitHub operations: - Create branches - Commit and push changes - Open pull requests From 55b689380fd61c95760fbd81d8e5ab3e01554c5e Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 13 Jun 2026 12:25:19 +0100 Subject: [PATCH 054/157] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/instructions/git.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index 264fdc00..2eb66889 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -21,6 +21,6 @@ alwaysApply: false 2. **Make changes** locally. 3. **Commit** with a clear message. 4. **Push** the branch. -5. **Open a PR** via the `github` command line interface. +5. **Open a PR** via the GitHub CLI (`gh`). --- \ No newline at end of file From 0e88cd84ea97277641e7814dd3c37f8ba1145bdd Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 13 Jun 2026 12:34:42 +0100 Subject: [PATCH 055/157] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/coding_standards_and_ai_advice.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md index 9c51378f..86963f8c 100644 --- a/docs/coding_standards_and_ai_advice.md +++ b/docs/coding_standards_and_ai_advice.md @@ -39,11 +39,9 @@ One observation from this tooling landscape is that different systems solve diff Code documentation for DDS3 is generated with Doxygen, which extracts formatted comments from the source code. Run the following command to generate the local documentation: -``` -bazelisk build //:doxygen_docs -``` + bazelisk build //:doxygen_docs -The generated HTML pages are available under `doxygen_output/html/`. Open `doxygen_output/html/pages.html` to read the documentation. +The generated HTML pages are written under `bazel-bin/doxygen_output/html/` (and packaged as `bazel-bin/doxygen_docs.zip`). Open `bazel-bin/doxygen_output/html/index.html` to read the documentation. ### Extracting compile_commands.json From 9c45ddeaa66f256dd5241bde6e9ce1113f125208 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sun, 14 Jun 2026 16:43:37 +0100 Subject: [PATCH 056/157] Updates as per review feedback. --- .github/instructions/github.instructions.md | 4 ++-- docs/coding_standards_and_ai_advice.md | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/instructions/github.instructions.md b/.github/instructions/github.instructions.md index 5a9bb712..de59b6ce 100644 --- a/.github/instructions/github.instructions.md +++ b/.github/instructions/github.instructions.md @@ -28,7 +28,7 @@ This project uses GitHub as its primary version control and collaboration platfo - CI build - All unit and integration tests - Any lint/format checks -5. Small PRs are preferred — keep them focused on one logical change. +5. Small PRs are preferred — keep each focused on one logical change. ## Tooling Use `git` for local version control, and the GitHub CLI (`gh`) for GitHub operations: @@ -38,7 +38,7 @@ Use `git` for local version control, and the GitHub CLI (`gh`) for GitHub operat - Check PR status ## Automation -- CI/CD runs automatically for all PRs. +- CI runs automatically for all PRs. - Approved PRs can be merged by maintainers. ``` diff --git a/docs/coding_standards_and_ai_advice.md b/docs/coding_standards_and_ai_advice.md index 86963f8c..6fc3952a 100644 --- a/docs/coding_standards_and_ai_advice.md +++ b/docs/coding_standards_and_ai_advice.md @@ -1,6 +1,6 @@ -# Coding agents and coding standards +# Coding Agents and Coding Standards -Coding agents are improving quickly, and the best tool or model for a task can change from one month to the next. This note collects the coding guidance and tooling recommendations that were assembled during the first quarter of 2026, when most of the modernisation work for release 3.0.0 was completed. +Coding agents are improving quickly, and the best tool or model for a task can change from one month to the next. This note collects coding guidance and tooling recommendations assembled during the first quarter of 2026, when most of the modernisation work for release 3.0.0 was completed. This document does not prescribe a specific MCP server setup. MCP servers can be powerful, but they also introduce security risks, so the right deployment strategy depends on the environment. @@ -19,7 +19,7 @@ Consistent style matters even more when both humans and coding agents are readin https://clangd.llvm.org -Language servers are familiar to most IDE users, but coding agents usually cannot interact with them directly. MCP wrappers can expose a language server to an agent, but they often do so by forwarding raw JSON responses that still need to be interpreted. If you also run Serena, prefer to let Serena handle the language-server integration. +Language servers are familiar to most IDE users, but coding agents usually cannot interact with them directly. MCP wrappers can expose a language server to an agent, but they often do so by forwarding raw JSON responses that still need interpretation. If you also run Serena, prefer to let Serena handle the language-server integration. ### Serena @@ -31,7 +31,7 @@ Serena is the most useful tool in this workflow. It provides semantic analysis a https://github.com/elara-labs/code-context-engine -Code Context Engine builds and maintains an index of the codebase, which lets a coding agent inspect the relevant parts of a file without scanning unrelated content across many files. +Code Context Engine builds and maintains an index of the codebase, letting a coding agent inspect relevant parts of a file without scanning unrelated content across many files. One observation from this tooling landscape is that different systems solve different problems. Serena helps explain what the code is doing, while a code index helps locate where the interesting code lives. That is more useful than simply surfacing syntactically similar code, which is often not enough to guide a change. @@ -55,6 +55,10 @@ bazel-compile-commands //... https://github.com/kiron1/bazel-compile-commands -An alternative is Hedron Compile Commands, which can be integrated into the Bazel build. It appears to be less actively maintained, but it is still worth knowing about: +An alternative is Hedron Compile Commands, which can be integrated into the Bazel build. It appears to be less actively maintained, but is still worth knowing about: -https://github.com/hedronvision/bazel-compile-commands-extractor \ No newline at end of file +https://github.com/hedronvision/bazel-compile-commands-extractor + +## Other Tooling + +We are not maintaining an official list of recommended tools for working with the DDS codebase. The CI scripts use `homebrew` or `apt-get` to install `doxygen`, so you may have to update `PATH` variables in build scripts if you install it using another method. \ No newline at end of file From 01f7ad0e081e623a8c0bf5b315a38cab0264b677 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sun, 14 Jun 2026 16:50:50 +0100 Subject: [PATCH 057/157] mentions MODULE.bazel.lock is version managed as per best practises. --- docs/BUILD_SYSTEM.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/BUILD_SYSTEM.md b/docs/BUILD_SYSTEM.md index 15655020..cc61cfcb 100644 --- a/docs/BUILD_SYSTEM.md +++ b/docs/BUILD_SYSTEM.md @@ -16,6 +16,8 @@ toolchain selection lives in `MODULE.bazel` and standard-language settings are primarily configured in `.bazelrc` (with a fallback default in `CPPVARIABLES.bzl`). +MODULE.bazel.lock is checked in and version managed as per current bazel best practises. + ### macOS SDK and Runtime Compatibility On macOS, binaries built against a newer SDK/runtime than the currently running @@ -29,6 +31,7 @@ If you see runtime loader failures after a toolchain or OS change: ## Visual Studio and Rider Build + The top-level `solution` folder contains a Visual Studio solution file `solution.slnx` and project files for the dds and all the samples. It also contains a `Directory.Build.props` file which defines the common properties for all the projects. @@ -36,8 +39,6 @@ file which defines the common properties for all the projects. Note this line in the `Directory.Build.props` file: `$(MSBuildThisFileDirectory)\..\Build\` defining the output directory for all the projects. - - ## API Layers The library is structured into three API layers: From b01a30ea2f5c00698e49419b24aaeccdcb67febd Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 8 Jun 2026 22:08:37 -0400 Subject: [PATCH 058/157] Trigger CI workflow From 401a567207014ab100b37ee7c8fccceb2643d04c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 10 Jun 2026 19:15:57 +0100 Subject: [PATCH 059/157] Apply Copilot's suggestions Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- web/tests/test_dds_mvp_js.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py index f71e96cb..cfa2b5fb 100644 --- a/web/tests/test_dds_mvp_js.py +++ b/web/tests/test_dds_mvp_js.py @@ -54,13 +54,17 @@ def test_dds_mvp_js(self) -> None: dds_mvp_js = rlocation("web/dds_mvp.js") env = os.environ.copy() env["DDS_MVP_JS"] = str(dds_mvp_js) - proc = subprocess.run( - [node, "--test", str(test_script)], - capture_output=True, - text=True, - check=False, - env=env, - ) + try: + proc = subprocess.run( + [node, "--test", str(test_script)], + capture_output=True, + text=True, + check=False, + env=env, + timeout=60, + ) + except subprocess.TimeoutExpired as exc: + self.fail(f"node --test timed out: {exc}") self.assertEqual( proc.returncode, 0, From 7f2123289d00ca283d21c54adb882240ef8c90de Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 16:59:19 -0400 Subject: [PATCH 060/157] Restore Node version gate lost in develop merge. Re-add SkipTest when Node is missing, too old for `node --test` (<18), or when `node --version` hangs. Lower the subprocess timeout to 55s so Python fails cleanly before Bazel's 60s short timeout. --- web/tests/test_dds_mvp_js.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/tests/test_dds_mvp_js.py b/web/tests/test_dds_mvp_js.py index cfa2b5fb..e0d247b4 100644 --- a/web/tests/test_dds_mvp_js.py +++ b/web/tests/test_dds_mvp_js.py @@ -37,12 +37,16 @@ def test_dds_mvp_js(self) -> None: if not node: raise unittest.SkipTest("node not found") - version = subprocess.run( - [node, "--version"], - capture_output=True, - text=True, - check=False, - ).stdout.strip() + try: + version = subprocess.run( + [node, "--version"], + capture_output=True, + text=True, + check=False, + timeout=5, + ).stdout.strip() + except subprocess.TimeoutExpired: + raise unittest.SkipTest("node --version timed out") try: major = int(version.lstrip("v").split(".", 1)[0]) except ValueError: @@ -61,7 +65,7 @@ def test_dds_mvp_js(self) -> None: text=True, check=False, env=env, - timeout=60, + timeout=55, ) except subprocess.TimeoutExpired as exc: self.fail(f"node --test timed out: {exc}") From b7487a18ac1dd3678274f43e3330852d39d0a44b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 20:01:30 -0400 Subject: [PATCH 061/157] Add code from develop lost during merge: Resolve dds_mvp.js via rlocation and DDS_MVP_JS so the test works under Bazel on Linux. Skip when Node is missing, too old for `node --test` (<18), or when `node --version` hangs. Add a 55s subprocess timeout so Python fails cleanly before Bazel's 60s short timeout. --- dds.code-workspace | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 dds.code-workspace diff --git a/dds.code-workspace b/dds.code-workspace new file mode 100644 index 00000000..876a1499 --- /dev/null +++ b/dds.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file From 332719f958acc8e9645caa2ee046bc60437c70f4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 12 Jun 2026 01:14:47 +0100 Subject: [PATCH 062/157] Delete dds.code-workspace - should have been in .gitignore --- dds.code-workspace | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 dds.code-workspace diff --git a/dds.code-workspace b/dds.code-workspace deleted file mode 100644 index 876a1499..00000000 --- a/dds.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file From c75efddaafd97bbf17550a35ba345b3a497af65b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 23:41:10 -0400 Subject: [PATCH 063/157] Applied Cursor's suggestions to address straightforward TODOs. --- library/src/quick_tricks.cpp | 2 -- library/src/solver_context/solver_context.cpp | 5 +++-- library/src/system/system.cpp | 10 ++++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/library/src/quick_tricks.cpp b/library/src/quick_tricks.cpp index ac1417b1..d1fade94 100644 --- a/library/src/quick_tricks.cpp +++ b/library/src/quick_tricks.cpp @@ -1116,8 +1116,6 @@ int QuickTricksPartnerHandNT( return qt; if ((countOwn <= 2) && (countLho <= 2) && (countRho <= 2)) { - // TODO: Is the fix to qt correct? - // qtricks += countPart - 2; qt += countPart - 2; if (qt >= cutoff) return qt; diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index fd85df64..34bd6cc9 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -13,7 +13,7 @@ // 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) - : thr_(nullptr), cfg_(cfg) //TODO: remove thr_(nullptr) which is redundant + : cfg_(cfg) { // Create an owned ThreadData instance and keep it in thr_. thr_ = std::make_shared(); @@ -234,7 +234,8 @@ auto SolverContext::reset_best_moves_lite() const -> void auto ThreadMemoryUsed() -> double { - // TODO: Only needed because SolverIF wants to set it. Avoid? + // 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.); diff --git a/library/src/system/system.cpp b/library/src/system/system.cpp index b6965cf3..ac8710c5 100644 --- a/library/src/system/system.cpp +++ b/library/src/system/system.cpp @@ -8,6 +8,7 @@ */ #include +#include #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) #include @@ -388,18 +389,19 @@ string System::get_constructor(int& cons) const int System::get_cores() const { + const unsigned int hw = std::thread::hardware_concurrency(); + if (hw > 0) + return static_cast(hw); + int cores = 0; #if defined(_WIN32) || defined(__CYGWIN__) SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); cores = static_cast(sysinfo.dwNumberOfProcessors); #elif defined(__APPLE__) || defined(__linux__) - cores = sysconf(_SC_NPROCESSORS_ONLN); + cores = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); #endif - // TODO Think about thread::hardware_concurrency(). - // This should be standard in C++11. - return cores; } From de19746b081cbf7666abd214573b64efcafdd1b2 Mon Sep 17 00:00:00 2001 From: Todd Zimnoch Date: Sat, 6 Jun 2026 23:26:42 -0400 Subject: [PATCH 064/157] Remove unnecessary, WASM-specific code and configurations --- CPPVARIABLES.bzl | 2 +- docs/wasm_build.md | 2 -- examples/analyse_play_bin.cpp | 7 +------ examples/calc_dd_table_pbn.cpp | 2 +- examples/solve_board.cpp | 2 +- examples/solve_board_pbn.cpp | 2 +- examples/wasm/calc_dd_table_pbn_test.cpp | 2 +- 7 files changed, 6 insertions(+), 13 deletions(-) diff --git a/CPPVARIABLES.bzl b/CPPVARIABLES.bzl index 68e26416..0f5366cd 100644 --- a/CPPVARIABLES.bzl +++ b/CPPVARIABLES.bzl @@ -72,7 +72,7 @@ DDS_LOCAL_DEFINES = select({ "//:debug_build_macos": [], "//:build_linux": [], "//:debug_build_linux": [], - "//:build_wasm": ["__WASM__"], + "//:build_wasm": [], "//conditions:default": [], }) + select({ "//:debug_all": ["DDS_DEBUG_ALL"], diff --git a/docs/wasm_build.md b/docs/wasm_build.md index f071beea..7a5739f9 100644 --- a/docs/wasm_build.md +++ b/docs/wasm_build.md @@ -136,8 +136,6 @@ The MVP link flags include `-sENVIRONMENT=web,node` so the same `.js` / `.wasm` ## Development notes -- The `__WASM__` preprocessor constant is defined for WASM builds (`CPPVARIABLES.bzl`). It was added to work around platform-specific code paths; revisit whether it can be narrowed or removed as WASM support matures. -- Some threading and platform-specific features are disabled or stubbed when `__WASM__` is set. - A reusable `cc_library` WASM artifact (not only example binaries) is not yet provided; today only `wasm_cc_binary` example targets are wired up. - The browser MVP lives under `web/`; see **Web browser (DDS MVP)** above and `//web:web_system_tests`. diff --git a/examples/analyse_play_bin.cpp b/examples/analyse_play_bin.cpp index 5b97cd68..82437304 100644 --- a/examples/analyse_play_bin.cpp +++ b/examples/analyse_play_bin.cpp @@ -30,7 +30,7 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) || defined(__WASM__) +#if defined(__APPLE__) SetMaxThreads(0); #endif @@ -62,12 +62,7 @@ auto main() -> int if (res != RETURN_NO_FAULT) { - #ifdef __WASM__ - // For WASM, we can't use ErrorMessage, so we'll just print the error code - snprintf(line, sizeof(line), "error code %d", res); - #else ErrorMessage(res, line); - #endif printf("DDS error: %s\n", line); } diff --git a/examples/calc_dd_table_pbn.cpp b/examples/calc_dd_table_pbn.cpp index 7ca7fba7..ce89fda9 100644 --- a/examples/calc_dd_table_pbn.cpp +++ b/examples/calc_dd_table_pbn.cpp @@ -27,7 +27,7 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) || defined(__WASM__) +#if defined(__APPLE__) SetMaxThreads(0); #endif diff --git a/examples/solve_board.cpp b/examples/solve_board.cpp index b42bffb2..862d575b 100644 --- a/examples/solve_board.cpp +++ b/examples/solve_board.cpp @@ -33,7 +33,7 @@ auto main() -> int bool match2; bool match3; -#if defined(__linux) || defined(__APPLE__) || defined(__WASM__) +#if defined(__APPLE__) SetMaxThreads(0); #endif diff --git a/examples/solve_board_pbn.cpp b/examples/solve_board_pbn.cpp index a24f5c7e..5496e5ad 100644 --- a/examples/solve_board_pbn.cpp +++ b/examples/solve_board_pbn.cpp @@ -32,7 +32,7 @@ auto main() -> int bool match2, match3; -#if defined(__linux) || defined(__APPLE__) || defined(__WASM__) +#if defined(__APPLE__) SetMaxThreads(0); #endif diff --git a/examples/wasm/calc_dd_table_pbn_test.cpp b/examples/wasm/calc_dd_table_pbn_test.cpp index 01a900c9..cc9fbc0d 100644 --- a/examples/wasm/calc_dd_table_pbn_test.cpp +++ b/examples/wasm/calc_dd_table_pbn_test.cpp @@ -13,7 +13,7 @@ #include "hands.hpp" TEST(CalcDdTablePbnWasmTest, MatchesReferenceTables) { -#if defined(__linux) || defined(__APPLE__) || defined(__WASM__) +#if defined(__APPLE__) SetMaxThreads(0); #endif From 48c091bc3f26e4f30967cca0dfc694c2c17a9010 Mon Sep 17 00:00:00 2001 From: Todd Zimnoch Date: Mon, 15 Jun 2026 00:55:18 -0400 Subject: [PATCH 065/157] Remove remaining SetMaxThreads(0) calls. Remove documentation about __WASM__ flag that is currently not necessary or used. --- docs/wasm_build.md | 1 - examples/analyse_all_plays_bin.cpp | 4 ---- examples/analyse_all_plays_pbn.cpp | 4 ---- examples/analyse_play_bin.cpp | 4 ---- examples/analyse_play_pbn.cpp | 4 ---- examples/calc_all_tables.cpp | 4 ---- examples/calc_all_tables_pbn.cpp | 4 ---- examples/calc_dd_table.cpp | 4 ---- examples/calc_dd_table_pbn.cpp | 4 ---- examples/calc_par_context_example.cpp | 4 ---- examples/dealer_par.cpp | 4 ---- examples/par.cpp | 4 ---- examples/solve_all_boards.cpp | 4 ---- examples/solve_board.cpp | 4 ---- examples/solve_board_pbn.cpp | 4 ---- examples/wasm/calc_dd_table_pbn_test.cpp | 3 --- library/tests/heuristic_sorting/minimal_weight_test.cpp | 6 ------ library/tests/solve_board/analyse_play_consistency.cpp | 2 -- library/tests/solve_board/trick_three_bug.cpp | 2 -- 19 files changed, 70 deletions(-) diff --git a/docs/wasm_build.md b/docs/wasm_build.md index 7a5739f9..2e973f47 100644 --- a/docs/wasm_build.md +++ b/docs/wasm_build.md @@ -98,7 +98,6 @@ For other experiments, copy built `.js` / `.wasm` files from `bazel-bin/examples | `-O3` | Aggressive optimization | | `-flto` | Link-time optimization | | `-fexceptions` | Enable C++ exceptions | -| `-D__WASM__` | Preprocessor constant for WASM builds | | `-sWASM=1` | Emscripten WASM output (link flag) | | `-sALLOW_MEMORY_GROWTH=1` | Allow heap growth at runtime | | `-sINITIAL_MEMORY=268435456` | 256MB initial memory | diff --git a/examples/analyse_all_plays_bin.cpp b/examples/analyse_all_plays_bin.cpp index f3013714..4593ee24 100644 --- a/examples/analyse_all_plays_bin.cpp +++ b/examples/analyse_all_plays_bin.cpp @@ -30,10 +30,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - bo.no_of_boards = 3; DDplays.no_of_boards = 3; diff --git a/examples/analyse_all_plays_pbn.cpp b/examples/analyse_all_plays_pbn.cpp index b293387b..d13884a4 100644 --- a/examples/analyse_all_plays_pbn.cpp +++ b/examples/analyse_all_plays_pbn.cpp @@ -30,10 +30,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - bo.no_of_boards = 3; DDplays.no_of_boards = 3; diff --git a/examples/analyse_play_bin.cpp b/examples/analyse_play_bin.cpp index 82437304..985b4438 100644 --- a/examples/analyse_play_bin.cpp +++ b/examples/analyse_play_bin.cpp @@ -30,10 +30,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { dl.trump = trump_suit_[handno]; diff --git a/examples/analyse_play_pbn.cpp b/examples/analyse_play_pbn.cpp index 5a94ae92..35197edd 100644 --- a/examples/analyse_play_pbn.cpp +++ b/examples/analyse_play_pbn.cpp @@ -28,10 +28,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { dlPBN.trump = trump_suit_[handno]; diff --git a/examples/calc_all_tables.cpp b/examples/calc_all_tables.cpp index 260ff452..b7b7f7c5 100644 --- a/examples/calc_all_tables.cpp +++ b/examples/calc_all_tables.cpp @@ -30,10 +30,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - DDdeals.no_of_tables = 3; for (int handno = 0; handno < 3; handno++) diff --git a/examples/calc_all_tables_pbn.cpp b/examples/calc_all_tables_pbn.cpp index 408a52ff..c304d976 100644 --- a/examples/calc_all_tables_pbn.cpp +++ b/examples/calc_all_tables_pbn.cpp @@ -30,10 +30,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - DDdealsPBN.no_of_tables = 3; for (int handno = 0; handno < 3; handno++) diff --git a/examples/calc_dd_table.cpp b/examples/calc_dd_table.cpp index 93825c29..0c1a53c8 100644 --- a/examples/calc_dd_table.cpp +++ b/examples/calc_dd_table.cpp @@ -27,10 +27,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { diff --git a/examples/calc_dd_table_pbn.cpp b/examples/calc_dd_table_pbn.cpp index ce89fda9..a3b4ceab 100644 --- a/examples/calc_dd_table_pbn.cpp +++ b/examples/calc_dd_table_pbn.cpp @@ -27,10 +27,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { strcpy(tableDealPBN.cards, pbn_hands_[handno]); diff --git a/examples/calc_par_context_example.cpp b/examples/calc_par_context_example.cpp index 3605d93d..ffd0d22a 100644 --- a/examples/calc_par_context_example.cpp +++ b/examples/calc_par_context_example.cpp @@ -170,10 +170,6 @@ auto main() -> int printf("DDS Examples: Par Calculation with SolverContext\n"); printf("================================================\n"); -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - // Run examples example_without_context(); example_with_context(); diff --git a/examples/dealer_par.cpp b/examples/dealer_par.cpp index 789cf87e..72a408a7 100644 --- a/examples/dealer_par.cpp +++ b/examples/dealer_par.cpp @@ -27,10 +27,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { set_table(&DDtable, handno); diff --git a/examples/par.cpp b/examples/par.cpp index cff51da8..eaf65f32 100644 --- a/examples/par.cpp +++ b/examples/par.cpp @@ -27,10 +27,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { set_table(&DDtable, handno); diff --git a/examples/solve_all_boards.cpp b/examples/solve_all_boards.cpp index 6c987ac3..5bd0b7fb 100644 --- a/examples/solve_all_boards.cpp +++ b/examples/solve_all_boards.cpp @@ -27,10 +27,6 @@ auto main() -> int char line[80]; bool match; -#if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); -#endif - bo.no_of_boards = 3; for (int handno = 0; handno < 3; handno++) { diff --git a/examples/solve_board.cpp b/examples/solve_board.cpp index 862d575b..196cb3c3 100644 --- a/examples/solve_board.cpp +++ b/examples/solve_board.cpp @@ -33,10 +33,6 @@ auto main() -> int bool match2; bool match3; -#if defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { dl.trump = trump_suit_[handno]; diff --git a/examples/solve_board_pbn.cpp b/examples/solve_board_pbn.cpp index 5496e5ad..b42b432c 100644 --- a/examples/solve_board_pbn.cpp +++ b/examples/solve_board_pbn.cpp @@ -32,10 +32,6 @@ auto main() -> int bool match2, match3; -#if defined(__APPLE__) - SetMaxThreads(0); -#endif - for (int handno = 0; handno < 3; handno++) { dlPBN.trump = trump_suit_[handno]; diff --git a/examples/wasm/calc_dd_table_pbn_test.cpp b/examples/wasm/calc_dd_table_pbn_test.cpp index cc9fbc0d..0dfdc6e9 100644 --- a/examples/wasm/calc_dd_table_pbn_test.cpp +++ b/examples/wasm/calc_dd_table_pbn_test.cpp @@ -13,9 +13,6 @@ #include "hands.hpp" TEST(CalcDdTablePbnWasmTest, MatchesReferenceTables) { -#if defined(__APPLE__) - SetMaxThreads(0); -#endif for (int handno = 0; handno < 3; ++handno) { DdTableDealPBN deal{}; diff --git a/library/tests/heuristic_sorting/minimal_weight_test.cpp b/library/tests/heuristic_sorting/minimal_weight_test.cpp index 465d0297..76ecb6bc 100644 --- a/library/tests/heuristic_sorting/minimal_weight_test.cpp +++ b/library/tests/heuristic_sorting/minimal_weight_test.cpp @@ -17,12 +17,6 @@ */ class MinimalWeightTest : public ::testing::Test { -protected: - void SetUp() override - { - // Initialize the DDS system - SetMaxThreads(0); - } }; TEST_F(MinimalWeightTest, BasicWeightAllocCall) { diff --git a/library/tests/solve_board/analyse_play_consistency.cpp b/library/tests/solve_board/analyse_play_consistency.cpp index 20f73303..abb5c4aa 100644 --- a/library/tests/solve_board/analyse_play_consistency.cpp +++ b/library/tests/solve_board/analyse_play_consistency.cpp @@ -251,8 +251,6 @@ auto hand_from_holdings(const std::array& suits) -> std::vector< class AnalysePlayConsistency : public ::testing::Test { -protected: - void SetUp() override { SetMaxThreads(0); } }; // The exact deal from dds-bridge/dds issue #156. diff --git a/library/tests/solve_board/trick_three_bug.cpp b/library/tests/solve_board/trick_three_bug.cpp index c96c746b..2e565237 100644 --- a/library/tests/solve_board/trick_three_bug.cpp +++ b/library/tests/solve_board/trick_three_bug.cpp @@ -35,8 +35,6 @@ inline auto dds_max(FutureTricks const & fut) -> size_t /// @details Reproduces the original bug scenario and validates the fix. TEST_F(TrickThreeBugTests, test_declarer_makes_nine_tricks) { - SetMaxThreads(0); - const int target = 0; const int solutions = 3; const int mode = 0; From a50133140576bc59fa1f7b6e71d3a53ec6cf9844 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 15 Jun 2026 15:27:47 -0400 Subject: [PATCH 066/157] Suppress gtest character-conversion warning for build, not only test The -Wno-character-conversion / -Wno-conversion suppressions were scoped to the `test` command, so they only took effect under `bazel test`. Plain `bazel build` of test targets (and tooling that compiles GoogleTest) still emitted the gtest-printers.h warning. Move the flags to the `build` config, which is inherited by `test`, `run`, etc., so the warning is suppressed in both cases. Co-authored-by: Cursor --- .bazelrc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.bazelrc b/.bazelrc index 8d1bd6a8..d653bec4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -29,10 +29,13 @@ build:asan --features=dbg build:asan --compilation_mode=dbg test:asan --test_timeout=120,300,900,3600 -# GoogleTest headers can trigger conversion warnings on Clang; suppress for tests. +# GoogleTest headers can trigger conversion warnings on Clang; suppress them. +# Scoped to `build` (not `test`) so the suppression also applies to plain +# `bazel build` of test targets and tooling that compiles GoogleTest, not just +# `bazel test`. The `build` config is inherited by `test`, `run`, etc. # On Linux, use -Wno-conversion for portability when the system compiler is GCC. -test:macos --cxxopt=-Wno-character-conversion -test:linux --cxxopt=-Wno-conversion +build:macos --cxxopt=-Wno-character-conversion +build:linux --cxxopt=-Wno-conversion # macOS-specific AddressSanitizer runtime lookup configuration. # The LLVM toolchain bundles the ASAN runtime. On macOS tests run from the # runfiles tree, so use @loader_path-based rpaths that walk back to execroot From 0db10adfff9224346ee0d4a0c904502b20310110 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Mon, 15 Jun 2026 10:01:29 -0400 Subject: [PATCH 067/157] Cap rows at ROWS, not SIZE Check src.Length before accessing src[0] --- dotnet/DDS_Core/Helpers/intArray5x4.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/DDS_Core/Helpers/intArray5x4.cs b/dotnet/DDS_Core/Helpers/intArray5x4.cs index 2a0948e1..edbefdff 100644 --- a/dotnet/DDS_Core/Helpers/intArray5x4.cs +++ b/dotnet/DDS_Core/Helpers/intArray5x4.cs @@ -70,10 +70,10 @@ public static implicit operator intArray5x4(int[][] src) { var buf = new intArray5x4(); - int k = 0; - int rows = Math.Min(src.Length, SIZE); - int cols = Math.Min(src[0].Length, COLS); + int rows = Math.Min(src.Length, ROWS); + int cols = src.Length > 0 ? Math.Min(src[0].Length, COLS) : 0; + int k = 0; for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) buf.data[k++] = src[r][c]; From ff8e36fe2339aff8e5f120fd4b97385fcd1cad1f Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 12 Jun 2026 21:08:32 -0400 Subject: [PATCH 068/157] Initial parallelization by Cursor --- library/tests/args.cpp | 7 +-- library/tests/dtest.cpp | 4 ++ library/tests/dtest_parallel.cpp | 95 ++++++++++++++++++++++++++++++++ library/tests/dtest_parallel.hpp | 28 ++++++++++ library/tests/loop.cpp | 67 ++++++++++++++++++++-- 5 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 library/tests/dtest_parallel.cpp create mode 100644 library/tests/dtest_parallel.hpp diff --git a/library/tests/args.cpp b/library/tests/args.cpp index 8c4c6c44..e83e4635 100644 --- a/library/tests/args.cpp +++ b/library/tests/args.cpp @@ -89,10 +89,9 @@ void usage( "-s, --solver One of: solve, calc, play, par, dealerpar.\n" << " (Default: solve)\n" << "\n" << - "-n, --numthr n Maximum number of threads (legacy option).\n" << - " (Default: 0 uses DDS/library defaults; when using\n" << - " the modern SolverContext API, prefer configuring\n" << - " threads via SolverConfig instead of this option.)\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" << diff --git a/library/tests/dtest.cpp b/library/tests/dtest.cpp index d4640cdd..1116fb1f 100644 --- a/library/tests/dtest.cpp +++ b/library/tests/dtest.cpp @@ -33,6 +33,10 @@ int main(int argc, char * argv[]) 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"; real_main(argc, argv); diff --git a/library/tests/dtest_parallel.cpp b/library/tests/dtest_parallel.cpp new file mode 100644 index 00000000..778bf26f --- /dev/null +++ b/library/tests/dtest_parallel.cpp @@ -0,0 +1,95 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#include "dtest_parallel.hpp" + +#include +#include +#include +#include + +#include + + +int dtest_effective_threads(const int requested, const int workload) +{ + if (workload <= 1) + return 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 dtest_run_parallel( + const int count, + const int requested_threads, + const std::function & body) +{ + 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 rc = body(i); + if (rc != RETURN_NO_FAULT) + return rc; + } + return RETURN_NO_FAULT; + } + + 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 (...) + { + 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; +} diff --git a/library/tests/dtest_parallel.hpp b/library/tests/dtest_parallel.hpp new file mode 100644 index 00000000..0e88414a --- /dev/null +++ b/library/tests/dtest_parallel.hpp @@ -0,0 +1,28 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#pragma once + +#include + +/// Resolve the worker thread count for a dtest batch. +/// +/// @param requested Thread count from -n (0 = auto from hardware). +/// @param workload Number of independent items in the batch. +/// @return Thread count in [1, workload]. +int dtest_effective_threads(int requested, int workload); + +/// Run @p body for each index in [0, count) using up to @p requested_threads workers. +/// +/// @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); diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 10a75fcc..11a22c9e 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -16,6 +16,8 @@ #include "TestTimer.hpp" #include "compare.hpp" #include "print.hpp" +#include "cst.hpp" +#include "dtest_parallel.hpp" using std::cout; using std::endl; @@ -26,6 +28,7 @@ using std::right; #define BATCHTIMES extern TestTimer timer; +extern OptionsType options; void loop_solve( @@ -57,7 +60,28 @@ void loop_solve( timer.start(count); int ret; - if ((ret = SolveAllChunks(bop, solvedbdp, 1)) != RETURN_NO_FAULT) + if (dtest_effective_threads(options.num_threads_, count) <= 1) + { + ret = SolveAllBoardsSeq(bop, solvedbdp); + } + else + { + solvedbdp->no_of_boards = count; + ret = dtest_run_parallel(count, options.num_threads_, + [&](const int j) -> int { + FutureTricks fut; + const int res = SolveBoardPBN( + bop->deals[j], bop->target[j], bop->solutions[j], bop->mode[j], + &fut, 0); + if (res == RETURN_NO_FAULT) + { + solvedbdp->solved_board[j] = fut; + return RETURN_NO_FAULT; + } + return res; + }); + } + if (ret != RETURN_NO_FAULT) { cout << "loop_solve: i " << i << ", return " << ret << "\n"; exit(0); @@ -114,8 +138,29 @@ bool loop_calc( timer.start(count); int ret; - if ((ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp)) - != RETURN_NO_FAULT) + if (dtest_effective_threads(options.num_threads_, count) <= 1) + { + ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp); + } + else + { + ret = dtest_run_parallel(count, options.num_threads_, + [&](const int j) -> int { + return CalcDDtablePBN(dealsp->deals[j], &resp->results[j]); + }); + if (ret == RETURN_NO_FAULT) + { + int strains = 0; + for (int k = 0; k < DDS_STRAINS; k++) + { + if (!filter[k]) + strains++; + } + // Match CalcAllTablesPBN accounting: 4 declarers per strain-board. + resp->no_of_boards = 4 * count * strains; + } + } + if (ret != RETURN_NO_FAULT) { cout << "loop_calc: i " << i << ", return " << ret << "\n"; exit(0); @@ -270,8 +315,20 @@ bool loop_play( timer.start(count); int ret; - if ((ret = AnalyseAllPlaysPBN(bop, playsp, solvedplp, 1)) - != RETURN_NO_FAULT) + 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) { printf("loop_play i %i: Return %d\n", i, ret); cout << "loop_play: i " << i << ": " << "return " << ret << "\n"; From 4eec636435a94d56863ebbf4979079c21789834c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 12 Jun 2026 21:43:14 -0400 Subject: [PATCH 069/157] =?UTF-8?q?Parallelize=20inside=20the=20library,?= =?UTF-8?q?=20mirroring=20solve=5Fall=5Fboards=5Fn=20=E2=80=94=20each=20wo?= =?UTF-8?q?rker=20gets=20a=20persistent=20SolverContext=20and=20pulls=20bo?= =?UTF-8?q?ards=20from=20a=20shared=20queue.=20While=20still=2050%=20slowe?= =?UTF-8?q?r=20than=20v=202.9,=20this=20produces=20a=20roughly=2030%=20wal?= =?UTF-8?q?l-clock=20improvement=20when=20solving=20100=20deals,=20e.g.,?= =?UTF-8?q?=20./bazel-bin/library/tests/dtest=20-f=20hands/list100.txt=20-?= =?UTF-8?q?s=20cal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/src/calc_tables.cpp | 78 +++++++++++++++++++++++++++++++++++-- library/tests/loop.cpp | 26 ++----------- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 96555a82..190156e7 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -8,6 +8,10 @@ */ #include "calc_tables.hpp" +#include +#include +#include + #include #include #include @@ -102,13 +106,81 @@ auto calc_all_boards_n( return RETURN_NO_FAULT; } -// Legacy overload: creates temporary context +// Legacy overload: parallel across boards, one SolverContext per worker. auto calc_all_boards_n( Boards * bop, SolvedBoards * solvedp) -> int { - SolverContext ctx; - return calc_all_boards_n(ctx, bop, solvedp); + 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; + + const int nthreads = std::max(1, + std::min(static_cast(std::thread::hardware_concurrency()), n)); + + if (nthreads <= 1) + { + SolverContext ctx; + return calc_all_boards_n(ctx, bop, solvedp); + } + + std::vector contexts(static_cast(nthreads)); + std::atomic next_board{0}; + std::atomic first_error{0}; + + auto worker = [&](const int worker_id) { + for (;;) + { + const int bno = next_board.fetch_add(1, std::memory_order_relaxed); + if (bno >= n || first_error.load(std::memory_order_relaxed) != 0) + break; + + const int err = calc_single_common_internal( + contexts[static_cast(worker_id)], *bop, *solvedp, bno); + if (err != 1) + { + int expected = 0; + first_error.compare_exchange_strong( + expected, err, std::memory_order_relaxed); + break; + } + } + }; + + START_BLOCK_TIMER; + { + std::vector threads; + threads.reserve(static_cast(nthreads)); + try + { + for (int i = 0; i < nthreads; ++i) + threads.emplace_back(worker, i); + } + catch (...) + { + for (auto & t : threads) + if (t.joinable()) + t.join(); + throw; + } + for (auto & t : threads) + t.join(); + } + END_BLOCK_TIMER; + + if (const int err = first_error.load(); err != 0) + return err; + + solvedp->no_of_boards = n; + +#ifdef DDS_SCHEDULER + scheduler.PrintTiming(); +#endif + + return RETURN_NO_FAULT; } diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 11a22c9e..ecc6b4eb 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -16,6 +16,8 @@ #include "TestTimer.hpp" #include "compare.hpp" #include "print.hpp" +#include + #include "cst.hpp" #include "dtest_parallel.hpp" @@ -137,29 +139,7 @@ bool loop_calc( strcpy(dealsp->deals[j].cards, deal_list[i+j].remainCards); timer.start(count); - int ret; - if (dtest_effective_threads(options.num_threads_, count) <= 1) - { - ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp); - } - else - { - ret = dtest_run_parallel(count, options.num_threads_, - [&](const int j) -> int { - return CalcDDtablePBN(dealsp->deals[j], &resp->results[j]); - }); - if (ret == RETURN_NO_FAULT) - { - int strains = 0; - for (int k = 0; k < DDS_STRAINS; k++) - { - if (!filter[k]) - strains++; - } - // Match CalcAllTablesPBN accounting: 4 declarers per strain-board. - resp->no_of_boards = 4 * count * strains; - } - } + const int ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp); if (ret != RETURN_NO_FAULT) { cout << "loop_calc: i " << i << ", return " << ret << "\n"; From ffbb296da85ba9996cd17003f5bf55ca2b0b2a9a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 14 Jun 2026 17:44:58 -0400 Subject: [PATCH 070/157] Extract shared batch parallelism into parallel_all_boards_n. Use one work-stealing helper for solve_all_boards_n and calc_all_boards_n; each path supplies only its per-board worker lambda. --- library/src/calc_tables.cpp | 59 +++++--------- library/src/solve_board.cpp | 49 +++--------- library/src/system/parallel_boards.cpp | 102 +++++++++++++++++++++++++ library/src/system/parallel_boards.hpp | 27 +++++++ 4 files changed, 155 insertions(+), 82 deletions(-) create mode 100644 library/src/system/parallel_boards.cpp create mode 100644 library/src/system/parallel_boards.hpp diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 190156e7..8003944d 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -8,7 +8,6 @@ */ #include "calc_tables.hpp" -#include #include #include @@ -17,6 +16,7 @@ #include #include #include +#include #include #include @@ -118,60 +118,35 @@ auto calc_all_boards_n( for (int k = 0; k < MAXNOOFBOARDS; k++) solvedp->solved_board[k].cards = 0; + START_BLOCK_TIMER; + const int nthreads = std::max(1, std::min(static_cast(std::thread::hardware_concurrency()), n)); + int err = RETURN_NO_FAULT; if (nthreads <= 1) { SolverContext ctx; - return calc_all_boards_n(ctx, bop, solvedp); - } - - std::vector contexts(static_cast(nthreads)); - std::atomic next_board{0}; - std::atomic first_error{0}; - - auto worker = [&](const int worker_id) { - for (;;) + for (int bno = 0; bno < n; ++bno) { - const int bno = next_board.fetch_add(1, std::memory_order_relaxed); - if (bno >= n || first_error.load(std::memory_order_relaxed) != 0) + err = calc_single_common_internal(ctx, *bop, *solvedp, bno); + if (err != RETURN_NO_FAULT) break; - - const int err = calc_single_common_internal( - contexts[static_cast(worker_id)], *bop, *solvedp, bno); - if (err != 1) - { - int expected = 0; - first_error.compare_exchange_strong( - expected, err, std::memory_order_relaxed); - break; - } } - }; - - START_BLOCK_TIMER; + } + else { - std::vector threads; - threads.reserve(static_cast(nthreads)); - try - { - for (int i = 0; i < nthreads; ++i) - threads.emplace_back(worker, i); - } - catch (...) - { - for (auto & t : threads) - if (t.joinable()) - t.join(); - throw; - } - for (auto & t : threads) - t.join(); + std::vector contexts(static_cast(nthreads)); + err = parallel_all_boards_n(n, nthreads, + [&](const int worker_id, const int bno) -> int { + return calc_single_common_internal( + contexts[static_cast(worker_id)], *bop, *solvedp, bno); + }); } + END_BLOCK_TIMER; - if (const int err = first_error.load(); err != 0) + if (err != RETURN_NO_FAULT) return err; solvedp->no_of_boards = n; diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index 82d76b8d..d57b8807 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -8,15 +8,13 @@ */ #include -#include #include -#include -#include #include "solve_board.hpp" #include #include #include +#include #include #include #include @@ -44,17 +42,11 @@ auto solve_all_boards_n( scheduler.RegisterRun(RunMode::DDS_RUN_SOLVE, bds); - const int nthreads = std::max(1, - std::min(static_cast(std::thread::hardware_concurrency()), n)); - - std::atomic next_board{0}; - std::atomic first_error{0}; + START_BLOCK_TIMER; - auto worker = [&] { - for (;;) { - const int bno = next_board.fetch_add(1, std::memory_order_relaxed); - if (bno >= n || first_error.load(std::memory_order_relaxed) != 0) - break; + const int err = parallel_all_boards_n(n, 0, + [&](const int worker_id, const int bno) -> int { + (void)worker_id; FutureTricks fut; const auto t0 = std::chrono::steady_clock::now(); @@ -66,37 +58,14 @@ auto solve_all_boards_n( if (dur < 0) dur = 0; scheduler.SetBoardTime(bno, static_cast(dur)); - if (res == 1) + if (res == RETURN_NO_FAULT) solved.solved_board[bno] = fut; - else { - int expected = 0; - first_error.compare_exchange_strong( - expected, res, std::memory_order_relaxed); - } - } - }; + return res; + }); - START_BLOCK_TIMER; - { - // Avoid std::jthread here: Emscripten's libc++ on Windows does not - // provide it yet, while std::thread is widely available. - std::vector threads; - threads.reserve(static_cast(nthreads)); - try { - for (int i = 0; i < nthreads; ++i) - threads.emplace_back(worker); - } catch (...) { - for (auto& t : threads) - if (t.joinable()) - t.join(); - throw; - } - for (auto& t : threads) - t.join(); - } END_BLOCK_TIMER; - if (const int err = first_error.load(); err != 0) + if (err != RETURN_NO_FAULT) return err; solved.no_of_boards = n; diff --git a/library/src/system/parallel_boards.cpp b/library/src/system/parallel_boards.cpp new file mode 100644 index 00000000..25ded0d3 --- /dev/null +++ b/library/src/system/parallel_boards.cpp @@ -0,0 +1,102 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#include "parallel_boards.hpp" + +#include +#include +#include +#include + +#include + + +auto parallel_all_boards_n( + const int count, + const int worker_cap, + const std::function& process_board) -> int +{ + if (count <= 0) + { + return RETURN_NO_FAULT; + } + + int workers = worker_cap; + 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)); + + if (workers == 1) + { + for (int bno = 0; bno < count; ++bno) + { + const int rc = process_board(0, bno); + if (rc != RETURN_NO_FAULT) + { + return rc; + } + } + return RETURN_NO_FAULT; + } + + std::atomic next{0}; + std::atomic first_error{RETURN_NO_FAULT}; + + auto worker = [&](const int worker_id) { + for (;;) + { + const int bno = next.fetch_add(1, std::memory_order_relaxed); + if (bno >= count || first_error.load(std::memory_order_relaxed) != RETURN_NO_FAULT) + { + break; + } + + const int rc = process_board(worker_id, bno); + if (rc != RETURN_NO_FAULT) + { + int expected = RETURN_NO_FAULT; + first_error.compare_exchange_strong( + expected, rc, std::memory_order_relaxed); + break; + } + } + }; + + std::vector threads; + threads.reserve(static_cast(workers)); + try + { + for (int t = 0; t < workers; ++t) + { + threads.emplace_back(worker, t); + } + } + 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 != RETURN_NO_FAULT ? err : RETURN_NO_FAULT; +} diff --git a/library/src/system/parallel_boards.hpp b/library/src/system/parallel_boards.hpp new file mode 100644 index 00000000..96f0baa1 --- /dev/null +++ b/library/src/system/parallel_boards.hpp @@ -0,0 +1,27 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#pragma once + +#include + + +/** + * @brief Process boards [0, count) with work-stealing parallelism. + * + * @param count Number of board indices to process. + * @param worker_cap Maximum worker threads; <= 0 uses hardware concurrency. + * @param process_board Called for each board; must return RETURN_NO_FAULT (1) + * on success. Receives the worker's thread index and board number. + * @return First non-success code from @p process_board, or RETURN_NO_FAULT. + */ +auto parallel_all_boards_n( + int count, + int worker_cap, + const std::function& process_board) -> int; From 4af520ff083bfcedfc585a6544c34f28d7e70978 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 11 Jun 2026 20:00:03 -0400 Subject: [PATCH 071/157] Fix IndexError in _convert_pbn for short PBN deal strings. Check bp < len(pbn_deal) before indexing while scanning for the dealer letter, and return early when the string ends before a direction is found. Co-authored-by: Cursor --- python/examples/dd_table_for_deal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/examples/dd_table_for_deal.py b/python/examples/dd_table_for_deal.py index 53652505..1498841d 100644 --- a/python/examples/dd_table_for_deal.py +++ b/python/examples/dd_table_for_deal.py @@ -113,10 +113,11 @@ def _convert_pbn(pbn_deal: str) -> list[list[int]]: bp = 0 while ( bp < 3 + and bp < len(pbn_deal) and pbn_deal[bp] not in "NWESnwes" ): bp += 1 - if bp >= 3: + if bp >= 3 or bp >= len(pbn_deal): return remain first = {"N": 0, "E": 1, "S": 2, "W": 3}[pbn_deal[bp].upper()] From a8322826bff0cae36c7175a21e6799fe06928cb8 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 12 Jun 2026 17:08:52 -0400 Subject: [PATCH 072/157] Added a test for the _convert_pbn fix. --- python/BUILD.bazel | 8 ++++++++ python/examples/BUILD.bazel | 12 ++++++++++-- python/tests/test_convert_pbn.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 python/tests/test_convert_pbn.py diff --git a/python/BUILD.bazel b/python/BUILD.bazel index fc003335..df169761 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -158,6 +158,14 @@ py_test( ], ) +py_test( + name = "convert_pbn_test", + size = "small", + main = "tests/test_convert_pbn.py", + srcs = ["tests/test_convert_pbn.py"], + deps = ["//python/examples:dd_table_for_deal_lib"], +) + py_wheel( name = "dds3_wheel", distribution = "dds3", diff --git a/python/examples/BUILD.bazel b/python/examples/BUILD.bazel index 39e7c152..8ef2d568 100644 --- a/python/examples/BUILD.bazel +++ b/python/examples/BUILD.bazel @@ -1,8 +1,16 @@ -load("@rules_python//python:defs.bzl", "py_binary") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") + +py_library( + name = "dd_table_for_deal_lib", + srcs = ["dd_table_for_deal.py"], + imports = ["."], + deps = ["//python:dds3_lib"], + visibility = ["//python:__pkg__"], +) py_binary( name = "dd_table_for_deal", srcs = ["dd_table_for_deal.py"], main = "dd_table_for_deal.py", - deps = ["//python:dds3_lib"], + deps = [":dd_table_for_deal_lib"], ) diff --git a/python/tests/test_convert_pbn.py b/python/tests/test_convert_pbn.py new file mode 100644 index 00000000..905b9430 --- /dev/null +++ b/python/tests/test_convert_pbn.py @@ -0,0 +1,32 @@ +"""Tests for PBN-to-bitmask conversion in dd_table_for_deal.""" + +import unittest + +from dd_table_for_deal import _convert_pbn + +_EMPTY_REMAIN = [[0] * 4 for _ in range(4)] + +_EXAMPLE_DEAL = ( + "N:73.QJT.AQ54.T752 QT6.876.KJ9.AQ84 " + "5.A95432.7632.K6 AKJ9842.K.T8.J93" +) + + +class ConvertPbnTest(unittest.TestCase): + def test_short_deal_strings_return_empty_remain(self) -> None: + """Short or malformed PBN strings must not raise IndexError.""" + for deal in ("", "N", "N:", "12", "abc"): + with self.subTest(deal=deal): + self.assertEqual(_convert_pbn(deal), _EMPTY_REMAIN) + + def test_valid_deal_parses_card_bitmasks(self) -> None: + remain = _convert_pbn(_EXAMPLE_DEAL) + + # North's spades: 73 + self.assertEqual(remain[0][0], 0x0080 | 0x0008) + # North's hearts: QJT + self.assertEqual(remain[0][1], 0x1000 | 0x0800 | 0x0400) + + +if __name__ == "__main__": + unittest.main() From 0f93669704547b927a55bc6790dfb23290536fc4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 14 Jun 2026 17:18:14 -0400 Subject: [PATCH 073/157] Updated visibility in python/examples/BUILD.bazel, per Copilot. --- python/examples/BUILD.bazel | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/examples/BUILD.bazel b/python/examples/BUILD.bazel index 8ef2d568..6e09bbed 100644 --- a/python/examples/BUILD.bazel +++ b/python/examples/BUILD.bazel @@ -5,7 +5,10 @@ py_library( srcs = ["dd_table_for_deal.py"], imports = ["."], deps = ["//python:dds3_lib"], - visibility = ["//python:__pkg__"], + visibility = [ + "//python:__pkg__", + "//python:__subpackages__", + ], ) py_binary( From 21bd24e59291738424f3a4be910375dcac2d91fd Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Tue, 16 Jun 2026 10:19:44 -0400 Subject: [PATCH 074/157] Add Link-Time Optimization for MacOS builds. --- CPPVARIABLES.bzl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CPPVARIABLES.bzl b/CPPVARIABLES.bzl index 0f5366cd..df464e52 100644 --- a/CPPVARIABLES.bzl +++ b/CPPVARIABLES.bzl @@ -3,6 +3,7 @@ DDS_CPPOPTS = select({ "//:build_macos": [ "-O3", + "-flto=thin", "-mtune=generic", "-fPIC", "-Wpedantic", @@ -86,7 +87,7 @@ DDS_LOCAL_DEFINES = select({ }) DDS_LINKOPTS = select({ - "//:build_macos": [], + "//:build_macos": ["-flto=thin"], "//:debug_build_macos": [], "//:build_linux": [], "//:debug_build_linux": [], From 58ab7c2ac047c24283bf5b1d145093c2cd4606f4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Tue, 16 Jun 2026 09:04:20 -0400 Subject: [PATCH 075/157] Avoid per-node shared_ptr churn in the search hot path Add a non-owning SolverContext::thread_ptr() accessor and use it at the hot ThreadData access sites in ab_search, quick_tricks, and later_tricks instead of copying the shared_ptr returned by thread(). This removes the atomic reference-count traffic from the inner search loop. Measured on dtest -f hands/list100.txt -s solve -n 14 (14 cores), interleaved against develop to cancel machine drift: ~3-4% lower user and system time with tighter run-to-run variance. Behavior is unchanged; the pointer lifetime is tied to the owning context. Co-authored-by: Cursor --- library/src/ab_search.cpp | 19 +++++++++++-------- library/src/later_tricks.cpp | 8 ++++---- library/src/quick_tricks.cpp | 8 ++++---- library/src/solver_context/solver_context.hpp | 11 +++++++++++ 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/library/src/ab_search.cpp b/library/src/ab_search.cpp index cb4d65bc..fbc4e8fd 100644 --- a/library/src/ab_search.cpp +++ b/library/src/ab_search.cpp @@ -90,7 +90,7 @@ bool ab_search( the value of the subtree is returned. This is a specialized AB function for hand_rel_first == 0. */ - auto thrp = ctx.thread(); + 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); @@ -192,7 +192,7 @@ static bool ab_search_0_ctx( the value of the subtree is returned. This is a specialized AB function for hand_rel_first == 0. */ - auto thrp = ctx.thread(); + ThreadData* thrp = ctx.thread_ptr(); int trump = thrp->trump; int hand = posPoint->first[depth]; int tricks = depth >> 2; @@ -493,7 +493,7 @@ static bool ab_search_1_ctx( const int depth, SolverContext& ctx) { - auto thrp = ctx.thread(); + 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); @@ -589,7 +589,9 @@ static bool ab_search_2_ctx( const int depth, SolverContext& ctx) { - auto thrp = ctx.thread(); +#ifdef DDS_AB_STATS + 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; @@ -680,7 +682,9 @@ static bool ab_search_3_ctx( unsigned short int makeWinRank[DDS_SUITS]; - auto thrp = ctx.thread(); +#ifdef DDS_AB_STATS + 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; @@ -826,7 +830,7 @@ void make_3( MoveType const * mply, SolverContext& ctx) { - auto thrp = ctx.thread(); + ThreadData* thrp = ctx.thread_ptr(); int firstHand = posPoint->first[depth]; const TrickDataType& data = ctx.move_gen().get_trick_data((depth + 3) >> 2); @@ -892,7 +896,7 @@ static void make_3_ctx( MoveType const * mply, SolverContext& ctx) { - auto thrp = ctx.thread(); + ThreadData* thrp = ctx.thread_ptr(); int firstHand = posPoint->first[depth]; const TrickDataType& data = ctx.move_gen().get_trick_data((depth + 3) >> 2); @@ -1111,7 +1115,6 @@ EvalType evaluate_with_context( const int trump, SolverContext& ctx) { - auto thrp = ctx.thread(); int s, h, hmax = 0, count = 0, k = 0; unsigned short rmax = 0; EvalType eval; diff --git a/library/src/later_tricks.cpp b/library/src/later_tricks.cpp index bc8e08ca..98102dcb 100644 --- a/library/src/later_tricks.cpp +++ b/library/src/later_tricks.cpp @@ -163,7 +163,7 @@ bool LaterTricksMIN( fprintf(stderr, "LaterTricksMIN: invalid aggr=%u (depth=%d)", aggr, depth); return true; // conservative fallback } - int h = ctx.thread()->rel[aggr].abs_rank[3][trump].hand; + int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; if (h == -1) return true; @@ -176,7 +176,7 @@ bool LaterTricksMIN( 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()->rel[aggr].abs_rank[3][trump].rank)) ]; + static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; return false; } } @@ -338,7 +338,7 @@ bool LaterTricksMAX( fprintf(stderr, "LaterTricksMAX: invalid aggr=%u (depth=%d)\n", aggr, depth); return false; // conservative fallback for MAX } - int h = ctx.thread()->rel[aggr].abs_rank[3][trump].hand; + int h = ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].hand; if (h == -1) return false; @@ -351,7 +351,7 @@ bool LaterTricksMAX( 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()->rel[aggr].abs_rank[3][trump].rank)) ]; + static_cast(static_cast(ctx.thread_ptr()->rel[aggr].abs_rank[3][trump].rank)) ]; return true; } } diff --git a/library/src/quick_tricks.cpp b/library/src/quick_tricks.cpp index d1fade94..0c161406 100644 --- a/library/src/quick_tricks.cpp +++ b/library/src/quick_tricks.cpp @@ -997,10 +997,10 @@ int QuickTricksPartnerHandTrump( for (int h = 0; h < DDS_HANDS; h++) ranks |= tpos.rank_in_suit[h][suit]; - if (ctx.thread()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) + if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(static_cast(ctx.thread()->rel[ranks].abs_rank[3][suit].rank)) ]; + static_cast(static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank)) ]; tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; @@ -1107,10 +1107,10 @@ int QuickTricksPartnerHandNT( for (int h = 0; h < DDS_HANDS; h++) ranks |= tpos.rank_in_suit[h][suit]; - if (ctx.thread()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) + if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(static_cast(ctx.thread()->rel[ranks].abs_rank[3][suit].rank)) ]; + static_cast(static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank)) ]; qt++; if (qt >= cutoff) return qt; diff --git a/library/src/solver_context/solver_context.hpp b/library/src/solver_context/solver_context.hpp index 47344033..a77a6347 100644 --- a/library/src/solver_context/solver_context.hpp +++ b/library/src/solver_context/solver_context.hpp @@ -77,6 +77,17 @@ class SolverContext 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(); + } + /** * @brief Access the current configuration snapshot. * From e1d42aadca6f44f52d539208b74da2463e7cc3a2 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 17 Jun 2026 09:04:21 -0400 Subject: [PATCH 076/157] Fixes so that DDS_AB_STATS will work when it's set. --- library/src/BUILD.bazel | 21 ++++++++- library/src/init.cpp | 8 +--- library/src/solver_context/solver_context.cpp | 39 ++++++++++++++-- library/src/solver_context/solver_context.hpp | 6 +-- library/src/system/BUILD.bazel | 3 ++ library/src/system/file.cpp | 46 +++++++++++++++++++ library/src/system/file.hpp | 45 ++++++++++++++++++ library/src/system/thread_data.hpp | 10 +++- 8 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 library/src/system/file.cpp create mode 100644 library/src/system/file.hpp diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 730bc3b7..0851a9c3 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -6,6 +6,21 @@ external_headers = [ "dds.hpp", ] +cc_library( + name = "ab_stats", + srcs = ["ab_stats.cpp"], + hdrs = ["ab_stats.hpp"], + includes = ["."], + copts = DDS_CPPOPTS, + linkopts = DDS_LINKOPTS, + local_defines = DDS_LOCAL_DEFINES, + visibility = [ + "//library/src:__subpackages__", + "//library/tests:__subpackages__", + ], + deps = ["//library/src/utility:constants"], +) + filegroup( name = "dds_sources", srcs = glob([ @@ -13,7 +28,10 @@ filegroup( "*.hpp", "*.h", ], - exclude = external_headers, + exclude = external_headers + [ + "ab_stats.cpp", + "ab_stats.hpp", + ], ) ) @@ -39,6 +57,7 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", "//library/src/system", "//library/src/solver_context:solver_context", ], diff --git a/library/src/init.cpp b/library/src/init.cpp index 27243514..d265a5a1 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -165,12 +165,8 @@ void InitDebugFiles() void CloseDebugFiles() { - for (unsigned thrId = 0; thrId < memory.NumThreads(); thrId++) - { - SolverContext tmp_ctx; - [[maybe_unused]] auto thrp = tmp_ctx.thread(); - thrp->close_debug_files(); - } + // Per-context debug files are opened in SolverContext::bind_thread_data() + // and closed in ~SolverContext(). } diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index 34bd6cc9..6f70827e 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -1,13 +1,43 @@ #include "solver_context.hpp" +#include #include #include #include +#include #include //#include #include #include +#include + +namespace { + +#if defined(DDS_TOP_LEVEL) || defined(DDS_AB_STATS) || defined(DDS_AB_HITS) || \ + 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)) + + DDS_DEBUG_SUFFIX; +} +#endif + +} // namespace + +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; + +#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()); +#endif +} // Owned-ThreadData constructor: allocate ThreadData as a member of the // SolverContext so callers can create a context at the top of the stack @@ -17,9 +47,7 @@ SolverContext::SolverContext(SolverConfig cfg) { // Create an owned ThreadData instance and keep it in thr_. thr_ = std::make_shared(); - // Ensure persistent facades like SearchContext see the bound ThreadData. - search_.set_thread(thr_); - search_.set_owner(this); + bind_thread_data(); } auto SolverContext::trans_table() const -> TransTable* @@ -117,7 +145,10 @@ auto SolverContext::dispose_trans_table() const -> void // Defaulted destructor defined out-of-line so destruction of the // owned std::shared_ptr happens where ThreadData is a // complete type. -SolverContext::~SolverContext() = default; +SolverContext::~SolverContext() +{ + if (thr_) thr_->close_debug_files(); +} auto SolverContext::reset_for_solve() const -> void { diff --git a/library/src/solver_context/solver_context.hpp b/library/src/solver_context/solver_context.hpp index a77a6347..6ba907a7 100644 --- a/library/src/solver_context/solver_context.hpp +++ b/library/src/solver_context/solver_context.hpp @@ -50,9 +50,7 @@ class SolverContext explicit SolverContext(std::shared_ptr thread, SolverConfig cfg = {}) : thr_(std::move(thread)), cfg_(cfg) { - // Bind the persistent facades to the underlying ThreadData. - search_.set_thread(thr_); - search_.set_owner(this); + bind_thread_data(); } // NOTE: constructors that accepted raw ThreadData* were removed as part @@ -442,6 +440,8 @@ class SolverContext // Transposition table is now owned per SearchContext and created lazily. // // See the developer note above for details on TT lifecycle and resets. + + void bind_thread_data(); }; auto ThreadMemoryUsed() -> double; diff --git a/library/src/system/BUILD.bazel b/library/src/system/BUILD.bazel index fb05075a..995aacc5 100644 --- a/library/src/system/BUILD.bazel +++ b/library/src/system/BUILD.bazel @@ -13,6 +13,7 @@ cc_library( "//library/src/api:api_definitions", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", # Utilities lives under system/util (header-only for now) ], include_prefix = "system", @@ -34,6 +35,7 @@ cc_library( "//library/src/api:api_definitions", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", ], include_prefix = "system", copts = DDS_CPPOPTS, @@ -53,6 +55,7 @@ cc_library( "//library/src/api:api_definitions", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", ], include_prefix = "system", copts = DDS_CPPOPTS, diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp new file mode 100644 index 00000000..52a146ed --- /dev/null +++ b/library/src/system/file.cpp @@ -0,0 +1,46 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#include "file.hpp" + +File::~File() +{ + Close(); +} + +void File::Reset() +{ + fname_.clear(); + file_open_ = false; +} + +void File::SetName(const std::string& fname_in) +{ + fname_ = fname_in; +} + +std::ofstream& File::GetStream() +{ + if (!file_open_) + { + fout_.open(fname_); + file_open_ = true; + } + + return fout_; +} + +void File::Close() +{ + if (file_open_) + { + fout_.close(); + file_open_ = false; + } +} diff --git a/library/src/system/file.hpp b/library/src/system/file.hpp new file mode 100644 index 00000000..180496b9 --- /dev/null +++ b/library/src/system/file.hpp @@ -0,0 +1,45 @@ +/* + DDS, a bridge double dummy solver. + + Copyright (C) 2006-2014 by Bo Haglund / + 2014-2018 by Bo Haglund & Soren Hein. + + See LICENSE and README. +*/ + +#pragma once + +#include +#include + +namespace dds { + +/** + * @brief Lazy-opening output file for debug/statistics logging. + */ +class File +{ + private: + + std::string fname_; + bool file_open_ = false; + std::ofstream fout_; + + public: + + File() = default; + + ~File(); + + void Reset(); + + void SetName(const std::string& fname_in); + + std::ofstream& GetStream(); + + void Close(); +}; + +} // namespace dds + +using dds::File; diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index e642b707..0c937da2 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -1,13 +1,19 @@ #ifndef DDS_THREAD_DATA_H #define DDS_THREAD_DATA_H +#include + #include #include #include - #ifdef DDS_AB_STATS - #include "ab_stats.hpp" +#include "ab_stats.hpp" +#endif + +#if defined(DDS_TOP_LEVEL) || defined(DDS_AB_STATS) || defined(DDS_AB_HITS) || \ + defined(DDS_TT_STATS) || defined(DDS_TIMING) || defined(DDS_MOVES) +#include "file.hpp" #endif #ifdef DDS_TIMING From e7b1763ea1e32b2c221504e71abd8b22ff737ef0 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 17 Jun 2026 09:57:07 -0400 Subject: [PATCH 077/157] Remove unused global counter from debug.h to fix Windows builds. Including debug.h from thread_data.hpp exposed a global named counter that conflicts with pybind11 on MSVC when warnings are treated as errors. Co-authored-by: Cursor --- library/src/utility/debug.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/library/src/utility/debug.h b/library/src/utility/debug.h index 93f66400..fb5623b6 100644 --- a/library/src/utility/debug.h +++ b/library/src/utility/debug.h @@ -123,18 +123,4 @@ #endif #endif -/// @name Performance Counters -/// @brief Statistics counters for profiling and analysis. -/// @{ - -/// @brief Number of available counter slots for performance tracking. -constexpr int COUNTER_SLOTS = 200; - -/// @brief Global array of performance counters. -/// Each thread may use these counters for statistics collection. -/// Size: COUNTER_SLOTS (200) entries. -extern long long counter[COUNTER_SLOTS]; - -/// @} - /// @} From d5a05f4b3c0b8a29651a6a3f000572f53143aa8b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:09:41 -0400 Subject: [PATCH 078/157] Fix debug file init for shared ThreadData wrappers. Only owning SolverContext instances should open or close per-thread debug files; helper contexts that wrap existing ThreadData must not reassign names mid-solve or close streams still in use by the primary context. Co-authored-by: Cursor --- library/src/init.cpp | 4 ++-- library/src/solver_context/solver_context.cpp | 8 +++++--- library/src/solver_context/solver_context.hpp | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/library/src/init.cpp b/library/src/init.cpp index d265a5a1..a29d0a39 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -165,8 +165,8 @@ void InitDebugFiles() void CloseDebugFiles() { - // Per-context debug files are opened in SolverContext::bind_thread_data() - // and closed in ~SolverContext(). + // Per-context debug files are opened when SolverContext owns its ThreadData + // (SolverContext(SolverConfig)) and closed in ~SolverContext() for owners only. } diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index 6f70827e..00af1e40 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -35,7 +35,8 @@ void SolverContext::bind_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()); + if (owns_thread_data_) + thr_->init_debug_files(next_debug_file_suffix()); #endif } @@ -43,7 +44,7 @@ void SolverContext::bind_thread_data() // 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) + : cfg_(cfg), owns_thread_data_(true) { // Create an owned ThreadData instance and keep it in thr_. thr_ = std::make_shared(); @@ -147,7 +148,8 @@ auto SolverContext::dispose_trans_table() const -> void // complete type. SolverContext::~SolverContext() { - if (thr_) thr_->close_debug_files(); + if (owns_thread_data_ && thr_) + thr_->close_debug_files(); } auto SolverContext::reset_for_solve() const -> void diff --git a/library/src/solver_context/solver_context.hpp b/library/src/solver_context/solver_context.hpp index 6ba907a7..ea360cec 100644 --- a/library/src/solver_context/solver_context.hpp +++ b/library/src/solver_context/solver_context.hpp @@ -47,6 +47,8 @@ struct SolverConfig class SolverContext { public: + // Wrap existing ThreadData (helper/sub-context). Does not open or close + // debug files; the owning context is responsible for debug file lifecycle. explicit SolverContext(std::shared_ptr thread, SolverConfig cfg = {}) : thr_(std::move(thread)), cfg_(cfg) { @@ -441,6 +443,9 @@ class SolverContext // // 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(); }; From b23a3f80f0f84a43c10c98e6516c6a5a3252b70d Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:25:32 -0400 Subject: [PATCH 079/157] Close debug files only when the last ThreadData owner is destroyed. Track whether debug files were opened on ThreadData and defer close until ~SolverContext() sees use_count() == 1, so shared/helper contexts do not close streams while another context is still writing. Co-authored-by: Cursor --- library/src/init.cpp | 3 ++- library/src/solver_context/solver_context.cpp | 8 ++++++-- library/src/system/thread_data.cpp | 12 +++++++++++- library/src/system/thread_data.hpp | 8 ++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/library/src/init.cpp b/library/src/init.cpp index a29d0a39..4cacda69 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -166,7 +166,8 @@ void InitDebugFiles() void CloseDebugFiles() { // Per-context debug files are opened when SolverContext owns its ThreadData - // (SolverContext(SolverConfig)) and closed in ~SolverContext() for owners only. + // (SolverContext(SolverConfig)) and closed in ~SolverContext() when the last + // shared_ptr to that ThreadData is released. } diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index 00af1e40..2a90abb2 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -33,11 +33,12 @@ void SolverContext::bind_thread_data() search_.set_owner(this); if (!thr_) return; + 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) - if (owns_thread_data_) thr_->init_debug_files(next_debug_file_suffix()); #endif + } } // Owned-ThreadData constructor: allocate ThreadData as a member of the @@ -148,7 +149,10 @@ auto SolverContext::dispose_trans_table() const -> void // complete type. SolverContext::~SolverContext() { - if (owns_thread_data_ && thr_) + // Close debug files only when this context holds the last shared_ptr to + // ThreadData. close_debug_files() is a no-op when files were never opened + // (e.g. non-owning wrappers over stack ThreadData). + if (thr_ && thr_.use_count() == 1) thr_->close_debug_files(); } diff --git a/library/src/system/thread_data.cpp b/library/src/system/thread_data.cpp index 9a2b277b..f788e569 100644 --- a/library/src/system/thread_data.cpp +++ b/library/src/system/thread_data.cpp @@ -6,6 +6,9 @@ using std::string; void ThreadData::init_debug_files([[maybe_unused]] const string& suffix) { + if (debug_files_initialized_) + return; + #ifdef DDS_TOP_LEVEL fileTopLevel.SetName(DDS_TOP_LEVEL_PREFIX + suffix); #endif @@ -30,10 +33,15 @@ void ThreadData::init_debug_files([[maybe_unused]] const string& suffix) #ifdef DDS_MOVES fileMoves.SetName(DDS_MOVES_PREFIX + suffix); #endif + + debug_files_initialized_ = true; } void ThreadData::close_debug_files() { + if (!debug_files_initialized_) + return; + #ifdef DDS_TOP_LEVEL fileTopLevel.Close(); #endif @@ -58,4 +66,6 @@ void ThreadData::close_debug_files() #ifdef DDS_MOVES fileMoves.Close(); #endif -} \ No newline at end of file + + debug_files_initialized_ = false; +} diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index 0c937da2..f6c38d29 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -99,11 +99,19 @@ struct ThreadData File fileMoves; #endif + // True after init_debug_files(); cleared by close_debug_files(). + bool debug_files_initialized_ = false; + // Initialize per-thread debug/stat files with a suffix (e.g., "_suffix"). void init_debug_files([[maybe_unused]] const std::string& suffix); // Close any open per-thread debug/stat files. void close_debug_files(); + + auto debug_files_initialized() const -> bool + { + return debug_files_initialized_; + } }; From 4c0d240250d6f62ab114cea324e4e7566d01495a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:33:23 -0400 Subject: [PATCH 080/157] Close open streams in File::Reset() before clearing state. Reset() previously cleared the filename and file_open_ flag without closing an already-open ofstream, which could leak handles and leave stale streams. Co-authored-by: Cursor --- library/src/system/file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index 52a146ed..5c817b0e 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -16,8 +16,8 @@ File::~File() void File::Reset() { + Close(); fname_.clear(); - file_open_ = false; } void File::SetName(const std::string& fname_in) From ded2fc1d1a1c3fb5ac5a358ec6084100271b5d99 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:40:56 -0400 Subject: [PATCH 081/157] Close open streams in File::SetName() before renaming. SetName() previously updated fname_ while leaving an open ofstream on the old path, so later writes could go to the wrong file. Co-authored-by: Cursor --- library/src/system/file.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index 5c817b0e..070e4629 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -22,6 +22,10 @@ void File::Reset() void File::SetName(const std::string& fname_in) { + if (fname_in == fname_) + return; + + Close(); fname_ = fname_in; } From d0169d56d475490c45011ea465caf3c4605fe6b9 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:47:23 -0400 Subject: [PATCH 082/157] Track File open state via ofstream::is_open(). GetStream() previously set file_open_ even when open() failed, leaving the object stuck with a closed but supposedly-open stream; skip open on empty names and rely on is_open() for lazy open and close. Co-authored-by: Cursor --- library/src/system/file.cpp | 10 ++-------- library/src/system/file.hpp | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index 070e4629..fdd05552 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -31,20 +31,14 @@ void File::SetName(const std::string& fname_in) std::ofstream& File::GetStream() { - if (!file_open_) - { + if (!fout_.is_open() && !fname_.empty()) fout_.open(fname_); - file_open_ = true; - } return fout_; } void File::Close() { - if (file_open_) - { + if (fout_.is_open()) fout_.close(); - file_open_ = false; - } } diff --git a/library/src/system/file.hpp b/library/src/system/file.hpp index 180496b9..d1a837b1 100644 --- a/library/src/system/file.hpp +++ b/library/src/system/file.hpp @@ -22,7 +22,6 @@ class File private: std::string fname_; - bool file_open_ = false; std::ofstream fout_; public: From 3324e1d174a9932dd812f80a4fcbccab3c1010e9 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:50:11 -0400 Subject: [PATCH 083/157] Remove no-op CloseDebugFiles() global teardown hook. Per-context debug files are closed in ~SolverContext(); the empty CloseDebugFiles() stub and library unload calls are no longer needed. Co-authored-by: Cursor --- library/src/dds.cpp | 13 ------------- library/src/init.cpp | 8 -------- library/src/init.hpp | 2 -- 3 files changed, 23 deletions(-) diff --git a/library/src/dds.cpp b/library/src/dds.cpp index 5c4c7f3d..265e77dc 100644 --- a/library/src/dds.cpp +++ b/library/src/dds.cpp @@ -39,7 +39,6 @@ extern "C" BOOL APIENTRY DllMain( SetMaxThreads(0); else if (ul_reason_for_call == DLL_PROCESS_DETACH) { - CloseDebugFiles(); FreeMemory(); #ifdef DDS_MEMORY_LEAKS_WIN32 _CrtDumpMemoryLeaks(); @@ -73,7 +72,6 @@ void DDSInitialize(void) */ void DDSFinalize(void) { - CloseDebugFiles(); FreeMemory(); } @@ -89,17 +87,6 @@ static void __attribute__ ((constructor)) libInit(void) SetMaxThreads(0); } - -/** - * @brief Library destructor for platforms supporting destructor attribute. - * - * This function is called when the library is unloaded. - */ -static void __attribute__ ((destructor)) libEnd(void) -{ - CloseDebugFiles(); -} - #endif #ifdef _MANAGED diff --git a/library/src/init.cpp b/library/src/init.cpp index 4cacda69..1e09d8b3 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -163,14 +163,6 @@ void InitDebugFiles() } -void CloseDebugFiles() -{ - // Per-context debug files are opened when SolverContext owns its ThreadData - // (SolverContext(SolverConfig)) and closed in ~SolverContext() when the last - // shared_ptr to that ThreadData is released. -} - - void SetDeal( const std::shared_ptr& thrp) { diff --git a/library/src/init.hpp b/library/src/init.hpp index d5c20e39..0c84da69 100644 --- a/library/src/init.hpp +++ b/library/src/init.hpp @@ -23,5 +23,3 @@ void InitWinners( const Deal& dl, Pos& posPoint, const std::shared_ptr& thrp); - -void CloseDebugFiles(); From a25d9ce7555e66a2bb9dc53fc87b8023d3c4c082 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 23:53:51 -0400 Subject: [PATCH 084/157] Qualify dds::File and remove header using declaration. Drop the global using alias from file.hpp so File stays in namespace dds and callers reference it explicitly. Co-authored-by: Cursor --- library/src/system/file.cpp | 10 +++++----- library/src/system/file.hpp | 2 -- library/src/system/thread_data.hpp | 14 +++++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index fdd05552..79255ad0 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -9,18 +9,18 @@ #include "file.hpp" -File::~File() +dds::File::~File() { Close(); } -void File::Reset() +void dds::File::Reset() { Close(); fname_.clear(); } -void File::SetName(const std::string& fname_in) +void dds::File::SetName(const std::string& fname_in) { if (fname_in == fname_) return; @@ -29,7 +29,7 @@ void File::SetName(const std::string& fname_in) fname_ = fname_in; } -std::ofstream& File::GetStream() +std::ofstream& dds::File::GetStream() { if (!fout_.is_open() && !fname_.empty()) fout_.open(fname_); @@ -37,7 +37,7 @@ std::ofstream& File::GetStream() return fout_; } -void File::Close() +void dds::File::Close() { if (fout_.is_open()) fout_.close(); diff --git a/library/src/system/file.hpp b/library/src/system/file.hpp index d1a837b1..9e32be83 100644 --- a/library/src/system/file.hpp +++ b/library/src/system/file.hpp @@ -40,5 +40,3 @@ class File }; } // namespace dds - -using dds::File; diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index f6c38d29..ff01aa3b 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -73,30 +73,30 @@ struct ThreadData Moves moves; #ifdef DDS_TOP_LEVEL - File fileTopLevel; + dds::File fileTopLevel; #endif #ifdef DDS_AB_STATS ABstats ABStats; - File fileABstats; + dds::File fileABstats; #endif #ifdef DDS_AB_HITS - File fileRetrieved; - File fileStored; + dds::File fileRetrieved; + dds::File fileStored; #endif #ifdef DDS_TT_STATS - File fileTTstats; + dds::File fileTTstats; #endif #ifdef DDS_TIMING TimerList timerList; - File fileTimerList; + dds::File fileTimerList; #endif #ifdef DDS_MOVES - File fileMoves; + dds::File fileMoves; #endif // True after init_debug_files(); cleared by close_debug_files(). From a1a3db7528ba37aaa4041ec67d880b3cca11fd5f Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 00:20:20 -0400 Subject: [PATCH 085/157] Fix init_debug_files suffix comment to match actual naming. Document that SolverContext passes ".txt" rather than a thread-id suffix, so debug output filenames are easier to trace. Co-authored-by: Cursor --- library/src/system/thread_data.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index ff01aa3b..19d4646b 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -102,7 +102,8 @@ struct ThreadData // True after init_debug_files(); cleared by close_debug_files(). bool debug_files_initialized_ = false; - // Initialize per-thread debug/stat files with a suffix (e.g., "_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. From 09818e9e0b605a6de994f09f39f64f2ae0d1de73 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 00:31:47 -0400 Subject: [PATCH 086/157] Include algorithm and cstdio in solver_context.cpp. Add direct headers for std::min and std::snprintf instead of relying on transitive includes from other headers. Co-authored-by: Cursor --- library/src/solver_context/solver_context.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/solver_context/solver_context.cpp b/library/src/solver_context/solver_context.cpp index 2a90abb2..4b947e37 100644 --- a/library/src/solver_context/solver_context.cpp +++ b/library/src/solver_context/solver_context.cpp @@ -1,6 +1,8 @@ #include "solver_context.hpp" +#include #include +#include #include #include #include From f1130e811d4c885b56e313f827212d24801e0d7e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 00:32:56 -0400 Subject: [PATCH 087/157] Fix SolverContext wrapper comment on debug file lifecycle. Document that wrapper contexts skip init but may close debug files when they are the last shared_ptr holder of the ThreadData. Co-authored-by: Cursor --- library/src/solver_context/solver_context.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/src/solver_context/solver_context.hpp b/library/src/solver_context/solver_context.hpp index ea360cec..7ba130e6 100644 --- a/library/src/solver_context/solver_context.hpp +++ b/library/src/solver_context/solver_context.hpp @@ -47,8 +47,9 @@ struct SolverConfig class SolverContext { public: - // Wrap existing ThreadData (helper/sub-context). Does not open or close - // debug files; the owning context is responsible for debug file lifecycle. + // 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) { From 84d8240fe95d6fefe47161ba14e246f0706c290c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 08:05:58 -0400 Subject: [PATCH 088/157] Avoid compiling ab_stats.cpp twice in testable_dds. Exclude ab_stats.cpp from testable_dds_sources and link //library/src:ab_stats explicitly, matching :dds and preventing duplicate ABstats symbols. Co-authored-by: Cursor --- library/src/BUILD.bazel | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 0851a9c3..2ca45481 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -65,9 +65,10 @@ cc_library( filegroup( name = "testable_dds_sources", - srcs = glob([ - "*.cpp", - ],), + srcs = glob( + ["*.cpp"], + exclude = ["ab_stats.cpp"], + ), visibility = [ "//examples:__pkg__", ], @@ -109,6 +110,7 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", "//library/src/system", "//library/src/solver_context:solver_context", ], @@ -135,6 +137,7 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", "//library/src/system:system_util_log", "//library/src/solver_context:solver_context_log", ], @@ -160,6 +163,7 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", + "//library/src:ab_stats", "//library/src/system:system_util_stats", "//library/src/solver_context:solver_context_stats", ], From b182fddbeeb8726003099821b7185b5c3c1e9228 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 10:03:50 -0400 Subject: [PATCH 089/157] Fix DDS_SCHEDULER build consistency and dtest -r reporting. Propagate scheduler define through :dds to fix Scheduler ODR/crash; use SolveAllBoards for parallel solve timing; clarify calc has no per-board data. Co-authored-by: Cursor --- library/src/BUILD.bazel | 10 +++++----- library/src/init.cpp | 3 --- library/tests/loop.cpp | 15 +-------------- library/tests/testcommon.cpp | 10 +++++++++- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 2ca45481..1533b8fd 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -1,4 +1,4 @@ -load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES") +load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES", "DDS_SCHEDULER_DEFINE") load("@rules_cc//cc:defs.bzl", "cc_library") external_headers = [ @@ -47,7 +47,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, visibility = ["//visibility:public"], include_prefix = "dds", deps = [ @@ -91,7 +91,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, include_prefix = "dds", visibility = [ "//:__pkg__", # allow root package to wrap/export @@ -124,7 +124,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, include_prefix = "dds", visibility = [ "//:__pkg__", @@ -150,7 +150,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, + local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, include_prefix = "dds", visibility = [ "//:__pkg__", diff --git a/library/src/init.cpp b/library/src/init.cpp index 1e09d8b3..3e22dd11 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -157,9 +157,6 @@ int STDCALL SetThreading( void InitDebugFiles() { -#ifdef DDS_SCHEDULER - InitFileScheduler(); -#endif } diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index ecc6b4eb..5db5e3bf 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -68,20 +68,7 @@ void loop_solve( } else { - solvedbdp->no_of_boards = count; - ret = dtest_run_parallel(count, options.num_threads_, - [&](const int j) -> int { - FutureTricks fut; - const int res = SolveBoardPBN( - bop->deals[j], bop->target[j], bop->solutions[j], bop->mode[j], - &fut, 0); - if (res == RETURN_NO_FAULT) - { - solvedbdp->solved_board[j] = fut; - return RETURN_NO_FAULT; - } - return res; - }); + ret = SolveAllBoards(bop, solvedbdp); } if (ret != RETURN_NO_FAULT) { diff --git a/library/tests/testcommon.cpp b/library/tests/testcommon.cpp index 3853f383..efc36dec 100644 --- a/library/tests/testcommon.cpp +++ b/library/tests/testcommon.cpp @@ -153,7 +153,15 @@ int real_main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) scheduler.GetBoardTimes(times); if (times.empty()) { - cout << "Per-board timing data not available. Rebuild with DDS_SCHEDULER enabled to collect per-board timings." << std::endl; + if (options.solver_ == Solver::DTEST_SOLVER_CALC) + { + cout << "Per-board timing data not available for calc (use -s solve -r)." + << std::endl; + } + else + { + cout << "Per-board timing data not available." << std::endl; + } } else { From 093d72282ee19c95c0943fbb581ec7d3a84ee12f Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 10:34:30 -0400 Subject: [PATCH 090/157] Initialize scheduler timing state and gate EndBlockTimer aggregation. Zero hands[]/group[] timing fields in Reset() and skip group/per-hand stats unless populated by the GetNumber dispatcher path. Co-authored-by: Cursor --- library/src/system/scheduler.cpp | 35 +++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/library/src/system/scheduler.cpp b/library/src/system/scheduler.cpp index fffcb180..09ad1add 100644 --- a/library/src/system/scheduler.cpp +++ b/library/src/system/scheduler.cpp @@ -135,7 +135,24 @@ void Scheduler::ClearTiming() 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; @@ -266,6 +283,9 @@ void Scheduler::MakeGroups(const Boards& bds) group[numGroups].strain = strain; group[numGroups].hash = key; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; numGroups++; } else @@ -332,6 +352,9 @@ void Scheduler::FinetuneGroups() group[numGroups].strain = 5; group[numGroups].hash = extraGroups; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; numGroups++; extraGroups++; @@ -422,6 +445,9 @@ void Scheduler::FinetuneGroups() group[numGroups].strain = 5; group[numGroups].hash = extraGroups; + group[numGroups].head = -1; + group[numGroups].actual = 0; + group[numGroups].repeatNo = 0; numGroups++; extraGroups++; @@ -903,7 +929,7 @@ void Scheduler::EndBlockTimer() if (timeUser > blockMax) blockMax = timeUser; - if (hp->repeatNo == 0) + if (hp->repeatNo == 0 && timeUser > 0) { int bin = timeUser / 1000; timeHist[bin]++; @@ -916,8 +942,11 @@ void Scheduler::EndBlockTimer() for (int g = 0; g < numGroups; g++) { - int head = group[g].head; - int NTflag = (hands[head].strain == 4 ? 1 : 0); + const int head = group[g].head; + if (head < 0 || head >= numHands) + continue; + + const int NTflag = (hands[head].strain == 4 ? 1 : 0); TimeStat ts; From 740d9b0cc9d9910457a7d37dc457e8b6f08130e7 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 12:08:50 -0400 Subject: [PATCH 091/157] Respect dtest thread limit in parallel solve while keeping per-board timing. Add worker_cap to solve_all_boards_n/pbn_n so loop_solve passes options.num_threads_ instead of always using hardware concurrency. Co-authored-by: Cursor --- library/src/solve_board.cpp | 102 ++++++++++++++++++------------------ library/src/solve_board.hpp | 10 ++++ library/tests/loop.cpp | 4 +- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index d57b8807..edd78abb 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -29,9 +29,41 @@ auto same_board( const unsigned index2) -> bool; +static auto boards_from_pbn( + 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.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; + } + + return RETURN_NO_FAULT; +} + + auto solve_all_boards_n( Boards const& bds, - SolvedBoards& solved) -> int + SolvedBoards& solved, + const int worker_cap) -> int { const int n = bds.no_of_boards; if (n > MAXNOOFBOARDS) @@ -44,7 +76,7 @@ auto solve_all_boards_n( START_BLOCK_TIMER; - const int err = parallel_all_boards_n(n, 0, + const int err = parallel_all_boards_n(n, worker_cap, [&](const int worker_id, const int bno) -> int { (void)worker_id; @@ -78,6 +110,19 @@ auto solve_all_boards_n( } +auto solve_all_boards_pbn_n( + BoardsPBN const& bop, + SolvedBoards& solved, + const int worker_cap) -> 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, worker_cap); +} + + /* * Solve a single bridge Deal in PBN format. * @@ -121,32 +166,7 @@ int STDCALL SolveAllBoards( BoardsPBN const * bop, SolvedBoards * solvedp) { - Boards bo; - 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.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) - != 1) - return RETURN_PBN_FAULT; - } - - int res = solve_all_boards_n(bo, * solvedp); - return res; + return solve_all_boards_pbn_n(*bop, *solvedp, 0); } @@ -163,29 +183,9 @@ int STDCALL SolveAllBoardsSeq( SolvedBoards * solvedp) { Boards bo; - 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.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) - != 1) - return RETURN_PBN_FAULT; - } - + const int rc = boards_from_pbn(*bop, bo); + if (rc != RETURN_NO_FAULT) + return rc; return solve_all_boards_n_seq(bo, * solvedp); } diff --git a/library/src/solve_board.hpp b/library/src/solve_board.hpp index 2ce53185..400fe68a 100644 --- a/library/src/solve_board.hpp +++ b/library/src/solve_board.hpp @@ -14,6 +14,16 @@ #include +auto solve_all_boards_n( + Boards const& bds, + SolvedBoards& solved, + int worker_cap = 0) -> int; + +auto solve_all_boards_pbn_n( + BoardsPBN const& bop, + SolvedBoards& solved, + int worker_cap = 0) -> int; + auto solve_all_boards_n_seq( Boards const& bds, SolvedBoards& solved) -> int; diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 5db5e3bf..8c564e03 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -20,6 +20,7 @@ #include "cst.hpp" #include "dtest_parallel.hpp" +#include using std::cout; using std::endl; @@ -68,7 +69,8 @@ void loop_solve( } else { - ret = SolveAllBoards(bop, solvedbdp); + ret = solve_all_boards_pbn_n(*bop, *solvedbdp, + dtest_effective_threads(options.num_threads_, count)); } if (ret != RETURN_NO_FAULT) { From 3d776b147f90b5bdc344f601dcb5959a8fdf1104 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 17 Jun 2026 22:45:05 -0400 Subject: [PATCH 092/157] Initial commit --- benchmark.sh | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100755 benchmark.sh diff --git a/benchmark.sh b/benchmark.sh new file mode 100755 index 00000000..c603c990 --- /dev/null +++ b/benchmark.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +# Compare dtest performance: DDS 3.0 (this repo) vs DDS 2.9 (libdds). +# +# Runs all combinations of solver (calc, solve) and hand file +# (list1/10/100/1000), then prints per-run timings and a summary. +# Does not pass -n to dtest (library default thread count). +# +# Usage: +# ./bench_dtest.sh +# REPEATS=3 ./bench_dtest.sh +# DTEST_30=/path/to/dtest DTEST_29=/path/to/dtest ./bench_dtest.sh +# +# Environment: +# DTEST_30 Path to DDS 3.0 dtest (default: bazel-bin in this repo) +# DTEST_29 Path to DDS 2.9 dtest +# HANDS_DIR Directory containing list*.txt files (default: ./hands) +# REPEATS Runs per combination per binary (default: 1) +# DRY_RUN If 1, print commands only + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DTEST_30="${DTEST_30:-$ROOT/bazel-bin/library/tests/dtest}" +DTEST_29="${DTEST_29:-/Users/adamw/src/bridge-hackathon/dds/libdds/.build/test/dtest}" +HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" +REPEATS="${REPEATS:-1}" +DRY_RUN="${DRY_RUN:-0}" + +SOLVERS=(calc solve) +FILES=(list1.txt list10.txt list100.txt) +# FILES=(list1.txt list10.txt list100.txt list1000.txt) + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ ! -x "$DTEST_30" ]]; then + echo "error: DDS 3.0 dtest not found or not executable: $DTEST_30" >&2 + echo "hint: bazel build //library/tests:dtest" >&2 + exit 1 +fi + +if [[ ! -x "$DTEST_29" ]]; then + echo "error: DDS 2.9 dtest not found or not executable: $DTEST_29" >&2 + exit 1 +fi + +for f in "${FILES[@]}"; do + if [[ ! -f "$HANDS_DIR/$f" ]]; then + echo "error: hand file not found: $HANDS_DIR/$f" >&2 + exit 1 + fi +done + +branch="unknown" +if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" +fi + +RESULTS="$(mktemp)" +trap 'rm -f "$RESULTS"' EXIT + +parse_dtest_output() { + awk ' + /User time \(ms\)/ { user = $4 } + /Sys time \(ms\)/ { sys = $4 } + /Avg user time \(ms\)/ { avg = $5 } + /Ratio/ { ratio = $2 } + END { + if (user == "") user = "NA" + if (sys == "") sys = "NA" + if (avg == "") avg = "NA" + if (ratio == "") ratio = "NA" + print user, sys, avg, ratio + } + ' +} + +run_dtest() { + local binary="$1" + local solver="$2" + local hands="$3" + + if [[ "$DRY_RUN" == "1" ]]; then + echo "DRY_RUN: $binary -f $hands -s $solver" >&2 + echo "0 0 0.00 0.00" + return 0 + fi + + local out + if ! out="$("$binary" -f "$hands" -s "$solver" 2>&1)"; then + echo "error: dtest failed: $binary -f $hands -s $solver" >&2 + echo "$out" >&2 + exit 1 + fi + parse_dtest_output <<<"$out" +} + +echo "DDS dtest benchmark" +echo "===================" +echo "3.0 binary: $DTEST_30" +echo "2.9 binary: $DTEST_29" +echo "hands dir: $HANDS_DIR" +echo "branch: $branch" +echo "repeats: $REPEATS" +echo + +printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" +printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + "------" "------------" "----" "--------" "--------" "----------" "------" "---" + +total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * 2 * REPEATS )) +run_no=0 + +for solver in "${SOLVERS[@]}"; do + for file in "${FILES[@]}"; do + hands="$HANDS_DIR/$file" + for pair in "2.9:$DTEST_29" "3.0:$DTEST_30"; do + ver="${pair%%:*}" + bin="${pair#*:}" + + for (( rep = 1; rep <= REPEATS; rep++ )); do + run_no=$((run_no + 1)) + if [[ "$REPEATS" -gt 1 ]]; then + run_label="${rep}/${REPEATS}" + else + run_label="1/1" + fi + + read -r user sys avg ratio < <(run_dtest "$bin" "$solver" "$hands") + + printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" + + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ + "$solver" "$file" "$ver" "$rep" "$user" "$sys" "$avg" "$ratio" \ + >>"$RESULTS" + done + done + done +done + +echo +echo "Summary (3.0 vs 2.9, user time)" +echo "===============================" +printf "%-6s %-12s %10s %10s %10s %s\n" \ + "solver" "file" "2.9_user" "3.0_user" "speedup" "note" +printf "%-6s %-12s %10s %10s %10s %s\n" \ + "------" "------------" "----------" "----------" "----------" "----" + +awk -F'\t' ' + { + base = $1 SUBSEP $2 + if ($3 == "2.9") { + s29[base] += $5 + c29[base]++ + } else if ($3 == "3.0") { + s30[base] += $5 + c30[base]++ + } + } + END { + split("calc solve", solvers, " ") + split("list1.txt list10.txt list100.txt list1000.txt", files, " ") + + for (si = 1; si <= 2; si++) { + for (fi = 1; fi <= 4; fi++) { + base = solvers[si] SUBSEP files[fi] + if (!(base in c29) || !(base in c30)) continue + u29 = s29[base] / c29[base] + u30 = s30[base] / c30[base] + speedup = (u30 > 0) ? u29 / u30 : 0 + note = (speedup >= 1) ? "3.0 faster" : "2.9 faster" + printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", + solvers[si], files[fi], u29, u30, speedup, note + } + } + } +' "$RESULTS" + +echo +echo "Completed $run_no runs ($total_runs expected)." From bb5c481b1baf21ecabb10e88ef74eada175c2119 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 17 Jun 2026 22:49:18 -0400 Subject: [PATCH 093/157] Do not hard-code the path for binary that's not in the repo --- benchmark.sh | 91 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index c603c990..affe5682 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash -# Compare dtest performance: DDS 3.0 (this repo) vs DDS 2.9 (libdds). +# Compare dtest performance between two binaries (e.g. DDS 3.0 vs 2.9). # # Runs all combinations of solver (calc, solve) and hand file # (list1/10/100/1000), then prints per-run timings and a summary. # Does not pass -n to dtest (library default thread count). # # Usage: -# ./bench_dtest.sh -# REPEATS=3 ./bench_dtest.sh -# DTEST_30=/path/to/dtest DTEST_29=/path/to/dtest ./bench_dtest.sh +# ./benchmark.sh --dtest2 /path/to/other/dtest +# REPEATS=3 ./benchmark.sh --dtest2 /path/to/other/dtest +# DTEST1=/path/to/dtest1 DTEST2=/path/to/dtest2 ./benchmark.sh # # Environment: -# DTEST_30 Path to DDS 3.0 dtest (default: bazel-bin in this repo) -# DTEST_29 Path to DDS 2.9 dtest +# DTEST1 Path to first dtest (default: bazel-bin in this repo) +# DTEST2 Path to second dtest (required unless --dtest2 is given) # HANDS_DIR Directory containing list*.txt files (default: ./hands) # REPEATS Runs per combination per binary (default: 1) # DRY_RUN If 1, print commands only @@ -20,8 +20,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DTEST_30="${DTEST_30:-$ROOT/bazel-bin/library/tests/dtest}" -DTEST_29="${DTEST_29:-/Users/adamw/src/bridge-hackathon/dds/libdds/.build/test/dtest}" +DTEST1="${DTEST1:-$ROOT/bazel-bin/library/tests/dtest}" HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" REPEATS="${REPEATS:-1}" DRY_RUN="${DRY_RUN:-0}" @@ -34,19 +33,21 @@ usage() { cat <&2 usage >&2 @@ -69,14 +80,20 @@ while [[ $# -gt 0 ]]; do esac done -if [[ ! -x "$DTEST_30" ]]; then - echo "error: DDS 3.0 dtest not found or not executable: $DTEST_30" >&2 +if [[ -z "${DTEST2:-}" ]]; then + echo "error: dtest2 required (use --dtest2 PATH or set DTEST2)" >&2 + usage >&2 + exit 1 +fi + +if [[ ! -x "$DTEST1" ]]; then + echo "error: dtest1 not found or not executable: $DTEST1" >&2 echo "hint: bazel build //library/tests:dtest" >&2 exit 1 fi -if [[ ! -x "$DTEST_29" ]]; then - echo "error: DDS 2.9 dtest not found or not executable: $DTEST_29" >&2 +if [[ ! -x "$DTEST2" ]]; then + echo "error: dtest2 not found or not executable: $DTEST2" >&2 exit 1 fi @@ -133,8 +150,8 @@ run_dtest() { echo "DDS dtest benchmark" echo "===================" -echo "3.0 binary: $DTEST_30" -echo "2.9 binary: $DTEST_29" +echo "dtest1: $DTEST1" +echo "dtest2: $DTEST2" echo "hands dir: $HANDS_DIR" echo "branch: $branch" echo "repeats: $REPEATS" @@ -151,7 +168,7 @@ run_no=0 for solver in "${SOLVERS[@]}"; do for file in "${FILES[@]}"; do hands="$HANDS_DIR/$file" - for pair in "2.9:$DTEST_29" "3.0:$DTEST_30"; do + for pair in "dtest2:$DTEST2" "dtest1:$DTEST1"; do ver="${pair%%:*}" bin="${pair#*:}" @@ -177,22 +194,22 @@ for solver in "${SOLVERS[@]}"; do done echo -echo "Summary (3.0 vs 2.9, user time)" -echo "===============================" +echo "Summary (dtest1 vs dtest2, user time)" +echo "=====================================" printf "%-6s %-12s %10s %10s %10s %s\n" \ - "solver" "file" "2.9_user" "3.0_user" "speedup" "note" + "solver" "file" "dtest2_user" "dtest1_user" "speedup" "note" printf "%-6s %-12s %10s %10s %10s %s\n" \ "------" "------------" "----------" "----------" "----------" "----" awk -F'\t' ' { base = $1 SUBSEP $2 - if ($3 == "2.9") { - s29[base] += $5 - c29[base]++ - } else if ($3 == "3.0") { - s30[base] += $5 - c30[base]++ + if ($3 == "dtest2") { + s2[base] += $5 + c2[base]++ + } else if ($3 == "dtest1") { + s1[base] += $5 + c1[base]++ } } END { @@ -202,13 +219,13 @@ awk -F'\t' ' for (si = 1; si <= 2; si++) { for (fi = 1; fi <= 4; fi++) { base = solvers[si] SUBSEP files[fi] - if (!(base in c29) || !(base in c30)) continue - u29 = s29[base] / c29[base] - u30 = s30[base] / c30[base] - speedup = (u30 > 0) ? u29 / u30 : 0 - note = (speedup >= 1) ? "3.0 faster" : "2.9 faster" + if (!(base in c2) || !(base in c1)) continue + u2 = s2[base] / c2[base] + u1 = s1[base] / c1[base] + speedup = (u1 > 0) ? u2 / u1 : 0 + note = (speedup >= 1) ? "dtest1 faster" : "dtest2 faster" printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", - solvers[si], files[fi], u29, u30, speedup, note + solvers[si], files[fi], u2, u1, speedup, note } } } From 16160aaac2e68b338b38081582cfc37cb1d5236f Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Wed, 17 Jun 2026 22:53:12 -0400 Subject: [PATCH 094/157] make dtest2 optional --- benchmark.sh | 109 +++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index affe5682..918903d7 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash -# Compare dtest performance between two binaries (e.g. DDS 3.0 vs 2.9). +# Benchmark dtest performance on one or two binaries. # # Runs all combinations of solver (calc, solve) and hand file -# (list1/10/100/1000), then prints per-run timings and a summary. +# (list1/10/100/1000), then prints per-run timings and an optional summary. # Does not pass -n to dtest (library default thread count). # # Usage: +# ./benchmark.sh # ./benchmark.sh --dtest2 /path/to/other/dtest -# REPEATS=3 ./benchmark.sh --dtest2 /path/to/other/dtest -# DTEST1=/path/to/dtest1 DTEST2=/path/to/dtest2 ./benchmark.sh +# REPEATS=3 ./benchmark.sh # # Environment: # DTEST1 Path to first dtest (default: bazel-bin in this repo) -# DTEST2 Path to second dtest (required unless --dtest2 is given) +# DTEST2 Optional second dtest for comparison # HANDS_DIR Directory containing list*.txt files (default: ./hands) # REPEATS Runs per combination per binary (default: 1) # DRY_RUN If 1, print commands only @@ -33,21 +33,22 @@ usage() { cat <&2 - usage >&2 - exit 1 -fi - if [[ ! -x "$DTEST1" ]]; then echo "error: dtest1 not found or not executable: $DTEST1" >&2 echo "hint: bazel build //library/tests:dtest" >&2 exit 1 fi -if [[ ! -x "$DTEST2" ]]; then +if [[ -n "${DTEST2:-}" && ! -x "$DTEST2" ]]; then echo "error: dtest2 not found or not executable: $DTEST2" >&2 exit 1 fi +BIN_PAIRS=("dtest1:$DTEST1") +if [[ -n "${DTEST2:-}" ]]; then + BIN_PAIRS=("dtest2:$DTEST2" "dtest1:$DTEST1") +fi +num_bins=${#BIN_PAIRS[@]} + for f in "${FILES[@]}"; do if [[ ! -f "$HANDS_DIR/$f" ]]; then echo "error: hand file not found: $HANDS_DIR/$f" >&2 @@ -151,7 +152,9 @@ run_dtest() { echo "DDS dtest benchmark" echo "===================" echo "dtest1: $DTEST1" -echo "dtest2: $DTEST2" +if [[ -n "${DTEST2:-}" ]]; then + echo "dtest2: $DTEST2" +fi echo "hands dir: $HANDS_DIR" echo "branch: $branch" echo "repeats: $REPEATS" @@ -162,13 +165,13 @@ printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ "------" "------------" "----" "--------" "--------" "----------" "------" "---" -total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * 2 * REPEATS )) +total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * num_bins * REPEATS )) run_no=0 for solver in "${SOLVERS[@]}"; do for file in "${FILES[@]}"; do hands="$HANDS_DIR/$file" - for pair in "dtest2:$DTEST2" "dtest1:$DTEST1"; do + for pair in "${BIN_PAIRS[@]}"; do ver="${pair%%:*}" bin="${pair#*:}" @@ -193,43 +196,45 @@ for solver in "${SOLVERS[@]}"; do done done -echo -echo "Summary (dtest1 vs dtest2, user time)" -echo "=====================================" -printf "%-6s %-12s %10s %10s %10s %s\n" \ - "solver" "file" "dtest2_user" "dtest1_user" "speedup" "note" -printf "%-6s %-12s %10s %10s %10s %s\n" \ - "------" "------------" "----------" "----------" "----------" "----" - -awk -F'\t' ' - { - base = $1 SUBSEP $2 - if ($3 == "dtest2") { - s2[base] += $5 - c2[base]++ - } else if ($3 == "dtest1") { - s1[base] += $5 - c1[base]++ +if [[ -n "${DTEST2:-}" ]]; then + echo + echo "Summary (dtest1 vs dtest2, user time)" + echo "=====================================" + printf "%-6s %-12s %10s %10s %10s %s\n" \ + "solver" "file" "dtest2_user" "dtest1_user" "speedup" "note" + printf "%-6s %-12s %10s %10s %10s %s\n" \ + "------" "------------" "----------" "----------" "----------" "----" + + awk -F'\t' ' + { + base = $1 SUBSEP $2 + if ($3 == "dtest2") { + s2[base] += $5 + c2[base]++ + } else if ($3 == "dtest1") { + s1[base] += $5 + c1[base]++ + } } - } - END { - split("calc solve", solvers, " ") - split("list1.txt list10.txt list100.txt list1000.txt", files, " ") - - for (si = 1; si <= 2; si++) { - for (fi = 1; fi <= 4; fi++) { - base = solvers[si] SUBSEP files[fi] - if (!(base in c2) || !(base in c1)) continue - u2 = s2[base] / c2[base] - u1 = s1[base] / c1[base] - speedup = (u1 > 0) ? u2 / u1 : 0 - note = (speedup >= 1) ? "dtest1 faster" : "dtest2 faster" - printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", - solvers[si], files[fi], u2, u1, speedup, note + END { + split("calc solve", solvers, " ") + split("list1.txt list10.txt list100.txt list1000.txt", files, " ") + + for (si = 1; si <= 2; si++) { + for (fi = 1; fi <= 4; fi++) { + base = solvers[si] SUBSEP files[fi] + if (!(base in c2) || !(base in c1)) continue + u2 = s2[base] / c2[base] + u1 = s1[base] / c1[base] + speedup = (u1 > 0) ? u2 / u1 : 0 + note = (speedup >= 1) ? "dtest1 faster" : "dtest2 faster" + printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", + solvers[si], files[fi], u2, u1, speedup, note + } } } - } -' "$RESULTS" + ' "$RESULTS" +fi echo echo "Completed $run_no runs ($total_runs expected)." From 1aaf69562135d641b3fccc8d0897dc4a1f86a8e2 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 09:33:50 -0400 Subject: [PATCH 095/157] Added max-deals parameter --- benchmark.sh | 78 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 918903d7..693a585e 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -15,6 +15,7 @@ # DTEST2 Optional second dtest for comparison # HANDS_DIR Directory containing list*.txt files (default: ./hands) # REPEATS Runs per combination per binary (default: 1) +# MAX_DEALS Include listN.txt files where N <= this value (default: 100) # DRY_RUN If 1, print commands only set -euo pipefail @@ -23,11 +24,10 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DTEST1="${DTEST1:-$ROOT/bazel-bin/library/tests/dtest}" HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" REPEATS="${REPEATS:-1}" +MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" SOLVERS=(calc solve) -FILES=(list1.txt list10.txt list100.txt) -# FILES=(list1.txt list10.txt list100.txt list1000.txt) usage() { cat <&2 usage >&2 @@ -81,6 +88,51 @@ while [[ $# -gt 0 ]]; do esac done +if ! [[ "$MAX_DEALS" =~ ^[0-9]+$ ]] || (( MAX_DEALS < 1 )); then + echo "error: max_deals must be a positive integer (got: $MAX_DEALS)" >&2 + exit 1 +fi + +select_hand_files() { + is_power_of_10() { + local n="$1" + (( n >= 1 )) || return 1 + while (( n > 1 )); do + (( n % 10 == 0 )) || return 1 + n=$(( n / 10 )) + done + return 0 + } + + local -a candidates=() + local path base count + + shopt -s nullglob + for path in "$HANDS_DIR"/list*.txt; do + base="${path##*/}" + if [[ "$base" =~ ^list([0-9]+)\.txt$ ]]; then + count="${BASH_REMATCH[1]}" + if is_power_of_10 "$count" && (( count <= MAX_DEALS )); then + candidates+=("${count}:${base}") + fi + fi + done + shopt -u nullglob + + if ((${#candidates[@]} == 0)); then + echo "error: no list10^n.txt files with n <= $MAX_DEALS in $HANDS_DIR" >&2 + exit 1 + fi + + FILES=() + local item + while IFS= read -r item; do + FILES+=("${item#*:}") + done < <(printf '%s\n' "${candidates[@]}" | sort -t: -k1,1n) +} + +select_hand_files + if [[ ! -x "$DTEST1" ]]; then echo "error: dtest1 not found or not executable: $DTEST1" >&2 echo "hint: bazel build //library/tests:dtest" >&2 @@ -156,6 +208,8 @@ if [[ -n "${DTEST2:-}" ]]; then echo "dtest2: $DTEST2" fi echo "hands dir: $HANDS_DIR" +echo "max_deals: $MAX_DEALS" +echo "files: ${FILES[*]}" echo "branch: $branch" echo "repeats: $REPEATS" echo @@ -205,7 +259,7 @@ if [[ -n "${DTEST2:-}" ]]; then printf "%-6s %-12s %10s %10s %10s %s\n" \ "------" "------------" "----------" "----------" "----------" "----" - awk -F'\t' ' + awk -F'\t' -v files="${FILES[*]}" ' { base = $1 SUBSEP $2 if ($3 == "dtest2") { @@ -218,18 +272,18 @@ if [[ -n "${DTEST2:-}" ]]; then } END { split("calc solve", solvers, " ") - split("list1.txt list10.txt list100.txt list1000.txt", files, " ") + nfiles = split(files, filearr, " ") for (si = 1; si <= 2; si++) { - for (fi = 1; fi <= 4; fi++) { - base = solvers[si] SUBSEP files[fi] + for (fi = 1; fi <= nfiles; fi++) { + base = solvers[si] SUBSEP filearr[fi] if (!(base in c2) || !(base in c1)) continue u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] speedup = (u1 > 0) ? u2 / u1 : 0 note = (speedup >= 1) ? "dtest1 faster" : "dtest2 faster" printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", - solvers[si], files[fi], u2, u1, speedup, note + solvers[si], filearr[fi], u2, u1, speedup, note } } } From ac460e12364f78118fdc1dc51598728fbc9725fc Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 09:05:18 -0400 Subject: [PATCH 096/157] Add --build option to benchmark.sh for dtest. Run bazel build //library/tests:dtest before benchmarking when --build is passed; DRY_RUN prints the build command without executing it. Co-authored-by: Cursor --- benchmark.sh | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 693a585e..abc5618c 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -2,12 +2,13 @@ # Benchmark dtest performance on one or two binaries. # # Runs all combinations of solver (calc, solve) and hand file -# (list1/10/100/1000), then prints per-run timings and an optional summary. +# (list1/10/100/1000/10000), then prints per-run timings and an optional summary. # Does not pass -n to dtest (library default thread count). # # Usage: # ./benchmark.sh -# ./benchmark.sh --dtest2 /path/to/other/dtest +# ./benchmark.sh --build +# ./benchmark.sh --build --dtest2 /path/to/other/dtest # REPEATS=3 ./benchmark.sh # # Environment: @@ -26,6 +27,7 @@ HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" +BUILD=0 SOLVERS=(calc solve) @@ -40,6 +42,7 @@ Options: -n REPEATS Runs per combination per binary (default: $REPEATS) --max-deals N Include list10^n.txt files with 10^n <= N (default: $MAX_DEALS) (alias: --max_deals) + --build Run bazel build //library/tests:dtest before benchmarking --dtest1 PATH First dtest binary (default: $DTEST1) --dtest2 PATH Optional second dtest binary for comparison @@ -48,6 +51,7 @@ Environment: Examples: ./benchmark.sh + ./benchmark.sh --build ./benchmark.sh --dtest2 /path/to/dtest ./benchmark.sh -n 5 --dtest2 /path/to/dtest DRY_RUN=1 ./benchmark.sh @@ -80,6 +84,10 @@ while [[ $# -gt 0 ]]; do MAX_DEALS="${1:?missing value for --max-deals}" shift ;; + --build) + BUILD=1 + shift + ;; *) echo "Unknown option: $1" >&2 usage >&2 @@ -133,6 +141,15 @@ select_hand_files() { select_hand_files +if [[ "$BUILD" == "1" ]]; then + if [[ "$DRY_RUN" == "1" ]]; then + echo "DRY_RUN: (cd $ROOT && bazel build //library/tests:dtest)" >&2 + else + echo "Building //library/tests:dtest..." >&2 + (cd "$ROOT" && bazel build //library/tests:dtest) + fi +fi + if [[ ! -x "$DTEST1" ]]; then echo "error: dtest1 not found or not executable: $DTEST1" >&2 echo "hint: bazel build //library/tests:dtest" >&2 From 45a34f525b082131d8f5a5eee5cf607613f14956 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 09:44:57 -0400 Subject: [PATCH 097/157] Pass dtest options via -- in benchmark.sh. Arguments after -- are forwarded to every dtest invocation (e.g. thread count and -r); benchmark -n remains the repeat count before --. Co-authored-by: Cursor --- benchmark.sh | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index abc5618c..3420c256 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -3,12 +3,14 @@ # # Runs all combinations of solver (calc, solve) and hand file # (list1/10/100/1000/10000), then prints per-run timings and an optional summary. -# Does not pass -n to dtest (library default thread count). +# Does not pass dtest options unless given after "--" (see below). # # Usage: # ./benchmark.sh # ./benchmark.sh --build +# ./benchmark.sh -- -n 8 -r # ./benchmark.sh --build --dtest2 /path/to/other/dtest +# ./benchmark.sh -n 5 -- -n 4 # REPEATS=3 ./benchmark.sh # # Environment: @@ -28,6 +30,7 @@ REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" BUILD=0 +DTEST_EXTRA=() SOLVERS=(calc solve) @@ -45,6 +48,10 @@ Options: --build Run bazel build //library/tests:dtest before benchmarking --dtest1 PATH First dtest binary (default: $DTEST1) --dtest2 PATH Optional second dtest binary for comparison + -- End benchmark options; remaining args are passed to dtest + (e.g. -- -n 8 -r for 8 threads and slow-board report) + +Note: -n before -- sets benchmark repeat count; -n after -- sets dtest threads. Environment: DTEST1, DTEST2, HANDS_DIR, REPEATS, MAX_DEALS, DRY_RUN @@ -52,6 +59,8 @@ Environment: Examples: ./benchmark.sh ./benchmark.sh --build + ./benchmark.sh -- -n 8 + ./benchmark.sh -n 3 -- -n 4 -r ./benchmark.sh --dtest2 /path/to/dtest ./benchmark.sh -n 5 --dtest2 /path/to/dtest DRY_RUN=1 ./benchmark.sh @@ -88,6 +97,11 @@ while [[ $# -gt 0 ]]; do BUILD=1 shift ;; + --) + shift + DTEST_EXTRA=("$@") + break + ;; *) echo "Unknown option: $1" >&2 usage >&2 @@ -202,16 +216,20 @@ run_dtest() { local binary="$1" local solver="$2" local hands="$3" + local -a cmd=("$binary" -f "$hands" -s "$solver") + if ((${#DTEST_EXTRA[@]} > 0)); then + cmd+=("${DTEST_EXTRA[@]}") + fi if [[ "$DRY_RUN" == "1" ]]; then - echo "DRY_RUN: $binary -f $hands -s $solver" >&2 + echo "DRY_RUN: ${cmd[*]}" >&2 echo "0 0 0.00 0.00" return 0 fi local out - if ! out="$("$binary" -f "$hands" -s "$solver" 2>&1)"; then - echo "error: dtest failed: $binary -f $hands -s $solver" >&2 + if ! out="$("${cmd[@]}" 2>&1)"; then + echo "error: dtest failed: ${cmd[*]}" >&2 echo "$out" >&2 exit 1 fi @@ -229,6 +247,9 @@ echo "max_deals: $MAX_DEALS" echo "files: ${FILES[*]}" echo "branch: $branch" echo "repeats: $REPEATS" +if ((${#DTEST_EXTRA[@]} > 0)); then + echo "dtest args: ${DTEST_EXTRA[*]}" +fi echo printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ From 2baf190f82c64fa4de48fffeb5fdb54879ba12d1 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 09:52:08 -0400 Subject: [PATCH 098/157] Rename benchmark repeat flag to --repeats and improve dry-run output. Avoids clashing with dtest -n; DRY_RUN now prints commands only without fake timing rows or summary. Co-authored-by: Cursor --- benchmark.sh | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 3420c256..236583fd 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -10,7 +10,7 @@ # ./benchmark.sh --build # ./benchmark.sh -- -n 8 -r # ./benchmark.sh --build --dtest2 /path/to/other/dtest -# ./benchmark.sh -n 5 -- -n 4 +# ./benchmark.sh --repeats 5 -- -n 4 # REPEATS=3 ./benchmark.sh # # Environment: @@ -42,8 +42,8 @@ Benchmark dtest across solver/file combinations. With --dtest2, compare two bina Options: -h, --help Show this help - -n REPEATS Runs per combination per binary (default: $REPEATS) - --max-deals N Include list10^n.txt files with 10^n <= N (default: $MAX_DEALS) + --repeats N Runs per combination per binary (default: 1 or $REPEATS) + --max-deals N Include list10^n.txt files with 10^n <= N (default: 100 or $MAX_DEALS) (alias: --max_deals) --build Run bazel build //library/tests:dtest before benchmarking --dtest1 PATH First dtest binary (default: $DTEST1) @@ -51,8 +51,6 @@ Options: -- End benchmark options; remaining args are passed to dtest (e.g. -- -n 8 -r for 8 threads and slow-board report) -Note: -n before -- sets benchmark repeat count; -n after -- sets dtest threads. - Environment: DTEST1, DTEST2, HANDS_DIR, REPEATS, MAX_DEALS, DRY_RUN @@ -60,9 +58,9 @@ Examples: ./benchmark.sh ./benchmark.sh --build ./benchmark.sh -- -n 8 - ./benchmark.sh -n 3 -- -n 4 -r + ./benchmark.sh --repeats 3 -- -n 4 -r ./benchmark.sh --dtest2 /path/to/dtest - ./benchmark.sh -n 5 --dtest2 /path/to/dtest + ./benchmark.sh --repeats 5 --dtest2 /path/to/dtest DRY_RUN=1 ./benchmark.sh EOF } @@ -73,9 +71,9 @@ while [[ $# -gt 0 ]]; do usage exit 0 ;; - -n) + --repeats) shift - REPEATS="${1:?missing value for -n}" + REPEATS="${1:?missing value for --repeats}" shift ;; --dtest1) @@ -223,7 +221,6 @@ run_dtest() { if [[ "$DRY_RUN" == "1" ]]; then echo "DRY_RUN: ${cmd[*]}" >&2 - echo "0 0 0.00 0.00" return 0 fi @@ -252,10 +249,12 @@ if ((${#DTEST_EXTRA[@]} > 0)); then fi echo -printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ - "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" -printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ - "------" "------------" "----" "--------" "--------" "----------" "------" "---" +if [[ "$DRY_RUN" != "1" ]]; then + printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" + printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + "------" "------------" "----" "--------" "--------" "----------" "------" "---" +fi total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * num_bins * REPEATS )) run_no=0 @@ -269,6 +268,12 @@ for solver in "${SOLVERS[@]}"; do for (( rep = 1; rep <= REPEATS; rep++ )); do run_no=$((run_no + 1)) + + if [[ "$DRY_RUN" == "1" ]]; then + run_dtest "$bin" "$solver" "$hands" + continue + fi + if [[ "$REPEATS" -gt 1 ]]; then run_label="${rep}/${REPEATS}" else @@ -288,7 +293,7 @@ for solver in "${SOLVERS[@]}"; do done done -if [[ -n "${DTEST2:-}" ]]; then +if [[ -n "${DTEST2:-}" && "$DRY_RUN" != "1" ]]; then echo echo "Summary (dtest1 vs dtest2, user time)" echo "=====================================" @@ -329,4 +334,8 @@ if [[ -n "${DTEST2:-}" ]]; then fi echo -echo "Completed $run_no runs ($total_runs expected)." +if [[ "$DRY_RUN" == "1" ]]; then + echo "DRY_RUN: $total_runs dtest invocations (not run)." +else + echo "Completed $run_no runs ($total_runs expected)." +fi From 7b0acab75f927e47f7c41054882b1d05b130a646 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 09:58:06 -0400 Subject: [PATCH 099/157] Fix benchmark.sh help text and max-deals error message. Clarify default vs env overrides for --repeats and --max-deals; error now says 10^n <= N to match filtering. Co-authored-by: Cursor --- benchmark.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 236583fd..2d4778c3 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -42,8 +42,8 @@ Benchmark dtest across solver/file combinations. With --dtest2, compare two bina Options: -h, --help Show this help - --repeats N Runs per combination per binary (default: 1 or $REPEATS) - --max-deals N Include list10^n.txt files with 10^n <= N (default: 100 or $MAX_DEALS) + --repeats N Runs per combination per binary (default: 1; env: REPEATS) + --max-deals N Include list10^n.txt files with 10^n <= N (default: 100; env: MAX_DEALS) (alias: --max_deals) --build Run bazel build //library/tests:dtest before benchmarking --dtest1 PATH First dtest binary (default: $DTEST1) @@ -140,7 +140,7 @@ select_hand_files() { shopt -u nullglob if ((${#candidates[@]} == 0)); then - echo "error: no list10^n.txt files with n <= $MAX_DEALS in $HANDS_DIR" >&2 + echo "error: no list10^n.txt files with 10^n <= $MAX_DEALS in $HANDS_DIR" >&2 exit 1 fi From ce744131c3f27a87cb3d746dde2e35d71dfa63ba Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 13:57:30 -0400 Subject: [PATCH 100/157] Rename benchmark binaries to branch/compare and fix column alignment. Use --branch/--compare (and BRANCH/COMPARE env) instead of dtest1/dtest2; widen ver/file columns and align summary output. Co-authored-by: Cursor --- benchmark.sh | 90 ++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 2d4778c3..b7806101 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -9,13 +9,13 @@ # ./benchmark.sh # ./benchmark.sh --build # ./benchmark.sh -- -n 8 -r -# ./benchmark.sh --build --dtest2 /path/to/other/dtest +# ./benchmark.sh --build --compare /path/to/other/dtest # ./benchmark.sh --repeats 5 -- -n 4 # REPEATS=3 ./benchmark.sh # # Environment: -# DTEST1 Path to first dtest (default: bazel-bin in this repo) -# DTEST2 Optional second dtest for comparison +# BRANCH Path to branch dtest (default: bazel-bin in this repo) +# COMPARE Optional second dtest binary for comparison # HANDS_DIR Directory containing list*.txt files (default: ./hands) # REPEATS Runs per combination per binary (default: 1) # MAX_DEALS Include listN.txt files where N <= this value (default: 100) @@ -24,7 +24,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DTEST1="${DTEST1:-$ROOT/bazel-bin/library/tests/dtest}" +BRANCH="${BRANCH:-$ROOT/bazel-bin/library/tests/dtest}" HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" @@ -38,7 +38,7 @@ usage() { cat <&2 +if [[ ! -x "$BRANCH" ]]; then + echo "error: branch binary not found or not executable: $BRANCH" >&2 echo "hint: bazel build //library/tests:dtest" >&2 exit 1 fi -if [[ -n "${DTEST2:-}" && ! -x "$DTEST2" ]]; then - echo "error: dtest2 not found or not executable: $DTEST2" >&2 +if [[ -n "${COMPARE:-}" && ! -x "$COMPARE" ]]; then + echo "error: compare binary not found or not executable: $COMPARE" >&2 exit 1 fi -BIN_PAIRS=("dtest1:$DTEST1") -if [[ -n "${DTEST2:-}" ]]; then - BIN_PAIRS=("dtest2:$DTEST2" "dtest1:$DTEST1") +BIN_PAIRS=("branch:$BRANCH") +if [[ -n "${COMPARE:-}" ]]; then + BIN_PAIRS=("compare:$COMPARE" "branch:$BRANCH") fi num_bins=${#BIN_PAIRS[@]} @@ -235,25 +235,25 @@ run_dtest() { echo "DDS dtest benchmark" echo "===================" -echo "dtest1: $DTEST1" -if [[ -n "${DTEST2:-}" ]]; then - echo "dtest2: $DTEST2" +printf "%-12s %s\n" "branch:" "$BRANCH" +if [[ -n "${COMPARE:-}" ]]; then + printf "%-12s %s\n" "compare:" "$COMPARE" fi -echo "hands dir: $HANDS_DIR" -echo "max_deals: $MAX_DEALS" -echo "files: ${FILES[*]}" -echo "branch: $branch" -echo "repeats: $REPEATS" +printf "%-12s %s\n" "hands dir:" "$HANDS_DIR" +printf "%-12s %s\n" "max_deals:" "$MAX_DEALS" +printf "%-12s %s\n" "files:" "${FILES[*]}" +printf "%-12s %s\n" "git branch:" "$branch" +printf "%-12s %s\n" "repeats:" "$REPEATS" if ((${#DTEST_EXTRA[@]} > 0)); then - echo "dtest args: ${DTEST_EXTRA[*]}" + printf "%-12s %s\n" "dtest args:" "${DTEST_EXTRA[*]}" fi echo if [[ "$DRY_RUN" != "1" ]]; then - printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" - printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ - "------" "------------" "----" "--------" "--------" "----------" "------" "---" + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ + "------" "-------------" "-------" "--------" "--------" "----------" "------" "---" fi total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * num_bins * REPEATS )) @@ -282,7 +282,7 @@ for solver in "${SOLVERS[@]}"; do read -r user sys avg ratio < <(run_dtest "$bin" "$solver" "$hands") - printf "%-6s %-12s %4s %8s %8s %10s %6s %s\n" \ + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ @@ -293,22 +293,22 @@ for solver in "${SOLVERS[@]}"; do done done -if [[ -n "${DTEST2:-}" && "$DRY_RUN" != "1" ]]; then +if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo - echo "Summary (dtest1 vs dtest2, user time)" - echo "=====================================" - printf "%-6s %-12s %10s %10s %10s %s\n" \ - "solver" "file" "dtest2_user" "dtest1_user" "speedup" "note" - printf "%-6s %-12s %10s %10s %10s %s\n" \ - "------" "------------" "----------" "----------" "----------" "----" + echo "Summary (branch vs compare, user time)" + echo "======================================" + printf "%-6s %-13s %12s %12s %10s %s\n" \ + "solver" "file" "compare_user" "branch_user" "speedup" "note" + printf "%-6s %-13s %12s %12s %10s %s\n" \ + "------" "-------------" "------------" "------------" "----------" "----" awk -F'\t' -v files="${FILES[*]}" ' { base = $1 SUBSEP $2 - if ($3 == "dtest2") { + if ($3 == "compare") { s2[base] += $5 c2[base]++ - } else if ($3 == "dtest1") { + } else if ($3 == "branch") { s1[base] += $5 c1[base]++ } @@ -324,8 +324,8 @@ if [[ -n "${DTEST2:-}" && "$DRY_RUN" != "1" ]]; then u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] speedup = (u1 > 0) ? u2 / u1 : 0 - note = (speedup >= 1) ? "dtest1 faster" : "dtest2 faster" - printf "%-6s %-12s %10.1f %10.1f %9.2fx %s\n", + note = (speedup >= 1) ? "branch faster" : "compare faster" + printf "%-6s %-13s %12.1f %12.1f %10.2fx %s\n", solvers[si], filearr[fi], u2, u1, speedup, note } } From 0d8c1bd740061a51879b8e619fa61dfe33f871e4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 13:58:41 -0400 Subject: [PATCH 101/157] Align benchmark summary speedup and note columns. Format speedup as a fixed-width string so the trailing x stays in-column; give note a 15-char field. Co-authored-by: Cursor --- benchmark.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index b7806101..5ea5f568 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -297,10 +297,10 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo echo "Summary (branch vs compare, user time)" echo "======================================" - printf "%-6s %-13s %12s %12s %10s %s\n" \ + printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "solver" "file" "compare_user" "branch_user" "speedup" "note" - printf "%-6s %-13s %12s %12s %10s %s\n" \ - "------" "-------------" "------------" "------------" "----------" "----" + printf "%-6s %-13s %12s %12s %10s %-15s\n" \ + "------" "-------------" "------------" "------------" "----------" "---------------" awk -F'\t' -v files="${FILES[*]}" ' { @@ -325,8 +325,9 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then u1 = s1[base] / c1[base] speedup = (u1 > 0) ? u2 / u1 : 0 note = (speedup >= 1) ? "branch faster" : "compare faster" - printf "%-6s %-13s %12.1f %12.1f %10.2fx %s\n", - solvers[si], filearr[fi], u2, u1, speedup, note + sp = sprintf("%9.2fx", speedup) + printf "%-6s %-13s %12.1f %12.1f %10s %-15s\n", + solvers[si], filearr[fi], u2, u1, sp, note } } } From b8d19b14e51c11cf81876312a57bda71e3090ee4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 14:09:15 -0400 Subject: [PATCH 102/157] Harden benchmark.sh validation, parsing, and compare summary. Validate repeats, skip binary checks in dry-run, run branch before compare, and tighten dtest timing parse so the cmp/branch summary is easier to read. Co-authored-by: Cursor --- benchmark.sh | 62 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 5ea5f568..c0c3714e 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -18,7 +18,7 @@ # COMPARE Optional second dtest binary for comparison # HANDS_DIR Directory containing list*.txt files (default: ./hands) # REPEATS Runs per combination per binary (default: 1) -# MAX_DEALS Include listN.txt files where N <= this value (default: 100) +# MAX_DEALS Include list10^n.txt files with 10^n <= N (default: 100) # DRY_RUN If 1, print commands only set -euo pipefail @@ -45,7 +45,7 @@ Options: --repeats N Runs per combination per binary (default: 1; env: REPEATS) --max-deals N Include list10^n.txt files with 10^n <= N (default: 100; env: MAX_DEALS) (alias: --max_deals) - --build Run bazel build //library/tests:dtest before benchmarking + --build Build branch dtest only (bazel build //library/tests:dtest) --branch PATH Branch dtest binary (default: $BRANCH) --compare PATH Optional second dtest binary for comparison -- End benchmark options; remaining args are passed to dtest @@ -113,6 +113,11 @@ if ! [[ "$MAX_DEALS" =~ ^[0-9]+$ ]] || (( MAX_DEALS < 1 )); then exit 1 fi +if ! [[ "$REPEATS" =~ ^[0-9]+$ ]] || (( REPEATS < 1 )); then + echo "error: repeats must be a positive integer (got: $REPEATS)" >&2 + exit 1 +fi + select_hand_files() { is_power_of_10() { local n="$1" @@ -162,20 +167,22 @@ if [[ "$BUILD" == "1" ]]; then fi fi -if [[ ! -x "$BRANCH" ]]; then - echo "error: branch binary not found or not executable: $BRANCH" >&2 - echo "hint: bazel build //library/tests:dtest" >&2 - exit 1 -fi +if [[ "$DRY_RUN" != "1" ]]; then + if [[ ! -x "$BRANCH" ]]; then + echo "error: branch binary not found or not executable: $BRANCH" >&2 + echo "hint: bazel build //library/tests:dtest" >&2 + exit 1 + fi -if [[ -n "${COMPARE:-}" && ! -x "$COMPARE" ]]; then - echo "error: compare binary not found or not executable: $COMPARE" >&2 - exit 1 + if [[ -n "${COMPARE:-}" && ! -x "$COMPARE" ]]; then + echo "error: compare binary not found or not executable: $COMPARE" >&2 + exit 1 + fi fi BIN_PAIRS=("branch:$BRANCH") if [[ -n "${COMPARE:-}" ]]; then - BIN_PAIRS=("compare:$COMPARE" "branch:$BRANCH") + BIN_PAIRS=("branch:$BRANCH" "compare:$COMPARE") fi num_bins=${#BIN_PAIRS[@]} @@ -186,9 +193,9 @@ for f in "${FILES[@]}"; do fi done -branch="unknown" +git_branch="unknown" if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" + git_branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" fi RESULTS="$(mktemp)" @@ -196,10 +203,10 @@ trap 'rm -f "$RESULTS"' EXIT parse_dtest_output() { awk ' - /User time \(ms\)/ { user = $4 } - /Sys time \(ms\)/ { sys = $4 } - /Avg user time \(ms\)/ { avg = $5 } - /Ratio/ { ratio = $2 } + /^User time \(ms\)/ { user = $NF } + /^Sys time \(ms\)/ { sys = $NF } + /^Avg user time \(ms\)/ { avg = $NF } + /^Ratio[[:space:]]/ { ratio = $NF } END { if (user == "") user = "NA" if (sys == "") sys = "NA" @@ -230,7 +237,12 @@ run_dtest() { echo "$out" >&2 exit 1 fi - parse_dtest_output <<<"$out" + local parsed + parsed="$(parse_dtest_output <<<"$out")" + if [[ "$parsed" == *"NA"* ]]; then + echo "warning: incomplete dtest timing output: ${cmd[*]}" >&2 + fi + echo "$parsed" } echo "DDS dtest benchmark" @@ -242,7 +254,7 @@ fi printf "%-12s %s\n" "hands dir:" "$HANDS_DIR" printf "%-12s %s\n" "max_deals:" "$MAX_DEALS" printf "%-12s %s\n" "files:" "${FILES[*]}" -printf "%-12s %s\n" "git branch:" "$branch" +printf "%-12s %s\n" "git branch:" "$git_branch" printf "%-12s %s\n" "repeats:" "$REPEATS" if ((${#DTEST_EXTRA[@]} > 0)); then printf "%-12s %s\n" "dtest args:" "${DTEST_EXTRA[*]}" @@ -295,10 +307,10 @@ done if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo - echo "Summary (branch vs compare, user time)" - echo "======================================" + echo "Summary (branch vs compare, total user ms; cmp/branch > 1 => branch faster)" + echo "==============================================================================" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ - "solver" "file" "compare_user" "branch_user" "speedup" "note" + "solver" "file" "compare_user" "branch_user" "cmp/branch" "note" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "------" "-------------" "------------" "------------" "----------" "---------------" @@ -323,9 +335,9 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then if (!(base in c2) || !(base in c1)) continue u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] - speedup = (u1 > 0) ? u2 / u1 : 0 - note = (speedup >= 1) ? "branch faster" : "compare faster" - sp = sprintf("%9.2fx", speedup) + cmp_branch = (u1 > 0) ? u2 / u1 : 0 + note = (cmp_branch >= 1) ? "branch faster" : "compare faster" + sp = sprintf("%9.2fx", cmp_branch) printf "%-6s %-13s %12.1f %12.1f %10s %-15s\n", solvers[si], filearr[fi], u2, u1, sp, note } From 650b7f22ab2b72b16a218ee1c98fe8821f0795c8 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 15:46:58 -0400 Subject: [PATCH 103/157] Address PR review comments in benchmark.sh. Use a portable mktemp template, normalize dtest "zero" timings, warn only when user/sys are missing, and label the compare summary as avg user ms. Co-authored-by: Cursor --- benchmark.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index c0c3714e..3d79a811 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -198,14 +198,14 @@ if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then git_branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" fi -RESULTS="$(mktemp)" +RESULTS="$(mktemp "${TMPDIR:-/tmp}/dds-benchmark.XXXXXX")" trap 'rm -f "$RESULTS"' EXIT parse_dtest_output() { awk ' - /^User time \(ms\)/ { user = $NF } - /^Sys time \(ms\)/ { sys = $NF } - /^Avg user time \(ms\)/ { avg = $NF } + /^User time \(ms\)/ { user = ($NF == "zero" ? 0 : $NF) } + /^Sys time \(ms\)/ { sys = ($NF == "zero" ? 0 : $NF) } + /^Avg user time \(ms\)/ { avg = ($NF == "zero" ? 0 : $NF) } /^Ratio[[:space:]]/ { ratio = $NF } END { if (user == "") user = "NA" @@ -239,7 +239,9 @@ run_dtest() { fi local parsed parsed="$(parse_dtest_output <<<"$out")" - if [[ "$parsed" == *"NA"* ]]; then + local parsed_user parsed_sys + read -r parsed_user parsed_sys _ _ <<<"$parsed" + if [[ "$parsed_user" == "NA" || "$parsed_sys" == "NA" ]]; then echo "warning: incomplete dtest timing output: ${cmd[*]}" >&2 fi echo "$parsed" @@ -307,7 +309,7 @@ done if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo - echo "Summary (branch vs compare, total user ms; cmp/branch > 1 => branch faster)" + echo "Summary (branch vs compare, avg user ms; cmp/branch > 1 => branch faster)" echo "==============================================================================" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "solver" "file" "compare_user" "branch_user" "cmp/branch" "note" From fe17f7bb65f76c9ae328737d786ed4ef847739d4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 19 Jun 2026 16:41:12 -0400 Subject: [PATCH 104/157] Improve benchmark run order, summary metrics, and compare sequencing. Run solve before calc and largest hand files first to reduce warmup bias, aggregate summary on avg_user per hand, and add --reverse to run compare before branch when comparing two binaries. Co-authored-by: Cursor --- benchmark.sh | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 3d79a811..5cd85d04 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Benchmark dtest performance on one or two binaries. # -# Runs all combinations of solver (calc, solve) and hand file -# (list1/10/100/1000/10000), then prints per-run timings and an optional summary. +# Runs all combinations of solver (solve, calc) and hand file +# (list100/1000/…/1), largest files first, then prints per-run timings and an optional summary. # Does not pass dtest options unless given after "--" (see below). # # Usage: @@ -30,9 +30,10 @@ REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" BUILD=0 +REVERSE=0 DTEST_EXTRA=() -SOLVERS=(calc solve) +SOLVERS=(solve calc) usage() { cat <&2 + exit 1 +fi + select_hand_files() { is_power_of_10() { local n="$1" @@ -153,7 +165,7 @@ select_hand_files() { local item while IFS= read -r item; do FILES+=("${item#*:}") - done < <(printf '%s\n' "${candidates[@]}" | sort -t: -k1,1n) + done < <(printf '%s\n' "${candidates[@]}" | sort -t: -k1,1rn) } select_hand_files @@ -182,7 +194,11 @@ fi BIN_PAIRS=("branch:$BRANCH") if [[ -n "${COMPARE:-}" ]]; then - BIN_PAIRS=("branch:$BRANCH" "compare:$COMPARE") + if [[ "$REVERSE" == "1" ]]; then + BIN_PAIRS=("compare:$COMPARE" "branch:$BRANCH") + else + BIN_PAIRS=("branch:$BRANCH" "compare:$COMPARE") + fi fi num_bins=${#BIN_PAIRS[@]} @@ -252,6 +268,11 @@ echo "===================" printf "%-12s %s\n" "branch:" "$BRANCH" if [[ -n "${COMPARE:-}" ]]; then printf "%-12s %s\n" "compare:" "$COMPARE" + if [[ "$REVERSE" == "1" ]]; then + printf "%-12s %s\n" "run order:" "compare, branch" + else + printf "%-12s %s\n" "run order:" "branch, compare" + fi fi printf "%-12s %s\n" "hands dir:" "$HANDS_DIR" printf "%-12s %s\n" "max_deals:" "$MAX_DEALS" @@ -312,7 +333,7 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo "Summary (branch vs compare, avg user ms; cmp/branch > 1 => branch faster)" echo "==============================================================================" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ - "solver" "file" "compare_user" "branch_user" "cmp/branch" "note" + "solver" "file" "compare_avg" "branch_avg" "cmp/branch" "note" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "------" "-------------" "------------" "------------" "----------" "---------------" @@ -320,15 +341,15 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then { base = $1 SUBSEP $2 if ($3 == "compare") { - s2[base] += $5 + s2[base] += $7 c2[base]++ } else if ($3 == "branch") { - s1[base] += $5 + s1[base] += $7 c1[base]++ } } END { - split("calc solve", solvers, " ") + split("solve calc", solvers, " ") nfiles = split(files, filearr, " ") for (si = 1; si <= 2; si++) { @@ -340,7 +361,7 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then cmp_branch = (u1 > 0) ? u2 / u1 : 0 note = (cmp_branch >= 1) ? "branch faster" : "compare faster" sp = sprintf("%9.2fx", cmp_branch) - printf "%-6s %-13s %12.1f %12.1f %10s %-15s\n", + printf "%-6s %-13s %12.2f %12.2f %10s %-15s\n", solvers[si], filearr[fi], u2, u1, sp, note } } From 4227f4084662904d1bce3f91ef53fca2d3e08a6c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 10:15:35 -0400 Subject: [PATCH 105/157] Derive avg_user in benchmark when dtest omits the avg line. Parse Number of hands and compute user/hands when Avg user time is missing (e.g. zero user time), replacing the broken Copilot autofix. Co-authored-by: Cursor --- benchmark.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index 5cd85d04..d5817247 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -219,6 +219,7 @@ trap 'rm -f "$RESULTS"' EXIT parse_dtest_output() { awk ' + /^Number of hands/ { hands = $NF } /^User time \(ms\)/ { user = ($NF == "zero" ? 0 : $NF) } /^Sys time \(ms\)/ { sys = ($NF == "zero" ? 0 : $NF) } /^Avg user time \(ms\)/ { avg = ($NF == "zero" ? 0 : $NF) } @@ -226,7 +227,11 @@ parse_dtest_output() { END { if (user == "") user = "NA" if (sys == "") sys = "NA" - if (avg == "") avg = "NA" + if (avg == "") { + if (user == 0) avg = 0 + else if (hands != "" && user != "NA" && hands > 0) avg = user / hands + else avg = "NA" + } if (ratio == "") ratio = "NA" print user, sys, avg, ratio } From 6691f546cd74aa8081b9f5210c61ff7765ebeeee Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 11:58:41 -0400 Subject: [PATCH 106/157] Fix benchmark summary ratio when branch_avg is zero. Show n/a/equal for 0/0, inf/branch faster when compare is nonzero and branch is zero, and keep 0.00x/compare faster when only compare is zero. Co-authored-by: Cursor --- benchmark.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index d5817247..01f3d226 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -363,9 +363,18 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then if (!(base in c2) || !(base in c1)) continue u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] - cmp_branch = (u1 > 0) ? u2 / u1 : 0 - note = (cmp_branch >= 1) ? "branch faster" : "compare faster" - sp = sprintf("%9.2fx", cmp_branch) + if (u1 == 0 && u2 == 0) { + sp = " n/a" + note = "equal" + } else if (u1 == 0) { + sp = " inf" + note = "branch faster" + } else { + cmp_branch = u2 / u1 + if (cmp_branch >= 1) note = "branch faster" + else note = "compare faster" + sp = sprintf("%9.2fx", cmp_branch) + } printf "%-6s %-13s %12.2f %12.2f %10s %-15s\n", solvers[si], filearr[fi], u2, u1, sp, note } From d173b08e1cc01d427e6d875c3b3b93c4751d72d9 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 12:01:35 -0400 Subject: [PATCH 107/157] Interleave branch and compare runs per repeat in benchmark. Run branch then compare (or reverse) for each repeat before advancing, so paired timings see the same CPU warmth instead of all branch runs first. Co-authored-by: Cursor --- benchmark.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 01f3d226..4529e298 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -49,7 +49,7 @@ Options: --build Build branch dtest only (bazel build //library/tests:dtest) --branch PATH Branch dtest binary (default: $BRANCH) --compare PATH Optional second dtest binary for comparison - --reverse With --compare, run compare before branch (default: branch first) + --reverse With --compare, run compare before branch each repeat (default: branch first) -- End benchmark options; remaining args are passed to dtest (e.g. -- -n 8 -r for 8 threads and slow-board report) @@ -274,9 +274,9 @@ printf "%-12s %s\n" "branch:" "$BRANCH" if [[ -n "${COMPARE:-}" ]]; then printf "%-12s %s\n" "compare:" "$COMPARE" if [[ "$REVERSE" == "1" ]]; then - printf "%-12s %s\n" "run order:" "compare, branch" + printf "%-12s %s\n" "run order:" "interleaved compare, branch" else - printf "%-12s %s\n" "run order:" "branch, compare" + printf "%-12s %s\n" "run order:" "interleaved branch, compare" fi fi printf "%-12s %s\n" "hands dir:" "$HANDS_DIR" @@ -302,11 +302,17 @@ run_no=0 for solver in "${SOLVERS[@]}"; do for file in "${FILES[@]}"; do hands="$HANDS_DIR/$file" - for pair in "${BIN_PAIRS[@]}"; do - ver="${pair%%:*}" - bin="${pair#*:}" - for (( rep = 1; rep <= REPEATS; rep++ )); do + for (( rep = 1; rep <= REPEATS; rep++ )); do + if [[ "$REPEATS" -gt 1 ]]; then + run_label="${rep}/${REPEATS}" + else + run_label="1/1" + fi + + for pair in "${BIN_PAIRS[@]}"; do + ver="${pair%%:*}" + bin="${pair#*:}" run_no=$((run_no + 1)) if [[ "$DRY_RUN" == "1" ]]; then @@ -314,12 +320,6 @@ for solver in "${SOLVERS[@]}"; do continue fi - if [[ "$REPEATS" -gt 1 ]]; then - run_label="${rep}/${REPEATS}" - else - run_label="1/1" - fi - read -r user sys avg ratio < <(run_dtest "$bin" "$solver" "$hands") printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ From bc93827123fe97e8d234e5e45ef8a4dc606b82ef Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 15:11:14 -0400 Subject: [PATCH 108/157] Revert "Fix benchmark summary ratio when branch_avg is zero." This reverts commit 5626ea7e182858d09c3b5d4582cb54df713e854a. --- benchmark.sh | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 4529e298..0d97023a 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -363,18 +363,9 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then if (!(base in c2) || !(base in c1)) continue u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] - if (u1 == 0 && u2 == 0) { - sp = " n/a" - note = "equal" - } else if (u1 == 0) { - sp = " inf" - note = "branch faster" - } else { - cmp_branch = u2 / u1 - if (cmp_branch >= 1) note = "branch faster" - else note = "compare faster" - sp = sprintf("%9.2fx", cmp_branch) - } + cmp_branch = (u1 > 0) ? u2 / u1 : 0 + note = (cmp_branch >= 1) ? "branch faster" : "compare faster" + sp = sprintf("%9.2fx", cmp_branch) printf "%-6s %-13s %12.2f %12.2f %10s %-15s\n", solvers[si], filearr[fi], u2, u1, sp, note } From f487aa59d2751f7495f4c8d12a787c4231e77758 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 15:20:30 -0400 Subject: [PATCH 109/157] Simplify benchmark cmp/branch ratio when averages are positive. Assume summary avg_user values are always positive and compute u2/u1 directly instead of guarding a zero branch average. Co-authored-by: Cursor --- benchmark.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index 0d97023a..a73f90d1 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -361,9 +361,10 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then for (fi = 1; fi <= nfiles; fi++) { base = solvers[si] SUBSEP filearr[fi] if (!(base in c2) || !(base in c1)) continue + # Every member of s1, c1, s2, and c2 will be positive. u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] - cmp_branch = (u1 > 0) ? u2 / u1 : 0 + cmp_branch = u2 / u1 note = (cmp_branch >= 1) ? "branch faster" : "compare faster" sp = sprintf("%9.2fx", cmp_branch) printf "%-6s %-13s %12.2f %12.2f %10s %-15s\n", From fcf7fb89644c4b6448d5dd068f5996ec4ca0d807 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 15:22:26 -0400 Subject: [PATCH 110/157] Show summary only by default when benchmarking with --compare. Add --details to opt into per-run timing rows; branch-only runs still print the full table as before. Co-authored-by: Cursor --- benchmark.sh | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index a73f90d1..e6307bd5 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -2,7 +2,8 @@ # Benchmark dtest performance on one or two binaries. # # Runs all combinations of solver (solve, calc) and hand file -# (list100/1000/…/1), largest files first, then prints per-run timings and an optional summary. +# (list100/1000/…/1), largest files first. With --compare, prints summary only +# unless --details; without --compare, prints per-run rows. # Does not pass dtest options unless given after "--" (see below). # # Usage: @@ -20,6 +21,7 @@ # REPEATS Runs per combination per binary (default: 1) # MAX_DEALS Include list10^n.txt files with 10^n <= N (default: 100) # DRY_RUN If 1, print commands only +# DETAILS If 1 with --compare, print per-run timing rows (default: summary only) set -euo pipefail @@ -29,6 +31,7 @@ HANDS_DIR="${HANDS_DIR:-$ROOT/hands}" REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" +DETAILS="${DETAILS:-0}" BUILD=0 REVERSE=0 DTEST_EXTRA=() @@ -39,7 +42,8 @@ usage() { cat < 0)); then fi echo -if [[ "$DRY_RUN" != "1" ]]; then +show_run_lines=1 +if [[ -n "${COMPARE:-}" && "$DETAILS" != "1" ]]; then + show_run_lines=0 +fi + +if [[ "$DRY_RUN" != "1" && "$show_run_lines" == "1" ]]; then printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ @@ -322,8 +342,10 @@ for solver in "${SOLVERS[@]}"; do read -r user sys avg ratio < <(run_dtest "$bin" "$solver" "$hands") - printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ - "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" + if [[ "$show_run_lines" == "1" ]]; then + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ + "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" + fi printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ "$solver" "$file" "$ver" "$rep" "$user" "$sys" "$avg" "$ratio" \ From 73462d440decc7142b1606ef7d7c947722a9645e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 16:10:26 -0400 Subject: [PATCH 111/157] Show transient per-run progress during compare benchmarks. On a tty, print per-run timing rows while --compare runs then erase them (including the table header) before the summary; use --details to keep rows. Co-authored-by: Cursor --- benchmark.sh | 64 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index e6307bd5..c36e5f6b 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -3,7 +3,8 @@ # # Runs all combinations of solver (solve, calc) and hand file # (list100/1000/…/1), largest files first. With --compare, prints summary only -# unless --details; without --compare, prints per-run rows. +# unless --details; without --compare, prints per-run rows. With --compare and a +# tty, per-run rows appear during the run then are cleared before the summary. # Does not pass dtest options unless given after "--" (see below). # # Usage: @@ -21,7 +22,7 @@ # REPEATS Runs per combination per binary (default: 1) # MAX_DEALS Include list10^n.txt files with 10^n <= N (default: 100) # DRY_RUN If 1, print commands only -# DETAILS If 1 with --compare, print per-run timing rows (default: summary only) +# DETAILS If 1 with --compare, keep per-run rows in output (default: transient on tty) set -euo pipefail @@ -52,8 +53,8 @@ Options: (alias: --max_deals) --build Build branch dtest only (bazel build //library/tests:dtest) --branch PATH Branch dtest binary (default: $BRANCH) - --compare PATH Optional second dtest binary for comparison (summary only) - --details With --compare, also print per-run timing rows + --compare PATH Optional second dtest binary (summary; transient progress on tty) + --details With --compare, keep per-run timing rows in final output --reverse With --compare, run compare before branch each repeat (default: branch first) -- End benchmark options; remaining args are passed to dtest (e.g. -- -n 8 -r for 8 threads and slow-board report) @@ -278,6 +279,41 @@ run_dtest() { echo "$parsed" } +progress_lines=0 +TRANSIENT_PROGRESS=0 +show_run_lines=1 + +print_run_table_header() { + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ + "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ + "------" "-------------" "-------" "--------" "--------" "----------" "------" "---" + if [[ "$TRANSIENT_PROGRESS" == "1" ]]; then + progress_lines=$((progress_lines + 2)) + fi +} + +print_run_row() { + printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ + "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" + if [[ "$TRANSIENT_PROGRESS" == "1" ]]; then + progress_lines=$((progress_lines + 1)) + fi +} + +clear_transient_progress() { + if [[ "$TRANSIENT_PROGRESS" != "1" || progress_lines -le 0 ]]; then + return + fi + # Cursor rests on a blank line below the last row; erase it too. + local lines_to_clear=$((progress_lines + 1)) + local i + for (( i = 0; i < lines_to_clear; i++ )); do + printf '\033[2K\033[1A' + done + progress_lines=0 +} + echo "DDS dtest benchmark" echo "===================" printf "%-12s %s\n" "branch:" "$BRANCH" @@ -285,6 +321,8 @@ if [[ -n "${COMPARE:-}" ]]; then printf "%-12s %s\n" "compare:" "$COMPARE" if [[ "$DETAILS" == "1" ]]; then printf "%-12s %s\n" "details:" "on" + elif [[ -t 1 ]]; then + printf "%-12s %s\n" "details:" "transient (cleared before summary)" else printf "%-12s %s\n" "details:" "off (summary only)" fi @@ -305,15 +343,16 @@ fi echo show_run_lines=1 +TRANSIENT_PROGRESS=0 if [[ -n "${COMPARE:-}" && "$DETAILS" != "1" ]]; then show_run_lines=0 + if [[ -t 1 ]]; then + TRANSIENT_PROGRESS=1 + fi fi -if [[ "$DRY_RUN" != "1" && "$show_run_lines" == "1" ]]; then - printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ - "solver" "file" "ver" "user_ms" "sys_ms" "avg_user" "ratio" "run" - printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ - "------" "-------------" "-------" "--------" "--------" "----------" "------" "---" +if [[ "$DRY_RUN" != "1" && ( "$show_run_lines" == "1" || "$TRANSIENT_PROGRESS" == "1" ) ]]; then + print_run_table_header fi total_runs=$(( ${#SOLVERS[@]} * ${#FILES[@]} * num_bins * REPEATS )) @@ -342,9 +381,8 @@ for solver in "${SOLVERS[@]}"; do read -r user sys avg ratio < <(run_dtest "$bin" "$solver" "$hands") - if [[ "$show_run_lines" == "1" ]]; then - printf "%-6s %-13s %7s %8s %8s %10s %6s %s\n" \ - "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" + if [[ "$show_run_lines" == "1" || "$TRANSIENT_PROGRESS" == "1" ]]; then + print_run_row "$solver" "$file" "$ver" "$user" "$sys" "$avg" "$ratio" "$run_label" fi printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ @@ -355,6 +393,8 @@ for solver in "${SOLVERS[@]}"; do done done +clear_transient_progress + if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo echo "Summary (branch vs compare, avg user ms; cmp/branch > 1 => branch faster)" From 90dec47bed947116592838d1ac07cabf80f85d3d Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 16:12:10 -0400 Subject: [PATCH 112/157] Drop cmp/branch interpretation from benchmark summary header. The ratio column is self-explanatory without the inline note. Co-authored-by: Cursor --- benchmark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index c36e5f6b..711adb45 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -397,7 +397,7 @@ clear_transient_progress if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then echo - echo "Summary (branch vs compare, avg user ms; cmp/branch > 1 => branch faster)" + echo "Summary (branch vs compare, avg user ms)" echo "==============================================================================" printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "solver" "file" "compare_avg" "branch_avg" "cmp/branch" "note" From ac8bce5e844f48b07945650db21950f6178be671 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 16:16:39 -0400 Subject: [PATCH 113/157] Add epsilon tolerance for equal benchmark comparisons. Default 0.5% (--epsilon / EPSILON) marks branch and compare as equal when avg user times differ by less than that relative threshold. Co-authored-by: Cursor --- benchmark.sh | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 711adb45..1879aa35 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -23,6 +23,7 @@ # MAX_DEALS Include list10^n.txt files with 10^n <= N (default: 100) # DRY_RUN If 1, print commands only # DETAILS If 1 with --compare, keep per-run rows in output (default: transient on tty) +# EPSILON With --compare, max % diff to treat branch/compare as equal (default: 0.5) set -euo pipefail @@ -33,6 +34,7 @@ REPEATS="${REPEATS:-1}" MAX_DEALS="${MAX_DEALS:-100}" DRY_RUN="${DRY_RUN:-0}" DETAILS="${DETAILS:-0}" +EPSILON="${EPSILON:-0.5}" BUILD=0 REVERSE=0 DTEST_EXTRA=() @@ -55,12 +57,13 @@ Options: --branch PATH Branch dtest binary (default: $BRANCH) --compare PATH Optional second dtest binary (summary; transient progress on tty) --details With --compare, keep per-run timing rows in final output + --epsilon PCT With --compare, treat timings within PCT% as equal (default: 0.5; env: EPSILON) --reverse With --compare, run compare before branch each repeat (default: branch first) -- End benchmark options; remaining args are passed to dtest (e.g. -- -n 8 -r for 8 threads and slow-board report) Environment: - BRANCH, COMPARE, HANDS_DIR, REPEATS, MAX_DEALS, DRY_RUN, DETAILS + BRANCH, COMPARE, HANDS_DIR, REPEATS, MAX_DEALS, DRY_RUN, DETAILS, EPSILON Examples: ./benchmark.sh @@ -113,6 +116,11 @@ while [[ $# -gt 0 ]]; do DETAILS=1 shift ;; + --epsilon) + shift + EPSILON="${1:?missing value for --epsilon}" + shift + ;; --) shift DTEST_EXTRA=("$@") @@ -136,6 +144,11 @@ if ! [[ "$REPEATS" =~ ^[0-9]+$ ]] || (( REPEATS < 1 )); then exit 1 fi +if ! [[ "$EPSILON" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + echo "error: epsilon must be a non-negative number (got: $EPSILON)" >&2 + exit 1 +fi + if [[ "$REVERSE" == "1" && -z "${COMPARE:-}" ]]; then echo "error: --reverse requires --compare" >&2 exit 1 @@ -331,6 +344,7 @@ if [[ -n "${COMPARE:-}" ]]; then else printf "%-12s %s\n" "run order:" "interleaved branch, compare" fi + printf "%-12s %s\n" "epsilon:" "${EPSILON}%" fi printf "%-12s %s\n" "hands dir:" "$HANDS_DIR" printf "%-12s %s\n" "max_deals:" "$MAX_DEALS" @@ -404,7 +418,12 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then printf "%-6s %-13s %12s %12s %10s %-15s\n" \ "------" "-------------" "------------" "------------" "----------" "---------------" - awk -F'\t' -v files="${FILES[*]}" ' + awk -F'\t' -v files="${FILES[*]}" -v epsilon_pct="$EPSILON" ' + function within_epsilon(a, b, eps, hi, lo) { + eps = epsilon_pct / 100 + if (a > b) { hi = a; lo = b } else { hi = b; lo = a } + return (hi <= 0 || (hi - lo) / hi <= eps) + } { base = $1 SUBSEP $2 if ($3 == "compare") { @@ -427,7 +446,13 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] cmp_branch = u2 / u1 - note = (cmp_branch >= 1) ? "branch faster" : "compare faster" + if (within_epsilon(u1, u2)) { + note = "equal" + } else if (cmp_branch >= 1) { + note = "branch faster" + } else { + note = "compare faster" + } sp = sprintf("%9.2fx", cmp_branch) printf "%-6s %-13s %12.2f %12.2f %10s %-15s\n", solvers[si], filearr[fi], u2, u1, sp, note From 841fefa899cf06e95ffc304922051ddd65abd98e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 16:29:19 -0400 Subject: [PATCH 114/157] Document --epsilon in benchmark.sh usage examples. Show the compare-mode tolerance flag in the header and help examples. Co-authored-by: Cursor --- benchmark.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmark.sh b/benchmark.sh index 1879aa35..5bd37d53 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -12,6 +12,7 @@ # ./benchmark.sh --build # ./benchmark.sh -- -n 8 -r # ./benchmark.sh --build --compare /path/to/other/dtest +# ./benchmark.sh --compare /path/to/other/dtest --epsilon 1 # ./benchmark.sh --repeats 5 -- -n 4 # REPEATS=3 ./benchmark.sh # @@ -72,6 +73,7 @@ Examples: ./benchmark.sh --repeats 3 -- -n 4 -r ./benchmark.sh --compare /path/to/dtest ./benchmark.sh --compare /path/to/dtest --details + ./benchmark.sh --compare /path/to/dtest --epsilon 1 ./benchmark.sh --compare /path/to/dtest --reverse ./benchmark.sh --repeats 5 --compare /path/to/dtest DRY_RUN=1 ./benchmark.sh From 28f6f2213b5767487759eb5705dc1cb168cfba94 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 17:07:44 -0400 Subject: [PATCH 115/157] Note TestTimer ms truncation in benchmark summary comment. Document that zero or skewed averages can come from per-interval int ms rounding and point to accumulating microseconds in TestTimer.cpp. Co-authored-by: Cursor --- benchmark.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index 5bd37d53..dca7660d 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -444,7 +444,9 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then for (fi = 1; fi <= nfiles; fi++) { base = solvers[si] SUBSEP filearr[fi] if (!(base in c2) || !(base in c1)) continue - # Every member of s1, c1, s2, and c2 will be positive. + # Every member of s1, c1, s2, and c2 should be positive. + # If not, it will be due to a rounding error. To fix, update + # TestTimer.cpp to accumulate microseconds rather than milliseconds. u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] cmp_branch = u2 / u1 From cd08753b0fc046f1f95128c1ee5b9519430049e4 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 17:09:39 -0400 Subject: [PATCH 116/157] Clarify benchmark comment on zero timing from ms truncation. Say rounding to zero rather than a rounding error. Co-authored-by: Cursor --- benchmark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index dca7660d..0f2abeee 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -445,7 +445,7 @@ if [[ -n "${COMPARE:-}" && "$DRY_RUN" != "1" ]]; then base = solvers[si] SUBSEP filearr[fi] if (!(base in c2) || !(base in c1)) continue # Every member of s1, c1, s2, and c2 should be positive. - # If not, it will be due to a rounding error. To fix, update + # If not, it will be due to rounding to zero. To fix, update # TestTimer.cpp to accumulate microseconds rather than milliseconds. u2 = s2[base] / c2[base] u1 = s1[base] / c1[base] From 79eb78fc971827f5d946e2ff3df54bdfb11c352a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 17:24:00 -0400 Subject: [PATCH 117/157] Fix transient progress erase clobbering header line. Clear the blank line under the table first, then move up per row without scrolling past the topmost table line. Co-authored-by: Cursor --- benchmark.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/benchmark.sh b/benchmark.sh index 0f2abeee..26c23119 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -320,11 +320,12 @@ clear_transient_progress() { if [[ "$TRANSIENT_PROGRESS" != "1" || progress_lines -le 0 ]]; then return fi - # Cursor rests on a blank line below the last row; erase it too. - local lines_to_clear=$((progress_lines + 1)) + # Cursor rests on a blank line below the last row; erase it, then each table line. + # Do not move up after clearing the topmost line (would hit the header above). + printf '\033[2K' local i - for (( i = 0; i < lines_to_clear; i++ )); do - printf '\033[2K\033[1A' + for (( i = 0; i < progress_lines; i++ )); do + printf '\033[1A\033[2K' done progress_lines=0 } From 63b3d9377452b45f9cecfc6f8cfa4c78f0342e3c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 21 Jun 2026 22:58:02 -0400 Subject: [PATCH 118/157] Fix progress_lines guard in clear_transient_progress. Use $progress_lines in the numeric test so -le compares the variable value. Co-authored-by: Cursor --- benchmark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index 26c23119..e4f2a605 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -317,7 +317,7 @@ print_run_row() { } clear_transient_progress() { - if [[ "$TRANSIENT_PROGRESS" != "1" || progress_lines -le 0 ]]; then + if [[ "$TRANSIENT_PROGRESS" != "1" || $progress_lines -le 0 ]]; then return fi # Cursor rests on a blank line below the last row; erase it, then each table line. From 0bee3b87c61b45adc8db846fc2df2a504ef0720e Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 20 Jun 2026 20:47:25 +0100 Subject: [PATCH 119/157] Extract resolve_worker_count helper for batch parallelism Collapse the duplicated max(1, min(hardware_concurrency, count)) worker-count formula into a single resolve_worker_count helper in parallel_boards, and rewire parallel_all_boards_n to use it. Pure refactor, no behaviour change. Co-Authored-By: Claude Opus 4.8 --- library/src/system/parallel_boards.cpp | 22 +++++++++++++++------- library/src/system/parallel_boards.hpp | 9 +++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/library/src/system/parallel_boards.cpp b/library/src/system/parallel_boards.cpp index 25ded0d3..750041e7 100644 --- a/library/src/system/parallel_boards.cpp +++ b/library/src/system/parallel_boards.cpp @@ -17,6 +17,20 @@ #include +auto resolve_worker_count( + 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; + } + return std::max(1, std::min(workers, count)); +} + + auto parallel_all_boards_n( const int count, const int worker_cap, @@ -27,13 +41,7 @@ auto parallel_all_boards_n( return RETURN_NO_FAULT; } - int workers = worker_cap; - 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)); + const int workers = resolve_worker_count(worker_cap, count); if (workers == 1) { diff --git a/library/src/system/parallel_boards.hpp b/library/src/system/parallel_boards.hpp index 96f0baa1..763b4716 100644 --- a/library/src/system/parallel_boards.hpp +++ b/library/src/system/parallel_boards.hpp @@ -12,6 +12,15 @@ #include +/** + * @brief Resolve the number of worker threads to use. + * + * @param max_threads Requested cap; <= 0 means "auto" (use hardware concurrency). + * @param count Number of work items; the result is clamped to [1, count]. + * @return The worker count to use. + */ +auto resolve_worker_count(int max_threads, int count) -> int; + /** * @brief Process boards [0, count) with work-stealing parallelism. * From a6cc63fd14043ae171e64197e104341de9258553 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 20 Jun 2026 20:49:16 +0100 Subject: [PATCH 120/157] Rename SetMaxThreads to InitialiseStaticMemory with deprecated alias The function initialises static library state (TT memory pools, scheduler / thread-manager, lookup tables) and no longer controls thread count. Add InitialiseStaticMemory() as the public name and keep SetMaxThreads as a thin deprecated alias for ABI/back-compat. Repoint internal callers (dds.cpp, examples); update .NET/Python binding comments only. Co-Authored-By: Claude Opus 4.8 --- dotnet/DDS_Core/DDS.cs | 2 ++ dotnet/DDS_Core/Native/DdsNative.cs | 2 ++ examples/dd_table_for_deal.cpp | 2 +- examples/migration_example.cpp | 2 +- library/src/api/dll.h | 25 +++++++++++++++++-------- library/src/dds.cpp | 8 ++++---- library/src/init.cpp | 19 +++++++++++++++++-- python/src/bindings.cpp | 3 ++- 8 files changed, 46 insertions(+), 17 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index adbaa594..90afffbb 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -30,6 +30,8 @@ public class DDS /// /// /// Maximum number of threads to use. + // The underlying C symbol SetMaxThreads was renamed to + // InitialiseStaticMemory; this P/Invoke still targets the deprecated alias. [Obsolete("Use SolverContext instead.")] public void SetMaxThreads(int userThreads) => DdsNative.SetMaxThreads(userThreads); diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 9ee2c487..73f36d6a 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -95,6 +95,8 @@ public static extern int dds_calc_par( SolverContextHandle ctx #endregion #region ====== Configuration and Resource Management ====== + // The C symbol SetMaxThreads is deprecated and now a thin alias of + // InitialiseStaticMemory; the userThreads argument is ignored. [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern void SetMaxThreads( int userThreads); diff --git a/examples/dd_table_for_deal.cpp b/examples/dd_table_for_deal.cpp index 9f59adfc..cc537939 100644 --- a/examples/dd_table_for_deal.cpp +++ b/examples/dd_table_for_deal.cpp @@ -227,7 +227,7 @@ auto main(int argc, char * argv[]) -> int char line[80]; #if defined(__linux) || defined(__APPLE__) - SetMaxThreads(0); + InitialiseStaticMemory(); #endif const int res = CalcDDtablePBN(tableDealPBN, &table); diff --git a/examples/migration_example.cpp b/examples/migration_example.cpp index e9e40088..0b78fa56 100644 --- a/examples/migration_example.cpp +++ b/examples/migration_example.cpp @@ -17,7 +17,7 @@ void solve_legacy(const Deal& deal) { - SetMaxThreads(4); + InitialiseStaticMemory(); SetResources(2000, 4); FutureTricks fut; diff --git a/library/src/api/dll.h b/library/src/api/dll.h index 7fbbdd44..0b97b713 100644 --- a/library/src/api/dll.h +++ b/library/src/api/dll.h @@ -428,21 +428,30 @@ struct DDSInfo +/** + * @brief Initialise the solver's static memory. + * + * Allocates the transposition-table memory pools, registers scheduler and + * thread-manager state, and performs one-time lookup-table initialisation. + * This does NOT control the number of worker threads — use the + * SolveAllBoardsN / CalcAllTablesN family for per-call thread caps. + */ +EXTERN_C DLLEXPORT auto STDCALL InitialiseStaticMemory() -> void; + /** * @brief Set the maximum number of threads used by the solver. * - * @deprecated In the modern C++ API, thread count is controlled by the - * embedding application (typically one SolverContext per worker - * thread). New code should create/destroy SolverContext instances - * in the application rather than calling this function. + * @deprecated Use InitialiseStaticMemory(); the thread count argument is + * ignored (internal batch threading was removed). In the modern + * C++ API, thread count is controlled by the embedding application + * (typically one SolverContext per worker thread), or per call via + * the SolveAllBoardsN / CalcAllTablesN family. * See docs/api_migration.md for modern C++ API examples. * - * @param userThreads Maximum number of threads to use + * @param userThreads Ignored; retained for backward compatibility. * * This function is part of the legacy C API and is maintained for backward - * compatibility. It has no direct equivalent in the modern API, where both - * threading and TT memory limits are configured via SolverContext and - * SolverConfig on a per-instance basis. + * compatibility. It simply forwards to InitialiseStaticMemory(). */ EXTERN_C DLLEXPORT auto STDCALL SetMaxThreads( int userThreads) -> void; diff --git a/library/src/dds.cpp b/library/src/dds.cpp index 265e77dc..4762df7c 100644 --- a/library/src/dds.cpp +++ b/library/src/dds.cpp @@ -36,7 +36,7 @@ extern "C" BOOL APIENTRY DllMain( { if (ul_reason_for_call == DLL_PROCESS_ATTACH) - SetMaxThreads(0); + InitialiseStaticMemory(); else if (ul_reason_for_call == DLL_PROCESS_DETACH) { FreeMemory(); @@ -61,9 +61,9 @@ void DDSInitialize(), DDSFinalize(); /** * @brief Initialize the DDS library. */ -void DDSInitialize(void) +void DDSInitialize(void) { - SetMaxThreads(0); + InitialiseStaticMemory(); } @@ -84,7 +84,7 @@ void DDSFinalize(void) */ static void __attribute__ ((constructor)) libInit(void) { - SetMaxThreads(0); + InitialiseStaticMemory(); } #endif diff --git a/library/src/init.cpp b/library/src/init.cpp index 3e22dd11..0fb61e32 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -36,14 +36,29 @@ int _initialized = 0; /* - * Set the maximum number of threads used by the solver. + * Initialise the solver's static memory: TT memory pools, scheduler / + * thread-manager state, and one-time lookup-table setup. + * + * Public API documentation is maintained in the API headers. + */ +void STDCALL InitialiseStaticMemory() +{ + SetResources(0, 0); +} + + +/* + * Deprecated alias for InitialiseStaticMemory(). The thread count is no + * longer meaningful (internal batch threading was removed), so the argument + * is ignored. * * Public API documentation is maintained in the API headers. */ void STDCALL SetMaxThreads( int userThreads) { - SetResources(0, userThreads); + (void) userThreads; + InitialiseStaticMemory(); } diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index be83081a..94170ba8 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -611,7 +611,8 @@ auto register_analysis_bindings(py::module_& module) -> void SetMaxThreads(user_threads); }, py::arg("user_threads") = 0, - "Legacy thread-resource hook (wraps the deprecated SetMaxThreads C API).\n\n" + "Legacy thread-resource hook (wraps the deprecated SetMaxThreads C API,\n" + "now a thin alias of InitialiseStaticMemory).\n\n" "This does NOT control DDS's batch parallelism and is retained only for\n" "backward compatibility. solve_all_boards_* already parallelise across the\n" "machine's hardware threads automatically (see solve_boards_n); the value\n" From 46e85db18122adac6ce7918461b0fa913d8a59cd Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 20 Jun 2026 20:50:51 +0100 Subject: [PATCH 121/157] Plumb optional max_threads through internal batch solvers Add a trailing max_threads (default 0 = auto) to solve_all_boards_n and the legacy parallel calc_all_boards_n, forwarding it to parallel_all_boards_n and using resolve_worker_count to size the SolverContext pool. Default preserves the current hardware_concurrency auto-sizing, so no behaviour change. Deviation from task: the C++ calc_dd_table overloads use the sequential context-aware calc_all_boards_n, so threading max_threads there would be a no-op; left unchanged. The C API CalcDDtable/CalcAllTables use the legacy parallel overload, which now carries the parameter for Task 04. Co-Authored-By: Claude Opus 4.8 --- library/src/calc_tables.cpp | 11 ++++++----- library/src/solve_board.cpp | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 8003944d..66699101 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -27,7 +27,8 @@ extern Scheduler scheduler; // Legacy overload (creates temporary context) auto calc_all_boards_n( Boards * bop, - SolvedBoards * solvedp) -> int; + SolvedBoards * solvedp, + int max_threads = 0) -> int; auto calc_single_common_internal( @@ -109,7 +110,8 @@ auto calc_all_boards_n( // Legacy overload: parallel across boards, one SolverContext per worker. auto calc_all_boards_n( Boards * bop, - SolvedBoards * solvedp) -> int + SolvedBoards * solvedp, + int max_threads) -> int { const int n = bop->no_of_boards; if (n > MAXNOOFBOARDS) @@ -120,8 +122,7 @@ auto calc_all_boards_n( START_BLOCK_TIMER; - const int nthreads = std::max(1, - std::min(static_cast(std::thread::hardware_concurrency()), n)); + const int nthreads = resolve_worker_count(max_threads, n); int err = RETURN_NO_FAULT; if (nthreads <= 1) @@ -137,7 +138,7 @@ auto calc_all_boards_n( else { std::vector contexts(static_cast(nthreads)); - err = parallel_all_boards_n(n, nthreads, + err = parallel_all_boards_n(n, max_threads, [&](const int worker_id, const int bno) -> int { return calc_single_common_internal( contexts[static_cast(worker_id)], *bop, *solvedp, bno); diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index edd78abb..78ee3437 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -63,7 +63,7 @@ static auto boards_from_pbn( auto solve_all_boards_n( Boards const& bds, SolvedBoards& solved, - const int worker_cap) -> int + int max_threads = 0) -> int { const int n = bds.no_of_boards; if (n > MAXNOOFBOARDS) @@ -76,7 +76,7 @@ auto solve_all_boards_n( START_BLOCK_TIMER; - const int err = parallel_all_boards_n(n, worker_cap, + const int err = parallel_all_boards_n(n, max_threads, [&](const int worker_id, const int bno) -> int { (void)worker_id; From 4b50a1ce18cd2bd8fac09899bd684e69e2b9940e Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 20 Jun 2026 20:53:03 +0100 Subject: [PATCH 122/157] Add public *N C variants accepting maxThreads Add SolveAllBoardsN, SolveAllBoardsBinN, CalcDDtableN, CalcDDtablePBNN, CalcAllTablesN and CalcAllTablesPBNN, which forward an explicit maxThreads (<= 0 = auto) down to the internal batch solvers. The existing functions keep their signatures and now delegate to the *N variants with 0, preserving ABI and current auto-sizing behaviour. Co-Authored-By: Claude Opus 4.8 --- library/src/api/dll.h | 72 +++++++++++++++++++++++++++++++++++++ library/src/calc_tables.cpp | 66 +++++++++++++++++++++++++++------- library/src/solve_board.cpp | 47 ++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 14 deletions(-) diff --git a/library/src/api/dll.h b/library/src/api/dll.h index 0b97b713..c27504a1 100644 --- a/library/src/api/dll.h +++ b/library/src/api/dll.h @@ -551,6 +551,17 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtable( struct DdTableDeal tableDeal, struct DdTableResults * tablep) -> int; +/** + * @brief CalcDDtable with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (hardware_concurrency) default. + */ +EXTERN_C DLLEXPORT auto STDCALL CalcDDtableN( + struct DdTableDeal tableDeal, + struct DdTableResults * tablep, + int maxThreads) -> int; + /** * @brief Calculate the double dummy table for a PBN Deal. * @@ -562,6 +573,17 @@ EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBN( struct DdTableDealPBN tableDealPBN, struct DdTableResults * tablep) -> int; +/** + * @brief CalcDDtablePBN with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (hardware_concurrency) default. + */ +EXTERN_C DLLEXPORT auto STDCALL CalcDDtablePBNN( + struct DdTableDealPBN tableDealPBN, + struct DdTableResults * tablep, + int maxThreads) -> int; + /** * @brief Calculate double dummy tables for multiple deals. * @@ -579,6 +601,20 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTables( struct DdTablesRes * resp, struct AllParResults * presp) -> int; +/** + * @brief CalcAllTables with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (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; + /** * @brief Calculate double dummy tables for multiple PBN deals. * @@ -596,6 +632,20 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBN( struct DdTablesRes * resp, struct AllParResults * presp) -> int; +/** + * @brief CalcAllTablesPBN with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (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; + /** * @brief Solve multiple bridge deals in PBN format. * @@ -607,10 +657,32 @@ EXTERN_C DLLEXPORT auto STDCALL SolveAllBoards( struct BoardsPBN const * bop, struct SolvedBoards * solvedp) -> int; +/** + * @brief SolveAllBoards with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (hardware_concurrency) default. + */ +EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsN( + struct BoardsPBN const * bop, + struct SolvedBoards * solvedp, + int maxThreads) -> int; + EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBin( struct Boards const * bop, struct SolvedBoards * solvedp) -> int; +/** + * @brief SolveAllBoardsBin with an explicit worker-thread cap. + * + * @param maxThreads Maximum worker threads; <= 0 selects the automatic + * (hardware_concurrency) default. + */ +EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsBinN( + struct Boards const * bop, + struct SolvedBoards * solvedp, + int maxThreads) -> int; + EXTERN_C DLLEXPORT auto STDCALL SolveAllBoardsSeq( struct BoardsPBN const * bop, struct SolvedBoards * solvedp) -> int; diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 66699101..727753e5 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -161,9 +161,10 @@ auto calc_all_boards_n( -int STDCALL CalcDDtable( +int STDCALL CalcDDtableN( DdTableDeal tableDeal, - DdTableResults * tablep) + DdTableResults * tablep, + int maxThreads) { Deal dl; Boards bo; @@ -192,7 +193,7 @@ int STDCALL CalcDDtable( ind++; } - int res = calc_all_boards_n(&bo, &solved); + int res = calc_all_boards_n(&bo, &solved, maxThreads); if (res != 1) return res; @@ -212,12 +213,21 @@ int STDCALL CalcDDtable( } -int STDCALL CalcAllTables( +int STDCALL CalcDDtable( + DdTableDeal tableDeal, + DdTableResults * tablep) +{ + return CalcDDtableN(tableDeal, tablep, 0); +} + + +int STDCALL CalcAllTablesN( DdTableDeals const * dealsp, int mode, int const trumpFilter[5], DdTablesRes * resp, - AllParResults * presp) + AllParResults * presp, + int maxThreads) { /* mode = 0: par calculation, vulnerability None mode = 1: par calculation, vulnerability All @@ -279,7 +289,7 @@ int STDCALL CalcAllTables( bo.no_of_boards = lastIndex + 1; - int res = calc_all_boards_n(&bo, &solved); + int res = calc_all_boards_n(&bo, &solved, maxThreads); if (res != 1) return res; @@ -317,12 +327,24 @@ int STDCALL CalcAllTables( } -int STDCALL CalcAllTablesPBN( - DdTableDealsPBN const * dealsp, +int STDCALL CalcAllTables( + DdTableDeals const * dealsp, int mode, int const trumpFilter[5], DdTablesRes * resp, AllParResults * presp) +{ + return CalcAllTablesN(dealsp, mode, trumpFilter, resp, presp, 0); +} + + +int STDCALL CalcAllTablesPBNN( + DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[5], + DdTablesRes * resp, + AllParResults * presp, + int maxThreads) { DdTableDeals dls; for (int k = 0; k < dealsp->no_of_tables; k++) @@ -331,24 +353,44 @@ int STDCALL CalcAllTablesPBN( dls.no_of_tables = dealsp->no_of_tables; - int res = CalcAllTables(&dls, mode, trumpFilter, resp, presp); + int res = CalcAllTablesN(&dls, mode, trumpFilter, resp, presp, maxThreads); return res; } -int STDCALL CalcDDtablePBN( +int STDCALL CalcAllTablesPBN( + DdTableDealsPBN const * dealsp, + int mode, + int const trumpFilter[5], + DdTablesRes * resp, + AllParResults * presp) +{ + return CalcAllTablesPBNN(dealsp, mode, trumpFilter, resp, presp, 0); +} + + +int STDCALL CalcDDtablePBNN( DdTableDealPBN tableDealPBN, - DdTableResults * tablep) + DdTableResults * tablep, + int maxThreads) { DdTableDeal tableDeal; if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) return RETURN_PBN_FAULT; - int res = CalcDDtable(tableDeal, tablep); + int res = CalcDDtableN(tableDeal, tablep, maxThreads); return res; } +int STDCALL CalcDDtablePBN( + DdTableDealPBN tableDealPBN, + DdTableResults * tablep) +{ + return CalcDDtablePBNN(tableDealPBN, tablep, 0); +} + + void detect_calc_duplicates( const Boards& bds, vector& uniques, diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index 78ee3437..23eedf51 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -162,11 +162,54 @@ int STDCALL SolveBoardPBN( * @param solvedp Pointer to results for solved Boards * @return 1 on success, error code otherwise */ +int STDCALL SolveAllBoardsN( + BoardsPBN const * bop, + SolvedBoards * solvedp, + int maxThreads) +{ + Boards bo; + 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.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) + != 1) + return RETURN_PBN_FAULT; + } + + int res = solve_all_boards_n(bo, * solvedp, maxThreads); + return res; +} + + int STDCALL SolveAllBoards( BoardsPBN const * bop, SolvedBoards * solvedp) { - return solve_all_boards_pbn_n(*bop, *solvedp, 0); + return SolveAllBoardsN(bop, solvedp, 0); +} + + +int STDCALL SolveAllBoardsBinN( + Boards const * bop, + SolvedBoards * solvedp, + int maxThreads) +{ + return solve_all_boards_n(* bop, * solvedp, maxThreads); } @@ -174,7 +217,7 @@ int STDCALL SolveAllBoardsBin( Boards const * bop, SolvedBoards * solvedp) { - return solve_all_boards_n(* bop, * solvedp); + return SolveAllBoardsBinN(bop, solvedp, 0); } From da0734c53aaf18818be902a2beb90d989d30325d Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sat, 20 Jun 2026 21:02:46 +0100 Subject: [PATCH 123/157] Add tests for worker-count override and the initialiser rename Add worker_count_test (resolve_worker_count edge cases) and max_threads_equivalence_test (CalcDDtableN / SolveAllBoardsBinN with an explicit cap match the auto path; InitialiseStaticMemory and the deprecated SetMaxThreads alias both leave the library usable). Migrate existing SetMaxThreads(1) call sites to InitialiseStaticMemory(), keeping one deliberate alias usage in the new test. Co-Authored-By: Claude Opus 4.8 --- library/tests/system/BUILD.bazel | 24 ++++ .../tests/system/context_equivalence_test.cpp | 2 +- .../tests/system/context_tt_facade_test.cpp | 6 +- .../system/max_threads_equivalence_test.cpp | 132 ++++++++++++++++++ library/tests/system/worker_count_test.cpp | 49 +++++++ 5 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 library/tests/system/max_threads_equivalence_test.cpp create mode 100644 library/tests/system/worker_count_test.cpp diff --git a/library/tests/system/BUILD.bazel b/library/tests/system/BUILD.bazel index 6d429426..5c172fd6 100644 --- a/library/tests/system/BUILD.bazel +++ b/library/tests/system/BUILD.bazel @@ -59,6 +59,30 @@ cc_test( ], ) +# Worker-count helper unit test +cc_test( + name = "worker_count_test", + size = "small", + srcs = ["worker_count_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/api:api_definitions", + "@googletest//:gtest_main", + ], +) + +# max_threads override equivalence + rename/alias initialisation test +cc_test( + name = "max_threads_equivalence_test", + size = "small", + srcs = ["max_threads_equivalence_test.cpp"], + deps = [ + "//library/src:testable_dds", + "//library/src/api:api_definitions", + "@googletest//:gtest_main", + ], +) + # Utilities feature flags tests cc_test( name = "utilities_feature_flags_test", diff --git a/library/tests/system/context_equivalence_test.cpp b/library/tests/system/context_equivalence_test.cpp index a63b5a96..4396046f 100644 --- a/library/tests/system/context_equivalence_test.cpp +++ b/library/tests/system/context_equivalence_test.cpp @@ -58,7 +58,7 @@ static DdTableDeal make_known_deal() TEST(SystemContextEquivalence, LegacyVsContextReturnCode) { // Ensure DDS system and thread-local memory are initialized - SetMaxThreads(1); + InitialiseStaticMemory(); const int thr = 0; FutureTricks ft_legacy{}; FutureTricks ft_ctx{}; diff --git a/library/tests/system/context_tt_facade_test.cpp b/library/tests/system/context_tt_facade_test.cpp index b9c6076a..3ab63ecd 100644 --- a/library/tests/system/context_tt_facade_test.cpp +++ b/library/tests/system/context_tt_facade_test.cpp @@ -15,7 +15,7 @@ extern Memory memory; TEST(SystemContextTTFacades, ResetAndResizeAreNoopsWithoutTT) { - SetMaxThreads(1); + InitialiseStaticMemory(); // 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) @@ -34,7 +34,7 @@ TEST(SystemContextTTFacades, ResetAndResizeAreNoopsWithoutTT) TEST(SystemContextTTFacades, ResizeCreatesWhenExisting) { - SetMaxThreads(1); + InitialiseStaticMemory(); // 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); @@ -51,7 +51,7 @@ TEST(SystemContextTTFacades, ResizeCreatesWhenExisting) TEST(SystemContextTTFacades, Lifecycle_LookupAddClearDispose) { - SetMaxThreads(1); + InitialiseStaticMemory(); if (memory.NumThreads() == 0) memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); diff --git a/library/tests/system/max_threads_equivalence_test.cpp b/library/tests/system/max_threads_equivalence_test.cpp new file mode 100644 index 00000000..b7210603 --- /dev/null +++ b/library/tests/system/max_threads_equivalence_test.cpp @@ -0,0 +1,132 @@ +/// @file max_threads_equivalence_test.cpp +/// @brief Tests that the *N batch APIs honour maxThreads and stay equivalent to +/// the auto path, plus that the rename/alias both initialise the library. + +#include +#include +#include + +#include +#include + +namespace +{ + +// Known deal from examples/hands.cpp (hand 0), as used by context_equivalence_test. +// 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; +} + +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; +} + +} // namespace + +// CalcDDtableN with maxThreads=1 must match the auto CalcDDtable. +TEST(MaxThreadsEquivalence, CalcDDtableNMatchesAuto) +{ + InitialiseStaticMemory(); + 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) +{ + InitialiseStaticMemory(); + 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 uninitialised. + 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; + } + } +} + +// InitialiseStaticMemory leaves the library usable for a subsequent solve. +TEST(MaxThreadsEquivalence, InitialiseStaticMemoryThenSolve) +{ + InitialiseStaticMemory(); + DdTableDeal deal = make_known_deal(); + DdTableResults table{}; + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); +} + +// The deprecated SetMaxThreads alias still initialises the library. +TEST(MaxThreadsEquivalence, DeprecatedSetMaxThreadsAliasStillWorks) +{ + SetMaxThreads(1); + DdTableDeal deal = make_known_deal(); + DdTableResults table{}; + EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); +} diff --git a/library/tests/system/worker_count_test.cpp b/library/tests/system/worker_count_test.cpp new file mode 100644 index 00000000..dfe6aecd --- /dev/null +++ b/library/tests/system/worker_count_test.cpp @@ -0,0 +1,49 @@ +/// @file worker_count_test.cpp +/// @brief Unit tests for resolve_worker_count (batch worker-count resolution). +/// +/// Validates the shared helper that maps an optional max_threads cap and a work +/// item count onto the number of worker threads to use. + +#include +#include +#include + +#include + +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)); +} + +} // 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)); +} + +TEST(ResolveWorkerCount, CapLargerThanCountClampsToCount) +{ + EXPECT_EQ(resolve_worker_count(1000, 5), 5); +} + +TEST(ResolveWorkerCount, CapSmallerThanCountAndHardwareIsHonoured) +{ + // 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); +} From 67fa2d4979d39c9021af16a77d194bf3db1c247a Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sun, 21 Jun 2026 18:16:13 +0100 Subject: [PATCH 124/157] fix: align argument names and default values. --- library/src/solve_board.cpp | 6 +++--- library/src/solve_board.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index 23eedf51..d484ac22 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -63,7 +63,7 @@ static auto boards_from_pbn( auto solve_all_boards_n( Boards const& bds, SolvedBoards& solved, - int max_threads = 0) -> int + int max_threads) -> int { const int n = bds.no_of_boards; if (n > MAXNOOFBOARDS) @@ -113,13 +113,13 @@ auto solve_all_boards_n( auto solve_all_boards_pbn_n( BoardsPBN const& bop, SolvedBoards& solved, - const int worker_cap) -> int + 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, worker_cap); + return solve_all_boards_n(bo, solved, max_threads); } diff --git a/library/src/solve_board.hpp b/library/src/solve_board.hpp index 400fe68a..5032aea0 100644 --- a/library/src/solve_board.hpp +++ b/library/src/solve_board.hpp @@ -17,12 +17,12 @@ auto solve_all_boards_n( Boards const& bds, SolvedBoards& solved, - int worker_cap = 0) -> int; + int max_threads = 0) -> int; auto solve_all_boards_pbn_n( BoardsPBN const& bop, SolvedBoards& solved, - int worker_cap = 0) -> int; + int max_threads = 0) -> int; auto solve_all_boards_n_seq( Boards const& bds, From 538604ba311a361e86acf4c4b99bebfd2746732f Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sun, 21 Jun 2026 18:26:08 +0100 Subject: [PATCH 125/157] fix: updates documenting comment. --- library/src/system/parallel_boards.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/system/parallel_boards.hpp b/library/src/system/parallel_boards.hpp index 763b4716..2f19b6de 100644 --- a/library/src/system/parallel_boards.hpp +++ b/library/src/system/parallel_boards.hpp @@ -16,7 +16,7 @@ * @brief Resolve the number of worker threads to use. * * @param max_threads Requested cap; <= 0 means "auto" (use hardware concurrency). - * @param count Number of work items; the result is clamped to [1, count]. + * @param count Number of work items; the result is clamped to [1, count] when count > 0 and to 1 when count <= 0. * @return The worker count to use. */ auto resolve_worker_count(int max_threads, int count) -> int; From 929b4f731010828156dc638497533780509a6795 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Sun, 21 Jun 2026 22:59:51 +0100 Subject: [PATCH 126/157] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dotnet/DDS_Core/DDS.cs | 2 +- library/src/api/dll.h | 2 +- library/src/solve_board.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 90afffbb..53a8a2c8 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -29,7 +29,7 @@ public class DDS /// via SolverContext and SolverConfig. /// /// - /// Maximum number of threads to use. + /// Ignored; retained for backward compatibility. // The underlying C symbol SetMaxThreads was renamed to // InitialiseStaticMemory; this P/Invoke still targets the deprecated alias. [Obsolete("Use SolverContext instead.")] diff --git a/library/src/api/dll.h b/library/src/api/dll.h index c27504a1..c3e59574 100644 --- a/library/src/api/dll.h +++ b/library/src/api/dll.h @@ -439,7 +439,7 @@ struct DDSInfo EXTERN_C DLLEXPORT auto STDCALL InitialiseStaticMemory() -> void; /** - * @brief Set the maximum number of threads used by the solver. + * @brief Deprecated alias of InitialiseStaticMemory(). * * @deprecated Use InitialiseStaticMemory(); the thread count argument is * ignored (internal batch threading was removed). In the modern diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index d484ac22..8ff52f25 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -187,7 +187,7 @@ int STDCALL SolveAllBoardsN( } if (convert_from_pbn(bop->deals[k].remainCards, bo.deals[k].remainCards) - != 1) + != RETURN_NO_FAULT) return RETURN_PBN_FAULT; } From 99a3a43dabf721a50bb73f379ddf5ff4b00b3104 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Mon, 22 Jun 2026 10:42:05 +0100 Subject: [PATCH 127/157] exports initialise static memory to python and deprecates set max threads. --- python/dds3/__init__.py | 3 +++ python/src/bindings.cpp | 43 ++++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/python/dds3/__init__.py b/python/dds3/__init__.py index 8da2bf96..dda8d813 100644 --- a/python/dds3/__init__.py +++ b/python/dds3/__init__.py @@ -7,6 +7,7 @@ from ._dds3 import calc_par from ._dds3 import calc_par_from_table from ._dds3 import dealer_par + from ._dds3 import initialise_static_memory from ._dds3 import module_name from ._dds3 import par from ._dds3 import set_max_threads @@ -25,6 +26,7 @@ from _dds3 import calc_par from _dds3 import calc_par_from_table from _dds3 import dealer_par + from _dds3 import initialise_static_memory from _dds3 import module_name from _dds3 import par from _dds3 import set_max_threads @@ -43,6 +45,7 @@ "calc_par", "calc_par_from_table", "dealer_par", + "initialise_static_memory", "module_name", "par", "set_max_threads", diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index 94170ba8..4ae723e5 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -598,8 +598,26 @@ auto register_calc_par_bindings(py::module_& module) -> void auto register_analysis_bindings(py::module_& module) -> void { - // set_max_threads: configure the worker-thread count used by the batch - // APIs (solve_all_boards_*, analyse_all_plays_pbn). 0 = auto-configure. + // initialise_static_memory: allocate the solver's static memory pools and + // perform one-time lookup-table initialisation. This does NOT control the + // worker-thread count; use solve_all_boards_* (which parallelise across the + // machine's hardware threads automatically) or one SolverContext per worker + // thread for per-board concurrency. + module.def( + "initialise_static_memory", + []() { + InitialiseStaticMemory(); + }, + "Initialise the solver's static memory.\n\n" + "Allocates the transposition-table memory pools and performs one-time\n" + "lookup-table initialisation. This does NOT control the number of worker\n" + "threads: solve_all_boards_* parallelise across the machine's hardware\n" + "threads automatically, and for per-board concurrency from Python you\n" + "create one SolverContext per worker thread and pass it to solve_board /\n" + "solve_board_pbn."); + + // set_max_threads: DEPRECATED alias of initialise_static_memory. The thread + // count argument is ignored; retained only for backward compatibility. module.def( "set_max_threads", [](const int user_threads) { @@ -608,19 +626,26 @@ auto register_analysis_bindings(py::module_& module) -> void "user_threads has invalid value " + std::to_string(user_threads) + " (expected >= 0; 0 = auto)"); } + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "set_max_threads() is deprecated; use initialise_static_memory(). " + "The user_threads argument is ignored.", + 1) != 0) { + throw py::error_already_set(); + } SetMaxThreads(user_threads); }, py::arg("user_threads") = 0, + "DEPRECATED: use initialise_static_memory() instead.\n\n" "Legacy thread-resource hook (wraps the deprecated SetMaxThreads C API,\n" - "now a thin alias of InitialiseStaticMemory).\n\n" + "now a thin alias of InitialiseStaticMemory). Calling this emits a\n" + "DeprecationWarning.\n\n" "This does NOT control DDS's batch parallelism and is retained only for\n" - "backward compatibility. solve_all_boards_* already parallelise across the\n" - "machine's hardware threads automatically (see solve_boards_n); the value\n" - "passed here does not size that pool. analyse_all_plays_pbn currently runs\n" - "sequentially. For per-board concurrency from Python, create one\n" - "SolverContext per worker thread and pass it to solve_board / solve_board_pbn.\n\n" + "backward compatibility. analyse_all_plays_pbn currently runs sequentially. For\n" + "per-board concurrency from Python, create one SolverContext per worker\n" + "thread and pass it to solve_board / solve_board_pbn.\n\n" "Args:\n" - " user_threads (int, optional): Must be >= 0; 0 = auto. Default: 0\n\n" + " user_threads (int, optional): Ignored; must be >= 0. Default: 0\n\n" "Raises:\n" " ValueError: If user_threads < 0."); From 50afb9a2dc921d3165df3c078220f4796c68e8e2 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Mon, 22 Jun 2026 20:38:46 +0100 Subject: [PATCH 128/157] fix: removes code duplication and uses nthreads consistently. --- library/src/calc_tables.cpp | 3 +-- library/src/solve_board.cpp | 29 +---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index 727753e5..b0446fe8 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -8,7 +8,6 @@ */ #include "calc_tables.hpp" -#include #include #include @@ -138,7 +137,7 @@ auto calc_all_boards_n( else { std::vector contexts(static_cast(nthreads)); - err = parallel_all_boards_n(n, max_threads, + err = parallel_all_boards_n(n, nthreads, [&](const int worker_id, const int bno) -> int { return calc_single_common_internal( contexts[static_cast(worker_id)], *bop, *solvedp, bno); diff --git a/library/src/solve_board.cpp b/library/src/solve_board.cpp index 8ff52f25..49fc7ef2 100644 --- a/library/src/solve_board.cpp +++ b/library/src/solve_board.cpp @@ -7,7 +7,6 @@ See LICENSE and README. */ -#include #include #include "solve_board.hpp" @@ -20,7 +19,6 @@ #include -extern Memory memory; extern Scheduler scheduler; auto same_board( @@ -167,32 +165,7 @@ int STDCALL SolveAllBoardsN( SolvedBoards * solvedp, int maxThreads) { - Boards bo; - 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.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; - } - - int res = solve_all_boards_n(bo, * solvedp, maxThreads); - return res; + return solve_all_boards_pbn_n(* bop, * solvedp, maxThreads); } From 7a858f1b795937b4b6288ceb4a8ad872de6a937c Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Mon, 22 Jun 2026 20:41:49 +0100 Subject: [PATCH 129/157] fix: releases the global interpreter lock before doing work in initialise static memory. --- python/src/bindings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index 4ae723e5..ab080117 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -606,6 +606,7 @@ auto register_analysis_bindings(py::module_& module) -> void module.def( "initialise_static_memory", []() { + py::gil_scoped_release release; InitialiseStaticMemory(); }, "Initialise the solver's static memory.\n\n" @@ -633,6 +634,7 @@ auto register_analysis_bindings(py::module_& module) -> void 1) != 0) { throw py::error_already_set(); } + py::gil_scoped_release release; SetMaxThreads(user_threads); }, py::arg("user_threads") = 0, From ca3d1e2b829232394c220754e22a2ad498926430 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Tue, 23 Jun 2026 08:07:40 +0100 Subject: [PATCH 130/157] fix: deletes the input guard on the ignored user_threads argument. --- python/src/bindings.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index ab080117..21333fea 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -622,11 +622,6 @@ auto register_analysis_bindings(py::module_& module) -> void module.def( "set_max_threads", [](const int user_threads) { - if (user_threads < 0) { - throw py::value_error( - "user_threads has invalid value " + std::to_string(user_threads) + - " (expected >= 0; 0 = auto)"); - } if (PyErr_WarnEx( PyExc_DeprecationWarning, "set_max_threads() is deprecated; use initialise_static_memory(). " From 4a467b0dd716cef0181738f5aa247b881f9c8a4f Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Tue, 23 Jun 2026 10:07:08 +0100 Subject: [PATCH 131/157] fix: removes test that is now incorrect. --- python/tests/test_analyse.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/python/tests/test_analyse.py b/python/tests/test_analyse.py index 1f589d2e..544c5b3e 100644 --- a/python/tests/test_analyse.py +++ b/python/tests/test_analyse.py @@ -61,11 +61,6 @@ def test_analyse_all_plays_missing_play(self) -> None: with self.assertRaises(KeyError): analyse_all_plays_pbn([{"remain_cards": DEAL}]) - def test_set_max_threads_rejects_negative(self) -> None: - with self.assertRaises(ValueError): - set_max_threads(-1) - set_max_threads(0) # 0 is valid (auto) - class TestDealerPar(unittest.TestCase): """Tests for dealer_par.""" From 9e12ddcebff1ed0e2a81e5d966123d2af8fb3997 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Wed, 24 Jun 2026 08:26:17 +0100 Subject: [PATCH 132/157] fix: remove mention of the guard on user_threads from the python doc string. --- python/src/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index 21333fea..c9399a30 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -642,7 +642,7 @@ auto register_analysis_bindings(py::module_& module) -> void "per-board concurrency from Python, create one SolverContext per worker\n" "thread and pass it to solve_board / solve_board_pbn.\n\n" "Args:\n" - " user_threads (int, optional): Ignored; must be >= 0. Default: 0\n\n" + " user_threads (int, optional): Ignored;\n\n" "Raises:\n" " ValueError: If user_threads < 0."); From a89fae85edb1c624fa01bbf2f979d479c70da445 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Thu, 25 Jun 2026 13:13:10 +0100 Subject: [PATCH 133/157] fix: removes incorrect mention of value error. --- python/src/bindings.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp index c9399a30..6240e4cb 100644 --- a/python/src/bindings.cpp +++ b/python/src/bindings.cpp @@ -607,7 +607,7 @@ auto register_analysis_bindings(py::module_& module) -> void "initialise_static_memory", []() { py::gil_scoped_release release; - InitialiseStaticMemory(); + InitializeStaticMemory(); }, "Initialise the solver's static memory.\n\n" "Allocates the transposition-table memory pools and performs one-time\n" @@ -635,16 +635,14 @@ auto register_analysis_bindings(py::module_& module) -> void py::arg("user_threads") = 0, "DEPRECATED: use initialise_static_memory() instead.\n\n" "Legacy thread-resource hook (wraps the deprecated SetMaxThreads C API,\n" - "now a thin alias of InitialiseStaticMemory). Calling this emits a\n" + "now a thin alias of InitializeStaticMemory). Calling this emits a\n" "DeprecationWarning.\n\n" "This does NOT control DDS's batch parallelism and is retained only for\n" "backward compatibility. analyse_all_plays_pbn currently runs sequentially. For\n" "per-board concurrency from Python, create one SolverContext per worker\n" "thread and pass it to solve_board / solve_board_pbn.\n\n" "Args:\n" - " user_threads (int, optional): Ignored;\n\n" - "Raises:\n" - " ValueError: If user_threads < 0."); + " user_threads (int, optional): Ignored;\n\n"); // analyse_play_pbn: double-dummy trick count after each card of a played hand. module.def( From 9f5adac7a65ff261ad9334373ec70c27f1f4ce31 Mon Sep 17 00:00:00 2001 From: Martin Nygren Date: Thu, 25 Jun 2026 13:13:50 +0100 Subject: [PATCH 134/157] fix: renames InitialiseStaticMemory to use the American misspelling. --- dotnet/DDS_Core/DDS.cs | 2 +- dotnet/DDS_Core/Native/DdsNative.cs | 2 +- examples/dd_table_for_deal.cpp | 4 --- examples/migration_example.cpp | 2 +- library/src/api/dll.h | 8 +++--- library/src/dds.cpp | 28 ++++++++++++++++--- library/src/init.cpp | 6 ++-- .../tests/system/context_equivalence_test.cpp | 2 +- .../tests/system/context_tt_facade_test.cpp | 6 ++-- .../system/max_threads_equivalence_test.cpp | 10 +++---- 10 files changed, 43 insertions(+), 27 deletions(-) diff --git a/dotnet/DDS_Core/DDS.cs b/dotnet/DDS_Core/DDS.cs index 53a8a2c8..7126e4fd 100644 --- a/dotnet/DDS_Core/DDS.cs +++ b/dotnet/DDS_Core/DDS.cs @@ -31,7 +31,7 @@ public class DDS /// /// Ignored; retained for backward compatibility. // The underlying C symbol SetMaxThreads was renamed to - // InitialiseStaticMemory; this P/Invoke still targets the deprecated alias. + // InitializeStaticMemory; this P/Invoke still targets the deprecated alias. [Obsolete("Use SolverContext instead.")] public void SetMaxThreads(int userThreads) => DdsNative.SetMaxThreads(userThreads); diff --git a/dotnet/DDS_Core/Native/DdsNative.cs b/dotnet/DDS_Core/Native/DdsNative.cs index 73f36d6a..8b9b7cf8 100644 --- a/dotnet/DDS_Core/Native/DdsNative.cs +++ b/dotnet/DDS_Core/Native/DdsNative.cs @@ -96,7 +96,7 @@ public static extern int dds_calc_par( SolverContextHandle ctx #region ====== Configuration and Resource Management ====== // The C symbol SetMaxThreads is deprecated and now a thin alias of - // InitialiseStaticMemory; the userThreads argument is ignored. + // InitializeStaticMemory; the userThreads argument is ignored. [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] public static extern void SetMaxThreads( int userThreads); diff --git a/examples/dd_table_for_deal.cpp b/examples/dd_table_for_deal.cpp index cc537939..48814f1a 100644 --- a/examples/dd_table_for_deal.cpp +++ b/examples/dd_table_for_deal.cpp @@ -226,10 +226,6 @@ auto main(int argc, char * argv[]) -> int DdTableResults table; char line[80]; -#if defined(__linux) || defined(__APPLE__) - InitialiseStaticMemory(); -#endif - const int res = CalcDDtablePBN(tableDealPBN, &table); if (res != RETURN_NO_FAULT) { diff --git a/examples/migration_example.cpp b/examples/migration_example.cpp index 0b78fa56..7114c1c2 100644 --- a/examples/migration_example.cpp +++ b/examples/migration_example.cpp @@ -17,7 +17,7 @@ void solve_legacy(const Deal& deal) { - InitialiseStaticMemory(); + InitializeStaticMemory(); SetResources(2000, 4); FutureTricks fut; diff --git a/library/src/api/dll.h b/library/src/api/dll.h index c3e59574..e1c3aa9c 100644 --- a/library/src/api/dll.h +++ b/library/src/api/dll.h @@ -436,12 +436,12 @@ struct DDSInfo * This does NOT control the number of worker threads — use the * SolveAllBoardsN / CalcAllTablesN family for per-call thread caps. */ -EXTERN_C DLLEXPORT auto STDCALL InitialiseStaticMemory() -> void; +EXTERN_C DLLEXPORT auto STDCALL InitializeStaticMemory() -> void; /** - * @brief Deprecated alias of InitialiseStaticMemory(). + * @brief Deprecated alias of InitializeStaticMemory(). * - * @deprecated Use InitialiseStaticMemory(); the thread count argument is + * @deprecated Use InitializeStaticMemory(); the thread count argument is * ignored (internal batch threading was removed). In the modern * C++ API, thread count is controlled by the embedding application * (typically one SolverContext per worker thread), or per call via @@ -451,7 +451,7 @@ EXTERN_C DLLEXPORT auto STDCALL InitialiseStaticMemory() -> void; * @param userThreads Ignored; retained for backward compatibility. * * This function is part of the legacy C API and is maintained for backward - * compatibility. It simply forwards to InitialiseStaticMemory(). + * compatibility. It simply forwards to InitializeStaticMemory(). */ EXTERN_C DLLEXPORT auto STDCALL SetMaxThreads( int userThreads) -> void; diff --git a/library/src/dds.cpp b/library/src/dds.cpp index 4762df7c..64991270 100644 --- a/library/src/dds.cpp +++ b/library/src/dds.cpp @@ -36,7 +36,7 @@ extern "C" BOOL APIENTRY DllMain( { if (ul_reason_for_call == DLL_PROCESS_ATTACH) - InitialiseStaticMemory(); + InitializeStaticMemory(); else if (ul_reason_for_call == DLL_PROCESS_DETACH) { FreeMemory(); @@ -63,18 +63,38 @@ void DDSInitialize(), DDSFinalize(); */ void DDSInitialize(void) { - InitialiseStaticMemory(); + InitializeStaticMemory(); } /** * @brief Finalize and clean up the DDS library. */ -void DDSFinalize(void) +void DDSFinalize(void) { FreeMemory(); } + +/** + * @brief Library constructor/destructor for Apple platforms. + * + * Register DDSInitialize/DDSFinalize so the library's static memory is set + * up automatically when the library is loaded, matching the behaviour of the + * Windows (DllMain) and USES_CONSTRUCTOR paths. This frees callers from having + * to call InitializeStaticMemory() themselves. + */ +static void __attribute__ ((constructor)) libInit(void) +{ + DDSInitialize(); +} + + +static void __attribute__ ((destructor)) libFini(void) +{ + DDSFinalize(); +} + #elif defined(USES_CONSTRUCTOR) /** @@ -84,7 +104,7 @@ void DDSFinalize(void) */ static void __attribute__ ((constructor)) libInit(void) { - InitialiseStaticMemory(); + InitializeStaticMemory(); } #endif diff --git a/library/src/init.cpp b/library/src/init.cpp index 0fb61e32..1b206925 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -41,14 +41,14 @@ int _initialized = 0; * * Public API documentation is maintained in the API headers. */ -void STDCALL InitialiseStaticMemory() +void STDCALL InitializeStaticMemory() { SetResources(0, 0); } /* - * Deprecated alias for InitialiseStaticMemory(). The thread count is no + * Deprecated alias for InitializeStaticMemory(). The thread count is no * longer meaningful (internal batch threading was removed), so the argument * is ignored. * @@ -58,7 +58,7 @@ void STDCALL SetMaxThreads( int userThreads) { (void) userThreads; - InitialiseStaticMemory(); + InitializeStaticMemory(); } diff --git a/library/tests/system/context_equivalence_test.cpp b/library/tests/system/context_equivalence_test.cpp index 4396046f..fe586b29 100644 --- a/library/tests/system/context_equivalence_test.cpp +++ b/library/tests/system/context_equivalence_test.cpp @@ -58,7 +58,7 @@ static DdTableDeal make_known_deal() TEST(SystemContextEquivalence, LegacyVsContextReturnCode) { // Ensure DDS system and thread-local memory are initialized - InitialiseStaticMemory(); + InitializeStaticMemory(); const int thr = 0; FutureTricks ft_legacy{}; FutureTricks ft_ctx{}; diff --git a/library/tests/system/context_tt_facade_test.cpp b/library/tests/system/context_tt_facade_test.cpp index 3ab63ecd..0b6f9fac 100644 --- a/library/tests/system/context_tt_facade_test.cpp +++ b/library/tests/system/context_tt_facade_test.cpp @@ -15,7 +15,7 @@ extern Memory memory; TEST(SystemContextTTFacades, ResetAndResizeAreNoopsWithoutTT) { - InitialiseStaticMemory(); + 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) @@ -34,7 +34,7 @@ TEST(SystemContextTTFacades, ResetAndResizeAreNoopsWithoutTT) TEST(SystemContextTTFacades, ResizeCreatesWhenExisting) { - InitialiseStaticMemory(); + 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); @@ -51,7 +51,7 @@ TEST(SystemContextTTFacades, ResizeCreatesWhenExisting) TEST(SystemContextTTFacades, Lifecycle_LookupAddClearDispose) { - InitialiseStaticMemory(); + InitializeStaticMemory(); if (memory.NumThreads() == 0) memory.Resize(1, DDS_TT_SMALL, THREADMEM_SMALL_DEF_MB, THREADMEM_SMALL_MAX_MB); diff --git a/library/tests/system/max_threads_equivalence_test.cpp b/library/tests/system/max_threads_equivalence_test.cpp index b7210603..52e48b9a 100644 --- a/library/tests/system/max_threads_equivalence_test.cpp +++ b/library/tests/system/max_threads_equivalence_test.cpp @@ -49,7 +49,7 @@ void expect_tables_equal(const DdTableResults& a, const DdTableResults& b) // CalcDDtableN with maxThreads=1 must match the auto CalcDDtable. TEST(MaxThreadsEquivalence, CalcDDtableNMatchesAuto) { - InitialiseStaticMemory(); + InitializeStaticMemory(); DdTableDeal deal = make_known_deal(); DdTableResults table_auto{}; @@ -70,7 +70,7 @@ TEST(MaxThreadsEquivalence, CalcDDtableNMatchesAuto) // SolveAllBoardsBinN with maxThreads=1 must match the auto SolveAllBoardsBin. TEST(MaxThreadsEquivalence, SolveAllBoardsBinNMatchesAuto) { - InitialiseStaticMemory(); + InitializeStaticMemory(); DdTableDeal table_deal = make_known_deal(); // Solve all five strains as separate boards. @@ -113,10 +113,10 @@ TEST(MaxThreadsEquivalence, SolveAllBoardsBinNMatchesAuto) } } -// InitialiseStaticMemory leaves the library usable for a subsequent solve. -TEST(MaxThreadsEquivalence, InitialiseStaticMemoryThenSolve) +// InitializeStaticMemory leaves the library usable for a subsequent solve. +TEST(MaxThreadsEquivalence, InitializeStaticMemoryThenSolve) { - InitialiseStaticMemory(); + InitializeStaticMemory(); DdTableDeal deal = make_known_deal(); DdTableResults table{}; EXPECT_EQ(CalcDDtable(deal, &table), RETURN_NO_FAULT); From efb7e7a1b26e7424792f2173230b28bf2d9fa51b Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 14:24:16 -0400 Subject: [PATCH 135/157] Per Cursor: Allow ASAN and TSAN tests to run on macOS. --- .bazelrc | 35 +++++++++++++++++++---------------- MODULE.bazel | 7 +++++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/.bazelrc b/.bazelrc index d653bec4..05bcd8c2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -19,11 +19,14 @@ build:linux --host_cxxopt=-std=c++20 build:opt --compilation_mode=opt # AddressSanitizer configuration -# Usage: -# Linux: bazel test --config=asan //path:target -# macOS: bazel test --config=asan --config=asan_macos //path:target +# Usage: bazel test --config=asan //path:target +# macOS-specific settings (Xcode toolchain) are pulled in automatically via asan_macos. +build:asan --config=asan_macos + build:asan --copt=-fsanitize=address build:asan --linkopt=-fsanitize=address +# U_FORTIFY_SOURCE conflicts with ASAN +build:asan --copt=-U_FORTIFY_SOURCE build:asan --strip=never build:asan --features=dbg build:asan --compilation_mode=dbg @@ -36,24 +39,24 @@ test:asan --test_timeout=120,300,900,3600 # On Linux, use -Wno-conversion for portability when the system compiler is GCC. build:macos --cxxopt=-Wno-character-conversion build:linux --cxxopt=-Wno-conversion -# macOS-specific AddressSanitizer runtime lookup configuration. -# The LLVM toolchain bundles the ASAN runtime. On macOS tests run from the -# runfiles tree, so use @loader_path-based rpaths that walk back to execroot -# and then into external/toolchains_llvm... -# Keep the clang major version in these paths aligned with darwin-aarch64 -# in MODULE.bazel. -# Bazel test binaries live at different depths under bazel-out/.../bin. -# Provide a small set of distinct @loader_path traversals to reach -# execroot/_main/external from common test binary depths. -build:asan_macos --linkopt=-Wl,-rpath,@loader_path/../../../../../external/toolchains_llvm++llvm+llvm_toolchain_llvm/lib/clang/20/lib/darwin -build:asan_macos --linkopt=-Wl,-rpath,@loader_path/../../../../../../external/toolchains_llvm++llvm+llvm_toolchain_llvm/lib/clang/20/lib/darwin -build:asan_macos --linkopt=-Wl,-rpath,@loader_path/../../../../../../../external/toolchains_llvm++llvm+llvm_toolchain_llvm/lib/clang/20/lib/darwin -build:asan_macos --linkopt=-Wl,-rpath,@loader_path/../../../../../../../../external/toolchains_llvm++llvm+llvm_toolchain_llvm/lib/clang/20/lib/darwin +# macOS AddressSanitizer must use the Xcode compiler/runtime. LLVM-instrumented +# binaries hang with LLVM's ASAN dylib and abort with Xcode's (version mismatch). +# Chained from build:asan; Apple toolchains do not match on Linux and are ignored. +build:asan_macos --extra_toolchains=@local_config_apple_cc_toolchains//:all +build:asan_macos --copt=-Wno-macro-redefined # ThreadSanitizer configuration # Usage: bazel test --config=tsan //path:target +# macOS runtime rpath is pulled in automatically via tsan_macos. +build:tsan --config=tsan_macos build:tsan --copt=-fsanitize=thread build:tsan --linkopt=-fsanitize=thread build:tsan --strip=never build:tsan --features=dbg build:tsan --compilation_mode=dbg +test:tsan --test_timeout=120,300,900,3600 +# macOS-specific ThreadSanitizer runtime lookup. +# LLVM 21.1.8's bundled libclang_rt.tsan_osx_dynamic.dylib segfaults at startup on +# current macOS; the Xcode toolchain's compiler-rt (same clang major) works with +# LLVM-instrumented binaries. Keep clang/21 aligned with MODULE.bazel / Xcode. +build:tsan_macos --linkopt=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/21/lib/darwin diff --git a/MODULE.bazel b/MODULE.bazel index e9766b27..5c02fb6e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -9,6 +9,9 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "pybind11_bazel", version = "3.0.1") bazel_dep(name = "rules_python", version = "2.0.1") bazel_dep(name = "toolchains_llvm", version = "1.7.0") +# Xcode CC toolchain for macOS AddressSanitizer builds (--config=asan). LLVM's ASAN +# runtime hangs on current macOS; see .bazelrc build:asan_macos. +bazel_dep(name = "apple_support", version = "1.24.2") # WASM builds (//examples/wasm:*, //web:dds_mvp_wasm). If you bump this version, # rebuild and run web/update_wasm.sh; see docs/wasm_build.md and patch_mvp_wasm.py. bazel_dep(name = "emsdk", version = "5.0.7") @@ -24,6 +27,10 @@ llvm.toolchain( ) use_repo(llvm, "llvm_toolchain") +# Apple toolchain repo; selected only for --config=asan via .bazelrc (not registered globally). +apple_cc_configure = use_extension("@apple_support//crosstool:setup.bzl", "apple_cc_configure_extension") +use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_toolchains") + python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( configure_coverage_tool = True, From e426b95b4af6b9dfc06d1d769c755cc224d3a579 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 14:30:06 -0400 Subject: [PATCH 136/157] Update MODULE.bazel.lock after adding apple_support for macOS ASAN. Co-authored-by: Cursor --- MODULE.bazel.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c9c77ee2..800cc995 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -260,7 +260,7 @@ }, "@@emsdk+//:emscripten_deps.bzl%emscripten_deps": { "general": { - "bzlTransitiveDigest": "reX42Ca3PEP5mFXph0pqMqqqwHJbvykl5X4FHfXD6qg=", + "bzlTransitiveDigest": "Dt5IF0PG0xjU5iMLeU6FQ1/xWvBqUgUognNk3r5pJXY=", "usagesDigest": "lqS0hMGr6MqmX63BGZOskMFcqzqO53K0UlYFxuE3QSU=", "recordedInputs": [ "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", From bfec6b37af93d8586c100c3ac5cc878e2d580a9e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 14:33:14 -0400 Subject: [PATCH 137/157] Cursor added documentation in three places: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/BUILD_SYSTEM.md — new section “AddressSanitizer and ThreadSanitizer (macOS)” covering: The three-way clang major coupling (MODULE.bazel, Xcode, .bazelrc TSAN rpath) Why ASAN uses the full Xcode toolchain vs TSAN using LLVM compile + Xcode runtime Steps when upgrading LLVM or Xcode Smoke-test commands Also corrected the outdated LLVM version note (darwin was listed as 20.1.8; it’s 21.1.8). .bazelrc — short pointers to BUILD_SYSTEM.md; TSAN rpath comment notes the hardcoded clang/21 must stay in sync. MODULE.bazel — comment on llvm_versions to update the TSAN rpath and verify Xcode when bumping LLVM. --- .bazelrc | 14 ++++++------ MODULE.bazel | 2 ++ docs/BUILD_SYSTEM.md | 51 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/.bazelrc b/.bazelrc index 05bcd8c2..bf57c0dd 100644 --- a/.bazelrc +++ b/.bazelrc @@ -20,12 +20,13 @@ build:opt --compilation_mode=opt # AddressSanitizer configuration # Usage: bazel test --config=asan //path:target -# macOS-specific settings (Xcode toolchain) are pulled in automatically via asan_macos. +# macOS uses the Xcode toolchain (build:asan_macos). See docs/BUILD_SYSTEM.md +# ("AddressSanitizer and ThreadSanitizer (macOS)") for clang/Xcode version coupling. build:asan --config=asan_macos build:asan --copt=-fsanitize=address build:asan --linkopt=-fsanitize=address -# U_FORTIFY_SOURCE conflicts with ASAN +# Undefine _FORTIFY_SOURCE; it conflicts with ASAN instrumentation. build:asan --copt=-U_FORTIFY_SOURCE build:asan --strip=never build:asan --features=dbg @@ -47,7 +48,9 @@ build:asan_macos --copt=-Wno-macro-redefined # ThreadSanitizer configuration # Usage: bazel test --config=tsan //path:target -# macOS runtime rpath is pulled in automatically via tsan_macos. +# macOS loads Xcode's compiler-rt via build:tsan_macos (rpath uses clang/N). +# Keep N aligned with MODULE.bazel llvm_versions and installed Xcode; see +# docs/BUILD_SYSTEM.md ("AddressSanitizer and ThreadSanitizer (macOS)"). build:tsan --config=tsan_macos build:tsan --copt=-fsanitize=thread build:tsan --linkopt=-fsanitize=thread @@ -55,8 +58,5 @@ build:tsan --strip=never build:tsan --features=dbg build:tsan --compilation_mode=dbg test:tsan --test_timeout=120,300,900,3600 -# macOS-specific ThreadSanitizer runtime lookup. -# LLVM 21.1.8's bundled libclang_rt.tsan_osx_dynamic.dylib segfaults at startup on -# current macOS; the Xcode toolchain's compiler-rt (same clang major) works with -# LLVM-instrumented binaries. Keep clang/21 aligned with MODULE.bazel / Xcode. +# Hardcoded rpath: .../lib/clang/21/... must match MODULE.bazel llvm major and Xcode. build:tsan_macos --linkopt=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/21/lib/darwin diff --git a/MODULE.bazel b/MODULE.bazel index 5c02fb6e..7c75ba76 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,6 +19,8 @@ bazel_dep(name = "emsdk", version = "5.0.7") llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") llvm.toolchain( name = "llvm_toolchain", + # When bumping these versions, also update .bazelrc build:tsan_macos rpath + # (clang/N) and confirm Xcode ships the same major; see docs/BUILD_SYSTEM.md. llvm_versions = { "darwin-aarch64": "21.1.8", "linux-x86_64": "21.1.8", diff --git a/docs/BUILD_SYSTEM.md b/docs/BUILD_SYSTEM.md index cc61cfcb..f261c964 100644 --- a/docs/BUILD_SYSTEM.md +++ b/docs/BUILD_SYSTEM.md @@ -5,11 +5,11 @@ and Visual Studio on Windows only. ## C++ Toolchain DDS uses `bazel-contrib/toolchains_llvm` for C++ compilation on supported -macOS, Linux and Windows hosts. The Bazel module configuration currently pins -host-specific LLVM versions in `MODULE.bazel`, including LLVM 20.1.8 for -`darwin-aarch64` and LLVM 21.1.8 for `linux-x86_64`, and registers the -downloaded toolchains via `@llvm_toolchain//:all`. Other hosts use Bazel's -default C++ toolchain resolution. +macOS, Linux and Windows hosts. The Bazel module configuration pins LLVM +**21.1.8** for `darwin-aarch64`, `linux-x86_64`, and `windows-x86_64` in +`MODULE.bazel`, and registers the downloaded toolchains via +`@llvm_toolchain//:all`. Other hosts use Bazel's default C++ toolchain +resolution. Project-specific warning and feature flags remain in `CPPVARIABLES.bzl`, while toolchain selection lives in `MODULE.bazel` and standard-language settings are @@ -29,6 +29,47 @@ If you see runtime loader failures after a toolchain or OS change: 2. Re-resolve Bazel toolchains (`bazelisk shutdown`, then `bazelisk clean --expunge`). 3. Re-run `bazelisk test //...` to confirm runtime compatibility. +### AddressSanitizer and ThreadSanitizer (macOS) + +Sanitizer builds use `--config=asan` or `--config=tsan` (see `.bazelrc`). +macOS-specific settings are chained automatically; you do not pass a separate +`asan_macos` or `tsan_macos` flag. + +**Clang / Xcode version coupling.** Three places must stay on the same **clang +major** version (currently **21**): + +| Location | Role | +|----------|------| +| `MODULE.bazel` → `llvm_versions` | Hermetic LLVM used for normal builds and for TSAN instrumentation | +| Installed Xcode (`xcrun clang++ --version`) | ASAN compiler/runtime; TSAN `compiler-rt` dylib | +| `.bazelrc` → `build:tsan_macos` rpath (`.../lib/clang/21/lib/darwin`) | Lets LLVM-instrumented TSAN binaries load Xcode's TSAN runtime | + +**ASAN (`--config=asan`).** macOS builds select the Xcode CC toolchain via +`apple_support` (`build:asan_macos`). LLVM's bundled ASAN runtime hangs at +startup; mixing LLVM-instrumented objects with Xcode's ASAN dylib aborts with +a version mismatch. Using Xcode for the full compile/link avoids that. + +**TSAN (`--config=tsan`).** Code is still compiled with the hermetic LLVM +toolchain; only the **runtime** comes from Xcode's `compiler-rt`. LLVM's TSAN +dylib segfaults on current macOS, but Xcode's dylib works with LLVM-instrumented +binaries when the clang major matches. + +**When upgrading LLVM or Xcode**, update all coupled paths together: + +1. Bump `llvm_versions` in `MODULE.bazel` (and run `bazel mod deps` to refresh + `MODULE.bazel.lock`). +2. Update the `clang/N` segment in `build:tsan_macos` in `.bazelrc` to match the + new major version. +3. Confirm Xcode ships that major: + `xcrun clang++ --version` + `xcrun clang -print-file-name=libclang_rt.tsan_osx_dynamic.dylib` +4. Re-run sanitizer smoke tests, e.g. + `bazel test --config=asan //library/tests/system:context_tt_facade_test` + `bazel test --config=tsan //library/tests/system:thread_safety_stress_test` + +If TSAN fails to start after an Xcode upgrade (dyld cannot load the runtime), +the rpath major is likely out of sync with the installed toolchain. + ## Visual Studio and Rider Build From 7eb38194ad26b7a2f3cc52a5d3b951847c8e56ea Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 14:37:35 -0400 Subject: [PATCH 138/157] Update docs to remove the --define=asan=true option. --- .github/copilot-instructions.md | 12 ++++++------ BUILD.bazel | 6 ------ CPPVARIABLES.bzl | 6 ------ docs/BUILD_SYSTEM.md | 1 + 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 67694860..7a482c61 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -41,8 +41,8 @@ bazel build -c opt //... # Build with debug symbols bazel build -c dbg //... -# Build with specific flags (example: enable ASAN) -bazel build --define=asan=true //... +# Build with AddressSanitizer (see .bazelrc build:asan and docs/BUILD_SYSTEM.md) +bazel build --config=asan //... ``` ### Build Time Expectations @@ -200,9 +200,9 @@ If CI fails: Before finalizing changes, optionally run: ```bash -# Check for memory leaks (Linux only) -bazel build --define=asan=true //... -bazel test --define=asan=true //... +# Check for memory leaks (see docs/BUILD_SYSTEM.md for macOS/Linux notes) +bazel build --config=asan //... +bazel test --config=asan //... # Performance test with real hands bazel build //library/tests:dtest @@ -276,7 +276,7 @@ bazel build //library/tests:dtest **Problem**: Tests fail with "Segmentation fault" - **Cause**: Usually memory management bug or uninitialized variable -- **Fix**: Run with ASAN: `bazel test --define=asan=true //library/tests:failing_test` +- **Fix**: Run with ASAN: `bazel test --config=asan //library/tests:failing_test` **Problem**: Tests fail with different results than expected - **Cause**: Possibly heuristic sorting or transposition table issues diff --git a/BUILD.bazel b/BUILD.bazel index d3263ea2..7399be18 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -68,12 +68,6 @@ config_setting( define_values = {"tt_reset_debug": "true"}, ) -# Enable AddressSanitizer via --define=asan=true -config_setting( - name = "asan", - define_values = {"asan": "true"}, -) - # Matches cc_* targets built under the wasm_cc_binary platform transition. # Use platform constraints (not deprecated `cpu` select) so this works # consistently across host OSes, including Windows. diff --git a/CPPVARIABLES.bzl b/CPPVARIABLES.bzl index df464e52..3e1dbb71 100644 --- a/CPPVARIABLES.bzl +++ b/CPPVARIABLES.bzl @@ -63,9 +63,6 @@ DDS_CPPOPTS = select({ "//conditions:default": [ "-std=c++20" ], -}) + select({ - "//:asan": ["-fsanitize=address"], - "//conditions:default": [], }) DDS_LOCAL_DEFINES = select({ @@ -92,9 +89,6 @@ DDS_LINKOPTS = select({ "//:build_linux": [], "//:debug_build_linux": [], "//conditions:default": [], -}) + select({ - "//:asan": ["-fsanitize=address"], - "//conditions:default": [], }) # Per-target define to enable scheduler timing when desired. diff --git a/docs/BUILD_SYSTEM.md b/docs/BUILD_SYSTEM.md index f261c964..69ce2845 100644 --- a/docs/BUILD_SYSTEM.md +++ b/docs/BUILD_SYSTEM.md @@ -32,6 +32,7 @@ If you see runtime loader failures after a toolchain or OS change: ### AddressSanitizer and ThreadSanitizer (macOS) Sanitizer builds use `--config=asan` or `--config=tsan` (see `.bazelrc`). +Do not use `--define=asan=true`; that path is not supported. macOS-specific settings are chained automatically; you do not pass a separate `asan_macos` or `tsan_macos` flag. From d8c0ec12968d7bc695a73cba8c53222bd87ad206 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 14:45:39 -0400 Subject: [PATCH 139/157] Add ASan and TSan jobs for CI. --- .github/workflows/ci_linux.yml | 48 ++++++++++++++++++++++++++++++++++ .github/workflows/ci_macos.yml | 27 +++++++++++++++++++ docs/BUILD_SYSTEM.md | 9 +++++++ 3 files changed, 84 insertions(+) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 11dae6cd..e65ebec1 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -41,3 +41,51 @@ jobs: path: bazel-testlogs/ if-no-files-found: ignore retention-days: 30 + + asan: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Bazelisk + uses: bazel-contrib/setup-bazel@0.19.0 + with: + bazelisk-version: "1.29.0" + + - name: Run tests (AddressSanitizer) + run: bazel test --config=asan --verbose_failures --test_output=errors //library/tests/... + + - name: Upload ASAN test logs + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-linux-asan + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 + + tsan: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Bazelisk + uses: bazel-contrib/setup-bazel@0.19.0 + with: + bazelisk-version: "1.29.0" + + - name: Run system tests (ThreadSanitizer) + run: bazel test --config=tsan --verbose_failures --test_output=errors //library/tests/system/... + + - name: Upload TSAN test logs + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-linux-tsan + path: bazel-testlogs/ + if-no-files-found: ignore + retention-days: 30 diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index 90a6d117..cdc76094 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -39,4 +39,31 @@ jobs: name: bazel-test-logs-macos path: bazel-testlogs/ if-no-files-found: ignore + retention-days: 30 + + sanitizers: + runs-on: macos-26 + timeout-minutes: 90 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Bazelisk + uses: bazel-contrib/setup-bazel@0.19.0 + with: + bazelisk-version: "1.29.0" + + - name: Run system tests (AddressSanitizer) + run: bazelisk test --config=asan --verbose_failures --test_output=errors //library/tests/system/... + + - name: Run system tests (ThreadSanitizer) + run: bazelisk test --config=tsan --verbose_failures --test_output=errors //library/tests/system/... + + - name: Upload sanitizer test logs + if: always() + uses: actions/upload-artifact@v6 + with: + name: bazel-test-logs-macos-sanitizers + path: bazel-testlogs/ + if-no-files-found: ignore retention-days: 30 \ No newline at end of file diff --git a/docs/BUILD_SYSTEM.md b/docs/BUILD_SYSTEM.md index 69ce2845..fc8659f6 100644 --- a/docs/BUILD_SYSTEM.md +++ b/docs/BUILD_SYSTEM.md @@ -71,6 +71,15 @@ binaries when the clang major matches. If TSAN fails to start after an Xcode upgrade (dyld cannot load the runtime), the rpath major is likely out of sync with the installed toolchain. +**CI.** GitHub Actions runs sanitizer jobs on pull requests to `main` and +`develop`: + +| Workflow | Job | Command | +|----------|-----|---------| +| `ci_linux.yml` | `asan` | `bazel test --config=asan //library/tests/...` | +| `ci_linux.yml` | `tsan` | `bazel test --config=tsan //library/tests/system/...` | +| `ci_macos.yml` | `sanitizers` | ASAN and TSAN on `//library/tests/system/...` (validates macOS toolchain/rpath wiring) | + ## Visual Studio and Rider Build From bf3f99c263faa9c86b09e54ef47fe7a7c8795472 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 15:26:49 -0400 Subject: [PATCH 140/157] Add a CI timeout on all platforms. --- .github/workflows/ci_linux.yml | 5 +++-- .github/workflows/ci_macos.yml | 3 ++- .github/workflows/ci_wasm.yml | 1 + .github/workflows/ci_windows.yml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index e65ebec1..870fd207 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -9,6 +9,7 @@ on: jobs: build_and_test: runs-on: ubuntu-latest + timeout-minutes: 20 steps: # 1️⃣ Checkout the repository - name: Checkout repository @@ -44,7 +45,7 @@ jobs: asan: runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v6 @@ -68,7 +69,7 @@ jobs: tsan: runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v6 diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index cdc76094..248d2993 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -8,6 +8,7 @@ on: jobs: build_and_test: runs-on: macos-26 + timeout-minutes: 20 steps: # Checkout the repository - name: Checkout repository @@ -43,7 +44,7 @@ jobs: sanitizers: runs-on: macos-26 - timeout-minutes: 90 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v6 diff --git a/.github/workflows/ci_wasm.yml b/.github/workflows/ci_wasm.yml index 8e5bf543..1bdcf3ae 100644 --- a/.github/workflows/ci_wasm.yml +++ b/.github/workflows/ci_wasm.yml @@ -9,6 +9,7 @@ on: jobs: build_and_test: runs-on: ubuntu-latest + timeout-minutes: 20 steps: # 1️⃣ Checkout the repository - name: Checkout repository diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index d228b98d..fda2dc27 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -8,6 +8,7 @@ on: jobs: build_and_test: runs-on: windows-latest + timeout-minutes: 20 steps: # Checkout the repository - name: Checkout repository From 66d823dfab8e4f0b36a88aaf8c682a9eea41b480 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 15:53:04 -0400 Subject: [PATCH 141/157] Fix issues identified by local ASan test. --- library/src/heuristic_sorting/heuristic_sorting.cpp | 2 +- library/src/moves/moves.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 97bbe15f..449c0edf 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -883,7 +883,7 @@ int rank_forces_ace(const HeuristicContext& ctx, const int cards4th) while (g >= 1 && ((mp.gap_[g] & removed) == mp.gap_[g])) g--; - if (! g) + if (g <= 0) return -1; // RHO's second-highest rank. diff --git a/library/src/moves/moves.cpp b/library/src/moves/moves.cpp index b4a99587..00a42101 100644 --- a/library/src/moves/moves.cpp +++ b/library/src/moves/moves.cpp @@ -72,6 +72,8 @@ Moves::Moves() { 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; From f348664ca5a74bc8c56dfb4b15ef02d09e484c80 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 16:17:40 -0400 Subject: [PATCH 142/157] Fix AddressSanitizer (ASAN) ODR (One Definition Rule) violations by marking global arrays as static. Commented out unused SORT_SOLVE_STRENGTH and SORT_SOLVE_STRENGTH_CUTOFF --- library/src/system/scheduler.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/library/src/system/scheduler.cpp b/library/src/system/scheduler.cpp index 09ad1add..1cefb850 100644 --- a/library/src/system/scheduler.cpp +++ b/library/src/system/scheduler.cpp @@ -476,24 +476,24 @@ bool Scheduler::SameHand( // that they scale somewhat proportionally to other cases. // The strength parameter is currently not used. -int SORT_SOLVE_TIMES[2][8] = +static int SORT_SOLVE_TIMES[2][8] = { { 284000, 91000, 37000, 23000, 17000, 15000, 13000, 4000 }, { 388000, 140000, 60000, 40000, 30000, 23000, 18000, 6000 }, }; -#define SORT_SOLVE_STRENGTH_CUTOFF 0 - -double SORT_SOLVE_STRENGTH[2][3] = -{ - { 1.525, 1.810, 0.0285 }, - { 1.585, 1.940, 0.0354 } -}; +// #define SORT_SOLVE_STRENGTH_CUTOFF 0 +// +// static double SORT_SOLVE_STRENGTH[2][3] = +// { +// { 1.525, 1.810, 0.0285 }, +// { 1.585, 1.940, 0.0354 } +// }; // Lower end of linear, upper end of linear, slope of linear, // exponential start, coefficient. -double SORT_SOLVE_FANOUT[2][5] = +static double SORT_SOLVE_FANOUT[2][5] = { { 30., 50., 0.07577, 1.515, 12. }, { 30., 50., 0.08144, 1.629, 12. } @@ -569,7 +569,7 @@ void Scheduler::SortSolve() // Lower end of linear, upper end of linear, slope of linear, // exponential start, coefficient. -double SORT_CALC_FANOUT[2][5] = +static double SORT_CALC_FANOUT[2][5] = { { 30., 50., 0.07812, 1.563, 13. }, { 30., 50., 0.07739, 1.548, 12. } @@ -625,7 +625,7 @@ void Scheduler::SortCalc() // These are specific times from a 12-core PC. The hope is // that they scale somewhat proportionally to other cases. -int SORT_TRACE_TIMES[2][8] = +static int SORT_TRACE_TIMES[2][8] = { { 157000, 47000, 26000, 18000, 16000, 14000, 10000, 6000 }, { 205000, 87000, 45000, 36000, 32000, 28000, 24000, 20000 }, @@ -636,7 +636,7 @@ int SORT_TRACE_TIMES[2][8] = // Slope between 16 and 48 incl // Average for 49-52 -double SORT_TRACE_DEPTH[2][4] = +static double SORT_TRACE_DEPTH[2][4] = { { 0.742, 0.411, 0.0414, 1.820 }, { 0.669, 0.428, 0.0346, 1.606 } @@ -645,7 +645,7 @@ double SORT_TRACE_DEPTH[2][4] = // Lower end of linear, upper end of linear, slope of linear, // exponential start, coefficient. -double SORT_TRACE_FANOUT[2][5] = +static double SORT_TRACE_FANOUT[2][5] = { { 30., 50., 0.07577, 1.515, 12. }, { 30., 50., 0.08166, 1.633, 13. } From 73faa5b20ff41ef8ae95df66b16223fa10501959 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 16:31:26 -0400 Subject: [PATCH 143/157] Moved mtx and mtxPrint into an anonymous namespace to address ASan failure. --- library/src/system/thread_mgr.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/src/system/thread_mgr.cpp b/library/src/system/thread_mgr.cpp index 0b3a9b39..a5a1d6b3 100644 --- a/library/src/system/thread_mgr.cpp +++ b/library/src/system/thread_mgr.cpp @@ -17,8 +17,12 @@ #include "thread_mgr.hpp" -mutex mtx; -mutex mtxPrint; +namespace { + +std::mutex mtx; +std::mutex mtxPrint; + +} // namespace ThreadMgr ThreadMgr::single_instance; From a5e29e9f308a192b6cbcdf020241b0bdd58b4b4e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 18 Jun 2026 16:47:39 -0400 Subject: [PATCH 144/157] Remove duplicate ThreadMgr to address an ASan error. Per Cursor: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BUILD change ensures a stats test links only system_util_stats, not system + system_util_stats. One system variant → one ThreadMgr → one set of mutexes. That matches the intent: a process-wide thread manager. --- library/src/solver_context/BUILD.bazel | 4 ++-- library/src/system/BUILD.bazel | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/src/solver_context/BUILD.bazel b/library/src/solver_context/BUILD.bazel index a566211b..f6dcb422 100644 --- a/library/src/solver_context/BUILD.bazel +++ b/library/src/solver_context/BUILD.bazel @@ -26,7 +26,7 @@ cc_library( includes = ["."], include_prefix = "solver_context", deps = [ - "//library/src/system", + "//library/src/system:system_util_log", "//library/src/api:api_definitions", "//library/src/trans_table", ], @@ -43,7 +43,7 @@ cc_library( includes = ["."], include_prefix = "solver_context", deps = [ - "//library/src/system", + "//library/src/system:system_util_stats", "//library/src/api:api_definitions", "//library/src/trans_table", ], diff --git a/library/src/system/BUILD.bazel b/library/src/system/BUILD.bazel index 995aacc5..a647463c 100644 --- a/library/src/system/BUILD.bazel +++ b/library/src/system/BUILD.bazel @@ -1,6 +1,8 @@ load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES", "DDS_SCHEDULER_DEFINE") load("@rules_cc//cc:defs.bzl", "cc_library") +# system, system_util_log, and system_util_stats compile the same sources with +# different feature defines. Link at most one variant into a binary. cc_library( name = "system", srcs = glob(["*.cpp"]), From e6bfbee60d38f517c9b018a84408b1f47587dc3f Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 25 Jun 2026 16:59:36 -0500 Subject: [PATCH 145/157] Enable DDS_AB_STATS via --define=ab_stats=true. Wire a Bazel config_setting into DDS_LOCAL_DEFINES so AB stats can be turned on without --cxxopt=-DDDS_AB_STATS; refresh MODULE.bazel.lock. Co-authored-by: Cursor --- BUILD.bazel | 6 ++++++ CPPVARIABLES.bzl | 3 +++ library/src/utility/debug.h | 1 + 3 files changed, 10 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index 7399be18..1799dab3 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -56,6 +56,12 @@ config_setting( define_values = {"scheduler": "true"}, ) +# Enable DDS_AB_STATS via --define=ab_stats=true +config_setting( + name = "ab_stats", + define_values = {"ab_stats": "true"}, +) + # Enable DDS_TT_CONTEXT_OWNERSHIP via --define=tt_context_ownership=true config_setting( name = "tt_context_ownership", diff --git a/CPPVARIABLES.bzl b/CPPVARIABLES.bzl index 3e1dbb71..6055bd8f 100644 --- a/CPPVARIABLES.bzl +++ b/CPPVARIABLES.bzl @@ -81,6 +81,9 @@ DDS_LOCAL_DEFINES = select({ }) + select({ "//:tt_reset_debug": ["DDS_DEBUG_TT_RESET"], "//conditions:default": [], +}) + select({ + "//:ab_stats": ["DDS_AB_STATS"], + "//conditions:default": [], }) DDS_LINKOPTS = select({ diff --git a/library/src/utility/debug.h b/library/src/utility/debug.h index fb5623b6..b0635547 100644 --- a/library/src/utility/debug.h +++ b/library/src/utility/debug.h @@ -53,6 +53,7 @@ /// @brief Enable AB search statistics (node counts, timing, etc.). /// Records alpha-beta search performance metrics for optimization analysis. +/// Enable via Bazel: --define=ab_stats=true // #define DDS_AB_STATS #define DDS_AB_STATS_PREFIX "ABstats" From cfceffa64062ecb626e746d9daffb5636eee0542 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 25 Jun 2026 16:24:10 -0500 Subject: [PATCH 146/157] Honor dtest -n in loop_calc via CalcAllTablesPBNN. Pass dtest_effective_threads through to calc batches so calc threading matches solve and responds to the -n flag. Co-authored-by: Cursor --- library/tests/loop.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 8c564e03..d7644a97 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -119,6 +119,12 @@ bool loop_calc( #endif int filter[5] = {0, 0, 0, 0, 0}; + int strain_count = 0; + for (int k = 0; k < DDS_STRAINS; k++) + { + if (!filter[k]) + strain_count++; + } for (int i = 0; i < number; i += stepsize) { @@ -128,7 +134,9 @@ bool loop_calc( strcpy(dealsp->deals[j].cards, deal_list[i+j].remainCards); timer.start(count); - const int ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp); + const int workload = count * strain_count; + const int threads = dtest_effective_threads(options.num_threads_, workload); + const int ret = CalcAllTablesPBNN(dealsp, -1, filter, resp, parp, threads); if (ret != RETURN_NO_FAULT) { cout << "loop_calc: i " << i << ", return " << ret << "\n"; From 5188c460a8d3d9086b2c4802469e1e49583540b2 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 26 Jun 2026 12:57:54 -0500 Subject: [PATCH 147/157] Stop passing -n to SetResources in dtest drivers. SetResources ignores maxThreads; threading is handled via dtest_effective_threads and the *N batch APIs. Passing -n only triggered a misleading library warning. itest.cpp will be removed in a future PR. --- library/tests/dtest.cpp | 2 +- library/tests/itest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/tests/dtest.cpp b/library/tests/dtest.cpp index 1116fb1f..d26abdfa 100644 --- a/library/tests/dtest.cpp +++ b/library/tests/dtest.cpp @@ -28,7 +28,7 @@ int main(int argc, char * argv[]) { read_args(argc, argv); - SetResources(options.memory_mb_, options.num_threads_); + SetResources(options.memory_mb_, 0); DDSInfo info; GetDDSInfo(&info); diff --git a/library/tests/itest.cpp b/library/tests/itest.cpp index 8e63b9ec..2eb6bbd2 100644 --- a/library/tests/itest.cpp +++ b/library/tests/itest.cpp @@ -28,7 +28,7 @@ int main(int argc, char * argv[]) { read_args(argc, argv); - SetResources(options.memory_mb_, options.num_threads_); + SetResources(options.memory_mb_, 0); DDSInfo info; GetDDSInfo(&info); From 66e297cf7cfe5945d7ca2d41a9ce8970b4a69deb Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 26 Jun 2026 19:35:55 +0100 Subject: [PATCH 148/157] Simplify Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- library/tests/loop.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index d7644a97..8c98165e 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -119,12 +119,7 @@ bool loop_calc( #endif int filter[5] = {0, 0, 0, 0, 0}; - int strain_count = 0; - for (int k = 0; k < DDS_STRAINS; k++) - { - if (!filter[k]) - strain_count++; - } + const int strain_count = DDS_STRAINS; for (int i = 0; i < number; i += stepsize) { From 34f8d667fd586b5e1ef59ea25bd01ce72dfe7568 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 26 Jun 2026 20:10:15 +0100 Subject: [PATCH 149/157] Use named constant Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- library/tests/loop.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 8c98165e..7dbeb6a8 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -118,9 +118,8 @@ bool loop_calc( setw(25) << right << "Time" << "\n"; #endif - int filter[5] = {0, 0, 0, 0, 0}; + int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; const int strain_count = DDS_STRAINS; - for (int i = 0; i < number; i += stepsize) { int count = (i + stepsize > number ? number - i : stepsize); From 7724718988f394a6e945123197c46e06ca381e4e Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Fri, 26 Jun 2026 20:33:23 -0500 Subject: [PATCH 150/157] Remove unused itest.cpp test driver. The file was excluded from the dtest build, had no Bazel target, and referenced a nonexistent realMain entry point. Co-authored-by: Cursor --- library/tests/BUILD.bazel | 1 - library/tests/args.hpp | 2 +- library/tests/itest.cpp | 47 --------------------------------------- 3 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 library/tests/itest.cpp diff --git a/library/tests/BUILD.bazel b/library/tests/BUILD.bazel index 0781deee..af1d80a7 100644 --- a/library/tests/BUILD.bazel +++ b/library/tests/BUILD.bazel @@ -7,7 +7,6 @@ filegroup( srcs = glob( ["*.cpp", "*.hpp"], exclude = [ - "itest.cpp", "context_equivalence.cpp", "calc_par_test.cpp", # Uses GoogleTest, compiled separately ], diff --git a/library/tests/args.hpp b/library/tests/args.hpp index d8eeea68..fa341a1f 100644 --- a/library/tests/args.hpp +++ b/library/tests/args.hpp @@ -13,7 +13,7 @@ /// @brief Command-line argument parsing for test utilities. /// /// Provides functions to parse and validate command-line options -/// for test driver programs (dtest, itest). Options include: +/// for the dtest driver program. Options include: /// - Input file specification /// - Solver type selection (solve, calc, play, par, dealer_par) /// - Number of threads diff --git a/library/tests/itest.cpp b/library/tests/itest.cpp deleted file mode 100644 index 2eb6bbd2..00000000 --- a/library/tests/itest.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - DDS, a bridge double dummy solver. - - Copyright (C) 2006-2014 by Bo Haglund / - 2014-2018 by Bo Haglund & Soren Hein. - - See LICENSE and README. -*/ - - -#include -#include -#include - -#include -#include "testcommon.hpp" -#include "args.hpp" -#include "cst.hpp" - -using std::cout; -using std::endl; -using std::setw; - -OptionsType options; - - -int main(int argc, char * argv[]) -{ - read_args(argc, argv); - - SetResources(options.memory_mb_, 0); - - DDSInfo info; - GetDDSInfo(&info); - cout << info.systemString << endl; - - realMain(argc, argv); - -#ifdef DDS_SCHEDULER - scheduler.PrintTiming(); -#endif - - FreeMemory(); - - exit(0); -} - From 8ec7a472d519bbb660a75695912120513b06509a Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 25 Jun 2026 18:02:39 -0500 Subject: [PATCH 151/157] Fix DumpRetrieved call under debug_all builds. Dereference the transposition-table NodeCards pointer to match the DumpRetrieved(const NodeCards&) signature when DDS_AB_HITS is enabled. Co-authored-by: Cursor --- library/src/ab_search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/ab_search.cpp b/library/src/ab_search.cpp index fbc4e8fd..762d75f0 100644 --- a/library/src/ab_search.cpp +++ b/library/src/ab_search.cpp @@ -225,7 +225,7 @@ static bool ab_search_0_ctx( { #ifdef DDS_AB_HITS DumpRetrieved(thrp->fileRetrieved.GetStream(), - * posPoint, cardsP, target, depth); + * posPoint, *cardsP, target, depth); #endif for (int ss = 0; ss < DDS_SUITS; ss++) From 3462d8c8a9ab6bfe2dbf3bce46b85bca0459fb02 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 25 Jun 2026 18:06:27 -0500 Subject: [PATCH 152/157] Restore DumpRetrieved and DumpStored for debug_all builds. Re-add the DDS_AB_HITS dump helpers in dump.cpp so debug_all links succeed when ab_search logs transposition-table hits. Co-authored-by: Cursor --- library/src/dump.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/library/src/dump.cpp b/library/src/dump.cpp index 860333ed..eb7419c7 100644 --- a/library/src/dump.cpp +++ b/library/src/dump.cpp @@ -15,6 +15,7 @@ #include "dump.hpp" #include #include +#include std::string PrintSuit(const unsigned short suitCode); @@ -335,3 +336,56 @@ void DumpTopLevel( 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) +{ + 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) +{ + 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) +{ + 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 + From 87a0b26fc3e7565db9a2a179fa1eb2e9cfb1fefa Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sat, 27 Jun 2026 18:51:04 -0500 Subject: [PATCH 153/157] Fix trump-void1 move ordering to restore v2.9 search efficiency The heuristic extraction refactor changed weight_alloc_trump_void1's first branch from `lead_suit == trump` to `suit == trump`. Since that is exhaustive with the following `else if (suit != trump)`, the three ruffing branches (using the `24 - rank + ...` formula) became dead code, and trump ruffs were scored with side-suit discard weights instead. This mis-ordered ruffs, costing alpha-beta cutoffs. The effect is small for solve but compounds heavily in calc's warm-TT iterative deepening: calc explored ~34% more nodes than v2.9. Restoring the original `lead_suit == trump` pitch branch makes the ruffing branches reachable again and cuts calc time ~25% (gap to v2.9: 1.37x -> 1.02x). Ordering-only change; double-dummy results are unchanged. Co-authored-by: Cursor --- .../heuristic_sorting/heuristic_sorting.cpp | 48 ++++--------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 449c0edf..95018f32 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -676,49 +676,17 @@ void weight_alloc_trump_void1(HeuristicContext& ctx) unsigned short suitCount = tpos.length[curr_hand][suit]; int suitAdd; - if (suit == trump) + if (lead_suit == trump) // We pitch { - // We trump a non-trump card. - - 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])) || - ((tpos.length[rho_lh][lead_suit] == 0) && - (tpos.length[rho_lh][trump] != 0))) - { - // Partner can win with a card or by ruffing. - suitAdd = 60 + (suitCount << 6) / 44; - } - else - { - suitAdd = -2 + (suitCount << 6) / 36; - // Don't ruff 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) && - (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; - } + 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. + suitAdd = (suitCount << 6) / 44; else { - suitAdd = -2 + (suitCount << 6) / 36; - // Don't ruff from Kx. + // Don't pitch from Kx. + suitAdd = (suitCount << 6) / 36; if ((suitCount == 2) && (tpos.second_best[suit].hand == curr_hand)) suitAdd += -4; From 4cc00736662f1bd1c88e397b385274495b072f23 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 28 Jun 2026 01:28:03 +0100 Subject: [PATCH 154/157] Fix incorrect comment Per Copilot. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- library/src/heuristic_sorting/heuristic_sorting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 95018f32..88478881 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -681,7 +681,7 @@ void weight_alloc_trump_void1(HeuristicContext& ctx) 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. + // RHO can win. suitAdd = (suitCount << 6) / 44; else { From 75a67cdd624629532b1b93ce68f20ae6fadf0105 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Sun, 28 Jun 2026 07:45:52 -0500 Subject: [PATCH 155/157] Fix signed->unsigned cast bugs that corrupted move ordering and pruning The heuristic/quick-tricks refactor introduced static_cast wrappers on values that v2.9 used as signed, changing search behavior: - make_3 / make_3_ctx: winner[]/second_best[] .hand and .rank were cast to unsigned char, turning the -1 "no card" sentinel into 255. This broke winner[trump].hand == -1 style checks in QuickTricks, losing cutoffs. - weight_alloc_trump_void2 / _void3: rel_rank[aggr[suit]][...] indexed through static_cast(aggr[suit]), truncating the 13-bit aggregate holding to 8 bits and reading the wrong rel_rank row. - QuickTricksPartnerHand{Trump,NT}: bit_map_rank index cast the signed rank through unsigned char. With these reverted to v2.9's signed handling, the per-move-generation ordering trace now matches v2.9 exactly (0 divergences on list1), closing the residual calc gap to parity. Ordering/pruning-only change; double-dummy results are unchanged and all library tests pass. Co-authored-by: Cursor --- library/src/ab_search.cpp | 16 ++++++++-------- .../src/heuristic_sorting/heuristic_sorting.cpp | 15 +++------------ library/src/quick_tricks.cpp | 4 ++-- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/library/src/ab_search.cpp b/library/src/ab_search.cpp index 762d75f0..c5b8ee96 100644 --- a/library/src/ab_search.cpp +++ b/library/src/ab_search.cpp @@ -878,10 +878,10 @@ void make_3( int aggr = posPoint->aggr[st]; - posPoint->winner[st].rank = static_cast(thrp->rel[aggr].abs_rank[1][st].rank); - posPoint->winner[st].hand = static_cast(thrp->rel[aggr].abs_rank[1][st].hand); - posPoint->second_best[st].rank = static_cast(thrp->rel[aggr].abs_rank[2][st].rank); - posPoint->second_best[st].hand = static_cast(thrp->rel[aggr].abs_rank[2][st].hand); + 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; } } @@ -944,10 +944,10 @@ static void make_3_ctx( int aggr = posPoint->aggr[st]; - posPoint->winner[st].rank = static_cast(thrp->rel[aggr].abs_rank[1][st].rank); - posPoint->winner[st].hand = static_cast(thrp->rel[aggr].abs_rank[1][st].hand); - posPoint->second_best[st].rank = static_cast(thrp->rel[aggr].abs_rank[2][st].rank); - posPoint->second_best[st].hand = static_cast(thrp->rel[aggr].abs_rank[2][st].hand); + 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; } } diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 88478881..8ddd0a2d 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -1213,10 +1213,7 @@ void weight_alloc_trump_void2(HeuristicContext& ctx) mply[k].rank < ctx.move1_rank) { // Don't underruff. - unsigned char aggrSuit = static_cast(tpos.aggr[suit]); - unsigned char moveRank = static_cast(mply[k].rank); - unsigned char relRankValue = static_cast(rel_rank[aggrSuit][moveRank]); - int r_rank = static_cast(relRankValue); + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; suitAdd = (suitCount << 6) / 40; mply[k].weight = -32 + r_rank + suitAdd; } @@ -1386,10 +1383,7 @@ void weight_alloc_trump_void3(HeuristicContext& ctx) { for (int k = last_num_moves; k < num_moves; k++) { - int r_rank = static_cast( - static_cast( - rel_rank[static_cast(tpos.aggr[suit])] - [static_cast(mply[k].rank)])); + 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 @@ -1404,10 +1398,7 @@ void weight_alloc_trump_void3(HeuristicContext& ctx) { for (int k = last_num_moves; k < num_moves; k++) { - int r_rank = static_cast( - static_cast( - rel_rank[static_cast(tpos.aggr[suit])] - [static_cast(mply[k].rank)])); + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; mply[k].weight = 33 + r_rank; } } diff --git a/library/src/quick_tricks.cpp b/library/src/quick_tricks.cpp index 0c161406..48f37fe5 100644 --- a/library/src/quick_tricks.cpp +++ b/library/src/quick_tricks.cpp @@ -1000,7 +1000,7 @@ int QuickTricksPartnerHandTrump( if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank)) ]; + static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; tpos.win_ranks[depth][commSuit] |= bit_map_rank[commRank]; @@ -1110,7 +1110,7 @@ int QuickTricksPartnerHandNT( if (ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].hand == partner[hand]) { tpos.win_ranks[depth][suit] |= bit_map_rank[ - static_cast(static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank)) ]; + static_cast(ctx.thread_ptr()->rel[ranks].abs_rank[3][suit].rank) ]; qt++; if (qt >= cutoff) return qt; From 72d08dafa2ca0c3dde72aa692a7484a23f0d6842 Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 2 Jul 2026 23:49:58 -0400 Subject: [PATCH 156/157] Rebased against develop. --- library/src/BUILD.bazel | 41 +-- library/src/calc_tables.cpp | 243 ++++++++++++------ .../heuristic_sorting/heuristic_sorting.cpp | 4 +- library/src/init.cpp | 55 ++-- library/src/solver_if.cpp | 11 +- library/src/system/file.cpp | 26 +- library/tests/loop.cpp | 25 +- library/tests/testcommon.cpp | 10 +- 8 files changed, 235 insertions(+), 180 deletions(-) diff --git a/library/src/BUILD.bazel b/library/src/BUILD.bazel index 1533b8fd..730bc3b7 100644 --- a/library/src/BUILD.bazel +++ b/library/src/BUILD.bazel @@ -1,4 +1,4 @@ -load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES", "DDS_SCHEDULER_DEFINE") +load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES") load("@rules_cc//cc:defs.bzl", "cc_library") external_headers = [ @@ -6,21 +6,6 @@ external_headers = [ "dds.hpp", ] -cc_library( - name = "ab_stats", - srcs = ["ab_stats.cpp"], - hdrs = ["ab_stats.hpp"], - includes = ["."], - copts = DDS_CPPOPTS, - linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES, - visibility = [ - "//library/src:__subpackages__", - "//library/tests:__subpackages__", - ], - deps = ["//library/src/utility:constants"], -) - filegroup( name = "dds_sources", srcs = glob([ @@ -28,10 +13,7 @@ filegroup( "*.hpp", "*.h", ], - exclude = external_headers + [ - "ab_stats.cpp", - "ab_stats.hpp", - ], + exclude = external_headers, ) ) @@ -47,7 +29,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, + local_defines = DDS_LOCAL_DEFINES, visibility = ["//visibility:public"], include_prefix = "dds", deps = [ @@ -57,7 +39,6 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", - "//library/src:ab_stats", "//library/src/system", "//library/src/solver_context:solver_context", ], @@ -65,10 +46,9 @@ cc_library( filegroup( name = "testable_dds_sources", - srcs = glob( - ["*.cpp"], - exclude = ["ab_stats.cpp"], - ), + srcs = glob([ + "*.cpp", + ],), visibility = [ "//examples:__pkg__", ], @@ -91,7 +71,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, + local_defines = DDS_LOCAL_DEFINES, include_prefix = "dds", visibility = [ "//:__pkg__", # allow root package to wrap/export @@ -110,7 +90,6 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", - "//library/src:ab_stats", "//library/src/system", "//library/src/solver_context:solver_context", ], @@ -124,7 +103,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, + local_defines = DDS_LOCAL_DEFINES, include_prefix = "dds", visibility = [ "//:__pkg__", @@ -137,7 +116,6 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", - "//library/src:ab_stats", "//library/src/system:system_util_log", "//library/src/solver_context:solver_context_log", ], @@ -150,7 +128,7 @@ cc_library( includes = ["."], copts = DDS_CPPOPTS, linkopts = DDS_LINKOPTS, - local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE, + local_defines = DDS_LOCAL_DEFINES, include_prefix = "dds", visibility = [ "//:__pkg__", @@ -163,7 +141,6 @@ cc_library( "//library/src/heuristic_sorting", "//library/src/trans_table", "//library/src/moves:moves", - "//library/src:ab_stats", "//library/src/system:system_util_stats", "//library/src/solver_context:solver_context_stats", ], diff --git a/library/src/calc_tables.cpp b/library/src/calc_tables.cpp index b0446fe8..4ff9fc45 100644 --- a/library/src/calc_tables.cpp +++ b/library/src/calc_tables.cpp @@ -8,6 +8,10 @@ */ #include "calc_tables.hpp" +#include +#include +#include +#include #include #include @@ -15,19 +19,144 @@ #include #include #include -#include #include #include extern Memory memory; extern Scheduler scheduler; +extern System sysdep; + +namespace { + +struct CalcRunParam +{ + Boards* bop = nullptr; + SolvedBoards* solvedp = nullptr; + std::atomic error{RETURN_NO_FAULT}; +}; + +struct CalcThreadPool +{ + std::mutex mu; + std::vector> slots; +}; + +CalcThreadPool& calc_thread_pool() +{ + static CalcThreadPool pool; + return pool; +} + +auto solver_config_for_thread(const unsigned thr_id) -> SolverConfig +{ + SolverConfig cfg; + if (thr_id < memory.NumThreads() && memory.ThreadSize(thr_id) == "S") + cfg.tt_kind_ = TTKind::Small; + return cfg; +} + +void ensure_calc_contexts(const int num_threads) +{ + if (num_threads <= 0) + return; + + auto& pool = calc_thread_pool(); + std::lock_guard lock(pool.mu); + const unsigned n = static_cast(num_threads); + if (pool.slots.size() < n) + pool.slots.resize(n); + for (unsigned k = 0; k < n; ++k) + { + if (!pool.slots[k]) + pool.slots[k] = std::make_unique(solver_config_for_thread(k)); + } +} + +void calc_chunk_common(const int thr_id, CalcRunParam& param) +{ + SolverContext& ctx = *calc_thread_pool().slots[static_cast(thr_id)]; + + while (true) + { + const schedType st = scheduler.GetNumber(thr_id); + const int index = st.number; + if (index == -1) + break; + + if (st.repeatOf != -1) + { + START_THREAD_TIMER(thr_id); + for (int k = 0; k < DDS_HANDS; k++) + { + param.bop->deals[index].first = k; + param.solvedp->solved_board[index].score[k] = + param.solvedp->solved_board[st.repeatOf].score[k]; + } + END_THREAD_TIMER(thr_id); + continue; + } + + START_THREAD_TIMER(thr_id); + const int res = calc_single_common_internal( + ctx, *param.bop, *param.solvedp, index); + END_THREAD_TIMER(thr_id); + + if (res != RETURN_NO_FAULT) + { + int expected = RETURN_NO_FAULT; + param.error.compare_exchange_strong( + expected, res, std::memory_order_relaxed); + break; + } + } +} + +auto run_calc_threads(CalcRunParam& param) -> int +{ + const int num_threads = sysdep.get_num_threads(); + ensure_calc_contexts(num_threads); + if (num_threads <= 1) + { + calc_chunk_common(0, param); + return RETURN_NO_FAULT; + } + + std::vector threads; + threads.reserve(static_cast(num_threads)); + try + { + for (int k = 0; k < num_threads; ++k) + threads.emplace_back(calc_chunk_common, k, std::ref(param)); + } + catch (...) + { + for (auto& th : threads) + { + if (th.joinable()) + th.join(); + } + throw; + } + + for (auto& th : threads) + th.join(); + + return RETURN_NO_FAULT; +} + +} // namespace + +auto clear_calc_thread_contexts() -> void +{ + std::lock_guard lock(calc_thread_pool().mu); + calc_thread_pool().slots.clear(); +} // Legacy overload (creates temporary context) auto calc_all_boards_n( Boards * bop, - SolvedBoards * solvedp, - int max_threads = 0) -> int; + SolvedBoards * solvedp) -> int; auto calc_single_common_internal( @@ -106,48 +235,33 @@ auto calc_all_boards_n( return RETURN_NO_FAULT; } -// Legacy overload: parallel across boards, one SolverContext per worker. +// Legacy overload: parallel across boards via scheduler + persistent contexts. auto calc_all_boards_n( Boards * bop, - SolvedBoards * solvedp, - int max_threads) -> int + SolvedBoards * solvedp) -> int { const int n = bop->no_of_boards; if (n > MAXNOOFBOARDS) return RETURN_TOO_MANY_BOARDS; + CalcRunParam param; + param.bop = bop; + param.solvedp = solvedp; + param.error.store(RETURN_NO_FAULT, std::memory_order_relaxed); + + scheduler.RegisterRun(RunMode::DDS_RUN_CALC, *bop); + for (int k = 0; k < MAXNOOFBOARDS; k++) solvedp->solved_board[k].cards = 0; START_BLOCK_TIMER; - const int nthreads = resolve_worker_count(max_threads, n); - - int err = RETURN_NO_FAULT; - if (nthreads <= 1) - { - SolverContext ctx; - for (int bno = 0; bno < n; ++bno) - { - err = calc_single_common_internal(ctx, *bop, *solvedp, bno); - if (err != RETURN_NO_FAULT) - break; - } - } - else - { - std::vector contexts(static_cast(nthreads)); - err = parallel_all_boards_n(n, nthreads, - [&](const int worker_id, const int bno) -> int { - return calc_single_common_internal( - contexts[static_cast(worker_id)], *bop, *solvedp, bno); - }); - } + const int ret_run = run_calc_threads(param); END_BLOCK_TIMER; - if (err != RETURN_NO_FAULT) - return err; + if (ret_run != RETURN_NO_FAULT) + return ret_run; solvedp->no_of_boards = n; @@ -155,15 +269,15 @@ auto calc_all_boards_n( scheduler.PrintTiming(); #endif - return RETURN_NO_FAULT; + const int err = param.error.load(std::memory_order_relaxed); + return err != RETURN_NO_FAULT ? err : RETURN_NO_FAULT; } -int STDCALL CalcDDtableN( +int STDCALL CalcDDtable( DdTableDeal tableDeal, - DdTableResults * tablep, - int maxThreads) + DdTableResults * tablep) { Deal dl; Boards bo; @@ -192,7 +306,7 @@ int STDCALL CalcDDtableN( ind++; } - int res = calc_all_boards_n(&bo, &solved, maxThreads); + int res = calc_all_boards_n(&bo, &solved); if (res != 1) return res; @@ -212,21 +326,12 @@ int STDCALL CalcDDtableN( } -int STDCALL CalcDDtable( - DdTableDeal tableDeal, - DdTableResults * tablep) -{ - return CalcDDtableN(tableDeal, tablep, 0); -} - - -int STDCALL CalcAllTablesN( +int STDCALL CalcAllTables( DdTableDeals const * dealsp, int mode, int const trumpFilter[5], DdTablesRes * resp, - AllParResults * presp, - int maxThreads) + AllParResults * presp) { /* mode = 0: par calculation, vulnerability None mode = 1: par calculation, vulnerability All @@ -288,7 +393,7 @@ int STDCALL CalcAllTablesN( bo.no_of_boards = lastIndex + 1; - int res = calc_all_boards_n(&bo, &solved, maxThreads); + int res = calc_all_boards_n(&bo, &solved); if (res != 1) return res; @@ -326,24 +431,12 @@ int STDCALL CalcAllTablesN( } -int STDCALL CalcAllTables( - DdTableDeals const * dealsp, - int mode, - int const trumpFilter[5], - DdTablesRes * resp, - AllParResults * presp) -{ - return CalcAllTablesN(dealsp, mode, trumpFilter, resp, presp, 0); -} - - -int STDCALL CalcAllTablesPBNN( +int STDCALL CalcAllTablesPBN( DdTableDealsPBN const * dealsp, int mode, int const trumpFilter[5], DdTablesRes * resp, - AllParResults * presp, - int maxThreads) + AllParResults * presp) { DdTableDeals dls; for (int k = 0; k < dealsp->no_of_tables; k++) @@ -352,44 +445,24 @@ int STDCALL CalcAllTablesPBNN( dls.no_of_tables = dealsp->no_of_tables; - int res = CalcAllTablesN(&dls, mode, trumpFilter, resp, presp, maxThreads); + int res = CalcAllTables(&dls, mode, trumpFilter, resp, presp); return res; } -int STDCALL CalcAllTablesPBN( - DdTableDealsPBN const * dealsp, - int mode, - int const trumpFilter[5], - DdTablesRes * resp, - AllParResults * presp) -{ - return CalcAllTablesPBNN(dealsp, mode, trumpFilter, resp, presp, 0); -} - - -int STDCALL CalcDDtablePBNN( +int STDCALL CalcDDtablePBN( DdTableDealPBN tableDealPBN, - DdTableResults * tablep, - int maxThreads) + DdTableResults * tablep) { DdTableDeal tableDeal; if (convert_from_pbn(tableDealPBN.cards, tableDeal.cards) != 1) return RETURN_PBN_FAULT; - int res = CalcDDtableN(tableDeal, tablep, maxThreads); + int res = CalcDDtable(tableDeal, tablep); return res; } -int STDCALL CalcDDtablePBN( - DdTableDealPBN tableDealPBN, - DdTableResults * tablep) -{ - return CalcDDtablePBNN(tableDealPBN, tablep, 0); -} - - void detect_calc_duplicates( const Boards& bds, vector& uniques, diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index 8ddd0a2d..b198b001 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -1213,7 +1213,7 @@ void weight_alloc_trump_void2(HeuristicContext& ctx) mply[k].rank < ctx.move1_rank) { // Don't underruff. - int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; + int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; suitAdd = (suitCount << 6) / 40; mply[k].weight = -32 + r_rank + suitAdd; } @@ -1383,7 +1383,7 @@ void weight_alloc_trump_void3(HeuristicContext& ctx) { for (int k = last_num_moves; k < num_moves; k++) { - int r_rank = rel_rank[tpos.aggr[suit]][mply[k].rank]; + 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 diff --git a/library/src/init.cpp b/library/src/init.cpp index 1b206925..6404f79d 100644 --- a/library/src/init.cpp +++ b/library/src/init.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include "init.hpp" #include #include @@ -36,29 +36,14 @@ int _initialized = 0; /* - * Initialise the solver's static memory: TT memory pools, scheduler / - * thread-manager state, and one-time lookup-table setup. - * - * Public API documentation is maintained in the API headers. - */ -void STDCALL InitializeStaticMemory() -{ - SetResources(0, 0); -} - - -/* - * Deprecated alias for InitializeStaticMemory(). The thread count is no - * longer meaningful (internal batch threading was removed), so the argument - * is ignored. + * Set the maximum number of threads used by the solver. * * Public API documentation is maintained in the API headers. */ void STDCALL SetMaxThreads( int userThreads) { - (void) userThreads; - InitializeStaticMemory(); + SetResources(0, userThreads); } @@ -89,17 +74,14 @@ void STDCALL SetResources( 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; + // Limit worker count by memory budget and hardware. + int thrMax; + if (maxThreadsIn <= 0) + thrMax = ncores; + else + thrMax = std::min(maxThreadsIn, ncores); + if (thrMax < 1) + thrMax = 1; // For simplicity we won't vary the amount of memory per thread // in the small and large versions. @@ -172,6 +154,20 @@ int STDCALL SetThreading( void InitDebugFiles() { +#ifdef DDS_SCHEDULER + InitFileScheduler(); +#endif +} + + +void CloseDebugFiles() +{ + for (unsigned thrId = 0; thrId < memory.NumThreads(); thrId++) + { + SolverContext tmp_ctx; + [[maybe_unused]] auto thrp = tmp_ctx.thread(); + thrp->close_debug_files(); + } } @@ -399,6 +395,7 @@ void STDCALL GetDDSInfo(DDSInfo * info) */ void STDCALL FreeMemory() { + clear_worker_contexts(); for (unsigned thrId = 0; thrId < memory.NumThreads(); thrId++) memory.ReturnThread(thrId); } diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index 1d100852..d9910a8c 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ extern Memory memory; extern Scheduler scheduler; +extern System sysdep; auto board_range_checks( @@ -72,10 +74,13 @@ int STDCALL SolveBoard( int solutions, int mode, FutureTricks * futp, - [[maybe_unused]] int thrId) + int thrId) { - SolverContext outer_ctx; - return solve_board(outer_ctx, dl, target, solutions, mode, futp); + if (thrId < 0 || thrId >= sysdep.get_num_threads()) + return RETURN_THREAD_INDEX; + + ensure_worker_contexts(sysdep.get_num_threads()); + return solve_board(worker_context_for(thrId), dl, target, solutions, mode, futp); } auto solve_board_internal( diff --git a/library/src/system/file.cpp b/library/src/system/file.cpp index 79255ad0..52a146ed 100644 --- a/library/src/system/file.cpp +++ b/library/src/system/file.cpp @@ -9,36 +9,38 @@ #include "file.hpp" -dds::File::~File() +File::~File() { Close(); } -void dds::File::Reset() +void File::Reset() { - Close(); fname_.clear(); + file_open_ = false; } -void dds::File::SetName(const std::string& fname_in) +void File::SetName(const std::string& fname_in) { - if (fname_in == fname_) - return; - - Close(); fname_ = fname_in; } -std::ofstream& dds::File::GetStream() +std::ofstream& File::GetStream() { - if (!fout_.is_open() && !fname_.empty()) + if (!file_open_) + { fout_.open(fname_); + file_open_ = true; + } return fout_; } -void dds::File::Close() +void File::Close() { - if (fout_.is_open()) + if (file_open_) + { fout_.close(); + file_open_ = false; + } } diff --git a/library/tests/loop.cpp b/library/tests/loop.cpp index 7dbeb6a8..ecc6b4eb 100644 --- a/library/tests/loop.cpp +++ b/library/tests/loop.cpp @@ -20,7 +20,6 @@ #include "cst.hpp" #include "dtest_parallel.hpp" -#include using std::cout; using std::endl; @@ -69,8 +68,20 @@ void loop_solve( } else { - ret = solve_all_boards_pbn_n(*bop, *solvedbdp, - dtest_effective_threads(options.num_threads_, count)); + solvedbdp->no_of_boards = count; + ret = dtest_run_parallel(count, options.num_threads_, + [&](const int j) -> int { + FutureTricks fut; + const int res = SolveBoardPBN( + bop->deals[j], bop->target[j], bop->solutions[j], bop->mode[j], + &fut, 0); + if (res == RETURN_NO_FAULT) + { + solvedbdp->solved_board[j] = fut; + return RETURN_NO_FAULT; + } + return res; + }); } if (ret != RETURN_NO_FAULT) { @@ -118,8 +129,8 @@ bool loop_calc( setw(25) << right << "Time" << "\n"; #endif - int filter[DDS_STRAINS] = {0, 0, 0, 0, 0}; - const int strain_count = DDS_STRAINS; + int filter[5] = {0, 0, 0, 0, 0}; + for (int i = 0; i < number; i += stepsize) { int count = (i + stepsize > number ? number - i : stepsize); @@ -128,9 +139,7 @@ bool loop_calc( strcpy(dealsp->deals[j].cards, deal_list[i+j].remainCards); timer.start(count); - const int workload = count * strain_count; - const int threads = dtest_effective_threads(options.num_threads_, workload); - const int ret = CalcAllTablesPBNN(dealsp, -1, filter, resp, parp, threads); + const int ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp); if (ret != RETURN_NO_FAULT) { cout << "loop_calc: i " << i << ", return " << ret << "\n"; diff --git a/library/tests/testcommon.cpp b/library/tests/testcommon.cpp index efc36dec..3853f383 100644 --- a/library/tests/testcommon.cpp +++ b/library/tests/testcommon.cpp @@ -153,15 +153,7 @@ int real_main([[maybe_unused]] int argc, [[maybe_unused]] char * argv[]) scheduler.GetBoardTimes(times); if (times.empty()) { - if (options.solver_ == Solver::DTEST_SOLVER_CALC) - { - cout << "Per-board timing data not available for calc (use -s solve -r)." - << std::endl; - } - else - { - cout << "Per-board timing data not available." << std::endl; - } + cout << "Per-board timing data not available. Rebuild with DDS_SCHEDULER enabled to collect per-board timings." << std::endl; } else { From b8143cd8335f8e9f205a9d4569ce2e49dfee912c Mon Sep 17 00:00:00 2001 From: Adam Wildavsky Date: Thu, 2 Jul 2026 23:55:35 -0400 Subject: [PATCH 157/157] Fix indentation in weight_alloc_trump_void1 and trump_void3 Whitespace-only cleanup of misindented if/else-if chains and wrapped conditions left over from the v2.9 port; no logic change. Co-authored-by: Cursor --- .../heuristic_sorting/heuristic_sorting.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/library/src/heuristic_sorting/heuristic_sorting.cpp b/library/src/heuristic_sorting/heuristic_sorting.cpp index b198b001..a9ab8980 100644 --- a/library/src/heuristic_sorting/heuristic_sorting.cpp +++ b/library/src/heuristic_sorting/heuristic_sorting.cpp @@ -672,7 +672,7 @@ void weight_alloc_trump_void1(HeuristicContext& ctx) const int partner_lh = partner[lead_hand]; const int rho_lh = rho[lead_hand]; - + unsigned short suitCount = tpos.length[curr_hand][suit]; int suitAdd; @@ -702,9 +702,9 @@ void weight_alloc_trump_void1(HeuristicContext& ctx) 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])) + if (tpos.rank_in_suit[rho_lh][lead_suit] > + (tpos.rank_in_suit[partner_lh][lead_suit] | + bit_map_rank[ctx.lead0_rank])) // Partner has winning card. suitAdd = 60 + (suitCount << 6) / 44; else if ((tpos.length[rho_lh][lead_suit] == 0) @@ -726,9 +726,9 @@ void weight_alloc_trump_void1(HeuristicContext& ctx) 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) - && (tpos.rank_in_suit[rho_lh][lead_suit] > - bit_map_rank[ctx.lead0_rank])) + 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 @@ -1181,7 +1181,7 @@ void weight_alloc_trump_void2(HeuristicContext& ctx) 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]]; @@ -1209,8 +1209,8 @@ void weight_alloc_trump_void2(HeuristicContext& ctx) for (int k = last_num_moves; k < num_moves; k++) { - if (ctx.move1_suit == trump && - mply[k].rank < ctx.move1_rank) + 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]; @@ -1218,7 +1218,7 @@ void weight_alloc_trump_void2(HeuristicContext& ctx) mply[k].weight = -32 + r_rank + suitAdd; } - else if (ctx.high1 == 0) + else if (ctx.high1 == 0) { // We ruff partner's winner over 2nd hand. if (max4th != 0)