Skip to content

Commit

Permalink
2024 Day15 Solved
Browse files Browse the repository at this point in the history
  • Loading branch information
smabuk committed Dec 15, 2024
1 parent 2bffeab commit 7667ddc
Show file tree
Hide file tree
Showing 2 changed files with 341 additions and 26 deletions.
283 changes: 257 additions & 26 deletions Solutions/2024/Day15.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,271 @@
public static partial class Day15 {

private static char[,] _map = default!;
private static IEnumerable<Direction> _directions = [];
private static List<Direction> _directions = [];
private static Action<string[], bool>? _visualise = null;

[Init]
public static void Load(string[] input, Action<string[], bool>? visualise = null)
{
_map = input
.TakeWhile(i => !string.IsNullOrWhiteSpace(i))
.To2dArray();
_directions = string.Join("", input.Skip(_map.RowsCount()))
.Select(c => c.ToDirection());

_directions = [..
string.Join("", input.Skip(_map.RowsCount()))
.Select(c => c.ToDirection())
];

_visualise = visualise;
_map.VisualiseMap("Initial state:");
}

public static int Part1(string[] _, Action<string[], bool>? visualise = null, params object[]? args)
public static int Part1(string[] _)
{
char[,] map = (char[,])_map.Clone();

map.VisualiseMap("Initial state:", visualise);

Robot robot = new(map.ForEachCell().Single(c => c.Value is ROBOT).Index);

foreach (var direction in _directions) {
if (robot.TryMove(direction, map, out Thing newRobot)) {
foreach (Direction direction in _directions) {
if (robot.TryMovePart1(direction, map, out Thing newRobot)) {
robot = (Robot)newRobot;
}

map.VisualiseMap($"Move {direction.FromDirection()}:", visualise);
map.VisualiseMap($"Move {direction.FromDirection()}:", clearScreen: true);
}

map.VisualiseMap("Final state:", visualise);
map.VisualiseMap("Final state:");

return map.SumOfGpsCoordinates();
}

private static bool TryMove(this Thing thing, Direction direction, char[,] map, out Thing newThing)
private static bool TryMovePart1(this Thing thing, Direction direction, char[,] map, out Thing newThing)
{
newThing = thing with { Location = thing.Location + direction.Delta() };
if (map.TryGetValue(newThing.Location, out char value) && value is WALL) {
char value = newThing.GetTheValue(map);

if (value is WALL) {
return false;
}

if (value is BOX) {
Box box = new(newThing.Location);
_ = box.TryMove(direction, map, out Thing _);
_ = map.TryGetValue(newThing.Location, out value);
_ = box.TryMovePart1(direction, map, out Thing _);
value = newThing.GetTheValue(map);
}

if (value is EMPTY) {
map[thing.Location.X, thing.Location.Y] = EMPTY;
map.SetThing(thing, EMPTY);
thing = newThing;
map[thing.Location.X, thing.Location.Y] = thing is Box ? BOX : ROBOT;
map.SetThing(thing, thing is Robot ? ROBOT : BOX);
return true;
}

return false;
}


public static int Part2(string[] _)
{
char[,] map = _map.MakeWide();
map.VisualiseMap("Wide initial state:");
Robot robot = new(map.ForEachCell().Single(c => c.Value is ROBOT).Index);

foreach (var direction in _directions) {
if (robot.TryMoveRobot(direction, map, out Thing newRobot)) {
robot = (Robot)newRobot;
}

if (_directions.Count < 20) {
map.VisualiseMap($"Move {direction.FromDirection()}:");
}
}

map.VisualiseMap("Final state:");

return map.SumOfGpsCoordinates();
}

private static bool TryMoveRobot(this Robot robot, Direction direction, char[,] map, out Thing newRobot)
{
newRobot = robot with { Location = robot.Location + direction.Delta() };

char value = newRobot.GetTheValue(map);
if (value is WALL) {
return false;
}

if (value is BOX_LEFT) {
WideBox box = new(newRobot.Location);
_ = box.TryMoveBox(direction, map);
value = newRobot.GetTheValue(map);
} else if (value is BOX_RIGHT) {
WideBox box = new(newRobot.Location - (1, 0));
_ = box.TryMoveBox(direction, map);
value = newRobot.GetTheValue(map);
}

if (value is EMPTY) {
map.SetThing(robot.Location, EMPTY);
map.SetThing(newRobot.Location, ROBOT);
return true;
}

return false;
}

private static bool TryMoveBox(this WideBox box, Direction direction, char[,] map)
{
WideBox newBox = box with { Location = box.Location + direction.Delta() };

return direction switch
{
Direction.Left or Direction.Right => newBox.TryMoveBoxHorizontally(box, direction, map),
Direction.Up or Direction.Down => newBox.TryMoveBoxVertically(box, direction, map),
_ => throw new NotImplementedException(),
};
}

private static bool TryMoveBoxVertically(this WideBox newBox, WideBox box, Direction direction, char[,] map)
{
char value1 = map.GetTheValue(newBox.Left);
char value2 = map.GetTheValue(newBox.Right);

if (value1 is WALL || value2 is WALL) {
return false;
}

if (value1 is BOX_LEFT or BOX_RIGHT || value2 is BOX_LEFT or BOX_RIGHT) {
(bool yes, List<WideBox> boxes1) = box.CanIMove(direction, map);
if (yes) {
List<WideBox> boxes = direction switch
{
Direction.Up => [.. boxes1.OrderBy(b => b.Location)],
Direction.Down => [.. boxes1.OrderByDescending(b => b.Location)],
_ => throw new NotImplementedException(),
};

foreach (WideBox boxToMove in boxes) {
map.SetThing(boxToMove.Left, EMPTY);
map.SetThing(boxToMove.Right, EMPTY);
map.SetThing(boxToMove.Left + direction.Delta(), BOX_LEFT);
map.SetThing(boxToMove.Right + direction.Delta(), BOX_RIGHT);
}

return true;
}
}

if (value1 is EMPTY && value2 is EMPTY) {
map.SetThing(box.Left, EMPTY);
map.SetThing(box.Right, EMPTY);
map.SetThing(box.Left + direction.Delta(), BOX_LEFT);
map.SetThing(box.Right + direction.Delta(), BOX_RIGHT);
}

return false;
}

private static bool TryMoveBoxHorizontally(this WideBox newBox, WideBox box, Direction direction, char[,] map)
{
char value = newBox.GetTheValue(map);

if (direction is Direction.Left && value is BOX_RIGHT) {
WideBox box1 = newBox with { Location = newBox.Location - (1, 0) };
_ = box1.TryMoveBox(direction, map);
value = newBox.GetTheValue(map);
} else if (direction is Direction.Right) {
value = map.GetTheValue(newBox.Location + (1, 0));
if (value is BOX_RIGHT) {
_ = newBox.TryMoveBox(direction, map);
value = newBox.GetTheValue(map);
} else if (value is BOX_LEFT) {
WideBox box1 = newBox with { Location = newBox.Location + (1, 0) };
_ = box1.TryMoveBox(direction, map);
value = box1.GetTheValue(map);
}
}

if (value is EMPTY) {
map.SetThing(box.Left, EMPTY);
map.SetThing(box.Right, EMPTY);
map.SetThing(newBox.Left, BOX_LEFT);
map.SetThing(newBox.Right, BOX_RIGHT);
return true;
}

return false;
}

private static (bool Yes, List<WideBox> WideBoxes) CanIMove(this WideBox? box, Direction direction, char[,] map)
{
if (box is null) {
return (true, []);
}

WideBox newBox = box with { Location = box.Location + direction.Delta() };
char value1 = newBox.Left.GetTheValue(map);
char value2 = newBox.Right.GetTheValue(map);

if (value1 is EMPTY && value2 is EMPTY) {
return (true, []);
}

if (value1 is WALL || value2 is WALL) {
map.VisualiseMap($"Can't Move: {direction} {box}");
return (false, []);
}

bool yes = true;
List<WideBox> boxes = [box];

WideBox? box1 = value1 switch {
BOX_RIGHT => newBox with { Location = newBox.Location - (1, 0) },
BOX_LEFT => newBox with { },
_ => null,
};

WideBox? box2 = value2 switch {
BOX_LEFT => newBox with { Location = newBox.Location + (1, 0) },
_ => null,
};

if (box1 is not null) {
(bool yes1, List<WideBox> boxes1) = box1.CanIMove(direction, map);
yes = yes && yes1;
boxes = [.. boxes, box1, .. boxes1];
}

if (box2 is not null) {
(bool yes2, List<WideBox> boxes2) = box2.CanIMove(direction, map);
yes = yes && yes2;
boxes = [.. boxes, box2, .. boxes2];
}

if (yes) {
return (true, [.. boxes.Distinct()]);
}

return (false, []);
}

private static int SumOfGpsCoordinates(this char[,] map)
{
return map
.ForEachCell()
.Where(c => c.Value is BOX)
.Where(c => c.Value is BOX or BOX_LEFT)
.Sum(c => c.Index.GpsCoordinate());
}

private static int GpsCoordinate(this Point location) => (location.Y * 100) + location.X;


public static string Part2(string[] input, params object[]? args) => NO_SOLUTION_WRITTEN_MESSAGE;
private static void SetThing(this char[,] map, Point location, char thing) => map[location.X, location.Y] = thing;
private static void SetThing(this char[,] map, Thing thing, char value) => map[thing.X, thing.Y] = value;
private static char GetTheValue(this char[,] map, Point location) => map[location.X, location.Y];
private static char GetTheValue(this Point location, char[,] map) => map[location.X, location.Y];
private static char GetTheValue(this Thing thing, char[,] map) => map[thing.Location.X, thing.Location.Y];


public static Direction ToDirection(this char c)
{
Expand All @@ -101,24 +298,58 @@ public static char FromDirection(this Direction direction)
};
}

private static void VisualiseMap(this char[,] map, string title, Action<string[], bool>? visualise, bool clearScreen = false)
private static char[,] MakeWide(this char[,] map)
{
if (visualise is null) {
char[,] newMap = new char[map.ColsCount() * 2, map.RowsCount()];
newMap = newMap.Fill('.');

foreach (Cell<char> cell in map.ForEachCell()) {
if (cell.Value is ROBOT) {
newMap[cell.Index.X * 2, cell.Index.Y] = ROBOT;
} else if (cell.Value is WALL) {
newMap[cell.Index.X * 2, cell.Index.Y] = WALL;
newMap[(cell.Index.X * 2) + 1, cell.Index.Y] = WALL;
} else if (cell.Value is BOX) {
newMap[cell.Index.X * 2, cell.Index.Y] = BOX_LEFT;
newMap[(cell.Index.X * 2) + 1, cell.Index.Y] = BOX_RIGHT;
}
}

return newMap;
}


private static void VisualiseMap(this char[,] map, string title, bool clearScreen = false)
{
if (_visualise is null) {
return;
}

string[] output = ["", title, .. map.AsStrings().Select(s => s.Replace('0', '.'))];
visualise?.Invoke(output, clearScreen);
_visualise?.Invoke(output, clearScreen);
}



private abstract record Thing(Point Location);
private abstract record Thing(Point Location)
{
public int X => Location.X;
public int Y => Location.Y;
};

private record Robot(Point Location) : Thing(Location);
private record Box(Point Location) : Thing(Location);
private record WideBox(Point Location) : Thing(Location)
{
public Point Left => Location;
public Point Right => Location + (1, 0);
public List<Point> Locations => [Left, Right];
}

private const char WALL = '#';
private const char EMPTY = '.';
private const char BOX = 'O';
private const char ROBOT = '@';
private const char WALL = '#';
private const char EMPTY = '.';
private const char BOX = 'O';
private const char BOX_LEFT = '[';
private const char BOX_RIGHT = ']';
private const char ROBOT = '@';
}
Loading

0 comments on commit 7667ddc

Please sign in to comment.