package main import ( "errors" "fmt" "math" "os" "github.com/fatih/color" termbox "github.com/nsf/termbox-go" "../../" ) var shortestSolutionDist int func main() { playMode := aoc.ArgIsSet("-play") fileNm := aoc.GetArgNumber(1) txtMaze := aoc.FileToStringSlice(fileNm) m := CreateMaze(txtMaze) if playMode { player := CreatePlayer(m) err := termbox.Init() //tWidth, tHeight := termbox.Size() if err != nil { fmt.Println("Error initializing termbox") os.Exit(1) } defer termbox.Close() var done bool for !done { fmt.Println(aoc.ClearScreen) player.PrintMaze() if player.CheckIsDone() { fmt.Println("Maze Completed in", player.dist, "steps!") fmt.Println("Press any key to quit.") done = true termbox.PollEvent() } else { ev := termbox.PollEvent() if ev.Type == termbox.EventKey { switch { case ev.Ch == 'q': done = true case ev.Key == termbox.KeyArrowUp: player.MoveUp() case ev.Key == termbox.KeyArrowDown: player.MoveDown() case ev.Key == termbox.KeyArrowLeft: player.MoveLeft() case ev.Key == termbox.KeyArrowRight: player.MoveRight() } } } } } else { shortestSolutionDist = -1 m.PrintMaze() m.StartSolve(0) fmt.Println("Shortest Solution: ", shortestSolutionDist) } } var numWorkers int func PrintStatus() { fmt.Println(aoc.ClearScreen) fmt.Println("Workers: ", numWorkers) } type Player struct { pos *Coord m *Maze hitPois *CoordCollection dist int } func CreatePlayer(m *Maze) *Player { p := new(Player) p.pos = CreateCoord(m.start.x, m.start.y) p.m = m p.hitPois = CreateCoordCollection() p.hitPois.Add(p.pos.x, p.pos.y, "0") return p } func (p *Player) CheckIsDone() bool { for _, v := range p.m.pois.coords { if !p.hitPois.Contains(v.x, v.y) { return false } } return true } func (p *Player) MoveUp() bool { if !p.CanMoveTo(p.pos.x, p.pos.y-1) { return false } p.pos.y-- p.dist++ p.CheckMovedPos() return true } func (p *Player) MoveDown() bool { if !p.CanMoveTo(p.pos.x, p.pos.y+1) { return false } p.pos.y++ p.dist++ p.CheckMovedPos() return true } func (p *Player) MoveRight() bool { if !p.CanMoveTo(p.pos.x+1, p.pos.y) { return false } p.pos.x++ p.dist++ p.CheckMovedPos() return true } func (p *Player) MoveLeft() bool { if !p.CanMoveTo(p.pos.x-1, p.pos.y) { return false } p.pos.x-- p.dist++ p.CheckMovedPos() return true } func (p *Player) CanMoveTo(x, y int) bool { return !p.m.walls.Contains(x, y) } func (p *Player) CheckMovedPos() { if c, err := p.m.pois.GetXY(p.pos.x, p.pos.y); err == nil { p.hitPois.Add(c.x, c.y, c.label) } return } func (p *Player) PrintMaze() { var err error poiC := color.New(color.BgGreen) hitPoiC := color.New(color.BgBlue) playerC := color.New(color.BgYellow) target := color.New(color.BgRed) next := p.FindClosestNewPoi() for y := 0; y < p.m.h; y++ { for x := 0; x < p.m.w; x++ { var c *Coord if c, err = p.m.walls.GetXY(x, y); err == nil { fmt.Print(c.label) } else if p.pos.is(x, y) { playerC.Print("@") } else if p.m.start.is(x, y) { hitPoiC.Print("0") } else if c, err = p.m.pois.GetXY(x, y); err == nil { if p.hitPois.Contains(x, y) { hitPoiC.Print(c.label) } else if next != nil && next.is(x, y) { target.Print(c.label) } else { poiC.Print(c.label) } } else { fmt.Print(".") } } fmt.Println() } fmt.Printf("Next Closest POI (%s: %d,%d)\n", next.label, next.x, next.y) fmt.Printf("Steps Taken: %d\n", p.dist) } func (p *Player) FindClosestNewPoi() *Coord { var shortestPoi Coord shortestDist := -1 for _, v := range p.m.pois.coords { if !p.hitPois.Contains(v.x, v.y) { if t := FindSLDistance(p.pos, &v); t < shortestDist || shortestDist == -1 { shortestDist = t shortestPoi = v } } } return &shortestPoi } type Coord struct { x, y int label string dist int } func CreateCoord(x, y int) *Coord { return &Coord{x: x, y: y, label: ""} } func (c *Coord) is(x, y int) bool { return c.x == x && c.y == y } type CoordCollection struct { coords []Coord } func CreateCoordCollection() *CoordCollection { cc := new(CoordCollection) return cc } func CreateCCFromCoordSlice(c []Coord) *CoordCollection { cc := new(CoordCollection) for i := range c { cc.Add(c[i].x, c[i].y, c[i].label) } return cc } func (cc *CoordCollection) Add(x, y int, l string) { if !cc.Contains(x, y) { cc.coords = append(cc.coords, Coord{x: x, y: y, label: l}) } } func (cc *CoordCollection) Contains(x, y int) bool { if cc.coords != nil && len(cc.coords) > 0 { for i := range cc.coords { if cc.coords[i].x == x && cc.coords[i].y == y { return true } } } return false } func (cc *CoordCollection) GetXY(x, y int) (*Coord, error) { for i := range cc.coords { if cc.coords[i].x == x && cc.coords[i].y == y { return &cc.coords[i], nil } } return nil, errors.New("Collection Doesn't Contain Coord") } 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 Maze struct { start, end *Coord pois *CoordCollection walls *CoordCollection h, w int solvePath Path testedPath Path } func CreateMaze(inp []string) *Maze { m := new(Maze) m.pois = CreateCoordCollection() m.walls = CreateCoordCollection() for y := range inp { for x := range inp[y] { if inp[y][x] == '#' { m.walls.Add(x, y, aoc.FillChar) } else if inp[y][x] != '.' { if inp[y][x] == '0' { m.start = &Coord{x: x, y: y, label: "0"} } else { m.pois.Add(x, y, string(inp[y][x])) } } if x > m.w { m.w = x } } if y > m.h { m.h = y } } m.w++ return m } func CopyMaze(walls, pois *CoordCollection) *Maze { newM := new(Maze) newM.pois = CreateCoordCollection() newM.walls = CreateCoordCollection() for _, v := range walls.coords { newM.walls.Add(v.x, v.y, v.label) } for _, v := range pois.coords { newM.pois.Add(v.x, v.y, v.label) } return newM } func (m *Maze) PrintMaze() { var err error poiC := color.New(color.BgGreen) for y := 0; y < m.h; y++ { for x := 0; x < m.w; x++ { var c *Coord if c, err = m.walls.GetXY(x, y); err == nil { fmt.Print(c.label) } else if m.start.is(x, y) { poiC.Print("0") } else if c, err = m.pois.GetXY(x, y); err == nil { poiC.Print(c.label) } else { fmt.Print(" ") } } fmt.Println() } } // StartSolve kicks off the solve and returns the lowest number of steps, or an error func (m *Maze) StartSolve(dist int) (int, error) { numWorkers++ solved, d := m.Solve(m.start.x, m.start.y, 0) dist += d numWorkers-- if !solved { return dist, errors.New("Couldn't solve maze. :(") } fmt.Println("Ended Solve (current workers:", numWorkers, "; current shortest:", shortestSolutionDist, ")") if dist < shortestSolutionDist || shortestSolutionDist == -1 { shortestSolutionDist = dist } return dist, nil } // We want to build a set of all solutions from one poi to all others // then find the shortest path that hits them all func (m *Maze) Solve(x, y, dist int) (bool, int) { wrkCoord := Coord{x: x, y: y, dist: dist} if m.end == nil || m.end.is(x, y) { // We found a point of interest! (or we're just starting) // No end set. For each poi, we want to fork a new maze being solved // with the end set to that poi, and that poi removed from the poi list if len(m.pois.coords) == 0 { // Unless there aren't any left, then we solved it return true, dist } for i := range m.pois.coords { endPoi := m.pois.coords[i] // Copy the pois newPois := make([]Coord, len(m.pois.coords)) copy(newPois, m.pois.coords) // Then delete the endPoi from it newPois = append(newPois[:i], newPois[i+1:]...) // Create a new maze with start set to this point and end set to the first poi newM := CopyMaze(m.walls, CreateCCFromCoordSlice(newPois)) newM.start = &wrkCoord newM.end = &endPoi newM.StartSolve(dist + 1) } } if m.testedPath.ContainsCoord(x, y) { return false, 0 } // Figure out if there is a shorter path to this coordinate if !m.walls.Contains(x-1, y) { if t := m.testedPath.GetCoordAt(x-1, y); t != nil { if t.dist+1 < wrkCoord.dist { return false, 0 } } } if !m.walls.Contains(x+1, y) { if t := m.testedPath.GetCoordAt(x+1, y); t != nil { if t.dist+1 < wrkCoord.dist { return false, 0 } } } if !m.walls.Contains(x, y-1) { if t := m.testedPath.GetCoordAt(x, y-1); t != nil { if t.dist+1 < wrkCoord.dist { return false, 0 } } } if !m.walls.Contains(x, y+1) { if t := m.testedPath.GetCoordAt(x, y+1); t != nil { if t.dist+1 < wrkCoord.dist { return false, 0 } } } m.testedPath.Append(wrkCoord) shortest := -1 var foundSol bool if m.testedPath.GetCoordAt(x-1, y) == nil && !m.walls.Contains(x-1, y) { if sol, nDist := m.Solve(x-1, y, wrkCoord.dist+1); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if m.testedPath.GetCoordAt(x, y-1) == nil && !m.walls.Contains(x, y-1) { if sol, nDist := m.Solve(x, y-1, wrkCoord.dist+1); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if m.testedPath.GetCoordAt(x+1, y) == nil && !m.walls.Contains(x+1, y) { if sol, nDist := m.Solve(x+1, y, wrkCoord.dist+1); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if m.testedPath.GetCoordAt(x, y+1) == nil && !m.walls.Contains(x, y+1) { if sol, nDist := m.Solve(x, y+1, wrkCoord.dist+1); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } return foundSol, dist + shortest } var shortestPoiDist map[string]int // Pt2StartSolve finds the shortest distance between every poi and every other poi func (m *Maze) Pt2StartSolve() { shortestPoiDist = make(map[string]int) for _, i := range m.pois.coords { if gud, dist := m.GetShortestPath(m.start, i, 0); gud { shortestPoiDist[m.start.label+";"+i.label] = dist } for _, j := range m.pois.coords { if i.label != j.label { fst, scd := i, j if i[0] > j[0] { fst, scd = j, i } if _, ok := shortestPoiDist[fst+";"+scd]; !ok { if gud, dist := m.GetShortestPath(i, j, 0); gud { shortestPoiDist[fst+";"+scd] = dist } } } } } // TODO: Find shortest path that hits them all } // GetShortestPath just finds the shortest path between two points func (m *Maze) GetShortestPath(beg *Coord, end *Coord, dist int) (int, bool) { // TODO: actually solve for shortest path return 0, false } /* Put on hold // We're going to find the closest POI (straight-line) and solve to it func (m *Maze) Solve(x, y, dist int) bool { wrkCoord := Coord{x: x, y: y, dist: dist} _ = wrkCoord if m.end == nil || m.end.is(x, y) { // We made it (or haven't started)! Do we have any more pois? if len(m.pois.coords) == 0 { // Nope, none left, we're done return true } m.end = m.FindClosestPoi() } return false } */ func (m *Maze) FindClosestPoi() *Coord { var shortestPoi Coord shortestDist := -1 for _, v := range m.pois.coords { if t := FindSLDistance(m.start, &v); t < shortestDist || shortestDist == -1 { shortestDist = t shortestPoi = v } } return &shortestPoi } func FindSLDistance(p1, p2 *Coord) int { a := math.Abs(float64(p1.x) - float64(p2.x)) b := math.Abs(float64(p1.y) - float64(p2.y)) return int(math.Pow(a, 2) + math.Pow(b, 2)) }