Hauptmenue, Optionen, Anleitung, Highscores und Weltkarte implementiert

Per DOSBox-Referenzaufnahmen (Xvfb + XTEST-Tastensimulation) den kompletten
Original-Menuefluss erfasst und nachgebaut:
- ESC am stehenden Titelbild oeffnet das Hauptmenue (Original-Verhalten --
  Space/Return tun dort nichts, per Einzeltasten-Sessions verifiziert; das
  Original zeigt auch nach 115s passiv keinen Attract-Loop).
- Hauptmenue: HIGHSCORES/ANLEITUNG/OPTIONEN/START/QUIT in roter FON_2-
  Schrift, Default-Auswahl START, der selektierte Eintrag BLINKT (so zeigt
  das Original den Cursor). PARTY1/PARTY2-Charaktergruppen links/rechts,
  Credits-Ticker in gelber FON_1-Schrift unter der Box.
- OPTIONEN: MUSIK EIN/AUS (wirkt auf pygame.mixer.music), EFFEKTE EIN/AUS
  (Flag fuer spaeteres Gameplay), EXIT.
- ANLEITUNG: gelbe Texttafel (Original-Joystick-Text auf Tastatur uebertragen).
- HIGHSCORES: Tabelle NAME/SCORE/ITEMS/LEV mit den Original-Defaults.
- START: Weltkarte (KARTE.PCC, kompletter Screen) -- v1 statisch, Taste
  zurueck ins Menue; Levelauswahl/Animationen folgen mit dem Gameplay.
- QUIT: Programm-Ende.

Technik (game/menu.py):
- BitmapFont-Parser fuer die Original-Fonts: FON_1/FON_1B (9x7-Zellen,
  8px-Vorschub -- nur so passen die 32-Zeichen-Zeilen der Anleitung in die
  Box) und FON_2 (16x15-Zellen, 3 Reihen). Outline-Rendering fuer die gelbe
  Schrift (Original zeichnet sie ueberall mit schwarzem Rand).
- PARTY-Sprites via make_border_bg_transparent freigestellt (papierweisser
  BG wie bei den Rauser-Assets -- ein Schwarz-Replace loescht faelschlich
  die Konturen, per Vergleichsrender nachgewiesen und verworfen).
- intro.py: Zustandsautomat Titelbild <-> Menue <-> Weltkarte; wait_for_key
  unterscheidet jetzt Fenster-X ('quit') von Taste ('key').

