Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
155 changes: 33 additions & 122 deletions library/src/moves/moves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,73 +439,64 @@ auto Moves::GetLength(const int trick, const int relHand) const -> int {
return moveList[trick][relHand].last + 1;
}

auto Moves::MakeSpecific(const MoveType &ourMply, const int trick,
const int relHand) -> void {
auto Moves::apply_move_to_track(const MoveType &move, const int relHand,
const int trick) -> void {
assert(trick >= 0 && trick < 13 && "apply_move_to_track: trick out of range");
assert(relHand >= 0 && relHand < DDS_HANDS);
if (relHand == 3)
assert(trick > 0 && "apply_move_to_track: trick must be > 0 when relHand==3");
trackp = &track[trick];

if (relHand == 0) {
trackp->move[0].suit = ourMply.suit;
trackp->move[0].rank = ourMply.rank;
trackp->move[0].sequence = ourMply.sequence;
trackp->move[0].suit = move.suit;
trackp->move[0].rank = move.rank;
trackp->move[0].sequence = move.sequence;
trackp->high[0] = 0;

trackp->lead_suit = ourMply.suit;
} else if (ourMply.suit == trackp->move[relHand - 1].suit) {
if (ourMply.rank > trackp->move[relHand - 1].rank) {
trackp->move[relHand].suit = ourMply.suit;
trackp->move[relHand].rank = ourMply.rank;
trackp->move[relHand].sequence = ourMply.sequence;
trackp->lead_suit = move.suit;
} else if (move.suit == trackp->move[relHand - 1].suit) {
if (move.rank > trackp->move[relHand - 1].rank) {
trackp->move[relHand].suit = move.suit;
trackp->move[relHand].rank = move.rank;
trackp->move[relHand].sequence = move.sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}
} else if (ourMply.suit == trump) {
trackp->move[relHand].suit = ourMply.suit;
trackp->move[relHand].rank = ourMply.rank;
trackp->move[relHand].sequence = ourMply.sequence;
} else if (move.suit == trump) {
trackp->move[relHand].suit = move.suit;
trackp->move[relHand].rank = move.rank;
trackp->move[relHand].sequence = move.sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}

trackp->play_suits[relHand] = ourMply.suit;
trackp->play_ranks[relHand] = ourMply.rank;

// When trick completes (4th card played), prepare next trick's state.
trackp->play_suits[relHand] = move.suit;
trackp->play_ranks[relHand] = move.rank;
if (relHand == 3) {
TrackType *newp = &track[trick - 1];

// Winner of this trick leads the next one.
newp->lead_hand = (trackp->lead_hand + trackp->high[3]) % 4;

// Update removed ranks to include all cards played in this trick.
TrackType &newt = track[trick - 1];
newt.lead_hand = (trackp->lead_hand + trackp->high[3]) % 4;
int r, s;
for (s = 0; s < DDS_SUITS; s++)
newp->removed_ranks[s] = trackp->removed_ranks[s];

newt.removed_ranks[s] = trackp->removed_ranks[s];
for (int h = 0; h < DDS_HANDS; h++) {
r = trackp->play_ranks[h];
s = trackp->play_suits[h];
newp->removed_ranks[s] |= bit_map_rank[r];
newt.removed_ranks[s] |= bit_map_rank[r];
}
}
}

auto Moves::MakeSpecific(const MoveType &ourMply, const int trick,
const int relHand) -> void {
apply_move_to_track(ourMply, relHand, trick);
}

auto Moves::MakeNext(const int trick, const int relHand,
const unsigned short ourWinRanks[DDS_SUITS])
const unsigned short ourWinRanks[DDS_SUITS])
-> MoveType const * {
// Find moves that are >= ourWinRanks[suit], but allow one
// "small" move per suit to explore losing options.
//
// The lowest_win array tracks the minimum rank to try next for each suit.
// After trying one card below the winning threshold, subsequent cards
// must meet the threshold.

int *lwp = track[trick].lowest_win[relHand];
MovePlyType &list = moveList[trick][relHand];
trackp = &track[trick];

MoveType *currp = nullptr, *prevp;

Expand Down Expand Up @@ -537,101 +528,21 @@ auto Moves::MakeNext(const int trick, const int relHand,
return nullptr;
}

if (relHand == 0) {
trackp->move[0].suit = currp->suit;
trackp->move[0].rank = currp->rank;
trackp->move[0].sequence = currp->sequence;
trackp->high[0] = 0;

trackp->lead_suit = currp->suit;
} else if (currp->suit == trackp->move[relHand - 1].suit) {
if (currp->rank > trackp->move[relHand - 1].rank) {
trackp->move[relHand].suit = currp->suit;
trackp->move[relHand].rank = currp->rank;
trackp->move[relHand].sequence = currp->sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}
} else if (currp->suit == trump) {
trackp->move[relHand].suit = currp->suit;
trackp->move[relHand].rank = currp->rank;
trackp->move[relHand].sequence = currp->sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}

trackp->play_suits[relHand] = currp->suit;
trackp->play_ranks[relHand] = currp->rank;

if (relHand == 3) {
TrackType &newt = track[trick - 1];

newt.lead_hand = (trackp->lead_hand + trackp->high[3]) % 4;

int r, s;
for (s = 0; s < DDS_SUITS; s++)
newt.removed_ranks[s] = trackp->removed_ranks[s];

for (int h = 0; h < DDS_HANDS; h++) {
r = trackp->play_ranks[h];
s = trackp->play_suits[h];
newt.removed_ranks[s] |= bit_map_rank[r];
}
}
apply_move_to_track(*currp, relHand, trick);

list.current++;
return currp;
}

auto Moves::MakeNextSimple(const int trick, const int relHand)
-> MoveType const * {
// Don't worry about small moves. Why not, actually?

MovePlyType &list = moveList[trick][relHand];
if (list.current > list.last)
return nullptr;

const MoveType &curr = list.move[list.current];

trackp = &track[trick];

if (relHand == 0) {
trackp->move[0].suit = curr.suit;
trackp->move[0].rank = curr.rank;
trackp->move[0].sequence = curr.sequence;
trackp->high[0] = 0;

trackp->lead_suit = curr.suit;
} else if (curr.suit == trackp->move[relHand - 1].suit) {
if (curr.rank > trackp->move[relHand - 1].rank) {
trackp->move[relHand].suit = curr.suit;
trackp->move[relHand].rank = curr.rank;
trackp->move[relHand].sequence = curr.sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}
} else if (curr.suit == trump) {
trackp->move[relHand].suit = curr.suit;
trackp->move[relHand].rank = curr.rank;
trackp->move[relHand].sequence = curr.sequence;
trackp->high[relHand] = relHand;
} else {
trackp->move[relHand] = trackp->move[relHand - 1];
trackp->high[relHand] = trackp->high[relHand - 1];
}

trackp->play_suits[relHand] = curr.suit;
trackp->play_ranks[relHand] = curr.rank;

if (relHand == 3) {
track[trick - 1].lead_hand = (trackp->lead_hand + trackp->high[3]) % 4;
}
apply_move_to_track(curr, relHand, trick);

list.current++;
return &curr;
Expand Down
11 changes: 11 additions & 0 deletions library/src/moves/moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,17 @@ class Moves {
* @return Pointer to chosen move, or nullptr if list exhausted
*/
auto MakeNextSimple(const int trick, const int relHand) -> MoveType const *;
/**
* @brief Update TrackType state to reflect a played move.
*
* Sets trackp to &track[trick] internally before updating state.
*
* @param move Move to apply
* @param relHand Relative hand index within the current trick (0..3)
* @param trick Trick index (0..12); must be > 0 when relHand==3 (updates track[trick-1])
*/
auto apply_move_to_track(const MoveType &move, const int relHand,
const int trick) -> void;
Comment thread
wopdevries marked this conversation as resolved.

/**
* @brief Advance to next move in list.
Expand Down
100 changes: 100 additions & 0 deletions library/tests/moves/moves_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,103 @@ TEST_F(MovesTest, GetLengthIsQuick) {
// Should complete 100k calls in reasonable time
EXPECT_LT(duration.count(), 500); // Less than 500ms for 100k
}

TEST_F(MovesTest, ApplyMoveToTrackLeadHand) {
const unsigned short (*rankInSuit)[4] = getSampleRankInSuit();
moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0);
moves->trackp = &moves->track[5];

MoveType move;
move.suit = 2; // Diamonds
move.rank = 10;
move.sequence = 0;

moves->apply_move_to_track(move, 0, 5);

EXPECT_EQ(moves->trackp->move[0].suit, 2);
EXPECT_EQ(moves->trackp->move[0].rank, 10);
EXPECT_EQ(moves->trackp->high[0], 0);
EXPECT_EQ(moves->trackp->lead_suit, 2);
EXPECT_EQ(moves->trackp->play_suits[0], 2);
EXPECT_EQ(moves->trackp->play_ranks[0], 10);
}

TEST_F(MovesTest, ApplyMoveToTrackFollowSuit) {
const unsigned short (*rankInSuit)[4] = getSampleRankInSuit();
moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0);
moves->trackp = &moves->track[5];

// Lead with Ace of Spades
MoveType lead;
lead.suit = 0;
lead.rank = 14;
lead.sequence = 0;
moves->apply_move_to_track(lead, 0, 5);

// Follow with King of Spades — lower rank loses
MoveType follow;
follow.suit = 0;
follow.rank = 13;
follow.sequence = 0;
moves->apply_move_to_track(follow, 1, 5);

// King < Ace so high stays at 0 (lead hand wins)
EXPECT_EQ(moves->trackp->high[1], 0);
EXPECT_EQ(moves->trackp->play_suits[1], 0);
EXPECT_EQ(moves->trackp->play_ranks[1], 13);
}

TEST_F(MovesTest, ApplyMoveToTrackTrumpBeatsNonTrump) {
const unsigned short (*rankInSuit)[4] = getSampleRankInSuit();
// trump = 0 (Spades)
moves->Init(5, 0, nullptr, nullptr, rankInSuit, 0, 0);
moves->trackp = &moves->track[5];

// Lead with Hearts (non-trump)
MoveType lead;
lead.suit = 1;
lead.rank = 14;
lead.sequence = 0;
moves->apply_move_to_track(lead, 0, 5);

// Follow with Spades (trump) — trump wins
MoveType trump_card;
trump_card.suit = 0;
trump_card.rank = 2;
trump_card.sequence = 0;
moves->apply_move_to_track(trump_card, 1, 5);

EXPECT_EQ(moves->trackp->high[1], 1); // hand 1 wins with trump
EXPECT_EQ(moves->trackp->move[1].suit, 0);
EXPECT_EQ(moves->trackp->move[1].rank, 2);
}

TEST_F(MovesTest, ApplyMoveToTrackTrickCompletion) {
const unsigned short (*rankInSuit)[4] = getSampleRankInSuit();
moves->Init(5, 0, nullptr, nullptr, rankInSuit, 3, 0);
moves->trackp = &moves->track[5];
moves->track[5].lead_hand = 0;
// Zero removed_ranks so we can verify apply_move_to_track sets specific bits
for (int s = 0; s < DDS_SUITS; s++) {
moves->track[5].removed_ranks[s] = 0;
moves->track[4].removed_ranks[s] = 0;
}
// All four hands play spades: A K Q J
MoveType cards[4];
for (int h = 0; h < 4; h++) {
cards[h].suit = 0;
cards[h].rank = 14 - h;
cards[h].sequence = 0;
moves->apply_move_to_track(cards[h], h, 5);
}
// Hand 0 played Ace - should win
EXPECT_EQ(moves->trackp->high[3], 0);
// Next trick lead_hand should be hand 0
EXPECT_EQ(moves->track[4].lead_hand, 0);
// removed_ranks[0] (spades) should have bits set for A(0x1000) K(0x0800) Q(0x0400) J(0x0200)
EXPECT_EQ(moves->track[4].removed_ranks[0], 0x1E00);
// Other suits untouched - should remain 0
EXPECT_EQ(moves->track[4].removed_ranks[1], 0);
EXPECT_EQ(moves->track[4].removed_ranks[2], 0);
EXPECT_EQ(moves->track[4].removed_ranks[3], 0);
}