Skip to content

Commit

Permalink
a little 2019
Browse files Browse the repository at this point in the history
  • Loading branch information
elwaxoro committed Jan 9, 2025
1 parent f71a7ad commit 7409231
Show file tree
Hide file tree
Showing 10 changed files with 572 additions and 0 deletions.
107 changes: 107 additions & 0 deletions advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec21.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.elwaxoro.advent.y2019

import kotlinx.coroutines.runBlocking
import org.elwaxoro.advent.PuzzleDayTester

class Dec21 : PuzzleDayTester(21, 2019) {

/**
* Droid notes from examples
* droid jump covers 4 tiles (landing on 5th)
* ABCD refers to the 4 tiles directly in front of the droid at every step
* From the sample, NOT D J means "if D is not ground, jump"
* This causes the droid to jump into the hole, 4 tiles away from where the jump starts
*
* NOT C J <--- is C is a hole?
* AND D J <--- C is a hole AND D is solid
* NOT A T <--- there is a hole right in front of us!
* OR T J <--- if either one got set: JUMP
*/
override fun part1(): Any = runBlocking {
SpringDroid(loadToLong(delimiter = ",")).runner(
"""
NOT C J
AND D J
NOT A T
OR T J
WALK
""".trimIndent().toMutableList()
)
} == 19355645L

/**
* RUN don't WALK!
* now have access to tiles EFGHI
* Rules are similar, but we can look farther ahead and avoid falling into stupid spots
*
* NOT C J <--- is C a hole?
* AND D J <--- C is a hole AND D is solid
* AND H J <--- H is also solid (4 away from landing on D) this is a safe 2 jump chain
* NOT B T <--- is B a hole?
* AND D T <--- B is a hole AND D is solid
* OR T J <--- if either is set to jump, JUMP
* NOT A T <--- jump or die, it's right in front of us
* OR T J <--- last check, JUMP
* RUN <--- FAST MODE GO
*/
override fun part2(): Any = runBlocking {
SpringDroid(loadToLong(delimiter = ",")).runner(
"""
NOT C J
AND D J
AND H J
NOT B T
AND D T
OR T J
NOT A T
OR T J
RUN
""".trimIndent().toMutableList()
)
} == 1137899149L

private class SpringDroid(
val code: List<Long>
) {

var lastOutput: Long = 0
var outputStr = ""

val input = """
NOT C J
AND D J
NOT A T
OR T J
WALK
""".trimIndent().toList().map { it.code }.toMutableList()

fun output(out: Long) {
lastOutput = out
val char = out.toInt().toChar()
outputStr += char
if (char == '\n') {
println(outputStr)
outputStr = ""
}
}

fun input(): Long =
if (input.isNotEmpty()) {
input.removeFirst().toLong()
} else {
0L
}

suspend fun runner(input: MutableList<Char>) =
ElfCode(code).runner(
setup = ElfCode.memExpander(5000),
input = { input.removeFirst().code.toLong() },
output = { output(it) },
).let {
lastOutput
}
}
}
44 changes: 44 additions & 0 deletions advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec22.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.elwaxoro.advent.y2019

import org.elwaxoro.advent.PuzzleDayTester
import org.elwaxoro.advent.remove

/**
* Day 22: Slam Shuffle
*/
class Dec22 : PuzzleDayTester(22, 2019) {

override fun part1(): Any = load().fold((0..10006).toList()) { deck, shuffle ->
if (shuffle == "deal into new stack") {
deck.stack()
} else if (shuffle.startsWith("cut")) {
deck.cut(shuffle.remove("cut ").toInt())
} else if (shuffle.startsWith("deal with increment")) {
deck.deal(shuffle.remove("deal with increment ").toInt())
} else {
throw IllegalStateException("I Dont' know what $shuffle is")
}
}.indexOf(2019)

override fun part2(): Any = "no way am I working this out on my own, I used something from the subreddit just so I can move on with life"

private fun List<Int>.stack(): List<Int> = reversed()

private fun List<Int>.cut(n: Int): List<Int> =
if (n > 0) {
drop(n) + take(n)
} else {
takeLast(-1 * n) + dropLast(-1 * n)
}

private fun List<Int>.deal(n: Int): List<Int> {
val l = toMutableList()
val out = IntArray(size)
var i = 0
while (l.isNotEmpty()) {
out[i] = l.removeFirst()
i = (i + n) % size
}
return out.toList()
}
}
172 changes: 172 additions & 0 deletions advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec23.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package org.elwaxoro.advent.y2019

import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.runBlocking
import org.elwaxoro.advent.PuzzleDayTester

