chironaut 0.3.5

A poker game library for Texas Hold'em and other poker variants
Documentation
use chironaut::{
    action::Action,
    game::Game,
    rules::GameRules,
    game::GameStage,
};

#[test]
fn test_side_pots() {
    // Create a new game with 3 players having different stack sizes
    let rules = GameRules::nlhe(10, 20);
    let mut game = Game::new(rules);
    
    // Add three players with very different stack sizes
    game.add_player("Big Stack".to_string(), 1000).unwrap();
    game.add_player("Medium Stack".to_string(), 500).unwrap();
    game.add_player("Short Stack".to_string(), 200).unwrap();
    
    // Start a hand
    game.start_hand().unwrap();
    
    // Print out initial state
    println!("Initial state:");
    println!("Dealer position: {}", game.dealer_position);
    println!("Current position: {}", game.current_position);
    
    for (i, player) in game.players.iter().enumerate() {
        println!("Player {}: {} chips {}, bet_amount {}", 
                 i, player.name, player.chips, player.bet_amount);
    }
    
    println!("Pot: {}", game.pot);
    println!("Current bet: {}", game.current_bet);
    
    // Track total chips before any actions
    let initial_total_chips = game.players.iter().map(|p| p.chips).sum::<u32>() + game.pot;
    
    // First player action - should call or raise
    let current_idx = game.current_position;
    println!("\nFirst to act: {} at position {}", 
             game.players[current_idx].name, current_idx);
    
    // Call the big blind or raise
    let call_amount = game.current_bet - game.players[current_idx].bet_amount;
    game.handle_action(Action::Bet(call_amount + 40)).unwrap(); // Call + raise 40
    
    println!("Current bet: {}", game.current_bet);
    
    // Second player action
    let current_idx = game.current_position;
    println!("\nSecond to act: {} at position {}", 
             game.players[current_idx].name, current_idx);
    
    // Call the current bet or raise
    let call_amount = game.current_bet - game.players[current_idx].bet_amount;
    game.handle_action(Action::Bet(call_amount + 80)).unwrap(); // Call + raise 80
    
    println!("Current bet: {}", game.current_bet);
    
    // If there's a third player to act in preflop
    if game.current_position < game.players.len() {
        let current_idx = game.current_position;
        println!("\nThird to act: {} at position {}", 
                 game.players[current_idx].name, current_idx);
        
        // This player should call with all their chips
        let current_bet = game.current_bet;
        let player_bet = game.players[current_idx].bet_amount;
        let call_amount = current_bet - player_bet;
        let available_chips = game.players[current_idx].chips;
        let bet_amount = call_amount.min(available_chips);
        
        println!("Current bet: {}, Player bet: {}, Call amount: {}, Available: {}", 
                 current_bet, player_bet, call_amount, available_chips);
        
        game.handle_action(Action::Bet(bet_amount)).unwrap();
    }
    
    // Continue with any other players who need to act preflop
    while game.stage == GameStage::Preflop && !game.is_round_complete() && game.current_position < game.players.len() {
        let current_idx = game.current_position;
        println!("\nAdditional action from: {} at position {}", 
                 game.players[current_idx].name, current_idx);
        
        let current_bet = game.current_bet;
        let player_bet = game.players[current_idx].bet_amount;
        let call_amount = current_bet - player_bet;
        
        game.handle_action(Action::Bet(call_amount)).unwrap();
    }
    
    // Print state after preflop
    println!("\nAfter preflop actions:");
    for (i, player) in game.players.iter().enumerate() {
        println!("Player {}: {} chips {}, bet_amount {}, is_all_in {}", 
                 i, player.name, player.chips, player.bet_amount, player.is_all_in);
    }
    println!("Pot: {}", game.pot);
    
    // Find all-in players
    let all_in_players = game.players.iter()
        .filter(|p| p.is_all_in)
        .count();
    
    println!("All-in players: {}", all_in_players);
    println!("Side pots: {:?}", game.side_pots);
    
    // Complete the hand
    while game.stage != GameStage::Showdown {
        if game.is_round_complete() {
            game.next_street().unwrap();
            println!("\nAdvanced to: {:?}", game.stage);
            println!("Community cards: {}", game.community_cards.len());
            
            // If not at showdown yet and betting is needed
            if game.stage != GameStage::Showdown {
                // Players check through remaining streets
                let active_players = game.players.iter()
                    .filter(|p| !p.is_folded && !p.is_all_in)
                    .count();
                
                for _ in 0..active_players {
                    if game.current_position < game.players.len() {
                        game.handle_action(Action::Bet(0)).unwrap();
                    }
                }
            }
        } else {
            // Handle any remaining actions
            if game.current_position < game.players.len() {
                let current_bet = game.current_bet;
                let player_bet = game.players[game.current_position].bet_amount;
                let call_amount = current_bet - player_bet;
                
                println!("Additional action needed from position {}, call amount: {}", 
                         game.current_position, call_amount);
                
                game.handle_action(Action::Bet(call_amount)).unwrap();
            } else {
                break; // Safety to avoid infinite loop
            }
        }
    }
    
    // If the pot isn't distributed yet, we need to evaluate the winners
    if game.pot > 0 {
        println!("Evaluating winner to distribute pot");
        game.evaluate_winner();
    }
    
    // Final state
    println!("\nFinal state:");
    for player in game.players.iter() {
        println!("Player {}: chips {}, is_all_in {}, is_folded {}", 
                 player.name, player.chips, player.is_all_in, player.is_folded);
    }
    
    println!("Pot: {}", game.pot);
    let final_total_chips = game.players.iter().map(|p| p.chips).sum::<u32>() + game.pot;
    println!("Total chips: {}, Initial total: {}", final_total_chips, initial_total_chips);
    
    // Total chips should be preserved
    assert_eq!(final_total_chips, initial_total_chips);
}