{
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 UParabolicReflectorDish;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Generics.Collections, USolver;
const
CRoundRockChar = 'O';
CCubeRockChar = '#';
CEmptyChar = '.';
CMaxSpinCount = 1000000000;
type
{ TRockPile }
TRockPile = class
private
FStart, FLength: Integer;
public
constructor Create(const AStart: Integer);
procedure AddRock;
function Any: Boolean;
procedure SetStart(const AStart: Integer);
function CalcWeight(const ALineCount: Integer): Integer;
end;
TRockPiles = specialize TObjectList;
{ TPlatform }
TPlatform = class
private
FLines: TStringList;
procedure TiltNorth;
procedure TiltSouth;
procedure TiltWest;
procedure TiltEast;
function IsEqualTo(const FOther: TStringList): Boolean;
procedure SwapRockLocation(const AColumn, ARockLineIndex, AEmptyLineIndex: Integer);
public
constructor Create;
destructor Destroy; override;
procedure Add(const ALine: string);
procedure Spin;
function CalcWeight: Integer;
end;
{ TParabolicReflectorDish }
TParabolicReflectorDish = class(TSolver)
private
FLineIndex: Integer;
FActivePiles, FFinishedPiles: TRockPiles;
FPlatform: TPlatform;
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
{ TRockPile }
constructor TRockPile.Create(const AStart: Integer);
begin
FStart := AStart;
FLength := 0;
end;
procedure TRockPile.AddRock;
begin
Inc(FLength);
end;
function TRockPile.Any: Boolean;
begin
Result := (FLength > 0);
end;
procedure TRockPile.SetStart(const AStart: Integer);
begin
FStart := AStart;
end;
function TRockPile.CalcWeight(const ALineCount: Integer): Integer;
begin
Result := FLength * (2 * (ALineCount - FStart) - FLength + 1) div 2;
end;
{ TPlatform }
procedure TPlatform.TiltNorth;
var
i, j, k: Integer;
begin
for i := 0 to FLines.Count - 1 do
for j := 1 to Length(FLines[i]) do
if FLines[i][j] = CRoundRockChar then
begin
k := i - 1;
while (k >= 0) and (FLines[k][j] = CEmptyChar) do
Dec(k);
Inc(k);
if k < i then
SwapRockLocation(j, i, k);
end;
end;
procedure TPlatform.TiltSouth;
var
i, j, k: Integer;
begin
for i := FLines.Count - 1 downto 0 do
for j := 1 to Length(FLines[i]) do
if FLines[i][j] = CRoundRockChar then
begin
k := i + 1;
while (k < FLines.Count) and (FLines[k][j] = CEmptyChar) do
Inc(k);
Dec(k);
if k > i then
SwapRockLocation(j, i, k);
end;
end;
procedure TPlatform.TiltWest;
var
i, j, k: Integer;
s: string;
begin
for i := 0 to FLines.Count - 1 do
begin
s := FLines[i];
k := 1;
for j := 1 to Length(s) do
begin
case s[j] of
CEmptyChar:
if k <= 0 then
k := j;
CRoundRockChar: begin
if (k > 0) and (k < j) then
begin
s[k] := CRoundRockChar;
s[j] := CEmptyChar;
Inc(k);
end
else
k := 0;
end;
CCubeRockChar: k := 0;
end;
end;
FLines[i] := s;
end;
end;
procedure TPlatform.TiltEast;
var
i, j, k: Integer;
s: string;
begin
for i := 0 to FLines.Count - 1 do
begin
s := FLines[i];
k := Length(s) + 1;
for j := Length(s) downto 1 do
begin
case s[j] of
CEmptyChar:
if k > Length(s) then
k := j;
CRoundRockChar: begin
if (k <= Length(s)) and (j < k) then
begin
s[k] := CRoundRockChar;
s[j] := CEmptyChar;
Dec(k);
end
else
k := Length(s) + 1;
end;
CCubeRockChar: k := Length(s) + 1;
end;
end;
FLines[i] := s;
end;
end;
function TPlatform.IsEqualTo(const FOther: TStringList): Boolean;
var
i: Integer;
begin
if FLines.Count = FOther.Count then
begin
Result := True;
for i := 0 to FLines.Count - 1 do
if FLines[i] <> FOther[i] then
begin
Result := False;
Exit;
end;
end
else
Result := False;
end;
procedure TPlatform.SwapRockLocation(const AColumn, ARockLineIndex, AEmptyLineIndex: Integer);
var
s: string;
begin
s := FLines[ARockLineIndex];
s[AColumn] := CEmptyChar;
FLines[ARockLineIndex] := s;
s := FLines[AEmptyLineIndex];
s[AColumn] := CRoundRockChar;
FLines[AEmptyLineIndex] := s;
end;
constructor TPlatform.Create;
begin
FLines := TStringList.Create;
end;
destructor TPlatform.Destroy;
begin
FLines.Free;
inherited Destroy;
end;
procedure TPlatform.Add(const ALine: string);
begin
FLines.Add(ALine);
end;
procedure TPlatform.Spin;
var
i, j, x: Integer;
match: Boolean;
history: specialize TObjectList;
begin
// Intializes history of platform rock configurations.
history := specialize TObjectList.Create;
history.Add(TStringList.Create);
history[0].AddStrings(FLines);
// Performs spins until a configuration from the history is encountered again.
for i := 1 to CMaxSpinCount do
begin
TiltNorth;
TiltWest;
TiltSouth;
TiltEast;
// Searches history for the current configuration.
j := 0;
match := False;
while (j < history.Count) and not match do
begin
match := IsEqualTo(history[j]);
if not match then
Inc(j);
end;
if match then
begin
x := CMaxSpinCount mod (i - j);
while x < j do
Inc(x, i - j);
FLines.Free;
FLines := history.ExtractIndex(x);
Break;
end
else begin
history.Add(TStringList.Create);
history[i].AddStrings(FLines);
end;
end;
history.Free;
end;
function TPlatform.CalcWeight: Integer;
var
i, j, len, count: Integer;
begin
Result := 0;
len := Length(FLines[0]);
for i := 0 to FLines.Count - 1 do
begin
count := 0;
for j := 1 to len do
if FLines[i][j] = CRoundRockChar then
Inc(count);
Inc(Result, count * (FLines.Count - i));
end;
end;
{ TParabolicReflectorDish }
constructor TParabolicReflectorDish.Create;
begin
FLineIndex := 0;
FActivePiles := TRockPiles.Create;
FFinishedPiles := TRockPiles.Create;
FPlatform := TPlatform.Create;
end;
destructor TParabolicReflectorDish.Destroy;
begin
FActivePiles.Free;
FFinishedPiles.Free;
FPlatform.Free;
inherited Destroy;
end;
procedure TParabolicReflectorDish.ProcessDataLine(const ALine: string);
var
i: Integer;
begin
Inc(FLineIndex);
// Initializes the list of active piles, one per column.
if FActivePiles.Count = 0 then
begin
FActivePiles.Count := Length(ALine);
for i:= 0 to FActivePiles.Count - 1 do
FActivePiles[i] := TRockPile.Create(0);
end;
// Updates the active piles from the current line.
for i := 1 to Length(ALine) do
begin
case ALine[i] of
CRoundRockChar: FActivePiles[i - 1].AddRock;
CCubeRockChar:
if FActivePiles[i - 1].Any then
begin
FFinishedPiles.Add(FActivePiles.ExtractIndex(i - 1));
FActivePiles.Insert(i - 1, TRockPile.Create(FLineIndex));
end
else
FActivePiles[i - 1].SetStart(FLineIndex);
end;
end;
FPlatform.Add(ALine);
end;
procedure TParabolicReflectorDish.Finish;
var
pile: TRockPile;
begin
for pile in FFinishedPiles do
Inc(FPart1, pile.CalcWeight(FLineIndex));
for pile in FActivePiles do
Inc(FPart1, pile.CalcWeight(FLineIndex));
// Spins the platform and weighs the rocks.
FPlatform.Spin;
FPart2 := FPlatform.CalcWeight;
end;
function TParabolicReflectorDish.GetDataFileName: string;
begin
Result := 'parabolic_reflector_dish.txt';
end;
function TParabolicReflectorDish.GetPuzzleName: string;
begin
Result := 'Day 14: Parabolic Reflector Dish';
end;
end.