diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi
index eba98fb..14849ab 100644
--- a/AdventOfCode.lpi
+++ b/AdventOfCode.lpi
@@ -117,6 +117,10 @@
+
+
+
+
diff --git a/AdventOfCode.lpr b/AdventOfCode.lpr
index 7d79baa..753b82f 100644
--- a/AdventOfCode.lpr
+++ b/AdventOfCode.lpr
@@ -26,7 +26,7 @@ uses
Classes, SysUtils, CustApp, USolver, UTrebuchet, UCubeConundrum, UGearRatios, UScratchcards, UGiveSeedFertilizer,
UWaitForIt, UCamelCards, UHauntedWasteland, UNumberTheory, UMirageMaintenance, UPipeMaze, UCosmicExpansion,
UHotSprings, UPointOfIncidence, UParabolicReflectorDish, ULensLibrary, UFloorWillBeLava, UClumsyCrucible,
- ULavaductLagoon, UAplenty;
+ ULavaductLagoon, UAplenty, UPulsePropagation;
type
@@ -70,6 +70,7 @@ begin
engine.RunAndFree(TClumsyCrucible.Create);
engine.RunAndFree(TLavaductLagoon.Create);
engine.RunAndFree(TAplenty.Create);
+ engine.RunAndFree(TPulsePropagation.Create);
engine.Free;
end;
diff --git a/solvers/UPulsePropagation.pas b/solvers/UPulsePropagation.pas
new file mode 100644
index 0000000..2bfda65
--- /dev/null
+++ b/solvers/UPulsePropagation.pas
@@ -0,0 +1,444 @@
+{
+ 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 UPulsePropagation;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Generics.Collections, Math, USolver;
+
+type
+ TModule = class;
+ TModules = specialize TObjectList;
+
+ { TPulse }
+
+ TPulse = record
+ Sender, Destination: TModule;
+ IsHigh: Boolean;
+ end;
+
+ TPulses = specialize TList;
+ TPulseQueue = specialize TQueue;
+
+ { TModule }
+
+ TModule = class
+ private
+ FName: string;
+ FOutputNames: TStringList;
+ FOutputs: TModules;
+ function CreatePulsesToOutputs(const AHighPulse: Boolean): TPulses;
+ public
+ property Name: string read FName;
+ property OutputNames: TStringList read FOutputNames;
+ constructor Create(const AName: string);
+ destructor Destroy; override;
+ procedure AddInput(const AInput: TModule); virtual;
+ procedure AddOutput(const AOutput: TModule); virtual;
+ function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; virtual; abstract;
+ function IsOff: Boolean; virtual;
+ end;
+
+ { TBroadcasterModule }
+
+ TBroadcasterModule = class(TModule)
+ public
+ function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
+ end;
+
+ { TFlipFlopModule }
+
+ TFlipFlopModule = class(TModule)
+ private
+ FState: Boolean;
+ public
+ function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
+ function IsOff: Boolean; override;
+ end;
+
+ { TConjectionBuffer }
+
+ TConjectionBuffer = record
+ Input: TModule;
+ LastState: Boolean;
+ end;
+
+ TConjectionBuffers = specialize TList;
+
+ { TConjunctionModule }
+
+ TConjunctionModule = class(TModule)
+ private
+ FInputBuffers: TConjectionBuffers;
+ procedure UpdateInputBuffer(constref AInput: TModule; const AState: Boolean);
+ function AreAllBuffersSame(const AIsHigh: Boolean): Boolean;
+ public
+ constructor Create(const AName: string);
+ destructor Destroy; override;
+ procedure AddInput(const AInput: TModule); override;
+ function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
+ function IsOff: Boolean; override;
+ end;
+
+ { TEndpointModule }
+
+ TEndpointModule = class(TModule)
+ public
+ function ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses; override;
+ end;
+
+ { TButtonResult }
+
+ TButtonResult = record
+ LowCount, HighCount: Integer;
+ end;
+
+ TButtonResults = specialize TList;
+
+ { TPulsePropagation }
+
+ TPulsePropagation = class(TSolver)
+ private
+ FModules: TModules;
+ FBroadcaster: TModule;
+ procedure UpdateModuleConnections;
+ function PushButton: TButtonResult;
+ function AreAllModulesOff: Boolean;
+ public
+ constructor Create;
+ destructor Destroy; override;
+ procedure ProcessDataLine(const ALine: string); override;
+ procedure Finish; override;
+ function GetDataFileName: string; override;
+ function GetPuzzleName: string; override;
+ end;
+
+const
+ CBroadcasterName = 'broadcaster';
+ CFlipFlopPrefix = '%';
+ CConjunctionPrefix = '&';
+ CButtonPushes = 1000;
+
+implementation
+
+{ TModule }
+
+function TModule.CreatePulsesToOutputs(const AHighPulse: Boolean): TPulses;
+var
+ pulse: TPulse;
+ output: TModule;
+begin
+ Result := TPulses.Create;
+ pulse.Sender := Self;
+ pulse.IsHigh := AHighPulse;
+ for output in FOutputs do
+ begin
+ pulse.Destination := output;
+ Result.Add(pulse);
+ end;
+end;
+
+constructor TModule.Create(const AName: string);
+begin
+ FName := AName;
+ FOutputNames := TStringList.Create;
+ FOutputs := TModules.Create(False);
+end;
+
+destructor TModule.Destroy;
+begin
+ FOutputNames.Free;
+ FOutputs.Free;
+ inherited Destroy;
+end;
+
+procedure TModule.AddInput(const AInput: TModule);
+begin
+
+end;
+
+procedure TModule.AddOutput(const AOutput: TModule);
+begin
+ FOutputs.Add(AOutput);
+end;
+
+function TModule.IsOff: Boolean;
+begin
+ Result := True;
+end;
+
+{ TBroadcasterModule }
+
+function TBroadcasterModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
+begin
+ Result := CreatePulsesToOutputs(AIsHigh);
+end;
+
+{ TFlipFlopModule }
+
+function TFlipFlopModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
+begin
+ if AIsHigh then
+ Result := TPulses.Create
+ else begin
+ FState := not FState;
+ Result := CreatePulsesToOutputs(FState);
+ end;
+end;
+
+function TFlipFlopModule.IsOff: Boolean;
+begin
+ Result := not FState;
+end;
+
+{ TConjunctionModule }
+
+procedure TConjunctionModule.UpdateInputBuffer(constref AInput: TModule; const AState: Boolean);
+var
+ i: Integer;
+ buffer: TConjectionBuffer;
+begin
+ for i := 0 to FInputBuffers.Count - 1 do
+ if FInputBuffers[i].Input = AInput then
+ begin
+ buffer := FInputBuffers[i];
+ buffer.LastState := AState;
+ FInputBuffers[i] := buffer;
+ Exit;
+ end;
+end;
+
+function TConjunctionModule.AreAllBuffersSame(const AIsHigh: Boolean): Boolean;
+var
+ buffer: TConjectionBuffer;
+begin
+ Result := True;
+ for buffer in FInputBuffers do
+ if buffer.LastState <> AIsHigh then
+ begin
+ Result := False;
+ Exit;
+ end;
+end;
+
+constructor TConjunctionModule.Create(const AName: string);
+begin
+ inherited Create(AName);
+ FInputBuffers := TConjectionBuffers.Create;
+end;
+
+destructor TConjunctionModule.Destroy;
+begin
+ FInputBuffers.Free;
+ inherited Destroy;
+end;
+
+procedure TConjunctionModule.AddInput(const AInput: TModule);
+var
+ buffer: TConjectionBuffer;
+begin
+ buffer.Input := AInput;
+ buffer.LastState := False;
+ FInputBuffers.Add(buffer);
+end;
+
+function TConjunctionModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
+begin
+ UpdateInputBuffer(ASender, AIsHigh);
+ Result := CreatePulsesToOutputs(not AreAllBuffersSame(True));
+end;
+
+function TConjunctionModule.IsOff: Boolean;
+begin
+ Result := AreAllBuffersSame(False);
+end;
+
+{ TEndpointModule }
+
+function TEndpointModule.ReceivePulse(const ASender: TModule; const AIsHigh: Boolean): TPulses;
+begin
+ Result := TPulses.Create;
+end;
+
+{ TPulsePropagation }
+
+procedure TPulsePropagation.UpdateModuleConnections;
+var
+ module, outModule: TModule;
+ name: string;
+ found: Boolean;
+begin
+ for module in FModules do
+ begin
+ for name in module.OutputNames do
+ begin
+ found := False;
+ for outModule in FModules do
+ if name = outModule.Name then
+ begin
+ found := True;
+ Break;
+ end;
+
+ if not found then
+ begin
+ outModule := TEndpointModule.Create(name);
+ FModules.Add(outModule);
+ end;
+
+ module.AddOutput(outModule);
+ outModule.AddInput(module);
+ end;
+
+ module.OutputNames.Clear;
+ end;
+end;
+
+function TPulsePropagation.PushButton: TButtonResult;
+var
+ queue: TPulseQueue;
+ pulse: TPulse;
+ pulses: TPulses;
+begin
+ queue := TPulseQueue.Create;
+ pulse.Sender := nil;
+ pulse.IsHigh := False;
+ pulse.Destination := FBroadcaster;
+ queue.Enqueue(pulse);
+ Result.LowCount := 0;
+ Result.HighCount := 0;
+
+ while queue.Count > 0 do
+ begin
+ pulse := queue.Dequeue;
+ if pulse.IsHigh then
+ Inc(Result.HighCount)
+ else
+ Inc(Result.LowCount);
+ pulses := pulse.Destination.ReceivePulse(pulse.Sender, pulse.IsHigh);
+ for pulse in pulses do
+ queue.Enqueue(pulse);
+ pulses.Free;
+ end;
+
+ queue.Free;
+end;
+
+function TPulsePropagation.AreAllModulesOff: Boolean;
+var
+ module: TModule;
+begin
+ Result := True;
+ for module in FModules do
+ if not module.IsOff then
+ begin
+ Result := False;
+ Exit;
+ end;
+end;
+
+constructor TPulsePropagation.Create;
+begin
+ FModules := TModules.Create;
+end;
+
+destructor TPulsePropagation.Destroy;
+begin
+ FModules.Free;
+ inherited Destroy;
+end;
+
+procedure TPulsePropagation.ProcessDataLine(const ALine: string);
+var
+ split: TStringArray;
+ module: TModule;
+ i: Integer;
+begin
+ split := ALine.Split(' ');
+ if split[0] = CBroadcasterName then
+ begin
+ FBroadcaster := TBroadcasterModule.Create(split[0]);
+ module := FBroadcaster;
+ end
+ else if split[0][1] = CFlipFlopPrefix then
+ module := TFlipFlopModule.Create(Copy(split[0], 2, Length(split[0]) - 1))
+ else if split[0][1] = CConjunctionPrefix then
+ module := TConjunctionModule.Create(Copy(split[0], 2, Length(split[0]) - 1));
+
+ for i := 2 to Length(split) - 2 do
+ module.OutputNames.Add(Copy(split[i], 1, Length(split[i]) - 1));
+ module.OutputNames.Add(split[Length(split) - 1]);
+
+ FModules.Add(module);
+end;
+
+procedure TPulsePropagation.Finish;
+var
+ results: TButtonResults;
+ finalResult: TButtonResult;
+ cycles, remainder, i, j, max: Integer;
+begin
+ UpdateModuleConnections;
+
+ // The pulse counts for the full puzzle input repeat themselves in a very specific way, but the system state does not.
+ // This indicates there is a better solution for this problem.
+ // TODO: See if there is a better solution based on the repeating patterns in the pulse counts.
+ results := TButtonResults.Create;
+ repeat
+ results.Add(PushButton);
+ until AreAllModulesOff or (results.Count >= CButtonPushes);
+
+ DivMod(CButtonPushes, results.Count, cycles, remainder);
+ finalResult.LowCount := 0;
+ finalResult.HighCount := 0;
+ max := results.Count - 1;
+ for j := 0 to 1 do
+ begin
+ for i := 0 to max do
+ begin
+ Inc(finalResult.LowCount, results[i].LowCount);
+ Inc(finalResult.HighCount, results[i].HighCount);
+ end;
+ if j = 0 then
+ begin
+ finalResult.LowCount := finalResult.LowCount * cycles;
+ finalResult.HighCount := finalResult.HighCount * cycles;
+ max := remainder - 1;
+ end;
+ end;
+
+ results.Free;
+
+ FPart1 := finalResult.LowCount * finalResult.HighCount;
+end;
+
+function TPulsePropagation.GetDataFileName: string;
+begin
+ Result := 'pulse_propagation.txt';
+end;
+
+function TPulsePropagation.GetPuzzleName: string;
+begin
+ Result := 'Day 20: Pulse Propagation';
+end;
+
+end.
+
diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi
index b3c0eea..6a6220f 100644
--- a/tests/AdventOfCodeFPCUnit.lpi
+++ b/tests/AdventOfCodeFPCUnit.lpi
@@ -120,6 +120,10 @@
+
+
+
+
diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr
index 0f1a515..58d65a8 100644
--- a/tests/AdventOfCodeFPCUnit.lpr
+++ b/tests/AdventOfCodeFPCUnit.lpr
@@ -7,7 +7,8 @@ uses
UGearRatiosTestCases, UScratchcardsTestCases, UGiveSeedFertilizerTestCases, UWaitForItTestCases, UCamelCardsTestCases,
UHauntedWastelandTestCases, UMirageMaintenanceTestCases, UPipeMazeTestCases, UCosmicExpansionTestCases,
UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases,
- UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases;
+ UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
+UPulsePropagationTestCases;
{$R *.res}
diff --git a/tests/UPulsePropagationTestCases.pas b/tests/UPulsePropagationTestCases.pas
new file mode 100644
index 0000000..c5e4503
--- /dev/null
+++ b/tests/UPulsePropagationTestCases.pas
@@ -0,0 +1,119 @@
+{
+ 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 UPulsePropagationTestCases;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, fpcunit, testregistry, USolver, UBaseTestCases, UPulsePropagation;
+
+type
+
+ { TPulsePropagationFullDataTestCase }
+
+ TPulsePropagationFullDataTestCase = class(TEngineBaseTest)
+ protected
+ function CreateSolver: ISolver; override;
+ published
+ procedure TestPart1;
+ procedure TestPart2;
+ end;
+
+ { TPulsePropagationExampleTestCase }
+
+ TPulsePropagationExampleTestCase = class(TExampleEngineBaseTest)
+ protected
+ function CreateSolver: ISolver; override;
+ published
+ procedure TestPart1;
+ end;
+
+ { TExample2PulsePropagation }
+
+ TExample2PulsePropagation = class(TPulsePropagation)
+ function GetDataFileName: string; override;
+ end;
+
+ { TPulsePropagationExample2TestCase }
+
+ TPulsePropagationExample2TestCase = class(TExampleEngineBaseTest)
+ protected
+ function CreateSolver: ISolver; override;
+ published
+ procedure TestPart1;
+ end;
+
+implementation
+
+{ TPulsePropagationFullDataTestCase }
+
+function TPulsePropagationFullDataTestCase.CreateSolver: ISolver;
+begin
+ Result := TPulsePropagation.Create;
+end;
+
+procedure TPulsePropagationFullDataTestCase.TestPart1;
+begin
+ AssertEquals(949764474, FSolver.GetResultPart1);
+end;
+
+procedure TPulsePropagationFullDataTestCase.TestPart2;
+begin
+ AssertEquals(-1, FSolver.GetResultPart2);
+end;
+
+{ TPulsePropagationExampleTestCase }
+
+function TPulsePropagationExampleTestCase.CreateSolver: ISolver;
+begin
+ Result := TPulsePropagation.Create;
+end;
+
+procedure TPulsePropagationExampleTestCase.TestPart1;
+begin
+ AssertEquals(32000000, FSolver.GetResultPart1);
+end;
+
+{ TExample2PulsePropagation }
+
+function TExample2PulsePropagation.GetDataFileName: string;
+begin
+ Result := 'pulse_propagation2.txt';
+end;
+
+{ TPulsePropagationExample2TestCase }
+
+function TPulsePropagationExample2TestCase.CreateSolver: ISolver;
+begin
+ Result := TExample2PulsePropagation.Create;
+end;
+
+procedure TPulsePropagationExample2TestCase.TestPart1;
+begin
+ AssertEquals(11687500, FSolver.GetResultPart1);
+end;
+
+initialization
+
+ RegisterTest(TPulsePropagationFullDataTestCase);
+ RegisterTest(TPulsePropagationExampleTestCase);
+ RegisterTest(TPulsePropagationExample2TestCase);
+end.
+