Skip to content

Commit 3c0037f

Browse files
committed
AOC - Day 20
1 parent 22b3b49 commit 3c0037f

File tree

3 files changed

+2047
-0
lines changed

3 files changed

+2047
-0
lines changed

src/main/java/Exercise20-part2.kt

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import kotlin.math.sqrt
2+
import kotlin.random.Random
3+
4+
fun main() {
5+
val tiles = object {}.javaClass.getResource("input-20.txt").readText()
6+
.split("\n\n")
7+
.map {
8+
val number = it.split("\n")[0]
9+
.replace(":", "")
10+
.replace("Tile ", "")
11+
.toLong()
12+
val top = it.split("\n")[1]
13+
val bottom = it.split("\n").last()
14+
val (left, right) = it.split("\n").drop(1)
15+
.map {
16+
it.toCharArray().let { it.first() to it.last() }
17+
}
18+
.fold("" to "") { acc, pair ->
19+
(acc.first + pair.first) to (acc.second + pair.second)
20+
}
21+
val body = it.split("\n").drop(1)
22+
.map { it.replace("\n", "") }
23+
.map { it.toCharArray() }
24+
.toTypedArray()
25+
Tile(number, top, bottom, left, right, body)
26+
}
27+
28+
val size = sqrt(tiles.size.toDouble()).toInt()
29+
val world = Array(size) {
30+
Array(size) { Tile(0, "", "", "", "", arrayOf()) }
31+
}
32+
33+
// Let's start to create the world from 0,0.
34+
// We pick a tile that we know having a neighbor on the right
35+
world[0][0] = tiles.filter { tiles.countNeighbor(it) == 2 }
36+
.first { tiles.countNeighbor(it, listOf(it.right)) == 1 }
37+
38+
var current = 0 to 0
39+
40+
while (world.hasEmptySpots()) {
41+
// We either move to the tile right or bottom and we scan
42+
// line the entire world till is completed.
43+
val next = findNextEmptySpot(current, size)
44+
val neighbor = tiles.findNeighbor(world, current, next)
45+
world[next.first][next.second] = neighbor
46+
current = next
47+
}
48+
49+
val tileSize = world[0][0].body.size - 2
50+
val merged = Array(world.size * tileSize) { CharArray(world.size * tileSize) }
51+
52+
for (i in world.indices) {
53+
for (j in world.indices) {
54+
// Copy tile i,j in the merged world
55+
val toCopy = world[i][j].body
56+
for (k in 1 until toCopy.size - 1) {
57+
for (l in 1 until toCopy.size - 1) {
58+
merged[(k - 1) + (tileSize * i)][(l - 1) + (tileSize * j)] = toCopy[k][l]
59+
}
60+
}
61+
}
62+
}
63+
64+
// #
65+
//# ## ## ###
66+
// # # # # # #
67+
val dragonPattern = listOf(
68+
0 to 0,
69+
+1 to +1,
70+
+4 to +1,
71+
+7 to +1,
72+
+10 to +1,
73+
+13 to +1,
74+
+16 to +1,
75+
+5 to 0,
76+
+6 to 0,
77+
+11 to 0,
78+
+12 to 0,
79+
+17 to 0,
80+
+18 to 0,
81+
+18 to -1,
82+
+19 to 0,
83+
)
84+
85+
var found = false
86+
while (!found) {
87+
for (i in merged.indices) {
88+
for (j in merged.indices) {
89+
if (merged[i][j] == '#') {
90+
val matches = dragonPattern.count {
91+
runCatching {
92+
merged[i + it.first][j + it.second] == '#'
93+
}.getOrDefault(false)
94+
}
95+
96+
// Dragon found! Let's stop the cycle once we found all of them
97+
if (matches == dragonPattern.size) {
98+
found = true
99+
dragonPattern.onEach {
100+
merged[i + it.first][j + it.second] = '0'
101+
}
102+
}
103+
}
104+
}
105+
}
106+
if (!found) {
107+
// Dragon not found! Let's try to rotate/flip the world
108+
when(Random.nextInt(3)) {
109+
0 -> merged.flipHorizontally()
110+
1 -> merged.flipVertically()
111+
else -> merged.rotatedRight()
112+
}
113+
}
114+
}
115+
116+
merged.sumBy { line -> line.count { it == '#'} }.also(::println)
117+
}
118+
119+
private fun List<Tile>.findNeighbor(world: Array<Array<Tile>>, current: Pair<Int, Int>, next: Pair<Int, Int>): Tile {
120+
val input = if (next.second == current.second + 1) {
121+
world[current.first][current.second]
122+
} else {
123+
world[next.first - 1][next.second]
124+
}
125+
val side = if (next.second == current.second + 1) input.right else input.bottom
126+
127+
if (next.second == current.second + 1) {
128+
// Regular sides
129+
this.firstOrNull { it.number != input.number && it.left == side }
130+
?.let { return it }
131+
this.firstOrNull { it.number != input.number && it.right == side }
132+
?.let { it.flipVertically(); return it }
133+
this.firstOrNull { it.number != input.number && it.top == side }
134+
?.let { it.rotateLeft(); it.flipHorizontally(); return it }
135+
this.firstOrNull { it.number != input.number && it.bottom == side }
136+
?.let { it.rotateRight(); return it }
137+
// Reversed
138+
this.firstOrNull { it.number != input.number && it.left.reversed() == side }
139+
?.let { it.flipHorizontally(); return it }
140+
this.firstOrNull { it.number != input.number && it.right.reversed() == side }
141+
?.let { it.rotateLeft(); it.rotateLeft(); return it }
142+
this.firstOrNull { it.number != input.number && it.top.reversed() == side }
143+
?.let { it.rotateLeft(); return it }
144+
this.firstOrNull { it.number != input.number && it.bottom.reversed() == side }
145+
?.let { it.rotateRight(); it.flipHorizontally(); return it }
146+
} else {
147+
// Regular sides
148+
this.firstOrNull { it.number != input.number && it.top == side }
149+
?.let { return it }
150+
this.firstOrNull { it.number != input.number && it.bottom == side }
151+
?.let { it.flipHorizontally(); return it }
152+
this.firstOrNull { it.number != input.number && it.left == side }
153+
?.let { it.rotateRight(); it.flipVertically(); return it }
154+
this.firstOrNull { it.number != input.number && it.right == side }
155+
?.let { it.rotateLeft(); return it }
156+
// Reversed
157+
this.firstOrNull { it.number != input.number && it.top.reversed() == side }
158+
?.let { it.flipVertically(); return it }
159+
this.firstOrNull { it.number != input.number && it.bottom.reversed() == side }
160+
?.let { it.rotateRight(); it.rotateRight(); return it }
161+
this.firstOrNull { it.number != input.number && it.left.reversed() == side }
162+
?.let { it.rotateRight(); return it }
163+
this.firstOrNull { it.number != input.number && it.right.reversed() == side }
164+
?.let { it.rotateLeft(); it.flipVertically(); return it }
165+
}
166+
error("Neighbor not found!")
167+
}
168+
169+
private fun findNextEmptySpot(current: Pair<Int, Int>, size: Int): Pair<Int, Int> {
170+
return if (current.second < size - 1) {
171+
current.first to current.second + 1
172+
} else {
173+
current.first + 1 to 0
174+
}
175+
}
176+
177+
private fun Array<Array<Tile>>.hasEmptySpots() = this.any { line -> line.any { it.number == 0L } }
178+
179+

