Anleitung 2, Gamepad, große Schrift, Profil-Reset, Würfel-Limit, Tipp nach Spielverlauf, KI-Zeitbudget, Deck-Warnung, Gischtläufer, Kampfbildschirm aufgeteilt

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 16:27:59 +02:00
parent 0dbc98b456
commit 4246d7e970
22 changed files with 728 additions and 164 deletions

View File

@@ -110,12 +110,13 @@ func _initialize() -> void:
test_starter_gegen_passende_gegner()
test_kurve_statistik_vorschlag_verbesserung()
test_wasser_antworten()
test_ki_zeitbudget()
print("%d Prüfungen bestanden, %d fehlgeschlagen" % [passed, failed])
quit(1 if failed > 0 else 0)
func test_kartenpool() -> void:
check(Cards.all().size() == 109, "109 Karten")
check(Cards.all().size() == 110, "110 Karten")
for key in Cards.COMBOS:
var found: bool = Cards.all().any(func(c): return c.is_combo() and Cards.combo_key(c.elements) == key)
check(found, "Kombo vorhanden: " + key)
@@ -1282,8 +1283,18 @@ func test_kurve_statistik_vorschlag_verbesserung() -> void:
var mono := starter
check(p.suggest_difficulty() == "", "kein Vorschlag am Anfang")
for i in 3:
p.record_battle(true, 1, mono)
check(p.suggest_difficulty() == "schwer" and p.notices.any(func(n): return n.begins_with("Tipp: 3 Siege")), "drei Siege: Schwer vorschlagen")
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 4)
check(p.suggest_difficulty() == "", "drei knappe Siege: noch kein Vorschlag")
p.records.streak = 0
p.records.margins = []
for i in 3:
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 20)
check(p.suggest_difficulty() == "schwer" and p.notices.any(func(n): return n.begins_with("Tipp: 3 Siege")), "drei deutliche Siege: Schwer vorschlagen")
p.records.streak = 0
p.records.margins = []
for i in 5:
p.record_battle(true, 1, mono, -1, [], 6, {}, 1.0, 2)
check(p.suggest_difficulty() == "schwer" and p.records.margins.size() == 5, "fünf Siege egal wie knapp")
p.settings.difficulty = "schwer"
p.take_notices()
for i in 3:
@@ -1319,3 +1330,27 @@ func test_wasser_antworten() -> void:
g.play_card(h, C("w_strudel"), t)
check(o.board.is_empty() and C("e_troll") in o.hand and h.hand.size() == hand, "Strudel: zurück auf die Hand, eine Karte gezogen")
check(C("w_ebbe").rarity == "gewöhnlich" and Profile.starter().collection.has("w_ebbe"), "Ebbe in der Startsammlung")
func test_ki_zeitbudget() -> void:
var r := RandomNumberGenerator.new()
r.seed = 12
var g := Game.new_battle(Profile.starter().deck_cards(), 5, r)
var h: Game.Player = g.players[0]
h.mulligan_done = true
h.energy = 10
h.max_energy = 10
for i in 8:
h.hand.append(Cards.by_id("f_funke"))
for p in g.players:
for i in 3:
put(p, "e_troll")
AI.time_budget_ms = 1
var t0 := Time.get_ticks_msec()
var act := AI.choose_action(g, h)
var ms := Time.get_ticks_msec() - t0
AI.time_budget_ms = 150
check(act.type in ["play", "attack", "end"] and ms < 80, "Zeitbudget greift (%d ms)" % ms)
check(Cards.by_id("wl_gischt").keywords == ["Flink"] and Cards.by_id("wl_gischt").combo() == "Sturm", "Gischtläufer für Sturm")
var v := Profile.from_dict(JSON.parse_string(JSON.stringify(Profile.starter().to_dict())))
check(v.settings.has("big_font") and not v.tutorial2_done and v.records.margins is Array, "Version 5 mit neuen Feldern")

View File

