Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions docs/python_interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,53 @@ result = dealer_par(dd_result, dealer=0, vulnerable=0)
print(result["score"], result["contracts"])
```

#### `analyse_play_pbn(remain_cards, play, trump=4, first=0, current_trick_suit=(0,0,0), current_trick_rank=(0,0,0), thread_index=0)`
#### `analyse_play(deal, play_suit, play_rank, context=None)`

Returns the double-dummy trick count after each card of a played hand (wraps `AnalysePlayPBN`).
Returns the double-dummy trick count after each card of a played hand, binary format. Wraps the
C++ `analyse_play()` entry point directly.

**Parameters:**
- `deal` (dict): Deal dict with keys `trump`, `first`, `remain_cards`, `current_trick_suit`,
`current_trick_rank` (see `solve_board`)
- `play_suit` (seq): Suit of each card played, in order (0=♠, 1=♥, 2=♦, 3=♣)
- `play_rank` (seq): Rank of each card played, in order (2-14), same length as `play_suit`
- `context` (SolverContext, optional): Reusable solver context for efficiency

**Returns:**
- dict with keys: `number` (int), `tricks` (list[int]) — `tricks[i]` is the trick count after `i`
cards, for the fixed partnership opposite `deal["first"]` (the hand that led to the first
trick) — not the side currently on lead, which alternates.

```python
from dds3 import analyse_play

deal = {
"trump": 4, "first": 0,
"remain_cards": [...],
"current_trick_suit": (0, 0, 0), "current_trick_rank": (0, 0, 0),
}
result = analyse_play(deal, play_suit=[0], play_rank=[6])
print(result["tricks"])
```

#### `analyse_play_pbn(remain_cards, play, trump=4, first=0, current_trick_suit=(0,0,0), current_trick_rank=(0,0,0), thread_index=0, context=None)`

Returns the double-dummy trick count after each card of a played hand. Converts PBN to binary and
calls the `analyse_play()` entry point directly.

**Parameters:**
- `remain_cards` (str): Full deal in PBN format before any card of `play`
- `play` (str): Cards played, 2 characters each (suit+rank), e.g. `"SAHK..."`
- `trump` (int, default=4): Trump suit (0=♠, 1=♥, 2=♦, 3=♣, 4=NT)
- `first` (int, default=0): Seat that leads (0=N, 1=E, 2=S, 3=W)
- `current_trick_suit` / `current_trick_rank` (seq, default=(0,0,0)): Cards already in the trick
- `thread_index` (int, default=0): Thread id
- `thread_index` (int, default=0): Ignored; use `context` instead
- `context` (SolverContext, optional): Reusable solver context for efficiency

**Returns:**
- dict with keys: `number` (int), `tricks` (list[int]) — `tricks[i]` is the trick count for the side to play after `i` cards.
- dict with keys: `number` (int), `tricks` (list[int]) — `tricks[i]` is the trick count after `i`
cards, for the fixed partnership opposite `first` (the hand that led to the first trick) — not
the side currently on lead, which alternates.

```python
from dds3 import analyse_play_pbn
Expand Down
2 changes: 2 additions & 0 deletions jni/exported_symbols.lds
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ _SolveAllChunksBin
_SolveAllChunksPBN
_SolveBoard
_SolveBoardPBN
_dds_c_analyse_play
_dds_c_calc_dd_table
_dds_c_calc_dd_table_pbn
_dds_c_calc_par
_dds_c_clear_tt
_dds_c_configure_tt
_dds_c_convert_from_pbn
_dds_c_convert_play_from_pbn
_dds_c_convert_to_dealer_text_format
_dds_c_convert_to_sides_text_format
_dds_c_create_solvercontext
Expand Down
2 changes: 2 additions & 0 deletions jni/version_script.lds
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
SolveAllChunksPBN;
SolveBoard;
SolveBoardPBN;
dds_c_analyse_play;
dds_c_calc_dd_table;
dds_c_calc_dd_table_pbn;
dds_c_calc_par;
dds_c_clear_tt;
dds_c_configure_tt;
dds_c_convert_from_pbn;
dds_c_convert_play_from_pbn;
dds_c_convert_to_dealer_text_format;
dds_c_convert_to_sides_text_format;
dds_c_create_solvercontext;
Expand Down
178 changes: 178 additions & 0 deletions library/src/analyse_play_adapter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include <api/analyse_play.hpp>
#include <solver_if.hpp>

