--- 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> = 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(); // = <number of players> - 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<usize>.
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<VecDeque<Card>>.
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<usize> = 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<usize> 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 / <i32 as Into<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(),