Add some test comment, remove original draft comments.
// #[allow(dead_code, unused)]// 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. usecore::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.usestd::{collections::VecDeque,time::Instant,cmp::Reverse};/// We use this `pretty_env_logger` as a logging framework.externcratepretty_env_logger;#[macro_use]externcratelog;// I import structures from my module files to use here.usecrate::{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.moddeck;modtable;modplayer;modcard;modrow;// We play this many game rounds. You want to keep this under 3 for any debug level above "warnings".constGAME_ROUNDS: i32=100_000;fnmain(){// Env variable makes the logger to log more or less:// RUST_LOG=debug cargo runpretty_env_logger::init();info!("Program starting.");// info level logginggame();info!("End of run.");}/*** Game ****//// `GameStat` structure collects global statistics about the run.structGameStat{game_count: i64,shuffle_count: i64,start_time: Instant,// use start_time.elapsed().as_nanos() or .as_secs()}/// Play all the game rounds.fngame(){// Whole game statistics stored hereletmutstats=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. :-)letmutdeck=Deck::new();// Create the players (up to 8 w/o changing the hand cards)letplayer_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()`].)letmutplayers: Vec<Player>=player_names.iter().map(|n|Player::new(n.to_string())).collect();// We use this often, so take it now.letplayer_count=players.len();// = <number of players> - 1// Start playing - GAME_ROUNDS rounds.forcnt_gamein1..=GAME_ROUNDS{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 10 cards for each playerdebug!("Dealing.");for_iin1..=10{forplayerin0..player_count{players[player].get_card(deck.pop().expect("Deck is empty while dealing to players"));}}// we need 5 cards from deck to build the rowsdebug!("Building the rows.");letmutcards=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"));});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)letmuttable=Table::new(cards);/* debug!("Table: {:?}\n", table); debug!("Players: {:?}\n", players); debug!("Deck: {:?}\n", deck); */debug!("We have the table ready: {:?}",table);// playing one game, players taking turnsdebug!("Players start taking turns");// Since players have 10 cards dealt we have exactly 10 turns.forturnin1..=10{debug!("Turn {}!",turn);trace!("The table: {:?}",table);// 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.forplayerin0..player_count{// get a card from the playerletplayer_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]lettopcard=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);// We actually move the card variable with its ownership everywhere, so no &refs.}// 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 oneswhiletable.has_player_cards(){letsmallest: 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);// Find the row with the closest smallest card, or None if smaller than any row head.letclosest_row=table.get_closest_row(&smallest);// That is an Option<usize>.trace!("Choose closest row: {:?}",closest_row);matchclosest_row{Some(rowid)=>{// We have to put it at the end of `rowid`.debug!("Putting down card into row {}",rowid);letplayer_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 firstletoverflow=table.put_card_into_row(smallest,rowid);// And this is Option<VecDeque<Card>>.ifletSome(cards)=overflow{// 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 headplayers[player_id].give_pile(cards);}},None=>{// Card too small, need to pick any row to take!letplayer_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 takeletrowid=players[player_id].pick_row_for_small_card(table.peek_rows(),&smallest.card);trace!("Picked row {}",rowid);// take the row cards ("bust")letcards=table.take_row(rowid);trace!("Took cards: {:?}",cards);// and give them to the player who owns the cardplayers[player_id].give_pile(cards);// put new card in the rowletoverflow=table.put_card_into_row(smallest,rowid);ifletSome(_)=overflow{// If this fires then I have fucked something up. :-)panic!("Player took whole row and it's already full");}}}}}// end of roundinfo!("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 hereletmutwinners: Vec<usize>=Vec::new();letmutwinscore: i32=i32::MAX-1;// Getting facy here about "larger than any valid value".foriin0..player_count{info!("Player {} has {} points",players[i].get_name(),players[i].get_points());ifplayers[i].get_points()<winscore{trace!("New winner {} with score {}",players[i].get_name(),players[i].get_points());winners.clear();winners.push(i);winscore=players[i].get_points();}elseifplayers[i].get_points()==winscore{trace!("New co-winner {} with score {}",players[i].get_name(),players[i].get_points());winners.push(i);}trace!("The list of winners is {:?}",winners);// get pile from Playerletcards=players[i].get_pile();// and give it back to the Deckdeck.push_cards(cards);// close Player round and update statsplayers[i].close_round();}// collect remaining Cards from Tabledeck.push_cards(table.collect_rows());trace!("Shall have full deck now, len {}",deck.len());// This is a Vec of `player_id`s, i is a &refforiinwinners.iter(){players[*i].inc_wins();// Increment win count of &Vec<usize> player_id}// Collect the names of the winners.letmutfinals=Vec::new();foriinwinners{finals.push(players[i].get_name());}info!("The winner(s): {:?}",&finals);}// Do the type conversions _very_ visibly. `as_micros()` results `u128`letelapsed_micro: f64=stats.start_time.elapsed().as_micros()asf64;// and `GAME_ROUNDS` is `i32`. We'll use `elapsed_micro/game_rounds` later.letgame_rounds: f64=GAME_ROUNDS.into();// ???which is same as the slightly uglier:let_res: f64=stats.start_time.elapsed().as_micros()asf64/<i32asInto<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 winsplayers.sort_by_cached_key(|x|Reverse(x.get_wins()));// DESC wins (caching is just for the show); using std::cmp::Reverse// Print out totalsforiin0..players.len(){letp=&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(),p.get_total_points(),p.get_rows_busted());}}// Tests. Try `cargo test`. // They have criminally low coverage, mostly due to the simplicity of the code.// Take it as some examples. #[cfg(test)]modtests{usestd::collections::VecDeque;userand::Rng;usecrate::{card::Card,player::{Player,PlayerCard},row::Row,table::Table};#[test]/// Test Card, and point generation logic.fncard_values(){letcard_values=vec![1,2,5,10,33,55,77];letcard_points=vec![1,1,2,3,5,7,5];foriin1..card_values.len(){letc=Card::new(card_values[i]);letp=c.points;assert!(p==card_points[i],"card={} card points={} i={} expected point={}",card_values[i],p,i,card_points[i]);}}#[test]fnplayer_take_pile(){// create a playerletmutp=Player::new("bob".to_string());// create a pileletmutpile=VecDeque::new();letmutrefpile=VecDeque::new();foriin5..10{letc=Card::new(i);pile.push_back(c);letc=Card::new(i);refpile.push_back(c);}// give the pile to playerp.give_pile(pile);assert!(p.get_rows_busted()==1);// get back the pile from player// p = p.gimme_pile();letpile=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 itemsassert_eq!(pile,refpile);assert!(pile.iter().all(|item|refpile.contains(item)));letmutpile=VecDeque::new();foriin4..=9{letc=Card::new(i);pile.push_back(c);}p.give_pile(pile);assert!(p.get_rows_busted()==2);}#[test]fnrow_push(){letmutrow=Row::new();letmutrefcard=VecDeque::new();// reference vec to checkforiin1..=7{letcval=i+5;letcard=Card::new(cval);// push a card into the rowifletSome(cards)=row.push_or_collect(card){// got the overflowprintln!("Got overflow row at {}!",i);assert!(i==6,"Overflow at wrong position: {} != 6",i);// we need to get the proper vecassert!(cards.iter().all(|item|refcard.contains(item)),"Got cards {:?}",cards);}else{println!("push success {}",i);}// remember the correct vec for checkingletcard=Card::new(cval);refcard.push_back(card);// check card valueassert!(row.last_card_value()==cval,"Last card value mismatch: got {} vs expected {}",row.last_card_value(),cval);}assert!(row.len()==2,"Row contains wrong amount of cards: {}",row.len());}#[test]fnsort_cards(){letmutcards: VecDeque<Card>=VecDeque::new();letmutrng=rand::thread_rng();for_in1..50{letn=rng.gen_range(1..104);cards.push_back(Card::new(n));}cards.make_contiguous().sort();foriin1..cards.len(){assert!(cards[i-1].value<=cards[i].value,"Bad ordering: {} > {}",cards[i-1].value,cards[i].value);}}#[test]fncheck_closest_row(){lettable=generate_table();letpcard=PlayerCard{player_id: 42,card: Card::new(42)};letclosest=table.get_closest_row(&pcard);assert_eq!(closest,Some(3));// index from 0}#[test]fncheck_smallest_player_card(){letmuttable=generate_table();letmutplayer_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;});letsmallest=table.get_smallest_player_card();assert_eq!(smallest,PlayerCard{player_id: 5,card: Card::new(6)});}fngenerate_table()-> Table{letmutrow_cards=VecDeque::new();vec![5,7,10,33,70].into_iter().for_each(|c|row_cards.push_back(Card::new(c)));lettable=Table::new(row_cards);table}}