feat: solved day 15 part 2
This commit is contained in:
@@ -27,4 +27,15 @@ public class PuzzleParserTests
|
|||||||
|
|
||||||
await Assert.That(result).IsEqualTo(1318523);
|
await Assert.That(result).IsEqualTo(1318523);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task PartTwoSolution_WhenGivenPuzzleInput_ItShouldReturnExpectedSolution()
|
||||||
|
{
|
||||||
|
var input = await GetPuzzleInput();
|
||||||
|
var (map, directions) = PuzzleParser.Parse(input);
|
||||||
|
|
||||||
|
var result = map.Scale().MoveRobot(directions).CalculateTotalBoxGpsCoordinates();
|
||||||
|
|
||||||
|
await Assert.That(result).IsEqualTo(1337648);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,70 @@ public class WarehouseMapTests
|
|||||||
"<^^>>>vv<v>>v<<",
|
"<^^>>>vv<v>>v<<",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[MethodDataSource(nameof(ScaleTestCases))]
|
||||||
|
public async Task Scale_WhenCalled_ItShouldReturnExpectedMap(ScaleTestCase testCase)
|
||||||
|
{
|
||||||
|
var expected = WideWarehouseMap.From(testCase.ExpectedMap);
|
||||||
|
|
||||||
|
var result = WarehouseMap.From(testCase.MapInput).Scale();
|
||||||
|
|
||||||
|
await Assert.That(result.Positions).IsEquivalentTo(expected.Positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Func<ScaleTestCase>> ScaleTestCases()
|
||||||
|
{
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##########",
|
||||||
|
"#..O..O.O#",
|
||||||
|
"#......O.#",
|
||||||
|
"#.OO..O.O#",
|
||||||
|
"#..O@..O.#",
|
||||||
|
"#O#..O...#",
|
||||||
|
"#O..O..O.#",
|
||||||
|
"#.OO.O.OO#",
|
||||||
|
"#....O...#",
|
||||||
|
"##########",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"####################",
|
||||||
|
"##....[]....[]..[]##",
|
||||||
|
"##............[]..##",
|
||||||
|
"##..[][]....[]..[]##",
|
||||||
|
"##....[]@.....[]..##",
|
||||||
|
"##[]##....[]......##",
|
||||||
|
"##[]....[]....[]..##",
|
||||||
|
"##..[][]..[]..[][]##",
|
||||||
|
"##........[]......##",
|
||||||
|
"####################",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"#######",
|
||||||
|
"#...#.#",
|
||||||
|
"#.....#",
|
||||||
|
"#..OO@#",
|
||||||
|
"#..O..#",
|
||||||
|
"#.....#",
|
||||||
|
"#######",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##....[][]@.##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ScaleTestCase(string[] MapInput, string[] ExpectedMap);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[MethodDataSource(nameof(MoveTestCases))]
|
[MethodDataSource(nameof(MoveTestCases))]
|
||||||
public async Task Move_WhenCalled_ItShouldMoveValuesToExpectedPositions(MoveTestCase testCase)
|
public async Task Move_WhenCalled_ItShouldMoveValuesToExpectedPositions(MoveTestCase testCase)
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
namespace WarehouseWoes.Tests;
|
||||||
|
|
||||||
|
public class WideWarehouseMapTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[MethodDataSource(nameof(CalculateTotalBoxGpsCoordinatesTestCases))]
|
||||||
|
public async Task CalculateTotalBoxGpsCoordinates_WhenCalledWithExampleInput_ItShouldReturnExpectedValue(CalculateTotalBoxGpsCoordinatesTestCase testCase)
|
||||||
|
{
|
||||||
|
var map = WideWarehouseMap.From(testCase.MapInput);
|
||||||
|
|
||||||
|
var result = map.CalculateTotalBoxGpsCoordinates();
|
||||||
|
|
||||||
|
await Assert.That(result).IsEqualTo(testCase.ExpectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Func<CalculateTotalBoxGpsCoordinatesTestCase>> CalculateTotalBoxGpsCoordinatesTestCases()
|
||||||
|
{
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"####################",
|
||||||
|
"##[].......[].[][]##",
|
||||||
|
"##[]...........[].##",
|
||||||
|
"##[]........[][][]##",
|
||||||
|
"##[]......[]....[]##",
|
||||||
|
"##..##......[]....##",
|
||||||
|
"##..[]............##",
|
||||||
|
"##..@......[].[][]##",
|
||||||
|
"##......[][]..[]..##",
|
||||||
|
"####################",
|
||||||
|
],
|
||||||
|
9021
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CalculateTotalBoxGpsCoordinatesTestCase(string[] MapInput, int ExpectedValue);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[MethodDataSource(nameof(MoveTestCases))]
|
||||||
|
public async Task Move_WhenCalled_ItShouldMoveValuesToExpectedPositions(MoveTestCase testCase)
|
||||||
|
{
|
||||||
|
var expected = WideWarehouseMap.From(testCase.ExpectedMap);
|
||||||
|
var directions = testCase.Directions.Select(Direction.From).ToList();
|
||||||
|
|
||||||
|
var result = WideWarehouseMap.From(testCase.MapInput).MoveRobot(directions);
|
||||||
|
|
||||||
|
await Assert.That(result.Positions).IsEquivalentTo(expected.Positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Func<MoveTestCase>> MoveTestCases()
|
||||||
|
{
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##....[][]@.##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"<",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]@..##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]@..##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"v",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[].@..##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[].@..##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"v",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.......@..##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.......@..##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"<",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##......@...##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##......@...##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"<",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##..........##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"^",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"^",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##.....@....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"<",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##....@.....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##....@.....##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"<",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##...@......##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##...@......##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"^",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##...@[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return () => new(
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##......##..##",
|
||||||
|
"##...[][]...##",
|
||||||
|
"##...@[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
],
|
||||||
|
"^",
|
||||||
|
[
|
||||||
|
"##############",
|
||||||
|
"##...[].##..##",
|
||||||
|
"##...@.[]...##",
|
||||||
|
"##....[]....##",
|
||||||
|
"##..........##",
|
||||||
|
"##..........##",
|
||||||
|
"##############",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MoveTestCase(string[] MapInput, string Directions, string[] ExpectedMap);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
record Direction(int Xd, int Yd)
|
||||||
|
{
|
||||||
|
private const char UpCharacter = '^';
|
||||||
|
private const char RightCharacter = '>';
|
||||||
|
private const char DownCharacter = 'v';
|
||||||
|
private const char LeftCharacter = '<';
|
||||||
|
|
||||||
|
private static readonly Direction Up = new(0, -1);
|
||||||
|
private static readonly Direction Down = new(0, 1);
|
||||||
|
public static readonly Direction Left = new(-1, 0);
|
||||||
|
public static readonly Direction Right = new(1, 0);
|
||||||
|
|
||||||
|
public static Direction From(char character)
|
||||||
|
{
|
||||||
|
return character switch
|
||||||
|
{
|
||||||
|
UpCharacter => Up,
|
||||||
|
RightCharacter => Right,
|
||||||
|
DownCharacter => Down,
|
||||||
|
LeftCharacter => Left,
|
||||||
|
_ => throw new ArgumentException(@$"{nameof(character)} is not a valid: {character}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
abstract class Map(Dictionary<Position, char> positions)
|
||||||
|
{
|
||||||
|
protected const char Robot = '@';
|
||||||
|
protected const char Wall = '#';
|
||||||
|
protected const char Box = 'O';
|
||||||
|
protected const char Empty = '.';
|
||||||
|
protected const char LeftBoxSide = '[';
|
||||||
|
protected const char RightBoxSide = ']';
|
||||||
|
|
||||||
|
public readonly Dictionary<Position, char> Positions = positions;
|
||||||
|
protected Position RobotPosition => Positions.First(kvp => kvp.Value is Robot).Key;
|
||||||
|
private int MaxX => Positions.Select(kvp => kvp.Key).Max(p => p.X);
|
||||||
|
private int MaxY => Positions.Select(kvp => kvp.Key).Max(p => p.Y);
|
||||||
|
public string[] MapGrid => ToString().Split(Environment.NewLine);
|
||||||
|
|
||||||
|
public abstract int CalculateTotalBoxGpsCoordinates();
|
||||||
|
public abstract Map MoveRobot(List<Direction> directions);
|
||||||
|
public virtual Map Scale() => this;
|
||||||
|
|
||||||
|
protected static Dictionary<Position, char> ConvertToDictionary(string[] input)
|
||||||
|
{
|
||||||
|
var positions = new Dictionary<Position, char>();
|
||||||
|
|
||||||
|
for (var y = 0; y < input.Length; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < input[0].Length; x++)
|
||||||
|
{
|
||||||
|
positions.Add(new(x, y), input[y][x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var map = new StringBuilder();
|
||||||
|
|
||||||
|
for (var y = 0; y <= MaxY; y++)
|
||||||
|
{
|
||||||
|
var row = new StringBuilder();
|
||||||
|
|
||||||
|
for (var x = 0; x <= MaxX; x++)
|
||||||
|
{
|
||||||
|
var value = Positions[new(x, y)];
|
||||||
|
row.Append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.AppendLine(row.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
record Position(int X, int Y)
|
||||||
|
{
|
||||||
|
public int GpsCoordinate => (100 * Y) + X;
|
||||||
|
|
||||||
|
public Position GetNextPosition(Direction direction)
|
||||||
|
{
|
||||||
|
return new(X + direction.Xd, Y + direction.Yd);
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-202
@@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
|
||||||
|
using WarehouseWoes;
|
||||||
|
|
||||||
if (args.Length is 0)
|
if (args.Length is 0)
|
||||||
{
|
{
|
||||||
@@ -19,208 +20,13 @@ var input = await File.ReadAllTextAsync(args[0]);
|
|||||||
var stopwatch = new Stopwatch();
|
var stopwatch = new Stopwatch();
|
||||||
stopwatch.Start();
|
stopwatch.Start();
|
||||||
|
|
||||||
var (map, directions) = PuzzleParser.Parse(input);
|
var (parsedMap, directions) = PuzzleParser.Parse(input);
|
||||||
|
|
||||||
|
var map = isPart2
|
||||||
|
? parsedMap.Scale()
|
||||||
|
: parsedMap;
|
||||||
|
|
||||||
var result = map.MoveRobot(directions).CalculateTotalBoxGpsCoordinates();
|
var result = map.MoveRobot(directions).CalculateTotalBoxGpsCoordinates();
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
Console.WriteLine($"The sum of all boxes' GPS coordinates is {result}. ({stopwatch.ElapsedMilliseconds}ms)");
|
Console.WriteLine($"The sum of all boxes' GPS coordinates is {result}. ({stopwatch.ElapsedMilliseconds}ms)");
|
||||||
|
|
||||||
static class PuzzleParser
|
|
||||||
{
|
|
||||||
public static (WarehouseMap Map, List<Direction> Directions) Parse(string input)
|
|
||||||
{
|
|
||||||
var inputParts = input.Split($"{Environment.NewLine}{Environment.NewLine}");
|
|
||||||
|
|
||||||
var map = WarehouseMap.From(inputParts[0].Split(Environment.NewLine));
|
|
||||||
|
|
||||||
var directions = inputParts[1]
|
|
||||||
.Where(character =>
|
|
||||||
{
|
|
||||||
var str = character.ToString();
|
|
||||||
return str != "\r" && str != "\n" && str != "\r\n";
|
|
||||||
})
|
|
||||||
.Select(Direction.From)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return (map, directions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WarehouseMap
|
|
||||||
{
|
|
||||||
private const char Robot = '@';
|
|
||||||
private const char Wall = '#';
|
|
||||||
private const char Box = 'O';
|
|
||||||
private const char Empty = '.';
|
|
||||||
|
|
||||||
public readonly Dictionary<Position, char> Positions;
|
|
||||||
|
|
||||||
private WarehouseMap(Dictionary<Position, char> positions)
|
|
||||||
{
|
|
||||||
Positions = positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WarehouseMap From(string[] input)
|
|
||||||
{
|
|
||||||
var positions = new Dictionary<Position, char>();
|
|
||||||
|
|
||||||
for (var y = 0; y < input.Length; y++)
|
|
||||||
{
|
|
||||||
for (var x = 0; x < input[0].Length; x++)
|
|
||||||
{
|
|
||||||
positions.Add(new(x, y), input[y][x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(positions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WarehouseMap MoveRobot(List<Direction> directions)
|
|
||||||
{
|
|
||||||
var positions = Positions.ToDictionary();
|
|
||||||
|
|
||||||
foreach (var direction in directions)
|
|
||||||
{
|
|
||||||
var robotPosition = positions.First(kvp => kvp.Value is Robot).Key;
|
|
||||||
var nextPosition = robotPosition.GetNextPosition(direction);
|
|
||||||
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
|
||||||
|
|
||||||
if (hasNextPosition is false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (nextPositionValue)
|
|
||||||
{
|
|
||||||
case Wall:
|
|
||||||
continue;
|
|
||||||
case Empty:
|
|
||||||
positions[robotPosition] = Empty;
|
|
||||||
positions[nextPosition] = Robot;
|
|
||||||
continue;
|
|
||||||
case Box when CanPushBox(nextPosition, direction, positions):
|
|
||||||
positions = PushBox(nextPosition, direction, positions);
|
|
||||||
positions[robotPosition] = Empty;
|
|
||||||
positions[nextPosition] = Robot;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(positions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CalculateTotalBoxGpsCoordinates()
|
|
||||||
{
|
|
||||||
var boxes = Positions.Where(kvp => kvp.Value is Box).ToList();
|
|
||||||
return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var maxX = Positions.Select(kvp => kvp.Key).Max(p => p.X);
|
|
||||||
var maxY = Positions.Select(kvp => kvp.Key).Max(p => p.Y);
|
|
||||||
|
|
||||||
var map = new StringBuilder();
|
|
||||||
|
|
||||||
for (var y = 0; y <= maxY; y++)
|
|
||||||
{
|
|
||||||
var row = new StringBuilder();
|
|
||||||
|
|
||||||
for (var x = 0; x <= maxX; x++)
|
|
||||||
{
|
|
||||||
var value = Positions[new(x, y)];
|
|
||||||
row.Append(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.AppendLine(row.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return map.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool CanPushBox(Position boxPosition, Direction direction, Dictionary<Position, char> positions)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var nextPosition = boxPosition.GetNextPosition(direction);
|
|
||||||
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
|
||||||
|
|
||||||
if (hasNextPosition is false)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (nextPositionValue)
|
|
||||||
{
|
|
||||||
case Box:
|
|
||||||
boxPosition = nextPosition;
|
|
||||||
continue;
|
|
||||||
case Empty:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<Position, Char> PushBox(Position boxPosition, Direction direction, Dictionary<Position, char> currentPositions)
|
|
||||||
{
|
|
||||||
var positions = currentPositions.ToDictionary();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var nextPosition = boxPosition.GetNextPosition(direction);
|
|
||||||
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
|
||||||
|
|
||||||
if (hasNextPosition is false)
|
|
||||||
{
|
|
||||||
return positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextPositionValue is Box)
|
|
||||||
{
|
|
||||||
boxPosition = nextPosition;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
positions[nextPosition] = Box;
|
|
||||||
|
|
||||||
return positions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Position(int X, int Y)
|
|
||||||
{
|
|
||||||
public int GpsCoordinate => (100 * Y) + X;
|
|
||||||
|
|
||||||
public Position GetNextPosition(Direction direction)
|
|
||||||
{
|
|
||||||
return new(X + direction.Xd, Y + direction.Yd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record Direction(int Xd, int Yd)
|
|
||||||
{
|
|
||||||
private const char UpCharacter = '^';
|
|
||||||
private const char RightCharacter = '>';
|
|
||||||
private const char DownCharacter = 'v';
|
|
||||||
private const char LeftCharacter = '<';
|
|
||||||
|
|
||||||
private static readonly Direction Up = new(0, -1);
|
|
||||||
private static readonly Direction Down = new(0, 1);
|
|
||||||
private static readonly Direction Left = new(-1, 0);
|
|
||||||
private static readonly Direction Right = new(1, 0);
|
|
||||||
|
|
||||||
public static Direction From(char character)
|
|
||||||
{
|
|
||||||
return character switch
|
|
||||||
{
|
|
||||||
UpCharacter => Up,
|
|
||||||
RightCharacter => Right,
|
|
||||||
DownCharacter => Down,
|
|
||||||
LeftCharacter => Left,
|
|
||||||
_ => throw new ArgumentException(@$"{nameof(character)} is not a valid: {character}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
static class PuzzleParser
|
||||||
|
{
|
||||||
|
public static (Map Map, List<Direction> Directions) Parse(string input)
|
||||||
|
{
|
||||||
|
var inputParts = input.Split($"{Environment.NewLine}{Environment.NewLine}");
|
||||||
|
|
||||||
|
var map = WarehouseMap.From(inputParts[0].Split(Environment.NewLine));
|
||||||
|
|
||||||
|
var directions = inputParts[1]
|
||||||
|
.Where(character =>
|
||||||
|
{
|
||||||
|
var str = character.ToString();
|
||||||
|
return str != "\r" && str != "\n" && str != "\r\n";
|
||||||
|
})
|
||||||
|
.Select(Direction.From)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return (map, directions);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
class WarehouseMap : Map
|
||||||
|
{
|
||||||
|
private WarehouseMap(Dictionary<Position, char> positions) : base(positions) {}
|
||||||
|
|
||||||
|
public static WarehouseMap From(string[] input) => new(ConvertToDictionary(input));
|
||||||
|
|
||||||
|
public override Map Scale()
|
||||||
|
{
|
||||||
|
var scaledPositions = new Dictionary<Position, char>();
|
||||||
|
var scaleFactor = 2;
|
||||||
|
|
||||||
|
foreach (var (position, value) in Positions)
|
||||||
|
{
|
||||||
|
var scaledX = position.X * scaleFactor;
|
||||||
|
var scaledY = position.Y;
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case Wall:
|
||||||
|
scaledPositions[new(scaledX, scaledY)] = Wall;
|
||||||
|
scaledPositions[new(scaledX + 1, scaledY)] = Wall;
|
||||||
|
break;
|
||||||
|
case Box:
|
||||||
|
scaledPositions[new(scaledX, scaledY)] = WideWarehouseMap.LeftBoxSide;
|
||||||
|
scaledPositions[new(scaledX + 1, scaledY)] = WideWarehouseMap.RightBoxSide;
|
||||||
|
break;
|
||||||
|
case Empty:
|
||||||
|
scaledPositions[new(scaledX, scaledY)] = Empty;
|
||||||
|
scaledPositions[new(scaledX + 1, scaledY)] = Empty;
|
||||||
|
break;
|
||||||
|
case Robot:
|
||||||
|
scaledPositions[new(scaledX, scaledY)] = Robot;
|
||||||
|
scaledPositions[new(scaledX + 1, scaledY)] = Empty;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ApplicationException($"Unknown value present {value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return WideWarehouseMap.From(scaledPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Map MoveRobot(List<Direction> directions)
|
||||||
|
{
|
||||||
|
var positions = Positions.ToDictionary();
|
||||||
|
var robotPosition = RobotPosition;
|
||||||
|
|
||||||
|
foreach (var direction in directions)
|
||||||
|
{
|
||||||
|
var nextPosition = robotPosition.GetNextPosition(direction);
|
||||||
|
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
||||||
|
|
||||||
|
if (hasNextPosition is false)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (nextPositionValue)
|
||||||
|
{
|
||||||
|
case Wall:
|
||||||
|
continue;
|
||||||
|
case Empty:
|
||||||
|
positions[robotPosition] = Empty;
|
||||||
|
positions[nextPosition] = Robot;
|
||||||
|
robotPosition = nextPosition;
|
||||||
|
continue;
|
||||||
|
case Box when CanPushBox(nextPosition, direction, positions):
|
||||||
|
positions = PushBox(nextPosition, direction, positions);
|
||||||
|
positions[robotPosition] = Empty;
|
||||||
|
positions[nextPosition] = Robot;
|
||||||
|
robotPosition = nextPosition;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WarehouseMap(positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int CalculateTotalBoxGpsCoordinates()
|
||||||
|
{
|
||||||
|
var boxes = Positions.Where(kvp => kvp.Value is Box).ToList();
|
||||||
|
return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanPushBox(Position boxPosition, Direction direction, Dictionary<Position, char> positions)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var nextPosition = boxPosition.GetNextPosition(direction);
|
||||||
|
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
||||||
|
|
||||||
|
if (hasNextPosition is false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (nextPositionValue)
|
||||||
|
{
|
||||||
|
case Box:
|
||||||
|
boxPosition = nextPosition;
|
||||||
|
continue;
|
||||||
|
case Empty:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<Position, Char> PushBox(Position boxPosition, Direction direction, Dictionary<Position, char> currentPositions)
|
||||||
|
{
|
||||||
|
var positions = currentPositions.ToDictionary();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var nextPosition = boxPosition.GetNextPosition(direction);
|
||||||
|
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
||||||
|
|
||||||
|
if (hasNextPosition is false)
|
||||||
|
{
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPositionValue is Box)
|
||||||
|
{
|
||||||
|
boxPosition = nextPosition;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
positions[nextPosition] = Box;
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
namespace WarehouseWoes;
|
||||||
|
|
||||||
|
class WideWarehouseMap : Map
|
||||||
|
{
|
||||||
|
private WideWarehouseMap(Dictionary<Position, char> positions) : base(positions) {}
|
||||||
|
|
||||||
|
public static WideWarehouseMap From(Dictionary<Position, char> positions) => new(positions);
|
||||||
|
public static WideWarehouseMap From(string[] input) => new(ConvertToDictionary(input));
|
||||||
|
|
||||||
|
public override Map MoveRobot(List<Direction> directions)
|
||||||
|
{
|
||||||
|
var positions = Positions.ToDictionary();
|
||||||
|
var robotPosition = RobotPosition;
|
||||||
|
|
||||||
|
foreach (var direction in directions)
|
||||||
|
{
|
||||||
|
var nextPosition = robotPosition.GetNextPosition(direction);
|
||||||
|
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
||||||
|
|
||||||
|
if (hasNextPosition is false)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (nextPositionValue)
|
||||||
|
{
|
||||||
|
case Wall:
|
||||||
|
continue;
|
||||||
|
case Empty:
|
||||||
|
positions[robotPosition] = Empty;
|
||||||
|
positions[nextPosition] = Robot;
|
||||||
|
robotPosition = nextPosition;
|
||||||
|
continue;
|
||||||
|
case LeftBoxSide:
|
||||||
|
case RightBoxSide:
|
||||||
|
var newPositions = PushBox(nextPosition, direction, positions);
|
||||||
|
|
||||||
|
if (newPositions == positions)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
positions = newPositions;
|
||||||
|
positions[robotPosition] = Empty;
|
||||||
|
positions[nextPosition] = Robot;
|
||||||
|
robotPosition = nextPosition;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WideWarehouseMap(positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int CalculateTotalBoxGpsCoordinates()
|
||||||
|
{
|
||||||
|
var boxes = Positions.Where(kvp => kvp.Value is LeftBoxSide).ToList();
|
||||||
|
return boxes.Select(kvp => kvp.Key).Sum(p => p.GpsCoordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<Position, Char> PushBox(Position boxPosition, Direction direction, Dictionary<Position, char> currentPositions)
|
||||||
|
{
|
||||||
|
var positions = currentPositions.ToDictionary();
|
||||||
|
var boxPositions = new Queue<Position>();
|
||||||
|
var visitedPositions = new HashSet<Position>();
|
||||||
|
boxPositions.Enqueue(boxPosition);
|
||||||
|
|
||||||
|
while (boxPositions.Count is not 0)
|
||||||
|
{
|
||||||
|
var currentPosition = boxPositions.Dequeue();
|
||||||
|
|
||||||
|
if (visitedPositions.Contains(currentPosition))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitedPositions.Add(currentPosition);
|
||||||
|
|
||||||
|
var nextPosition = currentPosition.GetNextPosition(direction);
|
||||||
|
var hasCurrentPosition = positions.TryGetValue(currentPosition, out var currentPositionValue);
|
||||||
|
var hasNextPosition = positions.TryGetValue(nextPosition, out var nextPositionValue);
|
||||||
|
|
||||||
|
if (hasCurrentPosition is false || hasNextPosition is false)
|
||||||
|
{
|
||||||
|
return currentPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPositionValue is Wall)
|
||||||
|
{
|
||||||
|
return currentPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (currentPositionValue)
|
||||||
|
{
|
||||||
|
case RightBoxSide:
|
||||||
|
{
|
||||||
|
var leftBoxSidePosition = currentPosition.GetNextPosition(Direction.Left);
|
||||||
|
boxPositions.Enqueue(leftBoxSidePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LeftBoxSide:
|
||||||
|
{
|
||||||
|
var rightBoxSidePosition = currentPosition.GetNextPosition(Direction.Right);
|
||||||
|
boxPositions.Enqueue(rightBoxSidePosition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPositionValue is Empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boxPositions.Enqueue(nextPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var position in visitedPositions.Reverse())
|
||||||
|
{
|
||||||
|
var nextPosition = position.GetNextPosition(direction);
|
||||||
|
|
||||||
|
var currentValue = positions[position];
|
||||||
|
var nextValue = positions[nextPosition];
|
||||||
|
|
||||||
|
positions[nextPosition] = currentValue;
|
||||||
|
positions[position] = nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,8 +36,7 @@ dotnet run -- <path-to-input-file> part2
|
|||||||
You can also build the projects and run the executables directly.
|
You can also build the projects and run the executables directly.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet build
|
dotnet build ./bin/Debug/net9.0/<project-name> <path-to-input-file>
|
||||||
./bin/Debug/net9.0/<project-name> <path-to-input-file>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Challenges
|
## Challenges
|
||||||
@@ -58,3 +57,4 @@ dotnet build
|
|||||||
| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/GardenGroups/) | CORNERS == SIDES |
|
| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/GardenGroups/) | CORNERS == SIDES |
|
||||||
| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/ClawContraption/) | Yay for algebra |
|
| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/ClawContraption/) | Yay for algebra |
|
||||||
| 14 | [PROBLEM](./14/PROBLEM.md) | [Solution](./14/RestroomRedoubt/) | Part 2 is not as complicated as you think. The answer is using the safety factor |
|
| 14 | [PROBLEM](./14/PROBLEM.md) | [Solution](./14/RestroomRedoubt/) | Part 2 is not as complicated as you think. The answer is using the safety factor |
|
||||||
|
| 15 | [PROBLEM](./15/PROBLEM.md) | [Solution](./15/WarehouseWoes/) | This breaks my previous star record! |
|
||||||
|
|||||||
Reference in New Issue
Block a user