use crate::lookup::{BIT_MAP_RANK, HIGHEST_RANK, LHO, PARTNER, RHO};
use crate::pos::Pos;
pub const MAXNODE: i32 = 1;
#[allow(dead_code)]
pub const MINNODE: i32 = 0;
pub const DDS_NOTRUMP: i32 = 4;
const DDS_SUITS: usize = 4;
const DDS_HANDS: usize = 4;
#[inline]
fn abs_rank(rank_in_suit: &[[u16; 4]; 4], aggr: u16, k: usize, suit: usize) -> (i32, i32) {
let mut count = 0usize;
for r in (2i32..=14).rev() {
let bit = BIT_MAP_RANK[r as usize];
if aggr & bit != 0 {
count += 1;
if count == k {
for (h, ranks) in rank_in_suit.iter().enumerate() {
if ranks[suit] & bit != 0 {
return (r, h as i32);
}
}
return (r, -1);
}
}
}
(0, -1)
}
#[inline]
pub fn quick_tricks(
tpos: &mut Pos,
hand: i32,
depth: i32,
target: i32,
trump: i32,
result: &mut bool,
node_type_store: &[i32; DDS_HANDS],
) -> i32 {
let hand_u = hand as usize;
let depth_u = depth as usize;
let mut comm_rank: i32 = 0;
let mut comm_suit: i32 = -1;
let mut lho_trump_ranks: i32;
let mut rho_trump_ranks: i32;
let mut lowest_qtricks = 0;
*result = true;
let mut qtricks = 0;
let cutoff_initial = if node_type_store[hand_u] == MAXNODE {
target - tpos.tricks_max
} else {
tpos.tricks_max - target + (depth >> 2) + 2
};
let mut cutoff = cutoff_initial;
let mut comm_partner = false;
for s in 0..DDS_SUITS {
if trump != DDS_NOTRUMP && trump != s as i32 {
let trump_u = trump as usize;
if tpos.winner[s].hand == PARTNER[hand_u] as i32 {
if tpos.rank_in_suit[hand_u][s] != 0
&& ((tpos.rank_in_suit[LHO[hand_u]][s] != 0
|| tpos.rank_in_suit[LHO[hand_u]][trump_u] == 0)
&& (tpos.rank_in_suit[RHO[hand_u]][s] != 0
|| tpos.rank_in_suit[RHO[hand_u]][trump_u] == 0))
{
comm_partner = true;
comm_suit = s as i32;
comm_rank = tpos.winner[s].rank;
break;
}
} else if tpos.second_best[s].hand == PARTNER[hand_u] as i32
&& tpos.winner[s].hand == hand
&& tpos.length[hand_u][s] >= 2
&& tpos.length[PARTNER[hand_u]][s] >= 2
{
if (tpos.rank_in_suit[LHO[hand_u]][s] != 0
|| tpos.rank_in_suit[LHO[hand_u]][trump_u] == 0)
&& (tpos.rank_in_suit[RHO[hand_u]][s] != 0
|| tpos.rank_in_suit[RHO[hand_u]][trump_u] == 0)
{
comm_partner = true;
comm_suit = s as i32;
comm_rank = tpos.second_best[s].rank;
break;
}
}
} else if trump == DDS_NOTRUMP {
if tpos.winner[s].hand == PARTNER[hand_u] as i32 {
if tpos.rank_in_suit[hand_u][s] != 0 {
comm_partner = true;
comm_suit = s as i32;
comm_rank = tpos.winner[s].rank;
break;
}
} else if tpos.second_best[s].hand == PARTNER[hand_u] as i32
&& tpos.winner[s].hand == hand
&& tpos.length[hand_u][s] >= 2
&& tpos.length[PARTNER[hand_u]][s] >= 2
{
comm_partner = true;
comm_suit = s as i32;
comm_rank = tpos.second_best[s].rank;
break;
}
}
}
if trump != DDS_NOTRUMP
&& !comm_partner
&& tpos.rank_in_suit[hand_u][trump as usize] != 0
&& tpos.winner[trump as usize].hand == PARTNER[hand_u] as i32
{
comm_partner = true;
comm_suit = trump;
comm_rank = tpos.winner[trump as usize].rank;
}
let mut suit: i32;
if trump == DDS_NOTRUMP {
suit = 0;
lho_trump_ranks = 0;
rho_trump_ranks = 0;
} else {
suit = trump;
lho_trump_ranks = i32::from(tpos.length[LHO[hand_u]][trump as usize]);
rho_trump_ranks = i32::from(tpos.length[RHO[hand_u]][trump as usize]);
}
loop {
let suit_u = suit as usize;
let count_own = i32::from(tpos.length[hand_u][suit_u]);
let count_lho = i32::from(tpos.length[LHO[hand_u]][suit_u]);
let count_rho = i32::from(tpos.length[RHO[hand_u]][suit_u]);
let count_part = i32::from(tpos.length[PARTNER[hand_u]][suit_u]);
let opps = count_lho | count_rho;
if opps == 0 && count_part == 0 {
if count_own == 0 {
if trump != DDS_NOTRUMP && trump != suit {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
} else if trump != DDS_NOTRUMP && trump == suit {
suit = i32::from(trump == 0);
} else {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
}
if suit > 3 {
break;
}
continue;
}
if trump != DDS_NOTRUMP && trump != suit {
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
qtricks += count_own;
if qtricks >= cutoff {
return qtricks;
}
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
qtricks += count_own;
if qtricks >= cutoff {
return qtricks;
}
if trump != DDS_NOTRUMP && suit == trump {
suit = i32::from(trump == 0);
} else {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
}
if suit > 3 {
break;
}
continue;
}
if opps == 0 && trump != DDS_NOTRUMP && suit == trump {
let mut sum = count_own.max(count_part);
for s in 0..DDS_SUITS {
if sum > 0
&& s as i32 != trump
&& count_own >= count_part
&& tpos.length[hand_u][s] > 0
&& tpos.length[PARTNER[hand_u]][s] == 0
{
sum += 1;
break;
}
}
if sum >= cutoff {
return sum;
}
} else if opps == 0 {
let sum = count_own.min(count_part);
if trump == DDS_NOTRUMP {
if sum >= cutoff {
return sum;
}
} else if suit != trump && lho_trump_ranks == 0 && rho_trump_ranks == 0 && sum >= cutoff
{
return sum;
}
}
if comm_partner {
if opps == 0 && count_own == 0 {
if trump != DDS_NOTRUMP && trump != suit {
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
qtricks += count_part;
tpos.win_ranks[depth_u][comm_suit as usize] |=
BIT_MAP_RANK[comm_rank as usize];
if qtricks >= cutoff {
return qtricks;
}
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
qtricks += count_part;
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
if qtricks >= cutoff {
return qtricks;
}
if trump != DDS_NOTRUMP && suit == trump {
suit = i32::from(trump == 0);
} else {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
}
if suit > 3 {
break;
}
continue;
} else if opps == 0 && trump != DDS_NOTRUMP && suit == trump {
let mut sum = count_own.max(count_part);
for s in 0..DDS_SUITS {
if sum > 0
&& s as i32 != trump
&& count_own <= count_part
&& tpos.length[PARTNER[hand_u]][s] > 0
&& tpos.length[hand_u][s] == 0
{
sum += 1;
break;
}
}
if sum >= cutoff {
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
return sum;
}
} else if opps == 0 {
let sum = count_own.min(count_part);
if trump == DDS_NOTRUMP {
if sum >= cutoff {
return sum;
}
} else if suit != trump
&& lho_trump_ranks == 0
&& rho_trump_ranks == 0
&& sum >= cutoff
{
return sum;
}
}
}
if tpos.winner[suit_u].rank == 0 {
if trump != DDS_NOTRUMP && suit == trump {
suit = i32::from(trump == 0);
} else {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
}
if suit > 3 {
break;
}
continue;
}
if tpos.winner[suit_u].hand == hand {
let mut res = 0;
if trump != DDS_NOTRUMP && trump != suit {
qtricks = qtricks_lead_hand_trump(
tpos,
cutoff,
depth,
count_lho,
count_rho,
lho_trump_ranks,
rho_trump_ranks,
count_own,
count_part,
suit,
qtricks,
&mut res,
hand,
);
if res == 1 {
return qtricks;
} else if res == 2 {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
} else {
qtricks = qtricks_lead_hand_nt(
tpos,
cutoff,
depth,
count_lho,
count_rho,
&mut lho_trump_ranks,
&mut rho_trump_ranks,
comm_partner,
comm_suit,
count_own,
count_part,
suit,
qtricks,
trump,
&mut res,
hand,
);
if res == 1 {
return qtricks;
} else if res == 2 {
if trump != DDS_NOTRUMP && trump == suit {
suit = i32::from(trump == 0);
} else {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
}
} else {
if tpos.winner[suit_u].hand == PARTNER[hand_u] as i32 {
if comm_partner {
let mut res = 0;
if trump != DDS_NOTRUMP && trump != suit {
qtricks = quick_tricks_partner_hand_trump(
tpos,
cutoff,
depth,
count_lho,
count_rho,
lho_trump_ranks,
rho_trump_ranks,
count_own,
count_part,
suit,
qtricks,
comm_suit,
comm_rank,
&mut res,
hand,
);
if res == 1 {
return qtricks;
} else if res == 2 {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
} else {
qtricks = quick_tricks_partner_hand_nt(
tpos, cutoff, depth, count_lho, count_rho, count_own, count_part, suit,
qtricks, comm_suit, comm_rank, &mut res, hand,
);
if res == 1 {
return qtricks;
} else if res == 2 {
if trump != DDS_NOTRUMP && trump == suit {
suit = i32::from(trump == 0);
} else {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
}
}
}
}
if trump != DDS_NOTRUMP
&& suit != trump
&& count_own > 0
&& lowest_qtricks == 0
&& (qtricks == 0
|| (tpos.winner[suit_u].hand != hand
&& tpos.winner[suit_u].hand != PARTNER[hand_u] as i32
&& tpos.winner[trump as usize].hand != hand
&& tpos.winner[trump as usize].hand != PARTNER[hand_u] as i32))
&& count_part == 0
&& tpos.length[PARTNER[hand_u]][trump as usize] > 0
{
if (count_rho > 0 || tpos.length[RHO[hand_u]][trump as usize] == 0)
&& (count_lho > 0 || tpos.length[LHO[hand_u]][trump as usize] == 0)
{
lowest_qtricks = 1;
if 1 >= cutoff {
return 1;
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
} else if count_rho == 0 && count_lho == 0 {
if (tpos.rank_in_suit[LHO[hand_u]][trump as usize]
| tpos.rank_in_suit[RHO[hand_u]][trump as usize])
< tpos.rank_in_suit[PARTNER[hand_u]][trump as usize]
{
lowest_qtricks = 1;
let rr = i32::from(
HIGHEST_RANK[tpos.rank_in_suit[PARTNER[hand_u]][trump as usize] as usize],
);
if rr != 0 {
tpos.win_ranks[depth_u][trump as usize] |= BIT_MAP_RANK[rr as usize];
if 1 >= cutoff {
return 1;
}
}
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
} else if count_lho == 0 {
if tpos.rank_in_suit[LHO[hand_u]][trump as usize]
< tpos.rank_in_suit[PARTNER[hand_u]][trump as usize]
{
lowest_qtricks = 1;
for rr in (2..=14).rev() {
if tpos.rank_in_suit[PARTNER[hand_u]][trump as usize] & BIT_MAP_RANK[rr]
!= 0
{
tpos.win_ranks[depth_u][trump as usize] |= BIT_MAP_RANK[rr];
break;
}
}
if 1 >= cutoff {
return 1;
}
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
} else if count_rho == 0 {
if tpos.rank_in_suit[RHO[hand_u]][trump as usize]
< tpos.rank_in_suit[PARTNER[hand_u]][trump as usize]
{
lowest_qtricks = 1;
for rr in (2..=14).rev() {
if tpos.rank_in_suit[PARTNER[hand_u]][trump as usize] & BIT_MAP_RANK[rr]
!= 0
{
tpos.win_ranks[depth_u][trump as usize] |= BIT_MAP_RANK[rr];
break;
}
}
if 1 >= cutoff {
return 1;
}
}
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
if suit > 3 {
break;
}
continue;
}
}
if qtricks >= cutoff {
return qtricks;
}
if trump != DDS_NOTRUMP && suit == trump {
suit = i32::from(trump == 0);
} else {
suit += 1;
if trump != DDS_NOTRUMP && suit == trump {
suit += 1;
}
}
if suit > 3 {
break;
}
}
if qtricks == 0 && (trump == DDS_NOTRUMP || tpos.winner[trump as usize].hand == -1) {
for ss in 0..DDS_SUITS {
if tpos.winner[ss].hand == -1 {
continue;
}
if tpos.length[hand_u][ss] > 0 {
tpos.win_ranks[depth_u][ss] = BIT_MAP_RANK[tpos.winner[ss].rank as usize];
}
}
cutoff = if node_type_store[hand_u] == MAXNODE {
tpos.tricks_max - target + (depth >> 2) + 2
} else {
target - tpos.tricks_max
};
if 1 >= cutoff {
return 0;
}
}
*result = false;
qtricks
}
#[allow(clippy::too_many_arguments)]
fn qtricks_lead_hand_trump(
tpos: &mut Pos,
cutoff: i32,
depth: i32,
count_lho: i32,
count_rho: i32,
lho_trump_ranks: i32,
rho_trump_ranks: i32,
count_own: i32,
count_part: i32,
suit: i32,
qtricks: i32,
res: &mut i32,
hand: i32,
) -> i32 {
*res = 1;
let suit_u = suit as usize;
let depth_u = depth as usize;
let hand_u = hand as usize;
let mut qt = qtricks;
if (count_lho != 0 || lho_trump_ranks == 0) && (count_rho != 0 || rho_trump_ranks == 0) {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.winner[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 1
&& count_rho <= 1
&& count_part <= 1
&& lho_trump_ranks == 0
&& rho_trump_ranks == 0
{
qt += count_own - 1;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
if tpos.second_best[suit_u].hand == hand {
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && count_part <= 2 {
qt += count_own - 2;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
} else if tpos.second_best[suit_u].hand == PARTNER[hand_u] as i32
&& count_own > 1
&& count_part > 1
{
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && (count_part <= 2 || count_own <= 2) {
qt += (count_own - 2).max(count_part - 2);
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
}
*res = 0;
qt
}
#[allow(clippy::too_many_arguments)]
fn qtricks_lead_hand_nt(
tpos: &mut Pos,
cutoff: i32,
depth: i32,
count_lho: i32,
count_rho: i32,
lho_trump_ranks: &mut i32,
rho_trump_ranks: &mut i32,
comm_partner: bool,
comm_suit: i32,
count_own: i32,
count_part: i32,
suit: i32,
qtricks: i32,
trump: i32,
res: &mut i32,
hand: i32,
) -> i32 {
*res = 1;
let suit_u = suit as usize;
let depth_u = depth as usize;
let hand_u = hand as usize;
let mut qt = qtricks;
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.winner[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if trump == suit && (!comm_partner || suit != comm_suit) {
*lho_trump_ranks = (*lho_trump_ranks - 1).max(0);
*rho_trump_ranks = (*rho_trump_ranks - 1).max(0);
}
if count_lho <= 1 && count_rho <= 1 && count_part <= 1 {
qt += count_own - 1;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
if tpos.second_best[suit_u].hand == hand {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if trump == suit && (!comm_partner || suit != comm_suit) {
*lho_trump_ranks = (*lho_trump_ranks - 1).max(0);
*rho_trump_ranks = (*rho_trump_ranks - 1).max(0);
}
if count_lho <= 2 && count_rho <= 2 && count_part <= 2 {
qt += count_own - 2;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
} else if tpos.second_best[suit_u].hand == PARTNER[hand_u] as i32
&& count_own > 1
&& count_part > 1
{
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if trump == suit && (!comm_partner || suit != comm_suit) {
*lho_trump_ranks = (*lho_trump_ranks - 1).max(0);
*rho_trump_ranks = (*rho_trump_ranks - 1).max(0);
}
if count_lho <= 2 && count_rho <= 2 && (count_part <= 2 || count_own <= 2) {
qt += (count_own - 2).max(count_part - 2);
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
*res = 0;
qt
}
#[allow(clippy::too_many_arguments)]
fn quick_tricks_partner_hand_trump(
tpos: &mut Pos,
cutoff: i32,
depth: i32,
count_lho: i32,
count_rho: i32,
lho_trump_ranks: i32,
rho_trump_ranks: i32,
count_own: i32,
count_part: i32,
suit: i32,
qtricks: i32,
comm_suit: i32,
comm_rank: i32,
res: &mut i32,
hand: i32,
) -> i32 {
*res = 1;
let suit_u = suit as usize;
let depth_u = depth as usize;
let hand_u = hand as usize;
let mut qt = qtricks;
if (count_lho != 0 || lho_trump_ranks == 0) && (count_rho != 0 || rho_trump_ranks == 0) {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.winner[suit_u].rank as usize];
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
qt += 1; if qt >= cutoff {
return qt;
}
if count_lho <= 1
&& count_rho <= 1
&& count_own <= 1
&& lho_trump_ranks == 0
&& rho_trump_ranks == 0
{
qt += count_part - 1;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
if tpos.second_best[suit_u].hand == PARTNER[hand_u] as i32 {
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && count_own <= 2 {
qt += count_part - 2;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
} else if tpos.second_best[suit_u].hand == hand && count_part > 1 && count_own > 1 {
if lho_trump_ranks == 0 && rho_trump_ranks == 0 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && (count_own <= 2 || count_part <= 2) {
qt += (count_part - 2).max(count_own - 2);
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
}
} else if suit == comm_suit
&& tpos.second_best[suit_u].hand == LHO[hand_u] as i32
&& (count_lho >= 2 || lho_trump_ranks == 0)
&& (count_rho >= 2 || rho_trump_ranks == 0)
{
let mut ranks: u16 = 0;
for h in 0..DDS_HANDS {
ranks |= tpos.rank_in_suit[h][suit_u];
}
let (third_rank, third_hand) = abs_rank(&tpos.rank_in_suit, ranks, 3, suit_u);
if third_hand == PARTNER[hand_u] as i32 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[third_rank as usize];
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_own <= 2
&& count_lho <= 2
&& count_rho <= 2
&& lho_trump_ranks == 0
&& rho_trump_ranks == 0
{
qt += count_part - 2;
if qt >= cutoff {
return qt;
}
}
}
}
*res = 0;
qt
}
#[allow(clippy::too_many_arguments)]
fn quick_tricks_partner_hand_nt(
tpos: &mut Pos,
cutoff: i32,
depth: i32,
count_lho: i32,
count_rho: i32,
count_own: i32,
count_part: i32,
suit: i32,
qtricks: i32,
comm_suit: i32,
comm_rank: i32,
res: &mut i32,
hand: i32,
) -> i32 {
*res = 1;
let suit_u = suit as usize;
let depth_u = depth as usize;
let hand_u = hand as usize;
let mut qt = qtricks;
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.winner[suit_u].rank as usize];
tpos.win_ranks[depth_u][comm_suit as usize] |= BIT_MAP_RANK[comm_rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 1 && count_rho <= 1 && count_own <= 1 {
qt += count_part - 1;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
if tpos.second_best[suit_u].hand == PARTNER[hand_u] as i32 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && count_own <= 2 {
qt += count_part - 2;
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
} else if tpos.second_best[suit_u].hand == hand && count_part > 1 && count_own > 1 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[tpos.second_best[suit_u].rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_lho <= 2 && count_rho <= 2 && (count_own <= 2 || count_part <= 2) {
qt += (count_part - 2).max(count_own - 2);
if qt >= cutoff {
return qt;
}
*res = 2;
return qt;
}
} else if suit == comm_suit && tpos.second_best[suit_u].hand == LHO[hand_u] as i32 {
let mut ranks: u16 = 0;
for h in 0..DDS_HANDS {
ranks |= tpos.rank_in_suit[h][suit_u];
}
let (third_rank, third_hand) = abs_rank(&tpos.rank_in_suit, ranks, 3, suit_u);
if third_hand == PARTNER[hand_u] as i32 {
tpos.win_ranks[depth_u][suit_u] |= BIT_MAP_RANK[third_rank as usize];
qt += 1;
if qt >= cutoff {
return qt;
}
if count_own <= 2 && count_lho <= 2 && count_rho <= 2 {
qt += count_part - 2;
if qt >= cutoff {
return qt;
}
}
}
}
*res = 0;
qt
}
#[inline]
pub fn quick_tricks_second_hand(
tpos: &mut Pos,
hand: i32,
depth: i32,
target: i32,
trump: i32,
node_type_store: &[i32; DDS_HANDS],
ini_depth: i32,
) -> bool {
if depth == ini_depth {
return false;
}
let hand_u = hand as usize;
let depth_u = depth as usize;
let ss = tpos.move_history[depth_u + 1].suit;
let ss_u = ss as usize;
let ranks: u16 = tpos.rank_in_suit[hand_u][ss_u] | tpos.rank_in_suit[PARTNER[hand_u]][ss_u];
for s in 0..DDS_SUITS {
tpos.win_ranks[depth_u][s] = 0;
}
if trump != DDS_NOTRUMP
&& ss != trump
&& ((tpos.rank_in_suit[hand_u][ss_u] == 0
&& tpos.rank_in_suit[hand_u][trump as usize] != 0)
|| (tpos.rank_in_suit[PARTNER[hand_u]][ss_u] == 0
&& tpos.rank_in_suit[PARTNER[hand_u]][trump as usize] != 0))
{
if tpos.rank_in_suit[LHO[hand_u]][ss_u] == 0
&& tpos.rank_in_suit[LHO[hand_u]][trump as usize] != 0
{
return false;
}
} else if ranks
> (BIT_MAP_RANK[tpos.move_history[depth_u + 1].rank as usize]
| tpos.rank_in_suit[LHO[hand_u]][ss_u])
{
if trump != DDS_NOTRUMP
&& ss != trump
&& tpos.rank_in_suit[LHO[hand_u]][trump as usize] != 0
&& tpos.rank_in_suit[LHO[hand_u]][ss_u] == 0
{
return false;
}
let rr = i32::from(HIGHEST_RANK[ranks as usize]);
tpos.win_ranks[depth_u][ss_u] = BIT_MAP_RANK[rr as usize];
} else {
return false;
}
let mut qtricks = 1;
let cutoff = if node_type_store[hand_u] == MAXNODE {
target - tpos.tricks_max
} else {
tpos.tricks_max - target + (depth >> 2) + 3
};
if qtricks >= cutoff {
return true;
}
if trump != DDS_NOTRUMP {
return false;
}
let hh = if tpos.rank_in_suit[hand_u][ss_u] > tpos.rank_in_suit[PARTNER[hand_u]][ss_u] {
hand } else {
PARTNER[hand_u] as i32
};
let hh_u = hh as usize;
if tpos.winner[ss_u].hand == hh
&& tpos.second_best[ss_u].rank != 0
&& tpos.second_best[ss_u].hand == hh
{
qtricks += 1;
tpos.win_ranks[depth_u][ss_u] |= BIT_MAP_RANK[tpos.second_best[ss_u].rank as usize];
if qtricks >= cutoff {
return true;
}
}
for s in 0..DDS_SUITS {
if s == ss_u || tpos.length[hh_u][s] == 0 {
continue;
}
if tpos.length[LHO[hh_u]][s] == 0
&& tpos.length[RHO[hh_u]][s] == 0
&& tpos.length[PARTNER[hh_u]][s] == 0
{
qtricks += i32::from(crate::lookup::COUNT_TABLE[tpos.rank_in_suit[hh_u][s] as usize]);
if qtricks >= cutoff {
return true;
}
} else if tpos.winner[s].rank != 0 && tpos.winner[s].hand == hh {
qtricks += 1;
tpos.win_ranks[depth_u][s] |= BIT_MAP_RANK[tpos.winner[s].rank as usize];
if qtricks >= cutoff {
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::move_type::HighCard;
const NODE_TYPE: [i32; 4] = [MAXNODE, MINNODE, MAXNODE, MINNODE];
#[test]
fn akq_void_returns_three() {
let mut pos = Pos::default();
let akq: u16 = 0x1000 | 0x0800 | 0x0400;
pos.rank_in_suit[0][0] = akq;
pos.length[0][0] = 3;
pos.aggr[0] = akq;
pos.winner[0] = HighCard { rank: 14, hand: 0 };
pos.second_best[0] = HighCard { rank: 13, hand: 0 };
for s in 1..4 {
pos.winner[s] = HighCard { rank: 0, hand: -1 };
pos.second_best[s] = HighCard { rank: 0, hand: -1 };
}
let mut success = false;
let qt = quick_tricks(
&mut pos,
0, 44,
3, DDS_NOTRUMP,
&mut success,
&NODE_TYPE,
);
assert!(
qt >= 3,
"expected ≥ 3 quick tricks holding AKQ alone, got {qt}"
);
assert!(success, "expected success flag set when target met");
}
#[test]
fn partial_qt_continues_search() {
let mut pos = Pos::default();
pos.rank_in_suit[0][0] = 0x1000;
pos.length[0][0] = 1;
pos.aggr[0] = 0x1000;
pos.winner[0] = HighCard { rank: 14, hand: 0 };
pos.second_best[0] = HighCard { rank: 0, hand: -1 };
for s in 1..4 {
pos.winner[s] = HighCard { rank: 0, hand: -1 };
pos.second_best[s] = HighCard { rank: 0, hand: -1 };
}
let mut success = true; let qt = quick_tricks(&mut pos, 0, 0, 13, DDS_NOTRUMP, &mut success, &NODE_TYPE);
assert_eq!(qt, 1, "single A → 1 quick trick");
assert!(
!success,
"expected success = false: heuristic can not determine outcome"
);
}
#[test]
fn no_winners_returns_zero() {
let mut pos = Pos::default();
for s in 0..4 {
pos.rank_in_suit[1][s] = 0x1000; pos.length[1][s] = 1;
pos.aggr[s] = 0x1000;
pos.winner[s] = HighCard { rank: 14, hand: 1 };
pos.second_best[s] = HighCard { rank: 0, hand: -1 };
}
let mut success = true;
let qt = quick_tricks(&mut pos, 0, 4, 1, DDS_NOTRUMP, &mut success, &NODE_TYPE);
assert_eq!(qt, 0, "no winners → 0 quick tricks");
assert!(
!success,
"expected success = false: heuristic indeterminate"
);
}
#[test]
fn second_hand_at_ini_depth_returns_false() {
let mut pos = Pos::default();
let result = quick_tricks_second_hand(
&mut pos,
0,
44,
13,
DDS_NOTRUMP,
&NODE_TYPE,
44, );
assert!(!result);
}
}