Lava abgeschwächt, Archetypen aus Datei mit CI-Messlauf, Kartentext folgt Schriftgröße, Gamepad in Menüs, Anleitung 2 mit sicherem Angriff, Übersetzungsprüfung, Lint und gestaffelte CI
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
29
tests/lint.gd
Normal file
29
tests/lint.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends SceneTree
|
||||
## Statische Prüfung: lädt jedes Skript; Parse- oder Kompilierfehler lassen den Lauf scheitern.
|
||||
## godot --headless --path . -s tests/lint.gd
|
||||
|
||||
|
||||
func scan(dir: String, out: Array) -> void:
|
||||
var d := DirAccess.open(dir)
|
||||
if d == null:
|
||||
return
|
||||
for sub in d.get_directories():
|
||||
if not sub.begins_with("."):
|
||||
scan(dir.path_join(sub), out)
|
||||
for f in d.get_files():
|
||||
if f.ends_with(".gd"):
|
||||
out.append(dir.path_join(f))
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var files := []
|
||||
for dir in ["res://scripts", "res://tests"]:
|
||||
scan(dir, files)
|
||||
var bad := 0
|
||||
for path in files:
|
||||
var script = load(path)
|
||||
if script == null or not (script is GDScript):
|
||||
printerr("FEHLER: %s lädt nicht" % path)
|
||||
bad += 1
|
||||
print("%d Skripte geprüft, %d fehlerhaft" % [files.size(), bad])
|
||||
quit(1 if bad > 0 else 0)
|
||||
1
tests/lint.gd.uid
Normal file
1
tests/lint.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c65qyffqln67m
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Führt Import und alle Headless-Prüfungen aus. Aufruf: tests/run_headless.sh [godot-binary]
|
||||
# Führt Import, statische Prüfung und alle Headless-Prüfungen aus. Aufruf: tests/run_headless.sh [godot-binary]
|
||||
set -euo pipefail
|
||||
GODOT="${1:-godot}"
|
||||
cd "$(dirname "$0")/.."
|
||||
"$GODOT" --headless --path . --import > /dev/null 2>&1 || true
|
||||
"$GODOT" --headless --path . -s tests/lint.gd 2>&1 | grep -E "FEHLER|Skripte" || true
|
||||
"$GODOT" --headless --path . -s tests/lint.gd > /dev/null 2>&1
|
||||
"$GODOT" --headless --path . -s tests/test_runner.gd 2>&1 | tee /tmp/dinger_tests.log | grep -E "FEHLER|Prüfungen|SCRIPT ERROR" || true
|
||||
grep -q " 0 fehlgeschlagen" /tmp/dinger_tests.log
|
||||
|
||||
@@ -34,4 +34,8 @@ func _initialize() -> void:
|
||||
wins += 1
|
||||
results["+".join(a)] = wins * 100 / n
|
||||
print("%-14s %3d%%" % ["+".join(a), wins * 100 / n])
|
||||
var f := FileAccess.open(DeckGen.ARCHETYPE_PATH, FileAccess.WRITE)
|
||||
f.store_string(JSON.stringify(results, "\t"))
|
||||
f.close()
|
||||
print("nach %s geschrieben" % DeckGen.ARCHETYPE_PATH)
|
||||
quit()
|
||||
|
||||
@@ -111,6 +111,7 @@ func _initialize() -> void:
|
||||
test_kurve_statistik_vorschlag_verbesserung()
|
||||
test_wasser_antworten()
|
||||
test_ki_zeitbudget()
|
||||
test_uebersetzung_vollstaendig()
|
||||
print("%d Prüfungen bestanden, %d fehlgeschlagen" % [passed, failed])
|
||||
quit(1 if failed > 0 else 0)
|
||||
|
||||
@@ -532,15 +533,16 @@ func test_feldkombos() -> void:
|
||||
g.play_card(h, C("w_reiter"))
|
||||
check(strong.frozen and not weak.frozen, "Dampf friert das stärkste Wesen")
|
||||
check(g.log.any(func(l): return l.begins_with("Feldkombo Dampf")), "Feldkombo im Verlauf")
|
||||
# Lava: 1 Schaden an alle gegnerischen Wesen
|
||||
# Lava: 1 Schaden an das stärkste gegnerische Wesen
|
||||
s = setup(["e_golem"])
|
||||
g = s[0]
|
||||
h = s[1]
|
||||
o = s[2][0]
|
||||
put(h, "f_wolf")
|
||||
var k := put(o, "f_kobold")
|
||||
var big := put(o, "e_troll")
|
||||
g.play_card(h, C("e_golem"))
|
||||
check(not (k in o.board), "Lava tötet den 2/1 Kobold")
|
||||
check(k in o.board and big.health == 5, "Lava trifft nur das stärkste Wesen")
|
||||
# Blitz: 2 Schaden an den schwächsten Gegner
|
||||
s = setup(["l_wind"], [], 2)
|
||||
g = s[0]
|
||||
@@ -1119,7 +1121,7 @@ func test_neue_fallen() -> void:
|
||||
g.end_turn()
|
||||
g.play_card(o, C("e_troll"))
|
||||
check(o.board.size() == 1 and o.board[0].frozen and h.traps.size() == 1, "Sandfalle friert ein, eine Falle je Auslöser")
|
||||
g.play_card(o, C("w_reiter")) # Wasser neben Erde: Schlamm heilt nur, der Windgeist überlebt
|
||||
g.play_card(o, C("w_reiter")) # Wasser neben Erde: Schlamm heilt nur
|
||||
check(h.board.size() == 1 and h.board[0].name == "Windgeist", "Spiegelfalle beschwört Windgeist beim zweiten Wesen")
|
||||
check(h.traps.is_empty(), "beide Fallen verbraucht")
|
||||
# Nebelfalle bricht ab und zieht, Dornenfalle verletzt und heilt
|
||||
@@ -1240,7 +1242,7 @@ func test_deckstaerke_und_schwierigkeit() -> void:
|
||||
|
||||
func test_starter_gegen_passende_gegner() -> void:
|
||||
check(Game.archetypes("leicht").size() == 5 and Game.archetypes("schwer").size() == 5 and Game.archetypes().size() == 10, "Archetypen geteilt")
|
||||
var table: Dictionary = DeckGen.ARCHETYPE_STRENGTH
|
||||
var table: Dictionary = DeckGen.strength()
|
||||
var keys: Array = table.keys()
|
||||
keys.sort_custom(func(a, b): return table[a] < table[b])
|
||||
check(keys[0] in Game.archetypes("leicht") and keys[-1] in Game.archetypes("schwer"), "schwächster Archetyp in Leicht, stärkster in Schwer")
|
||||
@@ -1358,3 +1360,37 @@ func test_ki_zeitbudget() -> void:
|
||||
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")
|
||||
|
||||
|
||||
## Jeder tr("...")-Text in der Oberfläche und jeder Anleitungsschritt hat einen englischen Eintrag.
|
||||
func test_uebersetzung_vollstaendig() -> void:
|
||||
var keys := {}
|
||||
var csv := FileAccess.get_file_as_string("res://translations/ui.csv")
|
||||
for line in csv.split("\n"):
|
||||
if line.is_empty() or line.begins_with("keys,"):
|
||||
continue
|
||||
var key := line
|
||||
if line.begins_with("\""):
|
||||
key = line.substr(1, line.find("\",", 1) - 1)
|
||||
else:
|
||||
key = line.substr(0, line.find(","))
|
||||
keys[key] = true
|
||||
var missing := []
|
||||
var d := DirAccess.open("res://scripts/ui")
|
||||
d.list_dir_begin()
|
||||
var name := d.get_next()
|
||||
while name != "":
|
||||
if name.ends_with(".gd"):
|
||||
var src := FileAccess.get_file_as_string("res://scripts/ui/" + name)
|
||||
var rx := RegEx.new()
|
||||
rx.compile("tr\\(\"([^\"]+)\"\\)")
|
||||
for m in rx.search_all(src):
|
||||
var k: String = m.get_string(1)
|
||||
if not keys.has(k) and not (k in missing):
|
||||
missing.append(k)
|
||||
name = d.get_next()
|
||||
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])
|
||||
|
||||
@@ -162,7 +162,7 @@ func _ready() -> void:
|
||||
add_child(main)
|
||||
await frames(3)
|
||||
var fast := OS.get_environment("DINGER_FAST") != ""
|
||||
var suite := [test_menu_und_regeln, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
|
||||
var suite := [test_menu_und_regeln, test_gamepad_menue, test_booster, test_deckbau, test_gegnerauswahl, test_einstellungen,
|
||||
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]
|
||||
@@ -513,6 +513,34 @@ func test_werkstatt() -> void:
|
||||
check(screen() is MenuScreen, "zurück im Menü")
|
||||
|
||||
|
||||
func test_gamepad_menue() -> void:
|
||||
check(get_viewport().gui_get_focus_owner() == null, "ohne Gamepad kein Fokus")
|
||||
await pad(JOY_BUTTON_DPAD_DOWN)
|
||||
var f := get_viewport().gui_get_focus_owner()
|
||||
check(f is Button and main.pad_mode, "Gamepad setzt den Fokus auf einen Knopf")
|
||||
var down := InputEventJoypadButton.new()
|
||||
down.button_index = JOY_BUTTON_DPAD_DOWN
|
||||
down.pressed = true
|
||||
Input.parse_input_event(down)
|
||||
await frames(2)
|
||||
var f2 := get_viewport().gui_get_focus_owner()
|
||||
check(f2 is Button and f2 != f, "Steuerkreuz wechselt den Knopf")
|
||||
var target_text: String = f2.text
|
||||
var accept := InputEventJoypadButton.new()
|
||||
accept.button_index = JOY_BUTTON_A
|
||||
accept.pressed = true
|
||||
get_viewport().push_input(accept)
|
||||
await frames(1)
|
||||
var release := InputEventJoypadButton.new()
|
||||
release.button_index = JOY_BUTTON_A
|
||||
release.pressed = false
|
||||
get_viewport().push_input(release)
|
||||
await frames(3)
|
||||
check(not (screen() is MenuScreen) or target_text == "Beenden", "A öffnet den fokussierten Bildschirm (%s)" % target_text)
|
||||
await hover(main)
|
||||
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")
|
||||
@@ -535,6 +563,15 @@ func test_anleitung2() -> void:
|
||||
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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user