update
Some checks failed
Tests / headless (push) Has been cancelled
Tests / fenster-schnell (push) Has been cancelled
Tests / fenster-voll (push) Has been cancelled
Tests / balance (push) Has been cancelled

This commit is contained in:
Team3
2026-09-19 13:37:12 +02:00
parent 89db101137
commit 8944fdd017
21 changed files with 62 additions and 562 deletions

View File

@@ -829,7 +829,7 @@ func test_deck_slots_und_werkstatt() -> void:
check(m.decks[0].cards == ["f_kobold", "f_kobold"] and m.active_deck == 0 and m.dust == 0, "Version 2 wird zu Slot 1")
var v3 := Profile.from_dict(JSON.parse_string(JSON.stringify(p.to_dict())))
check(v3.decks[1].cards == ["f_kobold"] and v3.deck_name(1) == "Deck 2", "Version 3 gelesen")
# Werkstatt
# Zerlegen und Herstellen
p.collection["f_kobold"] = 5
check(p.dismantle("f_kobold") == "" and p.collection["f_kobold"] == 4 and p.dust == 5, "Zerlegen bringt Staub")
check(p.dismantle("f_kobold", 3) != "" and p.collection["f_kobold"] == 4, "Deckbedarf schützt Karten")
@@ -984,8 +984,6 @@ func test_statistik_pity_tagesaufgaben() -> void:
check(p.card_stats["f_kobold"].games == 1 and p.card_stats["f_kobold"].wins == 1 and p.card_stats["e_wall"].games == 1, "Partien und Siege je Deckkarte")
p.record_battle(false, 1, deck, -1, [])
check(p.card_stats["f_kobold"].games == 2 and p.card_stats["f_kobold"].wins == 1, "Niederlage zählt Partie")
var top := p.top_cards(3)
check(top.size() == 3 and top[0].wins == 1, "Bestenliste der Karten")
# Pity-Zähler
p.coins = 100000
var r := RandomNumberGenerator.new()
@@ -1157,11 +1155,7 @@ func test_bestenliste() -> void:
check(p.records.streak == 0 and p.records.best_streak == 2, "Niederlage bricht die Serie")
for i in 5:
p.record_battle(true, 1, mono, -1, [], 10)
var path := "user://test_statistik.csv"
check(p.export_stats(path) == "", "Statistik exportiert")
var text := FileAccess.get_file_as_string(path)
check(text.begins_with("karte;seltenheit;gespielt;partien;siege") and "beste_serie;5" in text, "CSV-Inhalt")
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
check(p.records.best_streak == 5, "neue beste Serie")
Profile.today_override = ""
@@ -1318,7 +1312,7 @@ func test_ki_zeitbudget() -> void:
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")
check(v.settings.has("big_font") and v.records.margins is Array, "Version 5 mit neuen Feldern")
## Elementbooster enthalten nur Karten des Elements; Deck-Codes sind umkehrbar und robust.
@@ -1388,7 +1382,7 @@ func test_kampf_speichern() -> void:
check(s2.players.all(func(p): return not (p.hand is Array) and not (p.traps is Array) and not (p.deck is Array)), "Zuschauer-Sicht ohne Hände, Decks und Fallen")
## Jeder tr("...")-Text in der Oberfläche und jeder Anleitungsschritt hat einen englischen Eintrag.
## Jeder tr("...")-Text in der Oberfläche hat einen englischen Eintrag.
func test_uebersetzung_vollstaendig() -> void:
var keys := {}
var csv := FileAccess.get_file_as_string("res://translations/ui.csv")
@@ -1423,8 +1417,4 @@ func test_uebersetzung_vollstaendig() -> void:
for m in rx_cards.search_all(FileAccess.get_file_as_string("res://scripts/cards.gd")):
if not keys.has(m.get_string(1)) and not (m.get_string(1) in missing):
missing.append(m.get_string(1))
for stepset in [BattleTutorial.STEPS1, BattleTutorial.STEPS2]:
for st in stepset:
if not keys.has(st[0]):
missing.append(st[0])
check(missing.is_empty(), "fehlende Übersetzungen: %s" % [missing])

View File