Verifiziert: End-to-End-Lauf des Remakes unter Xvfb mit echten XTEST-Tasten
-- Intro -> ESC -> Menue -> START -> Karte -> zurueck -> Optionen -> Anleitung
-> Highscores -> QUIT beendet sauber (rc=0); alle Screens als Screenshots
gegen die DOSBox-Referenz geprueft. python -m game --once weiterhin exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-07-24 23:12:52 +02:00
co-authored by Claude Fable 5
parent 949a7eeda2
commit c8f7d4d990
3 changed files with 491 additions and 38 deletions
+49 -31
View File
@@ -439,15 +439,19 @@ def wait_or_skip(clock, seconds, screen_update_fn, fps=30):
def wait_for_key(clock, draw_fn, fps=30):
'''Zeigt das Standbild (draw_fn(1.0)) und wartet UNENDLICH, bis der User
Leertaste oder ESC drueckt (oder das Fenster schliesst). Alt+Enter schaltet
dabei weiter Vollbild um. Die geloopte Titelmusik laeuft die ganze Zeit
weiter. Gibt immer 'quit' zurueck -- danach ist das Programm zu Ende.'''
'''Zeigt das Standbild (draw_fn(1.0)) und wartet UNENDLICH auf eine Taste.
Alt+Enter schaltet dabei weiter Vollbild um; die geloopte Titelmusik
laeuft die ganze Zeit weiter. Rueckgabe:
'quit' -- Fenster-X (Programm beenden)
'key' -- ESC oder Leertaste gedrueckt (im Original oeffnet ESC am
Titelbild das Hauptmenue -- der Aufrufer entscheidet).'''
while True:
for event in pygame.event.get():
r = _process_event(event)
if r in ('quit', 'skip'): # auf dem letzten Screen beendet beides
if event.type == pygame.QUIT:
return 'quit'
if r in ('quit', 'skip'):
return 'key'
draw_fn(1.0)
pygame.display.flip()
clock.tick(fps)
@@ -868,32 +872,29 @@ def scene_kelloggs(clock, assets, audio_paths):
return wait_or_skip(clock, 1.0, lambda _t: show_kelloggs(1.0))
def scene_title(clock, assets, hold_forever=True):
'''Titelbild -- 2x2-Quadranten-Reveal, Scanlines loesen sich von der Mitte
auf. Danach bleibt das fertige, voll-farbige Titelbild STEHEN (die
Titelmusik loopt weiter), bis der User Leertaste/ESC drueckt -- dann endet
das Programm. hold_forever=False (fuer --once/Tests) haelt stattdessen nur
kurz und kehrt zurueck, statt auf eine Taste zu warten.'''
def scene_title_reveal(clock, assets):
'''Titelbild-Animation: 2x2-Quadranten-Reveal, Scanlines loesen sich von
der Mitte auf. Gibt ('quit'|None, title_img) zurueck -- 'skip' waehrend
der Wipe springt direkt zum fertigen Bild.'''
quads = build_title_quadrants(assets)
r = wait_or_skip(clock, TITLE_WIPE_SECONDS,
lambda t: _blit_title(render_title_wipe_frame(quads, t)))
if r == 'quit':
return 'quit'
# 'skip' waehrend der Wipe-Animation -> direkt zum fertigen Titelbild
title_img = build_title_image(quads)
draw = lambda _t: _blit_title(title_img)
if hold_forever:
return wait_for_key(clock, draw)
return wait_or_skip(clock, 3.0, draw)
return ('quit' if r == 'quit' else None), title_img
def run_once(clock, assets, audio_paths, hold_title=True):
'''Spielt die komplette Intro-Sequenz EINMAL ab (keine Endlosschleife mehr).
LEERTASTE ueberspringt den jeweils aktuellen Screen (die Scene liefert dann
'skip' -> naechste Scene), ESC/Fenster-X beendet das Intro. Der letzte
Screen (Titelbild) bleibt bei hold_title=True stehen und die Titelmusik
loopt, bis Leertaste/ESC das Programm beendet. hold_title=False (--once)
haelt das Titelbild nur kurz -- fuer automatisierte/headless Laeufe.'''
def hold_title(clock, title_img):
'''Fertiges Titelbild stehen lassen (Titelmusik loopt), bis eine Taste
kommt. Rueckgabe 'quit' (Fenster-X) oder 'key' (ESC/Leertaste -> der
Aufrufer oeffnet das Hauptmenue, wie im Original ESC).'''
return wait_for_key(clock, lambda _t: _blit_title(title_img))
def run_intro(clock, assets, audio_paths):
'''Spielt die Intro-Sequenz bis einschliesslich Titelbild-Animation EINMAL
ab. LEERTASTE ueberspringt den jeweils aktuellen Screen, Fenster-X/ESC
bricht ab. Rueckgabe ('quit'|None, title_img) -- title_img ist das fertige
Titelbild fuer die anschliessende Halte-Phase.'''
scenes = [
lambda: scene_rauser(clock, assets, audio_paths),
lambda: scene_pause(clock, 0.5),
@@ -901,18 +902,17 @@ def run_once(clock, assets, audio_paths, hold_title=True):
lambda: scene_pause(clock, 0.5),
lambda: scene_kelloggs(clock, assets, audio_paths),
lambda: scene_pause(clock, 0.5),
lambda: scene_title(clock, assets, hold_forever=hold_title),
]
for scene in scenes:
r = scene()
if r == 'quit':
return True
return 'quit', None
if r == 'skip' and pygame.mixer.get_init():
# beim Ueberspringen haengende Sound-Effekte (Sting/Factor5-Brausen)
# abschneiden -- pygame.mixer.stop() betrifft nur Sound-Kanaele, die
# geloopte Titelmusik (mixer.music) laeuft ungestoert weiter.
pygame.mixer.stop()
return False
return scene_title_reveal(clock, assets)
def main():
@@ -942,10 +942,28 @@ def main():
# fertige WAV-Pfade zu, nie mehr live-render im Hauptloop.
audio_paths, quit_requested = preload_all_audio(clock, assets)
# EIN Durchlauf (keine Endlosschleife): endet am stehenden Titelbild, das
# bis Leertaste/ESC gehalten wird (Titelmusik loopt). --once haelt nur kurz.
# Ablauf (wie Original, per DOSBox-Referenz): Intro -> Titelbild steht
# (Musik loopt) -> Taste oeffnet das Hauptmenue -> START=Weltkarte,
# QUIT=Ende, ESC=zurueck zum Titelbild. --once: nach der Titelbild-
# Animation kurz halten und beenden (fuer Tests/headless).
if not quit_requested:
run_once(clock, assets, audio_paths, hold_title=not once)
r, title_img = run_intro(clock, assets, audio_paths)
if r != 'quit' and title_img is not None:
if once:
wait_or_skip(clock, 3.0, lambda _t: _blit_title(title_img))
else:
from . import menu as menu_mod
m = menu_mod.MenuAssets(assets)
state = 'title'
while state not in ('quit',):
if state == 'title':
state = 'menu' if hold_title(clock, title_img) == 'key' else 'quit'
elif state == 'menu':
r = menu_mod.run_menu(clock, m)
state = {'start': 'map', 'title': 'title'}.get(r, 'quit')
elif state == 'map':
r = menu_mod.run_worldmap(clock, m)
state = 'menu' if r == 'menu' else 'quit'
if pygame.mixer.get_init():
pygame.mixer.music.stop()
+427
View File
@@ -0,0 +1,427 @@
"""Hauptmenue, Optionsmenue, Anleitung, Highscore-Tafel und Weltkarte.
Alles gegen echte DOSBox-Referenzaufnahmen (24.07.2026, Xvfb + XTEST-Tasten)
nachgebaut -- Ablauf im Original:
Titelbild --ESC--> Hauptmenue (faedet ein) mit Eintraegen
HIGHSCORES / ANLEITUNG / OPTIONEN / START / QUIT (rote FON_2-Schrift,
Default-Auswahl START). Der SELEKTIERTE Eintrag BLINKT (das ist der
Cursor -- kein Pfeil o.ae.). Links/rechts der Box winken die Charakter-
Gruppen PARTY1/PARTY2. Unter der Box laeuft ein Credits-Ticker in
kleiner gelber Schrift (FON_1/FON_1B) mit wechselnden Texten
("FREEGAMES #3", "REALISATION RAUSER ADVERTAINMENT GMBH REUTLINGEN",
"PRODUZIERT VON FACTOR 5", "ANIMATIONEN UND ILLUSTRATIONEN HARTWIG
NIEDERGASSEL").
OPTIONEN -> eigene Tafel: MUSIK EIN/AUS, EFFEKTE EIN/AUS, EXIT.
ANLEITUNG -> gelbe Text-Tafel (Joystick-Steuerung + Faehigkeiten).
HIGHSCORES -> Tabelle NAME/SCORE/ITEMS/LEV mit den Original-Defaults
(MATTHIAS 100000 ... BORIS 2500).
START -> Weltkarte (KARTE.PCC -- kompletter Screen inkl. Rahmen und
Charakteren; Animationen KARTE0-9 spaeter).
Screens sind 320x200 (wie die Original-Videomodi) und werden wie die
Intro-Screens letterboxed auf 320x240 -> Fenster skaliert.
Fonts (Zellen-Grids per Spaltenanalyse der PCC-Assets bestimmt):
FON_1 (320x7): 9x7-Zellen, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + AE OE UE ss
FON_1B (320x7): 9x7-Zellen, '1234567890;:!"%/()=?,.-' + Block + '#'
FON_2 (320x45): 16x15-Zellen, 3 Zeilen:
'ABCDEFGHIJKLMNOPQRST' / 'UVWXYZ.,:!?-' / '1234567890'
"""
import sys
import time
import pygame
from . import intro
# ---------------------------------------------------------------------------
# Bitmap-Fonts aus den FON_*.PCC-Assets
# ---------------------------------------------------------------------------
class BitmapFont:
'''Fixed-Cell-Bitmapfont aus einer PCC-Surface. Schwarz (0,0,0) ist
Hintergrund und wird transparent. mapping: dict Zeichen -> (col, row).'''
def __init__(self, surface, cell_w, cell_h, mapping, spacing=None):
surface = surface.convert_alpha()
# Schwarz -> transparent (PCC-Hintergrund)
arr = pygame.PixelArray(surface)
arr.replace((0, 0, 0, 255), (0, 0, 0, 0))
del arr
self.cell_w, self.cell_h = cell_w, cell_h
self.spacing = spacing if spacing is not None else cell_w
self.glyphs = {}
for ch, (col, row) in mapping.items():
rect = pygame.Rect(col * cell_w, row * cell_h, cell_w, cell_h)
if rect.right <= surface.get_width() and rect.bottom <= surface.get_height():
self.glyphs[ch] = surface.subsurface(rect)
def measure(self, text):
return len(text) * self.spacing, self.cell_h
def render(self, text, outline=None):
'''Rendert `text` auf eine neue Surface (Alpha). outline=(r,g,b):
1px-Umrandung in dieser Farbe (wie die gelbe Original-Schrift, die
ueberall mit schwarzem Rand gezeichnet wird).'''
w, h = self.measure(text)
pad = 1 if outline else 0
surf = pygame.Surface((w + 2 * pad, h + 2 * pad), pygame.SRCALPHA)
if outline:
# Umriss: Text 4x versetzt in outline-Farbe, dann normal drueber
mask = pygame.Surface((w, h), pygame.SRCALPHA)
x = 0
for ch in text.upper():
g = self.glyphs.get(ch)
if g:
mask.blit(g, (x, 0))
x += self.spacing
tinted = mask.copy()
tint = pygame.Surface(mask.get_size(), pygame.SRCALPHA)
tint.fill(outline + (255,))
tinted.blit(tint, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
# RGB durch outline-Farbe ersetzen, Alpha der Glyphen behalten
for dx, dy in ((0, 1), (2, 1), (1, 0), (1, 2), (0, 0), (2, 2), (0, 2), (2, 0)):
surf.blit(tinted, (dx, dy))
x = pad
for ch in text.upper():
g = self.glyphs.get(ch)
if g:
surf.blit(g, (x, pad))
x += self.spacing
return surf
def _mapping_from_rows(rows):
m = {}
for row, chars in enumerate(rows):
for col, ch in enumerate(chars):
m[ch] = (col, row)
return m
class MenuAssets:
'''Laedt und cached alle Menue-relevanten Surfaces + Fonts.'''
def __init__(self, assets):
self.assets = assets
self.menu_bg = assets.pcc_surface('MENU') # 320x200 Komplett-Screen
self.karte = assets.pcc_surface('KARTE') # 320x200 Weltkarte
# PARTY-Sprites haben (wie die Rauser-Assets) einen PAPIERWEISSEN
# Hintergrund und schwarze Konturen -- NICHT schwarz freistellen
# (das loescht die Outlines!), sondern die vom Rand aus
# zusammenhaengende weisse Flaeche per Flood-Fill transparent machen.
self.party1 = intro.make_border_bg_transparent(assets.pcc_surface('PARTY1'))
self.party2 = intro.make_border_bg_transparent(assets.pcc_surface('PARTY2'))
# kleine gelbe Schrift: FON_1 (Buchstaben) + FON_1B (Ziffern/Zeichen)
# -- ein gemeinsamer Font, beide Surfaces uebereinandergelegt waere
# falsch (gleiche Zeilenlage), daher zwei Fonts mit gleichem Grid und
# ein kombinierter render-Wrapper.
# Zellenraster ist 9px, die Engine rendert aber mit 8px Vorschub --
# nur so passen die 32-Zeichen-Zeilen der Anleitung in die Box
# (32 x 8 = 256px bei ~264px Box-Innenbreite, vgl. Referenz).
f1 = BitmapFont(assets.pcc_surface('FON_1'), 9, 7,
_mapping_from_rows(['ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc4\xd6\xdc\xdf']),
spacing=8)
f1b = BitmapFont(assets.pcc_surface('FON_1B'), 9, 7,
_mapping_from_rows(['1234567890;:!"%/()=?,.-\x7f#']),
spacing=8)
# Glyphen zusammenfuehren (disjunkte Zeichensaetze, gleiches Grid)
f1.glyphs.update(f1b.glyphs)
self.font_small = f1
self.font_big = BitmapFont(assets.pcc_surface('FON_2'), 16, 15,
_mapping_from_rows(['ABCDEFGHIJKLMNOPQRST',
'UVWXYZ.,:!?-',
'1234567890']))
@staticmethod
def _transparent(surface):
s = surface.convert_alpha()
arr = pygame.PixelArray(s)
arr.replace((0, 0, 0, 255), (0, 0, 0, 0))
del arr
return s
YELLOW_OUTLINE = (0, 0, 0)
TICKER_TEXTS = [
'FREEGAMES #3',
'REALISATION RAUSER ADVERTAINMENT GMBH REUTLINGEN',
'PRODUZIERT VON FACTOR 5',
'ANIMATIONEN UND ILLUSTRATIONEN HARTWIG NIEDERGASSEL',
]
TICKER_SECONDS = 4.0 # Anzeigedauer pro Credits-Text
BLINK_SECONDS = 0.35 # Blinkrhythmus des selektierten Eintrags
MENU_ITEMS = ['HIGHSCORES', 'ANLEITUNG', 'OPTIONEN', 'START', 'QUIT']
MENU_START_INDEX = 3 # Default-Auswahl im Original: START
# Layout (320x200-Koordinaten, aus den DOSBox-Referenzframes vermessen)
MENU_ITEM_Y0 = 44 # y des ersten Eintrags (HIGHSCORES)
MENU_ITEM_DY = 15 # Zeilenabstand
MENU_QUIT_GAP = 7 # QUIT ist etwas abgesetzt
PARTY1_POS = (34, 80)
PARTY2_POS = (218, 84)
TICKER_Y = 172 # Ticker-Zeile unterhalb der Box
def _draw_ticker(canvas, m, t):
idx = int(t / TICKER_SECONDS) % len(TICKER_TEXTS)
text = TICKER_TEXTS[idx]
# lange Texte auf 2 Zeilen brechen (wie im Original: "REALISATION RAUSER /
# ADVERTAINMENT GMBH REUTLINGEN" stand zweizeilig)
max_chars = 34
lines = []
if len(text) > max_chars:
words, cur = text.split(), ''
for wrd in words:
if len(cur) + len(wrd) + 1 > max_chars and cur:
lines.append(cur)
cur = wrd
else:
cur = (cur + ' ' + wrd).strip()
if cur:
lines.append(cur)
else:
lines = [text]
y = TICKER_Y
for line in lines:
surf = m.font_small.render(line, outline=YELLOW_OUTLINE)
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
y += 10
def _menu_canvas(m, items, selected, blink_on, t, hidden_indices=()):
'''Baut den kompletten Menue-Screen (320x200): Box, Parties, Eintraege
(selektierter blinkt), Ticker.'''
canvas = m.menu_bg.copy()
canvas.blit(m.party1, PARTY1_POS)
canvas.blit(m.party2, PARTY2_POS)
y = MENU_ITEM_Y0
for i, item in enumerate(items):
if i == len(items) - 1:
y += MENU_QUIT_GAP # letzter Eintrag (QUIT/EXIT) abgesetzt
visible = (i != selected) or blink_on
if i in hidden_indices:
visible = False
if visible:
surf = m.font_big.render(item)
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
y += MENU_ITEM_DY
_draw_ticker(canvas, m, t)
return canvas
def _text_panel(m, lines, y0=58, line_h=10):
'''Tafel-Screen: Menue-Box mit gelbem Text gefuellt (Anleitung/Copyright-
Stil). lines: Liste von (text, x) -- x=None zentriert.'''
canvas = m.menu_bg.copy()
y = y0
for entry in lines:
text, x = entry if isinstance(entry, tuple) else (entry, None)
if text:
surf = m.font_small.render(text, outline=YELLOW_OUTLINE)
if x is None:
x = (320 - surf.get_width()) // 2
canvas.blit(surf, (x, y))
y += line_h
return canvas
def _present(canvas):
intro._blit_centered(intro.build_letterboxed(canvas))
def _menu_loop(clock, draw_fn, on_key):
'''Generische Menue-Event-Schleife. draw_fn(t) zeichnet, on_key(event)
liefert None (weiter) oder ein Ergebnis (Schleife endet). Alt+Enter und
Fenster-X werden hier zentral behandelt.'''
start = time.monotonic()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return 'quit'
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN and (event.mod & pygame.KMOD_ALT):
intro.toggle_fullscreen()
continue
r = on_key(event)
if r is not None:
return r
draw_fn(time.monotonic() - start)
pygame.display.flip()
clock.tick(30)
def run_menu(clock, m):
'''Hauptmenue. Rueckgabe: 'start' | 'quit' | 'title' (ESC -> zurueck zum
Titelbild).'''
state = {'sel': MENU_START_INDEX}
def draw(t):
blink_on = int(t / BLINK_SECONDS) % 2 == 0
_present(_menu_canvas(m, MENU_ITEMS, state['sel'], blink_on, t))
def key(event):
if event.key == pygame.K_ESCAPE:
return 'title'
if event.key in (pygame.K_UP, pygame.K_LEFT):
state['sel'] = (state['sel'] - 1) % len(MENU_ITEMS)
elif event.key in (pygame.K_DOWN, pygame.K_RIGHT):
state['sel'] = (state['sel'] + 1) % len(MENU_ITEMS)
elif event.key in (pygame.K_RETURN, pygame.K_SPACE):
choice = MENU_ITEMS[state['sel']]
if choice == 'START':
return 'start'
if choice == 'QUIT':
return 'quit'
if choice == 'OPTIONEN':
r = run_options(clock, m)
if r == 'quit':
return 'quit'
elif choice == 'ANLEITUNG':
r = run_anleitung(clock, m)
if r == 'quit':
return 'quit'
elif choice == 'HIGHSCORES':
r = run_highscores(clock, m)
if r == 'quit':
return 'quit'
return None
return _menu_loop(clock, draw, key)
def run_options(clock, m):
'''Optionsmenue: MUSIK EIN/AUS, EFFEKTE EIN/AUS, EXIT. Rueckgabe 'back'
oder 'quit'. Musik-Toggle wirkt sofort auf pygame.mixer.music.'''
state = {'sel': 2, 'musik': True, 'effekte': True}
def items():
return [f'MUSIK {"EIN" if state["musik"] else "AUS"}',
f'EFFEKTE {"EIN" if state["effekte"] else "AUS"}',
'EXIT']
def draw(t):
blink_on = int(t / BLINK_SECONDS) % 2 == 0
canvas = m.menu_bg.copy()
canvas.blit(m.party1, PARTY1_POS)
canvas.blit(m.party2, PARTY2_POS)
its = items()
y = 52
for i, item in enumerate(its):
if i == len(its) - 1:
y += 18 # EXIT abgesetzt (wie Original-Referenz)
if (i != state['sel']) or blink_on:
surf = m.font_big.render(item)
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
y += MENU_ITEM_DY
_draw_ticker(canvas, m, t)
_present(canvas)
def key(event):
if event.key == pygame.K_ESCAPE:
return 'back'
if event.key in (pygame.K_UP, pygame.K_LEFT):
state['sel'] = (state['sel'] - 1) % 3
elif event.key in (pygame.K_DOWN, pygame.K_RIGHT):
state['sel'] = (state['sel'] + 1) % 3
elif event.key in (pygame.K_RETURN, pygame.K_SPACE):
if state['sel'] == 0:
state['musik'] = not state['musik']
if pygame.mixer.get_init():
if state['musik']:
pygame.mixer.music.unpause()
else:
pygame.mixer.music.pause()
elif state['sel'] == 1:
state['effekte'] = not state['effekte']
# Effekte-Flag greift, sobald es im Gameplay Soundeffekte gibt
else:
return 'back'
return None
return _menu_loop(clock, draw, key)
ANLEITUNG_LINES = [
('STEUERUNG:', 30),
('MIT LINKS/RECHTS STEUERST DU DIE', 30),
('FIGUR. MIT LEERTASTE ODER PFEIL', 30),
('HOCH KANN DIE FIGUR SPRINGEN,', 30),
('MIT PFEIL UNTEN KANN SICH DIE', 30),
('FIGUR DUCKEN.', 30),
('MIT ALT AKTIVIERST DU DIE', 30),
('F\xc4HIGKEITEN DER FIGUREN...', 30),
'',
('TONY: RENNEN', 30),
('SMACKS: WASSERBLASEN', 30),
('COCO: HANGELN UND KLETTERN', 30),
('TOUCAN: FLIEGEN', 30),
]
# Default-Highscores aus der Original-Referenzaufnahme
DEFAULT_HIGHSCORES = [
('MATTHIAS', 100000, 5000, 20),
('JAN', 80000, 4000, 17),
('MARC', 60000, 3000, 14),
('MICHAEL', 40000, 2000, 12),
('URTE', 20000, 1000, 10),
('PETER', 10000, 700, 8),
('ANTONY', 5000, 400, 6),
('BORIS', 2500, 200, 4),
]
def run_anleitung(clock, m):
'''Anleitungstafel (gelber Text). Beliebige Taste/ESC -> zurueck.
Text an die Original-Joystick-Seite angelehnt, auf Tastatur uebertragen
(das Remake hat keinen Joystick-Support).'''
canvas = _text_panel(m, ANLEITUNG_LINES, y0=36, line_h=8)
def draw(_t):
_present(canvas)
def key(event):
return 'back' # jede Taste schliesst die Tafel
return _menu_loop(clock, draw, key)
def run_highscores(clock, m):
'''Highscore-Tafel (Tabelle NAME/SCORE/ITEMS/LEV). Beliebige Taste ->
zurueck. Ticker laeuft weiter (wie im Original).'''
def draw(t):
lines = [('NAME SCORE ITEMS LEV', 55)]
for i, (name, score, items_, lev) in enumerate(DEFAULT_HIGHSCORES, 1):
lines.append((f'{i}. {name:<9}{score:>7}{items_:>7}{lev:>5}', 30))
canvas = m.menu_bg.copy()
y = 44 # eng gepackt, damit alle 9 Zeilen in der Box (bis ~y=145) bleiben
for text, x in lines:
surf = m.font_small.render(text, outline=YELLOW_OUTLINE)
canvas.blit(surf, (x, y))
y += 10
_draw_ticker(canvas, m, t)
_present(canvas)
def key(event):
return 'back'
return _menu_loop(clock, draw, key)
def run_worldmap(clock, m):
'''Weltkarte (KARTE.PCC -- kompletter 320x200-Screen inkl. Rahmen und
Charakteren). v1: statisch; die kleinen Animationsframes KARTE0-9 und die
Levelauswahl folgen mit dem Gameplay. ESC/Taste -> zurueck ins Menue.'''
canvas = m.karte
def draw(_t):
_present(canvas)
def key(event):
return 'menu'
return _menu_loop(clock, draw, key)