class Bullet(pygame.sprite.Sprite, Drawable, EventHandler): height = 5 width = 5 def __init__(self, x, y, direction: Directon = Directon.UP, velocity=5): super().__init__() self.velocity = velocity self.direction = direction self.rect = Rect(int(x - self.width / 2), y - self.height, self.width, self.height) self.image = Surface((self.width, self.height)) self.image.fill(WHITE) def handle(self, event: pygame.event.Event): pass def draw(self, surface: pygame.Surface): pygame.draw.rect(surface, WHITE, self.rect) def update(self, *args, **kwargs): # noinspection PyTypeChecker self.rect.move_ip(0, self.velocity * self.direction.value) if self.rect.y < 0 or self.rect.y > SCREEN_HEIGHT: self.kill()
class Select(Sprite): def __init__(self): Sprite.__init__(self) self.image = pygame.image.load('images/selector.png').convert() self.image.set_colorkey(PUCE, RLEACCEL) self.rect = Rect(10, 10, 30, 30) self.index = 0 def setX(self, x): newpos = (self.rect.topleft[0] + x) if (newpos > 0) and (newpos < COLUMNS * 40 + 10): self.rect.move_ip(x, 0) if x > 0: self.index += 1 else: self.index -= 1 def setY(self, y): newpos = (self.rect.topleft[1] + y) if (newpos > 0) and (newpos < ROWS * 40 + 10): self.rect.move_ip(0, y) if y > 0: self.index += COLUMNS else: self.index -= COLUMNS
class Star(pygame.sprite.Sprite, Drawable): height = 1 width = 1 move_step = 2 def __init__(self, direction: Directon = Directon.DOWN): super().__init__() self.direction = direction self.rect = Rect(random.randint(0, SCREEN_WITH), random.randint(0, SCREEN_HEIGHT), self.width, self.height) self.image = Surface((self.width, self.height)) self.image.fill(WHITE) def handle(self, event: pygame.event.Event): pass def draw(self, surface: pygame.Surface): pygame.draw.rect(surface, WHITE, self.rect) def update(self, *args, **kwargs): self.rect.move_ip(0, self.move_step) if self.rect.y < 0 or self.rect.y > SCREEN_HEIGHT: self.rect.y = 0 self.rect.x = random.randint(0, SCREEN_WITH)
def nearest_edge(self, x, y): """ The nearest lot boundary on which a fence might be erected """ lot = self.nearest_lot(x, y) if not lot: return None proximities = [(y - lot.top,"north"), (lot.bottom-y,"south"), (lot.right-x,"east"), (x-lot.left,"west")] proximities.sort() closest = proximities[0] dist,side = closest if dist > EDGE_TOLERANCE: return None horizontal = (side=="north" or side=="south") vertical = (side=="east" or side=="west") if horizontal: rect = Rect(lot.left, lot.top - FENCE_MARGIN_NORTH, lot.width, 2*FENCE_MARGIN_NORTH) if side == "south": rect.move_ip(0, LOT_DEPTH) if not WATER_RECT.colliderect(rect): return rect elif vertical: rect = Rect(lot.left - FENCE_MARGIN_WEST, lot.top, 2*FENCE_MARGIN_WEST, lot.height) if side == "east": rect.move_ip(LOT_WIDTH, 0) if not WATER_RECT.colliderect(rect): return rect
class Select(Sprite): def __init__(self): Sprite.__init__(self) self.image = pygame.image.load('images/selector.png').convert() self.image.set_colorkey(PUCE, RLEACCEL) self.rect=Rect(10, 10, 30, 30) self.index = 0 def setX(self, x): newpos = (self.rect.topleft[0] + x) if (newpos > 0) and (newpos < COLUMNS * 40 + 10): self.rect.move_ip(x,0) if x > 0: self.index += 1 else: self.index -= 1 def setY(self, y): newpos = (self.rect.topleft[1] + y) if (newpos > 0) and (newpos < ROWS * 40 + 10): self.rect.move_ip(0, y) if y > 0: self.index += COLUMNS else: self.index -= COLUMNS
def drawMap(self, surface_blit, tiles): for spr in tiles: rect = Rect(spr.rect.x - self.screenRect.x, spr.rect.y - self.screenRect.y, 0, 0) self.spritedict[spr] = surface_blit(spr.image, rect) surface_blit(self.fontRendererSmall.render(str(spr.f), False, (0, 0, 0)), rect) for action in spr.userActions: rect.move_ip(0, 10) surface_blit(self.fontRendererSmall.render(str(action), False, (0, 0, 0)), rect)
class Player(Animated): """ An instance is compatible with the level manager. Note that if :func:`rapidpg.levelmgr.collision.Level.update` is not used, a player instance doesn't have to be passed to the level manager """ def __init__(self, surfs, interval): """ :param surfs: A list of surfaces for animation :param interval: The interval between animation updates """ super(Player, self).__init__({"right": Animation(surfs, interval)}, lambda: "right", "right") self.jump_frames_left = 0 self.jumping = False self.in_air = False self.up_speed = 20 self.down_speed = 0 self.dir = 'right' self.surfs = surfs self.rect = Rect(0, 0, 0, 0) if surfs: self.rect = surfs[0].get_rect() self.animation_interval = interval self.speed = 7 def move(self, x, y): """ Alias for ``plr.rect.move_ip`` """ self.rect.move_ip(x, y) def start_jump(self): """ This method is used by the level manager to start the jump """ if not self.jumping: self.jumping = True self.in_air = True
class Gun(Sprite): def __init__(self, width, height, move_amount): super().__init__() self.width = width self.height = height self.move_amount = move_amount self.image = pygame.image.load(os.path.join(ASSETS_PATH, "sprites", "crosshair.png")).convert_alpha() self.image = pygame.transform.scale(self.image, (35, 35)) rect = self.image.get_rect() self.rect = Rect(rect.left + 5, rect.top + 5, rect.width - 10, rect.height - 10).move(width // 2, height // 2) def blit(self, surface): surface.blit(self.image, self.rect) def shoot(self, ducks): hit_ducks = pygame.sprite.spritecollide(self, ducks, dokill=False) return sum([duck.got_shot() for duck in hit_ducks if duck.alive]) def go_north(self): self.rect.move_ip(0, -self.move_amount) def go_northeast(self): self.rect.move_ip(self.move_amount, -self.move_amount) def go_east(self): self.rect.move_ip(self.move_amount, 0) def go_southeast(self): self.rect.move_ip(self.move_amount, self.move_amount) def go_south(self): self.rect.move_ip(0, self.move_amount) def go_southwest(self): self.rect.move_ip(-self.move_amount, self.move_amount) def go_west(self): self.rect.move_ip(-self.move_amount, 0) def go_northwest(self): self.rect.move_ip(-self.move_amount, -self.move_amount) def update(self): if self.rect.x >= self.width: self.rect.move_ip(-(self.rect.x - self.width + 1), 0) if self.rect.x < 0: self.rect.move_ip(-self.rect.x + 1, 0) if self.rect.y >= self.height: self.rect.move_ip(0, -(self.rect.y - self.height + 1)) if self.rect.y < 0: self.rect.move_ip(0, (-self.rect.y + 1))
class Hero(EventObserver): def __init__(self, x, y, rect_martix, container, evenent_handler): super().__init__(container, evenent_handler) self.rect_matrix = rect_martix self.map_point = (y, x) #cooridnates on map.txt self.x = x * 20 - 3 # coordinates used to paint object self.y = y * 20 - 3 self.speed = 2 self.active = True self.area_rect = Rect(self.x + 3, self.y + 3, 20, 20) self.direction = K_RIGHT self.new_direction = K_RIGHT self.movements = {K_UP: (0, -self.speed), K_RIGHT: (self.speed, 0), K_DOWN: (0, self.speed), K_LEFT: (-self.speed, 0), K_w: (0, -self.speed), K_d: (self.speed, 0), K_s: (0, self.speed), K_a: (-self.speed, 0), K_DELETE: (0, 0)} def move_hero(self, arguments): pass def move(self): self.x += self.movements[self.direction][0] self.y += self.movements[self.direction][1] self.area_rect.move_ip(self.movements[self.direction][0], self.movements[self.direction][1]) def go_back(self): self.x -= self.movements[self.direction][0] self.y -= self.movements[self.direction][1] self.area_rect.move_ip(-self.movements[self.direction][0], -self.movements[self.direction][1]) def get_proper_random_direction(self): directions = [] if self.direction in PLAYER_ONE_KEYS: for direction in PLAYER_ONE_KEYS : if not self.is_this_the_wall(direction): directions.append(direction) return directions[randint(0, len(directions) - 1)] else: for direction in PLAYER_TWO_KEYS: if not self.is_this_the_wall(direction): directions.append(direction) return directions[randint(0, len(directions) - 1)] def in_place_to_change_direction(self): return self.rect_matrix.is_at_direction_change_place(self.area_rect) #checks if next point with givien direction to current hero map_point have wall on it def is_this_the_wall(self, direction): return self.rect_matrix.is_this_the_wall(self.area_rect, (int(self.movements[direction][0]/self.speed), int(self.movements[direction][1]/self.speed))) def reload_movements(self): self.movements = {K_UP: (0, -self.speed), K_RIGHT: (self.speed, 0), K_DOWN: (0, self.speed), K_LEFT: (-self.speed, 0), K_DELETE: (0, 0)} def teleport(self, map_point): self.map_point = map_point #cooridnates on map.txt self.x = map_point[1] * 20 - 3 # coordinates used to paint object self.y = map_point[0] * 20 - 3 self.area_rect = Rect(self.x + 3, self.y + 3, 20, 20)
class Camera: """ Class for keeping track of the camera position. In early stage of development """ def __init__(self, screen_res, level_rect, x_speed=15, y_speed=15, left_threshold=10, right_threshold=75, up_threshold=10, down_threshold=75): """ :param screen_res: A tuple of int. (w, h) :param level_rect: A rectangle that covers all of the level :param x_speed: The horizontal speed of the camera :param y_speed: The vertical speed of the camera :param left_threshold: The percentage of screen to reach in order for the camera to scroll left :param right_threshold: The percentage of screen to reach in order for the camera to scroll right :param up_threshold: The percentage of screen to reach in order for the camera to scroll up :param down_threshold: The percentage of screen to reach in order for the camera to scroll down """ self.level_rect = level_rect self.horizontal_speed = x_speed self.vertical_speed = y_speed self.screen_res = screen_res self.rect = Rect((0, 0), screen_res) self.x_bound = self.level_rect.width - self.rect.width self.y_bound = self.level_rect.height - self.rect.height self.right_threshold = self.rect.width * right_threshold / 100 self.left_threshold = self.rect.width * left_threshold / 100 self.up_threshold = self.rect.height * up_threshold / 100 self.down_threshold = self.rect.height * down_threshold / 100 def pan_left(self, h_speed): if self.rect.x == 0: return if self.rect.move(-h_speed, 0).x < 0: self.rect.move_ip(-self.rect.x, 0) else: self.rect.move_ip(-h_speed, 0) def pan_right(self, h_speed): if self.rect.x == self.x_bound: return if self.rect.x + h_speed + self.rect.width > self.level_rect.width: self.rect.move_ip((self.level_rect.width - (self.rect.x + self.rect.width)), 0) else: self.rect.move_ip(h_speed, 0) def pan_down(self, v_speed): if self.rect.y == self.y_bound: return if self.rect.y + v_speed + self.rect.height > self.level_rect.height: self.rect.move_ip(0, (self.level_rect.height - self.rect.y - self.rect.height)) else: self.rect.move_ip(0, v_speed) def pan_up(self, v_speed): if self.rect.y == 0: return if self.rect.move(0, -v_speed).y < 0: self.rect.move_ip(0, -self.rect.y) else: self.rect.move_ip(0, -v_speed) def snap_to(self, player_rect): propose = self.rect.move(0, 0) propose.x = min(max(0, player_rect.x - self.screen_res[0] / 2), self.x_bound) propose.y = min(max(0, player_rect.y - self.screen_res[1] / 2), self.y_bound) self.rect = propose def update(self, player_rect, custom_speed=None): h_speed, v_speed = self.horizontal_speed, self.vertical_speed if custom_speed: h_speed, v_speed = custom_speed if player_rect.x - self.rect.x > self.right_threshold: self.pan_right(h_speed) elif player_rect.x - self.rect.x < self.left_threshold: self.pan_left(h_speed) if player_rect.y - self.rect.y > self.down_threshold: self.pan_down(v_speed) elif player_rect.y - self.rect.y < self.up_threshold: self.pan_up(v_speed)
class Block(Rect): def __init__(self): self.shape = [] # list to hold blocks which makes up the shape self.type = "" self.color = random.choice([BLUE, GREEN, RED, ORANGE]) draw = random.randint(1, 2) if draw == 1: self.type = "I" self.state = 1 self.block1 = Rect(0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(block_size, 0, block_size, block_size) self.block3 = Rect(block_size * 2, 0, block_size, block_size) self.block4 = Rect(block_size * 3, 0, block_size, block_size) elif draw == 2: self.type = "square" self.block1 = Rect(0, 0, block_size, block_size) self.block2 = Rect(block_size, 0, block_size, block_size) self.block3 = Rect(0, block_size, block_size, block_size) self.block4 = Rect(block_size, block_size, block_size, block_size) self.shape.append(self.block1) self.shape.append(self.block2) self.shape.append(self.block3) self.shape.append(self.block4) # elif draw == 3: # self.type = "T" # self.state = 1 # self.block1 = Rect(block_size, block_size, block_size, block_size) # self.block2 = Rect(block_size * 2, block_size, block_size, block_size) # self.block3 = Rect(block_size * 3, block_size, block_size, block_size) # self.block4 = Rect(block_size * 2, 0, block_size, block_size) def rotate(self): # prohibit rotation if finished positions already occupied !!! if self.type == "I": if self.state == 1: self.block1.move_ip(block_size, -block_size * 2) self.block2.move_ip(0, -block_size) self.block3.move_ip(-block_size, 0) self.block4.move_ip(-block_size * 2, block_size) self.state = 2 else: self.block1.move_ip(-block_size, block_size*2) self.block2.move_ip(0, block_size) self.block3.move_ip(block_size, 0) self.block4.move_ip(block_size*2, -block_size) self.state = 1 elif self.type == "square": pass # elif self.type == "T": # if self.state == 1: def collide_down(self, d): for item in self.shape: if item.bottomleft in d: return True return False def move_left(self, d): clear_to_move = True for item in self.shape: if (item.topleft[0] - block_size, item.topleft[1]) in d or item.left == 0: clear_to_move = False if clear_to_move: for item in self.shape: item.move_ip(-block_size, 0) def move_right(self, d): clear_to_move = True for item in self.shape: if (item.topleft[0] + block_size, item.topleft[1]) in d or item.right == block_size * col: clear_to_move = False if clear_to_move: for item in self.shape: item.move_ip(block_size, 0) def move_down(self): for item in self.shape: item.move_ip(0, block_size) def key_press(self, d): key = pygame.key.get_pressed() if key[pygame.K_LEFT]: self.move_left(d) if key[pygame.K_RIGHT]: self.move_right(d) if key[pygame.K_UP]: self.rotate() def draw(self, surface): for item in self.shape: pygame.draw.rect(surface, self.color, item) def get_bottom(self): return self.block4.bottom
class Graphic(Component): """A 2D image that can draw itself onto a Surface or another Graphic. It maintains a position relative to the associated Entity's on-screen position and will be drawn there accordingly. (For example, if the Entity's position is (30, 30) and this Graphic's position is (2, 3), it will be drawn to (32, 33) on-screen.) Several effects can also be applied to it, such as flipping the image and adding or reducing transparency. Attributes: _image (Surface): Contains the Graphic's actual pixel data. _rect (Rect): Contains the Graphic's x and y-offsets relative to its associated Entity, as well as its width and height. """ def __init__(self, source, x=0, y=0): """Declare and initialize instance variables. Args: source (Surface): Contains the 2D image associated with this Graphic. x (int): The x-offset of the Graphic's top-left corner relative to its associated Entity. The default value is 0. y (int): The y-offset of the Graphic's top-left corner relative to its associated Entity. The default value is 0. """ super(Graphic, self).__init__() self._image = convert_to_colorkey_alpha(source) self._rect = Rect(x, y, source.get_width(), source.get_height()) def offset(self, dx=0, dy=0): """Move the Graphic away from its original position relative to the associated Entity by a set horizontal and/or vertical distance. It is safe to pass floats as arguments to this method; they will automatically be rounded to the nearest whole number. Args: dx (int): The horizontal distance to travel. A positive value will move the Graphic to the right, while a negative value will move it to the left. Defaults to 0. dy (int): The vertical distance to travel. A positive value will move the Graphic down, while a negative value will move it up. Defaults to 0. """ self._rect.move_ip(int(round(dx)), int(round(dy))) def set_position(self, new_x=None, new_y=None): """Re-position the Graphic onto an exact location relative to its associated Entity. It is safe to pass floats as arguments to this method; they will automatically be rounded to the nearest whole number. Args: new_x (int): The x-coordinate of the top-left corner for the Graphic's new position. This parameter is optional; you can omit it from the function call if you want to retain the Graphic's x-position. new_y (int): The y-coordinate of the top-left corner for the Graphic's new position. This parameter is optional; you can omit it from the function call if you want to retain the Graphic's y-position. """ if new_x is not None: self._rect.x = int(round(new_x)) if new_y is not None: self._rect.y = int(round(new_y)) def get_width(self): return self._rect.width def get_height(self): return self._rect.height def center(self, axis, container_rect): """Move the associated Entity so that this Graphic is centered horizontally and/or vertically within an area of the screen. Args: axis (Axis): A literal from the Axis enum for specifying whether the image should be centered on the horizontal or vertical plane. To center the image on both planes, you can combine both values using the | (bitwise or) operator. container_rect (Rect): A Rect containing the position and dimensions of the area that this Graphic will be centered relative to. """ if (axis & Axis.horizontal) == Axis.horizontal: centered_x = (container_rect.width - self.get_width()) / 2 centered_x += container_rect.x self.entity.set_position(new_x=centered_x - self._rect.x) if (axis & Axis.vertical) == Axis.vertical: centered_y = (container_rect.height - self.get_height()) / 2 centered_y += container_rect.y self.entity.set_position(new_y=centered_y - self._rect.y) def is_contained(self, container_rect): """Return a Boolean indicating whether this Graphic is completely contained within an area of the screen. (i.e. No pixels exceed the area's boundaries.) Args: container_rect (Rect): Contains the position and dimensions of the area this Graphic will be compared to. """ if container_rect.contains(self.draw_rect()): return True else: return False def is_outside(self, container_rect): """Return a Boolean indicating whether this Graphic is completely outside of an area of the screen. (i.e. All pixels exceed the area's boundaries.) Args: container_rect (Rect): Contains the position and dimensions of the area this Graphic will be compared to. """ if not container_rect.colliderect(self.draw_rect()): return True else: return False def draw_rect(self): """Return a Rect containing the actual position the Graphic will be drawn to, with the Entity's position taken into account. """ return self._rect.move(self.entity.x, self.entity.y) def flip(self, axis): """Flip the image horizontally and/or vertically. Args: axis (Axis): A literal from the Axis enum for specifying whether to apply a horizontal or vertical flip. To flip the image both ways, you can combine both values using the | (bitwise or) operator. """ if (axis & Axis.horizontal) == Axis.horizontal: self._image = pygame.transform.flip(self._image, True, False) if (axis & Axis.vertical) == Axis.vertical: self._image = pygame.transform.flip(self._image, False, True) def magnify(self, zoom): """Enlarge or shrink the image using an equal scale for the width and height. Args: zoom (float): The amount used to multiply the image's dimensions. For example, passing a value of 2 when the image's dimensions are 24x24 will enlarge the image to 48x48. Passing 0.5 will shrink it to 12x12. """ magnified_image = pygame.transform.scale( self._image, (int(round(self.get_width() * zoom)), int(round(self.get_height() * zoom)))) self._image = magnified_image self._update_rect_dimensions() def resize(self, new_width, new_height): """Stretch and/or shrink the image to fit new dimensions. Args: new_width (int): The width that the image will shrink or stretch to fit. new_height (int): The height that the image will shrink or stretch to fit. """ resized_image = pygame.transform.scale( self._image, (int(round(new_width)), int(round(new_height)))) self._image = resized_image self._update_rect_dimensions() def _update_rect_dimensions(self): """Update the width and height of _rect with the current dimensions of _image. """ self._rect.width = self._image.get_width() self._rect.height = self._image.get_height() def opacify(self, amount): """Increase or decrease the image's transparency. Note that Graphic images always start with an opacity of 255, which is fully opaque. Args: amount (int): How much to add to the image's opacity value. Positive values will make the image more opaque, while negative values will make it more transparent. To make the image fully opaque, pass 255 or more. To make the image fully transparent, pass -255 or less. """ self._image.set_alpha(self._image.get_alpha() + amount) def is_opaque(self): """Return a Boolean indicating whether the image is fully opaque. """ if self._image.get_alpha() >= 255: return True else: return False def is_transparent(self): """Return a Boolean indicating whether the image is fully transparent. """ if self._image.get_alpha() <= 0: return True else: return False def blit(self, source, position, rect=None, special_flags=0): """Draw a Surface on top of this Graphic. Args: source (Surface): The image that will be drawn onto this Graphic. position (tuple of int, int): Contains the x and y-positions of the source image relative to this Graphic. area (Rect): An optional parameter specifying the region of the source image that will be used. Leave this parameter blank to draw the entire source image. special_flags (int): A combination of various PyGame flags for blitting effects. See the PyGame documentation on Surface.blit() for more information. This is an optional parameter; leave it blank to use no flags when blitting. Returns: A Rect containing the region of this Graphic that was drawn onto. """ x = position[0] y = position[1] return self._image.blit(source, (x, y), rect, special_flags) def draw(self, destination): """Draw this Graphic's image onto a destination Surface. Args: destination (Surface): Will have this Graphic drawn on it. Returns: A Rect containing the region of the destination that was drawn onto. """ return destination.blit(self._image, self.draw_rect())
class PointerSprite(Sprite): _layer = 999 image = pygame.image.load(resources.get_image_asset("pointer.png")) SPEED = 100 # pixels per second MAX_SPEED = 500 ACCELERATION = .1 MOVE = { # pixels per second X, pixels per second Y pygame.K_LEFT: (-SPEED, 0), pygame.K_RIGHT: (SPEED, 0), pygame.K_UP: (0, -SPEED), pygame.K_DOWN: (0, SPEED) } def __init__(self, vertex_group, scroll_group, *groups): super(PointerSprite, self).__init__(*groups) self.rect = Rect((0, 0), self.image.get_rect().size) self.movement = (0.0, 0.0) self.selected_vertex_id = None self.vertex_group = vertex_group self.image = self.image.convert() self.image.set_colorkey(COLOR_KEY) # The scroll_group is needed because the pointer exists in screen space, not world space. We have to translate # the screen location of the hotspot to its relative position in world space. self.scroll_group = scroll_group def update(self, delta, events): self.process_events(events) self.rect.move_ip(*self.calculate_movement(delta)) self.clamp_topleft() def clamp_topleft(self): w, h = pygame.display.get_surface().get_rect().size x, y = self.rect.topleft x = min(max(0, x), w) y = min(max(0, y), h) self.rect.topleft = (x, y) def process_events(self, events): for event in events: if pygame.MOUSEMOTION == event.type: self.movement = (0.0, 0.0) self.rect.topleft = event.pos elif pygame.MOUSEBUTTONUP == event.type: self.check_vertex_collision() elif pygame.KEYDOWN == event.type: if pygame.K_SPACE == event.key: self.check_vertex_collision() else: x, y = self.movement dx, dy = self.MOVE.get(event.key, (0.0, 0.0)) self.movement = (x + dx, y + dy) elif pygame.KEYUP == event.type: if pygame.K_LEFT == event.key or pygame.K_RIGHT == event.key: self.movement = (0.0, self.movement[1]) elif pygame.K_UP == event.key or pygame.K_DOWN == event.key: self.movement = (self.movement[0], 0.0) def check_vertex_collision(self): vertices = spritecollide(self, self.vertex_group, False, self.collide_hotspot) if vertices: self.selected_vertex_id = vertices[0].vertex.vertex_id logger.debug("Selected vertex %s" % self.selected_vertex_id) @staticmethod def collide_hotspot(pointer, vertex_sprite): view_x, view_y = pointer.scroll_group.view.topleft pointer_x, pointer_y = pointer.rect.topleft hotspot = (view_x + pointer_x, view_y + pointer_y) return vertex_sprite.rect.collidepoint(hotspot) def calculate_movement(self, delta): amount = delta / 1000.0 dx, dy = self.movement if dx != 0 or dy != 0: dx += dx * self.ACCELERATION dy += dy * self.ACCELERATION if abs(dx) > self.MAX_SPEED: dx = -self.MAX_SPEED if dx < 0 else self.MAX_SPEED if abs(dy) > self.MAX_SPEED: dy = -self.MAX_SPEED if dy < 0 else self.MAX_SPEED self.movement = (dx, dy) return amount * dx, amount * dy
class Block(Rect): def __init__(self, matrix): self.shape = [] # list to hold blocks which makes up the shape self.type = "" self.state = None self.color = random.choice([BLUE, GREEN, RED, ORANGE]) self.curr_steps = 0 self.steps = None self.starting_column = 4 starting_left_pos = 4 * block_size draw = random.randint(1, 7) # generate a shape if draw == 1: self.type = "I" # score() --> decides 1.state with best score 2.steps needed to the right self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 3, 0, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos, block_size * 2, block_size, block_size) self.block4 = Rect(starting_left_pos, block_size * 3, block_size, block_size) elif draw == 2: self.type = "square" # score() --> decides 1.state with best score 2.steps needed to the right self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos, 0, block_size, block_size) self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) elif draw == 3: self.type = "z" # score() --> decides 1.state with best score 2.steps needed to the right self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 2, block_size, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos + block_size, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + 0, block_size * 2, block_size, block_size) elif draw == 4: self.type = "z_2" # score() --> decides 1.state with best score 2.steps needed to the right self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos + block_size, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size * 2, block_size, block_size) elif draw == 5: self.type = "T" self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + 0, block_size * 2, block_size, block_size) elif self.state == 2: self.block1 = Rect(starting_left_pos + block_size, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 2, block_size, block_size, block_size) elif self.state == 3: self.block1 = Rect(starting_left_pos + block_size, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size * 2, block_size, block_size) elif draw == 6: self.type = "L" self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size * 2, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) self.block4 = Rect(starting_left_pos + 0, block_size, block_size, block_size) elif self.state == 2: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + 0, block_size * 2, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size * 2, block_size, block_size) elif self.state == 3: self.block1 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 2, block_size, block_size, block_size) elif draw == 7: self.type = "L_2" self.score(matrix) if self.state == 0: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + 0, block_size * 2, block_size, block_size) elif self.state == 1: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + 0, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 2, block_size, block_size, block_size) elif self.state == 2: self.block1 = Rect(starting_left_pos + block_size, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, block_size, block_size, block_size) self.block3 = Rect(starting_left_pos + 0, block_size * 2, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size, block_size * 2, block_size, block_size) elif self.state == 3: self.block1 = Rect(starting_left_pos + 0, 0, block_size, block_size) # left, top, width, height self.block2 = Rect(starting_left_pos + block_size, 0, block_size, block_size) self.block3 = Rect(starting_left_pos + block_size * 2, 0, block_size, block_size) self.block4 = Rect(starting_left_pos + block_size * 2, block_size, block_size, block_size) self.shape.append(self.block1) self.shape.append(self.block2) self.shape.append(self.block3) self.shape.append(self.block4) def score(self, matrix): # matrix: pos_matrix in Block class matrix_copy = copy.deepcopy(matrix) apex_list = [] # position i stores the row number of the highest block at column i. for j in range(col): try: idx = [matrix[i][j] for i in range(len(matrix))].index(1) apex_list.append(idx) except ValueError: apex_list.append(row) # for each type, evaluate all possible positions of each rotation. # The number of moves are (rotations * right). # first position stores max_score, second position stores state, # third position stores # of steps to take optimal_move = [None] * 3 max_score = float("-inf") shape_variants = Shape(self.type) # print("Type is ", self.type) for state in range(len(shape_variants.all_combos)): # print("State is ", state) rightmost = shape_variants.rightmost[state] + self.starting_column for step_number in range(0, col - rightmost): # steps to move RIGHT min_distance = math.inf for coord in shape_variants.pos[state]: # find the minimum distance to move # row number: coord[0], col number: coord[1]+step_number distance = apex_list[coord[1] + step_number + self.starting_column] - coord[0] if distance < min_distance: min_distance = distance # move them for coord in shape_variants.pos[state]: matrix_copy[coord[0] + min_distance - 1][coord[1] + step_number + self.starting_column] = 1 score = self.score_matrix(matrix_copy, min_distance) # print("\nMatrix after one possible movement is: ") # for idx, r in enumerate(matrix_copy): # print(f'{idx: > 5}', r) # print("\n") matrix_copy = copy.deepcopy(matrix) if score > max_score: max_score = score optimal_move[0] = max_score optimal_move[1] = state optimal_move[2] = step_number for step_number in range(-1, -(self.starting_column + 1), -1): # steps to move LEFT min_distance = math.inf for coord in shape_variants.pos[state]: # find the minimum distance to move # row number: coord[0], col number: coord[1] + step_number + starting_column distance = apex_list[coord[1] + step_number + self.starting_column] - coord[0] if distance < min_distance: min_distance = distance # move them for coord in shape_variants.pos[state]: matrix_copy[coord[0] + min_distance - 1][coord[1] + step_number + self.starting_column] = 1 score = self.score_matrix(matrix_copy, min_distance) # print("\nMatrix after one possible movement is: ") # for idx, r in enumerate(matrix_copy): # print(f'{idx: > 5}', r) # print("\n") matrix_copy = copy.deepcopy(matrix) if score > max_score: max_score = score optimal_move[0] = max_score optimal_move[1] = state optimal_move[2] = step_number self.state = optimal_move[1] self.steps = optimal_move[2] def score_matrix(self, matrix, move_distance): score = 0 row_cleared = 0 for r in matrix: if set(r) == {1}: row_cleared += 1 score += row_cleared * row_cleared landing_height = row - move_distance score -= landing_height holes = 0 for i in range(row - 1): for j in range(col): if matrix[i][j] == 1: try: for k in range(i + 1, row): if matrix[k][j] == 1: break if matrix[k][j] == 0: holes += 1 except: pass # print("# of holes: ", holes) score -= 4 * holes # cumulative well (a well with depth N will have 1+2+3+...+N score) well_sum = 0 depth = 0 for i in range(row): if matrix[i][0] == 0 and matrix[i][1] == 1: depth += 1 well_sum += depth if matrix[i][0] == 1: depth = 0 depth = 0 for i in range(row): if matrix[i][col - 1] == 0 and matrix[i][col - 2] == 1: depth += 1 well_sum += depth if matrix[i][col - 1] == 1: depth = 0 for c in range(1, col - 1): depth = 0 for r in range(row): if matrix[r][c] == 0 and matrix[r][c - 1] == 1 and matrix[r][c + 1] == 1: depth += 1 well_sum += depth else: depth = 0 score -= well_sum # if well_sum > 10: # print("Well sum: ", well_sum, "\nMatrix: ") # for r in matrix: # print(r) # print("\n") ######################################################### # Row Transition # row_transition = 0 for r in range(row): if 1 in matrix[r]: state = 1 for c in range(col): if matrix[r][c] == 0: curr_state = 0 if curr_state != state: state = curr_state row_transition += 1 if c == col - 1: row_transition += 1 elif matrix[r][c] == 1: curr_state = 1 if curr_state != state: state = curr_state row_transition += 1 # print(r, "cumulative transition: ", row_transition) score -= row_transition # print("row_transition is ", row_transition, "\nMatrix: ") # for row_idx, r in enumerate(matrix): # print(f'{row_idx:>5}', ": ", r) # print("\n") ##################################################### ##################################################### # Column Transition # col_transition = 0 for c in range(col): state = 0 for r in range(row): if matrix[r][c] == 1: curr_state = 1 if curr_state != state: state = curr_state col_transition += 1 elif matrix[r][c] == 0: curr_state = 0 if curr_state != state: state = curr_state col_transition += 1 if r == row - 1: col_transition += 1 score -= col_transition ##################################################### # print("cumulative col_transition is ", col_transition, "\nMatrix: ") # for row_idx, r in enumerate(matrix): # print(f'{row_idx:>5}', ": ", r) # print("\n") score -= col_transition return score def move_down(self): # move left or right to desirable position calculated by score function if self.steps < 0: # moving left if self.curr_steps > self.steps: for item in self.shape: item.move_ip(-block_size, 0) self.curr_steps -= 1 elif self.steps > 0: # moving right if self.curr_steps < self.steps: for item in self.shape: item.move_ip(block_size, 0) self.curr_steps += 1 # move down for item in self.shape: item.move_ip(0, block_size) # controlled by key_press def rotate(self): # prohibit rotation if finished positions already occupied !!! if self.type == "I": if self.state == 1: self.block1.move_ip(block_size, -block_size * 2) self.block2.move_ip(0, -block_size) self.block3.move_ip(-block_size, 0) self.block4.move_ip(-block_size * 2, block_size) self.state = 2 else: self.block1.move_ip(-block_size, block_size * 2) self.block2.move_ip(0, block_size) self.block3.move_ip(block_size, 0) self.block4.move_ip(block_size * 2, -block_size) self.state = 1 elif self.type == "square": pass # elif self.type == "T": # if self.state == 1: def collide_down(self, d): for item in self.shape: if item.bottomleft in d: return True return False def move_left(self, d): clear_to_move = True for item in self.shape: if (item.topleft[0] - block_size, item.topleft[1]) in d or item.left == 0: clear_to_move = False if clear_to_move: for item in self.shape: item.move_ip(-block_size, 0) def move_right(self, d): clear_to_move = True for item in self.shape: if (item.topleft[0] + block_size, item.topleft[1]) in d or item.right == block_size * col: clear_to_move = False if clear_to_move: for item in self.shape: item.move_ip(block_size, 0) def key_press(self, d): key = pygame.key.get_pressed() if key[pygame.K_LEFT]: self.move_left(d) if key[pygame.K_RIGHT]: self.move_right(d) if key[pygame.K_UP]: self.rotate() def draw(self, surface): for item in self.shape: pygame.draw.rect(surface, self.color, item) def get_bottom(self): return self.block4.bottom
class Widget(Sprite): """Use Widget class for better movement tracking Widget class inherits from Sprite class. Test cases for class Widget >>> from widget import * >>> import pygame >>> s = pygame.surface.Surface((30,30)) >>> w = Widget(s, (0,0,30,30), (0,0)) >>> w.rect <rect(0, 0, 30, 30)> >>> w.update() >>> w.rect <rect(0, 0, 30, 30)> >>> w.getMovement() [0, 0] >>> w.setX(1) >>> w.getX() 1 >>> w.setY(4) >>> w.getY() 4 >>> w.setMovement((3,5)) >>> w.getMovement() (3, 5) >>> w.getName() (0, 0) >>> w.setPosition((5,7)) >>> w.getPosition() (5, 7) """ def __init__(self, image, rect, name=''): """Instantiate a widget with a given surface, rectangle, and (x,y) movement pair. """ Sprite.__init__(self) self.movement = [0, 0] self.rect = Rect(rect) self.lastRect = self.rect self.image = image self.name = name self.frames = [] self.frameIndex = 0 self.frameRate = 1 self.timeDelay = WIDGETFRAMES self.lastUpdate = 0 self.world = None self.undone = False self.id = self.rect.top + self.rect.left +\ self.rect.width + self.rect.height def attachToWorld(self, world): self.world = world self.id = self.world.curWidgetID self.world.curWidgetID += 1 def startAnimation(self, frames, startIndex, frameRate): self.frames = frames self.frameIndex = startIndex self.frameRate = frameRate self.image = self.frames[startIndex] self.lastUpdate = self.timeDelay def __str__(self): return str(self.rect.left) + str(self.rect.top) + str(self.id) def setMovement(self, vector): """Set movement with a pair""" if (self.movement != [0, 0] and vector == [0, 0]): self.world.dirtyGroup.add(self) self.movement = vector def getMovement(self): """Return movement as a pair""" return self.movement def setStop(self): """Set movement to 0""" self.setMovement([0, 0]) def setY(self, y): """Set y-component of movement""" self.movement[1] = y def setX(self, x): """Set x-component of movement""" self.movement[0] = x def getX(self): """Get x-component of movement""" return self.movement[0] def getY(self): """Set y-component of movement""" return self.movement[1] def setPosition(self, pair): """Set x and y coords of Widget""" self.rect.topleft = pair def getPosition(self): """Get x and y coords of Widget""" return self.rect.topleft def update(self): """Move sprite according to its movement vector""" # Widget needs to be animated if (len(self.frames) > 0): if self.lastUpdate <= 0: self.frameIndex = (self.frameIndex + 1) % (len(self.frames)) self.image = self.frames[self.frameIndex] self.lastUpdate = self.timeDelay self.world.dirtyGroup.add(self) else: self.lastUpdate -= 1 elif (self.getMovement != [0, 0]): self.lastRect = Rect(self.rect) self.rect.move_ip(self.movement) self.world.dirtyGroup.add(self) def undoUpdate(self): """Widget returns to state prior to last update()""" self.rect = self.lastRect def getShadow(self): shadow = Sprite() shadow.rect = self.lastRect.move(0, 0) return shadow def getName(self): """Get name of Widget""" return self.name
class Widget(Sprite): """Use Widget class for better movement tracking Widget class inherits from Sprite class. Test cases for class Widget >>> from widget import * >>> import pygame >>> s = pygame.surface.Surface((30,30)) >>> w = Widget(s, (0,0,30,30), (0,0)) >>> w.rect <rect(0, 0, 30, 30)> >>> w.update() >>> w.rect <rect(0, 0, 30, 30)> >>> w.getMovement() [0, 0] >>> w.setX(1) >>> w.getX() 1 >>> w.setY(4) >>> w.getY() 4 >>> w.setMovement((3,5)) >>> w.getMovement() (3, 5) >>> w.getName() (0, 0) >>> w.setPosition((5,7)) >>> w.getPosition() (5, 7) """ def __init__(self, image, rect, name=''): """Instantiate a widget with a given surface, rectangle, and (x,y) movement pair. """ Sprite.__init__(self) self.movement = [0, 0] self.rect = Rect(rect) self.lastRect = self.rect self.image = image self.name = name self.frames = [] self.frameIndex = 0 self.frameRate = 1 self.timeDelay = WIDGETFRAMES self.lastUpdate = 0 self.world = None self.undone = False self.id = self.rect.top + self.rect.left +\ self.rect.width + self.rect.height def attachToWorld(self, world): self.world = world self.id = self.world.curWidgetID self.world.curWidgetID += 1 def startAnimation(self, frames, startIndex, frameRate): self.frames = frames self.frameIndex = startIndex self.frameRate = frameRate self.image = self.frames[startIndex] self.lastUpdate = self.timeDelay def __str__(self): return str(self.rect.left) + str(self.rect.top) + str(self.id) def setMovement(self, vector): """Set movement with a pair""" if(self.movement != [0,0] and vector == [0,0]): self.world.dirtyGroup.add(self) self.movement = vector def getMovement(self): """Return movement as a pair""" return self.movement def setStop(self): """Set movement to 0""" self.setMovement([0,0]) def setY(self, y): """Set y-component of movement""" self.movement[1] = y def setX(self, x): """Set x-component of movement""" self.movement[0] = x def getX(self): """Get x-component of movement""" return self.movement[0] def getY(self): """Set y-component of movement""" return self.movement[1] def setPosition(self, pair): """Set x and y coords of Widget""" self.rect.topleft = pair def getPosition(self): """Get x and y coords of Widget""" return self.rect.topleft def update(self): """Move sprite according to its movement vector""" # Widget needs to be animated if (len(self.frames) > 0): if self.lastUpdate <= 0: self.frameIndex = (self.frameIndex+1)%(len(self.frames)) self.image = self.frames[self.frameIndex] self.lastUpdate = self.timeDelay self.world.dirtyGroup.add(self) else: self.lastUpdate -= 1 elif(self.getMovement != [0,0]): self.lastRect = Rect(self.rect) self.rect.move_ip(self.movement) self.world.dirtyGroup.add(self) def undoUpdate(self): """Widget returns to state prior to last update()""" self.rect = self.lastRect def getShadow(self): shadow = Sprite() shadow.rect = self.lastRect.move(0,0) return shadow def getName(self): """Get name of Widget""" return self.name