package main import ( "fmt" "log" "os" "strconv" "strings" "time" "github.com/fatih/color" termbox "github.com/nsf/termbox-go" ) var tWidth, tHeight int // Puzzle 1 Input: 1364 31 39 // Puzzle 1 Test Input: 10 7 4 func main() { mode := "solve" if len(os.Args) < 4 { fmt.Println("Usage: day13 ") os.Exit(1) } seed := atoi(os.Args[1]) destX, destY := atoi(os.Args[2]), atoi(os.Args[3]) if len(os.Args) >= 5 { mode = os.Args[4] } f := CreateFloor(1, 1, destX, destY, seed) if err := termbox.Init(); err != nil { panic(err) } tWidth, tHeight = termbox.Size() termbox.Close() switch mode { case "solve": if f.Solve(f.start.x, f.start.y, 0, true) { f.dispCoord = f.end } ClearScreen() f.Print() fmt.Println("Shortest Path:", len(f.solvePath.coords)) case "walk": dist := 50 f.Walk(f.start.x, f.start.y, 0, dist, true) fmt.Println("Within", dist, "steps: ", len(f.testedPath.coords)) } } type Coord struct { x, y, dist int } func (c *Coord) Is(x, y int) bool { return c.x == x && c.y == y } func (c *Coord) Equals(t *Coord) bool { return c.x == t.x && c.y == t.y } func NewCoord(x, y int) *Coord { return &Coord{x, y, -1} } type Path struct { coords []Coord } func (p *Path) Append(c Coord) { p.coords = append(p.coords, c) } func (p *Path) ContainsCoord(x, y int) bool { for i := range p.coords { if p.coords[i].Is(x, y) { return true } } return false } func (p *Path) GetCoordAt(x, y int) *Coord { for i := range p.coords { if p.coords[i].Is(x, y) { return &p.coords[i] } } return nil } type Floor struct { start *Coord end *Coord seed int testedPath Path solvePath Path dispCoord *Coord } func CreateFloor(stX, stY, endX, endY, seed int) *Floor { f := Floor{ start: NewCoord(stX, stY), end: NewCoord(endX, endY), seed: seed, } return &f } func (f *Floor) Walk(x, y, dist, maxDist int, print bool) { wrkCoord := Coord{x, y, dist} if f.IsWall(x, y) || f.testedPath.ContainsCoord(x, y) { return } if dist == maxDist { f.testedPath.Append(wrkCoord) return } if print { f.dispCoord = &wrkCoord ClearScreen() f.Print() fmt.Println("Tested Spots:", len(f.testedPath.coords)) time.Sleep(time.Millisecond * 70) } if !f.IsWall(x-1, y) { if t := f.testedPath.GetCoordAt(x-1, y); t != nil { if t.dist+1 < wrkCoord.dist { return } } } if !f.IsWall(x+1, y) { if t := f.testedPath.GetCoordAt(x+1, y); t != nil { if t.dist+1 < wrkCoord.dist { return } } } if !f.IsWall(x, y-1) { if t := f.testedPath.GetCoordAt(x, y-1); t != nil { if t.dist+1 < wrkCoord.dist { return } } } if !f.IsWall(x, y+1) { if t := f.testedPath.GetCoordAt(x, y+1); t != nil { if t.dist+1 < wrkCoord.dist { return } } } f.testedPath.Append(wrkCoord) // Try intelligently first // (Attempt to move towards the exit) if x > 0 { f.Walk(x-1, y, wrkCoord.dist+1, maxDist, print) } if y > 0 { f.Walk(x, y-1, wrkCoord.dist+1, maxDist, print) } f.Walk(x+1, y, wrkCoord.dist+1, maxDist, print) f.Walk(x, y+1, wrkCoord.dist+1, maxDist, print) } func (f *Floor) Solve(x, y, dist int, print bool) bool { wrkCoord := Coord{x, y, dist} if f.end.Is(x, y) { return true } if f.IsWall(x, y) || f.testedPath.ContainsCoord(x, y) { return false } // Test if there is a shorter path to this coordinate if !f.IsWall(x-1, y) { if t := f.testedPath.GetCoordAt(x-1, y); t != nil { if t.dist+1 < wrkCoord.dist { return false } } } if !f.IsWall(x+1, y) { if t := f.testedPath.GetCoordAt(x+1, y); t != nil { if t.dist+1 < wrkCoord.dist { return false } } } if !f.IsWall(x, y-1) { if t := f.testedPath.GetCoordAt(x, y-1); t != nil { if t.dist+1 < wrkCoord.dist { return false } } } if !f.IsWall(x, y+1) { if t := f.testedPath.GetCoordAt(x, y+1); t != nil { if t.dist+1 < wrkCoord.dist { return false } } } if print { f.dispCoord = &wrkCoord ClearScreen() f.Print() fmt.Println("Tested Spots:", len(f.testedPath.coords)) time.Sleep(time.Millisecond * 70) } f.testedPath.Append(wrkCoord) // Try intelligently first // (Attempt to move towards the exit) if x > f.end.x && x > 0 { if f.Solve(x-1, y, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } if y > f.end.y && y > 0 { if f.Solve(x, y-1, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } if x < f.end.x { if f.Solve(x+1, y, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } if y < f.end.y { if f.Solve(x, y+1, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } // Intelligence failed us... Just find a move if x > 0 { if f.Solve(x-1, y, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } if y > 0 { if f.Solve(x, y-1, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } } // This is where it gets shaky... // Since we have an infinite maze, this could run forever // So we have a hard cutoff at: var MaxInt = int(^uint(0) >> 1) if len(f.testedPath.coords) >= MaxInt { fmt.Println("ERROR: Couldn't find a path.") os.Exit(1) } if f.Solve(x+1, y, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } if f.Solve(x, y+1, wrkCoord.dist+1, print) { f.solvePath.Append(wrkCoord) return true } return false } func (f *Floor) IsWall(x, y int) bool { sum := (x*x + 3*x + 2*x*y + y + y*y + f.seed) s := fmt.Sprintf("%b", sum) if strings.Count(s, "1")%2 == 0 { return false } return true } func (f *Floor) Print() { wall := color.New(color.BgWhite) space := color.New(color.FgWhite) g := color.New(color.BgGreen).Add(color.FgBlack) g.Add(color.Bold) r := color.New(color.BgRed) b := color.New(color.BgBlue).Add(color.BgBlack) b.Add(color.Bold) topY, topX := tHeight, tWidth botY, botX := 0, 0 // We want to center approx 20x20 on our current location // f.testedPath[len(f.testedPath)-1]? if len(f.testedPath.coords) > 0 { cntrCoord := f.testedPath.coords[len(f.testedPath.coords)-1] if topY < cntrCoord.y+(tHeight/2) { topY = cntrCoord.y + (tHeight / 2) } if topY > tHeight { botY = topY - tHeight } if topX < cntrCoord.x+(tWidth/2) { topX = cntrCoord.x + (tWidth / 2) } if topX > tWidth { botX = topX - tWidth } } for y := botY; y < topY; y++ { for x := botX; x < topX; x++ { if f.dispCoord != nil && f.dispCoord.Is(x, y) { g.Print("O") } else if f.solvePath.ContainsCoord(x, y) { g.Print(".") } else if f.testedPath.ContainsCoord(x, y) { r.Print(" ") } else { if f.end.Is(x, y) { b.Print("X") } else if f.IsWall(x, y) { wall.Print(" ") } else { space.Print(".") } } } fmt.Println() } } func ClearScreen() { fmt.Print("\033[H\033[2J") } func atoi(i string) int { var ret int var err error if ret, err = strconv.Atoi(i); err != nil { log.Fatal("Invalid Atoi") } return ret }