diff --git a/README.md b/README.md index 4ed6c97..e8850b0 100644 --- a/README.md +++ b/README.md @@ -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 - 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) -- 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) - Spielstand versioniert mit Sicherungskopie (`spielstand.json.bak`) - 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`: 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). -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) @@ -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/netstate.gd` – Zustandsbilder fürs Netzwerk - `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/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 - `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden - `translations/ui.csv` – englische Oberflächentexte diff --git a/anforderungen.md b/anforderungen.md index 76cb55e..bcc9986 100644 --- a/anforderungen.md +++ b/anforderungen.md @@ -5,7 +5,7 @@ - PC-Spiel - Jeder gegen jeden: Spieler und 1 bis 5 KI-Gegner (oder Spieler), alle mit gleichen Regeln - 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 - Ein unterbrochener Kampf lässt sich später fortsetzen - Geführte erste Partie als Einstieg diff --git a/scripts/profile.gd b/scripts/profile.gd index 78dae17..f38b6ef 100644 --- a/scripts/profile.gd +++ b/scripts/profile.gd @@ -1,6 +1,6 @@ class_name Profile 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 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_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 := [ {"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}, @@ -63,11 +47,10 @@ var collection := {} # id -> Anzahl var decks: Array = [] # DECK_SLOTS Einträge {"name": String, "cards": Array von ids} var active_deck := 0 var stats := {"games": 0, "wins": 0, "boosters": 0, "wins_by_opp": {}} -var achievements: Array = [] var campaign_stage := 0 # Anzahl abgeschlossener Kampagnenstufen 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 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 booster_since_mythic := 0 var tutorial_done := false @@ -151,8 +134,6 @@ static func from_dict(d: Dictionary) -> Profile: p.stats.boosters = int(st.get("boosters", 0)) for k in st.get("wins_by_opp", {}): 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)) var dl = d.get("daily", {}) 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: return {"version": SAVE_VERSION, "coins": coins, "dust": dust, "collection": collection, "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, "booster_since_mythic": booster_since_mythic, "tutorial_done": tutorial_done, "perk": perk, "records": records, "tutorial2_done": tutorial2_done} @@ -320,7 +301,6 @@ func craft(cid: String) -> String: dust -= cost collection[cid] = collection.get(cid, 0) + 1 daily_event("craft") - check_achievements() return "" @@ -404,11 +384,10 @@ func open_booster(r: RandomNumberGenerator = null, element: String = "") -> Arra collection[c.id] = collection.get(c.id, 0) + 1 stats.boosters += 1 daily_event("booster") - check_achievements() return cards -# --- Kampfergebnis, Erfolge, Tagesaufgabe ------------------------------------------- +# --- Kampfergebnis, Tagesaufgabe ------------------------------------------- ## Verbucht ein Kampfergebnis und gibt die Münzbelohnung zurück. ## 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") for e in Game.deck_elements(deck_used): daily_event("win_element:" + e) - check_achievements({"won": won, "n_opp": n_opp, "deck": deck_used}) return reward @@ -469,53 +447,6 @@ func _stat(cid: String) -> Dictionary: 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: return DIFFICULTIES.get(settings.get("difficulty", "normal"), DIFFICULTIES.normal) diff --git a/scripts/ui/achievements_screen.gd.uid b/scripts/ui/achievements_screen.gd.uid deleted file mode 100644 index a1cfb54..0000000 --- a/scripts/ui/achievements_screen.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cw4rtcukefdl8 diff --git a/scripts/ui/menu_screen.gd b/scripts/ui/menu_screen.gd index 886a7ae..53825ee 100644 --- a/scripts/ui/menu_screen.gd +++ b/scripts/ui/menu_screen.gd @@ -46,7 +46,7 @@ func _ready() -> void: 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, "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, "Einstellungen", sz, func(): app.show_screen(SettingsScreen.new())) Style.cbutton(grid, "Beenden", sz, func(): get_tree().quit()) diff --git a/scripts/ui/achievements_screen.gd b/scripts/ui/stats_screen.gd similarity index 67% rename from scripts/ui/achievements_screen.gd rename to scripts/ui/stats_screen.gd index 2ca1f1d..05dc013 100644 --- a/scripts/ui/achievements_screen.gd +++ b/scripts/ui/stats_screen.gd @@ -1,5 +1,6 @@ -class_name AchievementsScreen +class_name StatsScreen extends Control +## Statistik: Kämpfe, Tagesaufgabe, Bestenliste, beste Karten und CSV-Export. var app var export_label: Label @@ -8,7 +9,7 @@ var export_label: Label func _ready() -> void: var p: Profile = app.profile 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 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) @@ -19,17 +20,6 @@ func _ready() -> void: 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) 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) if not top.is_empty(): Style.spacer(col, 4) @@ -52,5 +42,5 @@ func export_stats() -> 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()) diff --git a/scripts/ui/stats_screen.gd.uid b/scripts/ui/stats_screen.gd.uid new file mode 100644 index 0000000..4704774 --- /dev/null +++ b/scripts/ui/stats_screen.gd.uid @@ -0,0 +1 @@ +uid://t0h7fpmpeuq4 diff --git a/tests/test_runner.gd b/tests/test_runner.gd index fea35f1..0d51eae 100644 --- a/tests/test_runner.gd +++ b/tests/test_runner.gd @@ -93,7 +93,7 @@ func _initialize() -> void: test_ki_besser_als_gierig() test_ki_spiele_enden() test_spielstand_migration() - test_erfolge_und_tagesaufgabe() + test_tagesaufgabe() test_kampagne() test_fallen() 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 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.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") p.stats.wins = 3 - p.achievements.append("erster_sieg") p.campaign_stage = 2 p.settings.volume = 0.3 var d := p.to_dict() check(d.version == Profile.SAVE_VERSION, "Version gespeichert") 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") -func test_erfolge_und_tagesaufgabe() -> void: +func test_tagesaufgabe() -> void: Profile.today_override = "2026-09-16" var p := Profile.starter() var coins := p.coins var reward := p.record_battle(true, 1, p.deck_cards()) 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(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") + check(p.coins == coins + reward + (p.daily_task().reward if p.daily.done else 0), "Münzen verbucht") p.take_notices() check(p.notices.is_empty(), "Meldungen abgeholt") 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() check(p.daily.date == "2026-09-17" and not p.daily.done and p.daily.progress == 0, "neuer Tag, neue Aufgabe") Profile.today_override = "" - # Mono-Deck-Erfolg var mono := [] for cid in ["f_funke", "f_kobold", "f_wolf", "f_feuerball", "f_asche", "f_glutpfeil", "f_elementar"]: for i in 3: mono.append(Cards.by_id(cid)) mono = mono.slice(0, 20) 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") @@ -719,7 +713,7 @@ func test_kampagne() -> void: check(p.campaign_stage == 1 and reward == Profile.REWARD_LOSS, "Niederlage schaltet nichts frei") p.campaign_stage = 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 guard := 0 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") p.campaign_stage = 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()))) 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") for i in 5: p.record_battle(true, 1, mono, -1, [], 10) - check("serie" in p.achievements, "Siegesserie-Erfolg") var path := "user://test_statistik.csv" check(p.export_stats(path) == "", "Statistik exportiert") var text := FileAccess.get_file_as_string(path) diff --git a/tests/ui_test.gd b/tests/ui_test.gd index 3575e98..2a7fe0e 100644 --- a/tests/ui_test.gd +++ b/tests/ui_test.gd @@ -165,7 +165,7 @@ func _ready() -> void: 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_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] if not fast: suite += [test_kampf.bind(2, 3), test_kampf_abbrechen] @@ -202,13 +202,8 @@ func test_booster() -> void: var scr = screen() check(scr is BoosterScreen, "Boosterbildschirm") var coins: int = main.profile.coins - var ach_before: int = main.profile.achievements.size() await click(scr.b_buy) - var bonus := 0 - 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(main.profile.coins == coins - Profile.BOOSTER_COST, "Booster bezahlt") check(scr.cards.size() == 5 and scr.revealed.is_empty(), "5 verdeckte Karten") await shot("03_booster_zu") 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ü") -func test_erfolge() -> void: - await click(find_button("Erfolge")) - check(screen() is AchievementsScreen, "Erfolge geöffnet") +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_erfolge") + await shot("14_statistik") await click(find_button("Zurück")) check(screen() is MenuScreen, "zurück im Menü") diff --git a/translations/ui.csv b/translations/ui.csv index 831ef1b..bcdf836 100644 --- a/translations/ui.csv +++ b/translations/ui.csv @@ -7,7 +7,6 @@ Netzwerkspiel,Network game Booster öffnen,Open boosters Deck bauen,Build deck Werkstatt,Workshop -Erfolge,Achievements Regeln,Rules Einstellungen,Settings Anleitung,Tutorial @@ -209,3 +208,4 @@ Starten,Start %d Gegner,%d opponents Übernehmen,Apply KI-Gegner,AI opponents +Statistik,Statistics diff --git a/translations/ui.en.translation b/translations/ui.en.translation index dfd34b4..fe8cbf1 100644 Binary files a/translations/ui.en.translation and b/translations/ui.en.translation differ