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:
co-authored by
Claude Fable 5
parent
949a7eeda2
commit
c8f7d4d990
+49
-31
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user