@@ -74,6 +74,14 @@ func key(code: int) -> void:
await frames(2)
func pad(button: int) -> void:
var ev := InputEventJoypadButton.new()
ev.button_index = button
ev.pressed = true
get_viewport().push_input(ev)
await frames(2)
## Mulligan-Maske wegklicken (Hand behalten), falls sie offen ist.
func clear_mulligan(scr) -> void:
if scr.mulligan_box.visible:
@@ -155,9 +163,10 @@ func _ready() -> void:
await frames(3)
var fast := OS.get_environment("DINGER_FAST") != ""
var suite := [test_menu_und_regeln, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_herausforderung]
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2, test_herausforderung]
if not fast:
suite += [test_hotseat, test_netzwerk, test_kampf.bind(2, 3), test_kampf_abbrechen]
suite.append(test_profil_zuruecksetzen)
for t in suite:
await ensure_menu()
await t.call()
@@ -290,7 +299,14 @@ func test_deckbau() -> void:
await click(find_button("Auto-Ausfüllen"))
check(scr.deck.size() == 20 and main.profile.deck_valid(2)[0], "Slot 3 automatisch gefüllt und spielbar")
await shot("25_deck_slots")
await click(scr.deck_list.get_children()[0]) # ungespeicherte Änderung: eine Karte entfernt
check(scr.dirty(), "Deck geändert")
await click(find_button("Zurück"))
check(scr.confirm.visible and screen() == scr, "Warnung bei ungespeicherten Änderungen")
await click(find_button("Weiter bearbeiten"))
check(not scr.confirm.visible, "weiter bearbeiten")
await click(find_button("Zurück"))
await click(find_button("Verwerfen"))
check(screen() is MenuScreen and main.profile.deck.size() == 20, "Änderungen verworfen")
@@ -360,6 +376,9 @@ func test_gegnerauswahl() -> void:
check(sel is OpponentSelectScreen, "Gegnerauswahl geöffnet")
check("Deckstärke" in sel.rating_label.text, "Deckstärke angezeigt")
check(sel.preview_labels[3].text.count("(") == 3 and sel.previews[3].size() == 3, "Vorschau zeigt drei Gegner mit Stärke")
for i in 3:
await click(sel.b_reroll)
check(sel.rerolls_left == 0 and sel.b_reroll.disabled, "höchstens dreimal neu würfeln")
await click(find_button("Schwer"))
check(main.profile.settings.difficulty == "schwer" and Profile.load_or_new(SAVE).settings.difficulty == "schwer", "Schwer gewählt und gespeichert")
var previewed: Array = sel.previews[1].map(func(o): return o.title)
@@ -382,11 +401,13 @@ func test_gegnerauswahl() -> void:
await click(find_button("Einzelspieler"))
await click(find_button("Normal"))
main.profile.records.streak = 3
main.profile.records.margins = [20, 22, 18]
screen().refresh()
check(screen().b_tip.visible and "Schwer" in screen().tip_label.text, "Vorschlag nach drei Siegen")
check(screen().b_tip.visible and "Schwer" in screen().tip_label.text, "Vorschlag nach drei deutlichen Siegen")
await click(find_button("Übernehmen"))
check(main.profile.settings.difficulty == "schwer", "Vorschlag übernommen")
main.profile.records.streak = 0
main.profile.records.margins = []
await click(find_button("Normal"))
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
@@ -434,7 +455,15 @@ func test_einstellungen() -> void:
check(main.profile.settings.animations == false, "Animationen abschaltbar")
check(is_equal_approx(Profile.load_or_new(SAVE).settings.volume, 0.3), "Einstellungen auf Platte")
scr.anim.button_pressed = true
scr.big_font.button_pressed = true
await frames(2)
check(main.profile.settings.big_font and Style.font_scale > 1.0 and screen() is SettingsScreen, "große Schrift aktiv")
screen().big_font.button_pressed = false
await frames(2)
check(is_equal_approx(Style.font_scale, 1.0), "normale Schrift")
var scr_b = screen()
await shot("13_einstellungen")
scr = scr_b
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
@@ -484,6 +513,45 @@ func test_werkstatt() -> void:
check(screen() is MenuScreen, "zurück im Menü")
func test_anleitung2() -> void:
check(main.profile.tutorial_done and not main.profile.tutorial2_done, "Teil 1 erledigt, Teil 2 offen")
check(find_button("Anleitung 2: Fallen") != null, "Menü bietet Teil 2 an")
await click(find_button("Anleitung 2: Fallen"))
var scr = screen()
check(scr is BattleScreen and scr.tutorial and scr.tutorial_part == 2, "Anleitung 2 gestartet")
scr.ai_delay = 0.0
var g: Game = scr.game
check(g.opponents[0].name == "Mara" and g.human.hand.any(func(c): return c.id == "w_falle") and g.human.hand.any(func(c): return c.id == "w_reiter"), "Mara, Frostfalle und Wellenreiter auf der Hand")
await frames(2)
check(scr.mulligan_box.visible and scr.tutorial_step == 0, "Schritt 1: Mulligan-Maske")
await click(scr.b_keep)
check(scr.tutorial_step == 1 and "Frostfalle" in scr.tutorial_text.text, "Schritt 2: Falle legen")
await shot("29_anleitung2")
# Zug 1 hat nur 1 Energie: Zug beenden, dann Falle legen
await key(KEY_SPACE)
await wait_for_human(scr)
check(scr.highlight_layer.get_child_count() >= 1, "Falle hervorgehoben")
var idx: int = g.human.hand.map(func(c): return c.id).find("w_falle")
check(idx >= 0 and idx < 9, "Falle auf der Hand")
await key(KEY_1 + idx)
check(g.human.traps.size() == 1 and scr.tutorial_step == 2, "Falle gelegt, Schritt 3")
await give_up()
check(main.profile.tutorial2_done and find_button("Anleitung") != null, "Teil 2 erledigt")
func test_profil_zuruecksetzen() -> void:
await click(find_button("Einstellungen"))
await click(find_button("Profil zurücksetzen"))
var scr = screen()
check(scr.confirm.visible, "Rückfrage")
await click(find_button("Abbrechen"))
check(not scr.confirm.visible, "abgebrochen")
await click(find_button("Profil zurücksetzen"))
await click(find_button("Ja, zurücksetzen"))
check(screen() is MenuScreen and main.profile.coins == Profile.START_COINS and not main.profile.tutorial_done, "Profil zurückgesetzt")
check(Profile.load_or_new(SAVE).coins == Profile.START_COINS, "zurückgesetzt gespeichert")
func test_herausforderung() -> void:
await click(find_button("Herausforderung"))
var scr = screen()
@@ -602,6 +670,20 @@ func test_anleitung() -> void:
await key(KEY_1 + idx)
check(g.human.board.size() == 1, "erneut gespielt")
await shot("22_anleitung")
# Gamepad: Zeiger bewegen, Karte spielen
await pad(JOY_BUTTON_DPAD_RIGHT)
check(scr.gp_zone == "hand" and scr.gp_index == 1 and scr.cursor_layer.get_child_count() == 1, "Gamepad-Zeiger auf zweiter Handkarte")
var pad_idx := -1
for i in g.human.hand.size():
if g.human.hand[i].is_creature() and g.can_play(g.human, g.human.hand[i])[0]:
pad_idx = i
break
if pad_idx >= 0:
while scr.gp_index != pad_idx:
await pad(JOY_BUTTON_DPAD_RIGHT)
var board_before: int = g.human.board.size()
await pad(JOY_BUTTON_A)
check(g.human.board.size() == board_before + 1, "Gamepad A spielt das Wesen")
scr.ai_delay = 0.75
await key(KEY_SPACE)
check(g.current() != g.human or g.turn >= 2, "Zug per Leertaste beendet")
@@ -639,9 +721,16 @@ func test_netzwerk() -> void:
var pid := OS.create_process(OS.get_executable_path(), args)
check(pid > 0, "Bot-Prozess gestartet")
var wait := 0
while main.net.names.size() < 2 and wait < 900:
while main.net.names.size() < 2 and wait < 600:
await frames(1)
wait += 1
if main.net.names.size() < 2:
print("HINWEIS: Bot kam nicht, zweiter Versuch")
OS.kill(pid)
pid = OS.create_process(OS.get_executable_path(), args)
while main.net.names.size() < 2 and wait < 1500:
await frames(1)
wait += 1
check(main.net.names.size() == 2, "Bot ist beigetreten (Pfad %s)" % main.net.get_path())
await shot("23_lobby")
await click(find_button("Start"))