Skip to content

Commit f672251

Browse files
committed
One more animation: 2025-04 (Printing Department)
* Fix a bug in GifSequenceWriter: The first row was not visible. * Add a new markdown file, dedicated to animations of grid-based solutions. * Add an animation for year 2025, day 4, part 2.
1 parent c9cd84c commit f672251

File tree

10 files changed

+70
-20
lines changed

10 files changed

+70
-20
lines changed

2025-07_laboratories-animation.gif

-473 KB
Binary file not shown.

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ Welcome to the Advent of Code[^aoc] Kotlin project created by [rhaendel][github]
44

55
In this repository, rhaendel is about to provide solutions for the puzzles using [Kotlin][kotlin] language.
66

7-
![Laboratories animation](2025-07_laboratories-animation.gif)
7+
![Laboratories animation](animations/2025-07_Laboratories.gif)
8+
9+
*More animated GIFs can be found at [here](animations/animations.md).*
810

911
## It's all about Learning
1012

@@ -48,16 +50,22 @@ If you're stuck with Kotlin-specific questions or anything related to this templ
4850
- [Kotlin Slack][slack]
4951
- Template [issue tracker][issues]
5052

51-
5253
[^aoc]:
53-
[Advent of Code][aoc] – An annual event of Christmas-oriented programming challenges started December 2015.
54-
Every year since then, beginning on the first day of December, a programming puzzle is published every day for twenty-five days.
55-
You can solve the puzzle and provide an answer using the language of your choice.
54+
[Advent of Code][aoc] – An annual event of Christmas-oriented programming challenges started December 2015.
55+
Every year since then, beginning on the first day of December, a programming puzzle is published every day for
56+
twenty-five days.
57+
You can solve the puzzle and provide an answer using the language of your choice.
5658

5759
[aoc]: https://adventofcode.com
60+
5861
[docs]: https://kotlinlang.org/docs/home.html
62+
5963
[github]: https://github.com/rhaendel
64+
6065
[issues]: https://github.com/kotlin-hands-on/advent-of-code-kotlin-template/issues
66+
6167
[kotlin]: https://kotlinlang.org
68+
6269
[slack]: https://surveys.jetbrains.com/s3/kotlin-slack-sign-up
70+
6371
[template]: https://github.com/kotlin-hands-on/advent-of-code-kotlin-template
106 KB
Loading
204 KB
Loading

animations/animations.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Animations
2+
3+
Some animated GIFs for Grid-based puzzles.
4+
5+
### Year 2025, Day 7 - Laboratories, Part 1
6+
7+
The animation shows a recursive algorithm calculating the path of a tachyon beam moving downwards, being split by
8+
different splitters until it exits the tachyon manifold at the bottom.
9+
10+
![Laboratories animation](2025-07_Laboratories.gif)
11+
12+
### Year 2025, Day 4 - Printing Department, Part 2
13+
14+
Iteratively, all accessible rolls of paper (those with fewer than four rolls of paper in their eight adjacent positions)
15+
are removed by a forklift.
16+
17+
![Printing Department animation](2025-04_PrintingDepartment.gif)
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package de.ronny_h.aoc.extensions.animation
22

33
import de.ronny_h.aoc.extensions.asList
4+
import io.github.oshai.kotlinlogging.KotlinLogging
45
import java.awt.Color
5-
import java.awt.Color.DARK_GRAY
66
import java.io.File
77

88
class AnimationRecorder {
99
private val frames = mutableListOf<List<String>>()
10+
private val logger = KotlinLogging.logger {}
1011

1112
fun record(frame: String) {
1213
frames.add(frame.asList())
1314
}
1415

15-
fun saveTo(fileName: String, colors: Map<Char, Color> = emptyMap(), background: Color = DARK_GRAY) = frames
16-
.map { it.createImage(colors, background) }
17-
.writeToGifFile(File(fileName))
16+
fun saveTo(fileName: String, colors: Map<Char, Color> = emptyMap(), background: Color = purpleBlue) {
17+
frames
18+
.map { it.createImage(colors, background) }
19+
.writeToGifFile(File(fileName))
20+
logger.info { "An animation was saved to: $fileName" }
21+
}
1822
}

src/main/kotlin/de/ronny_h/aoc/extensions/animation/GifSequenceWriter.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@ val darkGrey = Color(16, 16, 26)
2727

2828
private const val scale = 1
2929
private const val fontSize = scale * 20
30-
private const val drawStartX = 3
3130
private const val rowHeight = fontSize + scale
3231
private const val colWidth = 3 / 5.0 * fontSize
32+
private const val drawStartX = 3
33+
private const val drawStartY = rowHeight - 2
3334
private const val timeBetweenFramesMS = 250
3435
private val font = Font(MONOSPACED, PLAIN, fontSize)
3536

