-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
572 additions
and
0 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec21.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
172
advent/src/test/kotlin/org/elwaxoro/advent/y2019/Dec23.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.