diff --git a/AdvenOfCode.Contracts/AdvenOfCode.Contracts.csproj b/AdvenOfCode.Contracts/AdvenOfCode.Contracts.csproj
new file mode 100644
index 0000000..17b910f
--- /dev/null
+++ b/AdvenOfCode.Contracts/AdvenOfCode.Contracts.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
diff --git a/AdvenOfCode.Contracts/IPuzzleSolver.cs b/AdvenOfCode.Contracts/IPuzzleSolver.cs
new file mode 100644
index 0000000..45bf322
--- /dev/null
+++ b/AdvenOfCode.Contracts/IPuzzleSolver.cs
@@ -0,0 +1,7 @@
+namespace AdvenOfCode.Contracts;
+
+public interface IPuzzleSolver
+{
+ public T SolvePart1(string puzzleInputPath);
+ public T SolvePart2(string puzzleInputPath);
+}
\ No newline at end of file
diff --git a/AdventOfCode.sln b/AdventOfCode.sln
new file mode 100644
index 0000000..1750e5c
--- /dev/null
+++ b/AdventOfCode.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AoC_2025", "AoC_2025", "{05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025", "AoC_2025\AoC_2025.csproj", "{4F395AE3-DABB-4546-A4D0-5A62425FE168}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025.Tests", "AoC_2025.Tests\AoC_2025.Tests.csproj", "{197D9628-C2D1-4B33-9DB8-134F87F79904}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025.Runner", "AoC_2025.Runner\AoC_2025.Runner.csproj", "{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvenOfCode.Contracts", "AdvenOfCode.Contracts\AdvenOfCode.Contracts.csproj", "{0249A329-D5C1-4699-88BE-A668B362432E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4F395AE3-DABB-4546-A4D0-5A62425FE168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F395AE3-DABB-4546-A4D0-5A62425FE168}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F395AE3-DABB-4546-A4D0-5A62425FE168}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F395AE3-DABB-4546-A4D0-5A62425FE168}.Release|Any CPU.Build.0 = Release|Any CPU
+ {197D9628-C2D1-4B33-9DB8-134F87F79904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {197D9628-C2D1-4B33-9DB8-134F87F79904}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {197D9628-C2D1-4B33-9DB8-134F87F79904}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {197D9628-C2D1-4B33-9DB8-134F87F79904}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0249A329-D5C1-4699-88BE-A668B362432E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0249A329-D5C1-4699-88BE-A668B362432E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0249A329-D5C1-4699-88BE-A668B362432E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0249A329-D5C1-4699-88BE-A668B362432E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {4F395AE3-DABB-4546-A4D0-5A62425FE168} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
+ {197D9628-C2D1-4B33-9DB8-134F87F79904} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
+ {FAB5E28F-706A-4DE1-B5FD-54E31C3A8405} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
+ EndGlobalSection
+EndGlobal
diff --git a/AoC_2025.Runner/AoC_2025.Runner.csproj b/AoC_2025.Runner/AoC_2025.Runner.csproj
new file mode 100644
index 0000000..375c1ed
--- /dev/null
+++ b/AoC_2025.Runner/AoC_2025.Runner.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/AoC_2025.Runner/Program.cs b/AoC_2025.Runner/Program.cs
new file mode 100644
index 0000000..1d9e432
--- /dev/null
+++ b/AoC_2025.Runner/Program.cs
@@ -0,0 +1,28 @@
+// See https://aka.ms/new-console-template for more information
+
+using System.Globalization;
+using AoC_2025;
+
+var day01 = new Day01();
+const string day01Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day01.txt";
+Console.WriteLine(day01.SolvePart1(day01Path));
+Console.WriteLine(day01.SolvePart2(day01Path));
+Console.WriteLine();
+
+var day02 = new Day02();
+const string day02Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day02.txt";
+Console.WriteLine(day02.SolvePart1(day02Path));
+Console.WriteLine(day02.SolvePart2(day02Path));
+Console.WriteLine();
+
+var day03 = new Day03();
+const string day03Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day03.txt";
+Console.WriteLine(day03.SolvePart1(day03Path));
+Console.WriteLine(day03.SolvePart2(day03Path));
+Console.WriteLine();
+
+var day04 = new Day04();
+const string day04Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day04.txt";
+Console.WriteLine(day04.SolvePart1(day04Path));
+Console.WriteLine(day04.SolvePart2(day04Path));
+Console.WriteLine();
\ No newline at end of file
diff --git a/AoC_2025.Tests/AoC_2025.Tests.csproj b/AoC_2025.Tests/AoC_2025.Tests.csproj
new file mode 100644
index 0000000..19ac148
--- /dev/null
+++ b/AoC_2025.Tests/AoC_2025.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AoC_2025.Tests/Day01Test.cs b/AoC_2025.Tests/Day01Test.cs
new file mode 100644
index 0000000..e1b7e75
--- /dev/null
+++ b/AoC_2025.Tests/Day01Test.cs
@@ -0,0 +1,46 @@
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025.Tests;
+
+public class Day01Test
+{
+ private readonly IPuzzleSolver _sut;
+ private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day01.txt";
+ private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day01.txt";
+
+ public Day01Test()
+ {
+ _sut = new Day01();
+ }
+ [Fact]
+ public void Part01_Test_equals_3()
+ {
+ var actual = _sut.SolvePart1(TestInputPath);
+
+ Assert.Equal(3, actual);
+ }
+
+ [Fact]
+ public void Part01_Prod_equals_1097()
+ {
+ var actual = _sut.SolvePart1(ProdInputPath);
+
+ Assert.Equal(1097, actual);
+ }
+
+ [Fact]
+ public void Part02_Test_equals_16()
+ {
+ var actual = _sut.SolvePart2(TestInputPath);
+
+ Assert.Equal(16, actual);
+ }
+
+ [Fact]
+ public void Part02_Prod_equals_7101()
+ {
+ var actual = _sut.SolvePart2(ProdInputPath);
+
+ Assert.Equal(7101, actual);
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025.Tests/Day02Test.cs b/AoC_2025.Tests/Day02Test.cs
new file mode 100644
index 0000000..1ce0a2b
--- /dev/null
+++ b/AoC_2025.Tests/Day02Test.cs
@@ -0,0 +1,46 @@
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025.Tests;
+
+public class Day02Test
+{
+ private readonly IPuzzleSolver _sut;
+ private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day02.txt";
+ private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day02.txt";
+
+ public Day02Test()
+ {
+ _sut = new Day02();
+ }
+ [Fact]
+ public void Part01_Test_equals_1227775554()
+ {
+ var actual = _sut.SolvePart1(TestInputPath);
+
+ Assert.Equal(1227775554, actual);
+ }
+
+ [Fact]
+ public void Part01_Prod_equals_13919717792()
+ {
+ var actual = _sut.SolvePart1(ProdInputPath);
+
+ Assert.Equal(13919717792, actual);
+ }
+
+ [Fact]
+ public void Part02_Test_equals_4174379265()
+ {
+ var actual = _sut.SolvePart2(TestInputPath);
+
+ Assert.Equal(4174379265, actual);
+ }
+
+ [Fact]
+ public void Part02_Prod_equals_14582313461()
+ {
+ var actual = _sut.SolvePart2(ProdInputPath);
+
+ Assert.Equal(14582313461, actual);
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025.Tests/Day03Test.cs b/AoC_2025.Tests/Day03Test.cs
new file mode 100644
index 0000000..271d923
--- /dev/null
+++ b/AoC_2025.Tests/Day03Test.cs
@@ -0,0 +1,46 @@
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025.Tests;
+
+public class Day03Test
+{
+ private readonly IPuzzleSolver _sut;
+ private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day03.txt";
+ private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day03.txt";
+
+ public Day03Test()
+ {
+ _sut = new Day03();
+ }
+ [Fact]
+ public void Part01_Test_equals_357()
+ {
+ var actual = _sut.SolvePart1(TestInputPath);
+
+ Assert.Equal(357, actual);
+ }
+
+ [Fact]
+ public void Part01_Prod_equals_16812()
+ {
+ var actual = _sut.SolvePart1(ProdInputPath);
+
+ Assert.Equal(16812, actual);
+ }
+
+ [Fact]
+ public void Part02_Test_equals_3121910778619()
+ {
+ var actual = _sut.SolvePart2(TestInputPath);
+
+ Assert.Equal(3121910778619, actual);
+ }
+
+ [Fact]
+ public void Part02_Prod_equals_166345822896410()
+ {
+ var actual = _sut.SolvePart2(ProdInputPath);
+
+ Assert.Equal(166345822896410, actual);
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025.Tests/Day04Test.cs b/AoC_2025.Tests/Day04Test.cs
new file mode 100644
index 0000000..944d32b
--- /dev/null
+++ b/AoC_2025.Tests/Day04Test.cs
@@ -0,0 +1,46 @@
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025.Tests;
+
+public class Day04Test
+{
+ private readonly IPuzzleSolver _sut;
+ private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day04.txt";
+ private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day04.txt";
+
+ public Day04Test()
+ {
+ _sut = new Day04();
+ }
+ [Fact]
+ public void Part01_Test_equals_13()
+ {
+ var actual = _sut.SolvePart1(TestInputPath);
+
+ Assert.Equal(13, actual);
+ }
+
+ [Fact]
+ public void Part01_Prod_equals_1411()
+ {
+ var actual = _sut.SolvePart1(ProdInputPath);
+
+ Assert.Equal(1411, actual);
+ }
+
+ [Fact]
+ public void Part02_Test_equals_43()
+ {
+ var actual = _sut.SolvePart2(TestInputPath);
+
+ Assert.Equal(43, actual);
+ }
+
+ [Fact]
+ public void Part02_Prod_equals_8557()
+ {
+ var actual = _sut.SolvePart2(ProdInputPath);
+
+ Assert.Equal(8557, actual);
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025/AoC_2025.csproj b/AoC_2025/AoC_2025.csproj
new file mode 100644
index 0000000..e85e30f
--- /dev/null
+++ b/AoC_2025/AoC_2025.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/AoC_2025/Day01.cs b/AoC_2025/Day01.cs
new file mode 100644
index 0000000..14c9ff1
--- /dev/null
+++ b/AoC_2025/Day01.cs
@@ -0,0 +1,112 @@
+using System;
+using System.IO;
+using System.Linq;
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025;
+
+public class Day01 : IPuzzleSolver
+{
+ private string[] ParsePuzzleInput(string path)
+ {
+ var puzzleInput = File.ReadAllLines(path)
+ .Where(l => !string.IsNullOrWhiteSpace(l))
+ .ToArray();
+ return puzzleInput;
+ }
+
+ public int SolvePart1(string pathToPuzzleInput)
+ {
+ var rotationInstructions = ParsePuzzleInput(pathToPuzzleInput);
+ var dialPosition = 50;
+ var timesZeroWasHit = 0;
+ foreach (var rotationAmount in rotationInstructions.Select(ParseRotationAmount))
+ {
+ dialPosition = (dialPosition + rotationAmount) % 100;
+ if (dialPosition == 0)
+ timesZeroWasHit++;
+ }
+ return timesZeroWasHit;
+ }
+
+ public int SolvePart2(string pathToPuzzleInput)
+ {
+ var rotationInstructions = ParsePuzzleInput(pathToPuzzleInput);
+ var dialPosition = 50;
+ var timesZeroWasHit = 0;
+ foreach (var rotationAmount in rotationInstructions.Select(ParseRotationAmount))
+ {
+ var actualRotationAmount = 0;
+ if (rotationAmount < 0)
+ {
+ (var fullTurns, actualRotationAmount) = DivMod(rotationAmount, -100);
+ timesZeroWasHit += fullTurns;
+ if (dialPosition != 0 && dialPosition + actualRotationAmount <= 0)
+ timesZeroWasHit++;
+ }
+ else
+ {
+ (var fullTurns, actualRotationAmount) = DivMod(rotationAmount, 100);
+ timesZeroWasHit += fullTurns;
+ if (dialPosition + actualRotationAmount >= 100)
+ timesZeroWasHit++;
+ }
+
+ dialPosition = Mod(dialPosition + actualRotationAmount, 100);
+ }
+ return timesZeroWasHit;
+ }
+
+ private (int div, int mod) DivMod(int number, int divisor)
+ {
+ return (number / divisor, Mod(number, divisor));
+ }
+
+ private int Mod(int number, int divident)
+ {
+ var mod = number % divident;
+ if (number < 0 && divident > 0)
+ {
+ mod = divident + mod;
+ }
+
+ return mod;
+ }
+
+ private static bool HasPassedZero(bool hasOverflow, int dialPosition)
+ {
+ return dialPosition != 0 && hasOverflow;
+ }
+
+ private static (int nextDialPosition, bool passedZero) GetNextDialPosition(int rotationAmount, int dialPosition)
+ {
+ var actualRotation = rotationAmount % 100;
+ var nextDialPosition = dialPosition + actualRotation;
+ var hasOverflow = nextDialPosition is > 99 or < 1;
+ nextDialPosition %= 100;
+ if (nextDialPosition < 0)
+ {
+ nextDialPosition = 100 + nextDialPosition;
+ }
+ return (nextDialPosition, hasOverflow);
+ }
+
+ private static int ParseRotationAmount(string puzzleInput)
+ {
+ var direction = puzzleInput[..1];
+ var rotationAmount = int.Parse(puzzleInput[1..]);
+ if (direction == "L")
+ {
+ rotationAmount *= -1;
+ }
+
+ return rotationAmount;
+ }
+
+ private static int CountFullRotations(int rotationAmount)
+ {
+ var fullRotations = Math.Abs(rotationAmount / 100);
+
+ return fullRotations;
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025/Day02.cs b/AoC_2025/Day02.cs
new file mode 100644
index 0000000..e08cb18
--- /dev/null
+++ b/AoC_2025/Day02.cs
@@ -0,0 +1,131 @@
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025;
+
+public class Day02 : IPuzzleSolver
+{
+ private string[] ParsePuzzleInput(string path)
+ {
+ var puzzleInput = File.ReadAllText(path)
+ .Trim()
+ .Split(',', StringSplitOptions.RemoveEmptyEntries);
+ return puzzleInput;
+ }
+
+ public long SolvePart1(string pathToPuzzleInput)
+ {
+ var idRanges = ParsePuzzleInput(pathToPuzzleInput);
+ var invalidIds = idRanges.SelectMany(range => FindInvalidIds(range));
+ var sum = invalidIds.Sum();
+ return sum;
+ }
+
+ public long SolvePart2(string pathToPuzzleInput)
+ {
+ var idRanges = ParsePuzzleInput(pathToPuzzleInput);
+ var invalidIds = idRanges.SelectMany(range => FindInvalidSegmentedIds(range));
+ var sum = invalidIds.Sum();
+ return sum;
+ }
+
+ private IEnumerable FindInvalidIds(string range)
+ {
+ var (start, end) = Parse(range);
+ var halvableIds = FindHalvableIds(start, end);
+ var invalidIds = GetInvalidIds(halvableIds);
+ return invalidIds;
+ }
+
+ private IEnumerable FindInvalidSegmentedIds(string range)
+ {
+ var (start, end) = Parse(range);
+ var idsAsAllSegments = GetIdAsAllSegments(start, end);
+ var invalidIdSegments = FindIdsWhereAllSegmentsAreEqual(idsAsAllSegments);
+ var invalidIds = MergeSegments(invalidIdSegments);
+ var invalidIdNumbers = invalidIds.Select(id => long.Parse(id));
+ return invalidIdNumbers;
+ }
+
+ private (long start, long end) Parse(string range)
+ {
+ var parts = range.Split('-', StringSplitOptions.RemoveEmptyEntries);
+ return (long.Parse(parts[0]), long.Parse(parts[1]));
+ }
+
+ private IEnumerable FindHalvableIds(long start, long end)
+ {
+ var ids = Range(start, end);
+ var idStrings = ids.Select(id => id.ToString());
+ var halvableIds = idStrings.Where(id => (id.Length % 2) == 0);
+ return halvableIds;
+ }
+
+ private IEnumerable GetInvalidIds(IEnumerable halvableIds)
+ {
+ var idParts = halvableIds.Select(id => (id[..(id.Length / 2)], id[(id.Length / 2)..]));
+ var invalidIds = idParts.Where(parts=> parts.Item1 == parts.Item2);
+ var invalidNumberedIds = invalidIds.Select(id => long.Parse(id.Item1 + id.Item2));
+ return invalidNumberedIds;
+ }
+
+ private IEnumerable Range(long start, long end)
+ {
+ for (var i = start; i <= end; i++)
+ {
+ yield return i;
+ }
+ }
+
+ private IEnumerable>> GetIdAsAllSegments(long start, long end)
+ {
+ var ids = Range(start, end);
+ var idStrings = ids.Select(id => id.ToString());
+ var idSegments = idStrings.Select(id => GetSegments(id));
+ return idSegments;
+ }
+
+ private IEnumerable> GetSegments(string id)
+ {
+ for (int i = id.Length / 2; i > 0; i--)
+ {
+ if (id.Length % i == 0)
+ {
+ yield return id.Chunk(i).Select(chunks => new string(chunks));
+ }
+ }
+ }
+
+ private IEnumerable> FindIdsWhereAllSegmentsAreEqual(IEnumerable>> idAllSegments)
+ {
+ var segmentsWithOneAllMatching = FilterForAnyAllSegmentsEqual(idAllSegments);
+ var idSegments = GetFirstForEachSegments(segmentsWithOneAllMatching);
+ return idSegments;
+ }
+
+ private static IEnumerable> GetFirstForEachSegments(IEnumerable>> segmentsWithOneAllMatching)
+ {
+ var idSegments = segmentsWithOneAllMatching
+ .Select(segments => segments.First());
+ return idSegments;
+ }
+
+ private IEnumerable MergeSegments(IEnumerable> segments)
+ {
+ return segments.Select(segment => string.Concat(segment));
+ }
+
+ private IEnumerable>> FilterForAnyAllSegmentsEqual(IEnumerable>> idAllSegments)
+ {
+ var segmentsWithOneAllMatching = idAllSegments
+ .Where(idSegments => idSegments.Any(segments => CheckAllSegmentsEqual(segments)));
+ return segmentsWithOneAllMatching;
+ }
+
+ private bool CheckAllSegmentsEqual(IEnumerable segments)
+ {
+ var (allEqual, _) = segments.Aggregate(
+ (allEqual: true, prev: ""),
+ (tuple, segment) => (tuple.allEqual && (tuple.prev == "" || tuple.prev == segment), segment));
+ return allEqual;
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025/Day03.cs b/AoC_2025/Day03.cs
new file mode 100644
index 0000000..e62af4c
--- /dev/null
+++ b/AoC_2025/Day03.cs
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+using System.Linq;
+using AdvenOfCode.Contracts;
+
+namespace AoC_2025;
+
+public class Day03 : IPuzzleSolver
+{
+ private string[] ParsePuzzleInput(string path)
+ {
+ var puzzleInput = File.ReadAllLines(path)
+ .Where(line => !string.IsNullOrWhiteSpace(line))
+ .ToArray();
+ return puzzleInput;
+ }
+
+ public long SolvePart1(string pathToPuzzleInput)
+ {
+ var batterieBankStrings = ParsePuzzleInput(pathToPuzzleInput);
+ var batterieBanks = batterieBankStrings.Select(bbs => ParseBatterieBank(bbs));
+ var voltages = batterieBanks.Select(bb => FindMaxVoltage(bb));
+ var sumVoltages = voltages.Sum();
+ return sumVoltages;
+ }
+
+ public long SolvePart2(string pathToPuzzleInput)
+ {
+ const int voltageLength = 12;
+ var batterieBankStrings = ParsePuzzleInput(pathToPuzzleInput);
+ var batterieBanks = batterieBankStrings.Select(bbs => ParseBatterieBank(bbs));
+ var voltages = batterieBanks.Select(bb => GetMaxSuperVoltage(bb, voltageLength));
+ var sumVoltages = voltages.Sum();
+ return sumVoltages;
+ }
+
+ private int[] ParseBatterieBank(string batterieBank)
+ {
+ return batterieBank
+ .Select(c => new string(c, 1))
+ .Select(value => int.Parse(value))
+ .ToArray();
+ }
+
+ private long FindMaxVoltage(Span batterieBank)
+ {
+ var (firstNumber, position) = GetMax(batterieBank[..^1]);
+ var (secondNumber, _) = GetMax(batterieBank[(position + 1)..]);
+ var voltage = Combine(firstNumber, secondNumber);
+ return voltage;
+ }
+
+ private long Combine(long first, long second)
+ {
+ return (first * 10) + second;
+ }
+
+ private long GetMaxSuperVoltage(Span batterieBank, int voltageLength)
+ {
+ var indexedVoltageLength = voltageLength - 1;
+ var currentBankPosition = 0;
+ var superVoltage = 0L;
+ for (var i = 0; i < voltageLength; i++)
+ {
+ var (max, numberPosition) = GetMax(batterieBank[currentBankPosition..^(indexedVoltageLength - i)]);
+ superVoltage = Combine(superVoltage, max);
+ currentBankPosition += numberPosition + 1;
+ }
+
+ return superVoltage;
+ }
+
+ private (int max, int currentPosition) GetMax(Span batterieBank)
+ {
+ var max = batterieBank[0];
+ var currentPosition = 0;
+ for (var i = 1; i < batterieBank.Length; i++)
+ {
+ if (max < batterieBank[i])
+ {
+ max = batterieBank[i];
+ currentPosition = i;
+ }
+ }
+
+ return (max, currentPosition);
+ }
+}
\ No newline at end of file
diff --git a/AoC_2025/Day04.cs b/AoC_2025/Day04.cs
new file mode 100644
index 0000000..b276e44
--- /dev/null
+++ b/AoC_2025/Day04.cs
@@ -0,0 +1,102 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using AdvenOfCode.Contracts;
+using Coordinate = (int x, int y);
+
+namespace AoC_2025;
+
+public class Day04 : IPuzzleSolver
+{
+ private string[] ParsePuzzleInput(string path)
+ {
+ var puzzleInput = File.ReadAllLines(path)
+ .Where(line => !string.IsNullOrWhiteSpace(line))
+ .Select(line => line.Trim())
+ .ToArray();
+ return puzzleInput;
+ }
+
+ public long SolvePart1(string pathToPuzzleInput)
+ {
+ var paperrollGrid = ParsePuzzleInput(pathToPuzzleInput);
+ var accessiblePaperrolls = GetAccessiblePaperrolls(paperrollGrid);
+ var countAccessibleRolls = accessiblePaperrolls.Count();
+ return countAccessibleRolls;
+ }
+
+ public long SolvePart2(string pathToPuzzleInput)
+ {
+ var paperrollGrid = ParsePuzzleInput(pathToPuzzleInput);
+ var rollCoordinates = GetRollCoordinates(paperrollGrid);
+ var countAccessibleRolls = RecursiveCountAllRemovableRolls(rollCoordinates);
+ return countAccessibleRolls;
+ }
+
+ private IEnumerable GetAccessiblePaperrolls(IEnumerable paperrollGrid)
+ {
+ var rollCoordinates = GetRollCoordinates(paperrollGrid);
+ var rollsWithNeighbours = GetNeighbours(rollCoordinates);
+ var accessiblePaperrolls = FilterForLessThan(rollsWithNeighbours, 4);
+ return accessiblePaperrolls;
+ }
+
+ private int RecursiveCountAllRemovableRolls(HashSet coordinates)
+ {
+ var rollsWithNeighbours = GetNeighbours(coordinates);
+ var accessiblePaperrolls = FilterForLessThan(rollsWithNeighbours, 4).ToArray();
+ var countAccessibleRolls = accessiblePaperrolls.Length;
+ coordinates.ExceptWith(accessiblePaperrolls);
+ return countAccessibleRolls == 0
+ ? 0
+ : countAccessibleRolls + RecursiveCountAllRemovableRolls(coordinates);
+ }
+
+ private HashSet GetRollCoordinates(IEnumerable paperrollGrid)
+ {
+ var coordinates = paperrollGrid
+ .SelectMany((gridLine, xIndex) => GetCoordinates(gridLine, xIndex))
+ .ToHashSet();
+ return coordinates;
+ }
+
+ private IEnumerable GetCoordinates(string gridLine, int xIndex)
+ {
+ var indexedCharacters = gridLine.Select((character, yIndex) => (character, yIndex));
+ var validRolls = indexedCharacters.Where(tuple => tuple.character == '@');
+ var rollCoordinates = validRolls.Select(tuple => (xIndex, tuple.yIndex));
+ return rollCoordinates;
+ }
+
+ private IEnumerable<(Coordinate coordinate, Coordinate[] neighbours)> GetNeighbours(HashSet rollCoordinates)
+ {
+ var coordsWithNeighbourCount = rollCoordinates
+ .Select(coord => (coord, GetNeighbours(coord, rollCoordinates)));
+ return coordsWithNeighbourCount;
+ }
+
+ private Coordinate[] GetNeighbours(Coordinate coordinate, HashSet coordinates)
+ {
+ Coordinate[] neighbourDirections =
+ [
+ (-1, -1), (-1, 0), (-1, 1),
+ (0, -1), (0, 1),
+ (1, -1), (1, 0), (1, 1),
+ ];
+ var possibleNeighbours = neighbourDirections
+ .Select(direction => (coordinate.x + direction.x, coordinate.y + direction.y));
+ var actualNeighbours = possibleNeighbours
+ .Where(coord => coordinates.Contains(coord))
+ .ToArray();
+ return actualNeighbours;
+ }
+
+ private IEnumerable FilterForLessThan(IEnumerable<(Coordinate coordinate, Coordinate[] neighbours)> coordinatesWithNeighbours,
+ int lessThan)
+ {
+ var coordinatesWithLess = coordinatesWithNeighbours
+ .Where(entry => entry.neighbours.Count() < lessThan)
+ .Select(entry => entry.coordinate);
+ return coordinatesWithLess;
+ }
+}
\ No newline at end of file