use chironaut::{
action::Action,
game::Game,
rules::GameRules,
card::Card,
game::GameStage,
};
use std::io::{self, Write};
fn main() {
println!("=== CLI POKER GAME ===\n");
let rules = GameRules::nlhe(10, 20);
let mut game = Game::new(rules);
setup_players(&mut game);
let mut continue_playing = true;
while continue_playing {
game.start_hand().unwrap();
println!("\n--- NEW HAND STARTED ---");
play_hand(&mut game);
continue_playing = ask_continue();
}
println!("Thanks for playing!");
}
fn setup_players(game: &mut Game) {
println!("Setting up players (2 players with 1000 chips each)");
game.add_player_to_seat("Player 1".to_string(), 2000, 0).unwrap();
game.add_player_to_seat("Player 2".to_string(), 1000, 1).unwrap();
println!("Players added successfully!");
}
fn play_hand(game: &mut Game) {
let is_heads_up = game.players.len() == 2;
let sb_pos = if game.players.len() == 2 {
game.dealer_position
} else {
find_player_seat(game, game.dealer_position + 1)
};
let bb_pos = if game.players.len() == 2 {
find_player_seat(game, game.dealer_position + 1)
} else {
find_player_seat(game, game.dealer_position + 2)
};
println!(
"Blinds: {} (small blind, {}), {} (big blind, {})",
game.players[sb_pos].name,
game.rules.small_blind,
game.players[bb_pos].name,
game.rules.big_blind
);
if is_heads_up && game.current_position != sb_pos {
println!("Adjusting play order for heads-up: SB acts first preflop");
game.current_position = sb_pos;
}
println!("\n--- PREFLOP ---");
println!("Pot: {}", game.pot);
play_street(game);
print_player_chips(game);
if is_hand_over_early(game) {
handle_early_end(game);
return;
}
if matches!(game.stage, GameStage::Showdown) {
resolve_showdown(game);
return;
}
if is_heads_up && game.current_position != sb_pos && game.stage == GameStage::Flop {
println!("Adjusting play order for postflop: SB acts first");
game.current_position = sb_pos;
}
game.next_street().unwrap();
println!("\n--- FLOP ---");
println!("Community cards: {}", format_community_cards(&game.community_cards));
println!("Pot: {}", game.pot);
play_street(game);
print_player_chips(game);
if is_hand_over_early(game) {
handle_early_end(game);
return;
}
if matches!(game.stage, GameStage::Showdown) {
resolve_showdown(game);
return;
}
game.next_street().unwrap();
println!("\n--- TURN ---");
println!("Community cards: {}", format_community_cards(&game.community_cards));
println!("Pot: {}", game.pot);
play_street(game);
print_player_chips(game);
if is_hand_over_early(game) {
handle_early_end(game);
return;
}
if matches!(game.stage, GameStage::Showdown) {
resolve_showdown(game);
return;
}
game.next_street().unwrap();
println!("\n--- RIVER ---");
println!("Community cards: {}", format_community_cards(&game.community_cards));
println!("Pot: {}", game.pot);
play_street(game);
print_player_chips(game);
resolve_showdown(game);
}
fn find_player_seat(game: &Game, pos: usize) -> usize {
pos % game.players.len()
}
fn is_hand_over_early(game: &Game) -> bool {
game.players.iter().filter(|p| !p.is_folded).count() <= 1
}
fn handle_early_end(game: &mut Game) {
let winner = game.players.iter()
.position(|p| !p.is_folded)
.expect("Should have at least one player not folded");
let pot_amount = game.pot;
game.players[winner].chips += pot_amount;
println!("\n--- HAND ENDED EARLY ---");
println!("{} wins {} chips (all others folded)",
game.players[winner].name, pot_amount);
game.pot = 0;
}
fn play_street(game: &mut Game) {
if !game.community_cards.is_empty() {
println!("Community cards: {}", format_community_cards(&game.community_cards));
}
while !game.is_round_complete() {
let current_position = game.current_position;
let player_name = game.players[current_position].name.clone();
let player_chips = game.players[current_position].chips;
let player_bet_amount = game.players[current_position].bet_amount;
let player_seat = game.players[current_position].seat_position;
println!("\n{}'s turn (seat {})", player_name, player_seat);
println!("Your cards: {}", game.players[current_position].hand);
if !game.community_cards.is_empty() {
println!("Community cards: {}", format_community_cards(&game.community_cards));
}
let action = get_player_action(game);
match game.handle_action(action.clone()) {
Ok(_) => {
match action {
Action::Fold => println!("{} folds", player_name),
Action::Bet(amount) => {
if amount == 0 {
println!("{} checks", player_name);
}
else if player_bet_amount + amount == game.current_bet {
println!("{} calls", player_name);
}
else if amount == player_chips {
println!("{} goes all-in with {}", player_name, amount);
}
else if game.current_bet == 0 {
println!("{} bets {}", player_name, amount);
}
else {
println!("{} raises to {}", player_name, player_bet_amount + amount);
}
},
Action::Post(amount) => println!("{} posts {}", player_name, amount),
}
if matches!(game.stage, GameStage::Showdown) {
break;
}
},
Err(e) => {
println!("Invalid action: {}. Please try again.", e);
continue;
}
}
}
}
fn get_player_action(game: &Game) -> Action {
let current_player = &game.players[game.current_position];
let options = get_available_actions(game);
println!("Available actions:");
for (i, action) in options.iter().enumerate() {
match action {
Action::Fold => println!("{}. Fold", i + 1),
Action::Bet(amount) => {
if *amount == 0 {
println!("{}. Check", i + 1);
}
else if current_player.bet_amount + *amount == game.current_bet {
println!("{}. Call ({})", i + 1, amount);
}
else if *amount == current_player.chips {
println!("{}. All-In ({})", i + 1, amount);
}
else if game.current_bet == 0 {
println!("{}. Bet ({})", i + 1, amount);
}
else {
println!("{}. Raise to ({})", i + 1, current_player.bet_amount + *amount);
}
},
Action::Post(amount) => println!("{}. Post {}", i + 1, amount),
}
}
loop {
print!("> ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
if let Ok(choice) = input.parse::<usize>() {
if choice > 0 && choice <= options.len() {
return options[choice - 1].clone();
} else {
println!("Invalid choice. Please enter a number between 1 and {}", options.len());
}
} else {
println!("Invalid input. Please enter a number.");
}
}
}
fn get_available_actions(game: &Game) -> Vec<Action> {
let current_player = &game.players[game.current_position];
let mut actions = Vec::new();
actions.push(Action::Fold);
let is_preflop = game.stage == GameStage::Preflop;
if is_preflop && game.players.len() == 2 && current_player.bet_amount == game.rules.small_blind {
} else if game.current_bet == current_player.bet_amount {
actions.push(Action::Bet(0)); }
if game.current_bet > current_player.bet_amount {
let call_amount = game.current_bet - current_player.bet_amount;
if call_amount <= current_player.chips {
actions.push(Action::Bet(call_amount)); }
}
if game.current_bet == 0 {
let min_bet = game.rules.big_blind;
if current_player.chips > min_bet {
actions.push(Action::Bet(min_bet));
}
}
if game.current_bet > 0 && current_player.chips > (game.current_bet - current_player.bet_amount) {
let min_raise = game.current_bet * 2 - current_player.bet_amount;
if current_player.chips > min_raise {
actions.push(Action::Bet(min_raise));
}
}
if current_player.chips > 0 {
let all_in_amount = current_player.chips;
if !actions.iter().any(|a| matches!(a, Action::Bet(amount) if *amount == all_in_amount)) {
actions.push(Action::Bet(all_in_amount));
}
}
actions
}
fn is_hand_over(game: &Game) -> bool {
game.players.iter().filter(|p| !p.is_folded).count() <= 1
|| matches!(game.stage, GameStage::Showdown)
}
fn resolve_showdown(game: &mut Game) {
println!("\n--- HAND RESULTS ---");
if !game.community_cards.is_empty() {
println!("Community cards: {}", format_community_cards(&game.community_cards));
}
for player in &game.players {
if player.is_folded {
println!("{} folded", player.name);
} else {
println!("{}: {} (Chips: {})",
player.name,
player.hand,
player.chips);
}
}
let active_players: Vec<_> = game.players.iter()
.enumerate()
.filter(|(_, p)| !p.is_folded)
.collect();
let pot_amount = game.pot;
if active_players.len() == 1 {
let (winner_idx, _) = active_players[0];
game.players[winner_idx].chips += pot_amount;
println!("\n{} wins {} chips (all others folded)",
game.players[winner_idx].name, pot_amount);
}
else if active_players.len() > 1 {
let (winners, _best_hands) = game.evaluate_winners_without_paying();
if winners.is_empty() {
println!("\nNo winners determined!");
return;
}
let share = pot_amount / winners.len() as u32;
let remainder = pot_amount % winners.len() as u32;
for (i, &winner_idx) in winners.iter().enumerate() {
let mut amount = share;
if i == 0 {
amount += remainder; }
game.players[winner_idx].chips += amount;
println!("\n{} wins {} chips with best hand",
game.players[winner_idx].name, amount);
}
assert_eq!(share * winners.len() as u32 + remainder, pot_amount,
"Pot distribution error: awarded {} of {} chips",
share * winners.len() as u32 + remainder, pot_amount);
}
game.pot = 0;
}
fn ask_continue() -> bool {
println!("\nPlay another hand? (y/n)");
loop {
print!("> ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim().to_lowercase();
match input.as_str() {
"y" | "yes" => return true,
"n" | "no" => return false,
_ => println!("Please enter 'y' or 'n'"),
}
}
}
fn format_community_cards(cards: &[Card]) -> String {
cards.iter()
.map(|card| format!("{}", card))
.collect::<Vec<_>>()
.join(" ")
}
fn print_player_chips(game: &Game) {
println!("\n--- CHIP COUNTS ---");
for player in &game.players {
println!("{}: {} chips", player.name, player.chips);
}
}