package main import ( "fmt" "os" "strconv" "strings" "time" "github.com/fatih/color" termbox "github.com/nsf/termbox-go" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) var regs = map[string]int{ "a": 12, "b": 0, "c": 0, "d": 0, } var instructions, lastInst []string var curr, cursor int var pause, step, skip bool var breakpoints []int var eventChan chan termbox.Event func main() { var debug bool var inpFn string var done bool if inpFn = h.GetArgNumber(1); inpFn == "" { done = true } if inpFn != "" { instructions = h.FileToStringSlice(inpFn) } if h.ArgIsSet("-d") { debug = true } if h.ArgIsSet("-p") { pause = true } err := termbox.Init() //tWidth, tHeight := termbox.Size() if err != nil { fmt.Println("Error initializing termbox") os.Exit(1) } defer termbox.Close() eventChan = make(chan termbox.Event) go readUserInput(eventChan) go sendNoneEvent(eventChan) curr = 0 var ins []string for curr < len(instructions) || done { isBreak := isBreakpoint(curr) if debug { lastInst = ins } ins = strings.Fields(instructions[curr]) if len(ins) == 0 { break } if isMultOp(curr) { curr = doMultOp(curr) skip = true } if debug { if isBreak { pause = true step = false } for pause && !step && !done { // print state and wait for user to step PrintState() PrintInstructionState() PrintDebugHelp() ev := <-eventChan if ev.Type == termbox.EventKey { switch { case ev.Key == termbox.KeySpace: pause = !pause case ev.Ch == 'q': done = true case ev.Ch == 'b': toggleBreakpoint(cursor) case ev.Ch == 's': step = true case ev.Ch == 'u': updateRegister() case ev.Key == termbox.KeyArrowUp: if cursor > 0 { cursor-- } case ev.Key == termbox.KeyArrowDown: if cursor < len(instructions)-1 { cursor++ } } } } step = false } if done { // User hit 'q' break } if skip { skip = false continue } curr++ switch ins[0] { case "jnz": // If we have a jnz c -2 it could be moving all of c into another register v, ok := regs[ins[1]] if !ok { v = h.Atoi(ins[1]) } var p int if p, ok = regs[ins[2]]; !ok { p = h.Atoi(ins[2]) } if v != 0 { // Subtract 1 from the jump because we incremented already curr += p - 1 } case "mlt": // a three arg instruction: mlt a b c // take a * b, store in c var ok bool src1 := ins[1] src2 := ins[2] dst := ins[3] if _, ok := regs[dst]; !ok { // invalid destination continue } var src1I, src2I int if src1I, ok = regs[src1]; !ok { src1I = h.Atoi(src1) } if src2I, ok = regs[src2]; !ok { src2I = h.Atoi(src2) } regs[dst] = src1I * src2I case "cpy": src := ins[1] dst := ins[2] // check if dst is a register if _, ok := regs[dst]; !ok { // Nope, move along continue } // check if the src is a register if v, ok := regs[src]; ok { regs[dst] = v } else { // It's not, must be an int regs[dst] = h.Atoi(src) } case "inc": if _, ok := regs[ins[1]]; !ok { continue } regs[ins[1]] = regs[ins[1]] + 1 case "dec": if _, ok := regs[ins[1]]; !ok { continue } regs[ins[1]] = regs[ins[1]] - 1 case "tgl": // tgl alters an instruction src := ins[1] var srcI int if v, ok := regs[src]; ok { srcI = v } else { srcI = h.Atoi(src) } srcI = curr + srcI if srcI < 0 || srcI > len(instructions)-1 { // Invalid instruction continue } altPts := strings.Fields(instructions[srcI-1]) if len(altPts) == 2 { // one argument if altPts[0] == "inc" { altPts[0] = "dec" } else { altPts[0] = "inc" } } else { // two arguments if altPts[0] == "jnz" { altPts[0] = "cpy" } else { altPts[0] = "jnz" } } instructions[srcI-1] = strings.Join(altPts, " ") } if debug { PrintState() } } PrintState() fmt.Println("Press any key to exit") termbox.PollEvent() } // Fancy State Printing func PrintState() { fmt.Println(h.ClearScreen) PrintRegs() } func PrintRegs() { datLine := fmt.Sprint("\u2502 a:", regs["a"], " b:", regs["b"], " c:", regs["c"], " d:", regs["d"], " \u2502") fmt.Println("\u250C" + strings.Repeat("\u2500", len(datLine)-6) + "\u2510") fmt.Println(datLine) fmt.Println("\u2514" + strings.Repeat("\u2500", len(datLine)-6) + "\u2518") } func PrintInstructionState() { optim := color.New(color.FgGreen) for pi := range instructions { if pi == curr { fmt.Print(">") } else { fmt.Print(" ") } if isBreakpoint(pi) { fmt.Print("B") } else { fmt.Print(" ") } if cursor == pi { cursor := color.New(color.FgBlack).Add(color.BgWhite) if isMultOp(pi) { cursor.Println("+ " + instructions[pi] + " == optimized") } else if mo, _ := isInMultOp(pi); mo { cursor.Println("| " + instructions[pi]) } else { cursor.Println(instructions[pi]) } } else { if isMultOp(pi) { optim.Println("+ " + instructions[pi] + " == optimized") } else if mo, _ := isInMultOp(pi); mo { optim.Println("| " + instructions[pi]) } else { fmt.Println(instructions[pi]) } } } } func PrintDebugHelp() { fmt.Println("(s): step | (space): pause/unpause | (b): toggle breakpoint") fmt.Println("(u): update register to value | (q): quit") } func readUserInput(e chan termbox.Event) { for { e <- termbox.PollEvent() } } func sendNoneEvent(e chan termbox.Event) { for { time.Sleep(time.Second / 32) if !pause { e <- termbox.Event{Type: termbox.EventNone} } } } // isInMultOp returns true and the pc for the // start of the multiplication operation if pos is // located within it func isInMultOp(pos int) (bool, int) { // We need to check instructions[pos +- 5] to see if any // are a multiplication op and return the pos for the (first) one // that is if isMultOp(pos) { return true, pos } for i := 1; i < 6; i++ { if isMultOp(pos - i) { return true, (pos - i) } } return false, pos } // isMultOpStart return whether pos is the start of // what can be optimized as multiplication /* > cpy b c inc a dec c jnz c -2 dec d jnz d -5 == add (b * d) to a set c & d to 0 curr += 5 */ func isMultOp(pos int) bool { if pos < 0 || pos > len(instructions)-1 { return false } ins := strings.Fields(instructions[pos]) if len(ins) < 3 { return false } if ins[0] == "cpy" { if len(instructions) >= pos+6 { ins1 := strings.Fields(instructions[pos+1]) ins2 := strings.Fields(instructions[pos+2]) ins3 := strings.Fields(instructions[pos+3]) ins4 := strings.Fields(instructions[pos+4]) ins5 := strings.Fields(instructions[pos+5]) if ins1[0] == "inc" && ins2[0] == "dec" && ins3[0] == "jnz" && ins4[0] == "dec" && ins5[0] == "jnz" { allGood := true if allGood { // Do the multiplication // ins[1] * ins4[1] // add that value to ins1[1] // set ins[2] to 0 // set ins4[1] to 0 // Then add 5 to the pc src1 := ins[1] src2 := ins4[1] dst := ins1[1] if _, ok := regs[dst]; !ok { // Invalid destination register allGood = false } if _, ok := regs[src1]; !ok { // Invalid source register allGood = false } if _, ok := regs[src2]; !ok { allGood = false } return allGood } } } } return false } // doMultOp performs a multiplcation operation and returns the new pc func doMultOp(pos int) int { isOp, pos := isInMultOp(pos) if !isOp { // No optimization to do return pos } ins := strings.Fields(instructions[pos]) ins1 := strings.Fields(instructions[pos+1]) ins4 := strings.Fields(instructions[pos+4]) // Do the multiplication // ins[1] * ins4[1] // add that value to ins1[1] // set ins[2] to 0 // set ins4[1] to 0 // Then add 5 to the pc src1 := ins[1] src2 := ins4[1] dst := ins1[1] regs[dst] += (regs[src1] * regs[src2]) regs[ins[2]] = 0 regs[ins4[1]] = 0 pos += 6 return pos } func toggleBreakpoint(pos int) { _, pos = isInMultOp(pos) for i := 0; i < len(breakpoints); i++ { if breakpoints[i] == pos { breakpoints = append(breakpoints[:i], breakpoints[i+1:]...) return } } breakpoints = append(breakpoints, pos) } func isBreakpoint(pos int) bool { _, pos = isInMultOp(pos) for i := 0; i < len(breakpoints); i++ { if breakpoints[i] == pos { return true } } return false } func updateRegister() { var updReg string fmt.Println("Update which register? (a,b,c,d)") ev := <-eventChan if ev.Type == termbox.EventKey { switch { case ev.Ch == 'a': updReg = "a" case ev.Ch == 'b': updReg = "b" case ev.Ch == 'c': updReg = "c" case ev.Ch == 'd': updReg = "d" default: fmt.Println("Invalid register (" + string(ev.Ch) + ")") fmt.Println("Press any key to continue") ev = <-eventChan return } } fmt.Println("Enter new value (must be integer, end with enter)") var newVal string for ev.Key != termbox.KeyEnter { ev = <-eventChan if ev.Ch >= '0' && ev.Ch <= '9' { newVal += string(ev.Ch) fmt.Print(string(ev.Ch)) } } fmt.Println("Setting Register (" + updReg + ") to " + newVal) var v int var e error if v, e = strconv.Atoi(newVal); e != nil { fmt.Println() fmt.Println("Error parsing integer") fmt.Println(e) fmt.Println("Press any key to continue") ev = <-eventChan return } regs[updReg] = v }