Skip to content

Commit f82370b

Browse files
committed
Solution 2017-18, part 2 (Duet)
1 parent 05b0a57 commit f82370b

File tree

2 files changed

+149
-42
lines changed
  • src
    • main/kotlin/de/ronny_h/aoc/year2017/day18
    • test/kotlin/de/ronny_h/aoc/year2017/day18

2 files changed

+149
-42
lines changed

src/main/kotlin/de/ronny_h/aoc/year2017/day18/Duet.kt

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,67 @@ import de.ronny_h.aoc.AdventOfCode
44
import de.ronny_h.aoc.extensions.numbers.isInt
55
import de.ronny_h.aoc.extensions.numbers.toIntChecked
66
import de.ronny_h.aoc.year2017.day18.Instruction.*
7+
import kotlinx.coroutines.*
8+
import kotlinx.coroutines.channels.Channel
9+
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
10+
import kotlinx.coroutines.channels.ReceiveChannel
11+
import kotlinx.coroutines.channels.SendChannel
712

8-
fun main() = Duet().run(9423, 0)
13+
fun main() = Duet().run(9423, 7620)
914

1015
class Duet : AdventOfCode<Long>(2017, 18) {
11-
override fun part1(input: List<String>): Long = SoundPlayer(input.parseInstructions()).run()
16+
override fun part1(input: List<String>): Long = runBlocking {
17+
return@runBlocking Program(input).run()
18+
}
19+
20+
@OptIn(ExperimentalCoroutinesApi::class)
21+
override fun part2(input: List<String>): Long = runBlocking {
22+
val channel0 = Channel<Long>(UNLIMITED)
23+
val channel1 = Channel<Long>(UNLIMITED)
24+
val program0 = Program(input, 0, channel0, channel1)
25+
val program1 = Program(input, 1, channel1, channel0)
26+
27+
val job0 = launch { program0.run() }
28+
val job1 = launch { program1.run() }
29+
30+
launch {
31+
runDeadlockDetection(program0, program1, channel0, channel1)
32+
job0.cancelAndJoin()
33+
job1.cancelAndJoin()
34+
}
35+
36+
job0.join()
37+
println("program 0 completed")
38+
job1.join()
39+
println("program 1 completed")
1240

13-
override fun part2(input: List<String>): Long {
14-
return 0
41+
return@runBlocking program1.getNumberOfSends()
42+
}
43+
44+
@OptIn(ExperimentalCoroutinesApi::class)
45+
private suspend fun runDeadlockDetection(
46+
program0: Program,
47+
program1: Program,
48+
channel0: Channel<Long>,
49+
channel1: Channel<Long>,
50+
) {
51+
while (true) {
52+
delay(100)
53+
if (program0.isReceiving && program1.isReceiving && channel0.isEmpty && channel1.isEmpty) {
54+
println("deadlock detected!")
55+
return
56+
}
57+
}
1558
}
1659
}
1760

18-
fun List<String>.parseInstructions(): List<Instruction> = map {
61+
fun List<String>.parseInstructions(
62+
sendChannel: SendChannel<Long>?,
63+
receiveChannel: ReceiveChannel<Long>?
64+
): List<Instruction> = map {
1965
val parameters = it.substring(4)
2066
when (it.substring(0, 3)) {
21-
"snd" -> Sound(parameters)
67+
"snd" -> sendChannel?.let { Send(parameters.toValue(), sendChannel) } ?: Sound(parameters)
2268
"set" -> {
2369
val (register, value) = parameters.split(" ")
2470
SetValue(register, value.toValue())
@@ -39,13 +85,11 @@ fun List<String>.parseInstructions(): List<Instruction> = map {
3985
Modulo(register, value.toValue())
4086
}
4187

42-
"rcv" -> {
43-
Recover(parameters)
44-
}
88+
"rcv" -> receiveChannel?.let { Receive(parameters, receiveChannel) } ?: Recover(parameters)
4589

4690
"jgz" -> {
47-
val (register, value) = parameters.split(" ")
48-
JumpIfGreaterZero(register, value.toValue())
91+
val (value, offset) = parameters.split(" ")
92+
JumpIfGreaterZero(value.toValue(), offset.toValue())
4993
}
5094

5195
else -> error("unknown instruction: $it")
@@ -70,80 +114,114 @@ sealed interface Value {
70114
}
71115
}
72116

73-
sealed interface Instruction {
74-
fun executeOn(registers: MutableMap<String, Long>): Long
117+
private const val PROGRAM_NUMBER_REGISTER = "programNumber"
118+
private const val LAST_PLAYED_SOUND_REGISTER = "lastPlayedSound"
119+
private const val NUMBER_OF_SENDS_REGISTER = "numberOfSends"
75120

76-
companion object {
77-
private const val LAST_PLAYED_SOUND_REGISTER = "lastPlayedSound"
78-
}
121+
sealed interface Instruction {
122+
suspend fun executeOn(registers: MutableMap<String, Long>): Long
79123

80124
data class Sound(private val register: String) : Instruction {
81-
override fun executeOn(registers: MutableMap<String, Long>): Long {
125+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
82126
val frequency = registers.getValue(register)
83-
println("playing frequency $frequency")
84127
registers[LAST_PLAYED_SOUND_REGISTER] = frequency
85128
return frequency
86129
}
87130
}
88131

132+
data class Send(private val value: Value, private val channel: SendChannel<Long>) : Instruction {
133+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
134+
val toSend = value.toNumber(registers)
135+
channel.send(toSend)
136+
val numberOfSends = registers.getValue(NUMBER_OF_SENDS_REGISTER) + 1
137+
registers[NUMBER_OF_SENDS_REGISTER] = numberOfSends
138+
// println("${registers[PROGRAM_NUMBER_REGISTER]}: sent $toSend [$numberOfSends]")
139+
return toSend
140+
}
141+
}
142+
89143
data class SetValue(private val register: String, val value: Value) : Instruction {
90-
override fun executeOn(registers: MutableMap<String, Long>): Long {
144+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
91145
registers[register] = value.toNumber(registers)
92146
return registers.getValue(register)
93147
}
94148
}
95149

96150
data class Add(private val register: String, val value: Value) : Instruction {
97-
override fun executeOn(registers: MutableMap<String, Long>): Long {
151+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
98152
registers[register] = registers.getValue(register) + value.toNumber(registers)
99153
return registers.getValue(register)
100154
}
101155
}
102156

103157
data class Multiply(private val register: String, val value: Value) : Instruction {
104-
override fun executeOn(registers: MutableMap<String, Long>): Long {
158+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
105159
registers[register] = registers.getValue(register) * value.toNumber(registers)
106160
return registers.getValue(register)
107161
}
108162
}
109163

110164
data class Modulo(private val register: String, val value: Value) : Instruction {
111-
override fun executeOn(registers: MutableMap<String, Long>): Long {
165+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
112166
registers[register] = registers.getValue(register) % value.toNumber(registers)
113167
return registers.getValue(register)
114168
}
115169
}
116170

117171
data class Recover(val register: String) : Instruction {
118-
override fun executeOn(registers: MutableMap<String, Long>): Long {
172+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
119173
if (registers.getValue(register) != 0L) {
120-
val frequency = registers.getValue(LAST_PLAYED_SOUND_REGISTER)
121-
println("recover last played sound's frequency: $frequency")
122-
return frequency
174+
return registers.getValue(LAST_PLAYED_SOUND_REGISTER)
123175
}
124176
return 0
125177
}
126178
}
127179

128-
data class JumpIfGreaterZero(private val register: String, val value: Value) : Instruction {
129-
override fun executeOn(registers: MutableMap<String, Long>): Long {
130-
if (registers.getValue(register) > 0) {
131-
return value.toNumber(registers)
180+
data class Receive(private val register: String, private val channel: ReceiveChannel<Long>) : Instruction {
181+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
182+
val value = channel.receive()
183+
// println("${registers[PROGRAM_NUMBER_REGISTER]}: received $value")
184+
registers[register] = value
185+
return value
186+
}
187+
}
188+
189+
data class JumpIfGreaterZero(private val value: Value, private val offset: Value) : Instruction {
190+
override suspend fun executeOn(registers: MutableMap<String, Long>): Long {
191+
if (value.toNumber(registers) > 0) {
192+
return offset.toNumber(registers)
132193
}
133194
return 1
134195
}
135196
}
136197
}
137198

138-
class SoundPlayer(private val instructions: List<Instruction>) {
139-
private val registers = mutableMapOf<String, Long>().withDefault { 0 }
199+
class Program(
200+
input: List<String>,
201+
programNumber: Long = 0,
202+
sendChannel: Channel<Long>? = null,
203+
receiveChannel: Channel<Long>? = null,
204+
) {
205+
var isReceiving = false
206+
207+
private val instructions = input.parseInstructions(sendChannel, receiveChannel)
208+
private val registers = mutableMapOf(
209+
"p" to programNumber,
210+
PROGRAM_NUMBER_REGISTER to programNumber,
211+
).withDefault { 0 }
140212
private var instructionPointer = 0L
141213

142-
fun run(): Long {
214+
suspend fun run(): Long {
215+
println("program ${registers[PROGRAM_NUMBER_REGISTER]} started")
143216
while (instructionPointer in instructions.indices) {
144217
val instruction = instructions[instructionPointer.toIntChecked()]
218+
if (instruction is Receive) {
219+
isReceiving = true
220+
}
145221
val result = instruction.executeOn(registers)
222+
isReceiving = false
146223
if (instruction is Recover && registers.getValue(instruction.register) != 0L) {
224+
println("program ${registers[PROGRAM_NUMBER_REGISTER]}: instruction \"Recover\" with register not zero -> terminating")
147225
return result
148226
}
149227
instructionPointer += if (instruction is JumpIfGreaterZero) {
@@ -152,6 +230,9 @@ class SoundPlayer(private val instructions: List<Instruction>) {
152230
1L
153231
}
154232
}
233+
println("program ${registers[PROGRAM_NUMBER_REGISTER]}: instruction pointer ran out of bounds: $instructionPointer -> terminating")
155234
return -1L
156235
}
236+
237+
fun getNumberOfSends(): Long = registers.getValue(NUMBER_OF_SENDS_REGISTER)
157238
}

src/test/kotlin/de/ronny_h/aoc/year2017/day18/DuetTest.kt

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import de.ronny_h.aoc.year2017.day18.Value.Number
66
import de.ronny_h.aoc.year2017.day18.Value.Register
77
import io.kotest.core.spec.style.StringSpec
88
import io.kotest.matchers.shouldBe
9+
import kotlinx.coroutines.channels.Channel
10+
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
911

1012
class DuetTest : StringSpec({
1113

12-
val input = """
14+
val input1 = """
1315
set a 1
1416
add a 2
1517
mul a a
@@ -22,27 +24,51 @@ class DuetTest : StringSpec({
2224
jgz a -2
2325
""".asList()
2426

25-
"instructions can be parsed" {
26-
input.parseInstructions() shouldBe listOf(
27+
"instructions with sound can be parsed" {
28+
input1.parseInstructions(null, null) shouldBe listOf(
2729
SetValue("a", Number(1)),
2830
Add("a", Number(2)),
2931
Multiply("a", Register("a")),
3032
Modulo("a", Number(5)),
3133
Sound("a"),
3234
SetValue("a", Number(0)),
3335
Recover("a"),
34-
JumpIfGreaterZero("a", Number(-1)),
36+
JumpIfGreaterZero(Register("a"), Number(-1)),
3537
SetValue("a", Number(1)),
36-
JumpIfGreaterZero("a", Number(-2)),
38+
JumpIfGreaterZero(Register("a"), Number(-2)),
3739
)
3840
}
3941

4042
"part 1: the value of the recovered frequency the first time a rcv instruction is executed with a non-zero value" {
41-
Duet().part1(input) shouldBe 4
43+
Duet().part1(input1) shouldBe 4
4244
}
4345

44-
"part 2" {
45-
val input = listOf("")
46-
Duet().part2(input) shouldBe 0
46+
val input2 = """
47+
snd 1
48+
snd 2
49+
snd p
50+
rcv a
51+
rcv b
52+
rcv c
53+
rcv d
54+
""".asList()
55+
56+
"instructions with send and receive channels can be parsed" {
57+
val channel0 = Channel<Long>(UNLIMITED)
58+
val channel1 = Channel<Long>(UNLIMITED)
59+
60+
input2.parseInstructions(channel0, channel1) shouldBe listOf(
61+
Send(Number(1), channel0),
62+
Send(Number(2), channel0),
63+
Send(Register("p"), channel0),
64+
Receive("a", channel1),
65+
Receive("b", channel1),
66+
Receive("c", channel1),
67+
Receive("d", channel1),
68+
)
69+
}
70+
71+
"part 2: the number of times program 1 sent a value before a deadlock occurs" {
72+
Duet().part2(input2) shouldBe 3
4773
}
4874
})

0 commit comments

Comments
 (0)