diff --git a/2024/15/15.md b/2024/15/15.md new file mode 100644 index 0000000..1c57b91 --- /dev/null +++ b/2024/15/15.md @@ -0,0 +1,478 @@ +## \-\-- Day 15: Warehouse Woes \-\-- + +You appear back inside your own mini submarine! Each Historian drives +their mini submarine in a different direction; maybe the Chief has his +own submarine down here somewhere as well? + +You look up to see a vast school of [lanternfish](/2021/day/6) swimming +past you. On closer inspection, they seem quite anxious, so you drive +your mini submarine over to see if you can help. + +Because lanternfish populations grow rapidly, they need a lot of food, +and that food needs to be stored somewhere. That\'s why these +lanternfish have built elaborate warehouse complexes operated by robots! + +These lanternfish seem so anxious because they have lost control of the +robot that operates one of their most important warehouses! It is +currently running +amok, +pushing around boxes in the warehouse with no regard for lanternfish +logistics *or* lanternfish inventory management strategies. + +Right now, none of the lanternfish are brave enough to swim up to an +unpredictable robot so they could shut it off. However, if you could +anticipate the robot\'s movements, maybe they could find a safe option. + +The lanternfish already have a map of the warehouse and a list of +movements the robot will *attempt* to make (your puzzle input). The +problem is that the movements will sometimes fail as boxes are shifted +around, making the actual movements of the robot difficult to predict. + +For example: + + ########## + #..O..O.O# + #......O.# + #.OO..O.O# + #..O@..O.# + #O#..O...# + #O..O..O.# + #.OO.O.OO# + #....O...# + ########## + + ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ + vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< + <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ + ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< + ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ + <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> + ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< + v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ + +As the robot (`@`) attempts to move, if there are any boxes (`O`) in the +way, the robot will also attempt to push those boxes. However, if this +action would cause the robot or a box to move into a wall (`#`), nothing +moves instead, including the robot. The initial positions of these are +shown on the map at the top of the document the lanternfish gave you. + +The rest of the document describes the *moves* (`^` for up, `v` for +down, `<` for left, `>` for right) that the robot will attempt to make, +in order. (The moves form a single giant sequence; they are broken into +multiple lines just to make copy-pasting easier. Newlines within the +move sequence should be ignored.) + +Here is a smaller example to get started: + + ######## + #..O.O.# + ##@.O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + <^^>>>vv>v<< + +Were the robot to attempt the given sequence of moves, it would push +around the boxes as follows: + + Initial state: + ######## + #..O.O.# + ##@.O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move <: + ######## + #..O.O.# + ##@.O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move ^: + ######## + #.@O.O.# + ##..O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move ^: + ######## + #.@O.O.# + ##..O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move >: + ######## + #..@OO.# + ##..O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move >: + ######## + #...@OO# + ##..O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move >: + ######## + #...@OO# + ##..O..# + #...O..# + #.#.O..# + #...O..# + #......# + ######## + + Move v: + ######## + #....OO# + ##..@..# + #...O..# + #.#.O..# + #...O..# + #...O..# + ######## + + Move v: + ######## + #....OO# + ##..@..# + #...O..# + #.#.O..# + #...O..# + #...O..# + ######## + + Move <: + ######## + #....OO# + ##.@...# + #...O..# + #.#.O..# + #...O..# + #...O..# + ######## + + Move v: + ######## + #....OO# + ##.....# + #..@O..# + #.#.O..# + #...O..# + #...O..# + ######## + + Move >: + ######## + #....OO# + ##.....# + #...@O.# + #.#.O..# + #...O..# + #...O..# + ######## + + Move >: + ######## + #....OO# + ##.....# + #....@O# + #.#.O..# + #...O..# + #...O..# + ######## + + Move v: + ######## + #....OO# + ##.....# + #.....O# + #.#.O@.# + #...O..# + #...O..# + ######## + + Move <: + ######## + #....OO# + ##.....# + #.....O# + #.#O@..# + #...O..# + #...O..# + ######## + + Move <: + ######## + #....OO# + ##.....# + #.....O# + #.#O@..# + #...O..# + #...O..# + ######## + +The larger example has many more moves; after the robot has finished +those moves, the warehouse would look like this: + + ########## + #.O.O.OOO# + #........# + #OO......# + #OO@.....# + #O#.....O# + #O.....OO# + #O.....OO# + #OO....OO# + ########## + +The lanternfish use their own custom Goods Positioning System (GPS for +short) to track the locations of the boxes. The *GPS coordinate* of a +box is equal to 100 times its distance from the top edge of the map plus +its distance from the left edge of the map. (This process does not stop +at wall tiles; measure all the way to the edges of the map.) + +So, the box shown below has a distance of `1` from the top edge of the +map and `4` from the left edge of the map, resulting in a GPS coordinate +of `100 * 1 + 4 = 104`. + + ####### + #...O.. + #...... + +The lanternfish would like to know the *sum of all boxes\' GPS +coordinates* after the robot finishes moving. In the larger example, the +sum of all boxes\' GPS coordinates is `10092`. In the smaller example, +the sum is `2028`. + +Predict the motion of the robot and boxes in the warehouse. After the +robot is finished moving, *what is the sum of all boxes\' GPS +coordinates?* + +Your puzzle answer was `1568399`. + +The first half of this puzzle is complete! It provides one gold star: \* + +## \-\-- Part Two \-\-- {#part2} + +The lanternfish use your information to find a safe moment to swim in +and turn off the malfunctioning robot! Just as they start preparing a +festival in your honor, reports start coming in that a *second* +warehouse\'s robot is *also* malfunctioning. + +This warehouse\'s layout is surprisingly similar to the one you just +helped. There is one key difference: everything except the robot is +*twice as wide*! The robot\'s list of movements doesn\'t change. + +To get the wider warehouse\'s map, start with your original map and, for +each tile, make the following changes: + +- If the tile is `#`, the new map contains `##` instead. +- If the tile is `O`, the new map contains `[]` instead. +- If the tile is `.`, the new map contains `..` instead. +- If the tile is `@`, the new map contains `@.` instead. + +This will produce a new warehouse map which is twice as wide and with +wide boxes that are represented by `[]`. (The robot does not change +size.) + +The larger example from before would now look like this: + + #################### + ##....[]....[]..[]## + ##............[]..## + ##..[][]....[]..[]## + ##....[]@.....[]..## + ##[]##....[]......## + ##[]....[]....[]..## + ##..[][]..[]..[][]## + ##........[]......## + #################### + +Because boxes are now twice as wide but the robot is still the same size +and speed, boxes can be aligned such that they directly push two other +boxes at once. For example, consider this situation: + + ####### + #...#.# + #.....# + #..OO@# + #..O..# + #.....# + ####### + + 1: + print(prepend+sign[0] + grid[idx][jdx] + sign[1], end='') # Print with sign + else: + print(prepend+colored(sign,'green',attrs=["underline","bold"]), end=' ') # Print sign + else: + print(prepend+colored(grid[idx][jdx],'green',attrs=["underline","bold"]), end=' ') + else: + if positions is not None: + if (idx,jdx) in positions: + print(prepend+colored(grid[idx][jdx],'red'),end=' ') + else: + + print(prepend+grid[idx][jdx], end=' ') + else: + if grid[idx][jdx] == '.': + print(prepend+colored(grid[idx][jdx],'blue',attrs=["concealed"]),end=' ') + elif grid[idx][jdx] == 'O': + print(prepend+colored(grid[idx][jdx],'red'),end=' ') + elif grid[idx][jdx] == '#': + print(prepend+colored(grid[idx][jdx],'white'),end=' ') + else: + print(prepend+grid[idx][jdx], end=' ') # Regular grid element + print() + + + for r in range(len(grid[0])): + if r == 0: + print(' ',end='') + print(r,end=' ') + print() + + +def loadFile(input_f): + grid = [] + instructions = [] + with open(input_f) as file: + for line in file: + # load map part + if line.startswith("#"): + grid.append(list(line.rstrip())) + + # load instructions + elif line.startswith(('v','<','>','^')): + instructions += list(line.rstrip()) + return grid,instructions + +######################################### +# # +# Part 1 # +# # +######################################### +def part1(): + grid, instructions = loadFile(input_f) + + start = () + + for r,row in enumerate(grid): + for c, col in enumerate(row): + if grid[r][c] == '@': + start = (r,c) + + # translate arrows to something get_value_in_direction can use + directions = { + '^':('up',(-1, 0)), + 'v':('down',(1, 0)), + '>':('right',(0, 1)), + '<':('left',(0, -1)), + } + + pos = start + #print('Initial state') + #nprint(grid,pos) + #print() + #input() + for idx, inst in enumerate(instructions): + #print('Move',inst,'(',len(instructions)-idx,')') + dir = directions[inst][0] + next = get_value_in_direction(grid,pos,dir) + + # If wall, don't do anything + if next == '#': + #nprint(grid,pos) + #input() + continue + + # If free space, move there + if next == '.': + grid[pos[0]][pos[1]] = '.' + pos = addTuples(pos,directions[inst][1]) + grid[pos[0]][pos[1]] = '@' + + # If box, move the box and the stack of boxes. + if next == 'O': + #print('@',pos) + prev = pos + next_chars = ['@'] + next_poss = [pos] + skip = False + while True: + nextPos = addTuples(pos,directions[inst][1]) + nextChar = get_value_in_direction(grid,nextPos) + #print(nextPos,nextChar) + if nextChar == 'O': + next_chars.append(nextChar) + next_poss.append(nextPos) + pos = nextPos + if nextChar == '.': + next_chars.append(nextChar) + next_poss.append(nextPos) + break + if nextChar == '#': + skip = True + pos = prev + break + + #input() + if not skip: + for ndx,n in enumerate(next_poss): + if ndx == 0: + grid[n[0]][n[1]] = '.' + pos = next_poss[ndx+1] + else: + grid[n[0]][n[1]] = next_chars[ndx-1] + #input() + #nprint(grid,pos) + #print(len(instructions)-idx) + #time.sleep(0.05) + #input() + result = 0 + for r,row in enumerate(grid): + for c,char in enumerate(row): + if char == 'O': + result += (100*r+c) + return result +start_time = time.time() +print('Part 1:',part1(), '\t\t', round((time.time() - start_time)*1000), 'ms') + + +######################################### +# # +# Part 2 # +# # +######################################### + +def resizeGrid(grid:list) -> list: + newGrid = [] + for r,row in enumerate(grid): + newRow = [] + for c, char in enumerate(row): + if char == '#': + newRow.append('#') + newRow.append('#') + if char == 'O': + newRow.append('[') + newRow.append(']') + if char == '.': + newRow.append('.') + newRow.append('.') + if char == '@': + newRow.append('@') + newRow.append('.') + newGrid.append(newRow) + return newGrid + +def part2(): + + + def nprint2(grid, cur: set = None, sign: str = None, positions:list = None): + + + prepend = 0 + for idx, i in enumerate(grid): + for jdx, j in enumerate(i): + if jdx == 0: + prepend = str(idx)+' ' + else: + prepend = '' + + if (idx, jdx) == cur: + if sign is not None: + if len(sign) > 1: + print(prepend+sign[0] + grid[idx][jdx] + sign[1], end=' ') # Print with sign + else: + print(prepend+colored(sign,'green',attrs=["underline","bold"]), end=' ') # Print sign + else: + print(prepend+colored(grid[idx][jdx],'green',attrs=["underline","bold"]), end='') + else: + if positions is not None: + if (idx,jdx) in positions: + print(prepend+colored(grid[idx][jdx],'red'),end=' ') + else: + + print(prepend+grid[idx][jdx], end=' ') + else: + if grid[idx][jdx] == '.': + print(prepend+colored(grid[idx][jdx],'blue',attrs=["concealed"]),end='') + elif grid[idx][jdx] == 'O': + print(prepend+colored(grid[idx][jdx],'red'),end='') + elif grid[idx][jdx] == '#': + print(prepend+colored(grid[idx][jdx],'white'),end='') + else: + print(prepend+grid[idx][jdx], end='') # Regular grid element + print() + + + for r in range(len(grid[0])): + if r == 0: + print(' 0',end='') + + else: + if r%2 == 0: + print(r,end='') + else: + print(' ',end='') + print() + for r in range(len(grid[0])): + if r == 0: + print(' ',end='') + + else: + if r%2 != 0: + print(r,end='') + else: + if r != 9: + print(' ',end='') + else: + print('',end='') + print() + + + grid, instructions = loadFile(input_f) + + start = () + + for r,row in enumerate(grid): + for c, col in enumerate(row): + if grid[r][c] == '@': + start = (r,c) + + # translate arrows to something get_value_in_direction can use + directions = { + '^':('up',(-1, 0)), + 'v':('down',(1, 0)), + '>':('right',(0, 1)), + '<':('left',(0, -1)), + } + + pos = start + + nprint(grid) + #print(grid) + grid = resizeGrid(grid) + print() + nprint2(grid) + #print(grid) + + print() + + for idx, inst in enumerate(instructions): + #print('Move',inst,'(',len(instructions)-idx,')') + dir = directions[inst][0] + next = get_value_in_direction(grid,pos,dir) + + # If wall, don't do anything + if next == '#': + #nprint(grid,pos) + #input() + continue + + # If free space, move there + if next == '.': + grid[pos[0]][pos[1]] = '.' + pos = addTuples(pos,directions[inst][1]) + grid[pos[0]][pos[1]] = '@' + + # If box, move the box and the stack of boxes. + if next == 'O': + #print('@',pos) + prev = pos + next_chars = ['@'] + next_poss = [pos] + skip = False + while True: + nextPos = addTuples(pos,directions[inst][1]) + nextChar = get_value_in_direction(grid,nextPos) + #print(nextPos,nextChar) + if nextChar == 'O': + next_chars.append(nextChar) + next_poss.append(nextPos) + pos = nextPos + if nextChar == '.': + next_chars.append(nextChar) + next_poss.append(nextPos) + break + if nextChar == '#': + skip = True + pos = prev + break + + #input() + if not skip: + for ndx,n in enumerate(next_poss): + if ndx == 0: + grid[n[0]][n[1]] = '.' + pos = next_poss[ndx+1] + else: + grid[n[0]][n[1]] = next_chars[ndx-1] + +start_time = time.time() +print('Part 2:',part2(), '\t\t', round((time.time() - start_time)*1000), 'ms') \ No newline at end of file diff --git a/__pycache__/fred.cpython-311.pyc b/__pycache__/fred.cpython-311.pyc index 5a6813b..88f68b7 100644 Binary files a/__pycache__/fred.cpython-311.pyc and b/__pycache__/fred.cpython-311.pyc differ diff --git a/fred.py b/fred.py index 76ff892..cd59ccf 100644 --- a/fred.py +++ b/fred.py @@ -267,22 +267,38 @@ def nprint(grid, cur: set = None, sign: str = None, positions:list = None): if positions is not None and not isinstance(positions, list): raise TypeError("Positions must be a list.") + prepend = 0 for idx, i in enumerate(grid): for jdx, j in enumerate(i): + if jdx == 0: + prepend = str(idx)+' ' + else: + prepend = '' + if (idx, jdx) == cur: - if len(sign) > 1: - print(sign[0] + grid[idx][jdx] + sign[1], end='') # Print with sign + if sign is not None: + if len(sign) > 1: + print(prepend+sign[0] + grid[idx][jdx] + sign[1], end='') # Print with sign + else: + print(prepend+colored(sign,'green',attrs=["underline"]), end=' ') # Print sign else: - print(colored(sign,'blue'), end=' ') # Print sign + print(prepend+colored(grid[idx][jdx],'green',attrs=["underline"]), end=' ') else: if positions is not None: if (idx,jdx) in positions: - print(colored(grid[idx][jdx],'red'),end=' ') + print(prepend+colored(grid[idx][jdx],'red'),end=' ') else: - print(grid[idx][jdx], end=' ') + print(prepend+grid[idx][jdx], end=' ') else: - print(grid[idx][jdx], end=' ') # Regular grid element + print(prepend+grid[idx][jdx], end=' ') # Regular grid element print() + + + for r in range(len(grid[0])): + if r == 0: + print(' ',end='') + print(r,end=' ') + print() def list2int(x): """