291 lines
9.9 KiB
Plaintext
291 lines
9.9 KiB
Plaintext
{
|
|
Solutions to the Advent Of Code.
|
|
Copyright (C) 2023-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 UStepCounter;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, USolver, UCommon;
|
|
|
|
type
|
|
|
|
{ TStepCounter }
|
|
|
|
TStepCounter = class(TSolver)
|
|
private
|
|
FLines: TStringList;
|
|
FWidth, FHeight, FMaxSteps1, FMaxSteps2: Integer;
|
|
function FindStart: TPoint;
|
|
function IsInBounds(constref APoint: TPoint): Boolean;
|
|
function GetPosition(constref APoint: TPoint): Char;
|
|
procedure SetPosition(constref APoint: TPoint; const AValue: Char);
|
|
procedure PrepareMap;
|
|
function DoSteps(const AMaxSteps: Integer): Int64;
|
|
function CalcTargetPlotsOnInfiniteMap(const AMaxSteps: Integer): Int64;
|
|
public
|
|
constructor Create(const AMaxStepsPart1: Integer = 64; const AMaxStepsPart2: Integer = 26501365);
|
|
destructor Destroy; override;
|
|
procedure ProcessDataLine(const ALine: string); override;
|
|
procedure Finish; override;
|
|
function GetDataFileName: string; override;
|
|
function GetPuzzleName: string; override;
|
|
end;
|
|
|
|
const
|
|
CStartChar = 'S';
|
|
CPlotChar = '.';
|
|
CRockChar = '#';
|
|
CTraversedChar = '+';
|
|
|
|
implementation
|
|
|
|
{ TStepCounter }
|
|
|
|
function TStepCounter.FindStart: TPoint;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
for i := 1 to FWidth do
|
|
for j := 0 to FHeight - 1 do
|
|
if FLines[j][i] = CStartChar then
|
|
begin
|
|
Result.X := i;
|
|
Result.Y := j;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TStepCounter.IsInBounds(constref APoint: TPoint): Boolean;
|
|
begin
|
|
Result := (0 < APoint.X) and (APoint.X <= FWidth) and (0 <= APoint.Y) and (APoint.Y < FHeight);
|
|
end;
|
|
|
|
function TStepCounter.GetPosition(constref APoint: TPoint): Char;
|
|
begin
|
|
Result := FLines[APoint.Y][APoint.X];
|
|
end;
|
|
|
|
procedure TStepCounter.SetPosition(constref APoint: TPoint; const AValue: Char);
|
|
var
|
|
s: string;
|
|
begin
|
|
s := FLines[APoint.Y];
|
|
s[APoint.X] := AValue;
|
|
FLines[APoint.Y] := s;
|
|
end;
|
|
|
|
procedure TStepCounter.PrepareMap;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
for i := 2 to FWidth - 1 do
|
|
for j := 1 to FHeight - 2 do
|
|
if (FLines[j][i] <> CRockChar) and (FLines[j - 1][i] = CRockChar) and (FLines[j + 1][i] = CRockChar)
|
|
and (FLines[j][i - 1] = CRockChar) and (FLines[j][i + 1] = CRockChar) then
|
|
SetPosition(Point(i, j), CRockChar);
|
|
end;
|
|
|
|
function TStepCounter.DoSteps(const AMaxSteps: Integer): Int64;
|
|
var
|
|
mod2, currentStep: Integer;
|
|
currentPlots, nextPlots, temp: TPoints;
|
|
plot, next: TPoint;
|
|
pdirection: PPoint;
|
|
begin
|
|
currentStep := 0;
|
|
currentPlots := TPoints.Create;
|
|
currentPlots.Add(FindStart);
|
|
nextPlots := TPoints.Create;
|
|
|
|
// Counts the start if max steps is even.
|
|
mod2 := AMaxSteps and 1;
|
|
if mod2 = 0 then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
|
|
while currentStep < AMaxSteps do
|
|
begin
|
|
for plot in currentPlots do
|
|
for pdirection in CPCardinalDirections do
|
|
begin
|
|
next := plot + pdirection^;
|
|
if IsInBounds(next) and (GetPosition(next) = CPlotChar) then
|
|
begin
|
|
SetPosition(next, CTraversedChar);
|
|
nextPlots.Add(next);
|
|
end;
|
|
end;
|
|
|
|
currentPlots.Clear;
|
|
temp := currentPlots;
|
|
currentPlots := nextPlots;
|
|
nextPlots := temp;
|
|
Inc(currentStep);
|
|
|
|
// Positions where the number of steps are even or odd (for even or odd AMaxSteps, respectively) can be reached with
|
|
// trivial backtracking, so they count.
|
|
if currentStep and 1 = mod2 then
|
|
Inc(Result, currentPlots.Count);
|
|
end;
|
|
|
|
currentPlots.Free;
|
|
nextPlots.Free;
|
|
end;
|
|
|
|
function TStepCounter.CalcTargetPlotsOnInfiniteMap(const AMaxSteps: Integer): Int64;
|
|
var
|
|
half, k, i, j: Integer;
|
|
factor1, factor1B, factor2, factor4A: Int64;
|
|
begin
|
|
Result := 0;
|
|
|
|
// Asserts square input map with odd size.
|
|
if (FWidth <> FHeight) or (FWidth and 1 = 0) then
|
|
Exit;
|
|
// Asserts half map size is odd.
|
|
half := FWidth shr 1;
|
|
if half and 1 = 0 then
|
|
Exit;
|
|
// Asserts that there is an even k such that maximum number of steps is equal to k + 1/2 times the map size.
|
|
// k is the number of visited repeated maps, not counting the start map, when taking all steps in a straight line in
|
|
// any of the four directions.
|
|
k := (AMaxSteps - half) div FWidth;
|
|
if (k and 1 = 0) and (AMaxSteps <> k * FWidth + half) then
|
|
Exit;
|
|
|
|
// Assuming that the rocks on the map are sparse enough, and the central vertical and horizontal lines are empty,
|
|
// every free plot with odd (Manhattan) distance (not larger than AMaxSteps) to the start plot (because of trivial
|
|
// backtracking) on the maps is reachable, essentially formning a 45-degree rotated square shape centered on the start
|
|
// plot.
|
|
|
|
// Inside this "diamond" shape, 2k(k - 1) + 1 (k-th centered square number) copies of the map are traversed fully.
|
|
// However, there are two different types of these. (k - 1)^2 are traversed like the start map, where all plots with
|
|
// odd distance to the center are reachable (type 1), and k^2 are traversed such that all plots within odd distance to
|
|
// the center are reachable (type 2).
|
|
|
|
// On each of the corners of this "diamond" shape, there is one map traversed fully except for two adjacent of its
|
|
// corner triangles (type 3).
|
|
|
|
// On each of the edges of this "diamond" shape, there are k maps where only the corner triangle facing towards the
|
|
// shapes center is traversed (type 4), and k - 1 maps that are fully traversed except for the corner triangle facing
|
|
// away from the shapes center (type 5).
|
|
|
|
// The four different versions of type 4 do not overlap within a map, so they can be counted together (type 4A).
|
|
|
|
// Types 1, 3, and 5 share patterns, so they can also be counted together, but the parts of the patterns have
|
|
// different counts. Each corner (type 1A) is traversed (k - 1)^2 times for type 1, 2 times for type 3, and 3(k - 1)
|
|
// for type 5, that is (k - 1)^2 + 3k - 1 in total. The center (type 1B) is traversed (k - 1)^2 times for type 1, 4
|
|
// times for type 3, and 4(k - 1) for type 5, that is (k - 1)^2 + 4k.
|
|
// Equivalently, instead type 1 is traversed (k - 1)^2 + 3k - 1 times and type 1B is traversed k + 1 times.
|
|
|
|
// Types example for k = 2, half = 5:
|
|
// 4 5 2 4A
|
|
// ........... .....O.O.O. O.O.O.O.O.O O.O.O.O.O.O
|
|
// ........... ....O.O.O.O .O.O.O.O.O. .O.O...O.O.
|
|
// ........... ...O.O.O.O. O.O.O.O.O.O O.O.....O.O
|
|
// ......#.... ..O.O.#.O.O .O.O.O#O.O. .O....#..O.
|
|
// ...#....... .O.#.O.O.O. O.O#O.O.O.O O..#......O
|
|
// ........... O.O.O.O.O.O .O.O.O.O.O. ...........
|
|
// ....#..#..O .O.O#O.#.O. O.O.#.O.#.O O...#..#..O
|
|
// .........O. O.O.O.O.O.O .O.O.O.O.O. .O.......O.
|
|
// ........O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O
|
|
// .......O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O.
|
|
// ......O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.O.O.O.O
|
|
//
|
|
// 3 2 1 1A 1B
|
|
// .....O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O. .....O.....
|
|
// ....O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O ....O.O....
|
|
// ...O.O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.......O. ...O.O.O...
|
|
// ..O.O.#.O.O .O.O.O#O.O. O.O.O.#.O.O O.....#...O ..O.O.#.O..
|
|
// .O.#.O.O.O. O.O#O.O.O.O .O.#.O.O.O. ...#....... .O.#.O.O.O.
|
|
// O.O.O.O.O.O .O.O.O.O.O. O.O.OSO.O.O ........... O.O.O.O.O.O
|
|
// .O.O#O.#.O. O.O.#.O.#.O .O.O#O.#.O. ....#..#... .O.O#O.#.O.
|
|
// ..O.O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.........O ..O.O.O.O..
|
|
// ...O.O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.......O. ...O.O.O...
|
|
// ....O.O.O.O .O.O.O.O.O. O.O.O.O.O.O O.O.....O.O ....O.O....
|
|
// .....O.O.O. O.O.O.O.O.O .O.O.O.O.O. .O.O...O.O. .....O.....
|
|
|
|
// Sets factors, aka number of occurrences, for each type.
|
|
factor1 := (k - 1) * (k - 1) + 3 * k - 1;
|
|
factor1B := k + 1;
|
|
factor2 := k * k;
|
|
factor4A := k;
|
|
for i := 0 to FWidth - 1 do
|
|
for j := 1 to FWidth do
|
|
if FLines[i][j] <> CRockChar then
|
|
if (i and 1) = (j and 1) then
|
|
begin
|
|
// Counts types 1.
|
|
Result := Result + factor1;
|
|
// Counts types 1B.
|
|
if not ((i + j <= half) or (i + j > FWidth + half) or (i - j >= half) or (j - i > half + 1)) then
|
|
Result := Result + factor1B;
|
|
end
|
|
else begin
|
|
// Counts types 2.
|
|
Result := Result + factor2;
|
|
// Counts types 4A.
|
|
if (i + j <= half) or (i + j > FWidth + half) or (i - j >= half) or (j - i > half + 1) then
|
|
Result := Result + factor4A;
|
|
end
|
|
end;
|
|
|
|
constructor TStepCounter.Create(const AMaxStepsPart1: Integer; const AMaxStepsPart2: Integer);
|
|
begin
|
|
FMaxSteps1 := AMaxStepsPart1;
|
|
FMaxSteps2 := AMaxStepsPart2;
|
|
FLines := TStringList.Create;
|
|
end;
|
|
|
|
destructor TStepCounter.Destroy;
|
|
begin
|
|
FLines.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TStepCounter.ProcessDataLine(const ALine: string);
|
|
begin
|
|
FLines.Add(ALine);
|
|
end;
|
|
|
|
procedure TStepCounter.Finish;
|
|
begin
|
|
FWidth := Length(FLines[0]);
|
|
FHeight := FLines.Count;
|
|
PrepareMap;
|
|
|
|
FPart2 := CalcTargetPlotsOnInfiniteMap(FMaxSteps2);
|
|
FPart1 := DoSteps(FMaxSteps1);
|
|
end;
|
|
|
|
function TStepCounter.GetDataFileName: string;
|
|
begin
|
|
Result := 'step_counter.txt';
|
|
end;
|
|
|
|
function TStepCounter.GetPuzzleName: string;
|
|
begin
|
|
Result := 'Day 21: Step Counter';
|
|
end;
|
|
|
|
end.
|
|
|