package main import ( "fmt" "log" "os" "strconv" "strings" "time" "github.com/fatih/color" ) // Puzzle 1 Input: 1364 31 39 // Puzzle 1 Test Input: 10 7 4 // Puzzle 2: 101 < x < 128 ?? 103 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) 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) Pop() Coord { var ret Coord prePop := len(p.coords) ret, p.coords = p.coords[len(p.coords)-1], p.coords[:len(p.coords)-1] if len(p.coords) != prePop-1 { fmt.Println("Error, popping didn't result in one less size") os.Exit(1) } return ret } func (p *Path) ContainsCoord(c *Coord) bool { for i := range p.coords { if p.coords[i].Equals(c) { return true } } return false } func (p *Path) ContainsCoordXY(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.ContainsCoordXY(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.ContainsCoordXY(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("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) topY, topX := f.end.y+10, f.end.x+10 for y := 0; y < topY; y++ { for x := 0; x < topX; x++ { if f.dispCoord != nil && f.dispCoord.Is(x, y) { g.Print("O") } else if f.solvePath.ContainsCoordXY(x, y) { g.Print(".") } else if f.testedPath.ContainsCoordXY(x, y) { r.Print(" ") } else { if f.end.Is(x, y) { space.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 }