diff -r a2f0cb2b5c13 -r 0dd7f2c9fd81 src/main.rs --- a/src/main.rs Tue Jan 31 23:25:50 2023 +0100 +++ b/src/main.rs Sat Feb 04 22:46:13 2023 +0100 @@ -1,65 +1,95 @@ // #[allow(dead_code, unused)] -// vigyazz6! autplayer + +// vigyazz6! autoplayer +// +// This is a program automagically playing a card game called "Vigyazz6!". +// +// The rules are rather simple, but I'mrather lazy to reprint it in detail, +// so it'll be brief. +// +// The code is based on a deck of 104 numbered cards, all of them having +// a face `value` and punishment `points` (the less the better). +// There are 2 to 10 players (but above 8 the code shall be modified to +// deal less cards), and the game has 5 rows of maximum 5 cards on the +// table. +// All players throw cards at once, and they are put in the row with +// closest less value; if there is none, they have to pick and take +// any row. When a row is full (5) they have to take the row and their +// card starts a new row head. +// When players are out of cards the least points collected wins. +// +// Important: I do not use `unsafe` code, this is all tightly controlled. // +/// We use panic in several places where the code shall not go. use core::panic; +/// `VecDeque` is a Vec which can be pushed/popped at both ends +/// `time::Instant` is used to measure running time. +/// `cmp::Reverse` used for reverse sorting. use std::{collections::VecDeque, time::Instant, cmp::Reverse}; -use crate::{deck::Deck, player::Player, table::Table}; -// use rand::Rng; -// use std::mem; +/// We use this `pretty_env_logger` as a logging framework. +extern crate pretty_env_logger; +#[macro_use] extern crate log; +// I import structures from my module files to use here. +use crate::{deck::Deck, player::{Player, PlayerCard}, table::Table}; + +// Here we read the files and "get to know" what's in them. This isn't including, only scanning. mod deck; mod table; mod player; mod card; mod row; -extern crate pretty_env_logger; -#[macro_use] extern crate log; - +// We play this many game rounds. You want to keep this under 3 for any debug level above "warnings". const GAME_ROUNDS: i32 = 100_000; fn main() { + // Env variable makes the logger to log more or less: // RUST_LOG=debug cargo run pretty_env_logger::init(); - info!("Program starting."); + info!("Program starting."); // info level logging game(); info!("End of run."); } -// Card -// Deck -// Player -// Row - - /*** Game ****/ +/// `GameStat` structure collects global statistics about the run. struct GameStat { game_count: i64, shuffle_count: i64, start_time: Instant, // use start_time.elapsed().as_nanos() or .as_secs() } +/// Play all the game rounds. fn game() { + // Whole game statistics stored here let mut stats = GameStat { game_count:0, shuffle_count: 0, start_time: Instant::now() }; + // Create the game deck with all the card (ordered). Open box. Remove shrink wrap. :-) let mut deck = Deck::new(); - let player_names = vec![ "grin", "moni", "icbalint", "orsi", "topi", "kgb", "zsu", "csilla" ]; + // Create the players (up to 8 w/o changing the hand cards) + let player_names = vec![ "peter", "moni", "icbalint", "orsi", "topi", "kgb", "zsu", "csilla" ]; + // Iterate on player names, create Player struct from them and collect them into a Vec'tor. + // (On the borrow level we don't consume the player_names vector [using `iter()` instead of `into_iter()`].) let mut players: Vec = player_names.iter().map( |n| Player::new(n.to_string()) ).collect(); - let player_count = players.len(); // pc - 1 + // We use this often, so take it now. + let player_count = players.len(); // = - 1 + // Start playing - GAME_ROUNDS rounds. for cnt_game in 1..=GAME_ROUNDS { - debug!("Game round {} starts", cnt_game); + debug!("Game round {} starts", cnt_game); // debug level logging + // Shuffle the deck (using various methods) deck.shuffle(); stats.shuffle_count += 1; stats.game_count += 1; - // dealing + // dealing 10 cards for each player debug!("Dealing."); for _i in 1..=10 { for player in 0 .. player_count { @@ -67,75 +97,88 @@ } } - // we need 5 crds from deck + // we need 5 cards from deck to build the rows debug!("Building the rows."); let mut cards = VecDeque::new(); + // This is another way to write "for _ in (1..=5) { ... }" (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); + trace!("We pushed 5 cards to rows: {:?}\n", cards); + + // Create the Table (contains the Deck, the Rows (with 5 starting `cards`) and occasional cards thrown by Players) let mut table = Table::new(cards); - // DEBUG -/* println!("Table: {:?}\n", table); - println!("PLayers: {:?}\n", players); - println!("Deck: {:?}\n", deck); +/* debug!("Table: {:?}\n", table); + debug!("Players: {:?}\n", players); + debug!("Deck: {:?}\n", deck); */ debug!("We have the table ready: {:?}", table); - // playing + // playing one game, players taking turns debug!("Players start taking turns"); + // Since players have 10 cards dealt we have exactly 10 turns. for turn in 1..=10 { debug!("Turn {}!", turn); trace!("The table: {:?}", table); - // everyone puts a card face down + // Everyone puts a card "face down" (Rust promised not to peek). + // If I used the `players.for_each()` form I would hit the borrow checker: + // `players` would be repeatedly borrowed mutable and program wouldn't compile. 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(); + let player_id: i32 = player.try_into().unwrap(); // convert `usize` array index into `i32` [that was an unnecessary hassle: in fact I should rewrite this using usize everywhere] + let topcard = players[player].throw_card(); // If Player [module] was smart this would be a chosen card, but it would need player + // not just brains but possibility to peek at the rows. This wasn't in the plan, so + // we just take the top card in hand. // put it on the table ("turned face down") - table.lay_player_card( topcard, player_id ); + table.lay_player_card( topcard, player_id ); // We actually move the card variable with its ownership everywhere, so no &refs. } - // process cards + // Process cards on the Table. debug!("The Table process the throws."); + // Sort the cards by `value` the players have thrown. table.sort_cards(); + // Pick them one by one, from smallest to larger ones while table.has_player_cards() { - let smallest = table.get_smallest_player_card(); + let smallest: PlayerCard = table.get_smallest_player_card(); // I just wrote the PlayerCard type to show; Rust is smart enough to know. trace!("Take smallest card {:?}", &smallest); - let closest_row = table.get_closest_row(&smallest); + // Find the row with the closest smallest card, or None if smaller than any row head. + let closest_row = table.get_closest_row(&smallest); // That is an Option. trace!("Choose closest row: {:?}", closest_row); match closest_row { Some(rowid) => { + // We have to put it at the end of `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); + let player_id: usize = smallest.player_id.try_into().unwrap(); // By "just" indexing `players` (by `player_id`) I have avoided borrowing it and get into trouble later. + // Try to put the card to the end of the row, or collect full row first + let overflow = table.put_card_into_row(smallest, rowid); // And this is Option>. if let Some(cards) = overflow { - // row is full, got pile - debug!("Row is busted, {} collects", players[player_id].get_name()); - // player gets pile, card gets into row head + // Row is full, we got pile ("bust") + debug!("Row is busted, {} collects", players[player_id].get_name()); + // Player gets pile, thrown card was put 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(); + // Card too small, need to pick any row to take! + let player_id: usize = smallest.player_id.try_into().unwrap(); // We _directly_ access `PlayerCard.player_id` structure member, because borrow checker wasn't helping my mental hygiene. debug!("Too small from {}, picking row", players[player_id].get_name()); - // pick any row to take - // let rowid = players[ player_id ].pick_row_for_small_card(&table.rows, &smallest.card); + // Pick any row to take let rowid = players[ player_id ].pick_row_for_small_card(table.peek_rows(), &smallest.card); trace!("Picked row {}", rowid); - // take the row cards + // take the row cards ("bust") let cards = table.take_row(rowid); trace!("Took cards: {:?}", cards); + // and give them to the player who owns the card 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 { + // If this fires then I have fucked something up. :-) panic!("Player took whole row and it's already full"); } } @@ -147,8 +190,9 @@ info!("Round finished, len is {} µsec", stats.start_time.elapsed().as_micros()); debug!("End of round, counting and collecting back piles"); + // We collect the winner(s) and their score here let mut winners: Vec = Vec::new(); - let mut winscore: i32 = i32::MAX-1; + let mut winscore: i32 = i32::MAX-1; // Getting facy here about "larger than any valid value". for i in 0..player_count { info!("Player {} has {} points", players[i].get_name(), players[i].get_points()); @@ -165,41 +209,36 @@ } trace!("The list of winners is {:?}", winners); - // get pile from player + // get pile from Player let cards = players[i].get_pile(); - // and give it back to the deck + // and give it back to the Deck deck.push_cards(cards); - // close player round and update stats + // close Player round and update stats players[i].close_round(); } - // collect cards from table + // collect remaining Cards from Table deck.push_cards( table.collect_rows() ); trace!("Shall have full deck now, len {}", deck.len()); - let mut finals = Vec::new(); + // This is a Vec of `player_id`s, i is a &ref for i in winners.iter() { - players[*i].inc_wins(); + players[*i].inc_wins(); // Increment win count of &Vec player_id } + // Collect the names of the winners. + let mut finals = Vec::new(); for i in winners { finals.push( players[i].get_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(); - }); - */ } - // do the type conversions _very_ visibly + // Do the type conversions _very_ visibly. `as_micros()` results `u128` let elapsed_micro: f64 = stats.start_time.elapsed().as_micros() as f64; + // and `GAME_ROUNDS` is `i32`. We'll use `elapsed_micro/game_rounds` later. let game_rounds: f64 = GAME_ROUNDS.into(); - // which is same as the slightly uglier + // …which is same as the slightly uglier: let _res: f64 = stats.start_time.elapsed().as_micros() as f64 / >::into(GAME_ROUNDS); println!("Totals (game time {} µs, or {} s; {} µs/game), {} games played ({} shuffles):", @@ -212,10 +251,11 @@ // 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.get_wins()) ); // DESC wins (caching is just for the show) + players.sort_by_cached_key( |x| Reverse(x.get_wins()) ); // DESC wins (caching is just for the show); using std::cmp::Reverse + // Print out totals for i in 0..players.len() { - let p = &players[i]; + let p = &players[i]; // the rest looks simpler by using this. (Seems to confuse rust-analyzer in codium though.) println!("Player {} has wins {}, score {} (busted {} times)", p.get_name(), p.get_wins(),