This is a self-contained starter project template for solving Advent of Code puzzles in Kotlin! It uses Kotest and AocKt to minimize boilerplate and provide a smooth development experience.
This is a simple Gradle project, with slightly modified source-set directories to specialize it for use in AoC. For each puzzle, you will have five files, distributed as such:
projectDir
├── inputs
│ └── aockt
│ └── y2015
│ └── d01
│ └── input.txt
│ └── solution_part1.txt
│ └── solution_part2.txt
├── solutions
│ └── aockt
│ └── y2015
│ └── Y2015D01.kt
└── tests
└── aockt
└── y2015
└── Y2015D01Test.kt
There are a few simple key points here:
- Solution implementations go into
solutions
. While it is not a requirement to split them into packages by year or have a specific naming convention for the class names, doing so helps with organising. - Unit tests go into
tests
and should have the same structure as thesolutions
source set. - Input data goes into
inputs
. The format for these is enforced and needs to be followed:- The directory for each day has the base path:
/aockt/y[year]/d[zeroPaddedDay]
- In that directory, the input and the solutions go in the respective text files:
input.txt
,solution_part1.txt
, andsolution_part2.txt
. - If a solution is not yet known, it can be skipped.
- The directory for each day has the base path:
NOTE: Please do NOT commit your puzzle inputs!
The inputs
files are git ignored by default to prevent accidental commits.
However, this means that you would have to re-download your inputs and solutions every time you clone from a
different machine.
To avoid this, consider using something like git-crypt
to make your inputs
available to you alone, while still allowing them to be checked in on a public repository.
This section will demonstrate how you would use this project when solving new puzzles. For example, to implement the first ever AoC puzzle, you would do the following.
Step 1: Create your Solution Stub
Create a class
or object
for your solution that implements the Solution
type.
There is no requirement as to how to name it or where to put it, but a good convention is to split them by year as a
package, and name them in the YxxxxDxx
format.
Create the following class in your solutions
directory:
package aockt.y2015
import io.github.jadarma.aockt.core.Solution
object Y2015D01 : Solution
Following the same logic, create the following class in your tests
directory:
package aockt.y2015
import io.github.jadarma.aockt.test.AdventDay
import io.github.jadarma.aockt.test.AdventSpec
@AdventDay(2015, 1, "Not Quite Lisp")
class Y2015D01Test : AdventSpec<Y2015D01>({})
Note that the @AdventDay
annotation is required so that the test knows where to look for puzzle inputs in step 4.
Step 2: Define your Test Cases
Read the puzzle text from the AoC website.
If it gives you example inputs and outputs (or if you come up with others as you develop), you can define them as test
cases using the shouldOutput
and shouldAllOutput
helper functions.
package aockt.y2015
import io.github.jadarma.aockt.test.AdventDay
import io.github.jadarma.aockt.test.AdventSpec
@AdventDay(2015, 1, "Not Quite Lisp")
class Y2015D01Test : AdventSpec<Y2015D01>({
partOne {
listOf("(())", "()()") shouldAllOutput 0
listOf("(((", "(()(()(", "))(((((") shouldAllOutput 3
listOf("())", "))(") shouldAllOutput -1
listOf(")))", ")())())") shouldAllOutput -3
}
})
Step 3: Implement your solution
Implement the partOne
function.
When you run your test, your solution will be validated against the examples you defined.
package aockt.y2015
import io.github.jadarma.aockt.core.Solution
object Y2015D01 : Solution {
override fun partOne(input: String) = 42 /* No Spoilers! */
}
Step 4: Test against real input
Download your puzzle input from the AoC website and place it in your inputs
directory.
This is the only place where structure is enforced! Following the project structure, it should be
added in inputs/aockt/y2015/d01/input.txt
.
If you run the test again, you will see a new test group called "The solution", with two tests inside it: "Is correct" and "Is reasonably efficient". Since we don't know the correct solution yet, the correctness test will be greyed out, with your unverified answer in parens.
If the examples pass, it's a good chance that is the correct answer! Submit it to the AoC website and earn your star!
Step 5: Record the solution in your inputs and refactor!
Once you have the correct solution, add it in inputs/aockt/y2015/d01/solution_part1.txt
.
Now the tests will know what the correct answer for your input.txt
is and allow you to refactor your code while
constantly checking that your code still works as intended.
Step 6: Repeat steps 2-5 for the second part
Move on to the second part.
Since you are focusing on partTwo
now, you can temporarily ignore all tests for partOne
, just to speed up the
feedback loop.
partOne(enable = false) { /* ... */ }
BONUS: Isolated Debug Run
For more complex puzzles, you might find yourself unable to determine why your solution behaves differently, and you
want to employ the debugger.
Since the AdventSpec
is just a fancy FunSpec
, you can simply define a standalone test, which you can
run on its own in debug mode using the gutter icon.
class Y2015D01Test : AdventSpec<Y2015D01>({
test("debug") {
solution.partOne("some input")
}
})
If you have the Kotest Plugin installed, the IDE will show a
'Run' gutter icon near any AdventSpec
in your code.
After that, you can re-run the same test using Ctrl+F5
, or pressing the Rerun button in the test tool window.
This provides the most comfortable feedback loop when solving new problems.
You may also run tests in bulk by right clicking on a test package.
You can also run your tests from Gradle.
To run a specific test, pass it via its FQN:
./gradlew test --tests 'aockt.y2023.Y9999D01Test'
You may also filter and run tests in bulk. For example, running all tests within the same year package:
./gradlew test --tests 'aockt.y2023'
If for whatever reason you want to run tests in bulk but skip the parts that have been marked as expensive, you can filter them out as well:
./gradlew test -Dkotest.tags='!Expensive'