Erfolge entfernt, der Bildschirm zeigt nur noch Statistik

- Die dreizehn Erfolge samt Belohnungen, Meldungen und Spielstandfeld sind weg
- Der bisherige Erfolgs-Bildschirm heißt Statistik und zeigt Kämpfe, Tagesaufgabe,
  Bestenliste, beste Karten und den CSV-Export

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
Team3
2026-09-17 21:37:58 +02:00
parent 43073351b0
commit 89db101137
11 changed files with 29 additions and 120 deletions

View File

@@ -28,7 +28,7 @@ Der Spielstand liegt unter `user://spielstand.json`
- 100 Karten in `data/karten.json`: 4 Elemente, 10 Zweier-Kombos (auch gleiche Elemente), 4 mythische Dreifach-Kombos, 4 Fallen - 100 Karten in `data/karten.json`: 4 Elemente, 10 Zweier-Kombos (auch gleiche Elemente), 4 mythische Dreifach-Kombos, 4 Fallen
- Resonanz: gleiche Elemente auf dem Feld verstärken sich. Feldkombos: verschiedene Elemente lösen beim Ausspielen Effekte aus - Resonanz: gleiche Elemente auf dem Feld verstärken sich. Feldkombos: verschiedene Elemente lösen beim Ausspielen Effekte aus
- Fallen liegen verdeckt und lösen im gegnerischen Zug aus (die KI kennt fremde Fallen nicht) - Fallen liegen verdeckt und lösen im gegnerischen Zug aus (die KI kennt fremde Fallen nicht)
- Jeder gegen jeden mit ausgeloster Zugreihenfolge, 1 bis 5 KI-Gegner (KI mit Vorausschau), Kampagne mit 10 Magiern, Erfolge, Tagesaufgabe - Jeder gegen jeden mit ausgeloster Zugreihenfolge, 1 bis 5 KI-Gegner (KI mit Vorausschau), Kampagne mit 10 Magiern je Kapitel, Tagesaufgabe
- Booster, fünf Deck-Slots, Werkstatt (Zerlegen und Herstellen aus Staub), Einstellungen (Ton, KI-Tempo, Animationen) - Booster, fünf Deck-Slots, Werkstatt (Zerlegen und Herstellen aus Staub), Einstellungen (Ton, KI-Tempo, Animationen)
- Spielstand versioniert mit Sicherungskopie (`spielstand.json.bak`) - Spielstand versioniert mit Sicherungskopie (`spielstand.json.bak`)
- Anleitung (geführte erste Partie), Mulligan im ersten Zug, Tastatur (Leertaste, 1 bis 9, Esc, Enter) - Anleitung (geführte erste Partie), Mulligan im ersten Zug, Tastatur (Leertaste, 1 bis 9, Esc, Enter)
@@ -66,7 +66,7 @@ Der Spielstand liegt unter `user://spielstand.json`
`tests/run_headless.sh` führt Import, statische Prüfung (`tests/lint.gd`) und Headless-Prüfungen aus. `.github/workflows/tests.yml`: `tests/run_headless.sh` führt Import, statische Prüfung (`tests/lint.gd`) und Headless-Prüfungen aus. `.github/workflows/tests.yml`:
Headless und schneller Fenster-Test bei jedem Push, voller Fenster-Test und Archetypen-Messung nachts um 3 Uhr oder von Hand (Xvfb, lokal nicht geprüft). Headless und schneller Fenster-Test bei jedem Push, voller Fenster-Test und Archetypen-Messung nachts um 3 Uhr oder von Hand (Xvfb, lokal nicht geprüft).
`DINGER_FAST=1` lässt den Fenster-Test ohne die langen Kämpfe laufen (etwa ein Drittel der Zeit). `DINGER_FAST=1` lässt den Fenster-Test ohne die langen Kämpfe laufen (etwa ein Drittel der Zeit).
Statistiken aus Spieltests lassen sich unter Erfolge als CSV exportieren. Statistiken aus Spieltests lassen sich unter Statistik als CSV exportieren.
``` ```
godot --headless --path . --import # einmalig nach dem Klonen: Ressourcen importieren (macht start.sh auch) godot --headless --path . --import # einmalig nach dem Klonen: Ressourcen importieren (macht start.sh auch)
@@ -98,9 +98,9 @@ godot --headless --path . --export-release Windows build/dinger-windows.exe
- `scripts/deckgen.gd` – Deckstärke, Gegnerdecks entlang der Energiekurve, Archetypen - `scripts/deckgen.gd` – Deckstärke, Gegnerdecks entlang der Energiekurve, Archetypen
- `scripts/netstate.gd` – Zustandsbilder fürs Netzwerk - `scripts/netstate.gd` – Zustandsbilder fürs Netzwerk
- `scripts/ai.gd` – KI mit Vorausschau (plus gierige Vergleichs-KI) - `scripts/ai.gd` – KI mit Vorausschau (plus gierige Vergleichs-KI)
- `scripts/profile.gd` – Spielstand (versioniert), Booster, Erfolge, Tagesaufgabe, Einstellungen - `scripts/profile.gd` – Spielstand (versioniert), Booster, Tagesaufgabe, Einstellungen
- `scripts/campaign.gd` – Kampagnenstufen - `scripts/campaign.gd` – Kampagnenstufen
- `scripts/ui/` – Oberfläche: Menü, Gegnerauswahl, Kampagne, Lobby, Booster, Deckbau, Werkstatt, Kampf (Bildschirm, Layout, Effekte, Anleitung, Mulligan, Gamepad), Erfolge, Einstellungen, Regeln, Ton - `scripts/ui/` – Oberfläche: Menü, Gegnerauswahl, Kampagne, Lobby, Booster, Deckbau, Werkstatt, Kampf (Bildschirm, Layout, Effekte, Anleitung, Mulligan, Gamepad), Statistik, Einstellungen, Regeln, Ton
- `scenes/main.tscn` – Startszene - `scenes/main.tscn` – Startszene
- `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden - `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden
- `translations/ui.csv` – englische Oberflächentexte - `translations/ui.csv` – englische Oberflächentexte

