def __init__(self, level_num: int, raw_data: List[List[Tile]]) -> None: self.level_num = level_num self.tilemap_data = [Tile.VOID] * (LEVEL_WIDTH * LEVEL_HEIGHT) self.size = Size(len(raw_data[0]), len(raw_data)) offset = Point((LEVEL_WIDTH - self.size.width) // 2, (LEVEL_HEIGHT - self.size.height) // 2) for y in range(self.size.height): for x in range(self.size.width): pos = Point(x, y).offset(offset) self.tilemap_data[self._pos_to_offset(pos)] = raw_data[y][x]
def with_defaults(cls, items: Tuple[MenuItem, ...], layout: MenuLayout = MenuLayout()) -> "Menu": """Construct menu based on defaults. :param items: collection of menu items :param layout: layout information of the menu :return: newly created instance of Menu """ item_size = reduce(max_size, [item.size for item in items]) total_rows = -(-len(items) // layout.columns) calculated_rows = min(layout.rows, total_rows) if layout.rows else total_rows calculated_size = Size( layout.columns * item_size.width + (layout.columns - 1) * layout.item_space.width, calculated_rows * item_size.height + (calculated_rows - 1) * layout.item_space.height) centered_position = center_in_rect(calculated_size).position if layout.position: x = layout.position.x if layout.position.x >= 0 else centered_position.x y = layout.position.y if layout.position.y >= 0 else centered_position.y menu_position = Point(x, y) else: menu_position = centered_position return Menu( items=items, item_size=item_size, item_space=layout.item_space, columns=layout.columns, rows=calculated_rows, position=menu_position)
def tile_positions(self) -> Generator[Tuple[Point, Point], None, None]: """Generator for iterating over all valid tile positions inside both level and Pyxel's mega-tilemap (from top-left to bottom-right).""" tilemap_rect = tilemap_rect_nth(self.level_num) tilemap_points = tilemap_rect.inside_points() for point in tilemap_points: yield point.offset(Point(-tilemap_rect.x, -tilemap_rect.y)), point
def move(self, direction: Direction) -> None: """Move object position in given direction by one tile. Additionally reset offset. :param direction: direction of the movement """ self.tile_position = self.tile_position.move(direction) self.offset = Point(0, 0)
def draw(self, draw_as_secondary: bool = False) -> None: super().draw(draw_as_secondary) for i, item in enumerate(self.visible_items): item_space = self.menu.item_space calculated_item_size = self.menu.item_size.enlarge(item_space.width, item_space.height) position = self.menu.position.offset(Point( (i % self.menu.columns) * calculated_item_size.width, (i // self.menu.columns) * calculated_item_size.height)) item.draw(position, (self.top_row * self.menu.columns) + i == self.selected_item)
def update(self, dt_in_ms: float, stats: GameStats) -> Optional[GameAction]: running_action = super().update(dt_in_ms, stats) if running_action: move_direction = self.direction.opposite if self.backward else self.direction delta = self.elapsed_time / self.time_to_complete * TILE_SIZE self.game_object.position.offset = Point( int(delta * move_direction.dx), int(delta * move_direction.dy)) return running_action
def _draw_digits(self, gui_position: GuiPosition, text: str, gui_sprite: GuiSprite, colon_size: Optional[int] = None) -> None: position = self._get_position(gui_position) sprite = self._get_sprite(gui_sprite) char_pos = position for char in text: if char.isdigit(): sprite.draw(position=char_pos, frame=int(char)) char_size = colon_size if (char == ":" and colon_size) else sprite.width char_pos = char_pos.offset(Point(char_size + 1, 0))
class ObjectPosition: """Position of game object in the level. Attributes: tile_position - game object position in tilemap space offset - position offset relative to tile_position (expressed in pixels) """ tile_position: TilePosition offset: Point = Point(0, 0) def move(self, direction: Direction) -> None: """Move object position in given direction by one tile. Additionally reset offset. :param direction: direction of the movement """ self.tile_position = self.tile_position.move(direction) self.offset = Point(0, 0) def to_point(self) -> Point: """Convert tile position to a point in screen space (taking into account the offset).""" return self.tile_position.to_point().offset(self.offset)
def draw(self, position: Point, layer: Optional[Layer] = None, direction: Direction = Direction.UP, frame: int = 0) -> None: """Draw the sprite at given position using specified layer, direction and frame. If sprite is single-layered then it will be drawn _only on main_ layer. :param position: position of sprite to be drawn at :param layer: layer of sprite to be drawn at :param direction: direction-specific variant of sprite to be drawn :param frame: frame of sprite to be drawn """ if layer and layer.layer_index >= self.num_layers: return clamped_frame = min(frame, self.num_frames - 1) frame_offset_v = clamped_frame * self.uv_rect.h // self.num_frames top_layer_offset = self.num_layers - 1 u = self.uv_rect.x + top_layer_offset v = self.uv_rect.y + frame_offset_v + top_layer_offset if self.directional: u += self.uv_rect.w // Direction.num_directions( ) * direction.direction_index if layer: u -= layer.layer_index v -= layer.layer_index offset = layer.offset if layer else Point(0, 0) pyxel.blt(position.x + offset.x, position.y + offset.y, self.image_bank, u, v, self.width, self.height, self.transparency_color)
"""Module defining game screen which is displayed when level is completed.""" from typing import Tuple from bansoko.game.profile import LevelScore from bansoko.game.screens.screen_factory import ScreenFactory from bansoko.graphics import Point from bansoko.graphics.text import draw_text from bansoko.gui.menu import MenuController, TextMenuItem, MenuItem, Menu, MenuLayout LEVEL_TIME_POS = Point(104, 75) LEVEL_PUSHES_POS = Point(104, 84) LEVEL_STEPS_POS = Point(104, 93) class LevelCompletedController(MenuController): """Screen controller allowing player to choose what to do after completion of the level. Player can navigate to the next level, replay the current level (to get better score) or get back to Main Menu. """ def __init__(self, screen_factory: ScreenFactory, level_score: LevelScore): current_level_num = level_score.level_num last_level_completed = current_level_num == screen_factory.get_bundle( ).last_level next_level = TextMenuItem( "PLAY NEXT LEVEL", lambda: screen_factory.get_playfield_screen( screen_factory.get_player_profile().next_level_to_play( current_level_num))) finish_game = TextMenuItem("FINISH GAME", screen_factory.get_victory_screen)
def draw(self, draw_as_secondary: bool = False) -> None: super().draw(draw_as_secondary) draw_text(Point(79, 240), "(c) 2020 KRZYSZTOF FURTAK", TextStyle(color=7, shadow_color=1)) draw_text(Point(11, 240), f"v{__version__}", TextStyle(color=1))
def to_point(self) -> Point: """Convert tile position to a point in screen space.""" return Point(self.tile_x * TILE_SIZE, self.tile_y * TILE_SIZE)
def tilemap_offset(self) -> Point: """Offset of the tilemap, for properly centering on the screen.""" return Point( (self.size.width % 2) * TILE_SIZE // 2, (self.size.height % 2) * TILE_SIZE // 2)
def _offset_to_pos(offset: int) -> Point: return Point(offset % LEVEL_WIDTH, offset // LEVEL_WIDTH)
def _get_menu_position(frame_rect: Rect) -> Point: return Point(-1, frame_rect.bottom - PADDING.y - pyxel.FONT_HEIGHT)
"""Module for handling critical game errors.""" import textwrap import pyxel from bansoko import GAME_FRAME_TIME_IN_MS from bansoko.graphics import Point, center_in_rect, Rect from bansoko.graphics.text import draw_text, text_size from bansoko.gui.menu import MenuController, Menu, TextMenuItem, MenuLayout from bansoko.gui.navigator import ScreenNavigator PADDING = Point(8, 8) class ErrorScreen(MenuController): """Screen controller for displaying game critical errors. It's displayed when a non-recoverable error occurs during game startup. """ def __init__(self, message: str): self.error_message = self._build_error_message(message) self.frame_rect = self._get_frame_rect(self.error_message) menu = Menu.with_defaults( tuple([TextMenuItem("OK", lambda: None)]), MenuLayout(position=self._get_menu_position(self.frame_rect))) super().__init__(menu=menu) def draw(self, draw_as_secondary: bool = False) -> None: self._draw_background() self._draw_frame() super().draw(draw_as_secondary=draw_as_secondary)