325 lines
6.5 KiB
Go
325 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
termbox "github.com/nsf/termbox-go"
|
|
|
|
"../../"
|
|
)
|
|
|
|
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
|
|
|
|
func main() {
|
|
var debug bool
|
|
var inpFn string
|
|
var done bool
|
|
if inpFn = aoc.GetArgNumber(1); inpFn == "" {
|
|
done = true
|
|
}
|
|
if inpFn != "" {
|
|
instructions = aoc.FileToStringSlice(inpFn)
|
|
}
|
|
|
|
if aoc.ArgIsSet("-d") {
|
|
debug = true
|
|
}
|
|
if aoc.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 {
|
|
if debug {
|
|
lastInst = ins
|
|
}
|
|
ins = strings.Fields(instructions[curr])
|
|
if len(ins) == 0 {
|
|
break
|
|
}
|
|
// Optimize 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
|
|
*/
|
|
if ins[0] == "cpy" {
|
|
if len(instructions) >= curr+6 {
|
|
ins1 := strings.Fields(instructions[curr+1])
|
|
ins2 := strings.Fields(instructions[curr+2])
|
|
ins3 := strings.Fields(instructions[curr+3])
|
|
ins4 := strings.Fields(instructions[curr+4])
|
|
ins5 := strings.Fields(instructions[curr+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
|
|
}
|
|
if allGood {
|
|
regs[dst] += (regs[src1] * regs[src2])
|
|
curr += 6
|
|
skip = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if debug {
|
|
if isBreakpoint(curr + 1) {
|
|
pause = true
|
|
step = false
|
|
}
|
|
for pause && !step {
|
|
// print state and wait for user to step
|
|
PrintState()
|
|
fmt.Println("Last Instruction:", lastInst)
|
|
PrintInstructionState()
|
|
fmt.Println("(q): quit (space): pause/unpause (s): step (b): toggle breakpoint")
|
|
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.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 = aoc.Atoi(ins[1])
|
|
}
|
|
var p int
|
|
if p, ok = regs[ins[2]]; !ok {
|
|
p = aoc.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 = aoc.Atoi(src1)
|
|
}
|
|
if src2I, ok = regs[src2]; !ok {
|
|
src2I = aoc.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] = aoc.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 = aoc.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(aoc.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() {
|
|
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)
|
|
cursor.Println(instructions[pi])
|
|
} else {
|
|
fmt.Println(instructions[pi])
|
|
}
|
|
}
|
|
fmt.Println(instructions[curr])
|
|
}
|
|
|
|
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}
|
|
}
|
|
}
|
|
}
|
|
|
|
func toggleBreakpoint(pos int) {
|
|
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 {
|
|
for i := 0; i < len(breakpoints); i++ {
|
|
if breakpoints[i] == pos {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|