2019 Complete!

This commit is contained in:
Brian Buller 2020-11-06 15:04:08 -06:00
parent fd5be536a3
commit d6caa5a79d
11 changed files with 2733 additions and 109 deletions

264
2019/day25/cli_processor.go Normal file
View File

@ -0,0 +1,264 @@
package main
import (
"errors"
"strings"
"github.com/nsf/termbox-go"
)
type CliProc struct {
History []string
HistoryIdx int
Suggestions []string // Suggestions are 'autocomplete' commands
Proposals []string // Proposals are proposed 'next' commands
Buffer string
Cursor int
Complete bool
}
func NewCLI() *CliProc {
c := CliProc{}
return &c
}
func (c *CliProc) handleEvent(event termbox.Event) error {
if c.Complete {
return nil
}
if event.Ch == 0 {
switch event.Key {
case termbox.KeyEnter:
// Execute command
c.Complete = true
case termbox.KeySpace:
c.putInBuffer(" ")
case termbox.KeyBackspace, termbox.KeyBackspace2:
if c.Cursor > 0 {
c.Buffer = c.Buffer[:c.Cursor-1] + c.Buffer[c.Cursor:]
c.Cursor = c.Cursor - 1
}
case termbox.KeyDelete:
if c.Cursor < len(c.Buffer) {
c.Buffer = c.Buffer[:c.Cursor] + c.Buffer[c.Cursor+1:]
}
case termbox.KeyArrowLeft:
if c.Cursor > 0 {
c.Cursor = c.Cursor - 1
}
case termbox.KeyArrowRight:
if c.Cursor < len(c.Buffer) {
c.Cursor = c.Cursor + 1
} else {
// Complete suggestion
}
case termbox.KeyHome:
c.Cursor = 0
case termbox.KeyEnd:
c.Cursor = len(c.Buffer)
case termbox.KeyArrowUp:
val, err := c.HistoryBack()
if err != nil {
return err
}
c.Buffer = val
c.Cursor = len(c.Buffer)
case termbox.KeyArrowDown:
val, err := c.HistoryForward()
if err != nil {
return err
}
c.Buffer = val
c.Cursor = len(c.Buffer)
case termbox.KeyTab:
// Complete suggestion
args := strings.Split(c.Buffer, " ")
acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
if len(acText) > 0 {
c.Buffer = c.Buffer + acText
c.Cursor = len(c.Buffer)
}
case termbox.KeyCtrlU:
// Delete everything from the cursor forward
c.Buffer = c.Buffer[c.Cursor:]
c.Cursor = 0
}
} else {
c.putInBuffer(string(event.Ch))
/*
if c.Cursor == len(c.Buffer) {
c.Buffer = c.Buffer + string(event.Ch)
} else {
c.Buffer = c.Buffer[:c.Cursor] + string(event.Ch) + c.Buffer[c.Cursor:]
}
c.Cursor = c.Cursor + 1
*/
}
return nil
}
func (c *CliProc) putInBuffer(v string) {
c.Buffer = c.Buffer[:c.Cursor] + v + c.Buffer[c.Cursor:]
c.Cursor = c.Cursor + 1
}
func (c *CliProc) ClearBuffer() {
c.Buffer = ""
c.Cursor = 0
c.Complete = false
}
func (c *CliProc) GetBuffer() string {
return c.Buffer
}
func (c *CliProc) HistoryBack() (string, error) {
if len(c.History) < c.HistoryIdx+1 {
return "", errors.New("Already at oldest command")
}
c.HistoryIdx = c.HistoryIdx + 1
return c.History[len(c.History)-c.HistoryIdx], nil
}
func (c *CliProc) HistoryForward() (string, error) {
if c.HistoryIdx == 0 {
return "", errors.New("Already at most recent command")
}
c.HistoryIdx = c.HistoryIdx - 1
if c.HistoryIdx == 0 {
return "", nil
}
return c.History[len(c.History)-c.HistoryIdx], nil
}
func (c *CliProc) Suggest(cmd string) string {
allSuggestions := c.GetAllSuggestions(cmd)
if len(allSuggestions) == 0 {
return ""
}
return allSuggestions[0]
}
func (c *CliProc) GetDisplaySuggestions() []string {
var ret []string
v := c.GetAllSuggestions(c.Buffer)
if len(c.Buffer) > 0 {
for k := range v {
if strings.HasPrefix(v[k], c.Buffer) {
ret = append(ret, v[k])
}
}
} else {
// Normally, filter out 'drop's
for k := range v {
if !strings.HasPrefix(v[k], "drop ") {
ret = append(ret, v[k])
}
}
}
return ret
}
func (c *CliProc) GetAllSuggestions(cmd string) []string {
var ret []string
args := strings.Fields(cmd)
if len(args) == 0 {
// Return all commands
for _, v := range c.Suggestions {
ret = append(ret, v)
}
return ret
} else if len(args) == 1 && !strings.HasSuffix(cmd, " ") {
for _, v := range c.Suggestions {
if strings.HasPrefix(v, args[0]) {
ret = append(ret, v)
}
}
}
return ret
}
func (c *CliProc) Draw(x, y int, bg, fg termbox.Attribute) {
DrawString("[ "+strings.Join(c.GetDisplaySuggestions(), ", ")+" ]", x, y, bg, fg)
y = y + 1
prompt := '>'
termbox.SetCell(x, y, prompt, bg, fg)
x = x + 2
for k := range c.Buffer {
useFg, useBg := bg, fg
if k == c.Cursor {
useFg, useBg = fg, bg
}
termbox.SetCell(x+k, y, rune(c.Buffer[k]), useFg, useBg)
}
args := strings.Split(c.Buffer, " ")
acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
if len(acText) > 0 {
for k := range acText {
useFg, useBg := bg|termbox.AttrBold, fg
if k == 0 && c.Cursor == len(c.Buffer) {
useFg, useBg = fg|termbox.AttrBold, bg
}
termbox.SetCell(x+len(c.Buffer)+k, y, rune(acText[k]), useFg, useBg)
}
} else {
if c.Cursor == len(c.Buffer) {
termbox.SetCell(x+len(c.Buffer), y, ' ', fg, bg)
}
}
}
func (c *CliProc) GetAutocompleteText() string {
suggestions := c.GetAllSuggestions(c.Buffer)
if len(suggestions) == 1 {
args := strings.Split(c.Buffer, " ")
return strings.TrimPrefix(suggestions[0], args[len(args)-1])
} else if len(suggestions) > 1 {
// Find how many characters we _can_ autocomplete
var ret string
for k := range suggestions[0] {
allHave := true
for wrk := range suggestions {
if len(suggestions[wrk]) <= k || suggestions[0][k] != suggestions[wrk][k] {
allHave = false
break
}
}
if allHave {
ret = ret + string(suggestions[0][k])
}
}
return ret
}
return ""
}
func (c *CliProc) ClearSuggestions() {
c.Suggestions = nil
}
func (c *CliProc) AddSuggestion(s string) {
c.AddSuggestions([]string{s})
}
func (c *CliProc) AddSuggestions(s []string) {
c.Suggestions = append(c.Suggestions, s...)
}
func (c *CliProc) RemoveSuggestion(s string) {
var i int
for i = range c.Suggestions {
if c.Suggestions[i] == s {
break
}
}
c.Suggestions = append(c.Suggestions[:i], c.Suggestions[i+1:]...)
}

1382
2019/day25/debug-log Normal file

File diff suppressed because it is too large Load Diff

175
2019/day25/event_buffer.go Normal file
View File

@ -0,0 +1,175 @@
package main
import (
"strconv"
"time"
"github.com/nsf/termbox-go"
)
const (
DefaultExpireTime = time.Second / 2
)
type EventBuffer struct {
UpdateTime time.Time
TimeoutLen time.Duration
Buffer []termbox.Event
CursorPos int
}
func NewEventBuffer() *EventBuffer {
return &EventBuffer{
TimeoutLen: DefaultExpireTime,
CursorPos: -1,
}
}
func (eb *EventBuffer) checkExpiration() {
if eb.TimeoutLen != -1 && time.Now().Sub(eb.UpdateTime) > eb.TimeoutLen {
eb.Clear()
}
}
func (eb *EventBuffer) StartsWith(r rune) bool {
eb.checkExpiration()
return len(eb.Buffer) > 0 && eb.Buffer[0].Ch == r
}
func (eb *EventBuffer) Add(event termbox.Event) {
eb.checkExpiration()
if event.Key == termbox.KeyCtrlU {
eb.Clear()
} else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
eb.Backspace()
} else {
eb.Buffer = append(eb.Buffer, event)
}
eb.UpdateTime = time.Now()
}
func (eb *EventBuffer) Backspace() {
eb.checkExpiration()
if len(eb.Buffer) >= 1 {
eb.Buffer = eb.Buffer[:len(eb.Buffer)-1]
}
eb.UpdateTime = time.Now()
}
func (eb *EventBuffer) MoveCursorBack() {
if eb.CursorPos > 0 {
eb.CursorPos--
}
}
func (eb *EventBuffer) MoveCursorForward() {
if eb.CursorPos < len(eb.Buffer) {
eb.CursorPos++
}
}
func (eb *EventBuffer) SetCursorToStart() {
eb.CursorPos = 0
}
func (eb *EventBuffer) SetCursorToEnd() {
eb.CursorPos = len(eb.Buffer)
}
func (eb *EventBuffer) Clear() {
eb.Buffer = nil
eb.UpdateTime = time.Now()
}
func (eb *EventBuffer) Events() []termbox.Event {
eb.checkExpiration()
return eb.Buffer
}
func (eb *EventBuffer) String() string {
eb.checkExpiration()
var ret string
for _, v := range eb.Buffer {
if v.Ch != 0 {
ret = ret + string(v.Ch)
} else {
switch v.Key {
case termbox.KeySpace:
ret = ret + " "
case termbox.KeyArrowUp:
ret = ret + string(ChUpKeyValue)
case termbox.KeyArrowRight:
ret = ret + string(ChRightKeyValue)
case termbox.KeyArrowDown:
ret = ret + string(ChDownKeyValue)
case termbox.KeyArrowLeft:
ret = ret + string(ChLeftKeyValue)
case termbox.KeyEnter:
ret = ret + string(ChEnterKeyValue)
}
}
}
return ret
}
func (eb *EventBuffer) SetToString(s string) {
eb.Clear()
for _, v := range s {
if v == '\n' {
eb.Add(termbox.Event{Ch: 0, Key: termbox.KeyEnter})
} else {
eb.Add(termbox.Event{Ch: v})
}
}
}
func (eb *EventBuffer) MatchesString(s string) bool {
eb.checkExpiration()
if len(s) != len(eb.Buffer) {
return false
}
return s == eb.String()
}
func (eb *EventBuffer) MatchesEvents(events []termbox.Event) bool {
eb.checkExpiration()
if len(events) != len(eb.Buffer) {
return false
}
for k := range events {
if events[k].Ch != eb.Buffer[k].Ch || events[k].Key != eb.Buffer[k].Key {
return false
}
}
return true
}
func (eb *EventBuffer) StartsWithKey(key termbox.Key) bool {
eb.checkExpiration()
if len(eb.Buffer) > 0 {
return eb.Buffer[0].Key == key
}
return false
}
func (eb *EventBuffer) Size() int {
eb.checkExpiration()
return len(eb.Buffer)
}
func (eb *EventBuffer) IsNumber() bool {
_, err := strconv.Atoi(eb.String())
return err == nil
}
func (eb *EventBuffer) Number() (int, error) {
return strconv.Atoi(eb.String())
}
func (eb *EventBuffer) OptNumber(def int) int {
ret, err := strconv.Atoi(eb.String())
if err != nil {
return def
}
return ret
}

1
2019/day25/input Normal file

File diff suppressed because one or more lines are too long

96
2019/day25/main.go Normal file

File diff suppressed because one or more lines are too long

122
2019/day25/map.txt Normal file
View File

@ -0,0 +1,122 @@
9 5-6
| | |
8-4 7
|
1 |
| |
0-3
| |
2 |
|
C-B-A
| |
D E
|
F
|
H-G
|
I
|
*
0: Hull Breach ()
1: Hallway (giant electromagnet -> prevents movement)
2: Science Lab (astronaut ice cream)
3: Passages (mouse)
4: Arcade (spool of cat6)
5: Engineering (hypercube)
6: Crew Quarters (sand)
7: Navigation (antenna)
8: Observatory ()
9: Stables (infinite loop -> Locks up game)
A: Warp Drive Maintenance (mutex)
B: Gift Wrapping Center (boulder)
C: Sick Bay ()
D: Holodeck (escape pod -> Launched into Space -> Exit)
E: Kitchen ()
F: Hot Chocolate Fountain (photons -> Eaten by a Grue)
G: Corridor (molten lava)
H: Storage ()
I: Security Checkpoint ()
*: GOAL!
# All Items:
astronaut ice cream
mouse
spool of cat6
hypercube
sand
antenna
mutex
boulder
# Entry Attempts:
* 1: Too Light
astronaut ice cream
mouse
mutex
boulder
* 2: Too Heavy
astronaut ice cream
mouse
spool of cat6
hypercube
sand
antenna
mutex
boulder
* 3: Too Heavy
astronaut ice cream
mouse
spool of cat6
hypercube
sand
antenna
* 4: Too Heavy
astronaut ice cream
mouse
spool of cat6
hypercube
sand
* 5: Too Light
astronaut ice cream
mouse
spool of cat6
hypercube
* 6: Too Heavy
astronaut ice cream
mouse
spool of cat6
sand
* 7: Too Light
astronaut ice cream
mouse
spool of cat6
hypercube
* 8: Too Heavy
astronaut ice cream
mouse
spool of cat6
hypercube
antenna
* 9: Too Heavy
mutex
mouse
spool of cat6
hypercube
antenna
* 10: Too Heavy
astronaut ice cream
mouse
spool of cat6
antenna

File diff suppressed because one or more lines are too long

404
2019/day25/screen.go Normal file
View File

@ -0,0 +1,404 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
intcode "git.bullercodeworks.com/brian/adventofcode/2019/intcode-processor"
termboxScreen "github.com/br0xen/termbox-screen"
termboxUtil "github.com/br0xen/termbox-util"
"github.com/nsf/termbox-go"
)
const (
ModeWaiting = iota
ModeInput
)
const (
Bg = termbox.ColorBlack
Fg = termbox.ColorGreen
)
var spinner []rune
type Screen struct {
Title string
Program *intcode.Program
LastPI int
Mode int
Events *EventBuffer
Cli *CliProc
TheLog []string
LogStartIdx int
LogDisplayLines int
StatusText string
StatusSpinner int
Inventory []string
id int
}
func NewScreen(p *intcode.Program) *Screen {
s := Screen{
Title: "Day 25: Cryostasis",
Program: p,
Mode: ModeWaiting,
Events: NewEventBuffer(),
Cli: NewCLI(),
StatusSpinner: 0,
}
spinner = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▁'}
// Process to update screen from program
go func() {
for {
if p.WaitingForInput || p.State == intcode.RET_DONE {
if !s.Cli.Complete {
s.Mode = ModeInput
} else {
s.Mode = ModeWaiting
inp := s.Cli.GetBuffer()
s.Cli.ClearBuffer()
if s.IsEngineCommand(inp) {
if err := s.ProcessCommand(inp); err != nil {
s.Logln("Engine Error: " + err.Error())
}
continue
}
for k := range inp {
for !p.WaitingForInput {
time.Sleep(1)
}
s.StatusText = "Transmitting " + inp[0:k]
s.Log(string(inp[k]))
s.Program.Input(int(inp[k]))
}
s.StatusText = "Please Wait..."
s.Program.Input(int('\n'))
s.Logln("")
if strings.HasPrefix(inp, "take ") {
s.Cli.RemoveSuggestion(inp)
invAdd := strings.TrimPrefix(inp, "take ")
s.Inventory = append(s.Inventory, invAdd)
} else if strings.HasPrefix(inp, "drop ") {
invRem := strings.TrimPrefix(inp, "drop ")
for i := range s.Inventory {
if s.Inventory[i] == invRem {
s.Inventory = append(s.Inventory[:i], s.Inventory[i+1:]...)
break
}
}
}
}
}
if p.WaitingForOutput {
v := s.Program.Output()
s.StatusText = "Receiving '" + string(v) + "'"
if v == '\n' {
s.Logln("")
} else {
s.Log(string(v))
}
}
}
}()
// And actually run the program
s.Logln("Welcome to the Day25 Cryostasis Engine!")
s.Logln("Type '/start' to start")
s.Logln("Type '/load <save>' to load a save")
go func() {
ret := p.Run()
if ret == intcode.RET_DONE {
s.Logln("Program is done")
} else if ret == intcode.RET_ERR {
s.Logln("Program errored")
}
s.Logln("Type '/quit' to quit")
}()
return &s
}
func (s *Screen) Id() int { return s.id }
func (s *Screen) Initialize(bundle termboxScreen.Bundle) error {
s.Events.Clear()
s.TheLog = nil
return nil
}
func (s *Screen) HandleNoneEvent(event termbox.Event) int {
// Update the display
return s.Id()
}
func (s *Screen) HandleKeyEvent(event termbox.Event) int {
switch event.Key {
case termbox.KeyF5:
if err := s.SaveState("quick"); err != nil {
s.Logln("Engine Error: " + err.Error())
} else {
s.Logln("Quick Saved")
}
case termbox.KeyF7:
if err := s.LoadState("quick"); err != nil {
s.Logln("Engine Error: " + err.Error())
} else {
s.Logln("Quick Loaded")
}
case termbox.KeyPgup, termbox.KeyCtrlB:
// Page down the log
wrkLog := s.GetWrappedLog()
if s.LogStartIdx < len(wrkLog)-s.LogDisplayLines {
s.LogStartIdx = s.LogStartIdx + s.LogDisplayLines
}
if s.LogStartIdx > len(wrkLog)-s.LogDisplayLines {
s.LogStartIdx = len(wrkLog) - s.LogDisplayLines
}
case termbox.KeyPgdn, termbox.KeyCtrlF:
// Page up the log
if s.LogStartIdx > 0 {
s.LogStartIdx = s.LogStartIdx - s.LogDisplayLines
}
if s.LogStartIdx < 0 {
s.LogStartIdx = 0
}
default:
if s.Mode == ModeInput {
err := s.Cli.handleEvent(event)
if err != nil {
s.Log(err.Error())
}
}
}
return s.Id()
}
// ╭╼╾╮
// │┼━│
// ├┴┬┤
// ╰──╯
func (s *Screen) DrawScreen() {
s.DrawHeader()
s.DrawLog()
s.DrawInventory()
s.DrawTerminal()
}
func (s *Screen) DrawLog() {
w, h := termbox.Size()
y := 1
logW := w - 2
h = h - 4
s.LogDisplayLines = h - 1
wrkLog := s.GetWrappedLog()
currStart := len(wrkLog) - s.LogDisplayLines - s.LogStartIdx
if currStart < 0 {
currStart = 0
}
statusStart := (len(wrkLog) - s.LogDisplayLines - currStart)
if statusStart < 0 {
statusStart = 0
}
logStatus := fmt.Sprintf("%d/%d (%d:%d)", statusStart, len(wrkLog), s.LogStartIdx, currStart)
_, nY := termboxUtil.DrawStringAtPoint("╭"+logStatus+strings.Repeat("─", (logW-1-(len(logStatus))))+"╮", 1, y, Fg, Bg)
for len(wrkLog) < s.LogDisplayLines {
wrkLog = append([]string{"~"}, wrkLog...)
}
for k := currStart; k < len(wrkLog) && k < currStart+s.LogDisplayLines; k++ {
nY = nY + 1
v := "~"
if k >= 0 {
v = wrkLog[k]
}
if len(v) > logW-4 {
v = v[:logW-4]
} else if len(v) < logW-4 {
v = termboxUtil.AlignTextWithFill(v, logW-4, termboxUtil.AlignLeft, ' ')
}
_, nY = termboxUtil.DrawStringAtPoint(v, 2, nY, Fg, Bg)
termbox.SetCell(1, nY, '│', Fg, Bg)
termbox.SetCell((w - 1), nY, '│', Fg, Bg)
}
termboxUtil.DrawStringAtPoint("├"+strings.Repeat("─", (logW-1))+"┤", 1, nY+1, Fg, Bg)
}
func (s *Screen) DrawInventory() {
w, _ := termbox.Size()
title := "Inventory"
longest := len(title)
for k := range s.Inventory {
if len(s.Inventory[k]) > longest {
longest = len(s.Inventory[k])
}
}
title = "┬" + title + strings.Repeat("─", longest-len(title)) + "╮"
termboxUtil.DrawStringAtPoint(title, w-(longest+2), 1, Fg, Bg)
for k := range s.Inventory {
termboxUtil.DrawStringAtPoint(s.Inventory[k], w-(longest+1), 2+k, Fg, Bg)
termbox.SetCell((w - longest - 2), 2+k, '│', Fg, Bg)
termbox.SetCell((w - 1), 2+k, '│', Fg, Bg)
}
termboxUtil.DrawStringAtPoint("╰"+strings.Repeat("─", len(title)-6)+"┤", w-(longest+2), 2+len(s.Inventory), Fg, Bg)
}
func (s *Screen) DrawTerminal() {
w, h := termbox.Size()
termbox.SetCell(1, h-2, '│', Fg, Bg)
termbox.SetCell((w - 1), h-2, '│', Fg, Bg)
if s.Mode == ModeInput && !s.Cli.Complete {
s.Cli.Draw(2, h-3, Fg, Bg)
} else {
s.DrawStatusLine()
}
termboxUtil.DrawStringAtPoint("╰"+strings.Repeat("─", (w-3))+"╯", 1, h-1, Fg, Bg)
}
func (s *Screen) DrawStatusLine() {
_, h := termbox.Size()
if s.Program.Ptr != s.LastPI {
s.StatusSpinner = (s.StatusSpinner + 1) % len(spinner)
s.LastPI = s.Program.Ptr
}
status := string(spinner[s.StatusSpinner]) + " " + strconv.Itoa(s.LastPI)
for len(status) < 10 {
status = status + " "
}
termboxUtil.DrawStringAtPoint(status+" - "+s.StatusText, 2, h-2, Fg, Bg)
}
func (s *Screen) DrawHeader() {
width, _ := termbox.Size()
spaces := strings.Repeat(" ", ((width-len(s.Title))/2)+1)
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, s.Title, spaces), 0, 0, Bg, Fg)
}
func (s *Screen) ResizeScreen() {
s.Initialize(nil)
}
func (s *Screen) GetWrappedLog() []string {
w, _ := termbox.Size()
var wrkLog []string
for _, v := range s.TheLog {
var line string
pts := strings.Fields(v)
for k := range pts {
if len(line) == 0 {
line = pts[k]
} else {
if len(line+" "+pts[k]) < w-4 {
line = line + " " + pts[k]
} else {
wrkLog = append(wrkLog, line)
line = pts[k]
}
}
}
wrkLog = append(wrkLog, line)
}
return wrkLog
}
func (s *Screen) Logln(msg string) {
s.TheLog = append(s.TheLog, msg)
}
func (s *Screen) Log(msg string) {
last := s.TheLog[len(s.TheLog)-1]
last = last + msg
s.TheLog[len(s.TheLog)-1] = last
if last == "Command?" {
s.ParseLogForCommands()
}
}
func (s *Screen) ParseLogForCommands() {
// First fine the _previous_ 'Command?'
s.Cli.ClearSuggestions()
for k := len(s.TheLog) - 1; k >= 0; k-- {
if strings.HasPrefix(s.TheLog[k], "== ") {
// Room start
break
}
if strings.HasPrefix(s.TheLog[k], "- ") {
val := strings.TrimPrefix(s.TheLog[k], "- ")
switch val {
case "north", "east", "south", "west":
s.Cli.AddSuggestion(val)
default:
var have bool
for _, v := range s.Inventory {
if v == val {
have = true
break
}
}
if !have {
s.Cli.AddSuggestion("take " + val)
}
}
}
}
for _, v := range s.Inventory {
s.Cli.AddSuggestions([]string{"drop " + v})
}
}
func (s *Screen) IsEngineCommand(inp string) bool {
return inp == "/quit" || strings.HasPrefix(inp, "/save") || strings.HasPrefix(inp, "/load")
}
func (s *Screen) ProcessCommand(inp string) error {
if inp == "/quit" {
s.id = -1
} else if strings.HasPrefix(inp, "/save ") {
pts := strings.Split(inp, " ")
if len(pts) == 2 {
return s.SaveState(pts[1])
}
return errors.New("No filename given")
} else if strings.HasPrefix(inp, "/load ") {
pts := strings.Split(inp, " ")
if len(pts) == 2 {
return s.LoadState(pts[1])
}
return errors.New("No filename given")
}
return nil
}
func (s *Screen) SaveState(slot string) error {
// Save the engine state first
f, err := os.OpenFile("saveslot-"+slot+".sav", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.Encode(s)
return nil
}
func (s *Screen) LoadState(slot string) error {
hold, err := ioutil.ReadFile("saveslot-" + slot + ".sav")
if err != nil {
return err
}
json.NewDecoder(bytes.NewBuffer(hold)).Decode(&s)
return nil
}

33
2019/day25/util.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"strings"
termboxUtil "github.com/br0xen/termbox-util"
"github.com/nsf/termbox-go"
)
// Some characters for display
const (
ChUpKeyValue = '↑'
ChRightKeyValue = '→'
ChDownKeyValue = '↓'
ChLeftKeyValue = '←'
ChEnterKeyValue = '⏎'
ChCursor = '┇'
)
func DrawString(v string, x, y int, fg, bg termbox.Attribute) (int, int) {
termboxUtil.DrawStringAtPoint(v, x, y, fg, bg)
return (x + len(v)), (y + 1)
}
func FilterStrings(h []string, n string) []string {
var ret []string
for _, v := range h {
if strings.HasPrefix(v, n) {
ret = append(ret, v)
}
}
return ret
}

View File

@ -0,0 +1,118 @@
package intcodeprocessor
import (
"fmt"
"math"
"strconv"
)
type OptProc struct {
OriginalCode []int
Code []int
Ptr int
RelBase int
State int
Error error
Pause bool
Bail bool
WaitingForInput bool
WaitingForOutput bool
Debug bool
DebugToFile bool
}
func NewOptProc(prog []int) *OptProc {
p := new(OptProc)
p.State = RET_OK
p.OriginalCode = make([]int, len(prog))
max := math.MaxInt16
if 2*len(prog) > max {
max = 2 * len(prog)
}
p.Code = make([]int, max)
copy(p.OriginalCode, prog)
return p
}
func (p *OptProc) Run(in <-chan int, out chan<- int) int {
p.Ptr = 0
p.RelBase = 0
copy(p.Code, p.OriginalCode)
for p.State == RET_OK {
ins := fmt.Sprintf("%05d", p.Code[p.Ptr])
opcode, _ := strconv.Atoi(ins[3:])
arg := func(i int) int {
switch ins[3-i] {
case '1': // Immediate mode
return p.Ptr + i
case '2': // Relative mode
return p.RelBase + p.Code[p.Ptr+i]
default: // 1, position mode
return p.Code[p.Ptr+i]
}
}
switch opcode {
case OP_ADD:
p.Code[arg(3)] = p.Code[arg(1)] + p.Code[arg(2)]
p.Ptr = p.Ptr + 4
case OP_MLT:
p.Code[arg(3)] = p.Code[arg(1)] * p.Code[arg(2)]
p.Ptr += 4
case OP_INP:
p.WaitingForInput = true
v := <-in
p.WaitingForInput = false
p.Code[arg(1)] = v
p.Ptr += 2
case OP_OUT:
p.WaitingForOutput = true
out <- p.Code[arg(1)]
p.WaitingForOutput = false
p.Ptr += 2
case OP_JIT:
if p.Code[arg(1)] != 0 {
p.Ptr = p.Code[arg(2)]
} else {
p.Ptr += 3
}
case OP_JIF:
if p.Code[arg(1)] == 0 {
p.Ptr = p.Code[arg(2)]
} else {
p.Ptr += 3
}
case OP_ILT:
if p.Code[arg(1)] < p.Code[arg(2)] {
p.Code[arg(3)] = 1
} else {
p.Code[arg(3)] = 0
}
p.Ptr += 4
case OP_IEQ:
if p.Code[arg(1)] == p.Code[arg(2)] {
p.Code[arg(3)] = 1
} else {
p.Code[arg(3)] = 0
}
p.Ptr += 4
case OP_RBS:
p.RelBase += p.Code[arg(1)]
p.Ptr += 2
case OP_EXT:
p.State = RET_DONE
default:
p.State = RET_ERR
}
}
return p.State
}
func (p *OptProc) IsStopped() bool {
return p.State != RET_OK
}

View File

@ -1,12 +1,15 @@
package intcodeprocessor
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
"os"
"strings"
"time"
helpers "git.bullercodeworks.com/brian/adventofcode/helpers"
)
@ -37,21 +40,23 @@ const (
)
type Program struct {
originalCode []int
code []int
ptr int
relBase int
OriginalCode []int
Code []int
Ptr int
RelBase int
state int
error error
State int
Error error
bail bool
waitingForInput bool
input chan int
waitingForOutput bool
output chan int
Pause bool
Bail bool
WaitingForInput bool
InputChan chan int
WaitingForOutput bool
OutputChan chan int
debug bool
Debug bool
DebugToFile bool
}
func ReadIntCodeFile(fn string) []int {
@ -70,157 +75,180 @@ func ReadIntCodeFile(fn string) []int {
func NewProgram(prog []int) *Program {
p := new(Program)
p.originalCode = make([]int, len(prog))
p.code = make([]int, len(prog))
copy(p.originalCode, prog)
p.OriginalCode = make([]int, len(prog))
p.Code = make([]int, len(prog))
copy(p.OriginalCode, prog)
p.Reset()
return p
}
func (p *Program) EnableDebug() {
p.debug = true
func (p *Program) SaveState(slot string) error {
f, err := os.OpenFile("saveslot-"+slot+".sav", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.Encode(p)
return nil
}
func (p *Program) DisableDebug() {
p.debug = false
func (p *Program) LoadState(slot string) error {
hold, err := ioutil.ReadFile("saveslot-" + slot + ".sav")
if err != nil {
return err
}
json.NewDecoder(bytes.NewBuffer(hold)).Decode(&p)
return nil
}
func (p *Program) Pointer() int {
return p.Ptr
}
func (p *Program) DebugLog(l string) {
if p.debug {
if p.Debug {
fmt.Print(l)
}
}
func (p *Program) OutputToFile(l string) {
f, err := os.OpenFile("debug-log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
_, err = fmt.Fprintln(f, l)
}
func (p *Program) Reset() {
copy(p.code, p.originalCode)
p.ptr = 0
p.state = RET_OK
p.error = nil
p.waitingForInput = false
p.waitingForOutput = false
p.input = make(chan int)
p.output = make(chan int)
p.relBase = 0
}
func (p *Program) GetCode() []int {
return p.code
}
func (p *Program) State() int {
return p.state
}
func (p *Program) Error() error {
return p.error
copy(p.Code, p.OriginalCode)
p.Ptr = 0
p.State = RET_OK
p.Error = nil
p.Pause = false
p.WaitingForInput = false
p.WaitingForOutput = false
p.InputChan = make(chan int)
p.OutputChan = make(chan int)
p.RelBase = 0
}
func (p *Program) ForceQuit() {
p.bail = true
close(p.input)
close(p.output)
p.Bail = true
close(p.InputChan)
close(p.OutputChan)
}
func (p *Program) Run() int {
for {
p.state = p.Step()
if p.state != RET_OK {
return p.state
p.State = p.Step()
if p.State != RET_OK {
return p.State
}
}
}
func (p *Program) Step() int {
if p.bail {
p.error = errors.New("Force Quit")
for p.Pause {
time.Sleep(1)
}
if p.Bail {
p.Error = errors.New("Force Quit")
return RET_ERR
}
if len(p.code) < p.ptr {
p.error = errors.New("Pointer Exception")
if len(p.Code) < p.Ptr {
p.Error = errors.New("Pointer Exception")
return RET_ERR
}
p.DebugLog(p.String() + "\n")
intcode := p.readNext()
p.ptr++
p.Ptr++
switch p.opCode(intcode) {
case OP_ADD:
v1, v2, v3 := p.readNextThree()
p.DebugLog(fmt.Sprintf("ADD %d (%d, %d, %d)\n", intcode, v1, v2, v3))
p.ptr = p.ptr + 3
debug := fmt.Sprintf("ADD %d (%d, %d, %d)\n", intcode, v1, v2, v3)
p.DebugLog(debug)
if p.DebugToFile {
p.OutputToFile(fmt.Sprintf("%d: %s", p.Ptr, debug))
}
p.Ptr = p.Ptr + 3
p.opAdd(intcode, v1, v2, v3)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_MLT:
v1, v2, v3 := p.readNextThree()
p.DebugLog(fmt.Sprintf("MLT %d (%d, %d, %d)\n", intcode, v1, v2, v3))
p.ptr = p.ptr + 3
p.Ptr = p.Ptr + 3
p.opMult(intcode, v1, v2, v3)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_INP:
v1 := p.readNext()
p.DebugLog(fmt.Sprintf("INP %d (%d)\n", intcode, v1))
p.ptr++
p.Ptr++
p.opInp(intcode, v1)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_OUT:
v1 := p.readNext()
p.ptr++
p.Ptr++
p.DebugLog(fmt.Sprintf("OUT %d (%d)\n", intcode, v1))
p.opOut(intcode, v1)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_JIT:
v1, v2 := p.readNextTwo()
p.DebugLog(fmt.Sprintf("JIT %d (%d, %d)\n", intcode, v1, v2))
p.ptr = p.ptr + 2
p.Ptr = p.Ptr + 2
p.opJumpIfTrue(intcode, v1, v2)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_JIF:
v1, v2 := p.readNextTwo()
p.DebugLog(fmt.Sprintf("JIF %d (%d, %d)\n", intcode, v1, v2))
p.ptr = p.ptr + 2
p.Ptr = p.Ptr + 2
p.opJumpIfFalse(intcode, v1, v2)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_ILT:
v1, v2, dest := p.readNextThree()
p.DebugLog(fmt.Sprintf("ILT %d (%d, %d, %d)\n", intcode, v1, v2, dest))
p.ptr = p.ptr + 3
p.Ptr = p.Ptr + 3
p.opIfLessThan(intcode, v1, v2, dest)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_IEQ:
v1, v2, dest := p.readNextThree()
p.DebugLog(fmt.Sprintf("IEQ %d (%d, %d, %d)\n", intcode, v1, v2, dest))
p.ptr = p.ptr + 3
debug := fmt.Sprintf("IEQ %d (%d, %d, %d)\n", intcode, v1, v2, dest)
p.DebugLog(debug)
p.Ptr = p.Ptr + 3
p.opIfEqual(intcode, v1, v2, dest)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
case OP_RBS:
v1 := p.readNext()
p.DebugLog(fmt.Sprintf("RBS %d (%d)\n", intcode, v1))
p.ptr = p.ptr + 1
p.Ptr = p.Ptr + 1
p.opModRelBase(intcode, v1)
if p.error != nil {
if p.Error != nil {
return RET_ERR
}
return RET_OK
@ -229,31 +257,31 @@ func (p *Program) Step() int {
p.DebugLog(fmt.Sprintf("EXT %d\n", intcode))
return RET_DONE
}
p.error = errors.New(fmt.Sprintf("Invalid OpCode (%d)", intcode))
p.Error = errors.New(fmt.Sprintf("Invalid OpCode (%d)", intcode))
p.DebugLog(p.String())
return RET_ERR
}
func (p *Program) GetCurrentOpCode() int {
return p.code[p.ptr]
return p.Code[p.Ptr]
}
func (p *Program) GetProgramValueAt(idx int) int {
p.ensureLength(idx)
return p.code[idx]
return p.Code[idx]
}
func (p *Program) SetProgramValueAt(idx, val int) {
p.ensureLength(idx)
p.code[idx] = val
p.Code[idx] = val
}
func (p *Program) NeedsInput() bool {
return p.waitingForInput
return p.WaitingForInput
}
func (p *Program) NeedsOutput() bool {
return p.waitingForOutput
return p.WaitingForOutput
}
func (p *Program) opCode(intcode int) int {
@ -266,33 +294,33 @@ func (p *Program) paramMode(intcode, pNum int) int {
}
func (p *Program) ensureLength(idx int) {
for len(p.code) < idx+1 {
p.code = append(p.code, 0)
for len(p.Code) < idx+1 {
p.Code = append(p.Code, 0)
}
}
func (p *Program) readNext() int {
p.ensureLength(p.ptr)
return p.code[p.ptr]
p.ensureLength(p.Ptr)
return p.Code[p.Ptr]
}
func (p *Program) readNextTwo() (int, int) {
p.ensureLength(p.ptr + 1)
return p.code[p.ptr], p.code[p.ptr+1]
p.ensureLength(p.Ptr + 1)
return p.Code[p.Ptr], p.Code[p.Ptr+1]
}
func (p *Program) readNextThree() (int, int, int) {
p.ensureLength(p.ptr + 2)
return p.code[p.ptr], p.code[p.ptr+1], p.code[p.ptr+2]
p.ensureLength(p.Ptr + 2)
return p.Code[p.Ptr], p.Code[p.Ptr+1], p.Code[p.Ptr+2]
}
func (p *Program) get(mode, v int) int {
if mode == MODE_POS {
p.ensureLength(v)
return p.code[v]
return p.Code[v]
} else if mode == MODE_REL {
p.ensureLength(p.relBase + v)
return p.code[p.relBase+v]
p.ensureLength(p.RelBase + v)
return p.Code[p.RelBase+v]
}
return v
}
@ -300,21 +328,21 @@ func (p *Program) get(mode, v int) int {
func (p *Program) set(mode, idx, v int) {
if mode == MODE_POS {
p.ensureLength(idx)
p.code[idx] = v
p.Code[idx] = v
} else if mode == MODE_REL {
p.ensureLength(p.relBase + idx)
p.code[p.relBase+idx] = v
p.ensureLength(p.RelBase + idx)
p.Code[p.RelBase+idx] = v
}
}
func (p *Program) Input(v int) {
p.input <- v
//p.waitingForInput = false
p.InputChan <- v
//p.WaitingForInput = false
}
func (p *Program) Output() int {
v := <-p.output
p.waitingForOutput = false
v := <-p.OutputChan
p.WaitingForOutput = false
return v
}
@ -330,29 +358,29 @@ func (p *Program) opMult(intcode, a1, a2, dest int) {
func (p *Program) opInp(intcode, dest int) {
destmd := p.paramMode(intcode, 0)
p.waitingForInput = true
p.set(destmd, dest, <-p.input)
p.waitingForInput = false
p.WaitingForInput = true
p.set(destmd, dest, <-p.InputChan)
p.WaitingForInput = false
}
func (p *Program) opOut(intcode, val int) {
valmd := p.paramMode(intcode, 0)
ret := p.get(valmd, val)
p.waitingForOutput = true
p.output <- ret
p.WaitingForOutput = true
p.OutputChan <- ret
}
func (p *Program) opJumpIfTrue(intcode, v1, v2 int) {
v1md, v2md := p.paramMode(intcode, 0), p.paramMode(intcode, 1)
if p.get(v1md, v1) != 0 {
p.ptr = p.get(v2md, v2)
p.Ptr = p.get(v2md, v2)
}
}
func (p *Program) opJumpIfFalse(intcode, v1, v2 int) {
v1md, v2md := p.paramMode(intcode, 0), p.paramMode(intcode, 1)
if p.get(v1md, v1) == 0 {
p.ptr = p.get(v2md, v2)
p.Ptr = p.get(v2md, v2)
}
}
@ -376,17 +404,17 @@ func (p *Program) opIfEqual(intcode, v1, v2, dest int) {
func (p *Program) opModRelBase(intcode, v1 int) {
v1md := p.paramMode(intcode, 0)
p.relBase = p.relBase + p.get(v1md, v1)
p.RelBase = p.RelBase + p.get(v1md, v1)
}
func (p Program) String() string {
var ret string
ret = ret + fmt.Sprintf("(PTR: %d, RBS: %d)\n", p.ptr, p.relBase)
for k := range p.code {
if k == p.ptr {
ret = fmt.Sprintf("%s [%d]", ret, p.code[k])
ret = ret + fmt.Sprintf("(PTR: %d, RBS: %d)\n", p.Ptr, p.RelBase)
for k := range p.Code {
if k == p.Ptr {
ret = fmt.Sprintf("%s [%d]", ret, p.Code[k])
} else {
ret = fmt.Sprintf("%s %d", ret, p.code[k])
ret = fmt.Sprintf("%s %d", ret, p.Code[k])
}
}
return ret