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  }