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), ('□',SQUARE)) OPS = {'>':operator.add, '<':operator.sub} @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 '#' @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 def collision_at(shapes, row, col, shape): if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") # Take in all existing coordinates for y,x in shape.coords: if (y+row,x+col) in all_coords(shapes[:-1]): breakpoint() return True return False def out_of_bounds(row, col, height, width, shape=None): if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") for y,x in shape: #print(f"\t{row}+{y} > {height}\t", f"{col}+{x} > {width}\t", f"{col}+{x} < 0") if row+y > height-1: return True if col+x >= width: return True if col+x < 0: return True return False # @shared.profile def part1(rows): print(rows) instructions = [r for r in rows] height = 8 #height = 2022*4+4 width = 7 view = height - 20 rock = 0 shapes = [] spawning = True while rock < 2: if spawning: 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("Jet of gas pushes rock", next_move) # TODO: make COLLISION_AT TAKE AN OFFSET next_x = OPS[next_move](shapes[-1].x, 1) # Try Move left or right if not out_of_bounds(shapes[-1].y, next_x, height, width, shapes[-1].offsets) and not collision_at(shapes, shapes[-1].y, next_x, shape): shapes[-1].x = next_x else: print("but nothing happens") # check if hit bottom next_y = shapes[-1].y + 1 if out_of_bounds(next_y, next_x, height, width, shapes[-1].offsets): print("rock comes to rest") # hit bottom dont move down shapes[-1].moving = False spawning = True rock += 1 if collision_at(shapes, next_y, next_x, shape): # 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") print("-"*15) 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()