diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi
index 646efc6..0870890 100644
--- a/AdventOfCode.lpi
+++ b/AdventOfCode.lpi
@@ -41,6 +41,10 @@
         
         
       
+      
+        
+        
+      
     
   
   
diff --git a/AdventOfCode.lpr b/AdventOfCode.lpr
index c5c3607..83e2617 100644
--- a/AdventOfCode.lpr
+++ b/AdventOfCode.lpr
@@ -25,7 +25,7 @@ uses
   {$ENDIF}
   Classes, SysUtils, CustApp,
   USolver,
-  UTrebuchet, UCubeConundrum;
+  UTrebuchet, UCubeConundrum, UGearRatios;
 
 type
 
@@ -52,6 +52,7 @@ begin
   TTrebuchet.Run;
   TCubeConundrum.Run;
   engine := TSolverEngine.Create;
+  engine.RunAndFree(TGearRatios.Create);
   engine.Free;
 end;
 
diff --git a/USolver.pas b/USolver.pas
index a5e0de9..43b165c 100644
--- a/USolver.pas
+++ b/USolver.pas
@@ -53,7 +53,6 @@ type
     procedure Init; virtual;
     procedure ProcessDataLine(const ALine: string); virtual; abstract;
     procedure Finish; virtual; abstract;
-    procedure Free; virtual;
     function GetDataFileName: string; virtual; abstract;
     function GetPuzzleName: string; virtual; abstract;
     function GetResultPart1: Integer; virtual;
@@ -80,11 +79,6 @@ begin
   FPart2 := 0;
 end;
 
-procedure TSolver.Free;
-begin
-  Free;
-end;
-
 function TSolver.GetResultPart1: Integer;
 begin
   Result := FPart1;
