summaryrefslogtreecommitdiff
path: root/2024/aoc2024-d15.py
blob: a6bb5a0f11ba2447ffcaf1b920de565fafd7c614 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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);