"""Zeichenroutinen – alles, was Pixel auf den Bildschirm bringt.""" from __future__ import annotations import pygame from . import config from .case import Case from .sequences import CaseSpec # -------------------------------------------------------------------------- # Hintergrund-Bänder # -------------------------------------------------------------------------- def draw_top_bar(surface: pygame.Surface, assets, percent: float, subtitle: str = "") -> None: surface.fill( config.COLOR_TOP_BAR, (0, 0, config.SCREEN_WIDTH, config.TOP_BAR_HEIGHT) ) font = assets.font title = font.render(config.TITLE_TEXT, True, config.COLOR_WHITE) surface.blit(title, (40, config.TOP_BAR_HEIGHT // 2 - title.get_height() // 2)) if subtitle: small = (assets.font_small or font).render(subtitle, True, config.COLOR_WHITE) surface.blit( small, ( 40 + title.get_width() + 48, config.TOP_BAR_HEIGHT // 2 - small.get_height() // 2, ), ) _draw_progress_bar(surface, assets, percent) def _draw_progress_bar(surface: pygame.Surface, assets, percent: float) -> None: bar_width = config.PROGRESS_BAR_WIDTH bar_height = config.PROGRESS_BAR_HEIGHT x = config.SCREEN_WIDTH - bar_width - config.PROGRESS_BAR_RIGHT_MARGIN y = config.TOP_BAR_HEIGHT // 2 - bar_height // 2 gap = config.PROGRESS_BAR_GAP segment_width = (bar_width - (config.PROGRESS_BAR_SEGMENTS - 1) * gap) / config.PROGRESS_BAR_SEGMENTS red = min(255, int(percent * 2.55)) green = max(0, 255 - int(percent * 2.55)) color = (red, green, 0) for index in range(config.PROGRESS_BAR_SEGMENTS): segment_x = x + index * (segment_width + gap) rect = (segment_x, y, segment_width, bar_height) pygame.draw.rect(surface, config.COLOR_PROGRESS_TRACK, rect, border_radius=config.PROGRESS_BAR_RADIUS) if percent >= (index + 1) * 20: pygame.draw.rect(surface, color, rect, border_radius=config.PROGRESS_BAR_RADIUS) text = assets.font.render(f"{int(percent)}%", True, config.COLOR_WHITE) surface.blit(text, (x + bar_width + 10, y)) def draw_background(surface: pygame.Surface, assets) -> None: if assets.background is not None: surface.blit(assets.background, (0, config.TOP_BAR_HEIGHT)) else: pygame.draw.rect( surface, config.COLOR_BACKGROUND, (0, config.TOP_BAR_HEIGHT, config.SCREEN_WIDTH, config.GAME_AREA_HEIGHT), ) def clear_bottom_bar(surface: pygame.Surface) -> None: """Untere Leiste schwärzen (ohne Inhalt).""" surface.fill( config.COLOR_BOTTOM_BAR, ( 0, config.TOP_BAR_HEIGHT + config.GAME_AREA_HEIGHT, config.SCREEN_WIDTH, config.BOTTOM_BAR_HEIGHT, ), ) def draw_bottom_bar(surface: pygame.Surface, assets, upcoming: list[CaseSpec]) -> None: clear_bottom_bar(surface) top = config.SCREEN_HEIGHT - config.BOTTOM_BAR_HEIGHT label = (assets.font_small or assets.font).render(config.NEXT_LABEL, True, config.COLOR_WHITE) surface.blit(label, (40, top + 12)) preview_x = 40 for index, spec in enumerate(upcoming[: config.NEXT_PREVIEW_COUNT]): image = _preview_surface(assets, spec) height = image.get_height() if image is not None else 0 y = config.SCREEN_HEIGHT - config.NEXT_PREVIEW_MARGIN - height if image is None: width, height = _spec_size(spec) y = config.SCREEN_HEIGHT - config.NEXT_PREVIEW_MARGIN - height pygame.draw.rect( surface, config.CASE_COLORS[index % len(config.CASE_COLORS)], (preview_x, y, width, height), ) else: width = image.get_width() surface.blit(image, (preview_x, y)) preview_x += max(width, 60) + 20 _PREVIEW_CACHE: dict[tuple[int, int], pygame.Surface | None] = {} def _preview_surface(assets, spec: CaseSpec) -> pygame.Surface | None: """Vorschau-Grafik in verkleinertem Massstab (gecacht).""" key = (spec.units_w, spec.units_h) if key in _PREVIEW_CACHE: return _PREVIEW_CACHE[key] image = assets.case_surface(spec.units_w, spec.units_h) if assets is not None else None if image is not None: size = ( max(1, int(image.get_width() * config.NEXT_PREVIEW_SCALE)), max(1, int(image.get_height() * config.NEXT_PREVIEW_SCALE)), ) image = pygame.transform.smoothscale(image, size) _PREVIEW_CACHE[key] = image return image # -------------------------------------------------------------------------- # Auflieger # -------------------------------------------------------------------------- def draw_trailer(surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None: pygame.draw.rect( surface, config.COLOR_TRAILER, ( config.TRAILER_X + offset[0], config.TRAILER_Y + offset[1], config.TRAILER_WIDTH, config.TRAILER_HEIGHT, ), ) def draw_wall(surface: pygame.Surface, wall_x: float, offset: tuple[int, int] = (0, 0)) -> None: pygame.draw.line( surface, config.COLOR_WALL, (wall_x + offset[0], config.TRAILER_Y + offset[1]), (wall_x + offset[0], config.TRAILER_BOTTOM + offset[1]), 3, ) # -------------------------------------------------------------------------- # Koffer # -------------------------------------------------------------------------- def _spec_size(spec: CaseSpec) -> tuple[int, int]: return ( int(spec.units_w * config.UNIT_LENGTH), int(spec.units_h * config.UNIT_HEIGHT), ) def draw_case(surface: pygame.Surface, assets, case: Case, angle: float = 0.0) -> None: """Zeichnet einen Koffer – immer mit **seiner eigenen** Textur. Ein gedrehter Koffer wird um 90° gedreht gezeichnet, bekommt aber keine andere Grafik. """ x, y = int(case.x), int(case.y) width, height = int(case.width), int(case.height) image = assets.case_surface(*case.texture) if assets is not None else None if image is None: pygame.draw.rect(surface, case.color, (x, y, width, height)) return if case.is_rotated: image = pygame.transform.rotate(image, -90) if angle: rotated = pygame.transform.rotate(image, angle) surface.blit(rotated, rotated.get_rect(bottomleft=(x, y + height))) else: surface.blit(image, (x, y)) # -------------------------------------------------------------------------- # Hinweise und Overlays # -------------------------------------------------------------------------- def draw_hint(surface: pygame.Surface, assets, stack_possible: bool) -> None: if stack_possible: text, color = config.HINT_STACK, config.COLOR_HINT_STACK else: text, color = config.HINT_SNAP, config.COLOR_HINT_SNAP rendered = assets.font.render(text, True, color) surface.blit( rendered, ( config.SCREEN_WIDTH - config.SCREEN_WIDTH // 3 - rendered.get_width() // 2, config.SCREEN_HEIGHT - config.BOTTOM_BAR_HEIGHT // 2 - rendered.get_height() // 2, ), ) def draw_dim(surface: pygame.Surface, alpha: int = 170) -> None: """Dunkelt das ganze Bild ab (für Menü und Ergebnis).""" overlay = pygame.Surface((config.SCREEN_WIDTH, config.SCREEN_HEIGHT)) overlay.set_alpha(alpha) overlay.fill((0, 0, 0)) surface.blit(overlay, (0, 0)) def draw_overlay_lines( surface: pygame.Surface, assets, lines: list[str], ) -> None: """Dunkler Schleier mit zentrierten Textzeilen (Ergebnis-Anzeige).""" draw_dim(surface, 220) center_y = config.SCREEN_HEIGHT // 2 line_gap = 16 rendered: list[pygame.Surface] = [] total_height = 0 for line in lines: image = assets.font.render(line, True, config.COLOR_WHITE) rendered.append(image) total_height += image.get_height() + line_gap y = center_y - total_height // 2 for image in rendered: surface.blit(image, (config.SCREEN_WIDTH // 2 - image.get_width() // 2, y)) y += image.get_height() + line_gap