package main import ("math"; "errors"; "strconv"; "strings"; "fmt"; "os") //--------------------------------------------------------- func check(err error) { if err != nil { panic(err) } } //--------------------------------------------------------- type Computer struct { pc uint ra uint rb uint rc uint mem []byte } func NewComputer(input string) *Computer { registers := strings.Split(input, "\n\n")[0] memory := strings.Split(strings.Split(input, "\n\n")[1], " ")[1] lines := strings.Split(registers, "\n") ra, err := strconv.Atoi(strings.TrimSpace(strings.Split(lines[0], ":")[1])) check(err) rb, err := strconv.Atoi(strings.TrimSpace(strings.Split(lines[1], ":")[1])) check(err) rc, err := strconv.Atoi(strings.TrimSpace(strings.Split(lines[2], ":")[1])) check(err) mem := make([]byte, 0) for _, str := range strings.Split(memory, ",") { num, err := strconv.Atoi(str) check(err) mem = append(mem, byte(num)) } return &Computer{ pc: 0, ra: uint(ra), rb: uint(rb), rc: uint(rc), mem: mem, } } func (c *Computer) literalop() uint { // current literal operator return uint(c.mem[c.pc + 1]) } func (c *Computer) comboop() uint { // current combo operator val := uint(c.mem[c.pc + 1]) switch { case val == 4: val = c.ra case val == 5: val = c.rb case val == 6: val = c.rc case val == 7: panic("invalid program") } return val } func (c *Computer) adv(op uint) uint { // division numerator := c.ra denominator := math.Pow(2, float64(op)) return uint(float64(numerator) / float64(denominator)) } func (c *Computer) bxl(op uint) uint { // bitwise xor return c.rb ^ op } func (c *Computer) bst(op uint) uint { // mod 8 return op % 8 } func (c *Computer) jnz(op uint) { // jump if non zero if c.ra == 0 { return } else { c.pc = op - 2 return } } func (c *Computer) bxc() uint { // bitwise xor return c.rb ^ c.rc } func (c *Computer) tick() (int, error) { // returns an error if the program halts if c.pc >= uint(len(c.mem)) { return -1, errors.New("halted") } instr := c.mem[c.pc] out := -1 switch { case instr == 0: c.ra = c.adv(c.comboop()) case instr == 1: c.rb = c.bxl(c.literalop()) case instr == 2: c.rb = c.bst(c.comboop()) case instr == 3: c.jnz(c.literalop()) case instr == 4: c.rb = c.bxc() case instr == 5: out = int(c.comboop() % 8) case instr == 6: c.rb = c.adv(c.comboop()) case instr == 7: c.rc = c.adv(c.comboop()) } c.pc += 2 return out, nil } func (c *Computer) returns_its_source() bool { i := 0 for { out, err := c.tick() if err != nil { return false } if out != -1 { if byte(out) != c.mem[i] { return false } i++ if i == len(c.mem) { return true } } } // never reached return false } func (c *Computer) restore(ra uint) { c.pc = 0 c.ra = ra c.rb = 0 c.rc = 0 } //--------------------------------------------------------- func main() { dat, err := os.ReadFile("data.txt") check(err) input := strings.TrimSpace(string(dat)) comp := NewComputer(input) for i := range 1000000000 { comp.restore(uint(i)) if comp.returns_its_source() { fmt.Println(i) break } } }