···11+import fileinput
22+import copy
33+from itertools import permutations
44+55+from utils import parse_nums
66+77+88+def supports(b1, b2):
99+ # Returns true if b1 supports b2.
1010+ ax, ay, az, bx, by, bz = b1
1111+ cx, cy, cz, dx, dy, dz = b2
1212+1313+ b1_top = max(az, bz)
1414+ b2_base = min(cz, dz)
1515+1616+ if b1_top + 1 != b2_base:
1717+ return False
1818+1919+ ax, bx = min(ax, bx), max(ax, bx)
2020+ ay, by = min(ay, by), max(ay, by)
2121+2222+ cx, dx = min(cx, dx), max(cx, dx)
2323+ cy, dy = min(cy, dy), max(cy, dy)
2424+2525+ # can't support because off on one axis
2626+ if bx < cx or ax > dx:
2727+ return False
2828+2929+ if by < cy or ay > dy:
3030+ return False
3131+3232+ return True
3333+3434+def simulate(bricks):
3535+ """Returns the set of brick IDs that moved in the simulation."""
3636+ all_touched = set()
3737+ stationary = set()
3838+ while True:
3939+ moved = False
4040+ for i, brick in enumerate(bricks):
4141+ if i in stationary:
4242+ continue
4343+4444+ # If the brick is on the ground, it can't fall more.
4545+ if brick[2] == 1 or brick[5] == 1:
4646+ stationary.add(i)
4747+ continue
4848+4949+ # See if brick is supported by any other b2
5050+ supported = False
5151+ for j, b2 in enumerate(bricks):
5252+ if i == j:
5353+ continue
5454+5555+ if supports(b2, brick):
5656+ # Can't fall because b2 supports brick,
5757+ # but it might fall in a later tick.
5858+ supported = True
5959+6060+ if j in stationary:
6161+ # Nah, it won't.
6262+ stationary.add(i)
6363+ break
6464+6565+ if not supported:
6666+ # Make the brick fall one tick.
6767+ brick[2] -= 1
6868+ brick[5] -= 1
6969+7070+ # Brick i has now moved in this simulation.
7171+ all_touched.add(i)
7272+ moved = True
7373+ break
7474+7575+ if not moved:
7676+ break
7777+7878+ return all_touched
7979+8080+8181+# Read problem input.
8282+BRICKS = []
8383+for line in fileinput.input():
8484+ BRICKS.append(list(parse_nums(line.strip())))
8585+8686+# Try to simulate bricks lower to the ground first.
8787+BRICKS.sort(key=lambda b: min(b[2], b[5]))
8888+simulate(BRICKS)
8989+9090+# graph[a] -> b means a supports b.
9191+graph = {i: set() for i in range(len(BRICKS))}
9292+for i, j in permutations(range(len(BRICKS)), 2):
9393+ if supports(BRICKS[i], BRICKS[j]):
9494+ graph[i].add(j)
9595+9696+part_1 = 0
9797+part_2 = 0
9898+9999+for k in graph:
100100+ # Nothing above this brick.
101101+ if not graph[k]:
102102+ part_1 += 1
103103+ continue
104104+105105+ # Simulate removing the brick.
106106+ without_brick = [copy.copy(BRICKS[i]) for i in range(len(BRICKS)) if i != k]
107107+ num_moved = len(simulate(without_brick))
108108+ if num_movedd == 0:
109109+ part_1 += 1
110110+ else:
111111+ part_2 += num_moved
112112+113113+print("Part 1:", part_1)
114114+print("Part 2:", part_2)