src/main.rs
author Peter Gervai <grin@grin.hu>
Tue, 31 Jan 2023 22:16:26 +0100
changeset 3 3d9dba5b16e0
parent 2 950660fddec0
child 4 a2f0cb2b5c13
permissions -rw-r--r--
v1.00 - single file

// #[allow(dead_code, unused)]
// vigyazz6! autplayer
//

use core::{fmt, panic};
use std::{collections::VecDeque, time::Instant, cmp::Reverse};
use rand::Rng;
use std::mem;

extern crate pretty_env_logger;
#[macro_use] extern crate log;

const GAME_ROUNDS: i32 = 1_000_000;

fn main() {
    // RUST_LOG=debug cargo run
    pretty_env_logger::init();
    info!("Program starting.");
    game();
    info!("End of run.");
}

/*** Card ****/
#[derive(Debug)]
struct Card {
    value: i8,
    points: i8,
}

impl Card {
    fn new(value: i8)->Self {

        let mut points = 0;
        if value % 10 == 5 {
            // ends with 5 = 2 point
            points = 2;
            // println!("*5 add 1, val={}, pt={}", value, points);
        }

        if value % 10 == 0 {
            // ends with 0 = 3 point
            points = 3;
            // println!("*0 add 2, val={}, pt={}", value, points);
        }

        if value % 10 == value / 10 {
            // same numbers = 5 points (55=7)
            points += 5;
            // println!("NN add 5, val={}, pt={}", value, points);
        }

        if points == 0 {
            points = 1;
        }

        Card {
            value,
            points,
        }
    }
}

impl fmt::Display for Card {
    fn fmt( &self, f: &mut fmt::Formatter ) -> fmt::Result {
        write!(f, "(Card {}, points {})", self.value, self.points)
    }
}

impl PartialEq for Card {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

impl Eq for Card {} 

impl PartialOrd for Card {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match self.value.partial_cmp(&other.value) {
            Some(core::cmp::Ordering::Equal) => {None}
            ord => return ord,
        }
    }
}

impl Ord for Card {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.value.cmp(&other.value)
    }
}

/*** Deck ****/
#[derive(Debug)]
struct Deck {
    content: VecDeque<Card>,
}

impl Deck {
    fn new_empty() -> Self {
        debug!("Empty deck generated");
        Deck {
            content: VecDeque::new(),
        }
    }

    fn new() -> Self {
        debug!("Full deck generated");
        let content = (1..=104).into_iter().map( |n| Card::new(n) ).collect();
        Deck {
            content,
        }
    }

    fn shuffle( &mut self ) {
        let mut rng = rand::thread_rng();
        
        trace!("Deck before shuffle: len {}, {:?}", self.content.len(), self);
        debug!("Deck shuffled");
        // shufflers:
        // * naive: swap cards n times
        // * kgb: half the deck, take 1..4 cards sequentially from each
        // * grin: take 1..6 from front and put at bottom

        // naive shuffle: exchange random cards
        for _i in 1..=500 {
            let c1 = rng.gen_range(0 .. self.content.len());
            let c2 = rng.gen_range(0 .. self.content.len());
            if c1 != c2 {
                self.content.swap(c1, c2);
            }
        }
        trace!("Deck after shuffle: len {}, {:?}", self.content.len(), self);
    }

    // get top card from deck
    fn pop( &mut self ) -> Option<Card> {
        self.content.pop_front() 
    }

    // put a card into the bottom of the deck
    fn push( &mut self, c: Card ) {
        self.content.push_back(c);
    }

    fn push_cards( &mut self, cards: VecDeque<Card> ) {
        trace!("Collecting back, deck len is {}, cards {}", self.content.len(), cards.len());
        cards.into_iter().for_each( |card| self.push(card) );
        trace!("Deck len is {}", self.content.len());
    }

    fn len( &self ) -> usize {
        self.content.len()
    }

    fn _get_nth( &mut self, n: usize ) -> Card {
        if let Some(c) = self.content.remove(n) {
            c
        } else {
            panic!("get_nth: index {} out of bounds ({})!", n, self.content.len());
        }
    }
}

