use std::io::Write as _;
use yahtzee_engine::{Category, Game, HeuristicBot, Keep, Phase, Strategy, TurnAction};
fn show_card(game: &Game, seat: usize, name: &str) {
let card = game.scorecard(seat);
println!("--- {name} ---");
for category in Category::ALL {
match card.get(category) {
Some(value) => println!(" {category:16} {value:3}"),
None => println!(" {category:16} -"),
}
}
println!(
" upper {}/63 (+{}) yahtzee bonuses {} total {}",
card.upper_subtotal(),
card.upper_bonus(),
card.yahtzee_bonuses(),
card.total()
);
}
fn prompt(line: &mut String) -> Option<()> {
print!("> ");
std::io::stdout().flush().ok()?;
line.clear();
(std::io::stdin().read_line(line).ok()? > 0).then_some(())
}
fn human_action(game: &Game) -> Option<TurnAction> {
let view = game.view();
let legal: Vec<Category> = view.legal_categories().iter().collect();
println!("dice {} ({} roll(s) left)", view.dice(), view.rolls_left());
for (i, category) in legal.iter().enumerate() {
let value = view.state().apply(*category, view.dice());
let value = value.expect("legal").value;
println!(" s {:2} {category:16} {value:3}", i + 1);
}
let mut line = String::new();
loop {
prompt(&mut line)?;
let input = line.trim();
if input == "q" {
return None;
}
if let Some(rest) = input.strip_prefix('s')
&& let Ok(i) = rest.trim().parse::<usize>()
&& let Some(&category) = legal.get(i.wrapping_sub(1))
{
return Some(TurnAction::Score(category));
}
if view.rolls_left() > 0
&& let Some(rest) = input.strip_prefix('k')
&& let Ok(keep) = rest.trim().parse::<Keep>()
&& view.dice().contains(keep)
{
return Some(TurnAction::Reroll(keep));
}
println!("try `k 446`, `s 3`, or `q`");
}
}
fn main() {
let mut rng = rand::rng();
let mut bot = HeuristicBot::new();
let mut game = Game::new(2);
println!("You are seat 0; the heuristic bot is seat 1.");
while !game.is_over() {
match game.phase() {
Phase::AwaitingRoll => {
let dice = game.roll_with(&mut rng).expect("a roll is due");
if game.seat() == 1 {
println!("bot rolls {dice}");
}
}
Phase::AwaitingAction if game.seat() == 0 => {
let Some(action) = human_action(&game) else {
println!("resigned");
return;
};
if game.act(action).is_err() {
println!("illegal move; try again");
}
}
Phase::AwaitingAction => {
let action = if game.rolls_left() > 0 {
bot.choose_action(&game.view())
} else {
TurnAction::Score(bot.choose_category(&game.view()))
};
match action {
TurnAction::Reroll(keep) => println!("bot keeps [{keep}]"),
TurnAction::Score(category) => println!("bot scores {category}"),
}
game.act(action).expect("the bot plays legally");
}
Phase::Finished => unreachable!(),
}
}
show_card(&game, 0, "you");
show_card(&game, 1, bot.name());
let totals = game.totals();
println!("final: you {} - {} bot", totals[0], totals[1]);
}