diff --git a/solvers/UGearRatios.pas b/solvers/UGearRatios.pas
new file mode 100644
index 0000000..d93548f
--- /dev/null
+++ b/solvers/UGearRatios.pas
@@ -0,0 +1,147 @@
+{
+  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 UGearRatios;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, USolver;
+
+const
+  CDigitChars = ['0'..'9'];
+  CNonSymbolChars = ['0'..'9', '.'];
+
+type
+
+  { TGearRatios }
+
+  TGearRatios = class(TSolver)
+  private
+    FPreviousLine, FLine: string;
+    procedure ProcessDataLineTriplet(const APreviousLine, ALine, ANextLine: string);
+  public
+    procedure ProcessDataLine(const ALine: string); override;
+    procedure Finish; override;
+    function GetDataFileName: string; override;
+    function GetPuzzleName: string; override;
+  end;
+
+implementation
+
+{ TGearRatios }
+
+procedure TGearRatios.ProcessDataLineTriplet(const APreviousLine, ALine, ANextLine: string);
+var
+  i, numberStart, numberLength, partNumber: Integer;
+  inNumber, isPartNumber: Boolean;
+begin
+  inNumber := False;
+
+  for i := 1 to ALine.Length do
+  begin
+    // Checks if number starts.
+    if not inNumber and (ALine[i] in CDigitChars) then
+    begin
+      inNumber := True;
+      numberStart := i;
+
+      // Checks for a symbol in the column before the first digit.
+      isPartNumber := (i > 1) and
+        (not (APreviousLine[i - 1] in CNonSymbolChars)
+        or not (ALine[i - 1] in CNonSymbolChars)
+        or not (ANextLine[i - 1] in CNonSymbolChars));
+    end;
+
+    // Checks for a symbol in the column of the current digit.
+    if inNumber and not isPartNumber and
+      (not (APreviousLine[i] in CNonSymbolChars)
+      or not (ANextLine[i] in CNonSymbolChars)) then
+    begin
+      isPartNumber := True;
+    end;
+
+    // Checks if number ends.
+    if inNumber and (not (ALine[i] in CDigitChars) or (i = ALine.Length)) then
+    begin
+      inNumber := False;
+
+      // Checks for a symbol in the column after the last digit.
+      if not (APreviousLine[i] in CNonSymbolChars)
+        or not (ALine[i] in CNonSymbolChars)
+        or not (ANextLine[i] in CNonSymbolChars) then
+      begin
+        isPartNumber := True;
+      end;
+
+      // Counts if it is a part number.
+      if isPartNumber then
+      begin
+        numberLength := i - numberStart;
+        if ALine[i] in CDigitChars then
+          Inc(numberLength);
+        partNumber := StrToInt(Copy(ALine, numberStart, numberLength));
+        Inc(FPart1, partNumber);
+      end;
+    end;
+  end;
+end;
+
+procedure TGearRatios.ProcessDataLine(const ALine: string);
+begin
+  if not (FLine = '') then
+  begin
+    // Processes lines 2 to n - 1 in calls for lines 3 to n.
+    ProcessDataLineTriplet(FPreviousLine, FLine, ALine);
+    FPreviousLine := FLine;
+    FLine := ALine;
+  end
+  else if not (FPreviousLine = '') then
+  begin
+    // Processes line 1 in call for line 2.
+    FLine := ALine;
+    // Duplicates the first line for the algorithm because it does not influence the result.
+    ProcessDataLineTriplet(FPreviousLine, FPreviousLine, FLine);
+  end
+  else
+    // Processes nothing in call for line 1.
+    FPreviousLine := ALine;
+end;
+
+procedure TGearRatios.Finish;
+begin
+  // Processes line n.
+  // Duplicates the last line for the algorithm because it does not influence the result.
+  if FLine = '' then
+    FLine := FPreviousLine;
+  ProcessDataLineTriplet(FPreviousLine, FLine, FLine);
+end;
+
+function TGearRatios.GetDataFileName: string;
+begin
+  Result := 'gear_ratios.txt';
+end;
+
+function TGearRatios.GetPuzzleName: string;
+begin
+  Result := 'Day 3: Gear Ratios';
+end;
+
+end.
+
diff --git a/tests/AdventOfCodeFPCUnit.fpcunit.ini b/tests/AdventOfCodeFPCUnit.fpcunit.ini
new file mode 100644
index 0000000..b64eb5d
--- /dev/null
+++ b/tests/AdventOfCodeFPCUnit.fpcunit.ini
@@ -0,0 +1,15 @@
+[WindowState]
+Left=664
+Top=189
+Width=575
+Height=663
+
+[Tests]
+All Tests.Checked=1
+All Tests.Expanded=1
+TGearRatiosTestCase.Checked=1
+TGearRatiosTestCase.Expanded=1
+TGearRatiosTestCase.TestPuzzleExample.Checked=1
+TGearRatiosTestCase.TestPuzzleExample.Expanded=0
+TGearRatiosTestCase.TestEndOfLineNumber.Checked=1
+TGearRatiosTestCase.TestEndOfLineNumber.Expanded=0
diff --git a/tests/AdventOfCodeFPCUnit.ico b/tests/AdventOfCodeFPCUnit.ico
new file mode 100644
index 0000000..10c5fc1
Binary files /dev/null and b/tests/AdventOfCodeFPCUnit.ico differ
diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi
new file mode 100644
index 0000000..1d9db93
--- /dev/null
+++ b/tests/AdventOfCodeFPCUnit.lpi
@@ -0,0 +1,84 @@
+
+
+  
+    
+    
+    
+      
+      
+      
+      
+      
+    
+    
+       
+    
+    
+      
+      
+    
+    
+      
+    
+    
+      - 
+        
+      
 
+      - 
+        
+      
 
+      - 
+        
+      
 
+    
+    
+      
+        
+        
+      
+      
+        
+        
+      
+      
+        
+        
+      
+    
+  
+  
+    
+    
+    
+      
+    
+    
+      
+      
+      
+    
+    
+      
+        
+      
+      
+        
+          
+        
+      
+    
+  
+  
+    
+      - 
+        
+      
 
+      - 
+        
+      
 
+      - 
+        
+      
 
+    
+  
+
diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr
new file mode 100644
index 0000000..6ba23fb
--- /dev/null
+++ b/tests/AdventOfCodeFPCUnit.lpr
@@ -0,0 +1,15 @@
+program AdventOfCodeFPCUnit;
+
+{$mode objfpc}{$H+}
+
+uses
+  Interfaces, Forms, GuiTestRunner, UGearRatiosTestCases, USolver;
+
+{$R *.res}
+
+begin
+  Application.Initialize;
+  Application.CreateForm(TGuiTestRunner, TestRunner);
+  Application.Run;
+end.
+
diff --git a/tests/UGearRatiosTestCases.pas b/tests/UGearRatiosTestCases.pas
new file mode 100644
index 0000000..54d1036
--- /dev/null
+++ b/tests/UGearRatiosTestCases.pas
@@ -0,0 +1,83 @@
+{
+  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 UGearRatiosTestCases;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, UGearRatios;
+
+type
+
+  { TGearRatiosTestCase }
+
+  TGearRatiosTestCase = class(TTestCase)
+  protected
+    FSolver: TGearRatios;
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestPuzzleExample;
+    procedure TestEndOfLineNumber;
+  end;
+
+implementation
+
+{ TGearRatiosTestCase }
+
+procedure TGearRatiosTestCase.Setup;
+begin
+  FSolver := TGearRatios.Create;
+end;
+
+procedure TGearRatiosTestCase.TearDown;
+begin
+  FSolver.Free;
+end;
+
+procedure TGearRatiosTestCase.TestPuzzleExample;
+begin
+  FSolver.Init;
+  FSolver.ProcessDataLine('467..114..');
+  FSolver.ProcessDataLine('...*......');
+  FSolver.ProcessDataLine('..35..633.');
+  FSolver.ProcessDataLine('......#...');
+  FSolver.ProcessDataLine('617*......');
+  FSolver.ProcessDataLine('.....+.58.');
+  FSolver.ProcessDataLine('..592.....');
+  FSolver.ProcessDataLine('......755.');
+  FSolver.ProcessDataLine('...$.*....');
+  FSolver.ProcessDataLine('.664.598..');
+  FSolver.Finish;
+  AssertEquals('Result of part 1 calculation incorrect.', 4361, FSolver.GetResultPart1);
+end;
+
+procedure TGearRatiosTestCase.TestEndOfLineNumber;
+begin
+  FSolver.Init;
+  FSolver.ProcessDataLine('...$541');
+  FSolver.Finish;
+  AssertEquals('Result of part 1 calculation incorrect.', 541, FSolver.GetResultPart1);
+end;
+
+initialization
+
+  RegisterTest(TGearRatiosTestCase);
+end.