/*** Player ****/
#[derive(Debug)]
struct Player {
    name: String,
    hand: Deck,
    pile: VecDeque<Card>,
    game_point: i32,
    total_point: i32,
    rows_busted: i32,
    wins: i32,
}

impl Player {
    fn new(name: String)->Self {
        debug!("Player {} created", name);
        Player {
            name,
            hand: Deck::new_empty(),
            pile: VecDeque::new(),
            game_point: 0,
            total_point: 0,
            rows_busted: 0,
            wins: 0,
        }
    }

    // get one card from th dealer
    fn get_card( &mut self, card: Card ) {
        trace!("Player {} got a card {:?}, cards before {}", self.name, &card, self.hand.len());
        self.hand.push(card);
    }

    // throw a card from hand to the table
    fn throw_card( &mut self )->Card {
        if let Some(c) = self.hand.pop() {
            trace!("Player {} throws a card {:?}", self.name, &c);
            c
        } else {
            panic!("throw_card: Player {} has no card in hand!", self.name);
        }
    }

    // get a busted row of cards
    fn give_pile( &mut self, cards: VecDeque<Card> ) {
        for c in cards.into_iter() {
            self.game_point += c.points as i32;
            self.pile.push_back(c);
        }
        self.rows_busted += 1;
        trace!("Player {} got busted, count {}", self.name, &self.rows_busted);
    }

    // ask the player their score
    fn _tell_points( self ) -> i32 {
        self.game_point
    }

    fn inc_wins( &mut self ) {
        self.wins += 1;
    }

    // give back cards from the pile
    fn get_pile( &mut self ) -> VecDeque<Card> {
        trace!("Player {} gives back their pile", self.name);
        mem::take( &mut self.pile )
        // same effect:
        // self.pile.drain(..).collect()

        // very cumbersome manual fiddling (also reverted...)
/*         let mut throw: Vec<Card> = Vec::new();
        for _i in 0 .. self.pile.len() {
            throw.push( self.pile.pop().unwrap() );
        }
        throw
 */    
    }

    // I can do this just because I *throw away* c!
    // doesn't work if I want to use it.
/*     fn _gimme_pile(self)->Self {
        for c in &self.pile {
            println!("Throw {} ", c);
        }
        self
    }
 */
    fn close_round( &mut self ) {
        if self.hand.len() > 0 {
            panic!("Closing round when {} has {} cards in hand", self.name, self.hand.len());
        }

        if self.pile.len() > 0 {
            panic!("Closing round when {} stil have pile with {} cards", self.name, self.pile.len());
        }

        trace!("Player {} closing round; points={} total so far {}", self.name, self.game_point, self.total_point);
        self.total_point += self.game_point;
        self.game_point = 0;
    }

    // card too small: pick a row to collect from the rows
    fn pick_row_for_small_card( &self, rows: &Vec<Row>, playercard: &Card ) -> usize {
        trace!("Player {} picking a row for small card, card {:?}, rows {:?}", self.name, playercard, rows);

        // contains the summary point for each row
        let mut row_points = Vec::with_capacity(5);
        // the smallest row score
        let mut smallest = 999;
        // how many rows have the same smallest score
        let mut same_point = 0;
        // the first smallest row_id
        let mut smallest_rowid = 255;

        for rowid in 0 .. rows.len() {
            // DEBUG
            // println!("pick_row_for_small_card: rowlen {}, rowid {}", rows.len(), rowid);
            row_points.push( rows[rowid].sum() );

            if row_points[rowid] < smallest {
                // we have a new smallest row
                smallest = row_points[rowid];
                same_point = 0;
                smallest_rowid = rowid;
            
            } else if row_points[rowid] == smallest {
                // we have another row with same point as smallest
                same_point += 1;
            }
        }

        if same_point < 1 {
            // we have one smallest row
            smallest_rowid.try_into().unwrap()      // it's tiny, will fit into u8
        
        } else {
            // bored, we pick the first now anyway
            smallest_rowid.try_into().unwrap()
        }
        
    }
}

