By Eric Burden | November 28, 2023
The weather outside isn’t quite frightful yet, but the most wonderful time of year is fast approaching. That’s right, it’s almost time for Advent of Code! Like prior years, I’ll personally be using this event to learn a new programming language. This year, it’s Kotlin. And, as usual, I’ll be blogging each day’s puzzle, but I thought I’d kick this year off with a brief walkthrough of how (and a bit of why) I set up my own project repository for AoC. Having completed 8 years’ worth of puzzles now, I’ve found there’s a lot of value in a bit of setup beforehand.
Orientation
This guide will make a lot more sense if you’re already familiar with the setup and style of Advent of Code. Briefly, each day from December 1st through December 25th, a new puzzle will be made available. Each puzzle will have two parts. Generally, the second part is a slightly/somewhat more complicated version of the first part that requires a bit of re-factoring of the first part to make it more flexible or more efficient. Unlike sites like LeetCode or CodeWars, you won’t submit your code for each puzzle. Instead, each part has a definite answer that you can arrive at using any programming language (or other tool) at your disposal. These answers are submitted on the Advent of Code site. The input for each day is provided as a downloadable text file (of which there are many variations) and you’ll use the same input for both parts. Each part you solve earns you a “star”, for two “stars” per day which is fifty “stars” over the course of the event. Generally, the difficulty of the puzzles increases each day, with larger “jumps” in difficulty on the weekends.
Because all the work for your solution is done locally, it’s helpful to
organize your local project files. A quick internet search of “
Let’s Git Started
Probably the best place to start is deciding that you’re going to use version control, then deciding where you plan on hosting your project long term. GitHub is likely to be a popular choice, but GitLab, BitBucket, or any of the other VCS-hosting platforms works just as well. Frankly, you don’t need to host/share your code online, but it is nice to be able to share your code easily, especially if you decide to ask for help (more on that later).
Since I tend to use different languages for different years, I like to set up a repository for each year and include each year as a submodule within a single “parent” repository for organization. Here’s mine in case you’re curious, but other than a README and links to the submodules, there’s not much there.
One final note about repositories: Eric Wastl, the creator and producer of
Advent of Code and Advent of Code puzzles, has requested that individual inputs
not be included in your repository. I honestly need to clean up some of my older
repositories, but an easy way to do this is to add the input files (or folders)
to your .gitignore
file.
Building a Foundation
Once I have a repository, it’s time to set up the project. Now, this step is going to vary widely depending on the language you’ll be using to solve the puzzles. Many languages have at least one project template, some with scripts or applications to set up a project directory. If you have multiple options, I recommend keeping it as simple as possible. That said, there are a couple of considerations for project setup. I like to have:
- Clear separation between the code for each day’s puzzle.
- A testing framework/harness, with support for unit testing part one and part two of each day.
- Optionally, support for benchmarking the performance of your solutions.
I don’t always care about absolute performance, but one year I had set myself a goal of writing reasonably efficient Rust code, so I made sure to include benchmarking. If you’re looking at my repository for inspiration, ignore the setup for 2020. That was my first year doing Advent of Code, and the setup is kind of garbage. Instead, check out the project setups below. While there’s definitely some variation year-to-year and language-to-language, as long as you keep your code organized, you’re going to have a better time.
Julia Setup
In 2021, I solved the puzzles in Julia. I generally split the code for each day into three parts: (1) reading and parsing the input, (2) solving part one, (3) solving part two. In my Julia project, I have a separate file for each part and include the input parsing code in a file named “DayXX.jl” (where “XX” is “01” for day 1, “02” for day 2, “25” for day 25, etc.) to combine these three parts into a single module. There are some other scripts in there that I’ll discuss in the next section.
<project root>
├─inputs
│ └─DayXX
│ ├─input.txt
│ └─test.txt
├─src
│ ├─DayXX
│ │ ├─DayXX.jl
│ │ ├─Part01.jl
│ │ └─Part02.jl
│ ├─Benchmark.jl
│ ├─JuliaAdventOfCode.jl
│ └─RunAll.jl
├─tests
│ ├─DayXXTests.jl
│ └─runtests.jl
├─Manifest.toml
└─Project.toml
Rust Setup
In 2022, I solved the puzzles in Rust. Again, I’ve got Rust modules for each day, split into input parsing, part one, and part two code for each day (notice a pattern?). I’ve also got code for benchmarking this year. One thing to note in this Rust setup is that Rust allows (and even encourages) test code to be written alongside the code being tested, so there’s no separate folder for tests.
<project root>
├─benches
│ └─all_days.rs
├─input
│ └─XX
│ ├─input.txt
│ └─test.txt
├─src
│ ├─dayXX
│ │ ├─input.rs
│ │ ├─mod.rs
│ │ ├─part1.rs
│ │ └─part2.rs
│ ├─bin.rs
│ └─lib.rs
├─Cargo.toml
└─README.md
Kotlin Setup
Here’s what I’m planning to use for 2023. I realize it’s a bit different from what you might find elsewhere since I’m using Maven instead of Gradle, but I found a project template I liked using Maven.
<project root>
├─src
│ ├─main
│ │ ├─dev.ericburden.aoc2023
│ │ │ ├─Day##.kt
│ │ │ └─Resources.kt
│ │ └resources
│ │ └─day##.txt
│ └─test
│ ├─dev.ericburden.aoc2023
│ │ └─Day##Test.kt
│ └resources
│ └─day##ex##.txt
├─setup-day
├─pom.xml
└─README.md
Automating Common Tasks
One of the benefits to splitting your project up by day is that you’ll find yourself doing some of the same things every day. For me, the usual workflow includes:
- Creating a template for the current day’s puzzles, including tests.
- Downloading the input.
- Copy/pasting the example input(s) into a file.
- Attempting to solve part one.
- Testing the part one solution using the example input and expected output (preferably by adding a unit test for the example input)
- Checking that the part one solution works on the real input.
- Probably tweaking the part one solution until it does work on the real input.
- Memorializing the part one solution in a test case (so we don’t accidentally break it while working on part two).
- Copy/pasting the example for part two (if different).
- Attempting to solve part two.
- Testing the part two solution against the example(s).
- Checking that the part two solution works on the real input.
- Memorializing the part two solution in a separate test case.
- Running all the example and part one/two tests to confirm they all still work.
- Celebrate!
Input
Thankfully, some of these tasks can be automated, most easily the parts where
you create a template for the current day and download the input. A lot of the
boilerplate for the solution and tests can be generated as well. You can even
automate the part where you submit your answers, but I’ve never felt like I
needed that. This year, I’ve written a small shell script that relies on
the aoc-cli
tool to fetch inputs.
I’m including the contents of that script below because, by changing out the
text of the templated file contents, it could be easily adapted to just about
any setup.
#! /bin/bash
DAY=$1
[[ -z $2 ]] && YEAR=2022 || YEAR=$2
# Day must be passed as an argument
if [ -z "$DAY" ]; then
echo "Please provide the day as an argument."
exit 0
fi
# Don't try to setup days before the 1st or after the 25th
if (("$DAY" > 25 || "$DAY" < 1)); then
echo "There's no Day $1 in the advent calendar!"
exit 0
fi
# Don't try to set up years with no AoC (or in the future)
if ((YEAR < 2015 || YEAR > $(date +"%Y"))); then
echo "The year passed must be a valid AoC year!"
exit 0
fi
# Warn and quit if no .cookie file found
COOKIE=.cookie
if [ ! -f "$COOKIE" ]; then
echo "Store your AoC session cookie in '.cookie'!"
exit 0
fi
# Create the class files for the given day
PADDED=$(printf "%02d" "$DAY")
SOLUTION_PATH="src/main/kotlin/dev/ericburden/aoc2023/Day${PADDED}.kt"
TEST_PATH="src/test/kotlin/dev/ericburden/aoc2023/Day${PADDED}Test.kt"
if [ -f "SOLUTION_PATH" ]; then
echo "You've already set up Day $DAY !"
exit 0
fi
# Template for the solution file
cat >"$SOLUTION_PATH" <<EOL
package dev.ericburden.aoc2023
class Day${PADDED}(input: String) {
private val parsed = null
fun solvePart1(): Int = 0
fun solvePart2(): Int = 0
}
EOL
# Template for the test file
cat >"$TEST_PATH" <<EOL
package dev.ericburden.aoc2023
import dev.ericburden.aoc2023.Resources.resourceAsText
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@DisplayName("Day ${DAY}")
class Day${PADDED}Test {
private val example1 = resourceAsText("day${PADDED}ex01.txt")
private val input = resourceAsText("day${PADDED}.txt")
private val part1ExampleResult = 0
private val part1Answer = 0
private val part2ExampleResult = 0
private val part2Answer = 0
@Nested
@DisplayName("Part 1")
inner class Part1 {
@Test
fun \`Matches example\`() {
val answer = Day${PADDED}(example1).solvePart1()
assertThat(answer).isEqualTo(part1ExampleResult)
}
@Test
@Disabled
fun \`Actual answer\`() {
val answer = Day${PADDED}(input).solvePart1()
assertThat(answer).isEqualTo(part1Answer)
}
}
@Nested
@DisplayName("Part 2")
inner class Part2 {
@Test
@Disabled
fun \`Matches example\`() {
val answer = Day${PADDED}(example1).solvePart2()
assertThat(answer).isEqualTo(part2ExampleResult)
}
@Test
@Disabled
fun \`Actual answer\`() {
val answer = Day${PADDED}(input).solvePart2()
assertThat(answer).isEqualTo(part2Answer)
}
}
}
EOL
# Fetch the input with aoc-cli
RESOURCE_PATH=src/main/resources
mkdir -p $RESOURCE_PATH # Ensure directory exists
INPUT_PATH="${RESOURCE_PATH}/day${PADDED}.txt"
aoc -s $COOKIE -y "$YEAR" -d "$DAY" -I -i "$INPUT_PATH" -o download
# Create a blank file for the first example (others can be created manually)
touch "src/test/resources/day${PADDED}ex01.txt"
I suspect more could be done here to extract the examples into their own files as well.
Output
Most of my previous setups also include a facility to run a script or executable that accepts the day (1-25) as an argument, runs the code for that day’s puzzle, and outputs the result. This isn’t super useful in and of itself, but it’s kind of a fun feature. This is mostly useful if you’re working with someone else to help debug their solution by giving you an easy way to run another input through a working algorithm.
You could also have your script/binary report benchmarking results, which would
be a bit more useful. Here’s a
direct link
to my Rust binary that does this exact thing, in you’re interested. To run all
days, it would be invoked like ./advent_of_code_2023 --all --timed
.
We’re All In This Together
With the setup in place, the last nugget I’d like to leave you with is to point out that Advent of Code is a very popular coding event with hundreds of thousands of participants each year. One of the implications to this is that, if you’re even remotely on pace with the daily puzzles, there’s bound to be numerous other people working on the same puzzle as you are at the same time. In addition, there’s a lot of content out there for at least the mainstream programming languages. I mentioned at the top that I write up my solutions each year, but I’m definitely not the only (or even the most interesting) source of write-ups out there. For Kotlin, I’ve already seen that JetBrains has a set of livestreams planned, and I know they’re not the only ones. In addition, the r/adventofcode Sub-Reddit is extremely welcoming and hosts a daily “Solution MegaThread” in addition to encouraging participants to post questions about puzzles they’re having trouble with. One “pro-tip” that I’d offer is that, if you’re stuck on one of the daily puzzles, go scan the Solution MegaThread and just see what people are saying about the puzzle. Often, without even looking at their code, you can get enough of a hint to get you through. Alternatively, you can read someone else’s solution in a different language.
The Wrap-Up
I’ll close by pointing out once again that Advent of Code is a lot less structured than some other coding puzzles out there. This is by design. Advent of Code, according to the creator, is meant to be a tool for learning in community. My experience has been that this is absolutely true and the most satisfying way for me to engage with this event. I encourage everyone who is even vaguely interested in writing code and solving puzzles to participate and to participate to the end. Even if you need to read a blog post or watch a video to understand and implement a solution to one (or more) of the puzzles, as long as you’re learning, you’re getting the benefit of Advent of Code. Frankly, I’d say you’re probably learning something even if you’re copying someone else’s code, so long as you’re taking the time to understand what it’s doing (and probably re-factoring it to be easier to read along the way). Ultimately, Advent of Code should be fun and educational, so as long as that’s what you’re getting out of it, I’d say you’re on the right track.
I hope this brief introduction to setting up an Advent of Code project has been useful for you and helps smooth out some of the more tedious bits so you can get to the good stuff even faster. Happy coding, and Happy Holidays!