View File

@@ -5,7 +5,7 @@
- PC-Spiel - PC-Spiel
- Jeder gegen jeden: Spieler und 1 bis 5 KI-Gegner (oder Spieler), alle mit gleichen Regeln - Jeder gegen jeden: Spieler und 1 bis 5 KI-Gegner (oder Spieler), alle mit gleichen Regeln
- Einzelspieler-Knopf führt zur Auswahl der Gegnerzahl - Einzelspieler-Knopf führt zur Auswahl der Gegnerzahl
- Kampagne gegen benannte Magier, Erfolge und Tagesaufgaben als Fortschritt - Kampagne gegen benannte Magier und Tagesaufgaben als Fortschritt
- Im lokalen Netz zu mehreren spielbar, auch als Zuschauer - Im lokalen Netz zu mehreren spielbar, auch als Zuschauer
- Ein unterbrochener Kampf lässt sich später fortsetzen - Ein unterbrochener Kampf lässt sich später fortsetzen
- Geführte erste Partie als Einstieg - Geführte erste Partie als Einstieg

View File

@@ -1,6 +1,6 @@
class_name Profile class_name Profile
extends RefCounted extends RefCounted
## Spielstand: Sammlung, Münzen, Decks, Staub, Booster, Statistik, Erfolge, Tagesaufgabe, Einstellungen. ## Spielstand: Sammlung, Münzen, Decks, Staub, Booster, Statistik, Tagesaufgabe, Einstellungen.
const SAVE_VERSION := 5 const SAVE_VERSION := 5
const PITY_BOOSTERS := 20 # danach ist eine mythische Karte garantiert const PITY_BOOSTERS := 20 # danach ist eine mythische Karte garantiert
@@ -24,22 +24,6 @@ const DIFFICULTIES := {
const DUST_DISMANTLE := {"gewöhnlich": 5, "selten": 20, "episch": 100, "legendär": 400, "mythisch": 1600} const DUST_DISMANTLE := {"gewöhnlich": 5, "selten": 20, "episch": 100, "legendär": 400, "mythisch": 1600}
const DUST_CRAFT := {"gewöhnlich": 40, "selten": 100, "episch": 400, "legendär": 1600, "mythisch": 3200} const DUST_CRAFT := {"gewöhnlich": 40, "selten": 100, "episch": 400, "legendär": 1600, "mythisch": 3200}
const ACHIEVEMENTS := [
{"id": "erster_sieg", "name": "Erster Sieg", "text": "Gewinne einen Kampf.", "reward": 50},
{"id": "duellant", "name": "Duellant", "text": "Gewinne ein Duell.", "reward": 50},
{"id": "tischherr", "name": "Herr des Tisches", "text": "Gewinne gegen 3 oder mehr Gegner.", "reward": 100},
{"id": "chaosmeister", "name": "Chaosmeister", "text": "Gewinne gegen 5 Gegner.", "reward": 200},
{"id": "sammler", "name": "Sammler", "text": "Besitze 50 verschiedene Karten.", "reward": 100},
{"id": "vollstaendig", "name": "Vollständig", "text": "Besitze jede Karte.", "reward": 500},
{"id": "boosterfreund", "name": "Boosterfreund", "text": "Öffne 10 Booster.", "reward": 100},
{"id": "elementarist", "name": "Elementarist", "text": "Gewinne mit einem Deck aus nur einem Element.", "reward": 100},
{"id": "kombokoenig", "name": "Kombokönig", "text": "Gewinne mit mindestens 6 Kombokarten im Deck.", "reward": 100},
{"id": "kampagnenheld", "name": "Kampagnenheld", "text": "Schließe Kapitel 1 der Kampagne ab.", "reward": 300},
{"id": "elementmeister", "name": "Meister der Elemente", "text": "Schließe Kapitel 2 der Kampagne ab.", "reward": 500},
{"id": "archimagier", "name": "Archimagier", "text": "Schließe Kapitel 3 der Kampagne ab.", "reward": 800},
{"id": "serie", "name": "Siegesserie", "text": "Gewinne fünf Kämpfe in Folge.", "reward": 150},
]
const DAILY_TASKS := [ const DAILY_TASKS := [
{"id": "sieg", "kind": "win", "text": "Gewinne einen Kampf", "goal": 1, "reward": 80}, {"id": "sieg", "kind": "win", "text": "Gewinne einen Kampf", "goal": 1, "reward": 80},
{"id": "kaempfe", "kind": "game", "text": "Bestreite zwei Kämpfe", "goal": 2, "reward": 60}, {"id": "kaempfe", "kind": "game", "text": "Bestreite zwei Kämpfe", "goal": 2, "reward": 60},
@@ -63,11 +47,10 @@ var collection := {} # id -> Anzahl
var decks: Array = [] # DECK_SLOTS Einträge {"name": String, "cards": Array von ids} var decks: Array = [] # DECK_SLOTS Einträge {"name": String, "cards": Array von ids}
var active_deck := 0 var active_deck := 0
var stats := {"games": 0, "wins": 0, "boosters": 0, "wins_by_opp": {}} var stats := {"games": 0, "wins": 0, "boosters": 0, "wins_by_opp": {}}
var achievements: Array = []
var campaign_stage := 0 # Anzahl abgeschlossener Kampagnenstufen var campaign_stage := 0 # Anzahl abgeschlossener Kampagnenstufen
var daily := {"date": "", "task": "", "progress": 0, "done": false} var daily := {"date": "", "task": "", "progress": 0, "done": false}
var settings := {"volume": 0.8, "music": 0.5, "ai_speed": 1.0, "animations": true, "language": "de", "difficulty": "normal", "big_font": false} var settings := {"volume": 0.8, "music": 0.5, "ai_speed": 1.0, "animations": true, "language": "de", "difficulty": "normal", "big_font": false}
var notices: Array[String] = [] # Meldungen für die Oberfläche (Erfolge, Tagesaufgabe) var notices: Array[String] = [] # Meldungen für die Oberfläche (Tagesaufgabe, Fortschritt)
var card_stats := {} # id -> {"played", "games", "wins"} var card_stats := {} # id -> {"played", "games", "wins"}
var booster_since_mythic := 0 var booster_since_mythic := 0
var tutorial_done := false var tutorial_done := false
@@ -151,8 +134,6 @@ static func from_dict(d: Dictionary) -> Profile:
p.stats.boosters = int(st.get("boosters", 0)) p.stats.boosters = int(st.get("boosters", 0))
for k in st.get("wins_by_opp", {}): for k in st.get("wins_by_opp", {}):
p.stats.wins_by_opp[str(k)] = int(st.wins_by_opp[k]) p.stats.wins_by_opp[str(k)] = int(st.wins_by_opp[k])
for a in d.get("achievements", []):
p.achievements.append(str(a))
p.campaign_stage = int(d.get("campaign_stage", 0)) p.campaign_stage = int(d.get("campaign_stage", 0))
var dl = d.get("daily", {}) var dl = d.get("daily", {})
p.daily = {"date": str(dl.get("date", "")), "task": str(dl.get("task", "")), p.daily = {"date": str(dl.get("date", "")), "task": str(dl.get("task", "")),
@@ -186,7 +167,7 @@ static func from_dict(d: Dictionary) -> Profile:
func to_dict() -> Dictionary: func to_dict() -> Dictionary:
return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "collection": collection, return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "collection": collection,
"decks": decks, "active_deck": active_deck, "decks": decks, "active_deck": active_deck,
"stats": stats, "achievements": achievements, "campaign_stage": campaign_stage, "stats": stats, "campaign_stage": campaign_stage,
"daily": daily, "settings": settings, "card_stats": card_stats, "daily": daily, "settings": settings, "card_stats": card_stats,
"booster_since_mythic": booster_since_mythic, "tutorial_done": tutorial_done, "booster_since_mythic": booster_since_mythic, "tutorial_done": tutorial_done,
"perk": perk, "records": records, "tutorial2_done": tutorial2_done} "perk": perk, "records": records, "tutorial2_done": tutorial2_done}
@@ -320,7 +301,6 @@ func craft(cid: String) -> String:
dust -= cost dust -= cost
collection[cid] = collection.get(cid, 0) + 1 collection[cid] = collection.get(cid, 0) + 1
daily_event("craft") daily_event("craft")
check_achievements()
return "" return ""
@@ -404,11 +384,10 @@ func open_booster(r: RandomNumberGenerator = null, element: String = "") -> Arra
collection[c.id] = collection.get(c.id, 0) + 1 collection[c.id] = collection.get(c.id, 0) + 1
stats.boosters += 1 stats.boosters += 1
daily_event("booster") daily_event("booster")
check_achievements()
return cards return cards
# --- Kampfergebnis, Erfolge, Tagesaufgabe ------------------------------------------- # --- Kampfergebnis, Tagesaufgabe -------------------------------------------
## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück. ## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück.
## stage >= 0: Kampagnenstufe; ein erster Sieg schaltet die nächste Stufe frei. ## stage >= 0: Kampagnenstufe; ein erster Sieg schaltet die nächste Stufe frei.
@@ -459,7 +438,6 @@ func record_battle(won: bool, n_opp: int, deck_used: Array, stage: int = -1, pla
daily_event("win_vs3") daily_event("win_vs3")
for e in Game.deck_elements(deck_used): for e in Game.deck_elements(deck_used):
daily_event("win_element:" + e) daily_event("win_element:" + e)
check_achievements({"won": won, "n_opp": n_opp, "deck": deck_used})
return reward return reward
@@ -469,53 +447,6 @@ func _stat(cid: String) -> Dictionary:
return card_stats[cid] return card_stats[cid]
func check_achievements(ctx: Dictionary = {}) -> Array:
var unlocked := []
for a in ACHIEVEMENTS:
if a.id in achievements:
continue
if _achieved(a.id, ctx):
achievements.append(a.id)
coins += a.reward
unlocked.append(a)
notices.append("Erfolg: %s (+%d Münzen)" % [a.name, a.reward])
return unlocked
func _achieved(id: String, ctx: Dictionary) -> bool:
var won: bool = ctx.get("won", false)
var n: int = ctx.get("n_opp", 0)
var deck_used: Array = ctx.get("deck", [])
match id:
"erster_sieg":
return stats.wins >= 1
"duellant":
return won and n == 1
"tischherr":
return won and n >= 3
"chaosmeister":
return won and n == 5
"sammler":
return distinct_owned() >= 50
"vollstaendig":
return distinct_owned() >= Cards.all().size()
"boosterfreund":
return stats.boosters >= 10
"elementarist":
return won and Game.deck_elements(deck_used).size() == 1
"kombokoenig":
return won and deck_used.filter(func(c): return c.is_combo()).size() >= 6
"kampagnenheld":
return campaign_stage >= Campaign.STAGES.size()
"elementmeister":
return campaign_stage >= 2 * Campaign.STAGES.size()
"archimagier":
return campaign_stage >= Campaign.total()
"serie":
return records.best_streak >= 5
return false
func difficulty() -> Dictionary: func difficulty() -> Dictionary:
return DIFFICULTIES.get(settings.get("difficulty", "normal"), DIFFICULTIES.normal) return DIFFICULTIES.get(settings.get("difficulty", "normal"), DIFFICULTIES.normal)

View File

@@ -1 +0,0 @@
uid://cw4rtcukefdl8

View File

@@ -46,7 +46,7 @@ func _ready() -> void:
Style.cbutton(grid, "Booster öffnen", sz, func(): app.show_screen(BoosterScreen.new())) Style.cbutton(grid, "Booster öffnen", sz, func(): app.show_screen(BoosterScreen.new()))
Style.cbutton(grid, "Deck bauen", sz, func(): app.show_screen(DeckScreen.new())) Style.cbutton(grid, "Deck bauen", sz, func(): app.show_screen(DeckScreen.new()))
Style.cbutton(grid, "Werkstatt", sz, func(): app.show_screen(WorkshopScreen.new())) Style.cbutton(grid, "Werkstatt", sz, func(): app.show_screen(WorkshopScreen.new()))
Style.cbutton(grid, "Erfolge", sz, func(): app.show_screen(AchievementsScreen.new())) Style.cbutton(grid, "Statistik", sz, func(): app.show_screen(StatsScreen.new()))
Style.cbutton(grid, "Regeln", sz, func(): app.show_screen(RulesScreen.new())) Style.cbutton(grid, "Regeln", sz, func(): app.show_screen(RulesScreen.new()))
Style.cbutton(grid, "Einstellungen", sz, func(): app.show_screen(SettingsScreen.new())) Style.cbutton(grid, "Einstellungen", sz, func(): app.show_screen(SettingsScreen.new()))
Style.cbutton(grid, "Beenden", sz, func(): get_tree().quit()) Style.cbutton(grid, "Beenden", sz, func(): get_tree().quit())

View File

@@ -1,5 +1,6 @@
class_name AchievementsScreen class_name StatsScreen
extends Control extends Control
## Statistik: Kämpfe, Tagesaufgabe, Bestenliste, beste Karten und CSV-Export.
var app var app
var export_label: Label var export_label: Label
@@ -8,7 +9,7 @@ var export_label: Label
func _ready() -> void: func _ready() -> void:
var p: Profile = app.profile var p: Profile = app.profile
var col := Style.column(self, 900, 12, 4) var col := Style.column(self, 900, 12, 4)
Style.clabel(col, "Erfolge", 32, Style.GOLD, "center", true) Style.clabel(col, "Statistik", 32, Style.GOLD, "center", true)
var st: Dictionary = p.stats var st: Dictionary = p.stats
Style.clabel(col, "Kämpfe %d · Siege %d · Booster %d · Kampagne %d/%d" % [ Style.clabel(col, "Kämpfe %d · Siege %d · Booster %d · Kampagne %d/%d" % [
st.games, st.wins, st.boosters, p.campaign_stage, Campaign.total()], 16, Style.MUTED) st.games, st.wins, st.boosters, p.campaign_stage, Campaign.total()], 16, Style.MUTED)
@@ -19,17 +20,6 @@ func _ready() -> void:
Style.clabel(col, "Serie: %d (beste %d) · Schnellster Sieg: %s" % [p.records.streak, p.records.best_streak, Style.clabel(col, "Serie: %d (beste %d) · Schnellster Sieg: %s" % [p.records.streak, p.records.best_streak,
("%d Züge" % p.records.fastest_win) if p.records.fastest_win > 0 else "–"], 15, Style.MUTED) ("%d Züge" % p.records.fastest_win) if p.records.fastest_win > 0 else "–"], 15, Style.MUTED)
Style.spacer(col, 4) Style.spacer(col, 4)
for a in Profile.ACHIEVEMENTS:
var done: bool = a.id in p.achievements
var panel := PanelContainer.new()
panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL if done else Style.darker(Style.PANEL, 0.8), 6))
col.add_child(panel)
var r := Style.row(panel, 12)
r.custom_minimum_size = Vector2(0, 36)
Style.clabel(r, "✓" if done else "·", 22, Style.GREEN if done else Style.MUTED, "center", true, 40)
Style.clabel(r, a.name, 18, Style.TEXT if done else Style.MUTED, "left", true, 250)
Style.clabel(r, a.text, 15, Style.MUTED, "left", false, 380)
Style.clabel(r, "+%d" % a.reward, 16, Style.GOLD, "right", true, 90)
var top := p.top_cards(5) var top := p.top_cards(5)
if not top.is_empty(): if not top.is_empty():
Style.spacer(col, 4) Style.spacer(col, 4)
@@ -52,5 +42,5 @@ func export_stats() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE: if event.is_action_pressed("ui_cancel"):
app.show_screen(MenuScreen.new()) app.show_screen(MenuScreen.new())

View File

@@ -0,0 +1 @@
uid://t0h7fpmpeuq4

View File

@@ -93,7 +93,7 @@ func _initialize() -> void:
test_ki_besser_als_gierig() test_ki_besser_als_gierig()
test_ki_spiele_enden() test_ki_spiele_enden()
test_spielstand_migration() test_spielstand_migration()
test_erfolge_und_tagesaufgabe() test_tagesaufgabe()
test_kampagne() test_kampagne()
test_fallen() test_fallen()
test_ki_sieht_fallen_nicht() test_ki_sieht_fallen_nicht()
@@ -648,29 +648,25 @@ func test_spielstand_migration() -> void:
var alt := {"coins": 300, "collection": {"f_kobold": 2, "unbekannt": 1}, "deck": ["f_kobold", "f_kobold"]} var alt := {"coins": 300, "collection": {"f_kobold": 2, "unbekannt": 1}, "deck": ["f_kobold", "f_kobold"]}
var p := Profile.from_dict(alt) var p := Profile.from_dict(alt)
check(p.coins == 300 and p.collection == {"f_kobold": 2} and p.deck.size() == 2, "Version 1 gelesen") check(p.coins == 300 and p.collection == {"f_kobold": 2} and p.deck.size() == 2, "Version 1 gelesen")
check(p.stats.games == 0 and p.achievements.is_empty() and p.campaign_stage == 0, "Standardwerte ergänzt") check(p.stats.games == 0 and p.campaign_stage == 0, "Standardwerte ergänzt")
check(p.settings.volume == 0.8 and p.settings.animations, "Einstellungen Standard") check(p.settings.volume == 0.8 and p.settings.animations, "Einstellungen Standard")
p.stats.wins = 3 p.stats.wins = 3
p.achievements.append("erster_sieg")
p.campaign_stage = 2 p.campaign_stage = 2
p.settings.volume = 0.3 p.settings.volume = 0.3
var d := p.to_dict() var d := p.to_dict()
check(d.version == Profile.SAVE_VERSION, "Version gespeichert") check(d.version == Profile.SAVE_VERSION, "Version gespeichert")
var p2 := Profile.from_dict(JSON.parse_string(JSON.stringify(d))) var p2 := Profile.from_dict(JSON.parse_string(JSON.stringify(d)))
check(p2.stats.wins == 3 and p2.achievements == ["erster_sieg"] and p2.campaign_stage == 2, "Version 2 gelesen") check(p2.stats.wins == 3 and p2.campaign_stage == 2, "Version 2 gelesen")
check(is_equal_approx(p2.settings.volume, 0.3), "Einstellungen gelesen") check(is_equal_approx(p2.settings.volume, 0.3), "Einstellungen gelesen")
func test_erfolge_und_tagesaufgabe() -> void: func test_tagesaufgabe() -> void:
Profile.today_override = "2026-09-16" Profile.today_override = "2026-09-16"
var p := Profile.starter() var p := Profile.starter()
var coins := p.coins var coins := p.coins
var reward := p.record_battle(true, 1, p.deck_cards()) var reward := p.record_battle(true, 1, p.deck_cards())
check(reward == Profile.REWARD_WIN[1], "Duell-Belohnung") check(reward == Profile.REWARD_WIN[1], "Duell-Belohnung")
check("erster_sieg" in p.achievements and "duellant" in p.achievements, "Erfolge Erster Sieg und Duellant") check(p.coins == coins + reward + (p.daily_task().reward if p.daily.done else 0), "Münzen verbucht")
check(not ("tischherr" in p.achievements), "Herr des Tisches noch offen")
check(p.coins == coins + reward + 100 + (p.daily_task().reward if p.daily.done else 0), "Münzen inklusive Erfolge")
check(p.notices.size() >= 2, "Meldungen vorhanden")
p.take_notices() p.take_notices()
check(p.notices.is_empty(), "Meldungen abgeholt") check(p.notices.is_empty(), "Meldungen abgeholt")
check(p.daily.date == "2026-09-16" and p.daily.task != "", "Tagesaufgabe gesetzt") check(p.daily.date == "2026-09-16" and p.daily.task != "", "Tagesaufgabe gesetzt")
@@ -690,14 +686,12 @@ func test_erfolge_und_tagesaufgabe() -> void:
p.refresh_daily() p.refresh_daily()
check(p.daily.date == "2026-09-17" and not p.daily.done and p.daily.progress == 0, "neuer Tag, neue Aufgabe") check(p.daily.date == "2026-09-17" and not p.daily.done and p.daily.progress == 0, "neuer Tag, neue Aufgabe")
Profile.today_override = "" Profile.today_override = ""
# Mono-Deck-Erfolg
var mono := [] var mono := []
for cid in ["f_funke", "f_kobold", "f_wolf", "f_feuerball", "f_asche", "f_glutpfeil", "f_elementar"]: for cid in ["f_funke", "f_kobold", "f_wolf", "f_feuerball", "f_asche", "f_glutpfeil", "f_elementar"]:
for i in 3: for i in 3:
mono.append(Cards.by_id(cid)) mono.append(Cards.by_id(cid))
mono = mono.slice(0, 20) mono = mono.slice(0, 20)
p.record_battle(true, 3, mono) p.record_battle(true, 3, mono)
check("elementarist" in p.achievements and "tischherr" in p.achievements, "Elementarist und Herr des Tisches")
check(p.stats.wins == 3 and p.stats.wins_by_opp["1"] == 2, "Statistik") check(p.stats.wins == 3 and p.stats.wins_by_opp["1"] == 2, "Statistik")
@@ -719,7 +713,7 @@ func test_kampagne() -> void:
check(p.campaign_stage == 1 and reward == Profile.REWARD_LOSS, "Niederlage schaltet nichts frei") check(p.campaign_stage == 1 and reward == Profile.REWARD_LOSS, "Niederlage schaltet nichts frei")
p.campaign_stage = 9 p.campaign_stage = 9
p.record_battle(true, 5, p.deck_cards(), 9) p.record_battle(true, 5, p.deck_cards(), 9)
check(p.campaign_stage == 10 and "kampagnenheld" in p.achievements, "Kampagne abgeschlossen") check(p.campaign_stage == 10, "Kampagne abgeschlossen")
var sim := Game.new_battle_with(Profile.starter().deck_cards(), Campaign.opponents(0, r), r) var sim := Game.new_battle_with(Profile.starter().deck_cards(), Campaign.opponents(0, r), r)
var guard := 0 var guard := 0
while sim.winner == "" and guard < 300: while sim.winner == "" and guard < 300:
@@ -1140,7 +1134,7 @@ func test_kapitel_zwei_und_merkmalwahl() -> void:
check(g.human.perks == ["feuer_angriff"] and g.log.any(func(l): return l.begins_with("Du: Feuerwesen")), "eigenes Merkmal wirkt und steht im Verlauf") check(g.human.perks == ["feuer_angriff"] and g.log.any(func(l): return l.begins_with("Du: Feuerwesen")), "eigenes Merkmal wirkt und steht im Verlauf")
p.campaign_stage = 19 p.campaign_stage = 19
p.record_battle(true, 5, p.deck_cards(), 19) p.record_battle(true, 5, p.deck_cards(), 19)
check(p.campaign_stage == 20 and "elementmeister" in p.achievements, "Kapitel 2 abgeschlossen") check(p.campaign_stage == 20, "Kapitel 2 abgeschlossen")
var v := Profile.from_dict(JSON.parse_string(JSON.stringify(p.to_dict()))) var v := Profile.from_dict(JSON.parse_string(JSON.stringify(p.to_dict())))
check(v.perk == "feuer_angriff" and v.campaign_stage == 20, "Version 5 gelesen") check(v.perk == "feuer_angriff" and v.campaign_stage == 20, "Version 5 gelesen")
@@ -1163,7 +1157,6 @@ func test_bestenliste() -> void:
check(p.records.streak == 0 and p.records.best_streak == 2, "Niederlage bricht die Serie") check(p.records.streak == 0 and p.records.best_streak == 2, "Niederlage bricht die Serie")
for i in 5: for i in 5:
p.record_battle(true, 1, mono, -1, [], 10) p.record_battle(true, 1, mono, -1, [], 10)
check("serie" in p.achievements, "Siegesserie-Erfolg")
var path := "user://test_statistik.csv" var path := "user://test_statistik.csv"
check(p.export_stats(path) == "", "Statistik exportiert") check(p.export_stats(path) == "", "Statistik exportiert")
var text := FileAccess.get_file_as_string(path) var text := FileAccess.get_file_as_string(path)

View File

@@ -165,7 +165,7 @@ func _ready() -> void:
await frames(3) await frames(3)
var fast := OS.get_environment("DINGER_FAST") != "" var fast := OS.get_environment("DINGER_FAST") != ""
var suite := [test_menu_und_regeln, test_gamepad_menue, 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_statistik, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2,
test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster] test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster]
if not fast: if not fast:
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen] suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
@@ -202,13 +202,8 @@ func test_booster() -> void:
var scr = screen() var scr = screen()
check(scr is BoosterScreen, "Boosterbildschirm") check(scr is BoosterScreen, "Boosterbildschirm")
var coins: int = main.profile.coins var coins: int = main.profile.coins
var ach_before: int = main.profile.achievements.size()
await click(scr.b_buy) await click(scr.b_buy)
var bonus := 0 check(main.profile.coins == coins - Profile.BOOSTER_COST, "Booster bezahlt")
for a in Profile.ACHIEVEMENTS:
if a.id in main.profile.achievements.slice(ach_before):
bonus += a.reward
check(main.profile.coins == coins - Profile.BOOSTER_COST + bonus, "Booster bezahlt (plus Erfolgsmünzen %d)" % bonus)
check(scr.cards.size() == 5 and scr.revealed.is_empty(), "5 verdeckte Karten") check(scr.cards.size() == 5 and scr.revealed.is_empty(), "5 verdeckte Karten")
await shot("03_booster_zu") await shot("03_booster_zu")
var backs: Array = scr.slots.get_children().filter(func(c): return c is BoosterScreen.CardBack) var backs: Array = scr.slots.get_children().filter(func(c): return c is BoosterScreen.CardBack)
@@ -573,15 +568,15 @@ func test_einstellungen() -> void:
check(screen() is MenuScreen, "zurück im Menü") check(screen() is MenuScreen, "zurück im Menü")
func test_erfolge() -> void: func test_statistik() -> void:
await click(find_button("Erfolge")) await click(find_button("Statistik"))
check(screen() is AchievementsScreen, "Erfolge geöffnet") check(screen() is StatsScreen, "Statistik geöffnet")
var csv := "user://statistik.csv" var csv := "user://statistik.csv"
if FileAccess.file_exists(csv): if FileAccess.file_exists(csv):
DirAccess.remove_absolute(ProjectSettings.globalize_path(csv)) DirAccess.remove_absolute(ProjectSettings.globalize_path(csv))
await click(find_button("Statistik exportieren")) await click(find_button("Statistik exportieren"))
check(FileAccess.file_exists(csv) and "Gespeichert" in screen().export_label.text, "Statistik exportiert") check(FileAccess.file_exists(csv) and "Gespeichert" in screen().export_label.text, "Statistik exportiert")
await shot("14_erfolge") await shot("14_statistik")
await click(find_button("Zurück")) await click(find_button("Zurück"))
check(screen() is MenuScreen, "zurück im Menü") check(screen() is MenuScreen, "zurück im Menü")

View File

@@ -7,7 +7,6 @@ Netzwerkspiel,Network game
Booster öffnen,Open boosters Booster öffnen,Open boosters
Deck bauen,Build deck Deck bauen,Build deck
Werkstatt,Workshop Werkstatt,Workshop
Erfolge,Achievements
Regeln,Rules Regeln,Rules
Einstellungen,Settings Einstellungen,Settings
Anleitung,Tutorial Anleitung,Tutorial
@@ -209,3 +208,4 @@ Starten,Start
%d Gegner,%d opponents %d Gegner,%d opponents
Übernehmen,Apply Übernehmen,Apply
KI-Gegner,AI opponents KI-Gegner,AI opponents
Statistik,Statistics
1 keys en
7 Booster öffnen Open boosters
8 Deck bauen Build deck
9 Werkstatt Workshop
Erfolge Achievements
10 Regeln Rules
11 Einstellungen Settings
12 Anleitung Tutorial
208 %d Gegner %d opponents
209 Übernehmen Apply
210 KI-Gegner AI opponents
211 Statistik Statistics

Binary file not shown.