/*** Row ****/
// a row of cards on the table (max 5)
#[derive(Debug)]
struct Row {
    cards: VecDeque<Card>,
}

impl Row {
    const MAX_LEN: usize = 5;

    fn new() -> Self {
        Row {
            cards: VecDeque::with_capacity(5),
        }
    }

    fn push_or_collect( &mut self, card: Card ) -> Option<VecDeque<Card>> {
        trace!("Called push_or_collect on row {:?}", &self);
        if self.cards.len() < Self::MAX_LEN {
            trace!("Less than {} cards, putting at the end", Self::MAX_LEN);
            self.cards.push_back(card);
            None

        } else {
            trace!("Row is full, len {}, maxlen {}", self.cards.len(), Self::MAX_LEN);
            // row overflow
            let row_cards = mem::take( &mut self.cards );
            self.cards.push_back(card);
            if self.cards.len() != 1 {
                panic!("New row must have one card, not {}", self.cards.len());
            }
            Some(row_cards)
        }
    }

    fn take_row( &mut self ) -> VecDeque<Card> {
        // take cards and empty the row
        mem::take( &mut self.cards )
    }

    fn last_card_value(&self) -> i8 {
        // println!("last_card_value: cards {:?}, len {}", self.cards, self.cards.len());
        self.cards.get( self.cards.len()-1 ).unwrap().value
    }

    // sum of row card points
    fn sum(&self) -> i32 {
        let mut sum: i32 = 0;
        self.cards.iter().for_each(|card| {
            sum += card.points as i32;
        });
        sum
    }
}

/*** PlayerCard ****/
#[derive(Debug)]
struct PlayerCard {
    player_id: i32,
    card: Card,
}

impl PlayerCard {
    fn _get_player(&self) -> i32 {
        self.player_id
    }
}

impl PartialEq for PlayerCard {
    fn eq(&self, other: &Self) -> bool {
        self.card == other.card
    }
}

impl PartialOrd for PlayerCard {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match self.card.partial_cmp(&other.card) {
            Some(core::cmp::Ordering::Equal) => {None}
            ord => return ord,
        }
    }
}

/*** Table ****/
#[derive(Debug)]
struct Table {
    rows: Vec<Row>,
    player_cards: VecDeque<PlayerCard>, // owned by a player
}

impl Table {
    fn new(row_cards: VecDeque<Card>) -> Self {
        let mut rows = Vec::with_capacity(5);
        for card in row_cards {
            // create a new row then put a card into it
            let mut row = Row::new();
            if let Some(_c) = row.push_or_collect(card) {
                panic!("Freshly created row overflowed");
            }
            rows.push( row );
        }

        Table {
            rows,
            player_cards: VecDeque::new(),
        }
    }

    fn lay_player_card( &mut self, card: Card, player_id: i32 ) {
        self.player_cards.push_back( PlayerCard { player_id, card } );
    }

    fn sort_cards( &mut self ) {
        self.player_cards.make_contiguous().sort_by( |a,b| b.card.cmp(&a.card) );
    }

    fn has_player_cards( &self ) -> bool {
        self.player_cards.len() > 0
    }

    fn get_smallest_player_card( &mut self ) -> PlayerCard {
        // FIXME: check!
        self.player_cards.pop_back().expect("out of player cards on table")
    }

    fn get_closest_row( &self, pcard: &PlayerCard ) -> Option<usize> {
        // get the row id with last card closest smaller to players'
        let row_heads = self.get_row_heads();
        let mut closest_val = None;
        let mut diff = 127;
        for i in 0..row_heads.len() {
            if row_heads[i] < pcard.card.value && pcard.card.value - row_heads[i] < diff {
                closest_val = Some(i);
                diff = pcard.card.value - row_heads[i];
                // println!("DEBUG: pcard {}, row {}, head {}, diff {}, closest {:?}", pcard.card.value, i, row_heads[i], diff, closest_val);
            }
        }

        closest_val
    }

