package main import ( "errors" "fmt" "math" "os" "github.com/fatih/color" termbox "github.com/nsf/termbox-go" "../../" ) var shortestSolutionDist int var pois []string 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() //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) Contains(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])) } newOne := true for pi := range pois { if pois[pi] == string(inp[y][x]) { newOne = false break } } if newOne { pois = append(pois, 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() } } var shortestPoiDist map[string]int // StartSolve finds the shortest distance between every poi and every other poi // Then figures out the shortest one to hit them all func (m *Maze) StartSolve() { shortestPoiDist = make(map[string]int) for _, i := range m.pois.coords { if dist, gud := m.GetShortestPath(m.start.x, m.start.y, i.x, i.y, 0, *new(Path)); 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.label[0] > j.label[0] { fst, scd = j, i } if _, ok := shortestPoiDist[fst.label+";"+scd.label]; !ok { if dist, gud := m.GetShortestPath(i.x, i.y, j.x, j.y, 0, *new(Path)); gud { shortestPoiDist[fst.label+";"+scd.label] = dist } } } } } var poiString string fmt.Println("pois", pois) for i := range pois { poiString += pois[i] } poiPerms := aoc.StringPermutations(poiString) var wrk []string for i := range poiPerms { var found bool for j := range wrk { if wrk[j] == poiPerms[i] { found = true } } if !found { wrk = append(wrk, poiPerms[i]) } } poiPerms = wrk shortest := -1 var shortestPerm string for _, perm := range poiPerms { if perm[0] != '0' { continue } var permTtl int for i := range perm { if i > 0 { beg, end := string(perm[i-1]), string(perm[i]) if beg[0] > end[0] { beg, end = end, beg } permTtl += shortestPoiDist[beg+";"+end] } } if permTtl < shortest || shortest == -1 { shortestPerm = perm shortest = permTtl } } fmt.Println(shortestPerm, ": ", shortest) } // GetShortestPath just finds the shortest path between two points func (m *Maze) GetShortestPath(begX, begY, endX, endY, dist int, path Path) (int, bool) { if begX == endX && begY == endY { return dist, true } if path.Contains(begX, begY) { return 0, false } // Figure out if there is a shorter path to this coordinate if !m.walls.Contains(begX-1, begY) { if t := path.GetCoordAt(begX-1, begY); t != nil { if t.dist+1 < dist { return 0, false } } } if !m.walls.Contains(begX+1, begY) { if t := path.GetCoordAt(begX+1, begY); t != nil { if t.dist+1 < dist { return 0, false } } } if !m.walls.Contains(begX, begY-1) { if t := path.GetCoordAt(begX, begY-1); t != nil { if t.dist+1 < dist { return 0, false } } } if !m.walls.Contains(begX, begY+1) { if t := path.GetCoordAt(begX, begY+1); t != nil { if t.dist+1 < dist { return 0, false } } } path.Append(Coord{x: begX, y: begY, dist: dist}) shortest := -1 var foundSol bool if path.GetCoordAt(begX-1, begY) == nil && !m.walls.Contains(begX-1, begY) { if nDist, sol := m.GetShortestPath(begX-1, begY, endX, endY, dist+1, path); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if path.GetCoordAt(begX, begY-1) == nil && !m.walls.Contains(begX, begY-1) { if nDist, sol := m.GetShortestPath(begX, begY-1, endX, endY, dist+1, path); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if path.GetCoordAt(begX+1, begY) == nil && !m.walls.Contains(begX+1, begY) { if nDist, sol := m.GetShortestPath(begX+1, begY, endX, endY, dist+1, path); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } if path.GetCoordAt(begX, begY+1) == nil && !m.walls.Contains(begX, begY+1) { if nDist, sol := m.GetShortestPath(begX, begY+1, endX, endY, dist+1, path); sol { foundSol = true if nDist < shortest || shortest == -1 { shortest = nDist } } } return shortest, foundSol } 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)) }