import matrix import shared from dataclasses import dataclass, field from functools import cached_property from typing import Tuple, List 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): return [ (y+self.y,x+self.x) for y,x in self.shape[1]] def find_highest(shapes, default_height): if not shapes: return default_height #return min([y for s in shapes for y,_ in s.coords]) return min([s.coords[0][0] for s in shapes[-20:]]) #all_y = [] #for s in shapes[-20:]: # all_y.append(s.coords[0][0]) #return min(all_y) 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): 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: # Add last rock's coords to all coords if shapes: for c in shapes[-1].coords: all_coords.add(c) 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 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] # check if hit bottom next_offset = (1, next_offset[1]) if out_of_bounds(height, width, shapes, (next_offset[0],0)): # 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 #render(width, height, shapes) 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): print("NAH BRO") 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()