|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "bufio" |
| 5 | + "flag" |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + |
| 9 | + "github.com/fxnn/adventofcode2024/util" |
| 10 | +) |
| 11 | + |
| 12 | +const ( |
| 13 | + WALL = '#' |
| 14 | + EMPTY = '.' |
| 15 | + START = 'S' |
| 16 | + END = 'E' |
| 17 | +) |
| 18 | + |
| 19 | +func readCourse() [][]byte { |
| 20 | + var scanner = bufio.NewScanner(os.Stdin) |
| 21 | + |
| 22 | + var y int |
| 23 | + var course [][]byte |
| 24 | + for scanner.Scan() { |
| 25 | + var line = scanner.Text() |
| 26 | + var row = make([]byte, len(line)) |
| 27 | + for x, r := range line { |
| 28 | + row[x] = byte(r) |
| 29 | + } |
| 30 | + course = append(course, row) |
| 31 | + y++ |
| 32 | + } |
| 33 | + if err := scanner.Err(); err != nil { |
| 34 | + fmt.Fprintf(os.Stderr, "Error: %s\n", err) |
| 35 | + os.Exit(1) |
| 36 | + } |
| 37 | + |
| 38 | + return course |
| 39 | +} |
| 40 | + |
| 41 | +var directions = []util.Point{{X: -1, Y: 0}, {X: 1, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: -1}} |
| 42 | + |
| 43 | +func findMoves(course [][]byte) []util.Point { |
| 44 | + var f = findStart(course) |
| 45 | + var moves = []util.Point{f} |
| 46 | + for course[f.Y][f.X] != END { |
| 47 | + for _, d := range directions { |
| 48 | + var t = f.Add(d) |
| 49 | + if len(moves) > 1 && t == moves[len(moves)-2] { |
| 50 | + continue |
| 51 | + } |
| 52 | + if !t.IsInBounds(len(course), len(course[f.Y])) { |
| 53 | + continue |
| 54 | + } |
| 55 | + if course[t.Y][t.X] == WALL { |
| 56 | + continue |
| 57 | + } |
| 58 | + moves = append(moves, t) |
| 59 | + //fmt.Printf("move %d,%d -> %d,%d\n", f.Y, f.X, t.Y, t.X) |
| 60 | + f = t |
| 61 | + break |
| 62 | + } |
| 63 | + } |
| 64 | + return moves |
| 65 | +} |
| 66 | + |
| 67 | +func findStart(course [][]byte) util.Point { |
| 68 | + for y := 0; y < len(course); y++ { |
| 69 | + for x := 0; x < len(course[y]); x++ { |
| 70 | + if course[y][x] == START { |
| 71 | + return util.Point{Y: y, X: x} |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | + fmt.Println("Error: start not found") |
| 76 | + os.Exit(1) |
| 77 | + return util.Point{} |
| 78 | +} |
| 79 | + |
| 80 | +func findMoveIdx(moves []util.Point, move util.Point) int { |
| 81 | + for i := range moves { |
| 82 | + if moves[i] == move { |
| 83 | + return i |
| 84 | + } |
| 85 | + } |
| 86 | + return -1 |
| 87 | +} |
| 88 | + |
| 89 | +type cheat struct { |
| 90 | + start util.Point |
| 91 | + end util.Point |
| 92 | +} |
| 93 | + |
| 94 | +func findCheats(course [][]byte, moves []util.Point, startIdx int, minSaving int, cheatedWalls map[util.Point]util.Void) []cheat { |
| 95 | + var s = moves[startIdx] |
| 96 | + var cheats []cheat |
| 97 | + for _, d := range directions { |
| 98 | + var w = s.Add(d) |
| 99 | + if !w.IsInBounds(len(course), len(course[w.Y])) { |
| 100 | + continue |
| 101 | + } |
| 102 | + if course[w.Y][w.X] != WALL { |
| 103 | + continue |
| 104 | + } |
| 105 | + var e = w.Add(d) |
| 106 | + var ei = findMoveIdx(moves, e) |
| 107 | + // HINT: save 100 picoseconds, but this includes two additional steps |
| 108 | + if ei-startIdx < minSaving+2 { |
| 109 | + // TODO: might there be multiple cheats forward? |
| 110 | + continue |
| 111 | + } |
| 112 | + cheatedWalls[w] = util.Void{} |
| 113 | + fmt.Printf("%d picoseconds saved by cheat: %d,%d -> %d,%d\n", ei-startIdx-2, s.Y, s.X, e.Y, e.X) |
| 114 | + cheats = append(cheats, cheat{start: s, end: e}) |
| 115 | + } |
| 116 | + return cheats |
| 117 | +} |
| 118 | + |
| 119 | +func printCourseWithCheats(course [][]byte, cheats map[util.Point]util.Void) { |
| 120 | + for y := range course { |
| 121 | + for x := range course[y] { |
| 122 | + if _, ok := cheats[util.Point{Y: y, X: x}]; ok { |
| 123 | + fmt.Print("C") |
| 124 | + } else { |
| 125 | + fmt.Print(string(course[y][x])) |
| 126 | + } |
| 127 | + } |
| 128 | + fmt.Println() |
| 129 | + } |
| 130 | + fmt.Println() |
| 131 | +} |
| 132 | + |
| 133 | +func main() { |
| 134 | + var minSaving = flag.Int("minSaving", 100, "consider cheats that save at least minSaving picoseconds") |
| 135 | + flag.Parse() |
| 136 | + |
| 137 | + var course = readCourse() |
| 138 | + var moves = findMoves(course) |
| 139 | + var time = len(moves) - 1 |
| 140 | + fmt.Printf("course takes %d picoseconds\n", time) |
| 141 | + var cheats = make(map[cheat]util.Void) |
| 142 | + var cheatedWalls = make(map[util.Point]util.Void) |
| 143 | + for i := range moves { |
| 144 | + for _, c := range findCheats(course, moves, i, *minSaving, cheatedWalls) { |
| 145 | + cheats[c] = util.Void{} |
| 146 | + } |
| 147 | + } |
| 148 | + printCourseWithCheats(course, cheatedWalls) |
| 149 | + fmt.Printf("found %d unique cheats saving >= %d picoseconds", len(cheats), *minSaving) |
| 150 | +} |
0 commit comments