241 lines
7.3 KiB
Plaintext
241 lines
7.3 KiB
Plaintext
{
|
|
Solutions to the Advent Of Code.
|
|
Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
|
|
}
|
|
|
|
unit UClumsyCrucible;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Generics.Collections, Math, USolver;
|
|
|
|
type
|
|
|
|
{ TAxisData }
|
|
|
|
TAxisData = record
|
|
// The current minimum total heat loss to get from this node to the end if the first step is on this axis.
|
|
Minimum: Cardinal;
|
|
// True, if and only if the minimum has been set, i.e. a path has been found from this node to the end, with the
|
|
// first step on this axis.
|
|
IsTraversed,
|
|
// True. if a close node was updated (traversed) on the other axis, such that this minimum might be outdated. This
|
|
// means that if this node is on the work list, NeedsUpdate is True for at least one of the axes.
|
|
NeedsUpdate: Boolean;
|
|
end;
|
|
|
|
{ TNode }
|
|
|
|
TNode = record
|
|
Horizontal, Vertical: TAxisData;
|
|
LocalHeatLoss: Byte;
|
|
end;
|
|
|
|
TNodeArray = array of TNode;
|
|
TNodeArrays = specialize TList<TNodeArray>;
|
|
|
|
TWorkQueue = specialize TQueue<TPoint>;
|
|
|
|
{ TClumsyCrucible }
|
|
|
|
TClumsyCrucible = class(TSolver)
|
|
private
|
|
// Each item in FMap is a horizontal row of nodes.
|
|
FMap: TNodeArrays;
|
|
FWidth: Integer;
|
|
procedure InvalidateHorizontalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
|
procedure InvalidateVerticalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
|
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
|
|
|
|
const
|
|
CMaxStraight = 3;
|
|
|
|
{ TClumsyCrucible }
|
|
|
|
procedure TClumsyCrucible.InvalidateHorizontalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := Min(FWidth - 1, APosition.X + CMaxStraight) downto Max(0, APosition.X - CMaxStraight) do
|
|
if (i <> APosition.X) and not FMap[APosition.Y][i].Horizontal.NeedsUpdate then
|
|
begin
|
|
FMap[APosition.Y][i].Horizontal.NeedsUpdate := True;
|
|
if not FMap[APosition.Y][i].Vertical.NeedsUpdate then
|
|
AWorkQueue.Enqueue(Point(i, APosition.Y));
|
|
end;
|
|
end;
|
|
|
|
procedure TClumsyCrucible.InvalidateVerticalNeighbors(constref APosition: TPoint; constref AWorkQueue: TWorkQueue);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := Min(FMap.Count - 1, APosition.Y + CMaxStraight) downto Max(0, APosition.Y - CMaxStraight) do
|
|
if (i <> APosition.Y) and not FMap[i][APosition.X].Vertical.NeedsUpdate then
|
|
begin
|
|
FMap[i][APosition.X].Vertical.NeedsUpdate := True;
|
|
if not FMap[i][APosition.X].Horizontal.NeedsUpdate then
|
|
AWorkQueue.Enqueue(Point(APosition.X, i));
|
|
end;
|
|
end;
|
|
|
|
constructor TClumsyCrucible.Create;
|
|
begin
|
|
FMap := TNodeArrays.Create;
|
|
end;
|
|
|
|
destructor TClumsyCrucible.Destroy;
|
|
begin
|
|
FMap.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TClumsyCrucible.ProcessDataLine(const ALine: string);
|
|
var
|
|
i: Integer;
|
|
nodes: TNodeArray;
|
|
begin
|
|
FWidth := Length(ALine);
|
|
SetLength(nodes, FWidth);
|
|
for i := 0 to FWidth - 1 do
|
|
begin
|
|
nodes[i].LocalHeatLoss := StrToInt(ALine[i + 1]);
|
|
nodes[i].Horizontal.IsTraversed := False;
|
|
nodes[i].Horizontal.NeedsUpdate := False;
|
|
nodes[i].Vertical.IsTraversed := False;
|
|
nodes[i].Vertical.NeedsUpdate := False;
|
|
end;
|
|
FMap.Add(nodes);
|
|
end;
|
|
|
|
procedure TClumsyCrucible.Finish;
|
|
var
|
|
queue: TWorkQueue;
|
|
position: TPoint;
|
|
node: TNode;
|
|
newMinimum, acc: Cardinal;
|
|
i: Integer;
|
|
begin
|
|
queue := TWorkQueue.Create;
|
|
|
|
// Initializes work queue with end node.
|
|
FMap.Last[FWidth - 1].Horizontal.Minimum := 0;
|
|
FMap.Last[FWidth - 1].Horizontal.IsTraversed := True;
|
|
FMap.Last[FWidth - 1].Vertical := FMap.Last[FWidth - 1].Horizontal;
|
|
position := Point(FWidth - 1, FMap.Count - 1);
|
|
InvalidateHorizontalNeighbors(position, queue);
|
|
InvalidateVerticalNeighbors(position, queue);
|
|
|
|
// Processes work queue.
|
|
while queue.Count > 0 do
|
|
begin
|
|
position := queue.Dequeue;
|
|
node := FMap[position.Y][position.X];
|
|
|
|
// Updates horizontal data.
|
|
if node.Horizontal.NeedsUpdate then
|
|
begin
|
|
node.Horizontal.NeedsUpdate := False;
|
|
|
|
// Checks for better minimum in left direction.
|
|
newMinimum := Cardinal.MaxValue;
|
|
acc := 0;
|
|
for i := position.X - 1 downto Max(0, position.X - CMaxStraight) do
|
|
begin
|
|
Inc(acc, FMap[position.Y][i].LocalHeatLoss);
|
|
if FMap[position.Y][i].Vertical.IsTraversed then
|
|
newMinimum := Min(newMinimum, FMap[position.Y][i].Vertical.Minimum + acc);
|
|
end;
|
|
// Checks for better minimum in right direction.
|
|
acc := 0;
|
|
for i := position.X + 1 to Min(FWidth - 1, position.X + CMaxStraight) do
|
|
begin
|
|
Inc(acc, FMap[position.Y][i].LocalHeatLoss);
|
|
if FMap[position.Y][i].Vertical.IsTraversed then
|
|
newMinimum := Min(newMinimum, FMap[position.Y][i].Vertical.Minimum + acc);
|
|
end;
|
|
// Updates horizontal minimum and queues update for neighbors.
|
|
if not node.Horizontal.IsTraversed or (node.Horizontal.Minimum > newMinimum) then
|
|
begin
|
|
node.Horizontal.IsTraversed := True;
|
|
node.Horizontal.Minimum := newMinimum;
|
|
InvalidateVerticalNeighbors(position, queue);
|
|
end;
|
|
end;
|
|
|
|
// Updates vertical data.
|
|
if node.Vertical.NeedsUpdate then
|
|
begin
|
|
node.Vertical.NeedsUpdate := False;
|
|
|
|
// Checks for better minimum in up direction.
|
|
newMinimum := Cardinal.MaxValue;
|
|
acc := 0;
|
|
for i := position.Y - 1 downto Max(0, position.Y - CMaxStraight) do
|
|
begin
|
|
Inc(acc, FMap[i][position.X].LocalHeatLoss);
|
|
if FMap[i][position.X].Horizontal.IsTraversed then
|
|
newMinimum := Min(newMinimum, FMap[i][position.X].Horizontal.Minimum + acc);
|
|
end;
|
|
// Checks for better minimum in down direction.
|
|
acc := 0;
|
|
for i := position.Y + 1 to Min(FMap.Count - 1, position.Y + CMaxStraight) do
|
|
begin
|
|
Inc(acc, FMap[i][position.X].LocalHeatLoss);
|
|
if FMap[i][position.X].Horizontal.IsTraversed then
|
|
newMinimum := Min(newMinimum, FMap[i][position.X].Horizontal.Minimum + acc);
|
|
end;
|
|
// Updates vertical minimum and queues update for neighbors.
|
|
if not node.Vertical.IsTraversed or (node.Vertical.Minimum > newMinimum) then
|
|
begin
|
|
node.Vertical.IsTraversed := True;
|
|
node.Vertical.Minimum := newMinimum;
|
|
InvalidateHorizontalNeighbors(position, queue);
|
|
end;
|
|
end;
|
|
|
|
FMap[position.Y][position.X] := node;
|
|
end;
|
|
|
|
queue.Free;
|
|
|
|
node := FMap[0][0];
|
|
FPart1 := Min(node.Horizontal.Minimum, node.Vertical.Minimum);
|
|
end;
|
|
|
|
function TClumsyCrucible.GetDataFileName: string;
|
|
begin
|
|
Result := 'clumsy_crucible.txt';
|
|
end;
|
|
|
|
function TClumsyCrucible.GetPuzzleName: string;
|
|
begin
|
|
Result := 'Day 17: Clumsy Crucible';
|
|
end;
|
|
|
|
end.
|
|
|