card-calculator/src/game.rs
2025-07-11 17:22:04 +02:00

190 lines
4.6 KiB
Rust

use rand::{seq::SliceRandom, Rng};
pub trait Player {
fn select_card(
&mut self,
hand: &[Card],
stack: &[Card],
additional_information: &AdditionalInformation,
) -> Card;
}
pub trait PlayerBuilder: Sync {
fn build(&self) -> Box<dyn Player>;
}
#[derive(Debug)]
pub struct AdditionalInformation {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Color {
Clubs,
Spades,
Hearts,
Diamonds,
}
impl Color {
fn get_symbol(&self) -> char {
match self {
Color::Clubs => '♣',
Color::Spades => '♠',
Color::Hearts => '♥',
Color::Diamonds => '♦',
}
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Card {
pub color: Color,
pub value: u8,
}
impl std::fmt::Debug for Card {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.color.get_symbol(), self.get_value_char())
}
}
impl Card {
fn new(color: Color, value: u8) -> Self {
Self { color, value }
}
fn cost(&self) -> u8 {
match self.color {
Color::Spades => {
if self.value == 6 {
4
} else {
0
}
}
Color::Hearts => 1,
_ => 0,
}
}
fn get_value_char(&self) -> &'static str {
match self.value {
1 => "7",
2 => "8",
3 => "9",
4 => "10",
5 => "J",
6 => "Q",
7 => "K",
8 => "A",
_ => unreachable!(),
}
}
}
pub fn playeble(card: Card, hand: &[Card], stack_color: Option<Color>) -> bool {
if let Some(stack_color) = stack_color {
stack_color == card.color || hand.iter().all(|c| c.color != stack_color)
} else {
true
}
}
pub fn herzen(
player_builders: &[Box<dyn PlayerBuilder>],
starting_player: usize,
rng: &mut impl Rng,
) -> Vec<u8> {
let mut player = player_builders
.iter()
.map(|p| p.build())
.collect::<Vec<_>>();
let mut full_deck = (1..=8)
.flat_map(|u| {
[
Card::new(Color::Clubs, u),
Card::new(Color::Spades, u),
Card::new(Color::Hearts, u),
Card::new(Color::Diamonds, u),
]
.into_iter()
})
.collect::<Vec<_>>();
full_deck.shuffle(rng);
let hand_size = full_deck.len() / player.len();
let mut hands = (0..player.len())
.map(|i| full_deck[(hand_size * i)..(hand_size * (i + 1))].to_vec())
.collect::<Vec<_>>();
let mut round_starting_player = starting_player;
let mut points = vec![0; player.len()];
for i in 0..hand_size {
let mut stack = Vec::with_capacity(player.len());
println!("## Round {i}:");
for j in 0..player.len() {
let index = (round_starting_player + j) % player.len();
println!("# Player {index}");
let ai = AdditionalInformation {};
let played_card = player[index].select_card(&hands[index], &stack, &ai);
assert!(playeble(
played_card,
&hands[index],
stack.first().map(|c| c.color)
));
let card_index = hands[index].iter().position(|&c| c == played_card).unwrap();
hands[index].remove(card_index);
println!("# Player {index} played {played_card:?}");
stack.push(played_card);
}
let (stack_winner, winning_card) = stack
.iter()
.enumerate()
.max_by_key(|(_, &c)| {
if c.color == stack[0].color {
c.value
} else {
0
}
})
.unwrap();
let winner = (stack_winner + round_starting_player) % player.len();
let cost = stack.iter().map(|c| c.cost()).sum::<u8>();
println!(
"## Player {winner} takes the stack {stack:?} with {winning_card:?}, {cost} points"
);
points[winner] += cost;
round_starting_player = winner;
}
let remainder = &full_deck[hand_size * player.len()..];
let remainder_cost = remainder.iter().map(|c| c.cost()).sum::<u8>();
println!(
"## Player {round_starting_player} takes the remaining cards {remainder:?}, {remainder_cost} points"
);
points[round_starting_player] += remainder_cost;
println!("## Game ended. Points: {points:?}");
points
}