Skip to content

Commit

Permalink
Merge pull request #17 from MartinZikmund/feature/aoc2025-day16
Browse files Browse the repository at this point in the history
AoC 2024 Day 16 Solutions
  • Loading branch information
MartinZikmund authored Dec 19, 2024
2 parents 72df7be + 8a97595 commit b96b743
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/AdventOfCode.Puzzles/2024/16/Part1/Part1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
namespace AdventOfCode.Puzzles._2024._16.Part1;

public partial class Part1 : IPuzzleSolution
{
private int _width;
private int _height;
private char[,] _map;
private Point _start;
private Point _end;

public record State(Point Position, Point Direction);

public async Task<string> SolveAsync(StreamReader inputReader)
{
await ReadInputAsync(inputReader);

var lowestCost = FindLowestCost();
return lowestCost.ToString();
}

private int FindLowestCost()
{
var states = new PriorityQueue<State, int>();
var minimumCosts = new Dictionary<State, int>();
var finalized = new HashSet<State>();

var startingState = new State(_start, new(1, 0));
states.Enqueue(startingState, 0);
minimumCosts.Add(startingState, 0);

while (states.Count > 0)
{
var state = states.Dequeue();
finalized.Add(state);

// We go forward
var next = state.Position + state.Direction;
var nextState = new State(next, state.Direction);
AddNextIfImproved(nextState, minimumCosts[state] + 1);

// We rotate clockwise
nextState = new State(state.Position, new Point(-state.Direction.Y, state.Direction.X));
AddNextIfImproved(nextState, minimumCosts[state] + 1000);

// We rotate counter-clockwise
nextState = new State(state.Position, new Point(state.Direction.Y, -state.Direction.X));
AddNextIfImproved(nextState, minimumCosts[state] + 1000);

void AddNextIfImproved(State nextState, int cost)
{
if (nextState.Position.X < 0 || nextState.Position.X >= _width || nextState.Position.Y < 0 || nextState.Position.Y >= _height)
{
return;
}

if (_map[nextState.Position.X, nextState.Position.Y] == '#')
{
return;
}

if (!finalized.Contains(nextState))
{
if (!minimumCosts.TryGetValue(nextState, out var nextCost) || nextCost > cost)
{
minimumCosts[nextState] = cost;
states.Remove(nextState, out _, out _);
states.Enqueue(nextState, minimumCosts[nextState]);
}
}
}
}

var bestEndState = int.MaxValue;
foreach (var direction in Directions.WithoutDiagonals)
{
bestEndState = Math.Min(bestEndState, minimumCosts.GetValueOrDefault(new State(_end, direction), int.MaxValue));
}

return bestEndState;
}

private async Task ReadInputAsync(StreamReader inputReader)
{
var lines = await inputReader.ReadAllLinesAsync();
_width = lines[0].Length;
_height = lines.Count;
_map = new char[_width, _height];

for (var y = 0; y < _height; y++)
{
var line = lines[y];
for (var x = 0; x < _width; x++)
{
_map[x, y] = line[x];
if (_map[x, y] == 'S')
{
_start = new(x, y);
_map[x, y] = '.';
}
else if (_map[x, y] == 'E')
{
_end = new(x, y);
_map[x, y] = '.';
}
}
}
}
}
143 changes: 143 additions & 0 deletions src/AdventOfCode.Puzzles/2024/16/Part2/Part2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using AdventOfCode.Puzzles.Tools;

namespace AdventOfCode.Puzzles._2024._16.Part2;

public partial class Part2 : IPuzzleSolution
{
private int _width;
private int _height;
private char[,] _map;
private Point _start;
private Point _end;

public record State(Point Position, Point Direction);

public async Task<string> SolveAsync(StreamReader inputReader)
{
await ReadInputAsync(inputReader);

var lowestCost = FindBestPathTileCount();
return lowestCost.ToString();
}

private int FindBestPathTileCount()
{
var states = new PriorityQueue<State, int>();
var minimumCosts = new Dictionary<State, int>();
var finalized = new HashSet<State>();

var sourceStates = new Dictionary<State, HashSet<State>>();

var startingState = new State(_start, new(1, 0));
states.Enqueue(startingState, 0);
minimumCosts.Add(startingState, 0);

while (states.Count > 0)
{
var state = states.Dequeue();
finalized.Add(state);

// We go forward
var next = state.Position + state.Direction;
var nextState = new State(next, state.Direction);
AddNextIfImproved(nextState, minimumCosts[state] + 1);

// We rotate clockwise
nextState = new State(state.Position, new Point(-state.Direction.Y, state.Direction.X));
AddNextIfImproved(nextState, minimumCosts[state] + 1000);

// We rotate counter-clockwise
nextState = new State(state.Position, new Point(state.Direction.Y, -state.Direction.X));
AddNextIfImproved(nextState, minimumCosts[state] + 1000);

void AddNextIfImproved(State nextState, int cost)
{
if (nextState.Position.X < 0 || nextState.Position.X >= _width || nextState.Position.Y < 0 || nextState.Position.Y >= _height)
{
return;
}

if (_map[nextState.Position.X, nextState.Position.Y] == '#')
{
return;
}

if (!finalized.Contains(nextState))
{
if (!minimumCosts.TryGetValue(nextState, out var nextCost) || nextCost > cost)
{
minimumCosts[nextState] = cost;
sourceStates[nextState] = new();
states.Remove(nextState, out _, out _);
states.Enqueue(nextState, minimumCosts[nextState]);
}

if (minimumCosts[nextState] == cost)
{
sourceStates[nextState].Add(state);
}
}
}
}

var bestEndState = int.MaxValue;
foreach (var direction in Directions.WithoutDiagonals)
{
bestEndState = Math.Min(bestEndState, minimumCosts.GetValueOrDefault(new State(_end, direction), int.MaxValue));
}

var bestPathTiles = new HashSet<Point>();
Queue<State> backtrackQueue = new Queue<State>();

foreach (var direction in Directions.WithoutDiagonals.Where(d => minimumCosts.GetValueOrDefault(new State(_end, d), int.MaxValue) == bestEndState))
{
backtrackQueue.Enqueue(new State(_end, direction));
}

while (backtrackQueue.Count > 0)
{
var current = backtrackQueue.Dequeue();
bestPathTiles.Add(current.Position);

if (sourceStates.TryGetValue(current, out var sources))
{
foreach (var source in sources)
{
backtrackQueue.Enqueue(source);
}
}
}

return bestPathTiles.Count;
}

private async Task ReadInputAsync(StreamReader inputReader)
{
var lines = await inputReader.ReadAllLinesAsync();
_width = lines[0].Length;
_height = lines.Count;
_map = new char[_width, _height];

for (var y = 0; y < _height; y++)
{
var line = lines[y];
for (var x = 0; x < _width; x++)
{
_map[x, y] = line[x];
if (_map[x, y] == 'S')
{
_start = new(x, y);
_map[x, y] = '.';
}
else if (_map[x, y] == 'E')
{
_end = new(x, y);
_map[x, y] = '.';
}
}
}
}
}
17 changes: 17 additions & 0 deletions src/AdventOfCode.Puzzles/2024/16/TestData.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################
4 changes: 4 additions & 0 deletions src/AdventOfCode.Puzzles/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.Text;
global using AdventOfCode.Puzzles.Tools;

0 comments on commit b96b743

Please sign in to comment.