2019 Complete!
This commit is contained in:
404
2019/day25/screen.go
Normal file
404
2019/day25/screen.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user