Refactor the game

This commit is contained in:
hal8174 2025-07-10 14:14:12 +02:00
parent b289b18030
commit c2d97173f2
Signed by: hal8174
SSH key fingerprint: SHA256:NN98ZYwnrreQLSOV/g+amY7C3yL/mS1heD7bi5t6PPw
4 changed files with 347 additions and 210 deletions

View file

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
rand = "0.8.5" rand = { version = "0.8.5", features = ["small_rng"] }

168
src/game.rs Normal file
View file

@ -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<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: &[Box<dyn Player>], starting_player: usize, rng: &mut impl Rng) -> Vec<u8> {
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 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::<u8>();
println!("## Player {winner} takes the stack {stack:?} with {cost} points.");
points[winner] += cost;
round_starting_player = winner;
}
println!("## Game ended. Points: {points:?}");
points
}

View file

@ -1,224 +1,133 @@
use rand::seq::SliceRandom; use game::Player;
use rand::{rngs::SmallRng, SeedableRng};
#[derive(Clone, Copy, Hash, PartialEq, Eq)] mod game;
struct Card { mod player;
color: Color,
value: u8,
}
impl std::fmt::Debug for Card { // #[derive(Debug, Clone)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // enum Knowledge {
write!(f, "{}{}", self.color.get_symbol(), self.get_value_char()) // Full(Vec<Card>),
} // Partial([bool; 4]),
} // }
impl Card { // fn solve(
fn new(color: Color, value: u8) -> Self { // current: usize,
Self { color, value } // knowledge: &[Knowledge],
} // unseen: &[Card],
// stack: &mut Vec<Card>,
// remaining: usize,
// ) -> Vec<f64> {
// 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 { // let winning_card = stack
match self.color { // .iter()
Color::Spades => { // .enumerate()
if self.value == 6 { // .max_by_key(|g| {
4 // if g.1.color == first_card.color {
} else { // g.1.value
0 // } else {
} // 0
} // }
Color::Hearts => 1, // })
_ => 0, // .unwrap();
}
}
fn get_value_char(&self) -> &'static str { // let winning_player = (current + winning_card.0) % num_player;
match self.value {
1 => "7",
2 => "8",
3 => "9",
4 => "10",
5 => "J",
6 => "Q",
7 => "K",
8 => "A",
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)] // let cost = stack.iter().map(|c| c.cost()).sum::<u8>();
enum Color {
Clubs,
Spades,
Hearts,
Diamonds,
}
impl Color { // let mut new_stack = Vec::new();
fn get_symbol(&self) -> char { // let mut s = solve(
match self { // winning_player,
Color::Clubs => '♣', // knowledge,
Color::Spades => '♠', // unseen,
Color::Hearts => '♥', // &mut new_stack,
Color::Diamonds => '♦', // remaining - 1,
} // );
} // s[winning_player] += cost as f64;
} // s
// } else {
// match &knowledge[current] {
// Knowledge::Full(cards) => {
// let mut min: Option<Vec<f64>> = 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)] // own_knowledge[current] = Knowledge::Full(
enum Knowledge { // cards
Full(Vec<Card>), // .iter()
Partial([bool; 4]), // .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( // let m = solve(
current: usize, // (current + 1) % num_player,
knowledge: &[Knowledge], // &own_knowledge,
unseen: &[Card], // unseen,
stack: &mut Vec<Card>, // stack,
remaining: usize, // remaining,
) -> Vec<f64> { // );
let num_player = knowledge.len(); // if m[current] < min[current] {
if remaining == 0 { // min = m;
vec![0.0; num_player] // }
} else if stack.len() == num_player { // stack.pop();
let first_card = stack.first().unwrap(); // }
let winning_card = stack // min
.iter() // }
.enumerate() // }
.max_by_key(|g| { // Knowledge::Partial(_) => todo!(),
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::<u8>();
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<Vec<f64>> = 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!(),
}
}
}
fn main() { fn main() {
let mut full_deck = (1..=8) let player: [Box<dyn Player>; 2] = [
.flat_map(|u| { Box::new(player::highest::Highest {}),
[ Box::new(player::highest::Highest {}),
Card::new(Color::Clubs, u), ];
Card::new(Color::Spades, u),
Card::new(Color::Hearts, u),
Card::new(Color::Diamonds, u),
]
.into_iter()
})
.collect::<Vec<_>>();
let mut rng = rand::thread_rng(); let mut rng = SmallRng::from_entropy();
full_deck.shuffle(&mut rng); game::herzen(&player, 0, &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);
} }

60
src/player/mod.rs Normal file
View file

@ -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::<usize>().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()
}
}
}