initial commit

This commit is contained in:
2025-11-10 18:13:59 +01:00
commit 29b34fa3f0
12 changed files with 863 additions and 0 deletions

15
src/deck/card.rs Normal file
View File

@@ -0,0 +1,15 @@
use std::fmt;
use crate::deck::{Suit, Rank};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Card {
pub suit: Suit,
pub rank: Rank,
}
impl fmt::Display for Card {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.suit, self.rank)
}
}

56
src/deck/mod.rs Normal file
View File

@@ -0,0 +1,56 @@
mod rank;
pub use rank::Rank;
mod suit;
pub use suit::Suit;
mod card;
pub use card::Card;
use strum::IntoEnumIterator;
use rand::seq::SliceRandom;
use rand::rng;
pub struct Deck {
cards: Vec<Card>,
}
impl Default for Deck {
fn default() -> Self {
Self::new()
}
}
impl Deck {
pub fn new() -> Self {
let cards = Suit::iter()
.flat_map(|suit| Rank::iter().map(move |rank| Card { suit, rank }))
.collect();
Self { cards }
}
pub fn draw(&mut self) -> Option<Card> {
self.cards.pop()
}
pub fn shuffle(&mut self) {
self.cards.shuffle(&mut rng());
}
pub fn deal_4x8(&mut self) -> Option<[Vec<Card>; 4]> {
if self.cards.len() < 32 { return None; }
let mut hands = [Vec::with_capacity(8), Vec::with_capacity(8),
Vec::with_capacity(8), Vec::with_capacity(8)];
for _ in 0..8 {
for h in 0..4 {
hands[h].push(self.draw()?);
}
}
Some(hands)
}
pub fn iter(&self) -> impl Iterator<Item=&Card> {
self.cards.iter()
}
}

43
src/deck/rank.rs Normal file
View File

@@ -0,0 +1,43 @@
use std::fmt;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
pub enum Rank {
Ass,
Zehn,
Koenig,
Ober,
Unter,
Neun,
Acht,
Sieben,
}
impl Rank {
pub fn points(&self) -> u8 {
match self {
Rank::Ass => 11,
Rank::Zehn => 10,
Rank::Koenig => 4,
Rank::Ober => 3,
Rank::Unter => 3,
_ => 0
}
}
}
impl fmt::Display for Rank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Rank::Ass => "Ass",
Rank::Zehn => "Zehn",
Rank::Koenig => "König",
Rank::Ober => "Ober",
Rank::Unter => "Unter",
Rank::Neun => "Neun",
Rank::Acht => "Acht",
Rank::Sieben => "Sieben",
};
write!(f, "{}", name)
}
}

22
src/deck/suit.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::fmt;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
pub enum Suit {
Eichel,
Gras,
Herz,
Schell,
}
impl fmt::Display for Suit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Suit::Eichel => "Eichel",
Suit::Gras => "Gras",
Suit::Herz => "Herz",
Suit::Schell => "Schell",
};
write!(f, "{}", name)
}
}

125
src/gamemode/mod.rs Normal file
View File

