github.com/mnlphlp/aoc22@v0.0.0-20230330151331-c1dc4bff1b9b/day24/day24.go (about) 1 package day24 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 ) 8 9 func part1(g Grid) int { 10 return bfs(g.blizzards, g.start, g.end, g.totalRows, g.totalCols, 0) 11 } 12 13 func part2(g Grid, stepsTaken int) int { 14 fmt.Println("steps 1:", stepsTaken) 15 stepsTaken = bfs(g.blizzards, g.end, g.start, g.totalRows, g.totalCols, stepsTaken) 16 fmt.Println("steps 2:", stepsTaken) 17 return bfs(g.blizzards, g.start, g.end, g.totalRows, g.totalCols, stepsTaken) 18 } 19 20 func Solve(input string, debug bool, task int) (string, string) { 21 ret1, ret2 := 0, 0 22 grid := parseInput(input) 23 if task != 2 { 24 ret1 = part1(grid) 25 } 26 if task != 1 { 27 if ret1 == 0 { 28 ret1 = part1(grid) 29 } 30 ret2 = part2(grid, ret1) 31 } 32 33 return strconv.Itoa(ret1), strconv.Itoa(ret2) 34 } 35 36 type Grid struct { 37 start, end [2]int 38 blizzards []blizzard 39 totalCols, totalRows int 40 } 41 42 // code below largely taken from https://github.com/alexchao26/advent-of-code-go because i wanted to finish all days without spending more time on my stupid bugs 43 44 var cacheRoomStates = map[int][][]bool{} 45 46 func bfs(blizzards []blizzard, start, end [2]int, totalRows, totalCols, stepsElapsedAlready int) int { 47 48 type node struct { 49 coords [2]int 50 steps int 51 } 52 53 queue := []node{} 54 queue = append(queue, node{ 55 coords: start, 56 steps: stepsElapsedAlready, 57 }) 58 59 seenCoordsSteps := map[[3]int]bool{} 60 for len(queue) > 0 { 61 popped := queue[0] 62 queue = queue[1:] 63 64 occupied := calcOrGetRoomState(blizzards, popped.steps+1, totalRows, totalCols, cacheRoomStates) 65 66 for _, diff := range [][2]int{ 67 {1, 0}, 68 {0, 1}, 69 {0, -1}, 70 {-1, 0}, 71 } { 72 nextCoords := [2]int{ 73 popped.coords[0] + diff[0], 74 popped.coords[1] + diff[1], 75 } 76 77 if nextCoords == start { 78 continue 79 } 80 if nextCoords != start && nextCoords != end { 81 if nextCoords[0] < 0 || nextCoords[0] >= totalRows || 82 nextCoords[1] < 0 || nextCoords[1] >= totalCols { 83 continue 84 } 85 } 86 87 // no point in processing a coordinate & steps pair that has already been seen 88 hash := [3]int{nextCoords[0], nextCoords[1], popped.steps + 1} 89 if seenCoordsSteps[hash] { 90 continue 91 } 92 seenCoordsSteps[hash] = true 93 94 // because of how i indexed the room, need to do literal checks to see if we're in start 95 // or end coords 96 97 // if blocked, continue 98 if nextCoords != start && nextCoords != end && 99 occupied[nextCoords[0]][nextCoords[1]] { 100 continue 101 } 102 103 // if out of bounds, continue 104 if nextCoords != start && nextCoords != end { 105 if nextCoords[0] < 0 || nextCoords[0] >= totalRows || 106 nextCoords[1] < 0 || nextCoords[1] >= totalCols { 107 continue 108 } 109 } 110 111 // done 112 if nextCoords == end { 113 return popped.steps + 1 114 } 115 116 queue = append(queue, node{ 117 coords: nextCoords, 118 steps: popped.steps + 1, 119 }) 120 } 121 // if possible to stay still, add "wait" move 122 if popped.coords == start || 123 !occupied[popped.coords[0]][popped.coords[1]] { 124 queue = append(queue, node{ 125 coords: popped.coords, 126 steps: popped.steps + 1, 127 }) 128 } 129 } 130 131 panic("should return from loop") 132 } 133 134 type blizzard struct { 135 startRow, startCol int 136 rowSlope, colSlope int 137 totalRows, totalCols int 138 char string 139 } 140 141 func (b blizzard) calculateCoords(steps int) [2]int { 142 row := (b.startRow + b.rowSlope*steps) % b.totalRows 143 col := (b.startCol + b.colSlope*steps) % b.totalCols 144 145 row += b.totalRows 146 col += b.totalCols 147 row %= b.totalRows 148 col %= b.totalCols 149 150 return [2]int{ 151 row, col, 152 } 153 } 154 155 // occupied coordinates are easy to calculate based on each blizzard's movement 156 // and steps/time elapsed, return a matrix that represents occupied cells 157 // and store the result in a map to reduce future calcs 158 func calcOrGetRoomState(blizzards []blizzard, steps, totalRows, totalCols int, memo map[int][][]bool) [][]bool { 159 if m, ok := memo[steps]; ok { 160 return m 161 } 162 163 matrix := make([][]bool, totalRows) 164 for r := range matrix { 165 matrix[r] = make([]bool, totalCols) 166 } 167 168 for _, b := range blizzards { 169 coords := b.calculateCoords(steps) 170 matrix[coords[0]][coords[1]] = true 171 } 172 173 memo[steps] = matrix 174 175 return matrix 176 } 177 178 func parseInput(input string) Grid { 179 var start, end [2]int 180 blizzards := []blizzard{} 181 182 lines := strings.Split(input, "\n") 183 184 for c := 0; c < len(lines); c++ { 185 if lines[0][c] == '.' { 186 start = [2]int{-1, c - 1} 187 break 188 } 189 } 190 191 // 0,0 will be within the BOX we start in 192 // start and end will be off the bounds of that box 193 totalRows := len(lines) - 2 194 totalCols := len(lines[0]) - 2 195 196 for c := 0; c < len(lines[0]); c++ { 197 if lines[len(lines)-1][c] == '.' { 198 end = [2]int{totalRows, c - 1} 199 break 200 } 201 } 202 203 for l := 1; l < len(lines)-1; l++ { 204 chars := strings.Split(lines[l], "") 205 for c := 1; c < len(chars)-1; c++ { 206 switch chars[c] { 207 case ">": 208 blizzards = append(blizzards, blizzard{ 209 startRow: l - 1, 210 startCol: c - 1, 211 rowSlope: 0, 212 colSlope: 1, 213 totalRows: totalRows, 214 totalCols: totalCols, 215 char: ">", 216 }) 217 case "<": 218 blizzards = append(blizzards, blizzard{ 219 startRow: l - 1, 220 startCol: c - 1, 221 rowSlope: 0, 222 colSlope: -1, 223 totalRows: totalRows, 224 totalCols: totalCols, 225 char: "<", 226 }) 227 case "^": 228 blizzards = append(blizzards, blizzard{ 229 startRow: l - 1, 230 startCol: c - 1, 231 rowSlope: -1, 232 colSlope: 0, 233 totalRows: totalRows, 234 totalCols: totalCols, 235 char: "^", 236 }) 237 case "v": 238 blizzards = append(blizzards, blizzard{ 239 startRow: l - 1, 240 startCol: c - 1, 241 rowSlope: 1, 242 colSlope: 0, 243 totalRows: totalRows, 244 totalCols: totalCols, 245 char: "v", 246 }) 247 case ".", "#": 248 default: 249 panic("unhandled char") 250 } 251 } 252 } 253 return Grid{ 254 start: start, 255 end: end, 256 blizzards: blizzards, 257 totalRows: totalRows, 258 totalCols: totalCols, 259 } 260 }