From 01ec0be32cc845956cd7ea1aa285e2bbd628805c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 19 Dec 2023 17:26:18 +0100 Subject: [PATCH] Added solution for "Day 16: The Floor Will Be Lava", part 2 --- solvers/UFloorWillBeLava.pas | 217 +++++++++++++++++----------- tests/UFloorWillBeLavaTestCases.pas | 12 ++ 2 files changed, 148 insertions(+), 81 deletions(-) diff --git a/solvers/UFloorWillBeLava.pas b/solvers/UFloorWillBeLava.pas index 150e5bc..bf61afe 100644 --- a/solvers/UFloorWillBeLava.pas +++ b/solvers/UFloorWillBeLava.pas @@ -42,14 +42,27 @@ type EnergyChange: TEnergyState; end; + { TEnergyMap } + + TEnergyMap = class + private + FWidth, FHeight: Integer; + FStates: array of array of TEnergyState; + public + constructor Create(const AWidth, AHeight: Integer); + function IsBeamOutOfBounds(constref ABeam: TBeam): Boolean; + function Energize(constref APosition: TPoint; const AChange: TEnergyState): Boolean; + function CalcEnergizedTiles: Int64; + end; + { TFloorWillBeLava } TFloorWillBeLava = class(TSolver) private FLines: TStringList; - function IsBeamOutOfBounds(constref ABeam: TBeam; const AWidth, AHeight: Integer): Boolean; function GetTile(constref APosition: TPoint): Char; function GetNewBeam(constref APosition, ADirection: TPoint): TBeam; + function ProcessBeam(ABeam: TBeam): Int64; public constructor Create; destructor Destroy; override; @@ -62,7 +75,6 @@ type const CNoDirection: TPoint = (X: 0; Y: 0); CEmptyChar = '.'; - CStartingBeam: TBeam = (Position: (X: 0; Y: 0); Direction: (X: 1; Y: 0)); CTransitions: array of TTransition = ( (IncomingDirection: (X: 1; Y: 0); OutgoingDirection: (X: 0; Y: -1); SplitDirection: (X: 0; Y: 0); Tile: '/'; EnergyChange: esWestOrHorizontal), @@ -92,14 +104,58 @@ const implementation -{ TFloorWillBeLava } +{ TEnergyMap } -function TFloorWillBeLava.IsBeamOutOfBounds(constref ABeam: TBeam; const AWidth, AHeight: Integer): Boolean; +constructor TEnergyMap.Create(const AWidth, AHeight: Integer); +var + i, j: Integer; begin - Result := (ABeam.Position.X < 0) or (ABeam.Position.X >= AWidth) - or (ABeam.Position.Y < 0) or (ABeam.Position.Y >= AHeight); + FWidth := AWidth; + FHeight := AHeight; + SetLength(FStates, FWidth, FHeight); + for i := 0 to FWidth - 1 do + for j := 0 to FHeight - 1 do + FStates[i, j] := esNone; end; +function TEnergyMap.IsBeamOutOfBounds(constref ABeam: TBeam): Boolean; +begin + Result := (ABeam.Position.X < 0) or (ABeam.Position.X >= FWidth) + or (ABeam.Position.Y < 0) or (ABeam.Position.Y >= FHeight); +end; + +function TEnergyMap.Energize(constref APosition: TPoint; const AChange: TEnergyState): Boolean; +begin + Result := False; + case FStates[APosition.X, APosition.Y] of + esNone: FStates[APosition.X, APosition.Y] := AChange; + esWestOrHorizontal: + if AChange = esEastOrVertical then + FStates[APosition.X, APosition.Y] := esBoth + else + Result := True; + esEastOrVertical: + if AChange = esWestOrHorizontal then + FStates[APosition.X, APosition.Y] := esBoth + else + Result := True; + esBoth: Result := True; + end; +end; + +function TEnergyMap.CalcEnergizedTiles: Int64; +var + i, j: Integer; +begin + Result := 0; + for i := 0 to FWidth - 1 do + for j := 0 to FHeight - 1 do + if FStates[i, j] <> esNone then + Inc(Result); +end; + +{ TFloorWillBeLava } + function TFloorWillBeLava.GetTile(constref APosition: TPoint): Char; begin Result := FLines[APosition.Y][APosition.X + 1]; @@ -111,6 +167,62 @@ begin Result.Direction := ADirection; end; +function TFloorWillBeLava.ProcessBeam(ABeam: TBeam): Int64; +var + done: Boolean; + energyMap: TEnergyMap; + stack: specialize TStack; + transition: TTransition; + energyChange: TEnergyState; +begin + done := False; + energyMap := TEnergyMap.Create(Length(FLines[0]), FLines.Count); + stack := specialize TStack.Create; + + repeat + // Processes the current beam. + if energyMap.IsBeamOutOfBounds(ABeam) then + done := True + else begin + if ABeam.Direction.X <> 0 then + energyChange := esWestOrHorizontal + else + energyChange := esEastOrVertical; + + if GetTile(ABeam.Position) <> CEmptyChar then + begin + // Checks the current position for direction changes and splits. + for transition in CTransitions do + if (transition.IncomingDirection = ABeam.Direction) and (transition.Tile = GetTile(ABeam.Position)) then + begin + if transition.SplitDirection <> CNoDirection then + stack.Push(GetNewBeam(ABeam.Position + transition.SplitDirection, transition.SplitDirection)); + ABeam.Direction := transition.OutgoingDirection; + energyChange := transition.EnergyChange; + Break; + end; + end; + + done := energyMap.Energize(ABeam.Position, energyChange); + + // Moves the beam. + ABeam.Position := ABeam.Position + ABeam.Direction; + end; + + if done and (stack.Count > 0) then + begin + // Starts the next beam that was split earlier. + done := False; + ABeam := stack.Pop; + end; + until done; + + stack.Free; + + Result := energyMap.CalcEnergizedTiles; + energyMap.Free; +end; + constructor TFloorWillBeLava.Create; begin FLines := TStringList.Create; @@ -129,87 +241,30 @@ end; procedure TFloorWillBeLava.Finish; var - energyMap: array of array of TEnergyState; - width, height, i, j: Integer; - done: Boolean; - stack: specialize TStack; + i, x, y, width, height: Integer; beam: TBeam; - transition: TTransition; - energyChange: TEnergyState; - s: string; + count: Int64; begin - // Initializes energy map. width := Length(FLines[0]); height := FLines.Count; - SetLength(energyMap, width, height); - for i := 0 to width - 1 do - for j := 0 to height - 1 do - energyMap[i, j] := esNone; - - // Starts beam. - done := False; - beam := CStartingBeam; - stack := specialize TStack.Create; - - repeat - // Processes the current beam. - if IsBeamOutOfBounds(beam, width, height) then - done := True - else begin - if beam.Direction.X <> 0 then - energyChange := esWestOrHorizontal - else - energyChange := esEastOrVertical; - - if GetTile(beam.Position) <> CEmptyChar then - begin - // Checks the current position for direction changes and splits. - for transition in CTransitions do - if (transition.IncomingDirection = beam.Direction) and (transition.Tile = GetTile(beam.Position)) then - begin - if transition.SplitDirection <> CNoDirection then - stack.Push(GetNewBeam(beam.Position + transition.SplitDirection, transition.SplitDirection)); - beam.Direction := transition.OutgoingDirection; - energyChange := transition.EnergyChange; - Break; - end; - end; - - // Energizes the current position. - case energyMap[beam.Position.X, beam.Position.Y] of - esNone: energyMap[beam.Position.X, beam.Position.Y] := energyChange; - esWestOrHorizontal: - if energyChange = esEastOrVertical then - energyMap[beam.Position.X, beam.Position.Y] := esBoth - else - done := True; - esEastOrVertical: - if energyChange = esWestOrHorizontal then - energyMap[beam.Position.X, beam.Position.Y] := esBoth - else - done := True; - esBoth: done := True; - end; - - // Moves the beam. - beam.Position := beam.Position + beam.Direction; - end; - - if done and (stack.Count > 0) then + for y := 0 to 1 do + for x := 0 to 1 do begin - // Starts the next beam that was split earlier. - done := False; - beam := stack.Pop; + // Direction is horizontal for y = 0, and vertical for y = 1. + // Direction is positive for x = 0, and negative for x = 1. + beam.Direction := Point((1 - 2 * x) * (1 - y), (1 - 2 * x) * y); + // Looping over the height for y = 0, and over the width for y = 1. + for i := 0 to height - 1 + (width - height) * y do + begin + // Position is at left or top for x = 0, and right or bottom for x = 1. + beam.Position := Point((x * (width - 1)) * (1 - y) + i * y, (x * (height - 1) - i) * y + i); + count := ProcessBeam(beam); + if FPart1 <= 0 then + FPart1 := count; + if FPart2 < count then + FPart2 := count; + end; end; - until done; - - stack.Free; - - // Counts energized tiles. - for i := 0 to width - 1 do - for j := 0 to height - 1 do - if energyMap[i, j] <> esNone then - Inc(FPart1); end; function TFloorWillBeLava.GetDataFileName: string; diff --git a/tests/UFloorWillBeLavaTestCases.pas b/tests/UFloorWillBeLavaTestCases.pas index 8cb8bed..be19272 100644 --- a/tests/UFloorWillBeLavaTestCases.pas +++ b/tests/UFloorWillBeLavaTestCases.pas @@ -33,6 +33,7 @@ type function CreateSolver: ISolver; override; published procedure TestPart1; + procedure TestPart2; end; { TFloorWillBeLavaExampleTestCase } @@ -42,6 +43,7 @@ type function CreateSolver: ISolver; override; published procedure TestPart1; + procedure TestPart2; end; implementation @@ -58,6 +60,11 @@ begin AssertEquals(7392, FSolver.GetResultPart1); end; +procedure TFloorWillBeLavaFullDataTestCase.TestPart2; +begin + AssertEquals(7665, FSolver.GetResultPart2); +end; + { TFloorWillBeLavaExampleTestCase } function TFloorWillBeLavaExampleTestCase.CreateSolver: ISolver; @@ -70,6 +77,11 @@ begin AssertEquals(46, FSolver.GetResultPart1); end; +procedure TFloorWillBeLavaExampleTestCase.TestPart2; +begin + AssertEquals(51, FSolver.GetResultPart2); +end; + initialization RegisterTest(TFloorWillBeLavaFullDataTestCase);