diff --git a/Cargo.toml b/Cargo.toml index 53e6345..7714021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -rand = "0.8.5" +rand = { version = "0.8.5", features = ["small_rng"] } diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..931cc08 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,168 @@ +use rand::{seq::SliceRandom, Rng}; + +pub trait Player { + fn select_card( + &self, + hand: &[Card], + stack: &[Card], + additional_information: &AdditionalInformation, + ) -> Card; +} + +#[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) -> 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: &[Box], starting_player: usize, rng: &mut impl Rng) -> 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::>(); + + 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::>(); + + 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 winner = (player.len() + + stack + .iter() + .enumerate() + .max_by_key(|(_, &c)| { + if c.color == stack[0].color { + c.value + } else { + 0 + } + }) + .unwrap() + .0 + - round_starting_player) + % player.len(); + + let cost = stack.iter().map(|c| c.cost()).sum::(); + + println!("## Player {winner} takes the stack {stack:?} with {cost} points."); + + points[winner] += cost; + + round_starting_player = winner; + } + + println!("## Game ended. Points: {points:?}"); + + points +} diff --git a/src/main.rs b/src/main.rs index 6eddbcc..4edf67b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,224 +1,133 @@ -use rand::seq::SliceRandom; +use game::Player; +use rand::{rngs::SmallRng, SeedableRng}; -#[derive(Clone, Copy, Hash, PartialEq, Eq)] -struct Card { - color: Color, - value: u8, -} +mod game; +mod player; -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()) - } -} +// #[derive(Debug, Clone)] +// enum Knowledge { +// Full(Vec), +// Partial([bool; 4]), +// } -impl Card { - fn new(color: Color, value: u8) -> Self { - Self { color, value } - } +// fn solve( +// current: usize, +// knowledge: &[Knowledge], +// unseen: &[Card], +// stack: &mut Vec, +// remaining: usize, +// ) -> Vec { +// let num_player = knowledge.len(); +// if remaining == 0 { +// vec![0.0; num_player] +// } else if stack.len() == num_player { +// let first_card = stack.first().unwrap(); - fn cost(&self) -> u8 { - match self.color { - Color::Spades => { - if self.value == 6 { - 4 - } else { - 0 - } - } - Color::Hearts => 1, - _ => 0, - } - } +// let winning_card = stack +// .iter() +// .enumerate() +// .max_by_key(|g| { +// if g.1.color == first_card.color { +// g.1.value +// } else { +// 0 +// } +// }) +// .unwrap(); - 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!(), - } - } -} +// let winning_player = (current + winning_card.0) % num_player; -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -enum Color { - Clubs, - Spades, - Hearts, - Diamonds, -} +// let cost = stack.iter().map(|c| c.cost()).sum::(); -impl Color { - fn get_symbol(&self) -> char { - match self { - Color::Clubs => '♣', - Color::Spades => '♠', - Color::Hearts => '♥', - Color::Diamonds => '♦', - } - } -} +// let mut new_stack = Vec::new(); +// let mut s = solve( +// winning_player, +// knowledge, +// unseen, +// &mut new_stack, +// remaining - 1, +// ); +// s[winning_player] += cost as f64; +// s +// } else { +// match &knowledge[current] { +// Knowledge::Full(cards) => { +// let mut min: Option> = None; +// let mut own_knowledge = knowledge.to_vec(); +// if let Some(first_card) = stack.first().copied() { +// for i in 0..cards.len() { +// if cards[i].color == first_card.color { +// stack.push(cards[i]); -#[derive(Debug, Clone)] -enum Knowledge { - Full(Vec), - Partial([bool; 4]), -} +// own_knowledge[current] = Knowledge::Full( +// cards +// .iter() +// .enumerate() +// .filter(|(j, _)| j != &i) +// .map(|(_, &c)| c) +// .collect(), +// ); +// let m = solve( +// (current + 1) % num_player, +// &own_knowledge, +// unseen, +// stack, +// remaining, +// ); +// if let Some(min) = &mut min { +// if m[current] < min[current] { +// *min = m; +// } +// } else { +// min = Some(m); +// } +// stack.pop(); +// } +// } +// } +// if let Some(m) = min { +// m +// } else { +// let mut min = vec![f64::INFINITY; num_player]; +// for i in 0..cards.len() { +// stack.push(cards[i]); +// own_knowledge[current] = Knowledge::Full( +// cards +// .iter() +// .enumerate() +// .filter(|(j, _)| j != &i) +// .map(|(_, &c)| c) +// .collect(), +// ); -fn solve( - current: usize, - knowledge: &[Knowledge], - unseen: &[Card], - stack: &mut Vec, - remaining: usize, -) -> Vec { - let num_player = knowledge.len(); - if remaining == 0 { - vec![0.0; num_player] - } else if stack.len() == num_player { - let first_card = stack.first().unwrap(); +// let m = solve( +// (current + 1) % num_player, +// &own_knowledge, +// unseen, +// stack, +// remaining, +// ); +// if m[current] < min[current] { +// min = m; +// } +// stack.pop(); +// } - let winning_card = stack - .iter() - .enumerate() - .max_by_key(|g| { - if g.1.color == first_card.color { - g.1.value - } else { - 0 - } - }) - .unwrap(); - - let winning_player = (current + winning_card.0) % num_player; - - let cost = stack.iter().map(|c| c.cost()).sum::(); - - let mut new_stack = Vec::new(); - let mut s = solve( - winning_player, - knowledge, - unseen, - &mut new_stack, - remaining - 1, - ); - s[winning_player] += cost as f64; - s - } else { - match &knowledge[current] { - Knowledge::Full(cards) => { - let mut min: Option> = None; - let mut own_knowledge = knowledge.to_vec(); - if let Some(first_card) = stack.first().copied() { - for i in 0..cards.len() { - if cards[i].color == first_card.color { - stack.push(cards[i]); - - own_knowledge[current] = Knowledge::Full( - cards - .iter() - .enumerate() - .filter(|(j, _)| j != &i) - .map(|(_, &c)| c) - .collect(), - ); - let m = solve( - (current + 1) % num_player, - &own_knowledge, - unseen, - stack, - remaining, - ); - if let Some(min) = &mut min { - if m[current] < min[current] { - *min = m; - } - } else { - min = Some(m); - } - stack.pop(); - } - } - } - if let Some(m) = min { - m - } else { - let mut min = vec![f64::INFINITY; num_player]; - for i in 0..cards.len() { - stack.push(cards[i]); - own_knowledge[current] = Knowledge::Full( - cards - .iter() - .enumerate() - .filter(|(j, _)| j != &i) - .map(|(_, &c)| c) - .collect(), - ); - - let m = solve( - (current + 1) % num_player, - &own_knowledge, - unseen, - stack, - remaining, - ); - if m[current] < min[current] { - min = m; - } - stack.pop(); - } - - min - } - } - Knowledge::Partial(_) => todo!(), - } - } -} +// min +// } +// } +// Knowledge::Partial(_) => todo!(), +// } +// } +// } fn main() { - 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::>(); + let player: [Box; 2] = [ + Box::new(player::highest::Highest {}), + Box::new(player::highest::Highest {}), + ]; - let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_entropy(); - full_deck.shuffle(&mut rng); - - let cards = 4; - let player = 4; - - let mut knowledge = Vec::new(); - - for i in 0..player { - knowledge.push(Knowledge::Full( - full_deck[cards * i..cards * (i + 1)].to_vec(), - )); - } - - dbg!(&knowledge); - - let unseen = []; - - let mut stack = Vec::new(); - - let start = std::time::Instant::now(); - let r = solve(0, &knowledge, &unseen, &mut stack, cards); - - dbg!(start.elapsed()); - dbg!(r); + game::herzen(&player, 0, &mut rng); } diff --git a/src/player/mod.rs b/src/player/mod.rs new file mode 100644 index 0000000..a8201a6 --- /dev/null +++ b/src/player/mod.rs @@ -0,0 +1,60 @@ +pub mod cli { + + use crate::game::{playeble, Player}; + + pub struct Cli {} + + impl Player for Cli { + fn select_card( + &self, + hand: &[crate::game::Card], + stack: &[crate::game::Card], + additional_information: &crate::game::AdditionalInformation, + ) -> crate::game::Card { + let mut h = hand.to_vec(); + + h.sort(); + println!("hand: {:?}", h); + + h.retain(|&c| playeble(c, hand, stack.first().map(|d| d.color))); + + println!("playeble: {:?}", h); + println!("stack: {:?}", stack); + println!("additional information: {:?}", additional_information); + + let mut buffer = String::new(); + + let _ = std::io::stdin().read_line(&mut buffer); + + let index = buffer.trim().parse::().unwrap(); + + h[index] + } + } +} + +pub mod highest { + use std::cmp::Reverse; + + use crate::game::{playeble, Player}; + + pub struct Highest {} + + impl Player for Highest { + fn select_card( + &self, + hand: &[crate::game::Card], + stack: &[crate::game::Card], + additional_information: &crate::game::AdditionalInformation, + ) -> crate::game::Card { + let _ = additional_information; + let mut hand = hand.to_vec(); + + hand.sort_by_key(|c| Reverse(*c)); + *hand + .iter() + .find(|&&c| playeble(c, &hand, stack.first().map(|d| d.color))) + .unwrap() + } + } +}