diff --git a/map.py b/map.py new file mode 100644 index 0000000..24b9dfb --- /dev/null +++ b/map.py @@ -0,0 +1,26 @@ +from typing import List, Tuple + +class ObstacleSlice: + """ + The game world is divided up into distinct "slices," where each slice is a predefined set of + obstacles in specific positions. These slices are then put forward in random order + """ + + """ + How many units an obstacle slice is + """ + height=20 + + def __init__(self, obstacle_map: List[Tuple[int]], difficulty: int): + """ + Creates an obstacle slice + + :param obstacle_map: + A two-dimensional list description what obstacles are here. Each tuple describes a + single obstacle, its x, and y position, the obstacle type, the x mirror, and the y + mirror. + :param difficulty: + The difficulty of the game. This influences when this slice appears. + """ + self.obstacle_map = obstacle_map + self.difficulty = difficulty diff --git a/sprites.py b/sprites.py new file mode 100644 index 0000000..1c23b42 --- /dev/null +++ b/sprites.py @@ -0,0 +1,133 @@ +from typing import List, Tuple +from random import randint + +import thumby + +import images + +from map import ObstacleSlice + +class Entity: + """ + An entity exists within the game world. It has coordinates, bounds, and optionally collision and sprite + """ + def __init__(self, x: int = 0, y: int = 0, width: int = 8, height: int = 8, collision: bytearray = None, sprite: thumby.Sprite = None): + self.x = x + self.y = y + self.sprite = sprite + self.collision = collision + self.width = width + self.height = height + + def intersects_pixel(self, loc_x: int, loc_y: int) -> bool: + """ + Checks to see if this entity intersects a location in the game world. + + :param loc_x: + An global x location within the game world. + :param loc_y: + A global y position within the game world. + :return: + True if the collision shape of this entity intersects a point in the world, + False otherwise. + """ + if not self.collision: + return False + if loc_x < self.x or loc_x >= self.x + self.width or loc_y < self.y or loc_y >= self.y + self.height: + return False + rel_x = loc_x - self.x + rel_y = loc_y - self.y + return bool((self.collision[rel_x] >> (self.height - rel_y)) & 1) + + def can_render(self, current_depth: int) -> bool: + """ + Checks to see if this sprite is within the game screen. + + :param current_depth: + The current depth, as measured from the top of the screen. We can display anything + up to the screen height down from this number. + + :return: + True if the sprite shows up on screen in any capacity, false otherwise. + """ + if self.x < -self.width or self.x >= thumby.display.width: + return False + max = current_depth + min = current_depth - thumby.display.height + if self.y < (min - self.height) or self.y >= max: + return False + return True + + def update(self, tpf: float, current_depth: int, prev_depth: int) -> bool: + """ + Updates the state of the entity based on the game state. + + :param tpf: + The number of seconds since the last tick. + :param current_depth: + The current depth the game is on. + :param prev_depth: + The depth the game was on last tick. + + :return: + True if the game should keep this entity, false if the game should drop + this entity from memory. + """ + return False + + def render(self, tpf: float, current_depth: int, prev_depth: int) -> bool: + """ + Sets the position of the sprite and draws it. + + :param tpf: + The number of seconds since the last tick. + :param current_depth: + The current depth the game is on. + :param prev_depth: + The depth the game was on last tick. + + :return: + True if the game should actually draw the sprite, false the sprite + should be hidden. + """ + if self.sprite: + self.sprite.y = self.y - self.current_depth + self.sprite.x = self.x + if self.can_render(current_depth): + thumby.display.drawSprite(self.sprite) + return True + return False + +class Obstacle(Entity): + def update(self, tpf: float, current_depth: int, prev_depth: int) -> bool: + return self.can_render(current_depth) + + +def update_entities(current_slice: ObstacleSlice, current_depth: int, prev_depth: int) -> List[Entity]: + """ + Checks the current game state and generates new sprites based on said state. + + :param current_slice: + The current obstacle slice we are using. Once the bottom of the slice displays, we switch + to a new slice. + :param current_depth: + The global world depth we are at. This is modulused by the slice height to determine how + far through the slice we are. + :param prev_depth: + The depth we were at during the last tick. This helps make sure we don't miss spawning + entities + + :return: + A list of entities to generate this tick. + """ + if current_depth == prev_depth: + # If we haven't moved, we don't need to spawn anything + return [] + min_height = min(current_depth, prev_depth) + max_height = max(current_depth, prev_depth) + spawned = [] + for entity in current_slice.obstacle_map: + if y >= min_height and y < max_height: + sprite_image = OBSTACLES[entity[2]] + sprite = thumby.Sprite(sprite_image.width, sprite_image.height, sprite_image.sprite, entity[0], entity[1], -1, entity[3] - current_depth, entity[4]) + spawned = Obstacle(entity[0], entity[1], sprite_image.width, sprite_image.height, sprite_image.collision, sprite)