adventofcode/2016/day24/main.go

520 lines
11 KiB
Go

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))
}