How I'm Doing Advent Of Code in 2024

Advent Of Code has been around for ages. It’s one of those things that I’ve always wanted to do, but never managed to get into the groove of. I’d maybe do the first puzzle, maybe even a couple of days, but eventually the time of year would overtake me and it would fall by the wayside.

This year is a little different. We’re currently eight days through, and I’m fully up-to-date with all of the challenges. Full gold stars and everything. This is after coming into it five days late and needing to catch up. The difference has been that I’m not the only person I know who is taking part. AOC has caught on at work and there are people in my immediate network - on my direct team, even - all taking part and talking about it. We even have a private leaderboard[1]. Turns out that friendly competition can be a decent motivator for me!

But how am I actually completing the challenges? This is the ā€œstackā€ I’m using:

  • C# and .NET 8/9. (I didn’t get my environment upgraded to 9 until partway through)
  • A mix of VS Code and ā€œfull fatā€ Visual Studio. I keep meaning to try Rider too, now it’s free, but I haven’t yet.
  • A .NET Console project for the actual puzzle, and an MSTest project for unit tests.
  • A Powershell function to setup my stub project each day.

This last one was actually pretty key. .NET has come a long way recently, and the CLI tool is pretty great, but for the first few days I was having to follow process of around a dozen steps to get the project setup I wanted. It was frustrating. I’ve included the function below. As much so I can easily copy it to my work laptop tomorrow as for sharing šŸ˜…. Using the function makes getting started as simple as New-AdventOfCodeProject with optional parameters for the year and day.

I could have gone with JavaScript or something and avoided all that initial palaver with an npm init -y or similar, but sometimes I feel like I don’t write enough C# any more; it’s mostly JavaScript at work these days, so AOC is an ideal time to stretch some neglected muscles. Maybe even some new ones? It would be an ideal opportunity… Regardless, I’m really enjoying using C# for these.

As far as actual approach, I guess I’m essentially using TDD (Test Driven Development). The description gives you everything you need to create at least one unit test using the sample input and expected answer. Apart from the first couple of puzzles, I’ve started with writing this test before any implementation code. From there it’s a case of writing code to pass that unit test. Once the unit test is passing I add code to my programme to load the input from a text file and pass it to my processing code. Sometimes the real input will throw up edge cases I hadn’t thought of[2] so there will be some rework, but it’s all part of the process.

In terms of structure I usually have 3 files:

  • Program.cs - main application file, does the writing to the console.
  • input.txt - puzzle input.
  • Day<whatever>.cs - a class file which does the actual processing.

Within the class file the code is usually along the lines of:

namespace AdventOfCode24Day8;

public class DayEight
{
  // Keep a copy of our original puzzle input around
  private readonly string[] _originalInput;
  
  // Constructor
  public DayEight(string[] input)
  {
    _originalInput = input;
  }

  // Part One setup and processing
  public int PartOne()
  {
    // Anything specific to processing part one
    // pre-processing the input, etc...
    return PuzzleSolvingEntryPoint(_originalInput);
  }
  
  // Method that actually kicks off the processing.
  // Usually named something more relevant to the puzzle
  private static int PuzzleSolvingEntryPoint(input)
  {
    // ...
  }
  // Any helper methods, etc
}

This approach has been evolving over the last eight days I’ve been taking part, and will probably keep evolving over the remaining days of Advent. The first few days were definitely a bit more ā€œscrappyā€!

On the console, the programme is outputting a welcome and then the answer to each part as I complete it. I could probably get fancier, but one of my goals was to keep things as simple as possible. If it doesn’t take me much time then I’m more likely to stick with it. A very basic console app is perfect for this.

The Elephant in the Room

But what about AI? Couldn’t I get something to solve these puzzles for me? Well, yes, I likely could. I think I’m pretty good at ā€œPrompt Engineeringā€, so could probably make it happen. But doing so would completely ruin the point of it all for me. I’m not an Anti-AI purist. I think every software engineer should make best use of all the tools they have available to solve the problem in front of them as best they can. If Copilot wants to autocomplete parts of the method I’m writing and it looks like what I was about to type out manually[3] anyway, then I’m hitting the Tab key. But there are certain things I want to do. Solving the problem of the day for Advent Of Code is one of them. So while I’ll accept an autocomplete, I’m not asking the chat for the solution. I’m not heading off to ChatGPT, or Gemini, or something else, to feed the entire puzzle details in to get the answer. It would ruin the fun for me.

But once I’ve solved it and got my two stars for the day? Then I will happily query an LLM about the problem. I’ll compare how it approaches getting to a solution. Usually it takes a bit of iterative prompting to get it onto the right track, but that’s part of the challenge of AI tooling. Yesterday, as I was beginning to read a book on how to write Rust, I threw my C# solution into Claude, to see what an equivalent Rust implementation would look like. It was useful as a learning exercise - and, yes, Claude’s conversion did run successfully and produce the correct answers. I doubt it was an optimal conversion, but it did work.