    fn put_card_into_row( &mut self, pcard: PlayerCard, row_id: usize ) -> Option<VecDeque<Card>> {
        self.rows[row_id as usize].push_or_collect(pcard.card)
    }

    fn get_row_heads( &self ) -> Vec<i8> {
        let mut heads: Vec<i8> = Vec::new();
        for i in 0..self.rows.len() {
            heads.push( self.rows[i].last_card_value() );
        }
        heads
    }

    // take a whole row and hand it over
    fn take_row( &mut self, row_id: usize ) -> VecDeque<Card> {
        self.rows[row_id].take_row()
    }

    // collect remaining cards in the rows at the end of round
    fn collect_rows( &mut self ) -> VecDeque<Card> {
        let mut cards = VecDeque::new();
        for row in 0..self.rows.len() {
            self.rows[row]
                .take_row()
                .into_iter()
                .for_each(|card| cards.push_back(card));
        }
        cards
    }
}


/*** Game ****/

struct GameStat {
    game_count: i64,
    shuffle_count: i64,
    start_time: Instant, // use start_time.elapsed().as_nanos() or .as_secs()
}

fn game() {
    let mut stats = GameStat { game_count:0, shuffle_count: 0, start_time: Instant::now() };

    let mut deck = Deck::new();

    let player_names = vec![ "grin", "moni", "icbalint", "orsi", "topi", "kgb", "zsu", "csilla" ];
    let mut players: Vec<Player> = player_names.iter().map( |n| Player::new(n.to_string()) ).collect();

    let player_count = players.len(); // pc - 1

    for cnt_game in 1..=GAME_ROUNDS {
        debug!("Game round {} starts", cnt_game);
        deck.shuffle();
        stats.shuffle_count += 1;
        stats.game_count += 1;

        // dealing
        debug!("Dealing.");
        for _i in 1..=10 {
            for player in 0 .. player_count {
                players[player].get_card( deck.pop().expect("Deck is empty while dealing to players") );
            }
        }

        // we need 5 crds from deck
        debug!("Building the rows.");
        let mut cards = VecDeque::new();
        (1..=5).for_each(|_| {
            cards.push_back( deck.pop().expect("deck empty before starting the game") );
        });
        // println!("We push 5 cards to rows: {:?}\n", cards);
        let mut table = Table::new(cards);

        // DEBUG
/*         println!("Table: {:?}\n", table);
        println!("PLayers: {:?}\n", players);
        println!("Deck: {:?}\n", deck);
 */        
        debug!("We have the table ready: {:?}", table);

        // playing
        debug!("Players start taking turns");
        for turn in 1..=10 {
            debug!("Turn {}!", turn);
            trace!("The table: {:?}", table);

            // everyone puts a card face down
            for player in 0 .. player_count {
                let player_id: i32 = player.try_into().unwrap();
                // get a card from the player
                let topcard = players[player].throw_card();
                // put it on the table ("turned face down")
                table.lay_player_card( topcard, player_id );
            }

            // process cards
            debug!("The Table process the throws.");
            table.sort_cards();

            while table.has_player_cards() {
                let smallest = table.get_smallest_player_card();
                trace!("Take smallest card {:?}", &smallest);

                let closest_row = table.get_closest_row(&smallest);
                trace!("Choose closest row: {:?}", closest_row);

                match closest_row {
                    Some(rowid) => {
                        debug!("Putting down card into row {}", rowid);
                        let player_id: usize = smallest.player_id.try_into().unwrap();
                        let overflow = table.put_card_into_row(smallest, rowid);
                        if let Some(cards) = overflow {
                            // row is full, got pile
                            debug!("Row is busted, {} collects", players[player_id].name);
                            // player gets pile, card gets into row head
                            players[ player_id ].give_pile( cards );
                        }
                    },
                    None => {
                        // card too small, need to pick row!
                        let player_id: usize = smallest.player_id.try_into().unwrap();
                        debug!("Too small from {}, picking row", players[player_id].name);
                        // pick any row to take
                        let rowid = players[ player_id ].pick_row_for_small_card(&table.rows, &smallest.card);
                        trace!("Picked row {}", rowid);
                        // take the row cards
                        let cards = table.take_row(rowid);
                        trace!("Took cards: {:?}", cards);
                        players[ player_id ].give_pile( cards );
                        // put new card in the row
                        let overflow = table.put_card_into_row(smallest, rowid);
                        if let Some(_) = overflow {
                            panic!("Player took whole row and it's already full");
                        }
                    }
                }
            }
        }

        // end of round
        info!("Round finished, len is {} ??sec", stats.start_time.elapsed().as_micros());

        debug!("End of round, counting and collecting back piles");
        let mut winners: Vec<usize> = Vec::new();
        let mut winscore: i32 = i32::MAX-1;

        for i in 0..player_count {
            info!("Player {} has {} points", players[i].name, players[i].game_point);

            if players[i].game_point < winscore  {
                trace!("New winner {} with score {}", players[i].name, players[i].game_point);
                winners.clear();
                winners.push(i);
                winscore = players[i].game_point;
            
            } else if players[i].game_point == winscore {
                trace!("New co-winner {} with score {}", players[i].name, players[i].game_point);
                winners.push(i);
            }
            trace!("The list of winners is {:?}", winners);

            // get pile from player
            let cards = players[i].get_pile();
            // and give it back to the deck
            deck.push_cards(cards);
            // close player round and update stats
            players[i].close_round();
        }

        // collect cards from table
        deck.push_cards( table.collect_rows() );
        trace!("Shall have full deck now, len {}", deck.content.len());

        let mut finals = Vec::new();
        for i in winners.iter() {
            players[*i].inc_wins();
        }

        for i in winners {
            finals.push( &players[i].name );
        }
        info!("The winner(s): {:?}", &finals);

/*         players.iter().for_each(|player| {
            println!("Player {} has {} points", player.name, player.game_point);
            let cards = player.get_pile();
            deck.push_cards(cards);
            player.close_round();
        });
 */
    }

    let elapsed_micro: f64 = stats.start_time.elapsed().as_micros() as f64;
    let game_rounds: f64 = GAME_ROUNDS.into();

    let _res: f64 = stats.start_time.elapsed().as_micros() as f64 / <i32 as Into<f64>>::into(GAME_ROUNDS);

    println!("Totals (game time {} ??s, or {} s; {} ??s/game), {} games played ({} shuffles):", 
        stats.start_time.elapsed().as_micros(), 
        stats.start_time.elapsed().as_secs(),
        elapsed_micro / game_rounds, 
        stats.game_count,
        stats.shuffle_count,
    );

    // players.sort_by( |a, b| a.total_point.partial_cmp(&b.total_point).unwrap() );    // ASC points
    // players.sort_by( |a, b| b.wins.partial_cmp(&a.wins).unwrap() );                  // DESC wins
    players.sort_by_cached_key( |x| Reverse(x.wins) );                                  // DESC wins (caching is just for the show)

    for i in 0..players.len() {
        let p = &players[i];
        println!("Player {} has wins {}, score {} (busted {} times)", p.name, p.wins, p.total_point, p.rows_busted);
    }
}