/**
* Day 23: Category Six
*/
class Dec23 : PuzzleDayTester(23, 2019) {

override fun part1(): Any = runTheThing(NAT()) == 22829L

override fun part2(): Any = runTheThing(NAT2()) == 15678L

/**
* This is part 1, refactored to have a NAT which owns the 255 channel
*/
private fun runTheThing(nat: NAT) = runBlocking {
val code = loadToLong(delimiter = ",")

val network = (0..49).associateWith {
Channel<Packet>(capacity = Channel.UNLIMITED).also { c -> c.send(Packet(-1, it, listOf(it.toLong()))) }
}.toMutableMap()

val computers = (0..49).map { Computer(code, it, network) }

// setup the NAT
nat.network = network
nat.computers = computers
network[nat.address] = Channel(capacity = Channel.UNLIMITED)

// fire everything up!
computers.plus(nat).map {
async { it.run() }
}.joinAll()

nat.output
}

private data class Packet(
val from: Int,
val to: Int,
val data: List<Long>
)

private interface SuspendRunner {
suspend fun run()
}

private open class NAT(
val address: Int = 255,
var network: Map<Int, Channel<Packet>> = mapOf(),
var computers: List<Computer> = listOf(),
var output: Long = -1,
) : SuspendRunner {

/**
* Part 1:
* Just wait for a packet to show up, done
*/
override suspend fun run() {
while (output < 0) {
delay(1)
network[address]!!.tryReceive().getOrNull()?.let {
output = it.data.last()
}
// safety cutoff in case things get stuck, don't run forever
if (computers.all { it.isIdle() }) {
kill()
}
}
kill()
}

suspend fun kill() {
network.forEach { (k, v) ->
if (k != address) {
v.send(Packet(address, k, listOf(Long.MAX_VALUE)))
}
}
}
}

private class NAT2(
var lastPacket: Packet? = null,
var lastY: Long = -1,
) : NAT() {

/**
* Part 2:
* store last packet, wait for network idle, send last packet
* if last packet's Y is the same twice in a row: done
*/
override suspend fun run() {
while (output < 0) {
delay(1)
network[address]!!.tryReceive().getOrNull()?.let { lastPacket = it }
if (computers.all { it.isIdle() }) {
if (lastY == lastPacket?.data?.last()) {
output = lastY
} else {
val send = lastPacket!!
lastY = send.data.last()
network[0]?.send(send)
}
}
}
kill()
}
}

private class Computer(
val code: List<Long>,
val address: Int,
val network: Map<Int, Channel<Packet>>,
val inputBuffer: MutableList<Long> = mutableListOf(),
val outputBuffer: MutableList<Long> = mutableListOf(),
) : SuspendRunner {

var idle = 0

/**
* seems to work at 2, but cpu timing is maybe a factor here?
*/
fun isIdle(): Boolean = idle >= 2

/**
* Try to read a packet from the channel, dump it into the input buffer
* If input buffer is empty, increase idle counter and return -1
*/
suspend fun input(): Long {
delay(1) // always give up the thread
val res = network[address]!!.tryReceive()
res.getOrNull()?.let {
inputBuffer.addAll(it.data)
}

if (inputBuffer.isNotEmpty()) {
idle = 0
return inputBuffer.removeFirst()
} else {
idle++
return -1L
}
}

/**
* Wait for output buffer to have a full packet, then send it
*/
suspend fun output(out: Long) {
delay(1)
outputBuffer.add(out)
if (outputBuffer.size == 3) {
val packet = Packet(address, outputBuffer.removeFirst().toInt(), outputBuffer.take(2))
outputBuffer.clear()
network[packet.to]!!.send(packet)
}
}

override suspend fun run() {
ElfCode(code).runner(
setup = ElfCode.memExpander(500),
input = { input() },
output = { output(it) }
)
}
}
}
37 changes: 37 additions & 0 deletions advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec24.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.elwaxoro.advent.y2019

import org.elwaxoro.advent.*

/**
* Day 24: Planet of Discord
*/
class Dec24 : PuzzleDayTester(24, 2019) {

override fun part1(): Any = loader().let { bugs ->
val bounds = bugs.bounds()
val cycles = mutableSetOf<Set<Coord>>()
var next = bugs
while (!cycles.contains(next)) {
cycles.add(next)
next = next.cycle(bounds)
}
next.sumOf { bug -> 2 pow (bug.y * 5 + bug.x) }
}

override fun part2(): Any = "Another one I did not spend a ton of time with"

private fun Set<Coord>.cycle(bounds: Pair<Coord, Coord>): Set<Coord> =
this.flatMap { bug ->
val bugFree = bug.neighbors().filterNot { this.contains(it) }
// bugs die unless next to exactly one other bug
val newBug = bug.takeIf { bugFree.size == 3 }
// bugs auto-generate into an empty square with one or two bugs next to it
bugFree.map { nonBug ->
nonBug.takeIf { nonBug.neighbors().count { this.contains(it) } in (1..2) }
}.plus(newBug).filterNotNull().filter { bounds.contains(it) }
}.toSet()

private fun loader() = load().flatMapIndexed { y, row ->
row.mapIndexedNotNull { x, c -> Coord(x, y).takeIf { c == '#' } }
}.toSet()
}
Loading

0 comments on commit 7409231

Please sign in to comment.