From 8a6bf8604a4591a6fc00a16aa084e1c7599c6820 Mon Sep 17 00:00:00 2001 From: Parnic Date: Tue, 21 Dec 2021 22:51:21 -0600 Subject: [PATCH] Day 21 part 1 and WIP for part 2 I also improved the main startup code to require a little less maintenance. --- advent-of-code-2021.csproj | 3 + inputs/21.txt | 2 + src/21.cs | 134 +++++++++++++++++++++++++++++++++++++ src/main.cs | 48 ++++++------- 4 files changed, 160 insertions(+), 27 deletions(-) create mode 100644 inputs/21.txt create mode 100644 src/21.cs diff --git a/advent-of-code-2021.csproj b/advent-of-code-2021.csproj index d2794ff..5706de0 100644 --- a/advent-of-code-2021.csproj +++ b/advent-of-code-2021.csproj @@ -78,6 +78,9 @@ PreserveNewest + + PreserveNewest + diff --git a/inputs/21.txt b/inputs/21.txt new file mode 100644 index 0000000..f0848de --- /dev/null +++ b/inputs/21.txt @@ -0,0 +1,2 @@ +Player 1 starting position: 4 +Player 2 starting position: 8 \ No newline at end of file diff --git a/src/21.cs b/src/21.cs new file mode 100644 index 0000000..00a9149 --- /dev/null +++ b/src/21.cs @@ -0,0 +1,134 @@ +namespace aoc2021; + +internal class Day21 : Day +{ + private record struct GameState((int p1, int p2) Positions, int Turn, (int p1, int p2) Score, (long p1, long p2) Wins, int RollVal, int TotalRolls, int TotalRounds) + { + public override int GetHashCode() => HashCode.Combine(Positions.p1, Positions.p2, Turn, RollVal, TotalRounds); + } + + internal override void Go() + { + var lines = Util.ReadAllLines("inputs/21.txt"); + var player1Pos = int.Parse(lines.ElementAt(0).Split(": ")[1]); + var player2Pos = int.Parse(lines.ElementAt(1).Split(": ")[1]); + Part1(player1Pos, player2Pos); + Part2(player1Pos, player2Pos); + } + + private static void Part1(int player1Pos, int player2Pos) + { + using var t = new Timer(); + + var playerPos = new int[2] + { + player1Pos, + player2Pos, + }; + var (playerScore, numRolls) = PlayGame(playerPos, 1000, 10); + + t.Stop(); + Logger.Log($"<+black>> part1: <+white>{numRolls * playerScore.Min()}"); + } + + private static void Part2(int player1Pos, int player2Pos) + { + using var t = new Timer(); + + var playerPos = new int[2] + { + player1Pos, + player2Pos, + }; + var (p1wins, p2wins) = PlayQuantumGame(new List(playerPos), 21); + + t.Stop(); + Logger.Log($"<+black>> part2: p1: {p1wins:N0}, p2: {p2wins:N0} -> <+white>{Math.Max(p1wins, p2wins)}"); + } + + private static (long[] scores, long numRolls) PlayGame(int[] playerPos, int maxScore, int dieSides) + { + var playerScore = new long[2] + { + 0, + 0, + }; + + int dieVal = 1; + int turn = 0; + long numRolls = 0; + while (!playerScore.Any(x => x >= maxScore)) + { + for (int i = 0; i < 3; i++) + { + playerPos[turn] = PlayOneRoll(playerPos[turn], dieVal); + dieVal = (dieVal + 1) % dieSides; + numRolls++; + } + + playerScore[turn] += playerPos[turn]; + turn = 1 - turn; + } + + return (playerScore, numRolls); + } + + private static int PlayOneRoll(int playerPos, int dieVal) + { + return ((playerPos + dieVal - 1) % 10) + 1; + } + + private static readonly Dictionary cachedWinCases = new(); + + private static (long, long) PlayQuantumGame(List playerPos, int maxScore, List? playerScores = null, int turn = 0, int rollNum = 0, int rollVal = 0, int totalRounds = 0, int totalRolls = 0) + { + playerScores ??= new List { 0, 0 }; + if (cachedWinCases.TryGetValue(HashCode.Combine(playerPos[0], playerPos[1], turn, rollVal, totalRounds), out GameState winState)) + { + return winState.Wins; + } + + var wins = (0L, 0L); + while (true) + { + totalRounds++; + for (int i = rollNum; i < 3; i++) + { + totalRolls++; + var twoWins = PlayQuantumGame(new List(playerPos), maxScore, new List(playerScores), turn, i + 1, 2, totalRounds, totalRolls); + var threeWins = PlayQuantumGame(new List(playerPos), maxScore, new List(playerScores), turn, i + 1, 3, totalRounds, totalRolls); + wins = (wins.Item1 + twoWins.Item1 + threeWins.Item1, wins.Item2 + twoWins.Item2 + threeWins.Item2); + playerPos[turn] = PlayOneRoll(playerPos[turn], 1); + rollVal = 1; + } + if (rollNum == 3) + { + playerPos[turn] = PlayOneRoll(playerPos[turn], rollVal); + } + + playerScores[turn] += playerPos[turn]; + if (playerScores[turn] >= maxScore) + { + if (turn == 0) + { + wins.Item1++; + } + else + { + wins.Item2++; + } + + GameState state = new((playerPos[0], playerPos[1]), turn, (playerScores[0], playerScores[1]), wins, rollVal, totalRolls, totalRounds); + cachedWinCases[state.GetHashCode()] = state; + + break; + } + + turn = 1 - turn; + rollNum = 0; + rollVal = 0; + } + + return wins; + } +} diff --git a/src/main.cs b/src/main.cs index cdbdfa9..472cb60 100644 --- a/src/main.cs +++ b/src/main.cs @@ -1,14 +1,14 @@ using aoc2021; -var arg = args.FirstOrDefault(); -if (arg == "all") -{ - var types = System.Reflection.Assembly +var types = System.Reflection.Assembly .GetExecutingAssembly() .GetTypes() .Where(t => t.IsSubclassOf(typeof(Day)) && !t.IsAbstract && t.Name != "DayTemplate") .OrderBy(t => t.Name); +var arg = args.FirstOrDefault(); +if (arg == "all") +{ foreach (var type in types) { using var day = (Day)Activator.CreateInstance(type)!; @@ -17,28 +17,22 @@ if (arg == "all") } else { - using Day day = arg switch + Day? day = null; + if (string.IsNullOrEmpty(arg)) { - "1" => new Day01(), - "2" => new Day02(), - "3" => new Day03(), - //"4" => new Day04(), - "5" => new Day05(), - "6" => new Day06(), - "7" => new Day07(), - "8" => new Day08(), - "9" => new Day09(), - "10" => new Day10(), - "11" => new Day11(), - "12" => new Day12(), - "13" => new Day13(), - "14" => new Day14(), - "15" => new Day15(), - "16" => new Day16(), - "17"=> new Day17(), - "18" => new Day18(), - "19" => new Day19(), - _ => new Day20(), - }; - day.Go(); + day = new Day21(); + } + else + { + var type = types.FirstOrDefault(x => x.Name == $"Day{arg?.PadLeft(2, '0')}"); + if (type == null) + { + Logger.Log($"Unknown day {arg}"); + } + else + { + day = (Day?)Activator.CreateInstance(type); + } + } + day?.Go(); }