src/main/java/Exercise20.kt

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
fun main() {
2+
val tiles = object {}.javaClass.getResource("input-20.txt").readText()
3+
.split("\n\n")
4+
.map {
5+
val number = it.split("\n")[0]
6+
.replace(":", "")
7+
.replace("Tile ", "")
8+
.toLong()
9+
val top = it.split("\n")[1]
10+
val bottom = it.split("\n").last()
11+
val (left, right) = it.split("\n").drop(1)
12+
.map {
13+
it.toCharArray().let { it.first() to it.last() }
14+
}
15+
.fold("" to "") { acc, pair ->
16+
(acc.first + pair.first) to (acc.second + pair.second)
17+
}
18+
val body = it.split("\n").drop(1)
19+
.map { it.replace("\n", "") }
20+
.map { it.toCharArray() }
21+
.toTypedArray()
22+
Tile(number, top, bottom, left, right, body)
23+
}
24+
25+
tiles.filter {
26+
tiles.countNeighbor(it) == 2
27+
}.fold(1L) { acc, tile ->
28+
acc * tile.number
29+
}.also(::println)
30+
31+
}
32+
33+
fun List<Tile>.countNeighbor(
34+
input: Tile,
35+
sides: List<String> = listOf(input.left, input.top, input.right, input.bottom)
36+
): Int {
37+
return sides.filter { regularSide ->
38+
val reversedSide = regularSide.reversed()
39+
this.any {
40+
it.number != input.number && (
41+
it.top == regularSide ||
42+
it.top == reversedSide ||
43+
it.bottom == regularSide ||
44+
it.bottom == reversedSide ||
45+
it.left == regularSide ||
46+
it.left == reversedSide ||
47+
it.right == regularSide ||
48+
it.right == reversedSide)
49+
}
50+
}.count()
51+
}
52+
53+
class Tile(
54+
val number: Long,
55+
var top: String,
56+
var bottom: String,
57+
var left: String,
58+
var right: String,
59+
var body: Array<CharArray>
60+
) {
61+
private val n: Int get() = body.size
62+
63+
fun rotateRight() {
64+
body.rotatedRight()
65+
recomputeStrings()
66+
}
67+
68+
fun rotateLeft() {
69+
body.rotatedLeft()
70+
recomputeStrings()
71+
}
72+
73+
fun flipHorizontally() {
74+
body.flipHorizontally()
75+
recomputeStrings()
76+
}
77+
78+
fun flipVertically() {
79+
body.flipVertically()
80+
recomputeStrings()
81+
}
82+
83+
private fun recomputeStrings() {
84+
top = body[0].joinToString("")
85+
bottom = body.last().joinToString("")
86+
left = body.map { it.first() }.joinToString("")
87+
right = body.map { it.last() }.joinToString("")
88+
}
89+
}
90+
91+
fun Array<CharArray>.flipHorizontally() {
92+
val n = this.size
93+
for (i in 0 until n / 2) {
94+
for (j in 0 until n) {
95+
val t = this[i][j]
96+
this[i][j] = this[n - i - 1][j]
97+
this[n - i - 1][j] = t
98+
}
99+
}
100+
}
101+
102+
fun Array<CharArray>.flipVertically() {
103+
val n = this.size
104+
for (i in 0 until n / 2) {
105+
for (j in 0 until n) {
106+
val t = this[j][i]
107+
this[j][i] = this[j][n - i - 1]
108+
this[j][n - i - 1] = t
109+
}
110+
}
111+
}
112+
113+
fun Array<CharArray>.rotatedRight() {
114+
val n = this.size
115+
val rotated = Array(n) { CharArray(n) }
116+
for (i in 0 until n) {
117+
for (j in 0 until n) {
118+
rotated[i][j] = this[n - j - 1][i]
119+
}
120+
}
121+
for (i in 0 until n) {
122+
for (j in 0 until n) {
123+
this[i][j] = rotated[i][j]
124+
}
125+
}
126+
}
127+
128+
fun Array<CharArray>.rotatedLeft() {
129+
val n = this.size
130+
val rotated = Array(n) { CharArray(n) }
131+
for (i in 0 until n) {
132+
for (j in 0 until n) {
133+
rotated[i][j] = this[j][n - i - 1]
134+
}
135+
}
136+
for (i in 0 until n) {
137+
for (j in 0 until n) {
138+
this[i][j] = rotated[i][j]
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)