@@ -0,0 +1,125 @@
use crate::deck::{Card, Suit, Rank};
pub enum Gamemode {
Sauspiel(Suit),
Solo(Suit),
Wenz(Option<Suit>),
Geier(Option<Suit>),
Bettel,
Ramsch
}
impl Gamemode {
pub fn winning_card<'a>(&self, cards: [&'a Card; 4]) -> &'a Card {
match self {
Gamemode::Sauspiel(_) | Gamemode::Ramsch | Gamemode::Bettel =>
winner_for_trump(Suit::Herz, cards),
Gamemode::Solo(solo_suit) => winner_for_trump(*solo_suit, cards),
Gamemode::Wenz(wenz_suit) => winner_for_wenz(Rank::Unter, *wenz_suit, cards),
Gamemode::Geier(geier_suit) => winner_for_wenz(Rank::Ober, *geier_suit, cards),
}
}
}
fn winner_for_wenz(rank: Rank, trump_suit: Option<Suit>, cards: [&Card; 4]) -> &Card {
let ranks = [rank];
if cards.iter().any(|&c| is_trump(c, &ranks, trump_suit)) {
let winner_idx = cards
.iter()
.enumerate()
.filter(|&(_, &c)| is_trump(c, &ranks, trump_suit))
.max_by_key(|&(_, &c)| trump_strength_wenz(c, rank, trump_suit))
.map(|(i, _)| i)
.unwrap_or(0);
cards[winner_idx]
} else {
let first_suit = cards[0].suit;
let winner_idx = cards
.iter()
.enumerate()
.filter(|&(_, &c)| c.suit == first_suit)
.max_by_key(|&(_, &c)| non_trump_strength(c.rank))
.map(|(i, _)| i)
.unwrap_or(0);
cards[winner_idx]
}
}
fn winner_for_trump(trump_suit: Suit, cards: [&Card; 4]) -> &Card {
let ranks = [Rank::Ober, Rank::Unter];
if cards.iter().any(|&c| is_trump(c, &ranks, Some(trump_suit))) {
// Highest trump wins
let winner_idx = cards
.iter()
.enumerate()
.filter(|&(_, &c)| is_trump(c, &ranks, Some(trump_suit)))
.max_by_key(|&(_, &c)| trump_strength(c, trump_suit))
.map(|(i, _)| i)
.unwrap_or(0);
cards[winner_idx]
} else {
// No trump: highest of the led suit wins
let first_suit = cards[0].suit;
let winner_idx = cards
.iter()
.enumerate()
.filter(|&(_, &c)| c.suit == first_suit)
.max_by_key(|&(_, &c)| non_trump_strength(c.rank))
.map(|(i, _)| i)
.unwrap_or(0);
cards[winner_idx]
}
}
fn is_trump(card: &Card, trump_ranks: &[Rank], trump_suit: Option<Suit>) -> bool {
trump_ranks.contains(&card.rank) || (trump_suit == Some(card.suit))
}
// Trump strength according to Schafkopf:
// Obers: Eichel > Gras > Herz > Schell
// Unters: Eichel > Gras > Herz > Schell
// Then trump suit cards: A > 10 > K > 9 > 8 > 7
fn trump_strength(card: &Card, trump_suit: Suit) -> u16 {
match card.rank {
Rank::Ober => 300 + ober_unter_suit_strength(card.suit),
Rank::Unter => 200 + ober_unter_suit_strength(card.suit),
_ if card.suit == trump_suit => 100 + non_trump_strength(card.rank) as u16,
_ => 0,
}
}
fn trump_strength_wenz(card: &Card, rank: Rank, trump_suit: Option<Suit>) -> u16 {
if card.rank == rank {
200 + ober_unter_suit_strength(card.suit)
} else if trump_suit == Some(card.suit) {
100 + non_trump_strength(card.rank) as u16
} else {
0
}
}
fn ober_unter_suit_strength(suit: Suit) -> u16 {
match suit {
Suit::Eichel => 4,
Suit::Gras => 3,
Suit::Herz => 2,
Suit::Schell => 1,
}
}
fn non_trump_strength(rank: Rank) -> u8 {
match rank {
Rank::Ass => 8,
Rank::Zehn => 7,
Rank::Koenig => 6,
Rank::Ober => 5,
Rank::Unter => 4,
Rank::Neun => 3,
Rank::Acht => 2,
Rank::Sieben => 1,
}
}
#[cfg(test)]
mod tests;

313
src/gamemode/tests.rs Normal file
View File

