import matrix import shared from dataclasses import dataclass from typing import Tuple import operator # #### FLAT = ((0,0),(0,1),(0,2),(0,3)) # .#. # ### # .#. CROSS = ( (-2,1), (-1,0),(-1,1),(-1,2), (0,1)) # ..# # ..# # ### J = ( (-2,2), (-1,2), (0,0),(0,1),(0,2)) # # # # # # # # I = ((0,0),(-1,0),(-2,0),(-3,0)) # ## # ## SQUARE = ( (-1,0),(-1,1), (0,0),(0,1), ) ORDER = (('-',FLAT), ('+',CROSS), ('j',J), ('I',I), ('o',SQUARE)) OPS = {'>':operator.add, '<':operator.sub} OFF = {'>': (0,1), '<':(0,-1)} @dataclass class Shape: rock:int y:int x:int shape: Tuple[str,Tuple[Tuple[int,int]]] # ('j', ((0,0),....)) moving: bool = True @property def char(self): if self.moving: return '@' return self.at_rest_char @property def at_rest_char(self): return ORDER[self.rock%5][0] @property def str(self): return self.shape[0] @property def offsets(self): return self.shape[1] @property def coords(self): actual_coords = [] for y,x in self.shape[1]: actual_coords.append((y+self.y,x+self.x)) return actual_coords # TODO: check left/right movement into an object def find_highest(shapes, default_height): if not shapes: return default_height all_y = [] for s in shapes: for y,_ in s.coords: all_y.append(y) return min(all_y) def all_coords(shapes): coords = set() for s in shapes: for c in s.coords: coords.add(c) return coords all_coords = set() def collision_at(shapes, offset): if len(shapes) == 1: # Nothing to compare return False shape = shapes[-1] # Take in all existing coordinates for y,x in shape.coords: if (y+offset[0],x+offset[1]) in all_coords: return True return False def out_of_bounds(height, width, shapes, offset): shape = shapes[-1] if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") for y,x in shape.coords: #print(f"\t{row}+{y} > {height}\t", f"{col}+{x} > {width}\t", f"{col}+{x} < 0") if y+offset[0] >= height: return True if x+offset[1] >= width: return True if x+offset[1] < 0: return True return False # @shared.profile def part1(rows): print(rows) instructions = [r for r in rows] height = 20 height = 2022*4+4 width = 7 view = height - 20 rock = 0 shapes = [] spawning = True while rock < 2022: if spawning: if shapes: for c in shapes[-1].coords: all_coords.add(c) if rock % 1000 == 0: print("Spawn rock #", rock) X = 2 Y = find_highest(shapes, height) - 4 shape = Shape(rock=rock, y=Y, x=X, shape=ORDER[rock%len(ORDER)]) shapes.append(shape) spawning = False #render(width, height, shapes) #print("~"*20) # loop through instructions try: next_move = instructions.pop(0) except IndexError: instructions = [r for r in rows] next_move = instructions.pop(0) # Try to move right/left #print(f"Jet of gas pushes {shapes[-1].at_rest_char} rock", next_move) next_offset = OFF[next_move] if not out_of_bounds(height,width, shapes, (0, next_offset[1])) and not collision_at(shapes, next_offset): shapes[-1].x += next_offset[1] else: pass #print("but nothing happens") # check if hit bottom next_offset = (1, next_offset[1]) if out_of_bounds(height, width, shapes, (next_offset[0],0)): #print("rock comes to rest") # hit bottom dont move down shapes[-1].moving = False spawning = True rock += 1 continue if collision_at(shapes, (next_offset[0], 0)): # Hit another Block dont move down shapes[-1].moving = False spawning = True rock += 1 else: # can move down shapes[-1].y += 1 #print("rock falls one unit") #render(width, height, shapes) print(shapes[0], height, shapes[0].coords) print("LAST ROCK COUNT", rock+1) print(height - find_highest(shapes, height)) def render(width, height, shapes): print("-"*15) mx = matrix.matrix_of_size(width, height) for s in shapes: matrix.draw_shape_at(mx, s.y, s.x, s.offsets, s.char) print(matrix.ppmx(mx,pad=False,space=False)) # @shared.profile def part2(rows): pass def main(): rows = [row for row in shared.load_rows(17)][0] with shared.elapsed_timer() as elapsed: part1(rows) print("🕒", elapsed()) with shared.elapsed_timer() as elapsed: part2(rows) print("🕒", elapsed()) if __name__ == "__main__": main()