adventofcode/day13/main.go

334 lines
6.6 KiB
Go

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
func main() {
mode := "solve"
if len(os.Args) < 4 {
fmt.Println("Usage: day13 <seed> <dest-x> <dest-y>")
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
}