Advent of Code 2025 - Day 9, in Kotlin - Movie Theater
Kotlin solutions to parts 1 and 2 of Advent of Code 2025, Day 9: 'Movie Theater'
I ended up getting some help from /r/adventofcode on Reddit to solve part 2. I used a different approach that was a huge mess and I was not proud of, so you’ll never ever see it. The end result is something that I understand and can explain better than my first hack at it, and I like the way this code looks. We’ll also end up with a reusable Rectangle class in case we need it for one of the remaining three puzzles this year.
If you’d rather just view the code, my GitHub Repository is here.
Puzzle Input
Before we parse our input, let’s make our own Rectangle class to make both parts of today’s puzzle easier. We’ll provide an of function to create a Rectangle from two Point2D objects, but the rectangle itself will be represented by two IntRange objects, one for x and one for y.
We will also include a property for the area of the rectangle.
class Rectangle(val x: IntRange, val y: IntRange) {
val area: Long =
x.size().toLong() * y.size()
companion object {
fun of(a: Point2D, b: Point2D): Rectangle =
Rectangle(
min(a.x, b.x)..max(a.x, b.x),
min(a.y, b.y)..max(a.y, b.y),
)
}
}
With that in order, we can parse our input into a List<Point2D> using the Point2D.of() function we wrote a few days ago. We can also create a full list of rectangles by pairing up each of the points to make every possible rectangle, storing them in a List<Rectangle> called… wait for it… rectangles.
class Day09(input: List<String>) {
private val points: List<Point2D> = input.map { Point2D.of(it) }
private val rectangles: List<Rectangle> = points.flatMapIndexed { index, left ->
points.drop(index + 1).map { right ->
Rectangle.of(left, right)
}
}.sortedByDescending { it.area }
}
Note that we sort the rectangles from largest to smallest because both parts of the puzzle care about the largest rectangles.
We’ll make some additions to Rectangle when we get to part 2.
⭐ Day 9, Part 1
The puzzle text can be found here.
Because we have all the rectangles, we can get the rectangle with the largest area (the first() one in the list!), returning it for our answer to part 1.
// In Day09
fun solvePart1(): Long =
rectangles.first().area
Star earned! Onward!
⭐ Day 9, Part 2
The puzzle text can be found here.
My original approach to this part was exceptionally messy and hard to follow, to say the least. I turned to Reddit’s wonderful /r/adventofcode forum for help and found a few people converging on the same strategy, which I’ll implement below. Basically, because the input doesn’t create any null/empty areas within a large polygon, we can detect the largest rectangle as the one that does not cross over any of the borders/lines. Outside edges are fine, but any line that pierces the rectangle beyond the outer border makes that specific rectangle off limits.
To solve this part of the puzzle, we’ll need a couple of helper functions. First, we need to determine if two IntRange object overlap, so we’ll write an extension function called IntRange.overlaps() to give us that answer.
// In Extensions.kt
fun IntRange.overlaps(other: IntRange): Boolean =
max(first, other.first) <= min(last, other.last)
We also need to know the full size of an IntRange, so we’ll add a size() extension function. This is similar to the work we did the other day with LongRange, but I opted not to create an extension. This really feels like something Kotlin should have in the standard library.
// In Extensions.kt
fun IntRange.size(): Int =
last - first +1
We need to know when one Rectangle overlaps another, so we’ll add an overlaps(Rectangle) function to the Rectangle class we created earlier. It uses the IntRange.overlaps() function we just wrote.
// In Rectangle
fun overlaps(other: Rectangle): Boolean =
x.overlaps(other.x) && y.overlaps(other.y)
We also need to shrink our rectangles by one in each direction to get the inner rectangle. Since we’re using ranges, we can create a new Rectangle with x and y ranges that are +1 on the low end of the ranges and -1 on the high end of the ranges. Note that we use ..< to create an exclusive range, rather than .. and subtracting 1. The result comes out the same either way, and I wanted to show you that ..< exists.
// In Rectangle
fun inner(): Rectangle =
Rectangle(
x.first + 1..<x.last,
y.first + 1..<y.last
)
To solve the puzzle, we need to create the lines, which we will model as Rectangles. Each line is a 1-width rectangle of some kind. Because the lines need to form a loop, we add the first() point to the end of the points list (to form a circle), call zipWithNext to pair all of the Point2D objects together, and turn them into a Rectangle.
// In Day09
fun solvePart2(): Long {
val lines: List<Rectangle> = (points + points.first())
.zipWithNext()
.map { (left, right) -> Rectangle.of(left, right) }
return rectangles.first { rectangle ->
val inner = rectangle.inner()
lines.none { line -> line.overlaps(inner) }
}.area
}
All that’s left is to find the first rectangle whose inner rectangle does not overlap with any of the lines.
Star earned! See you tomorrow!
Further Reading
- Index of All Solutions - All posts and solutions for 2025, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 9
- Advent of Code - Come join in and do these challenges yourself!