6 votes

Day 21: Dirac Dice

Today's problem description: https://adventofcode.com/2021/day/21

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

2 comments

  1. Crespyl
    Link
    Well part one is easy enough, part two looks like the modular arithmetic problem from Day 22 of 2019, and is probably beyond my abilities (certainly beyond my abilities for tonight!). I'll...

    Well part one is easy enough, part two looks like the modular arithmetic problem from Day 22 of 2019, and is probably beyond my abilities (certainly beyond my abilities for tonight!). I'll probably do some reading over the next few days and see if I can make some headway, but I'm also heading up to visit the family for the rest of the holidays, so this will probably mark the end of my day-by-day solving.

    Happy Christmas and a merry New Year to everyone, it's been fun solving with y'all!

    Part 1 Ruby

    I went with a somewhat overly verbose object oriented approach, thinking that maybe part two would involve rolling lots of separate dice, deterministic or otherwise. I was sort of right, but not in a way that made this particular DDice class useful.

    class DDice
      def initialize()
        @max = 100
        @next_roll = 1
        @total_rolls = 0
      end
    
      def total_rolls
        @total_rolls
      end
    
      def roll
        @total_rolls += 1
        result = @next_roll
    
        @next_roll = @next_roll + 1
        @next_roll = 1 if @next_roll > @max
    
        return result
      end
    
      def roll_n(n)
        (0...n).map { roll }
      end
    end
    
    def wrap(n)
      n -= 10 until n <= 10
      n
    end
    
    def compute_p1(input)
      p1_pos = input.lines[0].chomp.chars.last.to_i
      p2_pos = input.lines[1].chomp.chars.last.to_i
    
      p1_score = 0
      p2_score = 0
    
      die = DDice.new
    
      loop do
        p1_rolls = die.roll_n(3)
        p1_step = p1_rolls.sum
        p1_pos = wrap(p1_pos + p1_step)
        p1_score += p1_pos
        # puts "Player 1 rolls #{p1_rolls.join('+')} and moves to space #{p1_pos} for a total score of #{p1_score}"
        break if p1_score >= 1000
    
        p2_rolls = die.roll_n(3)
        p2_step = p2_rolls.sum
        p2_pos = wrap(p2_pos + p2_step)
        p2_score += p2_pos
        # puts "Player 2 rolls #{p2_rolls.join('+')} and moves to space #{p2_pos} for a total score of #{p2_score}"
        break if p2_score >= 1000
      end
    
      winner = p1_score >= 1000 ? 'p1' : 'p2'
      loser  = p1_score >= 1000 ? 'p2' : 'p1'
      puts "#{winner} wins with #{[p1_score, p2_score].max} points, after #{die.total_rolls} rolls"
      puts "#{loser} loses with #{[p1_score, p2_score].min} points, after #{die.total_rolls} rolls"
    
      (([p1_score, p2_score].min) * die.total_rolls)
    end
    
    2 votes
  2. wycy
    (edited )
    Link
    Rust Finally got it, though not sure that I did it the intended way. It takes 2sec to run on my hardware. DetailsI implemented this as a recursive function to play through all the scenarios and...

    Rust

    Finally got it, though not sure that I did it the intended way. It takes 2sec to run on my hardware.

    DetailsI implemented this as a recursive function to play through all the scenarios and used Rust's `cached` crate to memoize the results of the function.
    Rust
    use std::env;
    use std::io::{self};
    
    const CIRCLE_SIZE: u64 = 10;
    const DICE_ROLLS_PER_TURN: u64 = 3;
    
    const DETERM_DICE_SIDES: u64 = 100;
    const WIN_SCORE_PART1: u64 = 1000;
    
    const WIN_SCORE_PART2: u64 = 21;
    
    fn add_arrays(first: [u64;2], second: [u64;2]) -> [u64;2] {
        let mut new: [u64;2] = [0;2];
        new[0] = first[0] + second[0];
        new[1] = first[1] + second[1];
        new
    }
    
    #[macro_use] extern crate lazy_static;
    lazy_static! {
        static ref ROLLS: Vec<[u64; 3]> = {
            let mut poss: Vec<[u64; 3]> = Vec::new();
            for d1 in 1..=3 {
                for d2 in 1..=3 {
                    for d3 in 1..=3 {
                        poss.push([d1, d2, d3]);
                    }
                }
            }
            poss
        };
    }
    
    #[macro_use] extern crate cached;
    
    cached! {
        PD;
        fn play_from(starting: [u64;2], scores: [u64;2], turn: usize, rolls: Option<[u64; 3]>) -> [u64; 2] = {
            let mut wins: [u64; 2] = [0; 2];
    
            // Initialize game
            let mut pos:   [u64; 2] = starting;
            let mut score: [u64; 2] = scores;
    
            // Play next move first
            match rolls {
                Some(roll) => {
                    pos[turn] = (pos[turn] + roll[0]+roll[1]+roll[2] - 1) % CIRCLE_SIZE + 1;
                    score[turn] += pos[turn];
                    if score[turn] >= WIN_SCORE_PART2 {
                        wins[turn] += 1;
                        return wins;
                    }
                },
                _ => {},
            }
    
            // Keep playing
            let player = if turn == 0 { 1 } else { 0 };
            for roll in ROLLS.iter() {
                wins = add_arrays(wins,play_from(pos,score, player, Some(*roll)));
            }
            wins
        }
    }
    
    fn play_part1(starting: [u64;2]) -> u64 {
        // Play game
        let mut pos:   [u64; 2] = starting;
        let mut score: [u64; 2] = [0; 2];
        let mut rolls = 0;
        let mut dice = 0;
        'play: loop {
    
            // Player Loop
            for player in 0..2 {
                let mut roll = 0;
                for _ in 0..DICE_ROLLS_PER_TURN {
                    dice += 1;
                    if dice > DETERM_DICE_SIDES { dice = 1; }
                    roll += dice;
                }
                pos[player] = (pos[player] + roll - 1) % CIRCLE_SIZE + 1;
                score[player] += pos[player];
                rolls += DICE_ROLLS_PER_TURN;
                if score[player] >= WIN_SCORE_PART1 { break 'play; }
            }
        }
        let loser = score.iter().min().unwrap();
        loser * rolls
    }
    
    fn solve(input: &str) -> io::Result<()> {
        // Input
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        
        // Starting positions
        let starting = input_str
            .split("\n")
            .map(|l| l.chars().last().unwrap().to_digit(10).unwrap())
            .collect::<Vec<_>>();
    
        let starting: [u64; 2] = [starting[0].into(), starting[1].into()];
    
        // Part 1
        let part1 = play_part1(starting.into());
        println!("Part 1: {}", part1); // 1004670
    
        // Part 2
        let part2 = play_from(starting, [0;2], 1, None); // start with player 1 because rolls=None (effectively starts with player 0)
        let part2 = part2.iter().max().unwrap();
        println!("Part 2: {}", part2); // 492043106122795
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    1 vote