3637
fun List<String>.createImage(colors: Map<Char, Color>, background: Color): BufferedImage {
3738
val gifColors = colors.withDefault { LIGHT_GRAY }
3839
val width = 2 * drawStartX + (this.first().length * colWidth).toInt()
39-
val height = size * rowHeight - scale * 13
40+
val height = drawStartY + size * rowHeight - scale * 13
4041
val bufferedImage = BufferedImage(width, height, TYPE_INT_RGB)
4142
val graphics2D = bufferedImage.graphics as Graphics2D
4243
graphics2D.font = font
@@ -46,7 +47,7 @@ fun List<String>.createImage(colors: Map<Char, Color>, background: Color): Buffe
4647
forEachIndexed { rowNo, row ->
4748
row.forEachIndexed { colNo, char ->
4849
graphics2D.color = gifColors.getValue(char)
49-
graphics2D.drawString("$char", (drawStartX + colNo * colWidth).toInt(), rowNo * rowHeight)
50+
graphics2D.drawString("$char", (drawStartX + colNo * colWidth).toInt(), drawStartY + rowNo * rowHeight)
5051
}
5152
}
5253
return bufferedImage

src/main/kotlin/de/ronny_h/aoc/year2025/day04/PrintingDepartment.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
package de.ronny_h.aoc.year2025.day04
22

33
import de.ronny_h.aoc.AdventOfCode
4+
import de.ronny_h.aoc.extensions.animation.gray
45
import de.ronny_h.aoc.extensions.grids.SimpleCharGrid
56
import de.ronny_h.aoc.year2025.day04.PaperGrid.Companion.EMPTY
7+
import de.ronny_h.aoc.year2025.day04.PaperGrid.Companion.PAPER
8+
import java.awt.Color.white
69

710
fun main() = PrintingDepartment().run(1397, 8758)
811

912
class PrintingDepartment : AdventOfCode<Int>(2025, 4) {
1013
override fun part1(input: List<String>): Int = PaperGrid(input).accessiblePaperRollsCoordinates().count()
1114

15+
// find an animation at: /animations/2025-04_PrintingDepartment.gif
1216
override fun part2(input: List<String>): Int {
1317
val grid = PaperGrid(input)
18+
// grid.recorder = AnimationRecorder()
19+
val result = grid.removeAllAccessiblePaperRolls()
20+
grid.recorder?.saveTo(
21+
"animations/$year-${paddedDay()}_${javaClass.simpleName}.gif",
22+
mapOf(
23+
PAPER to white,
24+
EMPTY to gray,
25+
),
26+
)
27+
return result
28+
}
29+
30+
private fun PaperGrid.removeAllAccessiblePaperRolls(): Int {
1431
var total = 0
1532
while (true) {
16-
grid.accessiblePaperRollsCoordinates().toList().apply {
33+
accessiblePaperRollsCoordinates().toList().apply {
1734
if (isEmpty()) return total
18-
forEach { grid[it] = EMPTY }
35+
forEach { set(it, EMPTY) }
1936
total += size
2037
}
2138
}
@@ -25,7 +42,7 @@ class PrintingDepartment : AdventOfCode<Int>(2025, 4) {
2542
class PaperGrid(input: List<String>) : SimpleCharGrid(input, EMPTY) {
2643

2744
companion object {
28-
private const val PAPER = '@'
45+
const val PAPER = '@'
2946
const val EMPTY = '.'
3047
}
3148

src/main/kotlin/de/ronny_h/aoc/year2025/day07/Laboratories.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,29 @@ package de.ronny_h.aoc.year2025.day07
33
import de.ronny_h.aoc.AdventOfCode
44
import de.ronny_h.aoc.extensions.animation.gray
55
import de.ronny_h.aoc.extensions.animation.lightGreen
6-
import de.ronny_h.aoc.extensions.animation.purpleBlue
76
import de.ronny_h.aoc.extensions.grids.Coordinates
87
import de.ronny_h.aoc.extensions.grids.Direction.*
98
import de.ronny_h.aoc.extensions.grids.SimpleCharGrid
109
import java.awt.Color.white
10+
import java.awt.Color.yellow
1111

1212
fun main() = Laboratories().run(1600, 8632253783011)
1313

1414
class Laboratories : AdventOfCode<Long>(2025, 7) {
15+
16+
// find an animation at: /animations/2025-07_Laboratories.gif
1517
override fun part1(input: List<String>): Long {
1618
val diagram = TachyonManifoldDiagram(input)
1719
// diagram.recorder = AnimationRecorder()
1820
val splitterCount = diagram.followTheBeam().splitterCount
1921
diagram.recorder?.saveTo(
20-
"2025-07_laboratories-animation.gif",
22+
"animations/$year-${paddedDay()}_${javaClass.simpleName}.gif",
2123
mapOf(
24+
start to yellow,
2225
emptySpace to gray,
2326
splitter to lightGreen,
2427
beam to white,
2528
),
26-
purpleBlue,
2729
)
2830
return splitterCount
2931
}
@@ -36,15 +38,16 @@ data class TachyonManifoldInfo(val splitterCount: Long, val numberOfTimeLines: L
3638
TachyonManifoldInfo(splitterCount + other.splitterCount, numberOfTimeLines + other.numberOfTimeLines)
3739
}
3840

41+
private const val start = 'S'
3942
private const val emptySpace = '.'
4043
private const val splitter = '^'
4144
private const val beam = '|'
4245
private const val outOfMap = '#'
4346

4447
class TachyonManifoldDiagram(input: List<String>) : SimpleCharGrid(input, outOfMap) {
45-
private val start = find('S')
48+
private val startLocation = find(start)
4649

47-
fun followTheBeam(): TachyonManifoldInfo = followTheBeam(start + SOUTH)
50+
fun followTheBeam(): TachyonManifoldInfo = followTheBeam(startLocation + SOUTH)
4851

4952
private val timelinesStartingAt = mutableMapOf<Coordinates, Long>()
5053

src/test/resources/reference.gif

794 Bytes
Loading

0 commit comments

Comments
 (0)