#[cfg(test)]
mod tests {
    // use core::panic;
    use std::collections::VecDeque;

    use rand::Rng;

    use crate::{Card, Player, Row, Table, PlayerCard};

    #[test]
    fn card_values() {
        let card_values = vec![1,2,5,10,33,55,77];
        let card_points = vec![1,1,2,3, 5, 7, 5];
        for i in 1 .. card_values.len() {
            let c = Card::new( card_values[i] );
            let p = c.points;
            assert!(p == card_points[i], "card={} card points={} i={} expected point={}", card_values[i], p, i, card_points[i]);
        }
    }

    #[test]
    fn player_take_pile() {
        // create a player
        let mut p = Player::new("bob".to_string());
        // create a pile
        let mut pile = VecDeque::new();
        let mut refpile = VecDeque::new();
        for i in 5..10 {
            let c = Card::new(i);
            pile.push_back(c);
            let c = Card::new(i);
            refpile.push_back(c);
        }
        // give the pile to player
        p.give_pile(pile);
        assert!( p.rows_busted == 1 );

        // get back the pile from player
        // p = p.gimme_pile();
        let pile = p.get_pile();
        
        // the pile we got shall be same as the pile we gave
        // this check is O(n^2), doesn't matter for less than 100 items
        assert_eq!( pile, refpile );
        assert!( pile.iter().all( |item| refpile.contains(item)) );

        let mut pile = VecDeque::new();
        for i in 4..=9 {
            let c = Card::new(i);
            pile.push_back(c);
        }
        p.give_pile(pile);
        assert!( p.rows_busted == 2 );
    }

