package main import ( "fmt" "math" "os" "sort" "time" "github.com/fatih/color" "../../" ) var tWidth, tHeight int func main() { fileNm := aoc.GetArgNumber(1) if len(os.Args) < 2 { fmt.Println("Usage: ./day24 ") os.Exit(1) } var f *Floor var poiString string f = CreateFloorFromFile(fileNm) for i := 0; i < len(f.pois); i++ { poiString += f.pois[i].Name() if f.debug { for j := i + 1; j < len(f.pois); j++ { idx := f.pois[i].Name() + ";" + f.pois[j].Name() fmt.Println(idx, f.shortestPaths[idx]) } } } poiPerms := aoc.StringPermutations(poiString) shortest := -1 var shortestPerm string for _, perm := range poiPerms { if perm[0] != '0' { continue } if aoc.ArgIsSet("-2") { // For part 2 we return to 0 perm = perm + "0" } 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 += f.shortestPaths[beg+";"+end] } } if permTtl < shortest || shortest == -1 { shortestPerm = perm shortest = permTtl } } fmt.Println(shortestPerm, ": ", shortest) } type Floor struct { cells []string start *Coord end *Coord pois []Coord shortestPaths map[string]int testedPath Path solvePath Path cellCount int debug bool } func CreateFloorFromFile(fileNm string) *Floor { f := new(Floor) f.debug = aoc.ArgIsSet("-d") f.cells = aoc.FileToStringSlice(fileNm) for y := range f.cells { for x := range f.cells[y] { if f.cells[y][x] != '#' { f.cellCount++ } if f.cells[y][x] != '#' && f.cells[y][x] != '.' { // A point of interest wrkCoord := NewCoord(x, y, f.cells[y][x]) f.pois = append(f.pois, *wrkCoord) if f.cells[y][x] == '0' { f.start = wrkCoord } } } } // Sort the pois sort.Sort(CoordByLabel(f.pois)) // Find the shortest paths between all points of interest f.shortestPaths = make(map[string]int) if aoc.ArgIsSet("-2") { /* Output from running part 1 1;2 232 1;3 30 1;4 188 1;5 230 1;6 80 1;7 54 2;3 218 2;4 56 2;5 74 2;6 212 2;7 278 3;4 174 3;5 216 3;6 66 3;7 80 4;5 54 4;6 168 4;7 234 5;6 210 5;7 276 6;7 126 */ f.shortestPaths["0;1"] = 30 f.shortestPaths["0;2"] = 258 f.shortestPaths["0;3"] = 56 f.shortestPaths["0;4"] = 214 f.shortestPaths["0;5"] = 256 f.shortestPaths["0;6"] = 106 f.shortestPaths["0;7"] = 44 f.shortestPaths["1;2"] = 232 f.shortestPaths["1;3"] = 30 f.shortestPaths["1;4"] = 188 f.shortestPaths["1;5"] = 230 f.shortestPaths["1;6"] = 80 f.shortestPaths["1;7"] = 54 f.shortestPaths["2;3"] = 218 f.shortestPaths["2;4"] = 56 f.shortestPaths["2;5"] = 74 f.shortestPaths["2;6"] = 212 f.shortestPaths["2;7"] = 278 f.shortestPaths["3;4"] = 174 f.shortestPaths["3;5"] = 216 f.shortestPaths["3;6"] = 66 f.shortestPaths["3;7"] = 80 f.shortestPaths["4;5"] = 54 f.shortestPaths["4;6"] = 168 //f.shortestPaths["4;7"] = 266 //f.shortestPaths["5;6"] = 262 //f.shortestPaths["5;7"] = 304 f.shortestPaths["6;7"] = 126 } for i := range f.pois { for j := range f.pois { if i != j { one, two := f.pois[i], f.pois[j] if one.label > two.label { one, two = two, one } idx := string(one.label) + ";" + string(two.label) if _, ok := f.shortestPaths[idx]; !ok { p := f.GetShortestPath(&one, &two) if f.debug { fmt.Println(p.Name()) f.PrintPath(p) fmt.Println("Path length:", len(p.coords), "\n") for i := range p.coords { fmt.Print(p.coords[i].Name(), ",") } fmt.Println() } f.shortestPaths[idx] = len(p.coords) - 1 } } } } return f } func (f *Floor) GetPoi(lbl byte) *Coord { for i := range f.pois { if f.pois[i].label == lbl { return &f.pois[i] } } return nil } var shortestFound int func (f *Floor) GetShortestPath(st, end *Coord) Path { f.solvePath = *new(Path) f.testedPath = *new(Path) hldStart := f.start f.start = end f.end = st shortestFound = -1 path, sol := f.Solve(f.start.x, f.start.y, 0) f.start = hldStart if sol { return path } return *new(Path) } func (f *Floor) Print() { for y := range f.cells { for x := range f.cells[y] { fmt.Print(string(f.cells[y][x])) } fmt.Println() } } func (f *Floor) PrintPath(p Path) { pathCol := color.New(color.BgGreen) for y := range f.cells { for x := range f.cells[y] { if p.ContainsCoord(x, y) { if f.cells[y][x] == '.' { pathCol.Print("O") } else { pathCol.Print(string(f.cells[y][x])) } } else { fmt.Print(string(f.cells[y][x])) } } fmt.Println() } } func (f *Floor) IsWall(x, y int) bool { return f.cells[y][x] == '#' } func (f *Floor) GetCoord(x, y int) *Coord { return NewCoord(x, y, f.cells[y][x]) } func (f *Floor) Solve(x, y, dist int) (Path, bool) { var tPathContains bool shortest := new(Path) wrkCoord := *f.GetCoord(x, y) wrkCoord.dist = dist if f.end.Is(x, y) { shortest.Append(wrkCoord) if shortestFound == -1 || dist < shortestFound { shortestFound = dist } return *shortest, true } if f.IsWall(x, y) || (shortestFound != -1 && dist > shortestFound) { return *shortest, false } // Test if we already have this coord via a shorter path if t := f.testedPath.GetCoordAt(x, y); t != nil { if t.dist <= dist { return *shortest, false } tPathContains = true t.dist = dist } // 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 *shortest, false } } } if !f.IsWall(x+1, y) { if t := f.testedPath.GetCoordAt(x+1, y); t != nil { if t.dist+1 < wrkCoord.dist { return *shortest, false } } } if !f.IsWall(x, y-1) { if t := f.testedPath.GetCoordAt(x, y-1); t != nil { if t.dist+1 < wrkCoord.dist { return *shortest, false } } } if !f.IsWall(x, y+1) { if t := f.testedPath.GetCoordAt(x, y+1); t != nil { if t.dist+1 < wrkCoord.dist { return *shortest, false } } } // We haven't found a shorter path to this coordinate, so carry on if !tPathContains { f.testedPath.Append(wrkCoord) if f.debug { fmt.Println(aoc.ClearScreen, f.start.Name(), "=>", f.end.Name(), "\n", len(f.testedPath.coords), "/", f.cellCount, "\n", "Shortest:", shortestFound) f.PrintPath(f.testedPath) time.Sleep(time.Millisecond * 50) } } var solvePaths []Path var didN, didE, didS, didW bool var preferNS bool if math.Abs(float64(f.end.y-y)) > math.Abs(float64(f.end.x-x)) { // Favor N/S movements preferNS = true } for tdir := 0; tdir < 2; tdir++ { if preferNS { switch { case f.end.y < y && !didN: f.TestAndUpdate(x, y-1, dist+1) if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didN = true case f.end.y > y && !didS: f.TestAndUpdate(x, y+1, dist+1) if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didS = true case f.end.x > x && !didE: f.TestAndUpdate(x+1, y, dist+1) if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didE = true case f.end.x < x && !didW: f.TestAndUpdate(x-1, y, dist+1) if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didW = true } } else { switch { case f.end.x > x && !didE: f.TestAndUpdate(x+1, y, dist+1) if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didE = true case f.end.x < x && !didW: f.TestAndUpdate(x-1, y, dist+1) if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didW = true case f.end.y < y && !didN: f.TestAndUpdate(x, y-1, dist+1) if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didN = true case f.end.y > y && !didS: f.TestAndUpdate(x, y+1, dist+1) if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } didS = true } } } if !didN { f.TestAndUpdate(x, y-1, dist+1) if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } } if !didE { f.TestAndUpdate(x+1, y, dist+1) if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } } if !didS { f.TestAndUpdate(x, y+1, dist+1) if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } } if !didW { f.TestAndUpdate(x-1, y, dist+1) if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol { p.Append(wrkCoord) solvePaths = append(solvePaths, p) } } var sol bool if sol = len(solvePaths) > 0; sol { shortest = &solvePaths[0] for i := range solvePaths { if solvePaths[i].Length()+1 < shortest.Length() { shortest = &solvePaths[i] } } } return *shortest, sol } func (f *Floor) TestAndUpdate(x, y, dist int) { if t := f.testedPath.GetCoordAt(x, y); t != nil { if t.dist > dist+1 { f.testedPath.SetCoordDist(x, y, dist+1) } } } type Coord struct { x, y, dist int label byte } func NewCoord(x, y int, label byte) *Coord { return &Coord{x, y, -1, label} } func (c *Coord) Name() string { if c.label != '#' && c.label != '.' && c.label != 0 { return string(c.label) } return fmt.Sprint("(", c.x, ",", c.y, ")") } func (c *Coord) Is(x, y int) bool { return c.x == x && c.y == y } type CoordByLabel []Coord func (a CoordByLabel) Len() int { return len(a) } func (a CoordByLabel) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a CoordByLabel) Less(i, j int) bool { return a[i].label < a[j].label } 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 } func (p *Path) SetCoordDist(x, y, dist int) bool { for i := range p.coords { if p.coords[i].Is(x, y) { p.coords[i].dist = dist return true } } return false } func (p *Path) Length() int { return len(p.coords) } func (p *Path) Name() string { return p.coords[0].Name() + " -> " + p.coords[len(p.coords)-1].Name() }