Day 22
Feelings
Part 1 ➟ easy and the code came out quite nice.
Part 2 ➟ what the hell am I trying to implement?
The rules in part 2 were quite confusing and explained a few times to increase the confusion even more.
And then there's this:
To play a sub-game of Recursive Combat, each player creates a new deck by making a copy of the next cards in their deck (the quantity of cards copied is equal to the number on the card they drew to trigger the sub-game).
I completely missed the part in bold for quite some time. Also the examples pass very well even if that functionality is missing.
Improvements for the future
Do some magic to run multiple games in parallel. For example in subgames, you could run three games in parallel: one that decides the winner, and two to precalculate next round for both winners.
Learnings
VecDeque
std::collections::VecDeque is just a double-ended queue, no biggies there. Just tried it out here the first.
iter_mut
I guess I may have used iter_mut actually successfully the first time here. Have tried a few times and it hasn't gone so well. But this time it did. So I've got that going for me. \o/
This gets a card from every player from the top of their decks.
fn pop_top(&mut self) -> Vec<u8> {
self.players.iter_mut().map(|p|p.deck.pop_front().unwrap()).collect()
}
Code that is enabled based on selected features
Cargo.toml:
[features]
vag_emissions = []
Cheat code somewhere in the .rs:
#[cfg(feature = "vag_emissions")]
if self.depth > 1 {
And the build with:
cargo build --release --features=vag_emissions
Implementing Debug-trait
#[derive(Default)]
pub struct Game {
pub game_id: usize,
pub depth: usize,
pub players: Vec<Player>,
pub round: usize,
pub winner: Option<usize>,
round_states: DeckStates,
}
Since my Game struct can grow quite large (particularly round_states: DeckStates), default derived Debug-trait doesn't make sense at all.
This one implements it without making it too verbose.
impl std::fmt::Debug for Game {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Game")
.field("id", &self.game_id)
.field("depth", &self.depth)
.field("winner", &self.winner)
.field("round", &self.round)
.field("players", &self.players)
.finish()
}
}
Implementing Clone-trait
Game is cloned by cloning only the player data.
impl Clone for Game {
fn clone(&self) -> Self {
Self {
players: self.players.clone(),
game_id: 0,
depth: self.depth + 1,
// clone just the players and their decks
.. Default::default()
}
}
}
Using Default-trait in new
Default just makes new so much nicer to write. and Game::new() just looks good elsewhere.
pub fn new() -> Self {
Self { ..Default::default() }
}
AtomicUsize
To make sure every simulated game gets unique ID, AtomicUsize is pretty nice choice.
To start using it:
use std::sync::atomic::{AtomicUsize, Ordering};
static GAME_ID: AtomicUsize = AtomicUsize::new(1);
To increment:
pub fn play(&mut self) {
let game_id = GAME_ID.fetch_add(1, Ordering::SeqCst);
self.game_id = game_id;
And to return current value:
pub fn game_id() -> usize {
GAME_ID.load(Ordering::SeqCst)
}