Files
Habegger_loader_game/loader_game/scene.py
T

267 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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 _wrap_text(text: str, font: pygame.font.Font, max_width: int) -> list[str]:
"""Bricht eine Zeile an Wortgrenzen um, wenn sie zu breit würde."""
words = text.split()
if not words:
return [text]
lines: list[str] = []
current = words[0]
for word in words[1:]:
candidate = f"{current} {word}"
if font.size(candidate)[0] <= max_width:
current = candidate
else:
lines.append(current)
current = word
lines.append(current)
return lines
def draw_overlay_lines(
surface: pygame.Surface,
assets,
lines: list[str],
) -> None:
"""Dunkler Schleier mit zentrierten Textzeilen (Ergebnis-Anzeige).
Die erste Zeile wird gross gesetzt, alle weiteren klein – so passen auch
längere Sprüche auf den Schirm.
"""
draw_dim(surface, 220)
big = assets.font
small = assets.font_small or big
max_width = config.SCREEN_WIDTH - 200
rendered: list[pygame.Surface] = []
for index, line in enumerate(lines):
font = big if index == 0 else small
for part in _wrap_text(line, font, max_width):
rendered.append(font.render(part, True, config.COLOR_WHITE))
line_gap = 18
total_height = sum(image.get_height() + line_gap for image in rendered)
y = config.SCREEN_HEIGHT // 2 - total_height // 2
# Dunkles Band hinter dem Text, damit er auch über Koffern lesbar bleibt.
widest = max(image.get_width() for image in rendered)
band = pygame.Surface((widest + 120, total_height + 20), pygame.SRCALPHA)
band.fill((0, 0, 0, 170))
surface.blit(
band,
(
config.SCREEN_WIDTH // 2 - band.get_width() // 2,
y - 10,
),
)
for image in rendered:
surface.blit(image, (config.SCREEN_WIDTH // 2 - image.get_width() // 2, y))
y += image.get_height() + line_gap