/**
* @brief Analyze a sequence of played cards (binary format) and determine the tricks taken.
*
* This function simulates play of a bridge Deal according to the provided play trace,
* using double dummy analysis to determine the number of tricks won at each step.
*
* @param ctx Solver context containing state and resources, reused across the
* initial solve and every subsequent per-card analyse_later_board
* call so the hint-bounded incremental searches see a warm TT.
* @param dl_in The Deal to analyze
* @param play The sequence of played cards (binary format)
* @param solvedp Pointer to result structure for solved play
* @return RETURN_NO_FAULT on success, error code otherwise
*/
auto analyse_play(
SolverContext& ctx,
const Deal& dl_in,
const PlayTraceBin& play,
SolvedPlay* solvedp) -> int
{
// Validate every claimed play.suit/play.rank entry before any of it is
// used to index bit_map_rank[] or dl.remainCards[player][suit] below --
// those arrays are sized for valid suits/ranks only, so an out-of-range
// value is an out-of-bounds access, not something a caller could catch
// as a C++ exception.
if (play.number < 0 || play.number > 52)
return RETURN_PLAY_FAULT;
for (int i = 0; i < play.number; i++)
{
if (play.suit[i] < 0 || play.suit[i] >= DDS_SUITS)
return RETURN_PLAY_FAULT;
if (play.rank[i] < 2 || play.rank[i] > 14)
return RETURN_PLAY_FAULT;
}

Deal dl = dl_in;

MoveType move;
FutureTricks fut;

int ret = solve_board_internal(ctx, dl, -1, 1, 1, &fut);
if (ret != RETURN_NO_FAULT)
return ret;
const int ini_depth = ctx.search().ini_depth();
const int numTricks = ((ini_depth + 3) >> 2) + 1;
const int numCardsPlayed = ((48 - ini_depth) % 4) + 1;

int last_trick = (play.number + 3) / 4;
int last_card = ((play.number + 3) % 4) + 1;
if (last_trick >= numTricks)
{
last_trick = numTricks-1;
last_card = 4;
}
solvedp->number = 0;

solvedp->tricks[0] = (numCardsPlayed % 2 == 1 ?
numTricks - fut.score[0] : fut.score[0]);
int hint = solvedp->tricks[0];
int hintDir;

int running_remainder = numTricks;
int running_declarer = 0;
int running_player = dl.first;
int running_side = 1; /* defenders */
int start_side = running_player % 2;

for (int trick = 1; trick <= last_trick; trick++)
{
int best_card = 0, best_suit = 0, best_player = 0, trump_played = 0;
int lc = (trick == last_trick ? last_card : 4);

bool haveCurrent = (numCardsPlayed > 1 && trick == 1);
int offset = 4 * (trick - 1) - (numCardsPlayed - 1);

for (int card = 1; card <= lc; card++)
{
int suit, rr;
bool usingCurrent = (haveCurrent && card < numCardsPlayed);
if (usingCurrent)
{
suit = dl.currentTrickSuit[card - 1];
rr = dl.currentTrickRank[card - 1];
}
else
{
suit = play.suit[offset + card - 1];
rr = play.rank[offset + card - 1];
}
unsigned hold = static_cast<unsigned>(bit_map_rank[rr] << 2);

move.suit = suit;
move.rank = rr;
move.sequence = rr;

/* Keep track of the winner of the trick so far */
if (card == 1)
{
best_card = rr;
best_suit = suit;
best_player = dl.first;
trump_played = (suit == dl.trump ? 1 : 0);
}
else if (suit == dl.trump)
{
if (! trump_played || rr > best_card)
{
best_card = rr;
best_suit = suit;
best_player = running_player;
trump_played = 1;
}
}
else if (! trump_played && suit == best_suit && rr > best_card)
{
best_card = rr;
best_player = running_player;
}

if ((dl.remainCards[running_player][suit] & hold) == 0)
{
if (! usingCurrent)
return RETURN_PLAY_FAULT;
}
else
dl.remainCards[running_player][suit] ^= hold;

if (card == 4)
{
running_declarer += (best_player % 2 == start_side ? 0 : 1);
running_remainder--;

if ((dl.first + best_player) % 2 == 0)
{
hintDir = 0; // Same side leads again; lower bound
hint = running_remainder - fut.score[0];
}
else
{
hintDir = 1; // Other ("our") side wins trick; upper bound
hint = fut.score[0] - 1;
}

dl.first = best_player;
running_side = (dl.first % 2 == start_side ? 1 : 0);
running_player = dl.first;
}
else
{
running_player = (running_player + 1) % 4;
running_side = 1 - running_side;
hint = running_remainder - fut.score[0];
hintDir = 0;
}

if (usingCurrent)
continue;

if ((ret = analyse_later_board(ctx, dl.first, &move, hint,
hintDir, &fut))
!= RETURN_NO_FAULT)
{
return ret;
}

int new_solved_decl = running_declarer + (running_side ?
running_remainder - fut.score[0] : fut.score[0]);

solvedp->tricks[offset + card] = new_solved_decl;
}
}
solvedp->number = 4 * last_trick + last_card - 3 - (numCardsPlayed - 1);

return RETURN_NO_FAULT;
}
1 change: 1 addition & 0 deletions library/src/api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cc_library(
cc_library(
name = "api_definitions",
hdrs = [
"analyse_play.hpp",
"calc_dd_table.hpp",
"calc_par.hpp",
"dds.h",
Expand Down
40 changes: 40 additions & 0 deletions library/src/api/analyse_play.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @file analyse_play.hpp
* @brief C++ interface for play analysis with explicit solver context.
*
* This header provides the C++-only, context-aware entry point for play
* analysis, allowing clients to manage solver state across multiple
* analyse_play calls.
*
* Naming note: this mirrors the legacy AnalysePlayBin/AnalysePlayPBN API
* family, so "analyse" (British spelling) is kept deliberately here,
* matching that existing name rather than switching to "analyze".
*
* @copyright (C) 2006-2014 by Bo Haglund / 2014-2018 by Bo Haglund & Soren Hein.
* @see LICENSE and README.
*/

#pragma once

#include <api/dds_data_types.hpp>
#include <solver_context/solver_context.hpp>

/**
* @brief Analyze a sequence of played cards (binary format) with explicit solver context.
*
* C++-only overload that accepts an explicit SolverContext, allowing clients
* to manage solver state and resources across multiple analyse_play calls.
* This is the preferred C++ API following DDS 3 snake_case naming conventions.
*
* @param ctx Solver context containing state and resources
* @param dl Deal to analyze
* @param play Sequence of played cards (binary format)
* @param solvedp Output structure for the solved play
* @return Error code (RETURN_NO_FAULT on success)
* @see AnalysePlayBin() for the backward-compatible PascalCase legacy version
*/
auto analyse_play(
SolverContext& ctx,
const Deal& dl,
const PlayTraceBin& play,
SolvedPlay* solvedp) -> int;
7 changes: 7 additions & 0 deletions library/src/api/dds_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <solver_context/solver_context.hpp>
#include <api/solve_board.hpp>
#include <api/calc_dd_table.hpp>
#include <api/analyse_play.hpp>

extern "C" {

Expand Down Expand Up @@ -64,5 +65,11 @@ extern "C" {
DdTableResults* table_results,
ParResults* par_results) -> int;

EXTERN_C DLLEXPORT auto dds_analyse_play(
DDS_SOLVER_CTX ctx,
const Deal& dl,
const PlayTraceBin& play,
SolvedPlay* solvedp) -> int;


}
32 changes: 32 additions & 0 deletions library/src/api/dds_c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,38 @@ DLLEXPORT int dds_c_convert_from_pbn(const char* pbn_deal,
}
}

DLLEXPORT int dds_c_analyse_play(
DDS_C_SOLVER_CTX ctx,
const struct Deal* dl,
const struct PlayTraceBin* play,
struct SolvedPlay* solved)
{
if (ctx == nullptr || dl == nullptr || play == nullptr || solved == nullptr)
return RETURN_UNKNOWN_FAULT;

try {
return dds_analyse_play(static_cast<SolverContext*>(ctx), *dl, *play, solved);
} catch (...) {
return RETURN_UNKNOWN_FAULT;
}
}

DLLEXPORT int dds_c_convert_play_from_pbn(
const struct PlayTracePBN* pbn_play,
struct PlayTraceBin* bin_play)
{
if (pbn_play == nullptr || bin_play == nullptr)
return RETURN_UNKNOWN_FAULT;

try {
/* convert_play_from_pbn already returns a proper RETURN_* code
(unlike convert_from_pbn's bare 1/0), so forward it as-is. */
return convert_play_from_pbn(*pbn_play, *bin_play);
} catch (...) {
return RETURN_UNKNOWN_FAULT;
}
}

DLLEXPORT void dds_c_configure_tt(DDS_C_SOLVER_CTX ctx, int tt_kind,
int def_mb, int max_mb)
{
Expand Down
Loading
Loading