adventofcode/2016/day24/main.go

501 lines
11 KiB
Go
Raw Permalink Normal View History

2016-12-24 19:25:04 +00:00
package main
import (
"fmt"
"math"
"os"
2016-12-28 14:48:23 +00:00
"sort"
"time"
2016-12-24 19:25:04 +00:00
"github.com/fatih/color"
2023-12-17 16:52:08 +00:00
h "git.bullercodeworks.com/brian/adventofcode/helpers"
2016-12-24 19:25:04 +00:00
)
2016-12-28 14:48:23 +00:00
var tWidth, tHeight int
2016-12-24 19:25:04 +00:00
func main() {
2023-12-17 16:52:08 +00:00
fileNm := h.GetArgNumber(1)
2016-12-28 14:48:23 +00:00
if len(os.Args) < 2 {
fmt.Println("Usage: ./day24 <mazefile>")
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])
}
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
}
2023-12-17 16:52:08 +00:00
poiPerms := h.StringPermutations(poiString)
2016-12-28 14:48:23 +00:00
shortest := -1
var shortestPerm string
for _, perm := range poiPerms {
if perm[0] != '0' {
continue
}
2023-12-17 16:52:08 +00:00
if h.ArgIsSet("-2") {
2016-12-28 14:48:23 +00:00
// 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
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
permTtl += f.shortestPaths[beg+";"+end]
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
if permTtl < shortest || shortest == -1 {
shortestPerm = perm
shortest = permTtl
}
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
fmt.Println(shortestPerm, ": ", shortest)
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
type Floor struct {
cells []string
start *Coord
end *Coord
pois []Coord
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
shortestPaths map[string]int
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
testedPath Path
solvePath Path
cellCount int
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
debug bool
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func CreateFloorFromFile(fileNm string) *Floor {
f := new(Floor)
2023-12-17 16:52:08 +00:00
f.debug = h.ArgIsSet("-d")
f.cells = h.FileToStringSlice(fileNm)
2016-12-28 14:48:23 +00:00
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
}
}
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
// Sort the pois
sort.Sort(CoordByLabel(f.pois))
// Find the shortest paths between all points of interest
f.shortestPaths = make(map[string]int)
2023-12-17 16:52:08 +00:00
if h.ArgIsSet("-2") {
2016-12-28 14:48:23 +00:00
/* 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
}
}
}
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
return f
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func (f *Floor) GetPoi(lbl byte) *Coord {
for i := range f.pois {
if f.pois[i].label == lbl {
return &f.pois[i]
}
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
return nil
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
var shortestFound int
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
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
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
return *new(Path)
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func (f *Floor) Print() {
for y := range f.cells {
for x := range f.cells[y] {
fmt.Print(string(f.cells[y][x]))
}
fmt.Println()
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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")
2016-12-24 19:25:04 +00:00
} else {
2016-12-28 14:48:23 +00:00
pathCol.Print(string(f.cells[y][x]))
2016-12-24 19:25:04 +00:00
}
} else {
2016-12-28 14:48:23 +00:00
fmt.Print(string(f.cells[y][x]))
2016-12-24 19:25:04 +00:00
}
}
fmt.Println()
}
}
2016-12-28 14:48:23 +00:00
func (f *Floor) IsWall(x, y int) bool {
return f.cells[y][x] == '#'
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func (f *Floor) GetCoord(x, y int) *Coord {
return NewCoord(x, y, f.cells[y][x])
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func (f *Floor) Solve(x, y, dist int) (Path, bool) {
var tPathContains bool
shortest := new(Path)
wrkCoord := *f.GetCoord(x, y)
wrkCoord.dist = dist
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
if f.end.Is(x, y) {
shortest.Append(wrkCoord)
if shortestFound == -1 || dist < shortestFound {
shortestFound = dist
}
return *shortest, true
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
if f.IsWall(x, y) || (shortestFound != -1 && dist > shortestFound) {
return *shortest, false
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
// 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
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
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
}
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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
}
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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
}
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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
2016-12-24 19:25:04 +00:00
}
}
}
2016-12-28 14:48:23 +00:00
// We haven't found a shorter path to this coordinate, so carry on
if !tPathContains {
f.testedPath.Append(wrkCoord)
if f.debug {
2023-12-17 16:52:08 +00:00
fmt.Println(h.ClearScreen, f.start.Name(), "=>", f.end.Name(), "\n", len(f.testedPath.coords), "/", f.cellCount, "\n", "Shortest:", shortestFound)
2016-12-28 14:48:23 +00:00
f.PrintPath(f.testedPath)
time.Sleep(time.Millisecond * 50)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
var solvePaths []Path
var didN, didE, didS, didW bool
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
var preferNS bool
if math.Abs(float64(f.end.y-y)) > math.Abs(float64(f.end.x-x)) {
// Favor N/S movements
preferNS = true
}
2016-12-24 19:25:04 +00:00
2016-12-28 14:48:23 +00:00
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)
2016-12-25 05:58:45 +00:00
}
2016-12-28 14:48:23 +00:00
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)
2016-12-25 05:58:45 +00:00
}
2016-12-28 14:48:23 +00:00
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
2016-12-25 05:58:45 +00:00
}
2016-12-28 14:48:23 +00:00
} 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)
2016-12-25 05:58:45 +00:00
}
2016-12-28 14:48:23 +00:00
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
2016-12-25 05:58:45 +00:00
}
}
}
2016-12-28 14:48:23 +00:00
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)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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]
2016-12-24 19:25:04 +00:00
}
}
}
2016-12-28 14:48:23 +00:00
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)
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
}
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
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
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]
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
return nil
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
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
2016-12-24 19:25:04 +00:00
}
}
2016-12-28 14:48:23 +00:00
return false
}
func (p *Path) Length() int {
return len(p.coords)
2016-12-24 19:25:04 +00:00
}
2016-12-28 14:48:23 +00:00
func (p *Path) Name() string {
return p.coords[0].Name() + " -> " + p.coords[len(p.coords)-1].Name()
2016-12-24 19:25:04 +00:00
}