Equally, I might use an LLM to help me with an ā€œextra assignmentā€ after the puzzle was finished with. Day six had a puzzle that was ripe for a console-based visualisation, so I got to work with Copilot over lunch to bring it to life. You can see the results in a 30 second clip over on Bluesky.

Wrapping Up

I’ll be putting all my solutions on GitHub eventually. A couple already are, but the rest are spread across multiple different computers right now, and I’d need to add proper .gitignore files and the like to half of them, so it may take me a while. I’d also want to post them publicly with a bit of a lag, to prevent spoiling the answer for anyone.

But yeah, now I’m finally taking part properly I’m enjoying Advent Of Code. A lot. Or maybe I’m enjoying rapidly climbing up our private leaderboard, I don’t know. But if you’ve thought about taking part but just haven’t for whatever reason, I do recommend giving it a shot. Treat it as a bit of no-stakes problem solving. Like Wordle, or the crossword or something. You might enjoy it too. I’ve been enjoying the coding brain-teaser format enough that I bought myself a whole book of them.

Oh, and here’s that PowerShell function I mentioned near the start. It’s simple but long, hence why I put it down here and out of the way:

function New-AdventOfCodeProject {
    param (
        [int]$Year,
        [int]$Day
    )
    if (-not $PSBoundParameters.ContainsKey('Year')) {
        $Year = (Get-Date).Year % 100
    }
    if (-not $PSBoundParameters.ContainsKey('Day')) {
        $Day = [int](Get-Date).Day
    }

    $projectName = "AdventOfCode${Year}Day${Day}"
    $projectPath = "$PWD\$projectName"
    $testProjectName = "${projectName}.Test"
    $testProjectPath = "$projectPath\$testProjectName"
    $fileNumber = Convert-ToInitCase -inputString (Convert-NumberToWords -number $Day)
    $testFileName = "Day${fileNumber}Tests.cs"

    # Create the new folder
    New-Item -ItemType Directory -Path $projectPath

    # Change into the new directory
    Set-Location -Path $projectPath

    # Create a new .NET console project
    dotnet new console -n $projectName

    # Create a new .NET mstest project
    dotnet new mstest -n $testProjectName

    # Change into the directory of the test project
    Set-Location -Path $testProjectPath

    # Add a reference from the test project to the console project
    dotnet add reference "..\$projectName\$projectName.csproj"

    # Rename the UnitTest1.cs file to Day{fileNumber}Tests.cs
    Rename-Item "$testProjectPath\UnitTest1.cs" $testFileName

    # Change back to the parent directory
    Set-Location -Path $projectPath

    # Add empty files to the console project: input.txt, and Day{fileNumber}.cs
    New-Item -ItemType File -Path "$projectPath\input.txt"
    New-Item -ItemType File -Path "$projectPath\Day${fileNumber}.cs"

    # Initialize a git repository
    git init

    # Copy LICENSE from templates
    Copy-Item "$env:USERPROFILE\src\1_templates\MIT.LICENSE" -Destination "$projectPath\LICENSE"

    # Copy .gitignore from templates
    Copy-Item "$env:USERPROFILE\src\1_templates\dotnet.gitignore" -Destination "$projectPath\.gitignore"

    # Add these files to git and commit them. Solution files will be added later.
    git add LICENSE .gitignore
    git commit -m "initial commit"

    # Open VS Code from the project parent directory
    code $projectPath
}

# Example usage:
# New-AdventOfCodeProject -Year 2024 -Day 7

function Convert-ToInitCase {
    param (
        [string]$inputString
    )

    $words = $inputString -split '\s+'
    $initCaseString = $words | ForEach-Object { $_.Substring(0,1).ToUpper() + $_.Substring(1).ToLower() }
    return -join $initCaseString
}

# Example usage:
# Convert-ToInitCase -inputString "hello world"  # Output: "HelloWorld"

function Convert-NumberToWords {
    param (
        [int]$number
    )

    $units = @("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine")
    $teens = @("ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen")
    $tens = @("", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety")

    if ($number -lt 10) {
        return $units[$number]
    } elseif ($number -lt 20) {
        return $teens[$number - 10]
    } elseif ($number -lt 100) {
        $tensPart = [math]::Floor($number / 10)
        $unitsPart = $number % 10
        if ($unitsPart -eq 0) {
            return $tens[$tensPart]
        } else {
            return "$($tens[$tensPart])-$($units[$unitsPart])"
        }
    } else {
        throw "Number out of range"
    }
}

# Example usage:
# Convert-NumberToWords -number 7  # Output: "seven"

  1. I’m currently 5th out of 22, which I don’t think is that bad for having started five days behind the leaders. Only three of us have full stars. ā†©ļøŽ

  2. Like the day I assumed all the numbers involved would fit into an Int32 and then had to make the switch to Int64 after my solution crashed halfway through. ā†©ļøŽ

  3. A lot of the autocomplete prompts I get are code relevant to previous years. Which is a great reminder that you need to be checking the output and suggestions from these things thoroughly. ā†©ļøŽ