Anleitung mit Hervorhebungen, Rückgängig, Kapitel 2, Herausforderungen, Fallen, Wiederverbinden, CI

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
Team3
2026-09-16 13:27:41 +02:00
parent 536a523f50
commit 46c3fae233
12 changed files with 462 additions and 70 deletions

View File

@@ -154,8 +154,8 @@ func _ready() -> void:
add_child(main)
await frames(3)
for t in [test_menu_und_regeln, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_hotseat,
test_netzwerk, test_kampf.bind(2, 3), test_kampf_abbrechen]:
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_herausforderung,
test_hotseat, test_netzwerk, test_kampf.bind(2, 3), test_kampf_abbrechen]:
await ensure_menu()
await t.call()
for suffix in ["", ".bak", ".corrupt"]:
@@ -358,6 +358,17 @@ func test_gegnerauswahl() -> void:
await give_up()
## Kampf verlassen: nach Spielende über den Ergebnisdialog, sonst per Aufgeben.
func leave_battle() -> void:
var scr = screen()
if scr is BattleScreen and scr.game != null and scr.game.winner != "":
await frames(2)
await click(find_button("Zurück zum Menü"))
check(screen() is MenuScreen, "Ergebnisdialog führt ins Menü")
else:
await give_up()
func give_up() -> void:
var scr = screen()
await clear_mulligan(scr)
@@ -390,6 +401,11 @@ func test_einstellungen() -> void:
func test_erfolge() -> void:
await click(find_button("Erfolge"))
check(screen() is AchievementsScreen, "Erfolge geöffnet")
var csv := "user://statistik.csv"
if FileAccess.file_exists(csv):
DirAccess.remove_absolute(ProjectSettings.globalize_path(csv))
await click(find_button("Statistik exportieren"))
check(FileAccess.file_exists(csv) and "Gespeichert" in screen().export_label.text, "Statistik exportiert")
await shot("14_erfolge")
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
@@ -427,6 +443,26 @@ func test_werkstatt() -> void:
check(screen() is MenuScreen, "zurück im Menü")
func test_herausforderung() -> void:
await click(find_button("Herausforderung"))
var scr = screen()
check(scr is ChallengeScreen, "Herausforderung geöffnet")
var ch := Challenge.current()
var allowed := Challenge.deck_allowed(ch, main.profile.deck_cards()) == ""
check(find_button("Antreten").disabled == not allowed, "Antreten je nach Deck")
await shot("28_herausforderung")
if allowed:
await click(find_button("Antreten"))
var b = screen()
check(b is BattleScreen and b.challenge == ch and b.game.opponents.size() == ch.n_opp, "Herausforderungskampf")
var everyone: String = ch.rules.get("everyone_perk", "")
check(everyone == "" or b.game.players.all(func(p): return everyone in p.perks), "Regel gilt für alle")
await give_up()
else:
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
func test_hotseat() -> void:
await click(find_button("Lokal zu mehreren"))
var scr = screen()
@@ -458,7 +494,7 @@ func test_hotseat() -> void:
await clear_mulligan(b)
check(b.hero_panels.size() == 3 and not b.hero_panels.has(cur), "drei andere am Tisch")
await shot("19_hotseat_kampf")
await give_up()
await leave_battle()
func test_sprache() -> void:
@@ -469,10 +505,12 @@ func test_sprache() -> void:
await frames(2)
check(TranslationServer.get_locale().begins_with("en") and main.profile.settings.language == "en", "Sprache auf Englisch")
check(TranslationServer.translate("Zurück") == "Back" and TranslationServer.translate("Einzelspieler") == "Single player", "Übersetzung geladen")
check(TranslationServer.translate("Zug %d · Du bist dran") == "Turn %d · Your turn" and TranslationServer.translate("Nicht genug Energie") == "Not enough energy", "dynamische Texte übersetzt")
check(find_button("Back") != null, "Knöpfe übersetzt")
await shot("26_englisch")
await click(find_button("Back"))
check(find_button("Single player") != null and find_button("Settings") != null, "Menü auf Englisch")
check(screen().coins_label.text.begins_with("Coins:"), "zusammengesetzte Texte übersetzt")
await click(find_button("Settings"))
var scr2 = screen()
scr2.language.select(0)
@@ -506,14 +544,36 @@ func test_anleitung() -> void:
if g.human.hand[i].is_creature() and g.human.hand[i].cost == 1:
idx = i
break
check(idx < 0 or scr.highlight_layer.get_child_count() >= 1, "spielbare Wesen hervorgehoben")
if idx >= 0:
var played_id: String = g.human.hand[idx].id
await key(KEY_1 + idx)
check(g.human.board.size() == 1, "Wesen per Taste gespielt")
check(scr.tutorial_step == 1, "Schritt 2 nach dem Ausspielen")
check(scr.highlight_layer.get_child_count() == 1 and scr.highlight_layer.get_child(0).get_rect().intersects(scr.b_end.get_rect()), "Zug-beenden-Knopf hervorgehoben")
# Rückgängig: Wesen zurück auf die Hand
check(scr.b_undo.visible, "Rückgängig angeboten")
var energy: int = g.human.energy
await click(scr.b_undo)
g = scr.game
check(g.human.board.is_empty() and g.human.hand.any(func(c): return c.id == played_id) and g.human.energy == energy + 1, "Rückgängig stellt Hand und Energie her")
check(not scr.b_undo.visible and g.log.back() == "Rückgängig.", "Rückgängig verbraucht")
await key(KEY_1 + idx)
check(g.human.board.size() == 1, "erneut gespielt")
await shot("22_anleitung")
scr.ai_delay = 0.75
await key(KEY_SPACE)
check(g.current() != g.human or g.turn >= 2, "Zug per Leertaste beendet")
await wait_for_human(scr)
await frames(2)
check(scr.b_skip.visible and not scr.b_end.visible, "Überspringen-Knopf während des Gegnerzugs")
await click(scr.b_skip)
var w := 0
while g.current() != g.human and g.winner == "" and w < 120:
await frames(1)
w += 1
check(g.current() == g.human and w < 60, "Gegnerzug übersprungen (%d Bilder)" % w)
scr.ai_delay = 0.0
await clear_mulligan(scr)
check(scr.tutorial_step >= 2 or idx < 0, "Schritt 3 im zweiten Zug")
var step_before: int = scr.tutorial_step
await click(scr.b_tut_next)
@@ -532,6 +592,8 @@ func test_netzwerk() -> void:
check(scr is LobbyScreen, "Lobby geöffnet")
await click(find_button("Spiel eröffnen"))
check(main.net.is_host and main.net.connected, "Spiel eröffnet")
if FileAccess.file_exists("user://bot_quit"):
DirAccess.remove_absolute(ProjectSettings.globalize_path("user://bot_quit"))
var args := ["--headless", "--path", ProjectSettings.globalize_path("res://"), "tests/net_bot.tscn", "--", "--ip=127.0.0.1", "--name=Bot"]
var pid := OS.create_process(OS.get_executable_path(), args)
check(pid > 0, "Bot-Prozess gestartet")
@@ -559,19 +621,46 @@ func test_netzwerk() -> void:
check(b.game.turn >= 3 and b.game.winner == "", "mehrere Runden gespielt, Partie läuft (Zug %d)" % b.game.turn)
check(b.game.log.any(func(l): return l.begins_with("Bot spielt") or l.begins_with("Bot behält") or l.begins_with("Bot tauscht")), "Bot hat gehandelt")
await shot("24_netzwerk_kampf")
OS.kill(pid)
# Bot verlässt geordnet: Platz bleibt frei, Züge werden übersprungen
var quit_file := "user://bot_quit"
var f := FileAccess.open(quit_file, FileAccess.WRITE)
f.store_string("x")
f.close()
var w3 := 0
while b.game.winner == "" and w3 < 1500:
while main.net.waiting.is_empty() and w3 < 600:
await frames(1)
w3 += 1
if b.game.winner != "":
check(b.game.winner == "human" and b.game.log.any(func(l): return "verlassen" in l), "Trennung des Bots zählt als Aufgabe")
await frames(2)
check(b.overlay.visible, "Spielende angezeigt")
await click(find_button("Zurück zum Menü"))
else:
print("HINWEIS: Bot-Trennung nach 25 s noch nicht erkannt, Aufgeben stattdessen")
await give_up()
check(main.net.waiting.has("Bot") and b.game.log.any(func(l): return "Verbindung verloren" in l), "Platz wird freigehalten")
var turn_before: int = b.game.turn
for i in 2:
await clear_mulligan(b)
if b.game.current() == b.perspective and b.game.winner == "":
await click(b.b_end)
var w4 := 0
while b.game.current() != b.perspective and b.game.winner == "" and w4 < 300:
await frames(1)
w4 += 1
check(b.game.turn >= turn_before + 1 and b.game.log.any(func(l): return "übersprungen" in l), "Züge des fehlenden Spielers werden übersprungen")
DirAccess.remove_absolute(ProjectSettings.globalize_path(quit_file))
await frames(5)
var pid2 := OS.create_process(OS.get_executable_path(), args)
var w5 := 0
while not main.net.waiting.is_empty() and w5 < 900:
await frames(1)
w5 += 1
check(main.net.waiting.is_empty() and main.net.seats.size() == 2 and b.game.log.any(func(l): return l == "Bot ist zurück."), "Bot wieder verbunden, Platz zurück")
var turn2: int = b.game.turn
for i in 2:
await clear_mulligan(b)
if b.game.current() == b.perspective and b.game.winner == "":
await click(b.b_end)
var w6 := 0
while b.game.current() != b.perspective and b.game.winner == "" and w6 < 900:
await frames(1)
w6 += 1
check(b.game.turn >= turn2 + 1, "Partie läuft nach der Rückkehr weiter")
OS.kill(pid2)
await leave_battle()
check(screen() is MenuScreen and not main.net.connected, "Netzwerk getrennt, zurück im Menü")
@@ -581,12 +670,31 @@ func test_kampagne() -> void:
check(scr is CampaignScreen, "Kampagne geöffnet")
await shot("15_kampagne")
check(find_button("Gesperrt") != null and find_button("Kämpfen") != null, "Stufen gesperrt und offen")
check(find_button("Kapitel 2").disabled and scr.perk_box == null, "Kapitel 2 und Merkmal noch gesperrt")
await click(find_button("Kämpfen"))
var battle = screen()
check(battle is BattleScreen and battle.stage == 0, "Kampagnenkampf gestartet")
check(battle.game.opponents.size() == 1 and battle.game.opponents[0].name == "Fenn", "Fenn ist der Gegner")
check(battle.game.opponents[0].perks == ["feuer_angriff"] and battle.game.log.any(func(l): return l.begins_with("Fenn: Feuerwesen")), "Boss-Merkmal aktiv")
await shot("16_kampagne_kampf")
await give_up()
# Kapitel 1 als abgeschlossen setzen: Kapitel 2 und Merkmalwahl
main.profile.campaign_stage = Campaign.STAGES.size()
await click(find_button("Kampagne"))
var scr2 = screen()
check(scr2.chapter == 2 and scr2.perk_box != null, "Kapitel 2 offen, Merkmal wählbar")
scr2.perk_box.select(1)
scr2.perk_box.item_selected.emit(1)
check(main.profile.perk == Game.PERKS.keys()[0], "Merkmal gewählt")
await shot("27_kampagne_kapitel2")
await click(find_button("Kämpfen"))
var b2 = screen()
check(b2 is BattleScreen and b2.stage == 10 and b2.game.opponents[0].perks.size() == 2, "Kapitel-2-Boss mit zwei Merkmalen")
check(b2.game.human.perks == [main.profile.perk], "eigenes Merkmal im Kampf")
await give_up()
main.profile.campaign_stage = 0
main.profile.perk = ""
main.save()
func test_kampf(n_opp: int, shot_turn: int) -> void: