506 lines
11 KiB
Go
506 lines
11 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/fatih/color"
|
||
|
termbox "github.com/nsf/termbox-go"
|
||
|
|
||
|
"../../"
|
||
|
)
|
||
|
|
||
|
var regs = map[string]int{
|
||
|
"a": 0,
|
||
|
"b": 0,
|
||
|
"c": 0,
|
||
|
"d": 0,
|
||
|
}
|
||
|
|
||
|
var instructions, lastInst []string
|
||
|
var curr, cursor int
|
||
|
var done, debug, pause, step, skip bool
|
||
|
var breakpoints []int
|
||
|
var eventChan chan termbox.Event
|
||
|
var outBuff string
|
||
|
|
||
|
// Available arguments:
|
||
|
// -d Debug Mode
|
||
|
// -p Pause on start
|
||
|
// -25 Run day 25 simulation
|
||
|
func main() {
|
||
|
var inpFn string
|
||
|
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
|
||
|
}
|
||
|
if aoc.ArgIsSet("-25") {
|
||
|
// If running the day 25 simulation, ignore debug and pause flags
|
||
|
fmt.Println("Running Day 25 simulation, disabling debug & pause")
|
||
|
debug = false
|
||
|
pause = false
|
||
|
}
|
||
|
if debug {
|
||
|
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)
|
||
|
}
|
||
|
if aoc.ArgIsSet("-25") {
|
||
|
var day25Solved bool
|
||
|
regAStart := regs["a"]
|
||
|
regBStart := regs["b"]
|
||
|
regCStart := regs["c"]
|
||
|
regDStart := regs["d"]
|
||
|
for !day25Solved {
|
||
|
regs["a"] = regAStart
|
||
|
regs["b"] = regBStart
|
||
|
regs["c"] = regCStart
|
||
|
regs["d"] = regDStart
|
||
|
ProcInstructions()
|
||
|
day25Solved = true
|
||
|
fmt.Println(regAStart, ":", outBuff)
|
||
|
if outBuff != "0101010101" {
|
||
|
day25Solved = false
|
||
|
regAStart++
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
ProcInstructions()
|
||
|
PrintState()
|
||
|
}
|
||
|
//fmt.Println("Press any key to exit")
|
||
|
//termbox.PollEvent()
|
||
|
}
|
||
|
|
||
|
func ProcInstructions() {
|
||
|
locRegs := make(map[string]int)
|
||
|
for k, v := range regs {
|
||
|
locRegs[k] = v
|
||
|
}
|
||
|
curr = 0
|
||
|
outBuff = ""
|
||
|
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 "out":
|
||
|
v, ok := regs[ins[1]]
|
||
|
if !ok {
|
||
|
outBuff += ins[1]
|
||
|
} else {
|
||
|
outBuff += aoc.Itoa(v)
|
||
|
}
|
||
|
if aoc.ArgIsSet("-25") && len(outBuff) == 10 {
|
||
|
// This should be long enough for our day 25 answer
|
||
|
return
|
||
|
}
|
||
|
// If we're not debugging, just print it and reset the buffer
|
||
|
if !debug && !aoc.ArgIsSet("-25") {
|
||
|
fmt.Print(outBuff)
|
||
|
outBuff = ""
|
||
|
}
|
||
|
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()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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() {
|
||
|
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
|
||
|
}
|