package main import ( "errors" "fmt" "math/rand" "os" "strings" "time" h "git.bullercodeworks.com/brian/adventofcode/helpers" "github.com/fatih/color" termbox "github.com/nsf/termbox-go" ) func main() { var inpFn string var done bool if inpFn = h.GetArgNumber(1); inpFn != "" { done = true } err := termbox.Init() tWidth, tHeight := termbox.Size() if err != nil { fmt.Println("Error initializing termbox") os.Exit(1) } defer termbox.Close() var menuPos int cursor := color.New(color.FgBlack).Add(color.BgWhite) for !done { fmt.Println(h.ClearScreen) title := color.New(color.FgBlack).Add(color.BgYellow) title.Println(CenterText("day 22", tWidth)) if menuPos == 0 { cursor.Println(CenterText("Start Small Game", tWidth)) } else { fmt.Println(CenterText("Start Small Game", tWidth)) } if menuPos == 1 { cursor.Println(CenterText("Start Medium Game", tWidth)) } else { fmt.Println(CenterText("Start Medium Game", tWidth)) } if menuPos == 2 { cursor.Println(CenterText("Start Large Game", tWidth)) } else { fmt.Println(CenterText("Start Large Game", tWidth)) } if menuPos == 3 { cursor.Println(CenterText("Exit", tWidth)) } else { fmt.Println(CenterText("Exit", tWidth)) } fmt.Println() fmt.Println(CenterText("To run the AdventOfCode Puzzle, pass the input as an argument to the program:", tWidth)) fmt.Println(CenterText("./day22 ", tWidth)) fmt.Println(CenterText("For the part 1 solution:", tWidth)) fmt.Println(CenterText("./day22 -1", tWidth)) ev := termbox.PollEvent() if ev.Type == termbox.EventKey { switch { case ev.Key == termbox.KeyArrowUp || ev.Ch == 'k': if menuPos > 0 { menuPos-- } case ev.Key == termbox.KeyArrowDown || ev.Ch == 'j': if menuPos < 3 { menuPos++ } case ev.Key == termbox.KeyEnter: done = true } } } // Default to small game sizeX, sizeY := 6, 6 switch menuPos { case 1: // Generate a medium game sizeX, sizeY = 10, 10 case 2: // Generate a large game sizeX, sizeY = 15, 15 case 3: return } // Create the display d := CreateDisplay(tWidth, tHeight) if inpFn != "" { input := h.FileToStringSlice(inpFn) d.reset(CreateNodesFromInput(input)) } else { d.reset(GenerateNodesForGrid(sizeX, sizeY)) // Grid is generated, designate one node as start (set used = 0) stX, stY := rand.Intn(sizeX), rand.Intn(sizeY) d.nodes[iToLoc(stX, stY)].Used = 0 d.cursX, d.cursY = stX, stY d.goalX, d.goalY = rand.Intn(sizeX), rand.Intn(sizeY) d.nodes[iToLoc(d.goalX, d.goalY)].Used = rand.Intn(75) } if h.ArgIsSet("-1") { var viablePairs []string for _, v1 := range d.nodes { for _, v2 := range d.nodes { if ok, _ := IsViablePair(v1, v2); ok { viablePairs = AddViablePair(v1, v2, viablePairs) } } } fmt.Println("Viable Pairs:", len(viablePairs)) return } done = false for !done { fmt.Println(h.ClearScreen) d.PrintGrid() ev := termbox.PollEvent() if d.goalX == 0 && d.goalY == 0 { if ev.Type == termbox.EventKey { done = true } } else { if ev.Type == termbox.EventResize { d.screenWidth, d.screenHeight = termbox.Size() } else if ev.Type == termbox.EventKey { switch { case ev.Key == termbox.KeyArrowUp || ev.Ch == 'k': d.resetError() if d.cursorMode { d.moveCursorUp() } else { d.moveDataDown() } case ev.Key == termbox.KeyArrowRight || ev.Ch == 'l': d.resetError() if d.cursorMode { d.moveCursorRight() } else { d.moveDataLeft() } case ev.Key == termbox.KeyArrowDown || ev.Ch == 'j': d.resetError() if d.cursorMode { d.moveCursorDown() } else { d.moveDataUp() } case ev.Key == termbox.KeyArrowLeft || ev.Ch == 'h': d.resetError() if d.cursorMode { d.moveCursorLeft() } else { d.moveDataRight() } case ev.Ch == 'q': done = true case ev.Key == termbox.KeySpace: d.switchMode() } } } } } func GenerateNodesForGrid(w, h int) (map[string]*Node, int, int) { nodes := make(map[string]*Node) rand.Seed(time.Now().UTC().UnixNano()) // It doesn't play well if we have totals that are less than half(?) the highest for y := 0; y <= h; y++ { for x := 0; x <= w; x++ { ttl := 20 + rand.Intn(100-20) used := rand.Intn(ttl) n := CreateNode(x, y, ttl, used) nodes[iToLoc(x, y)] = n } } return nodes, w, h } // Returns the node map and the max X and max Y values func CreateNodesFromInput(input []string) (map[string]*Node, int, int) { nodes := make(map[string]*Node) var maxX, maxY int for i := range input { if input[i] == "" { continue } if input[i][:9] == "/dev/grid" { var n *Node var err error if n, err = CreateNodeFromDfListing(input[i]); err != nil { fmt.Println(err) continue } nodes[h.Itoa(n.X)+";"+h.Itoa(n.Y)] = n if n.X > maxX { maxX = n.X } if n.Y > maxY { maxY = n.Y } } } return nodes, maxX, maxY } type Display struct { screenHeight, screenWidth int cursX, cursY int goalX, goalY int maxX, maxY int errorX, errorY int nodes map[string]*Node // Cursor Mode means the user can move the cursor around // Otherwise the screen locks on the node with 0 used cursorMode bool // moveCount tracks how many times we've moved data moveCount int message string } func CreateDisplay(h, w int) *Display { d := new(Display) d.screenHeight = h d.screenWidth = w d.errorX, d.errorY = -1, -1 return d } func (d *Display) resetError() { d.errorX, d.errorY = -1, -1 } func (d *Display) reset(n map[string]*Node, mX, mY int) { d.nodes = n d.maxX, d.maxY = mX, mY d.cursX, d.cursY = 0, 0 d.goalX, d.goalY = d.maxX, 0 d.errorX, d.errorY = -1, -1 d.message = "" d.cursorMode = true d.switchMode() } func (d *Display) switchMode() { d.cursorMode = !d.cursorMode if !d.cursorMode { // Find the empty node and set the cursor to it for _, v := range d.nodes { if v.Used == 0 { d.cursX, d.cursY = v.X, v.Y break } } } } func (d *Display) moveCursorUp() { if d.cursY > 0 { d.cursY-- } } func (d *Display) moveCursorRight() { if d.cursX < d.maxX { d.cursX++ } } func (d *Display) moveCursorDown() { if d.cursY < d.maxY { d.cursY++ } } func (d *Display) moveCursorLeft() { if d.cursX > 0 { d.cursX-- } } func (d *Display) moveDataUp() { if d.cursY >= d.maxY { d.message = "Invalid Request" return } src := iToLoc(d.cursX, d.cursY+1) dest := iToLoc(d.cursX, d.cursY) if ok, err := IsViablePair(d.nodes[src], d.nodes[dest]); ok { d.nodes[dest].Used = d.nodes[src].Used d.nodes[src].Used = 0 d.cursY++ d.message = "Data Moved" d.moveCount++ if src == iToLoc(d.goalX, d.goalY) { d.goalY-- } } else { d.errorX, d.errorY = d.cursX, d.cursY+1 d.message = fmt.Sprintf( "(%d;%d)[%3d/%3d] (%d;%d)[%3d/%3d] %s", d.nodes[src].X, d.nodes[src].Y, d.nodes[src].Used, d.nodes[src].Size, d.nodes[dest].X, d.nodes[dest].Y, d.nodes[dest].Used, d.nodes[dest].Size, err.Error(), ) } } func (d *Display) moveDataRight() { if d.cursX <= 0 { d.message = "Invalid Request" return } src := iToLoc(d.cursX-1, d.cursY) dest := iToLoc(d.cursX, d.cursY) if ok, err := IsViablePair(d.nodes[src], d.nodes[dest]); ok { d.nodes[dest].Used = d.nodes[src].Used d.nodes[src].Used = 0 d.cursX-- d.message = "Data Moved" d.moveCount++ if src == iToLoc(d.goalX, d.goalY) { d.goalX++ } } else { d.errorX, d.errorY = d.cursX-1, d.cursY d.message = fmt.Sprintf( "(%d;%d)[%3d/%3d] (%d;%d)[%3d/%3d] %s", d.nodes[src].X, d.nodes[src].Y, d.nodes[src].Used, d.nodes[src].Size, d.nodes[dest].X, d.nodes[dest].Y, d.nodes[dest].Used, d.nodes[dest].Size, err.Error(), ) } } func (d *Display) moveDataDown() { if d.cursY <= 0 { d.message = "Invalid Request" return } src := iToLoc(d.cursX, d.cursY-1) dest := iToLoc(d.cursX, d.cursY) if ok, err := IsViablePair(d.nodes[src], d.nodes[dest]); ok { d.nodes[dest].Used = d.nodes[src].Used d.nodes[src].Used = 0 d.cursY-- d.message = "Data Moved" d.moveCount++ if src == iToLoc(d.goalX, d.goalY) { d.goalY++ } } else { d.errorX, d.errorY = d.cursX, d.cursY-1 d.message = fmt.Sprintf( "(%d;%d)[%3d/%3d] (%d;%d)[%3d/%3d] %s", d.nodes[src].X, d.nodes[src].Y, d.nodes[src].Used, d.nodes[src].Size, d.nodes[dest].X, d.nodes[dest].Y, d.nodes[dest].Used, d.nodes[dest].Size, err.Error(), ) } } func (d *Display) moveDataLeft() { if d.cursX >= d.maxX { d.message = "Invalid Request" return } src := iToLoc(d.cursX+1, d.cursY) dest := iToLoc(d.cursX, d.cursY) if ok, err := IsViablePair(d.nodes[src], d.nodes[dest]); ok { d.nodes[dest].Used = d.nodes[src].Used d.nodes[src].Used = 0 d.cursX++ d.message = "Data Moved" d.moveCount++ if src == iToLoc(d.goalX, d.goalY) { d.goalX-- } } else { d.errorX, d.errorY = d.cursX+1, d.cursY d.message = fmt.Sprintf( "(%d;%d)[%3d/%3d] (%d;%d)[%3d/%3d] %s", d.nodes[src].X, d.nodes[src].Y, d.nodes[src].Used, d.nodes[src].Size, d.nodes[dest].X, d.nodes[dest].Y, d.nodes[dest].Used, d.nodes[dest].Size, err.Error(), ) } } func (d *Display) PrintGrid() { cursor := color.New(color.FgBlack).Add(color.BgWhite) if !d.cursorMode { cursor = color.New(color.FgBlack).Add(color.BgYellow) } goal := color.New(color.FgGreen) error := color.New(color.FgRed) df := color.New(color.FgWhite) if d.goalX == 0 && d.goalY == 0 { df = color.New(color.FgYellow) } // Each cell takes up 9 width // We can only fit about (d.screenWidth / 9) cells wide // and d.screenHeight tall //dispWidth := d.screenWidth / 9 numCellsW := d.screenWidth / 3 leftMost, rightMost := d.cursX-(numCellsW/2), d.cursX+(numCellsW/2) for leftMost < 0 { leftMost++ rightMost++ } if rightMost > d.maxX { rightMost = d.maxX } df.Print(" ") for j := leftMost; j <= rightMost; j++ { df.Printf("|%7d|", j) } df.Println() for i := 0; i <= d.maxY; i++ { df.Printf("|%2d|", i) for j := leftMost; j <= rightMost; j++ { n := d.nodes[iToLoc(j, i)] if j == d.cursX && i == d.cursY { cursor.Printf("[%3d/%3d]", n.Used, n.Size) } else if j == d.errorX && i == d.errorY { error.Printf("[%3d/%3d]", n.Used, n.Size) } else if j == d.goalX && i == d.goalY { goal.Printf("[%3d/%3d]", n.Used, n.Size) } else { if n != nil { df.Printf("[%3d/%3d]", n.Used, n.Size) } else { df.Print("[ / ]") } } } df.Println() } df.Println(d.message) df.Println("Moves:", d.moveCount, "Goal Data at:", iToLoc(d.goalX, d.goalY)) if d.cursorMode { cursor.Println("Cursor Mode") } else { cursor.Println("Data Move Mode") } df.Println("(q): quit, (space): switch mode") } func iToLoc(x, y int) string { return h.Itoa(x) + ";" + h.Itoa(y) } func AddViablePair(a, b *Node, list []string) []string { // Make sure this pair isn't already in the list for i := range list { if list[i] == a.GetLocString()+"=>"+b.GetLocString() || list[i] == b.GetLocString()+"=>"+a.GetLocString() { return list } } return append(list, a.GetLocString()+"=>"+b.GetLocString()) } func IsViablePair(a, b *Node) (bool, error) { fmt.Println(" Initiating Viable Pair Check:", a.GetLocString(), b.GetLocString()) if a == b { return false, errors.New("They're the same node") } //if a.Used == 0 { // return false, errors.New(a.GetLocString() + " has no data") //} if a.Used > b.Size-b.Used { return false, errors.New(b.GetLocString() + " doesn't have enough space") } return true, nil } type Node struct { X, Y int Size, Used int } func CreateNode(x, y, size, used int) *Node { n := new(Node) n.X, n.Y = x, y n.Size, n.Used = size, used return n } func CreateNodeFromDfListing(inp string) (*Node, error) { pts := strings.Fields(inp) if len(pts) != 5 { return nil, errors.New("Error Creating Node: Invalid DF Listing") } n := new(Node) parseLoc := strings.Split(pts[0], "-") if parseLoc[1][0] == 'x' { n.X = h.Atoi(parseLoc[1][1:]) } if parseLoc[2][0] == 'y' { n.Y = h.Atoi(parseLoc[2][1:]) } n.Size = h.Atoi(strings.TrimSuffix(pts[1], "T")) n.Used = h.Atoi(strings.TrimSuffix(pts[2], "T")) return n, nil } func (n *Node) GetLocString() string { return h.Itoa(n.X) + ";" + h.Itoa(n.Y) } func (n *Node) ToString() string { return fmt.Sprint("[", n.GetLocString(), "](S:", h.Itoa(n.Size), ";A:", h.Itoa(n.Size-n.Used), ";U:", h.Itoa(n.Used), ")") } func CenterText(txt string, width int) string { width -= len(txt) return strings.Repeat(" ", width/2) + txt + strings.Repeat(" ", width/2) }