{ 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.