400 lines
8.6 KiB
Plaintext
400 lines
8.6 KiB
Plaintext
{
|
|
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 <http://www.gnu.org/licenses/>.
|
|
}
|
|
|
|
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<TRockPile>;
|
|
|
|
{ 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<TStringList>;
|
|
begin
|
|
// Intializes history of platform rock configurations.
|
|
history := specialize TObjectList<TStringList>.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.
|
|
|