@@ -266,9 +266,17 @@ pub fn calculate_effective_tiles(hand_tiles: &[u32]) -> u32 {
266266 let current_shanten = calculate_shanten ( hand_tiles) ;
267267 let mut effective_count = 0 ;
268268
269- for tile_type in 0 ..34 {
270- let count_in_hand = hand_tiles. iter ( ) . filter ( |& & t| ( t / 4 ) == tile_type) . count ( ) ;
271- if count_in_hand >= 4 {
269+ let mut hand_counts = [ 0u8 ; TILE_MAX ] ;
270+ for & tile in hand_tiles {
271+ let tile_type = ( tile / 4 ) as usize ;
272+ if tile_type < TILE_MAX {
273+ hand_counts[ tile_type] += 1 ;
274+ }
275+ }
276+
277+ for tile_type in 0 ..34u32 {
278+ // skip: already holding all 4 copies
279+ if hand_counts[ tile_type as usize ] >= 4 {
272280 continue ;
273281 }
274282
@@ -299,19 +307,25 @@ pub fn calculate_best_ukeire(hand_tiles: &[u32], visible_tiles: &[u32]) -> u32 {
299307
300308 let current_shanten = calculate_shanten ( hand_tiles) ;
301309
310+ let mut base_counts = [ 0u8 ; TILE_MAX ] ;
311+ for & tile in hand_tiles {
312+ let tile_type = ( tile / 4 ) as usize ;
313+ if tile_type < TILE_MAX {
314+ base_counts[ tile_type] += 1 ;
315+ }
316+ }
317+
302318 for ( idx, _) in hand_tiles. iter ( ) . enumerate ( ) {
303319 let new_hand: Vec < u32 > = hand_tiles
304320 . iter ( )
305321 . enumerate ( )
306322 . filter ( |( i, _) | * i != idx)
307323 . map ( |( _, & t) | t)
308324 . collect ( ) ;
309- let mut new_hand_counts = [ 0u8 ; TILE_MAX ] ;
310- for & tile in & new_hand {
311- let tile_type = ( tile / 4 ) as usize ;
312- if tile_type < TILE_MAX {
313- new_hand_counts[ tile_type] += 1 ;
314- }
325+ let removed_type = ( hand_tiles[ idx] / 4 ) as usize ;
326+ let mut new_hand_counts = base_counts;
327+ if removed_type < TILE_MAX {
328+ new_hand_counts[ removed_type] -= 1 ;
315329 }
316330
317331 let new_shanten = calculate_shanten ( & new_hand) ;
@@ -320,7 +334,8 @@ pub fn calculate_best_ukeire(hand_tiles: &[u32], visible_tiles: &[u32]) -> u32 {
320334 }
321335
322336 let mut ukeire = 0 ;
323- for tile_type in 0 ..34 {
337+ for tile_type in 0 ..34u32 {
338+ // skip: already holding all 4 copies
324339 if new_hand_counts[ tile_type as usize ] >= 4 {
325340 continue ;
326341 }
@@ -330,7 +345,9 @@ pub fn calculate_best_ukeire(hand_tiles: &[u32], visible_tiles: &[u32]) -> u32 {
330345 let test_shanten = calculate_shanten ( & test_hand) ;
331346
332347 if test_shanten < new_shanten {
333- ukeire += 4 - visible_counts[ tile_type as usize ] ;
348+ ukeire += 4
349+ - visible_counts[ tile_type as usize ]
350+ - new_hand_counts[ tile_type as usize ] as u32 ;
334351 }
335352 }
336353
@@ -437,9 +454,17 @@ pub fn calculate_effective_tiles_3p(hand_tiles: &[u32]) -> u32 {
437454 let current_shanten = calculate_shanten_3p ( hand_tiles) ;
438455 let mut effective_count = 0 ;
439456
457+ let mut hand_counts = [ 0u8 ; TILE_MAX ] ;
458+ for & tile in hand_tiles {
459+ let tile_type = ( tile / 4 ) as usize ;
460+ if tile_type < TILE_MAX {
461+ hand_counts[ tile_type] += 1 ;
462+ }
463+ }
464+
440465 for & tile_type in & SANMA_VALID_TILE_TYPES {
441- let count_in_hand = hand_tiles . iter ( ) . filter ( | & & t| ( t / 4 ) == tile_type ) . count ( ) ;
442- if count_in_hand >= 4 {
466+ // skip: already holding all 4 copies
467+ if hand_counts [ tile_type as usize ] >= 4 {
443468 continue ;
444469 }
445470
@@ -470,19 +495,25 @@ pub fn calculate_best_ukeire_3p(hand_tiles: &[u32], visible_tiles: &[u32]) -> u3
470495
471496 let current_shanten = calculate_shanten_3p ( hand_tiles) ;
472497
498+ let mut base_counts = [ 0u8 ; TILE_MAX ] ;
499+ for & tile in hand_tiles {
500+ let tile_type = ( tile / 4 ) as usize ;
501+ if tile_type < TILE_MAX {
502+ base_counts[ tile_type] += 1 ;
503+ }
504+ }
505+
473506 for ( idx, _) in hand_tiles. iter ( ) . enumerate ( ) {
474507 let new_hand: Vec < u32 > = hand_tiles
475508 . iter ( )
476509 . enumerate ( )
477510 . filter ( |( i, _) | * i != idx)
478511 . map ( |( _, & t) | t)
479512 . collect ( ) ;
480- let mut new_hand_counts = [ 0u8 ; TILE_MAX ] ;
481- for & tile in & new_hand {
482- let tile_type = ( tile / 4 ) as usize ;
483- if tile_type < TILE_MAX {
484- new_hand_counts[ tile_type] += 1 ;
485- }
513+ let removed_type = ( hand_tiles[ idx] / 4 ) as usize ;
514+ let mut new_hand_counts = base_counts;
515+ if removed_type < TILE_MAX {
516+ new_hand_counts[ removed_type] -= 1 ;
486517 }
487518
488519 let new_shanten = calculate_shanten_3p ( & new_hand) ;
@@ -492,6 +523,7 @@ pub fn calculate_best_ukeire_3p(hand_tiles: &[u32], visible_tiles: &[u32]) -> u3
492523
493524 let mut ukeire = 0 ;
494525 for & tile_type in & SANMA_VALID_TILE_TYPES {
526+ // skip: already holding all 4 copies
495527 if new_hand_counts[ tile_type as usize ] >= 4 {
496528 continue ;
497529 }
@@ -501,7 +533,9 @@ pub fn calculate_best_ukeire_3p(hand_tiles: &[u32], visible_tiles: &[u32]) -> u3
501533 let test_shanten = calculate_shanten_3p ( & test_hand) ;
502534
503535 if test_shanten < new_shanten {
504- ukeire += 4 - visible_counts[ tile_type as usize ] ;
536+ ukeire += 4
537+ - visible_counts[ tile_type as usize ]
538+ - new_hand_counts[ tile_type as usize ] as u32 ;
505539 }
506540 }
507541
@@ -510,3 +544,90 @@ pub fn calculate_best_ukeire_3p(hand_tiles: &[u32], visible_tiles: &[u32]) -> u3
510544
511545 max_ukeire
512546}
547+
548+ #[ cfg( all( test, feature = "python" ) ) ]
549+ mod tests {
550+ use super :: * ;
551+
552+ /// Helper: build tile IDs from tile-type indices (each using instance 0).
553+ fn tiles_from_types ( types : & [ u32 ] ) -> Vec < u32 > {
554+ types. iter ( ) . map ( |& t| t * 4 ) . collect ( )
555+ }
556+
557+ #[ test]
558+ fn test_best_ukeire_subtracts_hand_counts ( ) {
559+ // 123m 456m 789m 12p 112z (14 tiles, shanten=0).
560+ // Best discard: 2z → 123m 456m 789m 12p 11z (tenpai, waiting 3p).
561+ // Draw 3p: remaining = 4 - 0(visible) - 0(hand) = 4.
562+ // Total ukeire = 4.
563+ let hand = tiles_from_types ( & [
564+ 0 , 1 , 2 , // 123m
565+ 3 , 4 , 5 , // 456m
566+ 6 , 7 , 8 , // 789m
567+ 9 , 10 , // 12p
568+ 27 , 27 , 28 , // 112z
569+ ] ) ;
570+ let visible: Vec < u32 > = vec ! [ ] ;
571+
572+ let ukeire = calculate_best_ukeire ( & hand, & visible) ;
573+ assert_eq ! ( ukeire, 4 , "tenpai waiting on 3p should have ukeire=4" ) ;
574+ }
575+
576+ #[ test]
577+ fn test_best_ukeire_3p_subtracts_hand_counts ( ) {
578+ // 123p 456p 789p 12s 112z (14 tiles, shanten=0).
579+ // Best discard: 2z → 123p 456p 789p 12s 11z (tenpai, waiting 3s).
580+ // Draw 3s: remaining = 4 - 0(visible) - 0(hand) = 4.
581+ let hand = tiles_from_types ( & [
582+ 9 , 10 , 11 , // 123p
583+ 12 , 13 , 14 , // 456p
584+ 15 , 16 , 17 , // 789p
585+ 18 , 19 , // 12s
586+ 27 , 27 , 28 , // 112z
587+ ] ) ;
588+ let visible: Vec < u32 > = vec ! [ ] ;
589+
590+ let ukeire = calculate_best_ukeire_3p ( & hand, & visible) ;
591+ assert_eq ! ( ukeire, 4 , "tenpai waiting on 3s should have ukeire=4" ) ;
592+ }
593+
594+ #[ test]
595+ fn test_best_ukeire_hand_copies_reduce_remaining ( ) {
596+ // 123m 456m 789m 33p 332z (14 tiles).
597+ // Discard 2z → 123m 456m 789m 33p 33z (13 tiles, tenpai).
598+ // Winning draw: 3p → 333p as koutsu, or 3z → 333z as koutsu.
599+ // 3p in hand: 2 copies → remaining = 4 - 0 - 2 = 2
600+ // 3z in hand: 2 copies → remaining = 4 - 0 - 2 = 2
601+ // Total ukeire = 4 (not 8 as old code would compute).
602+ let hand = tiles_from_types ( & [
603+ 0 , 1 , 2 , // 123m
604+ 3 , 4 , 5 , // 456m
605+ 6 , 7 , 8 , // 789m
606+ 11 , 11 , // 33p
607+ 29 , 29 , 28 , // 332z
608+ ] ) ;
609+ let visible: Vec < u32 > = vec ! [ ] ;
610+
611+ let ukeire = calculate_best_ukeire ( & hand, & visible) ;
612+ assert_eq ! ( ukeire, 4 , "hand copies should reduce remaining count" ) ;
613+ }
614+
615+ #[ test]
616+ fn test_best_ukeire_visible_and_hand_both_subtracted ( ) {
617+ // 123m 456m 789m 33p 332z (14 tiles).
618+ // 1 copy of 3p visible → 3p remaining = 4 - 1 - 2 = 1
619+ // 3z remaining = 4 - 0 - 2 = 2
620+ // Total ukeire = 3.
621+ let hand = tiles_from_types ( & [
622+ 0 , 1 , 2 , // 123m
623+ 3 , 4 , 5 , // 456m
624+ 6 , 7 , 8 , // 789m
625+ 11 , 11 , // 33p
626+ 29 , 29 , 28 , // 332z
627+ ] ) ;
628+ let visible = vec ! [ 11 * 4 + 2 ] ; // 3p instance 2
629+
630+ let ukeire = calculate_best_ukeire ( & hand, & visible) ;
631+ assert_eq ! ( ukeire, 3 , "visible + hand copies should both reduce count" ) ;
632+ }
633+ }
0 commit comments