{ 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 = '#'; 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; 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; s: string; 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] = '.') do Dec(k); Inc(k); if k < i then begin s := FLines[i]; s[j] := '.'; FLines[i] := s; s := FLines[k]; s[j] := CRoundRockChar; FLines[k] := s; end; end; end; procedure TPlatform.TiltSouth; var i, j, k: Integer; s: string; 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] = '.') do Inc(k); Dec(k); if k > i then begin s := FLines[i]; s[j] := '.'; FLines[i] := s; s := FLines[k]; s[j] := CRoundRockChar; FLines[k] := s; end; 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 '.': if k <= 0 then k := j; CRoundRockChar: begin if (k > 0) and (k < j) then begin s[k] := CRoundRockChar; s[j] := '.'; 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 '.': if k > Length(s) then k := j; CRoundRockChar: begin if (k <= Length(s)) and (j < k) then begin s[k] := CRoundRockChar; s[j] := '.'; 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; 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.