diff options
Diffstat (limited to '2024/aoc2024-d15.py')
-rw-r--r-- | 2024/aoc2024-d15.py | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/2024/aoc2024-d15.py b/2024/aoc2024-d15.py new file mode 100644 index 0000000..a6bb5a0 --- /dev/null +++ b/2024/aoc2024-d15.py @@ -0,0 +1,157 @@ +#advent of code 2024 +#day 15 +#could probably reduce the length of code even more +#if I were to index the part2 box +#instead of hardcoding the left and right part of the box +#but w/e, I don't feel like doing it +#the code is ~110 lines of code shorter after cleaning up +#explanation: +#given: picture of the grid (fg) and robots movement list (movement) +#create 2 grids: grid1 and grid2 for part 1 and part 2 respectively +#for part 2 the grid is made wider x2: the grid2 is created with +#resizing dictionary to know what to insert where +#there are 3 functions: GPS, movebox, stackbox +#GPS calculates the result required by puzzle after simulating +#the movement of the robot +#the box variable is a list of characters that are considered a box +#for each part of a puzzle: O for part 1, [ and ] for part 2 +#the thing is that by making it into a list, I don't need +#separate functions for each part: the logic checks if +#the current tile is equal to the last or the first element of this list +#to determine whether or not it's a box +#for list of length 1, element with index 0 and -1 are the same +#next it iterates over each step of the robot's movement in movement list +#before the robot moves, it checks what is in it's way +#if it's a floor ("."), then it takes a step +#if it's a wall ("#"), it stands in the same spot +#if it's a box, it runs the movebox or stackbox function +#to determine if it's viable or not +#stackbox is a function that is run only for part2 if the robot +#wants to push boxes up or down. if it's part 1 or it moves left or right +#it uses movebox function +#movebox function scans the grid in the provided direction +#(accordingly with robots desired movement) as long as it meets +#a wall tile or floor tile. if it's a wall: can't move +#if it's a floor, then for each tile in the path, from last tile to the first +#move each tile in given direction +#stackbox function queues each tile with a box +#and checks if something is above or below (depending on the robots direction) +#if there's another box, it queues up that tile and it's counterpart +#(left or right) to check it again recursively +#if at any moment there's a wall in the path, the robot can't move +#if the final tiles don't have only floor tiles above or below them, +#then each box tile is moved one step in correct direction, +#leaving behind a free space +#this comment is as long as solutions for days 1-13 +#but I just fucking know that I'll look this up one day and will +#not remember anything +grid1 = {}; +grid2 = {}; +dirs = {}; +dirs["^"] = (0,-1); +dirs["v"] = (0,1); +dirs["<"] = (-1,0); +dirs[">"] = (1,0); +resizing = {}; +resizing["#"] = "##"; +resizing["O"] = "[]"; +resizing["."] = ".."; +resizing["@"] = "@."; + +f = open("15.in","r"); +fg, movement = f.read().split("\n\n"); +movement = movement.replace("\n",""); +for y,l in enumerate(fg.split("\n")): + for x,c in enumerate(l.strip()): + if c == "@": + robot1 = (x,y); + robot2 = (2*x,y); + c = "."; + grid1[(x,y)] = c; + for offset in range(2): + grid2[(2*x+offset,y)] = resizing[c][offset]; +f.close(); + +def movebox(BoxPos,direction,grid,box,p2): + valid = True; + bx, by = BoxPos; #coordinates of 1st box to push + dirx,diry = direction; + distance = 1; #python ranges are exclusive on outside + NextTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + while NextTile == box[0] or NextTile == box[-1]: + distance += 1; + NextTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + LastTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + if LastTile == "#": #if wall, can't move + valid = False; + else: #replace each box in the way step by step in reversed order + for dist in range(distance): + NewPosition = (bx+distance*dirx -dist*dirx,by+distance*diry -dist*diry); + PrevPosition = (bx+distance*dirx -dist*dirx -dirx,by + distance*diry - dist*diry - diry); + grid[NewPosition] = grid[PrevPosition]; + grid[PrevPosition] = "."; + return valid; + +def stackbox(BoxPos,direction,grid,box,p2): + valid = True; + bx,by = BoxPos; + dirx,diry = direction; + if grid[BoxPos] == box[0]: BoxPos2 = (bx+1,by,box[-1]); + else: BoxPos2 = (bx-1,by,box[0]); + queue = [(bx,by,grid[BoxPos]),BoxPos2]; #each part of the box is a separate entry in queue + BoxesToMove = queue.copy(); + while queue: + PosToCheck = []; + for queuedpos in queue: + BX,BY,dummy = queuedpos; #dummy not needed for queue loop, needed later + NextTile = grid[(BX,BY+diry)]; + if NextTile == box[0]: + PosToCheck.append((BX,BY+diry,NextTile)); + PosToCheck.append((BX+1,BY+diry,grid[(BX+1,BY+diry)])); + elif NextTile == box[-1]: + PosToCheck.append((BX,BY+diry,NextTile)); + PosToCheck.append((BX-1,BY+diry,grid[(BX-1,BY+diry)])); + elif NextTile == "#": + valid = False; + break; + if valid: + queue = PosToCheck.copy(); + BoxesToMove.extend(PosToCheck); + else: + BoxesToMove = []; + queue = []; + BoxesToMove = sorted(BoxesToMove, key = lambda k: k[1],reverse=diry>0); + for BoxToMove in BoxesToMove: + boxx,boxy,prevtile = BoxToMove; + grid[(boxx,boxy+diry)] = prevtile; + grid[(boxx,boxy)] = "."; + return valid; + +def GPS(grid,robot,p2): + box = ["O"]; + if p2: box = ["[","]"]; + for d in movement: + dx,dy = dirs[d]; + rx,ry = robot; + NextPos = (rx+dx,ry+dy); + tile = grid.get(NextPos,"#"); + if tile == "#": #if wall, dont move + ok = False; + elif tile == box[0] or tile == box[-1]: #if boxes in the way, move boxes first + if p2 and dy != 0: + ok = stackbox(NextPos,dirs[d],grid,box,p2); + else: + ok = movebox(NextPos,dirs[d],grid,box,p2); + else: #if nothing in the way, then move + ok = True; + robot = (rx+ok*dx,ry+ok*dy); + score = 0; + for g in grid: + if grid[g] == box[0]: score += 100*g[1] + g[0]; + return score; + +part1 = GPS(grid1,robot1,False); +part2 = GPS(grid2,robot2,True); +print("part 1 =", part1); +print("part 2 =", part2); + |