{ 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 UPointOfIncidence; {$mode ObjFPC}{$H+} interface uses Classes, SysUtils, Generics.Collections, Math, USolver; type TMirrorCompareResult = (mcSame, mcOneDifference, mcDifferent); { TMirrorCandidate } TMirrorCandidate = record Position: Integer; IsFixAllowed: Boolean; Part: Integer; end; TMirrorCandidates = specialize TList; { TPointOfIncidence } TPointOfIncidence = class(TSolver) private FLines: TStringList; FHorizontalMirrorCandidates, FVerticalMirrorCandidates: TMirrorCandidates; procedure ProcessPatternLine(const ALine: string); procedure VerifyHorizontalCandidates(const ANewLine: string); procedure CheckForNewHorizontalCandidate(const ANewLine: string); function HaveSingleDifference(const ALine1, ALine2: string): Boolean; procedure VerifyVerticalCandidates(const ALine: string); procedure InitializeNewVerticalCandidates(const AFirstLine: string); function IsVerticalMirrorOfLine(const ALine: string; const AMirror: Integer): TMirrorCompareResult; procedure FinishPattern; function GetMirrorCandidate(const APosition: Integer; const AIsFixAllowed: Boolean; const APart: Integer): TMirrorCandidate; procedure SetMirrorCandidateFixed(const AMirrorCandidates: TMirrorCandidates; const AIndex: Integer); 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 { TPointOfIncidence } procedure TPointOfIncidence.ProcessPatternLine(const ALine: string); begin VerifyHorizontalCandidates(ALine); CheckForNewHorizontalCandidate(ALine); if FLines.Count > 0 then VerifyVerticalCandidates(ALine) else InitializeNewVerticalCandidates(ALine); FLines.Add(ALine); end; procedure TPointOfIncidence.VerifyHorizontalCandidates(const ANewLine: string); var i, mirroredIndex: Integer; begin // Checks if the current line fulfills the exisiting horizontal mirror candidates. i := FHorizontalMirrorCandidates.Count - 1; while i >= 0 do begin // Calculates the index of the line that the current line would be a duplicate of in regards to the i-th candidate. mirroredIndex := 2 * FHorizontalMirrorCandidates[i].Position - FLines.Count - 1; if mirroredIndex >= 0 then begin if ANewLine <> FLines[mirroredIndex] then begin if FHorizontalMirrorCandidates[i].IsFixAllowed and HaveSingleDifference(ANewLine, FLines[mirroredIndex]) then SetMirrorCandidateFixed(FHorizontalMirrorCandidates, i) else FHorizontalMirrorCandidates.Delete(i); end; end; Dec(i); end; end; procedure TPointOfIncidence.CheckForNewHorizontalCandidate(const ANewLine: string); begin // Checks for new horizontal mirror candidate between ALine and the previous line. if FLines.Count > 0 then begin if ANewLine = FLines[FLines.Count - 1] then begin FHorizontalMirrorCandidates.Add(GetMirrorCandidate(FLines.Count, False, 1)); FHorizontalMirrorCandidates.Add(GetMirrorCandidate(FLines.Count, True, 2)); end else if HaveSingleDifference(ANewLine, FLines[FLines.Count - 1]) then FHorizontalMirrorCandidates.Add(GetMirrorCandidate(FLines.Count, False, 2)); end; end; function TPointOfIncidence.HaveSingleDifference(const ALine1, ALine2: string): Boolean; var i: Integer; begin Result := False; for i := 1 to Length(ALine1) do begin if ALine1[i] <> ALine2[i] then begin if Result then begin // Found a second difference between the two lines, return False. Result := False; Exit; end else // Found a difference between the two lines. Result := True; end; end; end; procedure TPointOfIncidence.VerifyVerticalCandidates(const ALine: string); var i: Integer; begin // Checks if the current line fulfills the exisiting vertical mirror candidates. i := FVerticalMirrorCandidates.Count - 1; while i >= 0 do begin case IsVerticalMirrorOfLine(ALine, FVerticalMirrorCandidates[i].Position) of mcOneDifference: if FVerticalMirrorCandidates[i].IsFixAllowed then SetMirrorCandidateFixed(FVerticalMirrorCandidates, i) else FVerticalMirrorCandidates.Delete(i); mcDifferent: FVerticalMirrorCandidates.Delete(i); end; Dec(i); end; end; procedure TPointOfIncidence.InitializeNewVerticalCandidates(const AFirstLine: string); var i: Integer; begin // Checks the line for vertical mirror candidates, i.e. mirrors that work for this line. for i := 1 to Length(AFirstLine) - 1 do case IsVerticalMirrorOfLine(AFirstLine, i) of mcSame: begin FVerticalMirrorCandidates.Add(GetMirrorCandidate(i, False, 1)); FVerticalMirrorCandidates.Add(GetMirrorCandidate(i, True, 2)); end; mcOneDifference: FVerticalMirrorCandidates.Add(GetMirrorCandidate(i, False, 2)); end; end; function TPointOfIncidence.IsVerticalMirrorOfLine(const ALine: string; const AMirror: Integer): TMirrorCompareResult; var i : Integer; begin Result := mcSame; for i := 0 to Min(AMirror - 1, Length(ALine) - AMirror - 1) do begin if ALine[AMirror - i] <> ALine[AMirror + i + 1] then begin if Result = mcSame then Result := mcOneDifference else begin Result := mcDifferent; Break; end; end; end; end; procedure TPointOfIncidence.FinishPattern; var i: Integer; begin for i := 0 to FHorizontalMirrorCandidates.Count - 1 do begin if FHorizontalMirrorCandidates[i].Part = 1 then Inc(FPart1, FHorizontalMirrorCandidates[i].Position * 100) else if not FHorizontalMirrorCandidates[i].IsFixAllowed then Inc(FPart2, FHorizontalMirrorCandidates[i].Position * 100); end; for i := 0 to FVerticalMirrorCandidates.Count - 1 do begin if FVerticalMirrorCandidates[i].Part = 1 then Inc(FPart1, FVerticalMirrorCandidates[i].Position) else if not FVerticalMirrorCandidates[i].IsFixAllowed then Inc(FPart2, FVerticalMirrorCandidates[i].Position); end; FLines.Clear; FHorizontalMirrorCandidates.Clear; FVerticalMirrorCandidates.Clear; end; function TPointOfIncidence.GetMirrorCandidate(const APosition: Integer; const AIsFixAllowed: Boolean; const APart: Integer): TMirrorCandidate; begin Result.Position := APosition; Result.IsFixAllowed := AIsFixAllowed; Result.Part := APart; end; procedure TPointOfIncidence.SetMirrorCandidateFixed(const AMirrorCandidates: TMirrorCandidates; const AIndex: Integer); var mirrorCandidate: TMirrorCandidate; begin mirrorCandidate := AMirrorCandidates[AIndex]; mirrorCandidate.IsFixAllowed := False; AMirrorCandidates[AIndex] := mirrorCandidate; end; constructor TPointOfIncidence.Create; begin FLines := TStringList.Create; FHorizontalMirrorCandidates := TMirrorCandidates.Create; FVerticalMirrorCandidates := TMirrorCandidates.Create; end; destructor TPointOfIncidence.Destroy; begin FLines.Free; FHorizontalMirrorCandidates.Free; FVerticalMirrorCandidates.Free; inherited Destroy; end; procedure TPointOfIncidence.ProcessDataLine(const ALine: string); begin if ALine <> '' then ProcessPatternLine(ALine) else FinishPattern; end; procedure TPointOfIncidence.Finish; begin FinishPattern; end; function TPointOfIncidence.GetDataFileName: string; begin Result := 'point_of_incidence.txt'; end; function TPointOfIncidence.GetPuzzleName: string; begin Result := 'Day 13: Point of Incidence'; end; end.