    #[test]
    fn row_push() {
        let mut row = Row::new();
        let mut refcard = VecDeque::new();   // reference vec to check
        for i in 1..=7 {
            let cval = i+5;
            let card = Card::new(cval);
            // push a card into the row
            if let Some(cards) = row.push_or_collect(card) {
                // got the overflow
                println!("Got overflow row at {}!", i);
                assert!( i == 6, "Overflow at wrong position: {} != 6", i );
                // we need to get the proper vec
                assert!( cards.iter().all( |item| refcard.contains(item) ), "Got cards {:?}", cards ); 
            } else {
                println!("push success {}", i);
            }
            // remember the correct vec for checking
            let card = Card::new(cval);
            refcard.push_back(card);

            // check card value
            assert!( row.last_card_value() == cval, "Last card value mismatch: got {} vs expected {}", row.last_card_value(), cval );
        }
        assert!( row.cards.len() == 2, "Row contains wrong amount of cards: {}", row.cards.len() );
    }

    #[test]
    fn sort_cards() {
        let mut cards: VecDeque<Card> = VecDeque::new();
        let mut rng = rand::thread_rng();
        for _ in 1..50 {
            let n = rng.gen_range(1..104);
            cards.push_back( Card::new(n) );
        }
        cards.make_contiguous().sort();

        for i in 1..cards.len() {
            assert!( cards[i-1].value <= cards[i].value, "Bad ordering: {} > {}", cards[i-1].value,cards[i].value );
        }
    }

    #[test]
    fn check_closest_row() {
        let table = generate_table();
        let pcard = PlayerCard{ player_id: 42, card: Card::new(42) };
        let closest = table.get_closest_row(&pcard);
        assert_eq!( closest, Some(3) ); // index from 0
    }

    #[test]
    fn check_smallest_player_card() {
        let mut table = generate_table();
        
        let mut player_id = 1;
        vec![103, 8, 71, 93, 6].into_iter().for_each(|c| {
            table.lay_player_card( Card::new(c), player_id);
            player_id += 1;
        });

        let smallest = table.get_smallest_player_card();
        assert_eq!( smallest, PlayerCard{ player_id: 5, card: Card::new(6) } );
    }

    fn generate_table() -> Table  {
        let mut row_cards = VecDeque::new();
        vec![5,7,10,33,70].into_iter().for_each(|c| row_cards.push_back( Card::new(c) ));
        let table = Table::new(row_cards);
        table
    }
}

/*

- 1-104 lap, 
    - *5 - 2
    - *0 - 3
    - NN - 5
    - 55 - 7
- deck
- jatekosok; kezben tartott lapok; elvitt lapok; pontszamok; counter: elvitt sorok, okrok, total pont
-- keveres (keveresek szama)
-- osztas: mindenki 10 lap
-- start sorok: 5 kartya
- jatek (jatekok szama)
-- mindenki a felso lapot kiteszi
-- szamsorrendben felkerulnek, aki viszi, viszi
--- ha kisebb, akkor dontes
---- ha van legkevesebb, viszi
---- ha tobb min van, random valaszt
- osszesites
-- okrok, elvitt sorok
-- keveresek szama, jatekok szama, eltelt ido
- deck osszegyujtes


*/