@@ -164,8 +164,8 @@ func _ready() -> void:
add_child(main)
await frames(3)
var fast := OS.get_environment("DINGER_FAST") != ""
var suite := [test_menu_und_regeln, test_gamepad_menue, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
test_statistik, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2,
var suite := [test_menu, test_gamepad_menue, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
test_kampagne, test_sammlung, test_sprache, test_kampf_steuerung,
test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster]
if not fast:
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
@@ -187,14 +187,10 @@ func _ready() -> void:
get_tree().quit(1 if failed > 0 else 0)
func test_menu_und_regeln() -> void:
func test_menu() -> void:
check(screen() is MenuScreen, "Menü sichtbar")
check(find_button("Einzelspieler") != null and find_button("Beenden") != null, "Menüknöpfe vorhanden")
await shot("01_menu")
await click(find_button("Regeln"))
check(screen() is RulesScreen, "Regeln geöffnet")
await shot("02_regeln")
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
func test_booster() -> void:
@@ -568,23 +564,10 @@ func test_einstellungen() -> void:
check(screen() is MenuScreen, "zurück im Menü")
func test_statistik() -> void:
await click(find_button("Statistik"))
check(screen() is StatsScreen, "Statistik 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_statistik")
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
func test_werkstatt() -> void:
await click(find_button("Werkstatt"))
func test_sammlung() -> void:
await click(find_button("Sammlung"))
var scr = screen()
check(scr is WorkshopScreen, "Werkstatt geöffnet")
check(scr is CollectionScreen, "Sammlung geöffnet")
var p: Profile = main.profile
p.dust = 0
var in_decks := {}
@@ -607,8 +590,8 @@ func test_werkstatt() -> void:
await click(phoenix)
await click(scr.b_craft)
check(p.collection.get("ff_phoenix", 0) == 1 and p.dust == 5000 - Profile.DUST_CRAFT["legendär"], "Phönix hergestellt")
check(Profile.load_or_new(SAVE).collection.get("ff_phoenix", 0) == 1, "Werkstatt gespeichert")
await shot("20_werkstatt")
check(Profile.load_or_new(SAVE).collection.get("ff_phoenix", 0) == 1, "Sammlung gespeichert")
await shot("20_sammlung")
await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü")
@@ -642,41 +625,6 @@ func test_gamepad_menue() -> void:
check(not main.pad_mode and get_viewport().gui_get_focus_owner() == null, "Maus nimmt den Fokus wieder")
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")
# Mara läuft in die Falle: zwei Züge abwarten
for i in 2:
await clear_mulligan(scr)
if g.current() == g.human and g.winner == "":
await key(KEY_SPACE)
await wait_for_human(scr)
if scr.tutorial_step >= 3:
break
check(scr.tutorial_step >= 3 and g.log.any(func(l): return l.begins_with("Falle von Du")), "Mara löst die Frostfalle aus, Schritt 4")
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"))
@@ -686,7 +634,7 @@ func test_profil_zuruecksetzen() -> void:
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(screen() is MenuScreen and main.profile.coins == Profile.START_COINS and main.profile.collection.size() > 0, "Profil zurückgesetzt")
check(Profile.load_or_new(SAVE).coins == Profile.START_COINS, "zurückgesetzt gespeichert")
@@ -714,46 +662,35 @@ func test_sprache() -> void:
check(screen() is MenuScreen, "zurück im Menü")
func test_anleitung() -> void:
check(not main.profile.tutorial_done, "Anleitung noch offen")
await click(find_button("Anleitung"))
## Steuerung im Kampf: Tastatur, Rückgängig, Gamepad, Überspringen, Esc.
func test_kampf_steuerung() -> void:
await start_battle(1)
var scr = screen()
check(scr is BattleScreen and scr.tutorial, "Anleitung gestartet")
check(scr is BattleScreen, "Kampf gestartet")
scr.ai_delay = 0.0
await wait_for_human(scr)
var g: Game = scr.game
check(g.players[0] == g.human and g.opponents[0].name == "Fenn" and g.opponents[0].perks.is_empty(), "Fenn ohne Merkmal, Mensch beginnt")
await frames(2)
check(scr.mulligan_box.visible, "Mulligan-Maske im ersten Zug")
var views = scr.mulligan_layer.get_children()
await click(views[0])
check(scr.mulligan_selected == [0], "Karte zum Tausch gewählt")
await shot("22_anleitung_mulligan")
await click(scr.b_swap)
check(not scr.mulligan_box.visible and g.human.mulligan_done, "getauscht")
check(g.log.any(func(l): return "tauscht 1 Karte" in l), "Tausch im Verlauf")
check(scr.tutorial_step == 0 and "Energie" in scr.tutorial_text.text, "Schritt 1 sichtbar")
var idx := -1
for i in g.human.hand.size():
if g.human.hand[i].is_creature() and g.human.hand[i].cost == 1:
if g.human.hand[i].is_creature() and g.can_play(g.human, g.human.hand[i])[0]:
idx = i
break
check(idx < 0 or scr.highlight_layer.get_child_count() >= 1, "spielbare Wesen hervorgehoben")
check(idx >= 0, "spielbares Wesen auf der Hand")
if idx >= 0:
var played_id: String = g.human.hand[idx].id
var board_before: int = g.human.board.size()
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")
check(g.human.board.size() == board_before + 1, "Wesen per Taste gespielt")
# 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(g.human.board.size() == board_before and g.human.hand.any(func(c): return c.id == played_id) and g.human.energy > energy, "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")
check(g.human.board.size() == board_before + 1, "erneut gespielt")
await shot("22_kampf_steuerung")
# 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")
@@ -765,9 +702,9 @@ func test_anleitung() -> void:
if pad_idx >= 0:
while scr.gp_index != pad_idx:
await pad(JOY_BUTTON_DPAD_RIGHT)
var board_before: int = g.human.board.size()
var board_before2: int = g.human.board.size()
await pad(JOY_BUTTON_A)
check(g.human.board.size() == board_before + 1, "Gamepad A spielt das Wesen")
check(g.human.board.size() == board_before2 + 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")
@@ -781,16 +718,11 @@ func test_anleitung() -> void:
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)
check(scr.tutorial_step >= step_before + 1, "Weiter-Knopf (Schritt %d)" % step_before)
await key(KEY_ESCAPE)
check(scr.confirm.visible, "Esc öffnet Aufgeben")
await key(KEY_ESCAPE)
check(not scr.confirm.visible, "Esc schließt den Dialog")
await give_up()
check(main.profile.tutorial_done, "Anleitung als erledigt gespeichert")
func test_netzwerk() -> void: