2019 Complete!

This commit is contained in:
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
}