Gegnerauswahl und Kampagne aufgeräumt, Hotseat entfernt
- Einzelspieler: Schwierigkeit, darunter nur die zwei Deckstärken, Gegnerzahl als Schieberegler, Starten und Zurück; kein Würfeln, die Gegnerdecks entstehen beim Start - Kampagne: nur noch Name, Elementpunkte und Knopf, mit Innenabstand in den Zeilen - Hotseat (Lokal zu mehreren) samt Übergabe-Vorhang, Auswahlbildschirm und Tests entfernt - Netzwerktests laufen nur mit DINGER_NETZ=1, weil die Bot-Prozesse Minuten brauchen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq xvfb libgl1 libegl1
|
||||
- name: Import
|
||||
run: ./godot --headless --path . --import || true
|
||||
- name: Fenster-Test (voll, mit Netzwerk und Hotseat)
|
||||
- name: Fenster-Test (voll)
|
||||
run: xvfb-run -a --server-args="-screen 0 1280x800x24" ./godot --path . tests/ui_test.tscn 2>&1 | tee fenster.log | grep -E "FEHLER|Prüfungen" || true
|
||||
- name: Ergebnis
|
||||
run: grep -q " 0 fehlgeschlagen" fenster.log
|
||||
|
||||
10
README.md
10
README.md
@@ -29,7 +29,6 @@ Der Spielstand liegt unter `user://spielstand.json`
|
||||
- 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
|
||||
- Lokal zu mehreren: bis zu 4 Menschen am selben Rechner plus KI
|
||||
- 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 +65,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 Hotseat, Netzwerk und lange 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.
|
||||
|
||||
```
|
||||
@@ -76,7 +75,8 @@ godot --headless --path . -s tests/simulate.gd # Balance: KI spielt gegen K
|
||||
godot --headless --path . -s tests/matchup.gd # Startvorteil und Mono gegen Paar
|
||||
godot --headless --path . -s tests/strength_probe.gd # Stärke der Elementkombinationen messen (Werte in DeckGen.ARCHETYPE_STRENGTH)
|
||||
DINGER_SHOTS=/tmp/shots godot --path . tests/ui_test.tscn # Oberfläche mit Fenster und Screenshots
|
||||
DINGER_ONLY=test_host_wechsel,test_zuschauer godot --path . tests/ui_test.tscn # nur einzelne Oberflächentests
|
||||
DINGER_ONLY=test_deckbau godot --path . tests/ui_test.tscn # nur einzelne Oberflächentests
|
||||
DINGER_NETZ=1 godot --path . tests/ui_test.tscn # zusätzlich die langsamen Netzwerktests (Bot-Prozesse)
|
||||
N=200 godot --headless --path . -s tests/strength_probe.gd # Messlauf mit 200 statt 60 Partien je Archetyp
|
||||
godot --headless --path . -s tests/perf_probe.gd # Laufzeiten: KI-Entscheidung, Deckerzeugung, Musik
|
||||
godot --path . tests/frame_probe.tscn # Bildzeiten im Kampf mit 5 Gegnern
|
||||
@@ -94,13 +94,13 @@ godot --headless --path . --export-release Windows build/dinger-windows.exe
|
||||
## Aufbau
|
||||
|
||||
- `scripts/cards.gd` – Elemente, Kombos, Kartenklassen; Pool aus `data/karten.json`
|
||||
- `scripts/game.gd` – Spielregeln, Züge, Kampf, Fallen, Feldkombos, Resonanz, Deckregeln, Hotseat
|
||||
- `scripts/game.gd` – Spielregeln, Züge, Kampf, Fallen, Feldkombos, Resonanz, Deckregeln
|
||||
- `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/campaign.gd` – Kampagnenstufen
|
||||
- `scripts/ui/` – Oberfläche: Menü, Gegnerauswahl, Kampagne, Hotseat, 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), Erfolge, Einstellungen, Regeln, Ton
|
||||
- `scenes/main.tscn` – Startszene
|
||||
- `scripts/net.gd` – Netzwerkspiel (ENet) mit Wiederverbinden
|
||||
- `scripts/challenge.gd` – wöchentliche Herausforderungen
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- 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
|
||||
- Lokal zu mehreren am selben Rechner und im lokalen Netz möglich, auch als Zuschauer
|
||||
- Im lokalen Netz zu mehreren spielbar, auch als Zuschauer
|
||||
- Ein unterbrochener Kampf lässt sich später fortsetzen
|
||||
- Geführte erste Partie als Einstieg
|
||||
- Wöchentliche Herausforderungen und Bestenliste als Langzeitziel
|
||||
|
||||
@@ -156,7 +156,7 @@ class Player extends RefCounted:
|
||||
|
||||
|
||||
var human: Player # der (erste) menschliche Spieler
|
||||
var humans: Array = [] # alle menschlichen Spieler (Hotseat: mehrere)
|
||||
var humans: Array = [] # alle menschlichen Spieler (im Netz mehrere)
|
||||
var opponents: Array = [] # alle außer dem ersten Menschen
|
||||
var players: Array = [] # in Zugreihenfolge
|
||||
var winner_player: Player = null
|
||||
@@ -746,19 +746,3 @@ static func new_battle_with(deck: Array, opps: Array, r: RandomNumberGenerator =
|
||||
p.max_life = 35
|
||||
g.start(fixed_order)
|
||||
return g
|
||||
|
||||
|
||||
## Hotseat: mehrere Menschen am selben Rechner, dazu KI-Gegner.
|
||||
static func new_hotseat(human_decks: Array, n_ai: int, r: RandomNumberGenerator = null) -> Game:
|
||||
assert(human_decks.size() >= 1 and human_decks.size() + n_ai <= MAX_OPPONENTS + 1, "höchstens 6 am Tisch")
|
||||
if r == null:
|
||||
r = make_rng()
|
||||
var first := Player.new("Spieler 1", human_decks[0], true, START_LIFE)
|
||||
var others := []
|
||||
for i in range(1, human_decks.size()):
|
||||
others.append(Player.new("Spieler %d" % (i + 1), human_decks[i], true, START_LIFE))
|
||||
for i in n_ai:
|
||||
others.append(random_opponent(r, i + 1))
|
||||
var g := Game.new(first, others, r)
|
||||
g.start()
|
||||
return g
|
||||
|
||||
@@ -64,9 +64,6 @@ func move(dir: int) -> void:
|
||||
|
||||
|
||||
func confirm() -> void:
|
||||
if screen.handover.visible:
|
||||
screen.take_over()
|
||||
return
|
||||
if screen.mulligan.visible:
|
||||
screen.do_mulligan([])
|
||||
return
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
class_name BattleScreen
|
||||
extends Control
|
||||
## Kampf: Zustand, Eingabe und Abgleich der Ansicht. Layout in BattleLayout, Animationen in BattleFx.
|
||||
## Modi: Einzelspieler, Kampagne, Anleitung, Hotseat (Perspektivwechsel), Netzwerk (Host oder Mitspieler).
|
||||
## Modi: Einzelspieler, Kampagne, Anleitung, Netzwerk (Host, Mitspieler oder Zuschauer).
|
||||
|
||||
var app
|
||||
var n_opp: int
|
||||
var stage := -1 # Kampagnenstufe oder -1
|
||||
var tutorial := false
|
||||
var hotseat_decks: Array = []
|
||||
var hotseat_ai := 0
|
||||
var net_play := false # Netzwerkspiel (auch als Zuschauer)
|
||||
var online_seat := -1 # Netzwerk: eigener Platz, Zuschauer -1
|
||||
var net: Net
|
||||
@@ -41,7 +39,6 @@ var gp_index: int:
|
||||
get:
|
||||
return pad.index
|
||||
var challenge := {} # wöchentliche Herausforderung oder leer
|
||||
var preset_opponents: Array = [] # aus der Gegnervorschau übernommene Gegner
|
||||
var skip_ai := false # Gegnerzüge im Schnelldurchlauf
|
||||
var smart_ai := true # Vorausschau-KI (Leicht: einfache KI)
|
||||
var undo_state: Game = null # Zustand vor der letzten rückgängig machbaren Aktion
|
||||
@@ -83,8 +80,6 @@ var reward_label: Label
|
||||
var notice_label: Label
|
||||
var confirm: Control
|
||||
var b_save_leave: Button
|
||||
var handover: Control
|
||||
var handover_label: Label
|
||||
var mulligan: BattleMulligan
|
||||
var mulligan_box: Control:
|
||||
get:
|
||||
@@ -129,13 +124,6 @@ func _init(n: int = 2, campaign_stage: int = -1) -> void:
|
||||
stage = campaign_stage
|
||||
|
||||
|
||||
static func hotseat(decks: Array, n_ai: int) -> BattleScreen:
|
||||
var b := BattleScreen.new(0, -1)
|
||||
b.hotseat_decks = decks
|
||||
b.hotseat_ai = n_ai
|
||||
return b
|
||||
|
||||
|
||||
static func online(seat: int) -> BattleScreen:
|
||||
var b := BattleScreen.new(0, -1)
|
||||
b.net_play = true
|
||||
@@ -150,12 +138,6 @@ static func guided(part: int = 1) -> BattleScreen:
|
||||
return b
|
||||
|
||||
|
||||
static func against(opps: Array) -> BattleScreen:
|
||||
var b := BattleScreen.new(opps.size(), -1)
|
||||
b.preset_opponents = opps
|
||||
return b
|
||||
|
||||
|
||||
## Unterbrochenen Kampf aus der Datei fortsetzen (siehe save_battle).
|
||||
static func resume(data: Dictionary) -> BattleScreen:
|
||||
var s := BattleScreen.new(int(data.get("n_opp", 1)), int(data.get("stage", -1)))
|
||||
@@ -171,10 +153,6 @@ static func weekly(ch: Dictionary) -> BattleScreen:
|
||||
return b
|
||||
|
||||
|
||||
func is_hotseat() -> bool:
|
||||
return game != null and game.humans.size() > 1 and not net_play
|
||||
|
||||
|
||||
func is_online() -> bool:
|
||||
return net_play
|
||||
|
||||
@@ -189,7 +167,7 @@ func is_client() -> bool:
|
||||
|
||||
|
||||
func counts_for_profile() -> bool:
|
||||
return not is_hotseat() and not is_online()
|
||||
return not is_online()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -213,8 +191,6 @@ func _ready() -> void:
|
||||
return # der erste Zustand kommt vom Host
|
||||
elif saved_game != null:
|
||||
game = saved_game
|
||||
elif not hotseat_decks.is_empty():
|
||||
game = Game.new_hotseat(hotseat_decks, hotseat_ai)
|
||||
elif tutorial:
|
||||
guide = BattleTutorial.new(self, tutorial_part)
|
||||
var r := Game.make_rng()
|
||||
@@ -229,8 +205,6 @@ func _ready() -> void:
|
||||
elif stage >= 0:
|
||||
var r := Game.make_rng()
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), Campaign.opponents(stage, r, app.profile.target_rating()), r, false, own_rules())
|
||||
elif not preset_opponents.is_empty():
|
||||
game = Game.new_battle_with(app.profile.deck_cards(), preset_opponents, null, false, own_rules())
|
||||
else:
|
||||
game = Game.new_battle(app.profile.deck_cards(), n_opp, null, own_rules())
|
||||
smart_ai = bool(app.profile.difficulty().smart) or not counts_for_profile()
|
||||
@@ -336,12 +310,6 @@ func _build_static() -> void:
|
||||
b_save_leave = Style.button(confirm, "Speichern und zurück", Rect2(550, 420, 180, 46), save_and_leave, Color("285a3c"))
|
||||
b_save_leave.add_theme_font_size_override("font_size", 14)
|
||||
Style.button(confirm, "Weiterspielen", Rect2(745, 420, 180, 46), func(): confirm.visible = false)
|
||||
# Übergabe zwischen Menschen (Hotseat)
|
||||
handover = _overlay()
|
||||
Style.panel(handover, Rect2(340, 280, 600, 220), Style.PANEL, 10, Style.GOLD, 2)
|
||||
handover_label = Style.label(handover, "", 30, Style.TEXT, Rect2(340, 310, 600, 44), "center", true)
|
||||
Style.label(handover, "Maus weitergeben, dann weiter. Die Hand des Vorgängers ist verdeckt.", 16, Style.MUTED, Rect2(340, 362, 600, 24), "center")
|
||||
Style.button(handover, "Weiter", Rect2(540, 420, 200, 50), take_over, Color("285a3c"))
|
||||
|
||||
|
||||
func _layer() -> Control:
|
||||
@@ -475,7 +443,7 @@ func my_turn() -> bool:
|
||||
|
||||
|
||||
func any_overlay() -> bool:
|
||||
return confirm.visible or handover.visible or mulligan.visible or overlay.visible
|
||||
return confirm.visible or mulligan.visible or overlay.visible
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -506,9 +474,7 @@ func _input(event: InputEvent) -> void:
|
||||
if b_undo.visible:
|
||||
undo()
|
||||
KEY_ENTER, KEY_KP_ENTER:
|
||||
if handover.visible:
|
||||
take_over()
|
||||
elif mulligan.visible:
|
||||
if mulligan.visible:
|
||||
do_mulligan([])
|
||||
elif overlay.visible:
|
||||
leave()
|
||||
@@ -552,12 +518,7 @@ func _process(delta: float) -> void:
|
||||
return
|
||||
var cur := game.current()
|
||||
if cur.is_human:
|
||||
if cur != perspective and is_hotseat() and not handover.visible:
|
||||
handover_label.text = "%s ist dran" % cur.name
|
||||
handover.visible = true
|
||||
hand_layer.visible = false
|
||||
trap_layer.visible = false
|
||||
elif cur == perspective and not spectating() and cur.turns == 1 and not cur.mulligan_done and not mulligan.visible and not handover.visible:
|
||||
if cur == perspective and not spectating() and cur.turns == 1 and not cur.mulligan_done and not mulligan.visible:
|
||||
show_mulligan()
|
||||
return
|
||||
if is_client():
|
||||
@@ -623,17 +584,6 @@ func _finish() -> void:
|
||||
refresh()
|
||||
|
||||
|
||||
func take_over() -> void:
|
||||
handover.visible = false
|
||||
hand_layer.visible = true
|
||||
trap_layer.visible = true
|
||||
perspective = game.current()
|
||||
cancel()
|
||||
zoom_player = null
|
||||
build_hero_boxes()
|
||||
refresh()
|
||||
|
||||
|
||||
func say(m: String) -> void:
|
||||
msg_t = 2.5
|
||||
msg_label.text = m
|
||||
@@ -1110,7 +1060,6 @@ func refresh() -> void:
|
||||
reward_label.text = "Gemeinsame Partie, keine Münzen"
|
||||
overlay.visible = true
|
||||
confirm.visible = false
|
||||
handover.visible = false
|
||||
mulligan.visible = false
|
||||
|
||||
|
||||
|
||||
@@ -36,10 +36,6 @@ func build() -> void:
|
||||
p.perk = "" if i == 0 else keys[i - 1]
|
||||
app.save())
|
||||
pr.add_child(perk_box)
|
||||
var intro := ["Zehn Magier je Kapitel. Jede Stufe bringt mehr Gegner an den Tisch. Bosse haben Merkmale (★).",
|
||||
"Die Magier kehren mit zwei Merkmalen und größerem Gefolge zurück.",
|
||||
"Kapitel 3: drei Merkmale, volle Tische und die stärksten Decks."]
|
||||
Style.clabel(col, intro[chapter - 1], 14, Style.MUTED)
|
||||
var offset := (chapter - 1) * Campaign.STAGES.size()
|
||||
for k in Campaign.STAGES.size():
|
||||
var i := offset + k
|
||||
@@ -50,31 +46,27 @@ func build() -> void:
|
||||
panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL if not done else Style.darker(Style.PANEL, 0.8), 6,
|
||||
Style.GOLD if next else null, 2 if next else 0))
|
||||
col.add_child(panel)
|
||||
var r := Style.row(panel, 10)
|
||||
var pad := MarginContainer.new() # Innenabstand, damit Name und Knopf nicht am Rahmen kleben
|
||||
for side in ["left", "right"]:
|
||||
pad.add_theme_constant_override("margin_" + side, 14)
|
||||
panel.add_child(pad)
|
||||
var r := Style.row(pad, 10)
|
||||
r.custom_minimum_size = Vector2(0, 50)
|
||||
Style.clabel(r, str(i + 1), 18, Style.GOLD if next else Style.MUTED, "center", true, 36)
|
||||
Style.clabel(r, "%s · %s" % [st.name, st.title], 16, Style.TEXT if not done else Style.MUTED, "left", true, 290)
|
||||
var nm := Style.clabel(r, "%s · %s" % [st.name, st.title], 16, Style.TEXT if not done else Style.MUTED, "left", true, 420)
|
||||
nm.size_flags_horizontal = Control.SIZE_EXPAND_FILL # schiebt die Punkte und den Knopf nach rechts
|
||||
var dots := Control.new()
|
||||
dots.custom_minimum_size = Vector2(50, 0)
|
||||
dots.custom_minimum_size = Vector2(60, 0)
|
||||
dots.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var elems: Array = st.elements
|
||||
dots.draw.connect(func():
|
||||
for m in elems.size():
|
||||
dots.draw_circle(Vector2(10 + m * 18, dots.size.y / 2), 7, Cards.ELEMENT_COLORS[elems[m]]))
|
||||
r.add_child(dots)
|
||||
var extra: int = mini(st.extra, Game.MAX_OPPONENTS - 1)
|
||||
var table: String = "Duell" if extra == 0 else "%d am Tisch" % (extra + 2)
|
||||
var perks_text: String = ", ".join(st.perks.map(func(pk): return Game.PERKS[pk]))
|
||||
var target := Campaign.target_rating(i, p.target_rating())
|
||||
Style.clabel(r, "%s · Stärke %.1f · %d Münzen\n★ %s" % [table, target, st.reward, perks_text], 12, Style.MUTED, "left", false, 360)
|
||||
if done:
|
||||
Style.clabel(r, "abgeschlossen", 13, Style.GREEN, "right", false, 100)
|
||||
Style.cbutton(r, "Wiederholen", Vector2(130, 36), _start.bind(i))
|
||||
elif next:
|
||||
Style.clabel(r, "", 13, Style.GREEN, "right", false, 100)
|
||||
Style.cbutton(r, "Kämpfen", Vector2(130, 36), _start.bind(i), Color("285a3c"))
|
||||
else:
|
||||
Style.clabel(r, "", 13, Style.GREEN, "right", false, 100)
|
||||
var b := Style.cbutton(r, "Gesperrt", Vector2(130, 36), func(): pass)
|
||||
b.disabled = true
|
||||
if p.campaign_stage >= Campaign.total():
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
class_name HotseatScreen
|
||||
extends Control
|
||||
## Lokal zu mehreren am selben Rechner: Spielerzahl, KI-Zahl und Decks wählen.
|
||||
|
||||
var app
|
||||
var n_humans := 2
|
||||
var n_ai := 0
|
||||
var deck_choice: Array = [] # je Mensch: Slot-Index oder -1 für Zufallsdeck
|
||||
var human_buttons: Array = []
|
||||
var ai_buttons: Array = []
|
||||
var deck_rows: VBoxContainer
|
||||
var b_start: Button
|
||||
var hint: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var col := Style.column(self, 760, 40, 10)
|
||||
Style.clabel(col, "Lokal zu mehreren", 36, Style.GOLD, "center", true)
|
||||
Style.clabel(col, "Alle spielen am selben Rechner und reichen die Maus weiter. Jeder gegen jeden, ohne Münzen.", 16, Style.MUTED)
|
||||
Style.spacer(col, 10)
|
||||
Style.clabel(col, "Spieler", 20, Style.TEXT, "center", true)
|
||||
var hr := Style.row(col, 10)
|
||||
hr.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
for n in range(2, 5):
|
||||
human_buttons.append(Style.cbutton(hr, "%d Spieler" % n, Vector2(150, 44), func(): set_humans(n)))
|
||||
Style.clabel(col, "KI-Gegner", 20, Style.TEXT, "center", true)
|
||||
var ar := Style.row(col, 10)
|
||||
ar.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
for n in range(0, 5):
|
||||
ai_buttons.append(Style.cbutton(ar, "%d KI" % n if n > 0 else "keine KI", Vector2(120, 44), func(): set_ai(n)))
|
||||
Style.spacer(col, 6)
|
||||
Style.clabel(col, "Decks", 20, Style.TEXT, "center", true)
|
||||
deck_rows = VBoxContainer.new()
|
||||
deck_rows.add_theme_constant_override("separation", 6)
|
||||
col.add_child(deck_rows)
|
||||
Style.spacer(col, 6)
|
||||
hint = Style.clabel(col, "", 15, Style.ORANGE)
|
||||
var br := Style.row(col, 20)
|
||||
br.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
b_start = Style.cbutton(br, "Start", Vector2(200, 48), start, Color("285a3c"))
|
||||
Style.cbutton(br, "Zurück", Vector2(160, 48), func(): app.show_screen(MenuScreen.new()))
|
||||
for i in 4:
|
||||
deck_choice.append(-1)
|
||||
var p: Profile = app.profile
|
||||
if p.deck_valid()[0]:
|
||||
deck_choice[0] = p.active_deck
|
||||
refresh()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||
app.show_screen(MenuScreen.new())
|
||||
|
||||
|
||||
func set_humans(n: int) -> void:
|
||||
n_humans = n
|
||||
n_ai = mini(n_ai, Game.MAX_OPPONENTS + 1 - n_humans)
|
||||
refresh()
|
||||
|
||||
|
||||
func set_ai(n: int) -> void:
|
||||
n_ai = n
|
||||
refresh()
|
||||
|
||||
|
||||
func valid_slots() -> Array:
|
||||
var out := []
|
||||
for i in Profile.DECK_SLOTS:
|
||||
if app.profile.deck_valid(i)[0]:
|
||||
out.append(i)
|
||||
return out
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
var p: Profile = app.profile
|
||||
for i in human_buttons.size():
|
||||
human_buttons[i].disabled = (i + 2) == n_humans
|
||||
for i in ai_buttons.size():
|
||||
ai_buttons[i].disabled = i == n_ai or n_humans + i > Game.MAX_OPPONENTS + 1
|
||||
Style.clear(deck_rows)
|
||||
var slots := valid_slots()
|
||||
for h in n_humans:
|
||||
var r := Style.row(deck_rows, 12)
|
||||
r.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
Style.clabel(r, "Spieler %d" % (h + 1), 16, Style.TEXT, "right", true, 140)
|
||||
var ob := OptionButton.new()
|
||||
ob.custom_minimum_size = Vector2(340, 36)
|
||||
ob.add_item("Zufallsdeck", -1)
|
||||
for slot in slots:
|
||||
var elems := Game.deck_elements(p.deck_cards(slot))
|
||||
ob.add_item("%s (%s)" % [p.deck_name(slot), ", ".join(elems)], slot)
|
||||
ob.select(ob.get_item_index(deck_choice[h]) if deck_choice[h] >= 0 and slots.has(deck_choice[h]) else 0)
|
||||
var idx := h
|
||||
ob.item_selected.connect(func(i: int): deck_choice[idx] = ob.get_item_id(i))
|
||||
r.add_child(ob)
|
||||
hint.text = "%d am Tisch" % (n_humans + n_ai)
|
||||
|
||||
|
||||
func start() -> void:
|
||||
var p: Profile = app.profile
|
||||
var r := Game.make_rng()
|
||||
var decks := []
|
||||
for h in n_humans:
|
||||
var slot: int = deck_choice[h]
|
||||
if slot >= 0 and p.deck_valid(slot)[0]:
|
||||
decks.append(p.deck_cards(slot))
|
||||
else:
|
||||
var pool: Array = Cards.ELEMENTS.duplicate()
|
||||
Game.shuffle(pool, r)
|
||||
decks.append(Game.build_opponent_deck(r, [pool[0], pool[1]]))
|
||||
app.show_screen(BattleScreen.hotseat(decks, n_ai))
|
||||
@@ -1 +0,0 @@
|
||||
uid://crlne4y0uiygl
|
||||
@@ -4,7 +4,6 @@ extends Control
|
||||
var app
|
||||
var b_single: Button
|
||||
var b_campaign: Button
|
||||
var b_hotseat: Button
|
||||
var b_online: Button
|
||||
var b_tutorial: Button
|
||||
var b_challenge: Button
|
||||
@@ -44,7 +43,6 @@ func _ready() -> void:
|
||||
Color("5a4628") if not (p0.tutorial_done and p0.tutorial2_done) else null)
|
||||
b_single = Style.cbutton(grid, "Einzelspieler", sz, func(): app.show_screen(OpponentSelectScreen.new()), Color("285a3c"))
|
||||
b_campaign = Style.cbutton(grid, "Kampagne", sz, func(): app.show_screen(CampaignScreen.new()), Color("285a3c"))
|
||||
b_hotseat = Style.cbutton(grid, "Lokal zu mehreren", sz, func(): app.show_screen(HotseatScreen.new()), Color("285a3c"))
|
||||
b_online = Style.cbutton(grid, "Netzwerkspiel", sz, func(): app.show_screen(LobbyScreen.new()), Color("285a3c"))
|
||||
b_challenge = Style.cbutton(grid, "Herausforderung", sz, func(): app.show_screen(ChallengeScreen.new()), Color("285a3c"))
|
||||
Style.cbutton(grid, "Booster öffnen", sz, func(): app.show_screen(BoosterScreen.new()))
|
||||
|
||||
@@ -1,109 +1,79 @@
|
||||
class_name OpponentSelectScreen
|
||||
extends Control
|
||||
## Einzelspieler: Schwierigkeit, Gegnerzahl und Vorschau der Gegner (Element, Stärke).
|
||||
## Einzelspieler: Schwierigkeit wählen und die Gegnerzahl schieben. Die Gegnerdecks
|
||||
## werden erst beim Start zufällig gezogen (siehe BattleScreen und DeckGen).
|
||||
|
||||
var app
|
||||
var rating_label: Label
|
||||
var tip_label: Label
|
||||
var diff_buttons := {}
|
||||
var previews := {} # n -> Array von Game.Player
|
||||
var preview_labels := {}
|
||||
var b_tip: Button
|
||||
var b_reroll: Button
|
||||
var rerolls_left := 3
|
||||
var count_slider: HSlider
|
||||
var count_label: Label
|
||||
var info_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var col := Style.column(self, 1000, 30, 10)
|
||||
var col := Style.column(self, 1000, 60, 14)
|
||||
Style.clabel(col, "Einzelspieler", 36, Style.GOLD, "center", true)
|
||||
Style.clabel(col, "Jeder kämpft gegen jeden, alle nach denselben Regeln mit %d Leben. Du gewinnst, wenn alle anderen besiegt sind." % Game.START_LIFE, 15, Style.MUTED)
|
||||
Style.spacer(col, 10)
|
||||
Style.clabel(col, "Schwierigkeit", 20, Style.TEXT, "center", true)
|
||||
var dr := Style.row(col, 10)
|
||||
dr.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
for key in ["leicht", "normal", "schwer"]:
|
||||
var k: String = key
|
||||
diff_buttons[k] = Style.cbutton(dr, Profile.DIFFICULTIES[k].name, Vector2(150, 40), func(): set_difficulty(k))
|
||||
rating_label = Style.clabel(col, "", 14, Style.MUTED)
|
||||
var tr_row := Style.row(col, 10)
|
||||
tr_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
tip_label = Style.clabel(tr_row, "", 14, Style.GOLD, "right", false, 520)
|
||||
b_tip = Style.cbutton(tr_row, "Übernehmen", Vector2(140, 32), take_tip)
|
||||
Style.spacer(col, 2)
|
||||
for i in range(1, Game.MAX_OPPONENTS + 1):
|
||||
var n := i
|
||||
var r := Style.row(col, 16)
|
||||
r.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
Style.cbutton(r, "%d Gegner" % n, Vector2(150, 46), func(): start(n), Color("285a3c"))
|
||||
var info := (tr("Duell") if n == 1 else tr("%d am Tisch") % (n + 1)) + " · " + tr("%d Münzen") % Profile.REWARD_WIN[n]
|
||||
Style.clabel(r, info, 13, Style.MUTED, "left", false, 150)
|
||||
preview_labels[n] = Style.clabel(r, "", 13, Style.TEXT, "left", false, 600)
|
||||
preview_labels[n].autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
preview_labels[n].custom_minimum_size = Vector2(600, 0)
|
||||
preview_labels[n].size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
Style.spacer(col, 4)
|
||||
rating_label = Style.clabel(col, "", 15, Style.MUTED)
|
||||
Style.spacer(col, 24)
|
||||
count_label = Style.clabel(col, "", 20, Style.TEXT, "center", true)
|
||||
var sr := Style.row(col, 12)
|
||||
sr.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
Style.clabel(sr, "1", 15, Style.MUTED, "right", false, 20)
|
||||
count_slider = HSlider.new()
|
||||
count_slider.min_value = 1
|
||||
count_slider.max_value = Game.MAX_OPPONENTS
|
||||
count_slider.step = 1
|
||||
count_slider.value = 1
|
||||
count_slider.tick_count = Game.MAX_OPPONENTS
|
||||
count_slider.ticks_on_borders = true
|
||||
count_slider.custom_minimum_size = Vector2(420, 32)
|
||||
count_slider.value_changed.connect(func(_v): refresh())
|
||||
sr.add_child(count_slider)
|
||||
Style.clabel(sr, str(Game.MAX_OPPONENTS), 15, Style.MUTED, "left", false, 20)
|
||||
info_label = Style.clabel(col, "", 15, Style.MUTED)
|
||||
Style.spacer(col, 30)
|
||||
var br := Style.row(col, 16)
|
||||
br.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
b_reroll = Style.cbutton(br, "Neu würfeln", Vector2(190, 40), reroll)
|
||||
Style.cbutton(br, "Zurück", Vector2(150, 40), func(): app.show_screen(MenuScreen.new()))
|
||||
refresh(true)
|
||||
Style.cbutton(br, "Starten", Vector2(190, 46), start, Color("285a3c"))
|
||||
Style.cbutton(br, "Zurück", Vector2(150, 46), func(): app.show_screen(MenuScreen.new()))
|
||||
refresh()
|
||||
|
||||
|
||||
func set_difficulty(key: String) -> void:
|
||||
app.profile.settings.difficulty = key
|
||||
app.save()
|
||||
refresh(true)
|
||||
refresh()
|
||||
|
||||
|
||||
func take_tip() -> void:
|
||||
var tip: String = app.profile.suggest_difficulty()
|
||||
if tip != "":
|
||||
set_difficulty(tip)
|
||||
func opponents() -> int:
|
||||
return int(count_slider.value)
|
||||
|
||||
|
||||
## Höchstens dreimal neu würfeln, sonst ließen sich schwache Gegner erwürfeln.
|
||||
func reroll() -> void:
|
||||
if rerolls_left <= 0:
|
||||
return
|
||||
rerolls_left -= 1
|
||||
refresh(true)
|
||||
func start() -> void:
|
||||
app.show_screen(BattleScreen.new(opponents()))
|
||||
|
||||
|
||||
func start(n: int) -> void:
|
||||
app.show_screen(BattleScreen.against(previews[n]))
|
||||
|
||||
|
||||
## Baut die Gegner für jede Anzahl vor und zeigt Element und Stärke; der Kampf nutzt genau diese.
|
||||
func refresh(reroll: bool = false) -> void:
|
||||
func refresh() -> void:
|
||||
var p: Profile = app.profile
|
||||
var d := p.difficulty()
|
||||
for key in diff_buttons:
|
||||
var b: Button = diff_buttons[key]
|
||||
var active: bool = key == p.settings.difficulty
|
||||
b.add_theme_stylebox_override("normal", Style.box(Color("285a3c") if active else Style.PANEL_LIGHT, 8, Style.GOLD if active else Style.MUTED, 2))
|
||||
b.add_theme_stylebox_override("hover", Style.box(Style.lighter(Color("285a3c") if active else Style.PANEL_LIGHT), 8, Style.GOLD if active else Style.MUTED, 2))
|
||||
var arche: String = tr("schwache Archetypen") if d.tier == "leicht" else (tr("starke Archetypen") if d.tier == "schwer" else tr("alle Archetypen"))
|
||||
rating_label.text = tr("Deine Deckstärke %.1f · Gegnerdecks %.1f · %s · Münzen x%.2f · KI: %s") % [
|
||||
p.deck_rating(), p.target_rating(), arche, d.reward, tr("Vorausschau") if d.smart else tr("einfach")]
|
||||
var tip := p.suggest_difficulty()
|
||||
tip_label.text = (tr("Tipp: %s") % ((tr("%d Siege in Folge, versuche %s") % [p.records.streak, tr(Profile.DIFFICULTIES[tip].name)]) if p.records.streak >= 3
|
||||
else (tr("%d Niederlagen in Folge, versuche %s") % [p.records.loss_streak, tr(Profile.DIFFICULTIES[tip].name)]))) if tip != "" else ""
|
||||
b_tip.visible = tip != ""
|
||||
if reroll:
|
||||
var r := Game.make_rng()
|
||||
for n in range(1, Game.MAX_OPPONENTS + 1):
|
||||
var opps := []
|
||||
for i in n:
|
||||
opps.append(Game.random_opponent(r, i + 1, Game.START_LIFE, p.target_rating(), str(d.tier)))
|
||||
previews[n] = opps
|
||||
b_reroll.text = tr("Neu würfeln (%d)") % rerolls_left
|
||||
b_reroll.disabled = rerolls_left <= 0
|
||||
for n in previews:
|
||||
var parts: Array[String] = []
|
||||
for o in previews[n]:
|
||||
parts.append("%s (%.1f)" % [o.title, Game.deck_rating(o.deck)])
|
||||
preview_labels[n].text = ", ".join(parts)
|
||||
rating_label.text = tr("Deine Deckstärke %.1f · Gegnerdecks %.1f") % [p.deck_rating(), p.target_rating()]
|
||||
var n := opponents()
|
||||
count_label.text = tr("1 Gegner") if n == 1 else tr("%d Gegner") % n
|
||||
info_label.text = (tr("Duell") if n == 1 else tr("%d am Tisch") % (n + 1)) + " · " + tr("%d Münzen") % Profile.REWARD_WIN[n]
|
||||
|
||||
|
||||
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())
|
||||
|
||||
@@ -300,7 +300,6 @@ static func rules_text() -> String:
|
||||
lines.append("Ein unterbrochener Einzelspieler-Kampf lässt sich im Menü fortsetzen.")
|
||||
lines.append("")
|
||||
lines.append("Werkstatt: Karten zerlegen bringt Staub, aus Staub entstehen neue Karten. Fünf Deck-Slots.")
|
||||
lines.append("Lokal zu mehreren: bis zu vier Menschen am selben Rechner, dazu KI, ohne Münzen.")
|
||||
lines.append("Netzwerkspiel: Fällt der Host aus, übernimmt ein Mitspieler; Zuschauer sehen die Partie ohne Hände.")
|
||||
lines.append("Mulligan: Im ersten eigenen Zug darfst du einmal Handkarten zurücklegen und nachziehen.")
|
||||
lines.append("Deckstärke: Seltenheit, Schlüsselwörter und Effekte ergeben je Karte einen Wert. Gegnerdecks werden auf deine")
|
||||
|
||||
@@ -97,7 +97,6 @@ func _initialize() -> void:
|
||||
test_kampagne()
|
||||
test_fallen()
|
||||
test_ki_sieht_fallen_nicht()
|
||||
test_hotseat()
|
||||
test_zufallssamen()
|
||||
test_deck_slots_und_werkstatt()
|
||||
test_spielstand_sicherung()
|
||||
@@ -810,34 +809,6 @@ func test_ki_sieht_fallen_nicht() -> void:
|
||||
check(g.clone_for(h).players[hi].traps.size() == 1, "eigene Fallen bleiben in der eigenen Kopie")
|
||||
|
||||
|
||||
func test_hotseat() -> void:
|
||||
var r := RandomNumberGenerator.new()
|
||||
r.seed = 3
|
||||
var d := Profile.starter().deck_cards()
|
||||
var g := Game.new_hotseat([d, d], 1, r)
|
||||
check(g.players.size() == 3 and g.humans.size() == 2, "zwei Menschen und eine KI")
|
||||
check(g.humans[0].name == "Spieler 1" and g.humans[1].name == "Spieler 2", "Spielernamen")
|
||||
var ai = g.players.filter(func(p): return not p.is_human)[0]
|
||||
g.damage(ai, 99)
|
||||
g.check_end()
|
||||
check(g.winner == "", "zwei Menschen übrig: weiter")
|
||||
g.damage(g.humans[1], 99)
|
||||
g.check_end()
|
||||
check(g.winner == "human" and g.winner_player == g.humans[0], "Spieler 1 gewinnt")
|
||||
var g2 := Game.new_hotseat([d, d], 1, r)
|
||||
for hu in g2.humans:
|
||||
g2.damage(hu, 99)
|
||||
g2.check_end()
|
||||
check(g2.winner == "opponents" and g2.winner_player != null and not g2.winner_player.is_human, "KI gewinnt, wenn alle Menschen tot sind")
|
||||
var g3 := Game.new_hotseat([d, d, d], 0, r)
|
||||
check(g3.players.size() == 3 and g3.opponents.size() == 2, "drei Menschen ohne KI")
|
||||
var guard := 0
|
||||
while g3.winner == "" and guard < 300:
|
||||
AI.take_turn(g3, g3.current(), true)
|
||||
guard += 1
|
||||
check(g3.winner == "human" and g3.winner_player != null, "Hotseat-Partie endet mit Sieger")
|
||||
|
||||
|
||||
func test_zufallssamen() -> void:
|
||||
Game.test_seed = 77
|
||||
var a := Game.make_rng().randi()
|
||||
|
||||
@@ -168,7 +168,11 @@ func _ready() -> void:
|
||||
test_erfolge, test_kampagne, test_werkstatt, test_sprache, test_anleitung, test_anleitung2, test_herausforderung,
|
||||
test_kampf_fortsetzen, test_gamepad_deckbau, test_deckcode, test_elementbooster]
|
||||
if not fast:
|
||||
suite += [test_hotseat, test_netzwerk, test_host_wechsel, test_zuschauer, test_kampf.bind(2, 3), test_kampf_abbrechen]
|
||||
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
|
||||
# Die Netzwerktests starten Bot-Prozesse und warten auf Verbindungen; das dauert Minuten.
|
||||
# Sie laufen nur auf Wunsch: DINGER_NETZ=1 (oder gezielt über DINGER_ONLY).
|
||||
if OS.get_environment("DINGER_NETZ") != "":
|
||||
suite += [test_netzwerk, test_host_wechsel, test_zuschauer]
|
||||
suite.append(test_profil_zuruecksetzen)
|
||||
var only := OS.get_environment("DINGER_ONLY") # z. B. DINGER_ONLY=test_host_wechsel,test_zuschauer
|
||||
if only != "":
|
||||
@@ -373,25 +377,38 @@ func human_turn(scr) -> void:
|
||||
func start_battle(n_opp: int) -> void:
|
||||
await click(find_button("Einzelspieler"))
|
||||
check(screen() is OpponentSelectScreen, "Gegnerauswahl")
|
||||
await click(find_button("%d Gegner" % n_opp))
|
||||
screen().count_slider.value = n_opp
|
||||
await frames(1)
|
||||
await click(find_button("Starten"))
|
||||
|
||||
|
||||
func test_gegnerauswahl() -> void:
|
||||
await click(find_button("Einzelspieler"))
|
||||
var sel = screen()
|
||||
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")
|
||||
check("Deckstärke" in sel.rating_label.text and "Gegnerdecks" in sel.rating_label.text
|
||||
and not ("Archetypen" in sel.rating_label.text or "KI" in sel.rating_label.text), "unter der Schwierigkeit nur die zwei Stärken")
|
||||
check(find_button("Neu würfeln") == null and find_button("1 Gegner") == null and find_button("Starten") != null, "kein Würfeln und keine Gegnerknöpfe, nur Starten")
|
||||
check(sel.count_slider.min_value == 1 and sel.count_slider.max_value == Game.MAX_OPPONENTS and sel.count_slider.step == 1, "Gegnerzahl als Schieberegler von 1 bis %d" % Game.MAX_OPPONENTS)
|
||||
check(sel.opponents() == 1 and sel.count_label.text == "1 Gegner" and "Duell" in sel.info_label.text, "Regler beginnt beim Duell")
|
||||
var rect: Rect2 = sel.count_slider.get_global_rect()
|
||||
await click_at(Vector2(rect.end.x - 4, rect.get_center().y)) # ganz rechts anklicken
|
||||
check(sel.opponents() == Game.MAX_OPPONENTS and sel.count_label.text == "%d Gegner" % Game.MAX_OPPONENTS, "Mausklick am rechten Rand stellt %d Gegner ein" % Game.MAX_OPPONENTS)
|
||||
sel.count_slider.grab_focus()
|
||||
await key(KEY_LEFT)
|
||||
check(sel.opponents() == Game.MAX_OPPONENTS - 1, "Pfeiltaste links nimmt einen Gegner weg")
|
||||
sel.count_slider.value = 4
|
||||
await frames(1)
|
||||
check(sel.opponents() == 4 and sel.count_label.text == "4 Gegner" and "5 am Tisch" in sel.info_label.text
|
||||
and str(Profile.REWARD_WIN[4]) in sel.info_label.text, "Regler auf 4: fünf am Tisch mit Belohnung")
|
||||
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)
|
||||
await shot("07_gegnerauswahl")
|
||||
await click(find_button("1 Gegner"))
|
||||
sel.count_slider.value = 1
|
||||
await frames(1)
|
||||
await click(find_button("Starten"))
|
||||
var hard = screen()
|
||||
check(hard.game.opponents.map(func(o): return o.title) == previewed, "Kampf nutzt die vorgeschauten Gegner")
|
||||
check(hard is BattleScreen and hard.game.opponents.size() == 1, "Kampf mit einem Gegner gestartet")
|
||||
var own := Game.deck_rating(main.profile.deck_cards())
|
||||
var opp: Game.Player = hard.game.opponents[0]
|
||||
var opp_rating := Game.deck_rating(opp.deck + opp.hand)
|
||||
@@ -399,22 +416,13 @@ func test_gegnerauswahl() -> void:
|
||||
await give_up()
|
||||
await click(find_button("Einzelspieler"))
|
||||
await click(find_button("Leicht"))
|
||||
await click(find_button("1 Gegner"))
|
||||
await click(find_button("Starten"))
|
||||
var easy = screen()
|
||||
opp = easy.game.opponents[0]
|
||||
check(Game.deck_rating(opp.deck + opp.hand) <= own * 1.0 + 0.15 and not easy.smart_ai, "Leicht: schwächeres Deck, einfache KI")
|
||||
await give_up()
|
||||
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 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ü")
|
||||
for n in [1, 5]:
|
||||
@@ -707,40 +715,6 @@ func test_herausforderung() -> void:
|
||||
check(screen() is MenuScreen, "zurück im Menü")
|
||||
|
||||
|
||||
func test_hotseat() -> void:
|
||||
await click(find_button("Lokal zu mehreren"))
|
||||
var scr = screen()
|
||||
check(scr is HotseatScreen, "Hotseat-Auswahl")
|
||||
await click(find_button("3 Spieler"))
|
||||
await click(find_button("1 KI"))
|
||||
check(scr.n_humans == 3 and scr.n_ai == 1, "3 Menschen, 1 KI")
|
||||
await shot("17_hotseat")
|
||||
await click(find_button("Start"))
|
||||
var b = screen()
|
||||
check(b is BattleScreen and b.is_hotseat() and b.game.players.size() == 4, "Hotseat-Kampf gestartet")
|
||||
b.ai_delay = 0.0
|
||||
var guard := 0
|
||||
while not b.handover.visible and b.game.winner == "" and guard < 400:
|
||||
if b.game.current() == b.perspective:
|
||||
await clear_mulligan(b)
|
||||
if b.game.current() == b.perspective:
|
||||
await click(b.b_end)
|
||||
await frames(1)
|
||||
guard += 1
|
||||
check(b.handover.visible, "Übergabe erscheint")
|
||||
var cur: Game.Player = b.game.current()
|
||||
check(cur.is_human and cur != b.perspective, "anderer Mensch ist dran")
|
||||
await shot("18_hotseat_uebergabe")
|
||||
await click(find_button("Weiter"))
|
||||
check(b.perspective == cur and not b.handover.visible, "Perspektive gewechselt")
|
||||
check(b.hand_views.size() == cur.hand.size() and b.name_label.text == cur.name, "Hand und Name des neuen Spielers")
|
||||
check(b.mulligan_box.visible, "neuer Spieler bekommt seine Mulligan-Maske")
|
||||
await clear_mulligan(b)
|
||||
check(b.hero_panels.size() == 3 and not b.hero_panels.has(cur), "drei andere am Tisch")
|
||||
await shot("19_hotseat_kampf")
|
||||
await leave_battle()
|
||||
|
||||
|
||||
func test_sprache() -> void:
|
||||
await click(find_button("Einstellungen"))
|
||||
var scr = screen()
|
||||
@@ -1028,7 +1002,7 @@ func test_zuschauer() -> void:
|
||||
check(b.game != null and b.game.turn >= 2 and b.hand_views.is_empty() and not b.mulligan_box.visible and not b.b_end.visible,
|
||||
"Zuschauer sieht die Partie ohne Hände und ohne Züge (Zug %d)" % (b.game.turn if b.game != null else 0))
|
||||
await shot("35_zuschauer")
|
||||
check(not b.handover.visible and not b.is_hotseat(), "Zuschauer bekommt keine Hotseat-Übergabe")
|
||||
check(not b.b_end.visible and not b.b_undo.visible, "Zuschauer hat keine Zugknöpfe")
|
||||
if screen() is BattleScreen: # die Bots spielen schnell, die Partie kann schon vorbei sein
|
||||
await leave_battle()
|
||||
else:
|
||||
|
||||
@@ -3,7 +3,6 @@ Dinger,Dinger
|
||||
Ein Kartenspiel: jeder gegen jeden,A card game: free-for-all
|
||||
Einzelspieler,Single player
|
||||
Kampagne,Campaign
|
||||
Lokal zu mehreren,Local multiplayer
|
||||
Netzwerkspiel,Network game
|
||||
Booster öffnen,Open boosters
|
||||
Deck bauen,Build deck
|
||||
@@ -95,20 +94,11 @@ Schwierigkeit,Difficulty
|
||||
Leicht,Easy
|
||||
Normal,Normal
|
||||
Schwer,Hard
|
||||
"Deine Deckstärke %.1f · Gegnerdecks %.1f · %s · Münzen x%.2f · KI: %s","Your deck strength %.1f · Opponent decks %.1f · %s · Coins x%.2f · AI: %s"
|
||||
schwache Archetypen,weaker archetypes
|
||||
starke Archetypen,stronger archetypes
|
||||
alle Archetypen,all archetypes
|
||||
Vorausschau,lookahead
|
||||
einfach,simple
|
||||
Stärke %.1f,Strength %.1f
|
||||
"Deckstärke: Durchschnitt aus Seltenheit, Schlüsselwörtern und Effekten","Deck strength: average of rarity, keywords and effects"
|
||||
Tipp: %s,Tip: %s
|
||||
Übernehmen,Apply
|
||||
Neu würfeln,Reroll
|
||||
Deck verbessern: %s ins aktive Deck (%s),Improve deck: add %s to the active deck (%s)
|
||||
Anleitung 2: Fallen,Tutorial 2: Traps
|
||||
Neu würfeln (%d),Reroll (%d)
|
||||
Profil zurücksetzen,Reset profile
|
||||
Große Schrift,Large text
|
||||
Verwerfen,Discard
|
||||
@@ -126,13 +116,10 @@ Fenn verliert bei 0 Leben. Tausche im ersten Zug einer Partie schwache Starthän
|
||||
"Beende den Zug und warte. Greift Mara dich oder ein Wesen an, verpufft der Angriff und ihr Wesen friert ein.","End your turn and wait. If Mara attacks you or a creature, the attack fizzles and her creature freezes."
|
||||
Feldkombo Dampf: Ein Wasser-Wesen neben einem Feuer-Wesen friert das stärkste gegnerische Wesen ein. Spiele beide nacheinander.,Field combo Steam: a water creature next to a fire creature freezes the strongest enemy creature. Play both in sequence.
|
||||
"Rückgängig (U) nimmt ein gerade gespieltes Wesen zurück, solange nichts ausgelöst wurde. Viel Erfolg gegen Mara!",Undo (U) takes back a creature you just played as long as nothing was triggered. Good luck against Mara!
|
||||
"%d Siege in Folge, versuche %s","%d wins in a row, try %s"
|
||||
"%d Niederlagen in Folge, versuche %s","%d losses in a row, try %s"
|
||||
Duell,Duel
|
||||
%d am Tisch,%d at the table
|
||||
%d Spieler am Tisch,%d players at the table
|
||||
%d Münzen,%d coins
|
||||
Stärke %.1f · %d Münzen,Strength %.1f · %d coins
|
||||
Abbrechen,Cancel
|
||||
"Ja, zurücksetzen","Yes, reset"
|
||||
"Speichern und zurück","Save and go back"
|
||||
@@ -217,3 +204,8 @@ Kapitel %d,Chapter %d
|
||||
Zuschauen,Watch
|
||||
Zuschauer,Spectator
|
||||
"Zuschauer: Hände sind verdeckt","Spectator: hands are hidden"
|
||||
Deine Deckstärke %.1f · Gegnerdecks %.1f,Your deck strength %.1f · Opponent decks %.1f
|
||||
Starten,Start
|
||||
1 Gegner,1 opponent
|
||||
%d Gegner,%d opponents
|
||||
Übernehmen,Apply
|
||||
|
||||
|
Binary file not shown.
Reference in New Issue
Block a user