author | Peter Gervai <grin@grin.hu> |
Sun, 05 Feb 2023 01:42:36 +0100 | |
changeset 8 | 91154fe07100 |
parent 5 | 0dd7f2c9fd81 |
permissions | -rw-r--r-- |
2 | 1 |
// #[allow(dead_code, unused)] |
5 | 2 |
|
3 |
// vigyazz6! autoplayer |
|
4 |
// |
|
5 |
// This is a program automagically playing a card game called "Vigyazz6!". |
|
6 |
// |
|
7 |
// The rules are rather simple, but I'mrather lazy to reprint it in detail, |
|
8 |
// so it'll be brief. |
|
9 |
// |
|
10 |
// The code is based on a deck of 104 numbered cards, all of them having |
|
11 |
// a face `value` and punishment `points` (the less the better). |
|
12 |
// There are 2 to 10 players (but above 8 the code shall be modified to |
|
13 |
// deal less cards), and the game has 5 rows of maximum 5 cards on the |
|
14 |
// table. |
|
15 |
// All players throw cards at once, and they are put in the row with |
|
16 |
// closest less value; if there is none, they have to pick and take |
|
17 |
// any row. When a row is full (5) they have to take the row and their |
|
18 |
// card starts a new row head. |
|
19 |
// When players are out of cards the least points collected wins. |
|
20 |
// |
|
21 |
// Important: I do not use `unsafe` code, this is all tightly controlled. |
|
0 | 22 |
// |
23 |
||
5 | 24 |
/// We use panic in several places where the code shall not go. |
4 | 25 |
use core::panic; |
5 | 26 |
/// `VecDeque` is a Vec which can be pushed/popped at both ends |
27 |
/// `time::Instant` is used to measure running time. |
|
28 |
/// `cmp::Reverse` used for reverse sorting. |
|
3 | 29 |
use std::{collections::VecDeque, time::Instant, cmp::Reverse}; |
4 | 30 |
|
5 | 31 |
/// We use this `pretty_env_logger` as a logging framework. |
32 |
extern crate pretty_env_logger; |
|
33 |
#[macro_use] extern crate log; |
|
4 | 34 |
|
5 | 35 |
// I import structures from my module files to use here. |
36 |
use crate::{deck::Deck, player::{Player, PlayerCard}, table::Table}; |
|
37 |
||
38 |
// Here we read the files and "get to know" what's in them. This isn't including, only scanning. |
|
4 | 39 |
mod deck; |
40 |
mod table; |
|
41 |
mod player; |
|
42 |
mod card; |
|
43 |
mod row; |
|
0 | 44 |
|
5 | 45 |
// We play this many game rounds. You want to keep this under 3 for any debug level above "warnings". |
4 | 46 |
const GAME_ROUNDS: i32 = 100_000; |
2 | 47 |
|
0 | 48 |
fn main() { |
5 | 49 |
// Env variable makes the logger to log more or less: |
2 | 50 |
// RUST_LOG=debug cargo run |
51 |
pretty_env_logger::init(); |
|
5 | 52 |
info!("Program starting."); // info level logging |
1 | 53 |
game(); |
2 | 54 |
info!("End of run."); |
0 | 55 |
} |
56 |
||
57 |
||
2 | 58 |
/*** Game ****/ |
59 |
||
5 | 60 |
/// `GameStat` structure collects global statistics about the run. |
2 | 61 |
struct GameStat { |
62 |
game_count: i64, |
|
63 |
shuffle_count: i64, |
|
64 |
start_time: Instant, // use start_time.elapsed().as_nanos() or .as_secs() |
|
65 |
} |
|
66 |
||
5 | 67 |
/// Play all the game rounds. |
0 | 68 |
fn game() { |
5 | 69 |
// Whole game statistics stored here |
2 | 70 |
let mut stats = GameStat { game_count:0, shuffle_count: 0, start_time: Instant::now() }; |
1 | 71 |
|
5 | 72 |
// Create the game deck with all the card (ordered). Open box. Remove shrink wrap. :-) |
1 | 73 |
let mut deck = Deck::new(); |
74 |
||
5 | 75 |
// Create the players (up to 8 w/o changing the hand cards) |
76 |
let player_names = vec![ "peter", "moni", "icbalint", "orsi", "topi", "kgb", "zsu", "csilla" ]; |
|
77 |
// Iterate on player names, create Player struct from them and collect them into a Vec'tor. |
|
78 |
// (On the borrow level we don't consume the player_names vector [using `iter()` instead of `into_iter()`].) |
|
1 | 79 |
let mut players: Vec<Player> = player_names.iter().map( |n| Player::new(n.to_string()) ).collect(); |
80 |
||
5 | 81 |
// We use this often, so take it now. |
82 |
let player_count = players.len(); // = <number of players> - 1 |
|
1 | 83 |
|
5 | 84 |
// Start playing - GAME_ROUNDS rounds. |
2 | 85 |
for cnt_game in 1..=GAME_ROUNDS { |
5 | 86 |
debug!("Game round {} starts", cnt_game); // debug level logging |
87 |
// Shuffle the deck (using various methods) |
|
1 | 88 |
deck.shuffle(); |
2 | 89 |
stats.shuffle_count += 1; |
90 |
stats.game_count += 1; |
|
1 | 91 |
|
5 | 92 |
// dealing 10 cards for each player |
2 | 93 |
debug!("Dealing."); |
3 | 94 |
for _i in 1..=10 { |
1 | 95 |
for player in 0 .. player_count { |
96 |
players[player].get_card( deck.pop().expect("Deck is empty while dealing to players") ); |
|
97 |
} |
|
98 |
} |
|
99 |
||
5 | 100 |
// we need 5 cards from deck to build the rows |
2 | 101 |
debug!("Building the rows."); |
102 |
let mut cards = VecDeque::new(); |
|
5 | 103 |
// This is another way to write "for _ in (1..=5) { ... }" |
3 | 104 |
(1..=5).for_each(|_| { |
2 | 105 |
cards.push_back( deck.pop().expect("deck empty before starting the game") ); |
3 | 106 |
}); |
5 | 107 |
trace!("We pushed 5 cards to rows: {:?}\n", cards); |
108 |
||
109 |
// Create the Table (contains the Deck, the Rows (with 5 starting `cards`) and occasional cards thrown by Players) |
|
1 | 110 |
let mut table = Table::new(cards); |
111 |
||
5 | 112 |
/* debug!("Table: {:?}\n", table); |
113 |
debug!("Players: {:?}\n", players); |
|
114 |
debug!("Deck: {:?}\n", deck); |
|
2 | 115 |
*/ |
116 |
debug!("We have the table ready: {:?}", table); |
|
1 | 117 |
|
5 | 118 |
// playing one game, players taking turns |
2 | 119 |
debug!("Players start taking turns"); |
5 | 120 |
// Since players have 10 cards dealt we have exactly 10 turns. |
1 | 121 |
for turn in 1..=10 { |
2 | 122 |
debug!("Turn {}!", turn); |
123 |
trace!("The table: {:?}", table); |
|
0 | 124 |
|
5 | 125 |
// Everyone puts a card "face down" (Rust promised not to peek). |
126 |
// If I used the `players.for_each()` form I would hit the borrow checker: |
|
127 |
// `players` would be repeatedly borrowed mutable and program wouldn't compile. |
|
1 | 128 |
for player in 0 .. player_count { |
129 |
// get a card from the player |
|
5 | 130 |
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] |
131 |
let topcard = players[player].throw_card(); // If Player [module] was smart this would be a chosen card, but it would need player |
|
132 |
// not just brains but possibility to peek at the rows. This wasn't in the plan, so |
|
133 |
// we just take the top card in hand. |
|
1 | 134 |
// put it on the table ("turned face down") |
5 | 135 |
table.lay_player_card( topcard, player_id ); // We actually move the card variable with its ownership everywhere, so no &refs. |
1 | 136 |
} |
137 |
||
5 | 138 |
// Process cards on the Table. |
2 | 139 |
debug!("The Table process the throws."); |
5 | 140 |
// Sort the cards by `value` the players have thrown. |
1 | 141 |
table.sort_cards(); |
142 |
||
5 | 143 |
// Pick them one by one, from smallest to larger ones |
2 | 144 |
while table.has_player_cards() { |
5 | 145 |
let smallest: PlayerCard = table.get_smallest_player_card(); // I just wrote the PlayerCard type to show; Rust is smart enough to know. |
2 | 146 |
trace!("Take smallest card {:?}", &smallest); |
147 |
||
5 | 148 |
// Find the row with the closest smallest card, or None if smaller than any row head. |
149 |
let closest_row = table.get_closest_row(&smallest); // That is an Option<usize>. |
|
2 | 150 |
trace!("Choose closest row: {:?}", closest_row); |
0 | 151 |
|
2 | 152 |
match closest_row { |
153 |
Some(rowid) => { |
|
5 | 154 |
// We have to put it at the end of `rowid`. |
2 | 155 |
debug!("Putting down card into row {}", rowid); |
5 | 156 |
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. |
157 |
// Try to put the card to the end of the row, or collect full row first |
|
158 |
let overflow = table.put_card_into_row(smallest, rowid); // And this is Option<VecDeque<Card>>. |
|
2 | 159 |
if let Some(cards) = overflow { |
5 | 160 |
// Row is full, we got pile ("bust") |
161 |
debug!("Row is busted, {} collects", players[player_id].get_name()); |
|
162 |
// Player gets pile, thrown card was put into row head |
|
2 | 163 |
players[ player_id ].give_pile( cards ); |
164 |
} |
|
165 |
}, |
|
166 |
None => { |
|
5 | 167 |
// Card too small, need to pick any row to take! |
168 |
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. |
|
4 | 169 |
debug!("Too small from {}, picking row", players[player_id].get_name()); |
5 | 170 |
// Pick any row to take |
4 | 171 |
let rowid = players[ player_id ].pick_row_for_small_card(table.peek_rows(), &smallest.card); |
2 | 172 |
trace!("Picked row {}", rowid); |
5 | 173 |
// take the row cards ("bust") |
2 | 174 |
let cards = table.take_row(rowid); |
175 |
trace!("Took cards: {:?}", cards); |
|
5 | 176 |
// and give them to the player who owns the card |
2 | 177 |
players[ player_id ].give_pile( cards ); |
178 |
// put new card in the row |
|
179 |
let overflow = table.put_card_into_row(smallest, rowid); |
|
3 | 180 |
if let Some(_) = overflow { |
5 | 181 |
// If this fires then I have fucked something up. :-) |
2 | 182 |
panic!("Player took whole row and it's already full"); |
183 |
} |
|
1 | 184 |
} |
185 |
} |
|
186 |
} |
|
187 |
} |
|
188 |
||
189 |
// end of round |
|
2 | 190 |
info!("Round finished, len is {} ??sec", stats.start_time.elapsed().as_micros()); |
1 | 191 |
|
2 | 192 |
debug!("End of round, counting and collecting back piles"); |
5 | 193 |
// We collect the winner(s) and their score here |
2 | 194 |
let mut winners: Vec<usize> = Vec::new(); |
5 | 195 |
let mut winscore: i32 = i32::MAX-1; // Getting facy here about "larger than any valid value". |
2 | 196 |
|
197 |
for i in 0..player_count { |
|
4 | 198 |
info!("Player {} has {} points", players[i].get_name(), players[i].get_points()); |
2 | 199 |
|
4 | 200 |
if players[i].get_points() < winscore { |
201 |
trace!("New winner {} with score {}", players[i].get_name(), players[i].get_points()); |
|
2 | 202 |
winners.clear(); |
203 |
winners.push(i); |
|
4 | 204 |
winscore = players[i].get_points(); |
2 | 205 |
|
4 | 206 |
} else if players[i].get_points() == winscore { |
207 |
trace!("New co-winner {} with score {}", players[i].get_name(), players[i].get_points()); |
|
2 | 208 |
winners.push(i); |
209 |
} |
|
210 |
trace!("The list of winners is {:?}", winners); |
|
211 |
||
5 | 212 |
// get pile from Player |
2 | 213 |
let cards = players[i].get_pile(); |
5 | 214 |
// and give it back to the Deck |
2 | 215 |
deck.push_cards(cards); |
5 | 216 |
// close Player round and update stats |
2 | 217 |
players[i].close_round(); |
218 |
} |
|
219 |
||
5 | 220 |
// collect remaining Cards from Table |
2 | 221 |
deck.push_cards( table.collect_rows() ); |
4 | 222 |
trace!("Shall have full deck now, len {}", deck.len()); |
1 | 223 |
|
5 | 224 |
// This is a Vec of `player_id`s, i is a &ref |
2 | 225 |
for i in winners.iter() { |
5 | 226 |
players[*i].inc_wins(); // Increment win count of &Vec<usize> player_id |
2 | 227 |
} |
228 |
||
5 | 229 |
// Collect the names of the winners. |
230 |
let mut finals = Vec::new(); |
|
2 | 231 |
for i in winners { |
4 | 232 |
finals.push( players[i].get_name() ); |
2 | 233 |
} |
234 |
info!("The winner(s): {:?}", &finals); |
|
235 |
} |
|
236 |
||
5 | 237 |
// Do the type conversions _very_ visibly. `as_micros()` results `u128` |
3 | 238 |
let elapsed_micro: f64 = stats.start_time.elapsed().as_micros() as f64; |
5 | 239 |
// and `GAME_ROUNDS` is `i32`. We'll use `elapsed_micro/game_rounds` later. |
3 | 240 |
let game_rounds: f64 = GAME_ROUNDS.into(); |
5 | 241 |
// ???which is same as the slightly uglier: |
3 | 242 |
let _res: f64 = stats.start_time.elapsed().as_micros() as f64 / <i32 as Into<f64>>::into(GAME_ROUNDS); |
243 |
||
244 |
println!("Totals (game time {} ??s, or {} s; {} ??s/game), {} games played ({} shuffles):", |
|
2 | 245 |
stats.start_time.elapsed().as_micros(), |
246 |
stats.start_time.elapsed().as_secs(), |
|
3 | 247 |
elapsed_micro / game_rounds, |
2 | 248 |
stats.game_count, |
249 |
stats.shuffle_count, |
|
250 |
); |
|
251 |
||
3 | 252 |
// players.sort_by( |a, b| a.total_point.partial_cmp(&b.total_point).unwrap() ); // ASC points |
253 |
// players.sort_by( |a, b| b.wins.partial_cmp(&a.wins).unwrap() ); // DESC wins |
|
5 | 254 |
players.sort_by_cached_key( |x| Reverse(x.get_wins()) ); // DESC wins (caching is just for the show); using std::cmp::Reverse |
2 | 255 |
|
5 | 256 |
// Print out totals |
2 | 257 |
for i in 0..players.len() { |
5 | 258 |
let p = &players[i]; // the rest looks simpler by using this. (Seems to confuse rust-analyzer in codium though.) |
4 | 259 |
println!("Player {} has wins {}, score {} (busted {} times)", |
260 |
p.get_name(), |
|
261 |
p.get_wins(), |
|
262 |
p.get_total_points(), |
|
263 |
p.get_rows_busted()); |
|
0 | 264 |
} |
265 |
} |
|
266 |
||
8
91154fe07100
Add some test comment, remove original draft comments.
Peter Gervai <grin@grin.hu>
parents:
5
diff
changeset
|
267 |
// Tests. Try `cargo test`. |
91154fe07100
Add some test comment, remove original draft comments.
Peter Gervai <grin@grin.hu>
parents:
5
diff
changeset
|
268 |
// They have criminally low coverage, mostly due to the simplicity of the code. |
91154fe07100
Add some test comment, remove original draft comments.
Peter Gervai <grin@grin.hu>
parents:
5
diff
changeset
|
269 |
// Take it as some examples. |
0 | 270 |
#[cfg(test)] |
271 |
mod tests { |
|
1 | 272 |
use std::collections::VecDeque; |
273 |
use rand::Rng; |
|
274 |
||
4 | 275 |
use crate::{card::Card, player::{Player, PlayerCard}, row::Row, table::Table}; |
0 | 276 |
|
277 |
#[test] |
|
8
91154fe07100
Add some test comment, remove original draft comments.
Peter Gervai <grin@grin.hu>
parents:
5
diff
changeset
|
278 |
/// Test Card, and point generation logic. |
0 | 279 |
fn card_values() { |
280 |
let card_values = vec![1,2,5,10,33,55,77]; |
|
281 |
let card_points = vec![1,1,2,3, 5, 7, 5]; |
|
282 |
for i in 1 .. card_values.len() { |
|
283 |
let c = Card::new( card_values[i] ); |
|
284 |
let p = c.points; |
|
285 |
assert!(p == card_points[i], "card={} card points={} i={} expected point={}", card_values[i], p, i, card_points[i]); |
|
286 |
} |
|
287 |
} |
|
288 |
||
289 |
#[test] |
|
290 |
fn player_take_pile() { |
|
291 |
// create a player |
|
292 |
let mut p = Player::new("bob".to_string()); |
|
293 |
// create a pile |
|
1 | 294 |
let mut pile = VecDeque::new(); |
2 | 295 |
let mut refpile = VecDeque::new(); |
0 | 296 |
for i in 5..10 { |
297 |
let c = Card::new(i); |
|
1 | 298 |
pile.push_back(c); |
0 | 299 |
let c = Card::new(i); |
2 | 300 |
refpile.push_back(c); |
0 | 301 |
} |
2 | 302 |
// give the pile to player |
303 |
p.give_pile(pile); |
|
4 | 304 |
assert!( p.get_rows_busted() == 1 ); |
0 | 305 |
|
306 |
// get back the pile from player |
|
307 |
// p = p.gimme_pile(); |
|
2 | 308 |
let pile = p.get_pile(); |
0 | 309 |
|
310 |
// the pile we got shall be same as the pile we gave |
|
311 |
// this check is O(n^2), doesn't matter for less than 100 items |
|
2 | 312 |
assert_eq!( pile, refpile ); |
0 | 313 |
assert!( pile.iter().all( |item| refpile.contains(item)) ); |
314 |
||
1 | 315 |
let mut pile = VecDeque::new(); |
316 |
for i in 4..=9 { |
|
0 | 317 |
let c = Card::new(i); |
1 | 318 |
pile.push_back(c); |
0 | 319 |
} |
2 | 320 |
p.give_pile(pile); |
4 | 321 |
assert!( p.get_rows_busted() == 2 ); |
0 | 322 |
} |
323 |
||
324 |
#[test] |
|
325 |
fn row_push() { |
|
326 |
let mut row = Row::new(); |
|
2 | 327 |
let mut refcard = VecDeque::new(); // reference vec to check |
0 | 328 |
for i in 1..=7 { |
329 |
let cval = i+5; |
|
330 |
let card = Card::new(cval); |
|
331 |
// push a card into the row |
|
332 |
if let Some(cards) = row.push_or_collect(card) { |
|
333 |
// got the overflow |
|
334 |
println!("Got overflow row at {}!", i); |
|
335 |
assert!( i == 6, "Overflow at wrong position: {} != 6", i ); |
|
336 |
// we need to get the proper vec |
|
337 |
assert!( cards.iter().all( |item| refcard.contains(item) ), "Got cards {:?}", cards ); |
|
338 |
} else { |
|
339 |
println!("push success {}", i); |
|
340 |
} |
|
341 |
// remember the correct vec for checking |
|
342 |
let card = Card::new(cval); |
|
2 | 343 |
refcard.push_back(card); |
0 | 344 |
|
345 |
// check card value |
|
346 |
assert!( row.last_card_value() == cval, "Last card value mismatch: got {} vs expected {}", row.last_card_value(), cval ); |
|
347 |
} |
|
4 | 348 |
assert!( row.len() == 2, "Row contains wrong amount of cards: {}", row.len() ); |
0 | 349 |
} |
1 | 350 |
|
351 |
#[test] |
|
352 |
fn sort_cards() { |
|
2 | 353 |
let mut cards: VecDeque<Card> = VecDeque::new(); |
1 | 354 |
let mut rng = rand::thread_rng(); |
3 | 355 |
for _ in 1..50 { |
1 | 356 |
let n = rng.gen_range(1..104); |
2 | 357 |
cards.push_back( Card::new(n) ); |
1 | 358 |
} |
2 | 359 |
cards.make_contiguous().sort(); |
1 | 360 |
|
361 |
for i in 1..cards.len() { |
|
362 |
assert!( cards[i-1].value <= cards[i].value, "Bad ordering: {} > {}", cards[i-1].value,cards[i].value ); |
|
363 |
} |
|
364 |
} |
|
365 |
||
2 | 366 |
#[test] |
367 |
fn check_closest_row() { |
|
368 |
let table = generate_table(); |
|
369 |
let pcard = PlayerCard{ player_id: 42, card: Card::new(42) }; |
|
370 |
let closest = table.get_closest_row(&pcard); |
|
371 |
assert_eq!( closest, Some(3) ); // index from 0 |
|
372 |
} |
|
373 |
||
374 |
#[test] |
|
375 |
fn check_smallest_player_card() { |
|
376 |
let mut table = generate_table(); |
|
377 |
|
|
378 |
let mut player_id = 1; |
|
379 |
vec![103, 8, 71, 93, 6].into_iter().for_each(|c| { |
|
380 |
table.lay_player_card( Card::new(c), player_id); |
|
381 |
player_id += 1; |
|
382 |
}); |
|
383 |
||
384 |
let smallest = table.get_smallest_player_card(); |
|
385 |
assert_eq!( smallest, PlayerCard{ player_id: 5, card: Card::new(6) } ); |
|
386 |
} |
|
387 |
||
388 |
fn generate_table() -> Table { |
|
389 |
let mut row_cards = VecDeque::new(); |
|
390 |
vec![5,7,10,33,70].into_iter().for_each(|c| row_cards.push_back( Card::new(c) )); |
|
391 |
let table = Table::new(row_cards); |
|
392 |
table |
|
393 |
} |
|
0 | 394 |
} |
395 |