Skip to content

Commit 8c38657

Browse files
committed
feat: Solving day17
1 parent 8d21a04 commit 8c38657

File tree

5 files changed

+287
-0
lines changed

5 files changed

+287
-0
lines changed

docs/day17.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
url: "https://adventofcode.com/2024/day/17"
3+
---
4+
5+
## Day 17: Chronospatial Computer
6+
7+
The Historians push the button on their strange device, but this time, you all just feel like you're falling.
8+
9+
"Situation critical", the device announces in a familiar voice. "Bootstrapping process failed. Initializing debugger...."
10+
11+
The small handheld device suddenly unfolds into an entire computer! The Historians look around nervously before one of them tosses it to you.
12+
13+
This seems to be a 3-bit computer: its program is a list of 3-bit numbers (0 through 7), like `0,1,2,3`. The computer also has three registers named `A`, `B`, and `C`, but these registers aren't limited to 3 bits and can instead hold any integer.
14+
15+
The computer knows eight instructions, each identified by a 3-bit number (called the instruction's opcode). Each instruction also reads the 3-bit number after it as an input; this is called its operand.
16+
17+
A number called the instruction pointer identifies the position in the program from which the next opcode will be read; it starts at 0, pointing at the first 3-bit number in the program. Except for jump instructions, the instruction pointer increases by `2` after each instruction is processed (to move past the instruction's opcode and its operand). If the computer tries to read an opcode past the end of the program, it instead halts.
18+
19+
So, the program `0,1,2,3` would run the instruction whose opcode is `0` and pass it the operand `1`, then run the instruction having opcode `2` and pass it the operand `3`, then halt.
20+
21+
There are two types of operands; each instruction specifies the type of its operand. The value of a literal operand is the operand itself. For example, the value of the literal operand `7` is the number `7`. The value of a combo operand can be found as follows:
22+
23+
* Combo operands `0` through `3` represent literal values `0` through `3`.
24+
* Combo operand `4` represents the value of register `A`.
25+
* Combo operand `5` represents the value of register `B`.
26+
* Combo operand `6` represents the value of register `C`.
27+
* Combo operand `7` is reserved and will not appear in valid programs.
28+
29+
The eight instructions are as follows:
30+
31+
The `adv` instruction (opcode `0`) performs division. The numerator is the value in the `A` register. The denominator is found by raising 2 to the power of the instruction's combo operand. (So, an operand of `2` would divide `A` by 4 (`2^2`); an operand of 5 would divide `A` by `2^B`.) The result of the division operation is truncated to an integer and then written to the `A` register.
32+
33+
The `bxl` instruction (opcode `1`) calculates the bitwise XOR of register `B` and the instruction's literal operand, then stores the result in register `B`.
34+
35+
The `bst` instruction (opcode `2`) calculates the value of its combo operand modulo 8 (thereby keeping only its lowest 3 bits), then writes that value to the `B` register.
36+
37+
The `jnz` instruction (opcode `3`) does nothing if the `A` register is `0`. However, if the `A` register is not zero, it jumps by setting the instruction pointer to the value of its literal operand; if this instruction jumps, the instruction pointer is not increased by `2` after this instruction.
38+
39+
The `bxc` instruction (opcode `4`) calculates the bitwise XOR of register `B` and register `C`, then stores the result in register `B`. (For legacy reasons, this instruction reads an operand but ignores it.)
40+
41+
The `out` instruction (opcode `5`) calculates the value of its combo operand modulo 8, then outputs that value. (If a program outputs multiple values, they are separated by commas.)
42+
43+
The `bdv` instruction (opcode `6`) works exactly like the adv instruction except that the result is stored in the `B` register. (The numerator is still read from the `A` register.)
44+
45+
The `cdv` instruction (opcode `7`) works exactly like the adv instruction except that the result is stored in the `C` register. (The numerator is still read from the `A` register.)
46+
47+
Here are some examples of instruction operation:
48+
49+
* If register `C` contains `9`, the program `2,6` would set register `B` to `1`.
50+
* If register `A` contains `10`, the program `5,0,5,1,5,4` would output `0,1,2`.
51+
* If register `A` contains `2024`, the program `0,1,5,4,3,0` would output `4,2,5,6,7,7,7,7,3,1,0` and leave `0` in register `A`.
52+
* If register `B` contains `29`, the program `1,7` would set register `B` to `26`.
53+
* If register `B` contains `2024` and register `C` contains `43690`, the program `4,0` would set register `B` to `44354`.
54+
55+
The Historians' strange device has finished initializing its debugger and is displaying some information about the program it is trying to run (your puzzle input). For example:
56+
57+
```txt
58+
Register A: 729
59+
Register B: 0
60+
Register C: 0
61+
62+
Program: 0,1,5,4,3,0
63+
```
64+
65+
Your first task is to determine what the program is trying to output. To do this, initialize the registers to the given values, then run the given program, collecting any output produced by `out` instructions. (Always join the values produced by out instructions with commas.) After the above program halts, its final output will be `4,6,3,5,6,3,5,2,1,0`.
66+
67+
Using the information provided by the debugger, initialize the registers to the given values, then run the program. Once it halts, what do you get if you use commas to join the values it output into a single string?

src/day17/day17.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package day17
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
)
7+
8+
func Solve(input string) string {
9+
cmp := parseInput(input)
10+
for {
11+
if cmp.tick() {
12+
break
13+
}
14+
}
15+
return strings.Join(cmp.outBuffer, ",")
16+
}
17+
18+
func parseInput(input string) computer {
19+
lines := strings.Split(input, "\n")
20+
a, _ := strconv.Atoi(lines[0][12:])
21+
b, _ := strconv.Atoi(lines[1][12:])
22+
c, _ := strconv.Atoi(lines[2][12:])
23+
cmp := computer{regA: uint(a), regB: uint(b), regC: uint(c)}
24+
for _, instr := range strings.Split(lines[4][9:], ",") {
25+
i, _ := strconv.Atoi(instr)
26+
cmp.instructions = append(cmp.instructions, instruction(i))
27+
}
28+
return cmp
29+
}

src/day17/day17_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package day17
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestSample(t *testing.T) {
8+
input := `Register A: 729
9+
Register B: 0
10+
Register C: 0
11+
12+
Program: 0,1,5,4,3,0`
13+
if Solve(input) != "4,6,3,5,6,3,5,2,1,0" {
14+
t.Errorf("Calculated solution was not expected")
15+
}
16+
}
17+
18+
func TestPart1(t *testing.T) {
19+
input := `Register A: 41644071
20+
Register B: 0
21+
Register C: 0
22+
23+
Program: 2,4,1,2,7,5,1,7,4,4,0,3,5,5,3,0`
24+
if Solve(input) != "3,1,5,3,7,4,2,7,5" {
25+
t.Errorf("Calculated solution was not expected")
26+
}
27+
}

src/day17/emulator.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package day17
2+
3+
import (
4+
"strconv"
5+
)
6+
7+
type instruction uint8
8+
9+
const (
10+
ADV instruction = iota
11+
BXL
12+
BST
13+
JNZ
14+
BXC
15+
OUT
16+
BDV
17+
CDV
18+
)
19+
20+
type computer struct {
21+
regA, regB, regC uint
22+
ip uint
23+
instructions []instruction
24+
outBuffer []string
25+
}
26+
27+
func (c *computer) tick() bool {
28+
instr := c.instructions[c.ip]
29+
operand := c.nextOperand()
30+
advance := true
31+
32+
switch instr {
33+
case ADV:
34+
result := c.regA / (1 << operand)
35+
c.regA = result
36+
break
37+
case BXL:
38+
result := c.regB ^ uint(operand)
39+
c.regB = result
40+
break
41+
case BST:
42+
result := uint(operand) % 8
43+
c.regB = result
44+
break
45+
case JNZ:
46+
if c.regA == 0 {
47+
break
48+
}
49+
c.ip = operand
50+
advance = false
51+
break
52+
case BXC:
53+
result := c.regB ^ c.regC
54+
c.regB = result
55+
break
56+
case OUT:
57+
result := operand % 8
58+
c.outBuffer = append(c.outBuffer, strconv.Itoa(int(result)))
59+
break
60+
case BDV:
61+
result := c.regA / (1 << operand)
62+
c.regB = result
63+
break
64+
case CDV:
65+
result := c.regA / (1 << operand)
66+
c.regC = result
67+
break
68+
}
69+
if advance {
70+
c.ip += 2
71+
}
72+
73+
return int(c.ip) >= len(c.instructions)
74+
}
75+
76+
func (c *computer) nextOperand() uint {
77+
i := c.instructions[c.ip]
78+
o := c.instructions[c.ip+1]
79+
comboOperand := i == ADV || i == BST || i == OUT || i == BDV || i == CDV
80+
if !comboOperand {
81+
return uint(o)
82+
}
83+
84+
if o <= 3 {
85+
return uint(o)
86+
} else if o == 4 {
87+
return c.regA
88+
} else if o == 5 {
89+
return c.regB
90+
} else if o == 6 {
91+
return c.regC
92+
}
93+
panic("Combo operand of 7 provided")
94+
}

src/day17/emulator_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package day17
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func compareOutBuffer(t *testing.T, given, expected []string) {
9+
if len(given) != len(expected) {
10+
t.Errorf("Given and expected string slices not of equal length")
11+
}
12+
for i := range given {
13+
if given[i] != expected[i] {
14+
t.Errorf("Given string slice did not match expected")
15+
}
16+
}
17+
}
18+
19+
func TestSmall1(t *testing.T) {
20+
cmp := computer{regC: 9}
21+
cmp.instructions = append(cmp.instructions, 2, 6)
22+
cmp.tick()
23+
if cmp.regB != 1 {
24+
t.Errorf("Calculated solution was not expected")
25+
}
26+
}
27+
28+
func TestSmall2(t *testing.T) {
29+
cmp := computer{regA: 10}
30+
cmp.instructions = append(cmp.instructions, 5, 0, 5, 1, 5, 4)
31+
for {
32+
if cmp.tick() {
33+
break
34+
}
35+
}
36+
compareOutBuffer(t, cmp.outBuffer, strings.Split("0,1,2", ","))
37+
}
38+
39+
func TestSmall3(t *testing.T) {
40+
cmp := computer{regA: 2024}
41+
cmp.instructions = append(cmp.instructions, 0, 1, 5, 4, 3, 0)
42+
for {
43+
if cmp.tick() {
44+
break
45+
}
46+
}
47+
if cmp.regA != 0 {
48+
t.Errorf("Calculated solution was not expected")
49+
}
50+
compareOutBuffer(t, cmp.outBuffer, strings.Split("4,2,5,6,7,7,7,7,3,1,0", ","))
51+
52+
}
53+
54+
func TestSmall4(t *testing.T) {
55+
cmp := computer{regB: 29}
56+
cmp.instructions = append(cmp.instructions, 1, 7)
57+
cmp.tick()
58+
if cmp.regB != 26 {
59+
t.Errorf("Calculated solution was not expected")
60+
}
61+
}
62+
63+
func TestSmall5(t *testing.T) {
64+
cmp := computer{regB: 2024, regC: 43690}
65+
cmp.instructions = append(cmp.instructions, 4, 0)
66+
cmp.tick()
67+
if cmp.regB != 44354 {
68+
t.Errorf("Calculated solution was not expected")
69+
}
70+
}

0 commit comments

Comments
 (0)