@@ -0,0 +1,313 @@
use super::*;
use crate::deck::{Card, Suit, Rank};
fn card(suit: Suit, rank: Rank) -> Card {
Card { suit, rank }
}
#[test]
fn winner_test_1() {
let c1 = card(Suit::Herz, Rank::Ober);
let c2 = card(Suit::Gras, Rank::Ober);
let c3 = card(Suit::Schell, Rank::Ass);
let c4 = card(Suit::Gras, Rank::Koenig);
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c1);
let winner = Gamemode::Wenz(Some(Suit::Herz)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c1);
let winner = Gamemode::Wenz(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c4);
let winner = Gamemode::Wenz(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// Extra: Solo and Herz-trump modes behave consistently with O/U > suit trumps
let winner = Gamemode::Solo(Suit::Gras).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
let winner = Gamemode::Bettel.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
let winner = Gamemode::Ramsch.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
}
#[test]
fn sauspiel_trump_hierarchy() {
// In Sauspiel, trump is always Herz; Obers > Unters > Herz-suit trumps
let c1 = card(Suit::Eichel, Rank::Neun); // led suit, non-trump
let c2 = card(Suit::Gras, Rank::Ober); // trump (Ober)
let c3 = card(Suit::Herz, Rank::Ass); // trump (trump suit)
let c4 = card(Suit::Schell, Rank::Unter); // trump (Unter)
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// More checks on the same trick:
// Wenz: only Unters are trump -> Unter wins
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c4);
// Geier: only Obers are trump -> Ober wins
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// Solo (any suit): O/U outrank suit trumps -> Ober wins
let winner = Gamemode::Solo(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// Herz-trump modes equivalent
let winner = Gamemode::Bettel.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
let winner = Gamemode::Ramsch.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
}
#[test]
fn sauspiel_ober_suit_precedence() {
// Among Obers: Eichel > Gras > Herz > Schell
let c1 = card(Suit::Gras, Rank::Koenig); // led
let c2 = card(Suit::Eichel, Rank::Ober); // highest Ober
let c3 = card(Suit::Herz, Rank::Ober); // lower Ober
let c4 = card(Suit::Schell, Rank::Unter); // trump but below any Ober
let winner = Gamemode::Sauspiel(Suit::Gras).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// More checks:
let winner = Gamemode::Solo(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]); // O/U trump
assert_eq!(winner, &c2);
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]); // Obers trump
assert_eq!(winner, &c2);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]); // only Unter trump
assert_eq!(winner, &c4);
let winner = Gamemode::Bettel.winning_card([&c1, &c2, &c3, &c4]); // Herz-trump
assert_eq!(winner, &c2);
}
#[test]
fn sauspiel_no_trump_led_suit_highest() {
// No Obers/Unters and no Herz cards: highest of led suit wins (A > 10 > K > 9 > 8 > 7)
let c1 = card(Suit::Eichel, Rank::Koenig); // led suit
let c2 = card(Suit::Gras, Rank::Ass);
let c3 = card(Suit::Eichel, Rank::Zehn); // higher than König
let c4 = card(Suit::Schell, Rank::Neun);
let winner = Gamemode::Sauspiel(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]); // no Unters
assert_eq!(winner, &c3);
let winner = Gamemode::Solo(Suit::Gras).winning_card([&c1, &c2, &c3, &c4]); // Gras suit trump
assert_eq!(winner, &c2);
let winner = Gamemode::Solo(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]); // Schell suit trump
assert_eq!(winner, &c4);
let winner = Gamemode::Solo(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // both Eichel trumps; A>10>K...
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Schell
assert_eq!(winner, &c4);
}
#[test]
fn solo_suit_trumps_only_internal_order() {
// In Solo, chosen suit is trump plus all Obers/Unters; with only suit trumps present, A > 10 > K > 9 > 8 > 7
let c1 = card(Suit::Schell, Rank::Zehn); // trump suit
let c2 = card(Suit::Gras, Rank::Koenig);
let c3 = card(Suit::Schell, Rank::Ass); // highest among suit trumps
let c4 = card(Suit::Eichel, Rank::Neun);
let winner = Gamemode::Solo(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // no O/U, Herz not present -> follow suit
assert_eq!(winner, &c3);
let winner = Gamemode::Solo(Suit::Gras).winning_card([&c1, &c2, &c3, &c4]); // only Gras becomes trump
assert_eq!(winner, &c2);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]); // no Unters -> follow suit
assert_eq!(winner, &c3);
let winner = Gamemode::Wenz(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Schell
assert_eq!(winner, &c3);
}
#[test]
fn wenz_unter_trumps_over_optional_suit_trump() {
// In Wenz with extra suit trump, Unters outrank any suit trumps
let c1 = card(Suit::Eichel, Rank::Ass); // led
let c2 = card(Suit::Gras, Rank::Koenig); // trump by suit (Gras) if chosen
let c3 = card(Suit::Schell, Rank::Unter); // trump by Unter (beats suit trumps)
let c4 = card(Suit::Gras, Rank::Ass); // trump by suit (Gras) if chosen
let winner = Gamemode::Wenz(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]); // only Unter trump
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]); // no Obers -> follow suit
assert_eq!(winner, &c1);
let winner = Gamemode::Geier(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Gras
assert_eq!(winner, &c4);
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // O/U trump -> Unter wins
assert_eq!(winner, &c3);
}
#[test]
fn wenz_unter_precedence_between_suits() {
// Unter precedence: Eichel > Gras > Herz > Schell
let c1 = card(Suit::Herz, Rank::Neun); // led
let c2 = card(Suit::Gras, Rank::Unter);
let c3 = card(Suit::Eichel, Rank::Unter); // highest Unter
let c4 = card(Suit::Schell, Rank::Unter);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Wenz(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]); // Unters still outrank suit trumps
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]); // no Obers -> follow suit
assert_eq!(winner, &c1);
let winner = Gamemode::Geier(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Gras
assert_eq!(winner, &c2);
let winner = Gamemode::Sauspiel(Suit::Schell).winning_card([&c1, &c2, &c3, &c4]); // O/U trump -> highest Unter by suit
assert_eq!(winner, &c3);
}
#[test]
fn wenz_no_trump_led_suit_highest() {
// No Unters and no optional suit trumps: highest of led suit wins
let c1 = card(Suit::Eichel, Rank::Koenig); // led suit
let c2 = card(Suit::Gras, Rank::Ass);
let c3 = card(Suit::Eichel, Rank::Zehn); // higher than König
let c4 = card(Suit::Schell, Rank::Neun);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Wenz(Some(Suit::Gras)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Gras
assert_eq!(winner, &c2);
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]); // no Obers -> follow suit
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Schell
assert_eq!(winner, &c4);
let winner = Gamemode::Solo(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // Eichel suit trump
assert_eq!(winner, &c3);
}
#[test]
fn geier_ober_trumps_over_optional_suit_trump() {
// In Geier with extra suit trump, Obers outrank any suit trumps
let c1 = card(Suit::Gras, Rank::Ass); // led
let c2 = card(Suit::Schell, Rank::Koenig); // trump by suit (optional)
let c3 = card(Suit::Eichel, Rank::Ober); // trump by Ober (beats suit trumps)
let c4 = card(Suit::Schell, Rank::Ass); // trump by suit (optional)
let winner = Gamemode::Geier(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Geier(None).winning_card([&c1, &c2, &c3, &c4]); // Obers trump
assert_eq!(winner, &c3);
let winner = Gamemode::Wenz(None).winning_card([&c1, &c2, &c3, &c4]); // no Unters -> follow suit
assert_eq!(winner, &c1);
let winner = Gamemode::Wenz(Some(Suit::Schell)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Schell
assert_eq!(winner, &c4);
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // O/U trump -> Ober wins
assert_eq!(winner, &c3);
}
#[test]
fn bettel_behaves_like_herz_trump() {
// Current implementation treats Bettel like Herz-trump
let c1 = card(Suit::Gras, Rank::Ass); // led
let c2 = card(Suit::Herz, Rank::Neun); // trump by Herz suit
let c3 = card(Suit::Schell, Rank::Ass);
let c4 = card(Suit::Eichel, Rank::Koenig);
let winner = Gamemode::Bettel.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c2);
// More checks:
let winner = Gamemode::Ramsch.winning_card([&c1, &c2, &c3, &c4]); // same as Bettel currently
assert_eq!(winner, &c2);
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // Herz trump
assert_eq!(winner, &c2);
let winner = Gamemode::Solo(Suit::Herz).winning_card([&c1, &c2, &c3, &c4]); // Herz trump in Solo
assert_eq!(winner, &c2);
let winner = Gamemode::Solo(Suit::Gras).winning_card([&c1, &c2, &c3, &c4]); // Gras trump; no O/U
assert_eq!(winner, &c1);
let winner = Gamemode::Wenz(Some(Suit::Herz)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Herz
assert_eq!(winner, &c2);
let winner = Gamemode::Geier(Some(Suit::Herz)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Herz
assert_eq!(winner, &c2);
}
#[test]
fn ramsch_behaves_like_herz_trump() {
// Current implementation treats Ramsch like Herz-trump
let c1 = card(Suit::Eichel, Rank::Ass); // led
let c2 = card(Suit::Schell, Rank::Koenig);
let c3 = card(Suit::Herz, Rank::Zehn); // trump by Herz suit
let c4 = card(Suit::Gras, Rank::Neun);
let winner = Gamemode::Ramsch.winning_card([&c1, &c2, &c3, &c4]);
assert_eq!(winner, &c3);
// More checks:
let winner = Gamemode::Bettel.winning_card([&c1, &c2, &c3, &c4]); // same as Ramsch currently
assert_eq!(winner, &c3);
let winner = Gamemode::Sauspiel(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // Herz trump
assert_eq!(winner, &c3);
let winner = Gamemode::Solo(Suit::Herz).winning_card([&c1, &c2, &c3, &c4]); // Herz trump in Solo
assert_eq!(winner, &c3);
let winner = Gamemode::Solo(Suit::Eichel).winning_card([&c1, &c2, &c3, &c4]); // Eichel trump; no O/U
assert_eq!(winner, &c1);
let winner = Gamemode::Wenz(Some(Suit::Herz)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Herz
assert_eq!(winner, &c3);
let winner = Gamemode::Geier(Some(Suit::Herz)).winning_card([&c1, &c2, &c3, &c4]); // suit trump Herz
assert_eq!(winner, &c3);
}

3
src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod deck;
pub mod gamemode;
pub mod player;

175
src/player/mod.rs Normal file
View File

@@ -0,0 +1,175 @@
use std::io::{self, Write};
use crate::deck::Card;
use std::fmt;
#[derive(Debug)]
pub enum PlayerError {
NoCards,
}
impl fmt::Display for PlayerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PlayerError::NoCards => write!(f, "no cards available to play"),
}
}
}
impl std::error::Error for PlayerError {}
pub struct PlayerBase {
pub id: u32,
pub name: String,
}
impl PlayerBase {
pub fn new(id: u32, name: impl Into<String>) -> Self {
Self { id, name: name.into() }
}
pub fn id(&self) -> u32 {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
pub fn play_card(&mut self, hand: &mut Vec<Card>) -> Result<Card, PlayerError> {
hand.pop().ok_or(PlayerError::NoCards)
}
}
pub trait PlayerBaseAccess {
fn base(&self) -> &PlayerBase;
fn base_mut(&mut self) -> &mut PlayerBase;
fn id(&self) -> u32 {
self.base().id()
}
fn name(&self) -> &str {
self.base().name()
}
fn set_name(&mut self, name: impl Into<String>) {
self.base_mut().set_name(name);
}
}
pub trait ExternalPlayer: PlayerBaseAccess {
fn play_card(&mut self, hand: &mut Vec<Card>) -> Result<Card, PlayerError> {
self.base_mut().play_card(hand)
}
}
pub trait InternalPlayer: PlayerBaseAccess {
fn play_card_from_hand(&mut self) -> Result<Card, PlayerError>;
fn receive_card(&mut self, card: Card);
fn set_hand(&mut self, hand: Vec<Card>);
fn hand(&self) -> &Vec<Card>;
}
pub struct HumanPlayer {
pub base: PlayerBase,
pub hand: Vec<Card>,
}
impl HumanPlayer {
pub fn new(id: u32, name: impl Into<String>) -> Self {
Self {
base: PlayerBase::new(id, name),
hand: Vec::with_capacity(8),
}
}
}
impl PlayerBaseAccess for HumanPlayer {
fn base(&self) -> &PlayerBase {
&self.base
}
fn base_mut(&mut self) -> &mut PlayerBase {
&mut self.base
}
}
impl InternalPlayer for HumanPlayer {
fn play_card_from_hand(&mut self) -> Result<Card, PlayerError> {
if self.hand.is_empty() {
return Err(PlayerError::NoCards);
}
println!("{}'s hand:", self.name());
for (i, c) in self.hand.iter().enumerate() {
println!(" {}: {}", i, c);
}
print!("Select card index to play: ");
let _ = io::stdout().flush();
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok() {
if let Ok(idx) = input.trim().parse::<usize>() {
if idx < self.hand.len() {
return Ok(self.hand.remove(idx));
}
}
}
// fallback: pop last
self.hand.pop().ok_or(PlayerError::NoCards)
}
fn receive_card(&mut self, card: Card) {
self.hand.push(card);
}
fn set_hand(&mut self, hand: Vec<Card>) {
self.hand = hand;
}
fn hand(&self) -> &Vec<Card> {
&self.hand
}
}
pub struct NpcPlayer {
pub base: PlayerBase,
pub hand: Vec<Card>,
}
impl NpcPlayer {
pub fn new(id: u32, name: impl Into<String>) -> Self {
Self { base: PlayerBase::new(id, name), hand: Vec::with_capacity(8) }
}
}
impl PlayerBaseAccess for NpcPlayer {
fn base(&self) -> &PlayerBase { &self.base }
fn base_mut(&mut self) -> &mut PlayerBase { &mut self.base }
}
impl InternalPlayer for NpcPlayer {
fn play_card_from_hand(&mut self) -> Result<Card, PlayerError> {
if self.hand.is_empty() {
Err(PlayerError::NoCards)
} else {
Ok(self.hand.remove(0))
}
}
fn receive_card(&mut self, card: Card) {
self.hand.push(card);
}
fn set_hand(&mut self, hand: Vec<Card>) {
self.hand = hand;
}
fn hand(&self) -> &Vec<Card> {
&self.hand
}
}