From 95e06e2b1007b2878a6e224ade5951289c0ee843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 9 Dec 2023 00:59:18 +0100 Subject: [PATCH] Added solution for "Day 8: Haunted Wasteland", part 1 --- AdventOfCode.lpi | 4 + AdventOfCode.lpr | 4 +- solvers/UHauntedWasteland.pas | 239 +++++++++++++++++++++++++++ tests/AdventOfCodeFPCUnit.lpi | 4 + tests/AdventOfCodeFPCUnit.lpr | 3 +- tests/UHauntedWastelandTestCases.pas | 112 +++++++++++++ 6 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 solvers/UHauntedWasteland.pas create mode 100644 tests/UHauntedWastelandTestCases.pas diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi index c8fe206..ac855eb 100644 --- a/AdventOfCode.lpi +++ b/AdventOfCode.lpi @@ -61,6 +61,10 @@ + + + + diff --git a/AdventOfCode.lpr b/AdventOfCode.lpr index 306c507..4d5448f 100644 --- a/AdventOfCode.lpr +++ b/AdventOfCode.lpr @@ -25,7 +25,8 @@ uses {$ENDIF} Classes, SysUtils, CustApp, USolver, - UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer, UWaitForIt, UCamelCards; + UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer, UWaitForIt, UCamelCards, + UHauntedWasteland; type @@ -57,6 +58,7 @@ begin engine.RunAndFree(TGiveSeedFertilizer.Create); engine.RunAndFree(TWaitForIt.Create); engine.RunAndFree(TCamelCards.Create); + engine.RunAndFree(THauntedWasteland.Create); engine.Free; end; diff --git a/solvers/UHauntedWasteland.pas b/solvers/UHauntedWasteland.pas new file mode 100644 index 0000000..97d611c --- /dev/null +++ b/solvers/UHauntedWasteland.pas @@ -0,0 +1,239 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 2023 Stefan Müller + + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +} + +unit UHauntedWasteland; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, Generics.Collections, USolver; + +type + + { TNode } + + TNode = class + private + FName: string; + FLeft, FRight: TNode; + public + constructor Create(const AName: string); + property Name: string read FName; + procedure AddNeighbors(constref ALeft, ARight: TNode); + function TryStep(const ADirection: Char; out ONewLocation: TNode): Boolean; + end; + + TNodes = specialize TObjectList; + + { THauntedWasteland } + + THauntedWasteland = class(TSolver) + private + FDirections: string; + FNextDirectionIndex: Integer; + FNodes, FUnparsedNodes: TNodes; + FCurrentNode, FTargetNode: TNode; + procedure ProcessNode(const ALine: string); + procedure DoSteps; + public + constructor Create; + destructor Destroy; override; + procedure ProcessDataLine(const ALine: string); override; + procedure Finish; override; + function GetDataFileName: string; override; + function GetPuzzleName: string; override; + end; + +implementation + +{ TNode } + +constructor TNode.Create(const AName: string); +begin + FName := AName; + FLeft := nil; + FRight := nil; +end; + +procedure TNode.AddNeighbors(constref ALeft, ARight: TNode); +begin + FLeft := ALeft; + FRight := ARight; +end; + +function TNode.TryStep(const ADirection: Char; out ONewLocation: TNode): Boolean; +begin + ONewLocation := nil; + Result := False; + case ADirection of + 'L': + if FLeft <> nil then + begin + ONewLocation := FLeft; + Result := True; + end; + 'R': + if FRight <> nil then + begin + ONewLocation := FRight; + Result := True; + end; + end; +end; + +{ THauntedWasteland } + +procedure THauntedWasteland.ProcessNode(const ALine: string); +var + name, left, right: string; + i: Integer; + parsingNode, leftNode, rightNode: TNode; +begin + name := LeftStr(ALine, 3); + left := Copy(ALine, 8, 3); + right := Copy(ALine, 13, 3); + + // Tries to find the new node in unparsed nodes. + i := 0; + parsingNode := nil; + while (parsingNode = nil) and (i < FUnparsedNodes.Count) do + begin + if FUnparsedNodes[i].Name = name then + begin + // New node was already encountered and is now parsed. + parsingNode := FUnparsedNodes[i]; + FUnparsedNodes.Delete(i); + end; + Inc(i); + end; + + // Adds new node to list of all nodes. We do this before looking for leftNode and rightNode because the node might + // reference itself. + if parsingNode = nil then + begin + parsingNode := TNode.Create(name); + FNodes.Add(parsingNode); + end; + + // Tries to find leftNode and rightNode. + i := 0; + leftNode := nil; + rightNode := nil; + while ((leftNode = nil) or (rightNode = nil)) and (i < FNodes.Count) do + begin + if (leftNode = nil) and (FNodes[i].Name = left) then + begin + leftNode := FNodes[i]; + end; + if (rightNode = nil) and (FNodes[i].Name = right) then + begin + rightNode := FNodes[i]; + end; + Inc(i); + end; + + // Adds leftNode and/or rightNode to list of all nodes and unparsed nodes. + if leftNode = nil then + begin + leftNode := TNode.Create(left); + FNodes.Add(leftNode); + FUnparsedNodes.Add(leftNode); + end; + if rightNode = nil then + begin + if right = left then + // This happens only if both left and right nodes are new. + rightNode := leftNode + else begin + rightNode := TNode.Create(right); + FNodes.Add(rightNode); + FUnparsedNodes.Add(rightNode); + end; + end; + + parsingNode.AddNeighbors(leftNode, rightNode); + + // Checks for start and end nodes. + if name = 'ZZZ' then + FTargetNode := parsingNode + else if name = 'AAA' then + FCurrentNode := parsingNode; +end; + +procedure THauntedWasteland.DoSteps; +var + new: TNode; +begin + if FCurrentNode <> nil then + begin + while (FCurrentNode <> FTargetNode) and FCurrentNode.TryStep(FDirections[FNextDirectionIndex], new) do + begin + FCurrentNode := new; + Inc(FNextDirectionIndex); + if FNextDirectionIndex > Length(FDirections) then + FNextDirectionIndex := 1; + Inc(FPart1); + end; + end; +end; + +constructor THauntedWasteland.Create; +begin + FNextDirectionIndex := 1; + FNodes := TNodes.Create; + FUnparsedNodes := TNodes.Create(False); + FCurrentNode := nil; + FTargetNode := nil; +end; + +destructor THauntedWasteland.Destroy; +begin + FNodes.Free; + FUnparsedNodes.Free; + inherited Destroy; +end; + +procedure THauntedWasteland.ProcessDataLine(const ALine: string); +begin + if FDirections = '' then + FDirections := ALine + else if (ALine <> '') and ((FTargetNode = nil) or (FCurrentNode <> FTargetNode)) then + begin + ProcessNode(ALine); + DoSteps; + end; +end; + +procedure THauntedWasteland.Finish; +begin + +end; + +function THauntedWasteland.GetDataFileName: string; +begin + Result := 'haunted_wasteland.txt'; +end; + +function THauntedWasteland.GetPuzzleName: string; +begin + Result := 'Day 8: Haunted Wasteland'; +end; + +end. + diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi index c3472c8..597e381 100644 --- a/tests/AdventOfCodeFPCUnit.lpi +++ b/tests/AdventOfCodeFPCUnit.lpi @@ -72,6 +72,10 @@ + + + + diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr index 5601cc9..7554d8e 100644 --- a/tests/AdventOfCodeFPCUnit.lpr +++ b/tests/AdventOfCodeFPCUnit.lpr @@ -4,7 +4,8 @@ program AdventOfCodeFPCUnit; uses Interfaces, Forms, GuiTestRunner, USolver, UBaseTestCases, UTrebuchetTestCases, UCubeConundrumTestCases, - UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases; + UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases, + UHauntedWastelandTestCases; {$R *.res} diff --git a/tests/UHauntedWastelandTestCases.pas b/tests/UHauntedWastelandTestCases.pas new file mode 100644 index 0000000..01fa5ba --- /dev/null +++ b/tests/UHauntedWastelandTestCases.pas @@ -0,0 +1,112 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 2023 Stefan Müller + + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +} + +unit UHauntedWastelandTestCases; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, USolver, UBaseTestCases, UHauntedWasteland; + +type + + { THauntedWastelandFullDataTestCase } + + THauntedWastelandFullDataTestCase = class(TEngineBaseTest) + protected + function CreateSolver: ISolver; override; + published + procedure TestPart1; + end; + + { THauntedWastelandExampleTestCase } + + THauntedWastelandExampleTestCase = class(TExampleEngineBaseTest) + protected + function CreateSolver: ISolver; override; + published + procedure TestPart1; + end; + + { TExample2HauntedWasteland } + + TExample2HauntedWasteland = class(THauntedWasteland) + function GetDataFileName: string; override; + end; + + { THauntedWastelandExample2TestCase } + + THauntedWastelandExample2TestCase = class(TExampleEngineBaseTest) + protected + function CreateSolver: ISolver; override; + published + procedure TestPart1; + end; + +implementation + +{ THauntedWastelandFullDataTestCase } + +function THauntedWastelandFullDataTestCase.CreateSolver: ISolver; +begin + Result := THauntedWasteland.Create; +end; + +procedure THauntedWastelandFullDataTestCase.TestPart1; +begin + AssertEquals(14257, FSolver.GetResultPart1); +end; + +{ THauntedWastelandExampleTestCase } + +function THauntedWastelandExampleTestCase.CreateSolver: ISolver; +begin + Result := THauntedWasteland.Create; +end; + +procedure THauntedWastelandExampleTestCase.TestPart1; +begin + AssertEquals(2, FSolver.GetResultPart1); +end; + +{ TExample2HauntedWasteland } + +function TExample2HauntedWasteland.GetDataFileName: string; +begin + Result := 'haunted_wasteland2.txt'; +end; + +{ THauntedWastelandExample2TestCase } + +function THauntedWastelandExample2TestCase.CreateSolver: ISolver; +begin + Result := TExample2HauntedWasteland.Create; +end; + +procedure THauntedWastelandExample2TestCase.TestPart1; +begin + AssertEquals(6, FSolver.GetResultPart1); +end; + +initialization + + RegisterTest(THauntedWastelandFullDataTestCase); + RegisterTest(THauntedWastelandExampleTestCase); + RegisterTest(THauntedWastelandExample2TestCase); +end.