Day 21 part 1 and WIP for part 2
I also improved the main startup code to require a little less maintenance.
This commit is contained in:
@ -78,6 +78,9 @@
|
|||||||
<None Update="inputs\20.txt">
|
<None Update="inputs\20.txt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="inputs\21.txt">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
2
inputs/21.txt
Normal file
2
inputs/21.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Player 1 starting position: 4
|
||||||
|
Player 2 starting position: 8
|
134
src/21.cs
Normal file
134
src/21.cs
Normal file
@ -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()}<r>");
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int>(playerPos), 21);
|
||||||
|
|
||||||
|
t.Stop();
|
||||||
|
Logger.Log($"<+black>> part2: p1: {p1wins:N0}, p2: {p2wins:N0} -> <+white>{Math.Max(p1wins, p2wins)}<r>");
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int, GameState> cachedWinCases = new();
|
||||||
|
|
||||||
|
private static (long, long) PlayQuantumGame(List<int> playerPos, int maxScore, List<int>? playerScores = null, int turn = 0, int rollNum = 0, int rollVal = 0, int totalRounds = 0, int totalRolls = 0)
|
||||||
|
{
|
||||||
|
playerScores ??= new List<int> { 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<int>(playerPos), maxScore, new List<int>(playerScores), turn, i + 1, 2, totalRounds, totalRolls);
|
||||||
|
var threeWins = PlayQuantumGame(new List<int>(playerPos), maxScore, new List<int>(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;
|
||||||
|
}
|
||||||
|
}
|
48
src/main.cs
48
src/main.cs
@ -1,14 +1,14 @@
|
|||||||
using aoc2021;
|
using aoc2021;
|
||||||
|
|
||||||
var arg = args.FirstOrDefault();
|
var types = System.Reflection.Assembly
|
||||||
if (arg == "all")
|
|
||||||
{
|
|
||||||
var types = System.Reflection.Assembly
|
|
||||||
.GetExecutingAssembly()
|
.GetExecutingAssembly()
|
||||||
.GetTypes()
|
.GetTypes()
|
||||||
.Where(t => t.IsSubclassOf(typeof(Day)) && !t.IsAbstract && t.Name != "DayTemplate")
|
.Where(t => t.IsSubclassOf(typeof(Day)) && !t.IsAbstract && t.Name != "DayTemplate")
|
||||||
.OrderBy(t => t.Name);
|
.OrderBy(t => t.Name);
|
||||||
|
|
||||||
|
var arg = args.FirstOrDefault();
|
||||||
|
if (arg == "all")
|
||||||
|
{
|
||||||
foreach (var type in types)
|
foreach (var type in types)
|
||||||
{
|
{
|
||||||
using var day = (Day)Activator.CreateInstance(type)!;
|
using var day = (Day)Activator.CreateInstance(type)!;
|
||||||
@ -17,28 +17,22 @@ if (arg == "all")
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using Day day = arg switch
|
Day? day = null;
|
||||||
|
if (string.IsNullOrEmpty(arg))
|
||||||
{
|
{
|
||||||
"1" => new Day01(),
|
day = new Day21();
|
||||||
"2" => new Day02(),
|
}
|
||||||
"3" => new Day03(),
|
else
|
||||||
//"4" => new Day04(),
|
{
|
||||||
"5" => new Day05(),
|
var type = types.FirstOrDefault(x => x.Name == $"Day{arg?.PadLeft(2, '0')}");
|
||||||
"6" => new Day06(),
|
if (type == null)
|
||||||
"7" => new Day07(),
|
{
|
||||||
"8" => new Day08(),
|
Logger.Log($"Unknown day <cyan>{arg}<r>");
|
||||||
"9" => new Day09(),
|
}
|
||||||
"10" => new Day10(),
|
else
|
||||||
"11" => new Day11(),
|
{
|
||||||
"12" => new Day12(),
|
day = (Day?)Activator.CreateInstance(type);
|
||||||
"13" => new Day13(),
|
}
|
||||||
"14" => new Day14(),
|
}
|
||||||
"15" => new Day15(),
|
day?.Go();
|
||||||
"16" => new Day16(),
|
|
||||||
"17"=> new Day17(),
|
|
||||||
"18" => new Day18(),
|
|
||||||
"19" => new Day19(),
|
|
||||||
_ => new Day20(),
|
|
||||||
};
|
|
||||||
day.Go();
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user