2025 Advent of Code
It’s just over a week until Advent of Code 2025 starts, here is my usual post about my approach for this year.
This time, I’m going back to a past tactic of trying to learn a new language at the same time as solving the problems, I don’t really have the time to do this but I didn’t want to go another year where I use a language I already know. This years choice is ๐ฅ๐ฅ๐ฅ Ruby.
I was surprised to learn that Ruby has been around since 1995, the first time I really heard about it was when I learnt about Ruby on Rails and that was around 2006 so even then it was a well established language. Given the language is ~30 years old, it should have an established set of tools available that would typically be used in an engineering context. In 2025, Ruby appears at number 18 in the list of programming languages in Stack Overflow’s Developer Survey, I’m surprised it isn’t a little higher but it seems to be a favourite for web development and hasn’t been picked up in the AI trend. Even though it’s not a top 10 language, some popular open source software is written in Ruby including:
- Jekyll static site generator
- Discource online community platform
- Homebrew package management for Macs.
So, lets take a dive into what I’ve set up for this years challenges.
Installing and bootstrapping Ruby
Ruby, like python, comes preinstalled on Macs and so to prevent issues that can be caused by updating or using this it’s best to install a venv type version manager. For ruby this is rbenv, which manages the global versions of Ruby available on a given machine.
1โฐโโฏ brew install rbenv
Once rbenv is installed, we need to run init which leads to updates in ~/.zprofile
1โฐโโฏ rbenv init
The latest version of Ruby is currently 3.4.7; we need to make this available as a Ruby version in rbenv.
1โฐโโฏ rbenv install 3.4.7
rbenv now knows about v3.4.7 but we need to set this as the version to use for the AoC project else the system version will be used. In the AoC project folder we need to run:
1โฐโโฏ rbenv local 3.4.7
This creates a .ruby-version file locally which contains the ruby version the projet uses, I guess the clue is in the name. The final step of Ruby setup is to bootstrap the project so that we can add the dependencies.
1โฐโโฏ bundle init
This creates a Gemfile for me to add Ruby Gems to, I’ll cover the Gems I’m going to use in the next section.
Setting up the project
Now that I have ruby installed and ready to go, I want to add some source files and things that can be used
first thing is the folder structure. GitHub gives a couple of files but I need folders and ruby files to test the structure with.
The structure I’ll be using is:
12025-advent-of-code/
2โ
3โโโ .git/
4โโโ .github/ # Contains GitHub build workflows
5โโโ .vscode/ # Contains debug and run configs
6โโโ config/
7โ โโโ pre_commit.yml. # Configuration of pre-commit checks to perform
8โโโ lib/ # Daily solution code
9โ โโโ day_01/
10โ โโโ task_01.rb
11โ โโโ task_02.rb
12โ
13โโโ test/ # Unit tests
14โ โโโ day_01/
15โ โโโ example_input_01.txt # Example input from the AOC challenge
16โ โโโ input.txt # Real input
17โ โโโ test_task_01.rb
18โ โโโ test_task_02.rb
19โโโ .gitignore # Files I don't want to check into Git
20โโโ .rubocop.yml # RuboCop linter/formatter rules
21โโโ .ruby-version # The version of Ruby that the project uses
22โโโ Gemfile # Lists of gems
23โโโ Gemfile.lock # Auto-generated by Bundler
24โโโ Rakefile # The build tasks that are available to the project
25โโโ setup_day.sh # Shell script for rapidly setting up the repo for the next day's task
And as with all previous years I will not be checking the real input (input.txt each day) into GitHub, this respects the wishes of the event organisers.
Gemfile
I need to define the libraries I’ll use in my Gemfile and I can add these simply but one thing I’ve used with other AoC solutions is a separation between the development, test, build and runtime libraries so I will try to follow a similar convention here. As I understand it, with Ruby I can define groups of gems, all builds will include all groups unless I explicitly exclude them. For example, I could have a group called :runtime and a group called :development; on local build I wouldn’t exclude anything, but on the deployable build I would exclude :development. As I want to work “prod-like” I’ll try to group libraries and then exclude some when on the build server.
1source "https://rubygems.org"
2
3group :development do
4 ...
5end
6
7group :test do
8 ...
9end
10
11group :runtime do
12 ...
13end
After adding each dependency I run
1โฐโโฏ bundle install
I think that this installs the library executables to a centrl Ruby repo on my machine and then links the version in the lock file to the project. I can’t see any evidence of the dependencies existing within my project which is why I think it works this way.
Make file
In this case not make but rake, which is very similar but for Ruby. I’ll add this as a :runtime dependency as it should always be in place for all the tasks that follow in this post.
Make files contain all the useful commands for the project, in the case of mine I will include a test target to trigger the unit test framework and also a lint target so that I can manually trigger a run of the linter. I may never use the lint target but adding it gives me a bit more practice.
Unit test framework
It looks like the standard framework to use for unit testing is minitest so this is the framework I’ll adopt.
I’ll add the test framwork as a :test library, it will be needed locally and on the build server but not deployed to production as tests wouldn’t be deployed to production.
Linter
The linter I’ve chosen is rubocop, this does almost everything I need and seems to be a Ruby standard. I’ll add this to the :development group. I also want rubocop to lint my test files to make sure they match the expected test format, so for this I will use a rubocop extension rubocop-minitest. The rubocop-minitest extension is also added to the :development group.
Once rubocop is installed it needs to be configured, this is done in the .rubocop.yml file in the root directory. I will try to leave as many rules on as possible, the ones that I want to configure are:
- SuggestExtensions, I’ll disbale this otherwise every time I lint I will get a list of “helpful” suggested extra extensions
- Naming/VariableNumber, I’ll set the
EnforcedStyletosnake_case, this means that variables with numbers in them can have an underscore between the name and the number, for examplevariable_01instead ofvariable01 - Style/Documentation, I’ll disbale this as I don’t want to always add docs.
- Metrics/MethodLength, I’ll disable this instead of configuring a greater
Maxvalue as I might have functions that end up being longer, I might refactor in future but will want to check in a version so that it’s recorded.
A final fun thing I’ll configure with Rubocop is setting the formatter to be the pacman formatter.
Pre-commit package
My goto pre-commit hook utility is usually pre-commit, created by the brilliant Anthony Sottile who has himself completed Advent of Code challenges in the past, 2021 being the most recent year.
For this year I will use a package called pre-commit but this is one written in Ruby. I’ve found it a bit clunky to get going with it, but I’ll stick with it for now and if it causes me issues there will be a blog post in how to move from one package to another.
GitHub actions
As I’m using rake, my actions file only really does two things, firstly intsll ruby and secondly run the test task which is the default task in the rake file. If the tests fail, the build will fail and I’ll be notified, this is a great simulation of a prod environment where tests might “work on my machine” - the build server becomes the source of truth.
Bootstrap script
Each year I’ve found it very beneficial to have a script I can run that will create a folder with files for the next set of challenges. I’ve copied the one I used last year and updated it to match the 2025 project I’ve created.
Library files
When I do AoC there are a few library functions that I like to have ready as they are useful for problems that have come up in the past. This year will be no exception, Ruby is completely new to me so working on these library functions will help to familiarise myself with the language before the first challenge is released. The utils library will contain:
- Point - a class to wrap x,y con-ordinates. I create this for all those 2D space problems. Key functions are:
- get_manhattan_distance - this returns the distance from a Point to another point
in_bounds?- tests whether a point sits within a given bounding box
Debugger
The final thing I get setup is a debugger for when I inevitably have a solution that works for the example but fails for the real input. Whilst print statements work, the output can become hard to follow so I like to have a debugger handy. To debug I will install the debase library in my project as a :development dependency and install the Ruby LSP plugin into Visual Code.
That’s it, my 2025 AoC repo all set up and ready to go.