190 lines
4.6 KiB
Rust
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
|
|
}
|