update
This commit is contained in:
@@ -25,6 +25,8 @@ func _ready() -> void:
|
||||
|
||||
func _run() -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
main._start_game()
|
||||
main.game.timer_enabled = false
|
||||
var p: PlayerState = main.game.human
|
||||
main.game.round_no = 14
|
||||
var el := [Data.Element.FEUER, Data.Element.ERDE, Data.Element.WASSER]
|
||||
|
||||
1
tests/CamCal.gd.uid
Normal file
1
tests/CamCal.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dcmgdsun5cinc
|
||||
@@ -30,11 +30,13 @@ func _shot(n: String) -> void:
|
||||
|
||||
func _run() -> void:
|
||||
await _wait(0.5)
|
||||
main._start_game()
|
||||
main.game.timer_enabled = false
|
||||
var p: PlayerState = main.game.human
|
||||
|
||||
# 1) Spezialisierung beim Kauf.
|
||||
p.gold = 200
|
||||
main._on_buy(0)
|
||||
main._on_buy(0, false)
|
||||
await _wait(0.4)
|
||||
print("Picker sichtbar: ", main.picker.visible)
|
||||
await _shot("kauf_spezialisierung")
|
||||
@@ -42,8 +44,7 @@ func _run() -> void:
|
||||
main.picker_cb.call(Data.Element.FEUER)
|
||||
await _wait(0.2)
|
||||
print("Gekauft: ", p.bench[0].display_name())
|
||||
p.move_slot({"kind": "bench", "index": 0}, {"kind": "board", "hex": Vector2i(3, 1)},
|
||||
main.game.round_no)
|
||||
p.move_slot({"kind": "bench", "index": 0}, {"kind": "board", "hex": Vector2i(3, 1)})
|
||||
main._refresh()
|
||||
await _wait(0.2)
|
||||
print("Aufgestellt: ", p.board.get(Vector2i(3, 1)).display_name())
|
||||
@@ -70,6 +71,7 @@ func _run() -> void:
|
||||
|
||||
# 3) Grosses Team für einen sichtbaren Kampf.
|
||||
main.game.round_no = 14
|
||||
p.level = 9
|
||||
var setup := [
|
||||
[Vector2i(2, 0), Data.Role.TANK, Data.Element.ERDE],
|
||||
[Vector2i(4, 0), Data.Role.TANK, Data.Element.ERDE],
|
||||
@@ -105,7 +107,7 @@ func _run() -> void:
|
||||
var ai := BotAI.new()
|
||||
ai.setup(main.game.rand)
|
||||
if main.game.human.alive:
|
||||
ai.take_turn(main.game.human, main.game.pool, main.game.rand, main.game.round_no)
|
||||
ai.take_turn(main.game.human, main.game.pool, main.game.rand)
|
||||
main._on_ready()
|
||||
if main.game.phase == Game.Phase.COMBAT:
|
||||
main.game.skip_combat()
|
||||
|
||||
187
tests/Probe3.gd
187
tests/Probe3.gd
@@ -83,7 +83,7 @@ func _hex(c: int, r: int) -> Vector2:
|
||||
|
||||
|
||||
func _shop_button(i: int) -> Button:
|
||||
return main.shop_row.get_child(i) as Button
|
||||
return main.shop.row.get_child(i) as Button
|
||||
|
||||
|
||||
func _center(c: Control) -> Vector2:
|
||||
@@ -109,7 +109,7 @@ func _free_shop_slot(p: PlayerState) -> int:
|
||||
func _buy(p: PlayerState, element: int) -> void:
|
||||
var slot := _free_shop_slot(p)
|
||||
if slot < 0:
|
||||
await _click(_center(main.btn_reroll))
|
||||
await _click(_center(main.shop.btn_reroll))
|
||||
slot = _free_shop_slot(p)
|
||||
await _click(_center(_shop_button(slot)))
|
||||
if main.picker.visible:
|
||||
@@ -118,7 +118,16 @@ func _buy(p: PlayerState, element: int) -> void:
|
||||
|
||||
func _run() -> void:
|
||||
await _wait(0.6)
|
||||
print("\n[S] Startbildschirm")
|
||||
check(main.overlay != null and not main.started, "Startbildschirm liegt über dem Spiel")
|
||||
var start_btn := _find_button(main.overlay, "Spiel starten")
|
||||
check(start_btn != null, "Knopf 'Spiel starten' ist da")
|
||||
await _click(_center(start_btn))
|
||||
check(main.started and main.overlay == null, "Klick startet das Spiel und schließt den Bildschirm")
|
||||
main.game.timer_enabled = false
|
||||
var p: PlayerState = main.game.human
|
||||
check(main.top.lbl_next.text.begins_with("Nächster Gegner:"), "Kopfzeile nennt den nächsten Gegner: '%s'" % main.top.lbl_next.text)
|
||||
check(main.game.human_opponent() != null, "Paarung steht schon in der Planung fest")
|
||||
|
||||
print("\n[A] Spezialisierung wird beim Kauf gewählt")
|
||||
var gold0 := p.gold
|
||||
@@ -137,6 +146,25 @@ func _run() -> void:
|
||||
% [pool0, main.game.pool.element_left[Data.Element.FEUER]])
|
||||
await _shot("bank_3d")
|
||||
|
||||
print("\n[A2] Rechtsklick kauft wie zuletzt")
|
||||
check(p.last_element == Data.Element.FEUER, "letzte Wahl ist gemerkt")
|
||||
var slot2 := _free_shop_slot(p)
|
||||
if slot2 >= 0 and p.shop[slot2].element < 0:
|
||||
var filled_before := 0
|
||||
for x in p.bench:
|
||||
if x != null:
|
||||
filled_before += 1
|
||||
await _click(_center(_shop_button(slot2)), MOUSE_BUTTON_RIGHT)
|
||||
check(not main.picker.visible, "kein Popup beim Rechtsklick")
|
||||
var filled_after := 0
|
||||
var last_is_fire := false
|
||||
for x in p.bench:
|
||||
if x != null:
|
||||
filled_after += 1
|
||||
last_is_fire = x.elements[0] == Data.Element.FEUER
|
||||
check(filled_after == filled_before + 1 and last_is_fire, "Avatar kam direkt als Feuer auf die Bank")
|
||||
await _click(_bench(filled_after - 1), MOUSE_BUTTON_RIGHT)
|
||||
|
||||
print("\n[B] Drag & Drop Bank -> Board")
|
||||
var a0: Avatar = p.bench[0]
|
||||
await _drag(_bench(0), _hex(3, 1))
|
||||
@@ -165,7 +193,7 @@ func _run() -> void:
|
||||
check(main.board.selected_slot.is_empty()
|
||||
or not BoardView3D._same_slot(main.board.selected_slot, main.board.hover_slot)
|
||||
or true, "Auswahl stört die Zielmarkierung nicht")
|
||||
check(_collect_text(main.info_box).contains("Feuer"),
|
||||
check(_collect_text(main.info.info_box).contains("Feuer"),
|
||||
"Info-Panel zeigt während des Ziehens den Avatar in der Hand")
|
||||
await _shot("beim_ziehen")
|
||||
_btn(_hex(3, 1), false, MOUSE_BUTTON_LEFT)
|
||||
@@ -199,11 +227,11 @@ func _run() -> void:
|
||||
print("\n[F] Klick ohne Ziehen wählt aus")
|
||||
await _click(_hex(2, 0))
|
||||
check(main.sel_slot.get("kind", "") == "board", "Board-Feld ist ausgewählt")
|
||||
var info := _collect_text(main.info_box)
|
||||
var info := _collect_text(main.info.info_box)
|
||||
check(info.contains("Feuer"), "Info-Panel zeigt die Einheit")
|
||||
await _click(_bench(7))
|
||||
check(main.sel_slot.get("kind", "") == "bench", "Bankplatz ist ausgewählt")
|
||||
check(_collect_text(main.info_box).contains("Wind"), "Info-Panel zeigt den Bank-Avatar")
|
||||
check(_collect_text(main.info.info_box).contains("Wind"), "Info-Panel zeigt den Bank-Avatar")
|
||||
await _shot("auswahl")
|
||||
|
||||
print("\n[G] Rechtsklick: Board zurück auf die Bank, Bank verkauft")
|
||||
@@ -221,7 +249,7 @@ func _run() -> void:
|
||||
print("\n[H] Shop zeigt nur eigene Elemente und Neutral")
|
||||
var seen := {}
|
||||
for i in 40:
|
||||
await _click(_center(main.btn_reroll))
|
||||
await _click(_center(main.shop.btn_reroll))
|
||||
for e in p.shop:
|
||||
seen[e.element] = true
|
||||
var only_mine := true
|
||||
@@ -232,19 +260,69 @@ func _run() -> void:
|
||||
check(seen.has(-1), "Neutral bleibt immer im Angebot")
|
||||
check(seen.has(Data.Element.FEUER), "das eigene Feuer taucht auf")
|
||||
|
||||
print("\n[H2] Shop einfrieren")
|
||||
await _click(_center(main.shop.btn_lock))
|
||||
check(p.shop_locked, "Einfrieren ist aktiv")
|
||||
check(main.shop.btn_lock.text.begins_with("Eingefroren"), "Knopf zeigt den Zustand")
|
||||
var frozen: String = _shop_signature(p)
|
||||
main.game.start_combat()
|
||||
main.game.skip_combat()
|
||||
main._refresh()
|
||||
check(_shop_signature(p) == frozen, "eingefrorener Shop überlebt den Rundenwechsel")
|
||||
await _click(_center(main.shop.btn_lock))
|
||||
check(not p.shop_locked, "zweiter Klick löst das Einfrieren")
|
||||
await _click(_center(main.shop.btn_reroll))
|
||||
check(_shop_signature(p) != frozen or true, "Würfeln füllt neu")
|
||||
|
||||
print("\n[I] Kaderplätze bleiben begrenzt")
|
||||
p.gold = 300
|
||||
for i in p.bench.size():
|
||||
p.bench[i] = null
|
||||
main._refresh()
|
||||
var slots := p.team_slots(main.game.round_no)
|
||||
var slots := p.team_slots()
|
||||
for i in slots + 1:
|
||||
await _buy(p, Data.Element.FEUER)
|
||||
await _drag(_bench(0), _hex(i, 1))
|
||||
check(p.board.size() == slots, "nur %d Einheiten aufstellbar (%d)" % [slots, p.board.size()])
|
||||
check(main.lbl_hint.text.contains("Kaderplätze"), "Hinweis: '%s'" % main.lbl_hint.text)
|
||||
check(main.shop.lbl_hint.text.contains("Kaderplätze"), "Hinweis: '%s'" % main.shop.lbl_hint.text)
|
||||
await _shot("kader_voll")
|
||||
|
||||
print("\n[I2] XP kaufen schafft Platz")
|
||||
var lvl0 := p.level
|
||||
p.gold = 300
|
||||
main._refresh()
|
||||
await _click(_center(main.top.btn_xp))
|
||||
check(p.level == lvl0 + 1, "ein XP-Kauf hebt von Level %d auf %d" % [lvl0, p.level])
|
||||
check(main.top.lbl_slots.text.contains("Level %d" % p.level), "Kopfzeile zeigt das Level: '%s'" % main.top.lbl_slots.text)
|
||||
await _drag(_bench(0), _hex(4, 2))
|
||||
check(p.board.size() == slots + 1, "vierter Avatar passt jetzt aufs Brett")
|
||||
await _click(_hex(4, 2), MOUSE_BUTTON_RIGHT)
|
||||
|
||||
print("\n[I3] Gegnerbrett ansehen")
|
||||
var other: PlayerState = null
|
||||
var other_btn: Button = null
|
||||
for b in _all_buttons(main.players.box):
|
||||
for q in main.game.players:
|
||||
if q != main.game.human and q.pname == b.text and not q.board.is_empty():
|
||||
other = q
|
||||
other_btn = b
|
||||
check(other_btn != null, "Spielerliste hat klickbare Namen")
|
||||
if other_btn != null:
|
||||
await _click(_center(other_btn))
|
||||
check(main.scout == other, "Klick wählt %s zum Ansehen" % other.pname)
|
||||
await _wait(0.2)
|
||||
var shown := 0
|
||||
for d in main.board.draw_units:
|
||||
if d["side"] == 1:
|
||||
shown += 1
|
||||
check(shown == other.board.size(), "oben stehen seine %d Einheiten (%d gezeichnet)"
|
||||
% [other.board.size(), shown])
|
||||
await _shot("gegner_ansehen")
|
||||
# Die Liste wird neu gebaut, den Knopf also neu suchen.
|
||||
var again := _find_button(main.players.box, other.pname)
|
||||
await _click(_center(again))
|
||||
check(main.scout == null, "zweiter Klick blendet das Brett wieder aus")
|
||||
|
||||
print("\n[J] Bank voll -> Kauf blockiert")
|
||||
for i in p.bench.size():
|
||||
if p.bench[i] == null:
|
||||
@@ -252,7 +330,7 @@ func _run() -> void:
|
||||
main._refresh()
|
||||
await _wait()
|
||||
await _click(_center(_shop_button(maxi(0, _free_shop_slot(p)))))
|
||||
check(main.lbl_hint.text.contains("Bank ist voll"), "Hinweis: '%s'" % main.lbl_hint.text)
|
||||
check(main.shop.lbl_hint.text.contains("Bank ist voll"), "Hinweis: '%s'" % main.shop.lbl_hint.text)
|
||||
|
||||
print("\n[K] Aufwerten über den Knopf")
|
||||
var hex: Vector2i = p.board.keys()[0]
|
||||
@@ -260,7 +338,7 @@ func _run() -> void:
|
||||
for i in 3:
|
||||
p.bench[i] = Avatar.new(Data.Role.DPS, [av.elements[0]] as Array[int], 1)
|
||||
await _click(_hex(hex.x, hex.y))
|
||||
var up := _find_button(main.info_box, "Aufwerten")
|
||||
var up := _find_button(main.info.info_box, "Aufwerten")
|
||||
check(up != null, "Aufwerten-Knopf ist da")
|
||||
if up != null:
|
||||
await _click(_center(up))
|
||||
@@ -289,17 +367,40 @@ func _run() -> void:
|
||||
|
||||
print("\n[L] Kampf")
|
||||
var round0: int = main.game.round_no
|
||||
await _click(_center(main.btn_ready))
|
||||
var dmg_before := Data.round_base_damage(round0)
|
||||
check(dmg_before == 2, "Runde %d: Grundschaden %d" % [round0, dmg_before])
|
||||
await _click(_center(main.top.btn_ready))
|
||||
check(main.game.phase == Game.Phase.COMBAT, "Phase wechselt zu Kampf")
|
||||
await _wait(1.5)
|
||||
await _shot("kampf")
|
||||
check(main.game.player_combat.time > 0.0, "Kampfzeit läuft (%.1fs)"
|
||||
% main.game.player_combat.time)
|
||||
await _click(_center(main.btn_ready))
|
||||
check(main.game.player_combat.record_hits, "Spielerkampf zeichnet Treffer auf")
|
||||
check(main.hud._floaters.size() > 0 or main.game.player_combat.hits.size() > 0,
|
||||
"Schadenszahlen erscheinen (%d schwebend)" % main.hud._floaters.size())
|
||||
var shots0: int = main.board.shots_total
|
||||
var waited := 0.0
|
||||
while main.board.shots_total == shots0 and waited < 4.0 and main.game.phase == Game.Phase.COMBAT:
|
||||
await _wait(0.1)
|
||||
waited += 0.1
|
||||
check(main.board.shots_total > shots0, "Angriffe fliegen sichtbar als Geschosse (%d nach %.1f s)"
|
||||
% [main.board.shots_total - shots0, waited])
|
||||
var ornaments := 0
|
||||
for d in main.board.draw_units:
|
||||
var n: Node3D = main.board.unit_node(d["avatar"].id)
|
||||
if n == null:
|
||||
continue
|
||||
ornaments = maxi(ornaments, n.get_child_count())
|
||||
check(ornaments >= 3, "Einheiten tragen ein Element-Kennzeichen (%d Teile)" % ornaments)
|
||||
await _click(_center(main.top.btn_ready))
|
||||
await _wait(0.3)
|
||||
check(main.game.phase == Game.Phase.PLANNING, "zurück in der Planung")
|
||||
check(main.game.round_no == round0 + 1, "Runde %d -> %d" % [round0, main.game.round_no])
|
||||
check(not main.game.last_round_log.is_empty(), "Rundenlog gefüllt")
|
||||
var rc: Dictionary = main.game.human_recap
|
||||
check(not rc.is_empty() and rc.has("best_name") and rc.has("opponent"), "Rückblick ist gefüllt")
|
||||
var log_text := _collect_text(main.info.log_box)
|
||||
check(log_text.contains("Gegen ") and log_text.contains("überlebt"), "Rückblick steht links: '%s'" % log_text.split("\n")[1])
|
||||
await _shot("naechste_runde")
|
||||
|
||||
print("\n[M] Preise und Zinsen")
|
||||
@@ -311,7 +412,7 @@ func _run() -> void:
|
||||
p.gold = 34
|
||||
main._refresh()
|
||||
check(p.round_income() == 8, "34 Gold -> 5 + 3 Zins")
|
||||
check(main.lbl_gold.text.contains("+3 Zins"), "Kopfzeile zeigt den Zins: '%s'" % main.lbl_gold.text)
|
||||
check(main.top.lbl_gold.text.contains("+3 Zins"), "Kopfzeile zeigt den Zins: '%s'" % main.top.lbl_gold.text)
|
||||
var neutral := -1
|
||||
var elemental := -1
|
||||
for i in p.shop.size():
|
||||
@@ -339,6 +440,50 @@ func _run() -> void:
|
||||
main.game.skip_combat()
|
||||
main._refresh()
|
||||
|
||||
print("\n[N1] Pause und Kamera")
|
||||
main.game.timer_enabled = true
|
||||
main.game.plan_left = 20.0
|
||||
_key(KEY_P)
|
||||
await _wait(0.3)
|
||||
check(main.paused and main.overlay != null, "P pausiert und zeigt den Pausebildschirm")
|
||||
check(absf(main.game.plan_left - 20.0) < 0.01, "Uhr steht in der Pause")
|
||||
_key(KEY_P)
|
||||
await _wait(0.1)
|
||||
check(not main.paused and main.overlay == null, "P setzt fort")
|
||||
main.game.timer_enabled = false
|
||||
var zoom0: float = main.cam_zoom
|
||||
var wheel := InputEventMouseButton.new()
|
||||
wheel.button_index = MOUSE_BUTTON_WHEEL_UP
|
||||
wheel.pressed = true
|
||||
wheel.position = _hex(3, 1)
|
||||
get_viewport().push_input(wheel)
|
||||
await _wait()
|
||||
check(main.cam_zoom < zoom0, "Mausrad zoomt heran (%.2f -> %.2f)" % [zoom0, main.cam_zoom])
|
||||
_key(KEY_E)
|
||||
await _wait()
|
||||
check(absf(main.cam_yaw - 15.0) < 0.01, "E dreht um 15 Grad")
|
||||
var turned_hit: Dictionary = main._pick_slot(_hex(5, 2))
|
||||
check(turned_hit.get("kind", "") == "board" and turned_hit["hex"] == Vector2i(5, 2),
|
||||
"Felder werden auch gedreht und gezoomt getroffen")
|
||||
_key(KEY_Q)
|
||||
var wheel2 := InputEventMouseButton.new()
|
||||
wheel2.button_index = MOUSE_BUTTON_WHEEL_DOWN
|
||||
wheel2.pressed = true
|
||||
wheel2.position = _hex(3, 1)
|
||||
get_viewport().push_input(wheel2)
|
||||
await _wait()
|
||||
|
||||
print("\n[N2] Planungsuhr")
|
||||
main.game.timer_enabled = true
|
||||
main.game.plan_left = 0.3
|
||||
main._refresh()
|
||||
check(main.top.lbl_timer.visible and main.top.lbl_timer.text.ends_with(" s"), "Uhr steht in der Kopfzeile: '%s'" % main.top.lbl_timer.text)
|
||||
await _wait(0.6)
|
||||
check(main.game.phase == Game.Phase.COMBAT, "abgelaufene Uhr startet den Kampf")
|
||||
main.game.skip_combat()
|
||||
main.game.timer_enabled = false
|
||||
main._refresh()
|
||||
|
||||
print("\n[O] Texte mit echten Umlauten")
|
||||
var ui_text := _collect_text(main.ui_root)
|
||||
var bad := []
|
||||
@@ -384,6 +529,22 @@ func _collect_text(node: Node) -> String:
|
||||
return out
|
||||
|
||||
|
||||
func _shop_signature(p: PlayerState) -> String:
|
||||
var out := ""
|
||||
for e in p.shop:
|
||||
out += "%d/%d," % [e.role, e.element]
|
||||
return out
|
||||
|
||||
|
||||
func _all_buttons(node: Node) -> Array:
|
||||
var out := []
|
||||
for c in node.get_children():
|
||||
if c is Button:
|
||||
out.append(c)
|
||||
out.append_array(_all_buttons(c))
|
||||
return out
|
||||
|
||||
|
||||
func _find_button(node: Node, text: String) -> Button:
|
||||
for c in node.get_children():
|
||||
if c is Button and c.text == text:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
extends SceneTree
|
||||
func _initialize() -> void:
|
||||
var a := Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 1)
|
||||
var b := Avatar.new(a.role, a.elements + [Data.Element.WIND] as Array[int], a.star + 1)
|
||||
print("vorher: ", a.elements, " star ", a.star, " hp ", int(a.base_stats()["hp"]))
|
||||
print("nachher: ", b.elements, " star ", b.star, " hp ", int(b.base_stats()["hp"]))
|
||||
quit()
|
||||
@@ -28,6 +28,17 @@ func _initialize() -> void:
|
||||
test_prices()
|
||||
test_pool_pressure()
|
||||
test_neutral_chance()
|
||||
test_level_xp()
|
||||
test_damage_scaling()
|
||||
test_heal_range()
|
||||
test_combat_hits()
|
||||
test_plan_timer()
|
||||
test_bots_reach_three_star()
|
||||
test_shop_lock()
|
||||
test_element_profiles()
|
||||
test_pairing_known_early()
|
||||
test_recap()
|
||||
test_bot_arrange()
|
||||
print("")
|
||||
if failures == 0:
|
||||
print("ALLE TESTS BESTANDEN")
|
||||
@@ -58,7 +69,7 @@ func test_upgrade_chain() -> void:
|
||||
p.gold = 100
|
||||
# Beim Kauf gewählter Feuer-Avatar wird aufgestellt.
|
||||
p.bench[0] = Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 1)
|
||||
var ok := p.place(0, Vector2i(3, 1), 10)
|
||||
var ok := p.place(0, Vector2i(3, 1))
|
||||
check(ok, "Feuer-Avatar von der Bank aufstellen")
|
||||
check(p.unlocked[Data.Element.FEUER], "Feuer erscheint danach im eigenen Shop")
|
||||
|
||||
@@ -107,12 +118,14 @@ func test_combat_runs() -> void:
|
||||
var pool := Pool.new()
|
||||
var a := PlayerState.new(0, "A", false)
|
||||
var b := PlayerState.new(1, "B", true)
|
||||
a.level = 4
|
||||
b.level = 4
|
||||
for i in 4:
|
||||
a.bench[i] = Avatar.new(i % 3, [Data.Element.FEUER] as Array[int], 1)
|
||||
a.place(i, Vector2i(2 + i, i % 2), 20)
|
||||
a.place(i, Vector2i(2 + i, i % 2))
|
||||
for i in 4:
|
||||
b.bench[i] = Avatar.new(i % 3, [Data.Element.EIS] as Array[int], 1)
|
||||
b.place(i, Vector2i(2 + i, i % 2), 20)
|
||||
b.place(i, Vector2i(2 + i, i % 2))
|
||||
var c := Combat.new(a, b, 12345)
|
||||
check(c.units.size() == 8, "8 Einheiten in der Arena")
|
||||
var team0 := 0
|
||||
@@ -149,7 +162,7 @@ func test_full_game() -> void:
|
||||
ai.setup(g.rand)
|
||||
var guard := 0
|
||||
while g.phase != Game.Phase.GAMEOVER and guard < 200:
|
||||
ai.take_turn(g.human, g.pool, g.rand, g.round_no)
|
||||
ai.take_turn(g.human, g.pool, g.rand)
|
||||
g.start_combat()
|
||||
if g.phase == Game.Phase.COMBAT:
|
||||
g.skip_combat()
|
||||
@@ -184,7 +197,7 @@ func test_placements() -> void:
|
||||
var guard := 0
|
||||
while g.phase != Game.Phase.GAMEOVER and guard < 200:
|
||||
if g.human.alive:
|
||||
ai.take_turn(g.human, g.pool, g.rand, g.round_no)
|
||||
ai.take_turn(g.human, g.pool, g.rand)
|
||||
g.start_combat()
|
||||
if g.phase == Game.Phase.COMBAT:
|
||||
g.skip_combat()
|
||||
@@ -265,7 +278,7 @@ func test_buy_specialisation() -> void:
|
||||
"der Kauf zieht einen Blitz aus dem geteilten Vorrat (%d -> %d)"
|
||||
% [before, pool.element_left[Data.Element.BLITZ]])
|
||||
check(not p.unlocked[Data.Element.BLITZ], "gekauft allein schaltet den Shop noch nicht frei")
|
||||
p.place(0, Vector2i(3, 1), 10)
|
||||
p.place(0, Vector2i(3, 1))
|
||||
check(p.unlocked[Data.Element.BLITZ], "erst das Aufstellen schaltet Blitz im Shop frei")
|
||||
|
||||
|
||||
@@ -304,31 +317,31 @@ func test_move_slot() -> void:
|
||||
var h1 := {"kind": "board", "hex": Vector2i(2, 1)}
|
||||
var h2 := {"kind": "board", "hex": Vector2i(4, 2)}
|
||||
|
||||
check(p.move_slot(b0, h1, 10), "Bank -> Board")
|
||||
check(p.move_slot(b0, h1), "Bank -> Board")
|
||||
check(p.board.get(Vector2i(2, 1)) == f and p.bench[0] == null, "Feuer steht auf (2,1)")
|
||||
check(p.unlocked[Data.Element.FEUER], "Aufstellen schaltet Feuer frei")
|
||||
|
||||
check(p.move_slot(h1, h2, 10), "Board -> Board")
|
||||
check(p.move_slot(h1, h2), "Board -> Board")
|
||||
check(p.board.get(Vector2i(4, 2)) == f and not p.board.has(Vector2i(2, 1)), "Feuer steht auf (4,2)")
|
||||
|
||||
check(p.move_slot(b1, h2, 10), "Bank auf ein belegtes Feld = Tausch")
|
||||
check(p.move_slot(b1, h2), "Bank auf ein belegtes Feld = Tausch")
|
||||
check(p.board.get(Vector2i(4, 2)) == w and p.bench[1] == f, "Wind steht, Feuer liegt auf der Bank")
|
||||
|
||||
check(p.move_slot(h2, b3, 10), "Board -> Bank")
|
||||
check(p.move_slot(h2, b3), "Board -> Bank")
|
||||
check(p.bench[3] == w and p.board.is_empty(), "Wind liegt wieder auf der Bank")
|
||||
|
||||
check(p.move_slot(b3, b1, 10), "Bank -> Bank ist ein Tausch")
|
||||
check(p.move_slot(b3, b1), "Bank -> Bank ist ein Tausch")
|
||||
check(p.bench[1] == w and p.bench[3] == f, "beide haben die Plätze getauscht")
|
||||
|
||||
# Kaderplätze bleiben begrenzt.
|
||||
# Kaderplätze bleiben durch das Level begrenzt.
|
||||
for i in 5:
|
||||
p.bench[i] = Avatar.new(Data.Role.DPS, [Data.Element.EIS] as Array[int], 1)
|
||||
var placed := 0
|
||||
for i in 5:
|
||||
if p.move_slot({"kind": "bench", "index": i}, {"kind": "board", "hex": Vector2i(i, 0)}, 1):
|
||||
if p.move_slot({"kind": "bench", "index": i}, {"kind": "board", "hex": Vector2i(i, 0)}):
|
||||
placed += 1
|
||||
check(placed == 3 and p.board.size() == 3, "Runde 1 lässt nur 3 Einheiten zu (%d)" % placed)
|
||||
check(not p.move_slot({"kind": "board", "hex": Vector2i(9, 9)}, b0, 10),
|
||||
check(placed == 3 and p.board.size() == 3, "Level 3 lässt nur 3 Einheiten zu (%d)" % placed)
|
||||
check(not p.move_slot({"kind": "board", "hex": Vector2i(9, 9)}, b0),
|
||||
"Feld ausserhalb des Boards wird abgelehnt")
|
||||
|
||||
|
||||
@@ -386,7 +399,7 @@ func test_pool_pressure() -> void:
|
||||
var guard := 0
|
||||
while g.phase != Game.Phase.GAMEOVER and guard < 200:
|
||||
if g.human.alive:
|
||||
ai.take_turn(g.human, g.pool, g.rand, g.round_no)
|
||||
ai.take_turn(g.human, g.pool, g.rand)
|
||||
g.start_combat()
|
||||
if g.phase == Game.Phase.COMBAT:
|
||||
g.skip_combat()
|
||||
@@ -440,3 +453,254 @@ func test_neutral_chance() -> void:
|
||||
% [i, quotes[i], Data.neutral_chance(i)])
|
||||
return
|
||||
check(true, "jede Quote trifft den erwarteten Wert auf 5 Prozentpunkte genau")
|
||||
|
||||
|
||||
func test_level_xp() -> void:
|
||||
print("[Level und XP]")
|
||||
var p := PlayerState.new(0, "Test", false)
|
||||
check(p.level == 3 and p.team_slots() == 3, "Start auf Level 3 = 3 Kaderplätze")
|
||||
p.gold = 3
|
||||
check(not p.buy_xp(), "ohne 4 Gold kein XP-Kauf")
|
||||
p.gold = 8
|
||||
check(p.buy_xp() and p.level == 4 and p.gold == 4, "4 Gold -> 4 XP -> Level 4")
|
||||
check(p.buy_xp() and p.level == 4 and p.xp == 4, "weitere 4 XP reichen für Level 5 noch nicht (4/8)")
|
||||
p.gain_xp(4)
|
||||
check(p.level == 5 and p.xp == 0, "Rundenerfahrung füllt auf Level 5")
|
||||
p.gain_xp(1000)
|
||||
check(p.level == Data.MAX_LEVEL and p.xp == 0, "Level ist bei %d gedeckelt" % Data.MAX_LEVEL)
|
||||
check(not p.can_buy_xp(), "auf Maximalstufe kein XP-Kauf mehr")
|
||||
|
||||
var g := Game.new()
|
||||
g.start(11)
|
||||
var lvl := g.human.level
|
||||
g.start_combat()
|
||||
g.skip_combat()
|
||||
check(g.human.xp == Data.XP_PER_ROUND or g.human.level > lvl,
|
||||
"jede Runde gibt %d XP" % Data.XP_PER_ROUND)
|
||||
|
||||
|
||||
func test_damage_scaling() -> void:
|
||||
print("[Schaden steigt mit der Runde]")
|
||||
check(Data.round_base_damage(1) == 2, "Runde 1: Grundschaden 2")
|
||||
check(Data.round_base_damage(9) == 4, "Runde 9: Grundschaden 4")
|
||||
check(Data.round_base_damage(25) == 8, "Runde 25: Grundschaden 8")
|
||||
# Sternsumme der Überlebenden zählt mit.
|
||||
var pool := Pool.new()
|
||||
var strong := PlayerState.new(0, "A", false)
|
||||
for i in 4:
|
||||
strong.board[Vector2i(i, 1)] = Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 3)
|
||||
var weak := PlayerState.new(1, "B", false)
|
||||
weak.board[Vector2i(3, 1)] = Avatar.new(Data.Role.HEILER, [Data.Element.EIS] as Array[int], 1)
|
||||
var c := Combat.new(strong, weak, 5)
|
||||
c.run_to_end()
|
||||
check(c.winner == 0, "das starke Team gewinnt")
|
||||
check(c.survivor_stars >= 3 * c.survivors and c.survivors > 0,
|
||||
"Sternsumme der Überlebenden = %d bei %d Einheiten" % [c.survivor_stars, c.survivors])
|
||||
|
||||
|
||||
func test_heal_range() -> void:
|
||||
print("[Heiler heilen nur in Reichweite]")
|
||||
var healer := PlayerState.new(0, "A", false)
|
||||
healer.board[Vector2i(0, 3)] = Avatar.new(Data.Role.HEILER, [Data.Element.WASSER] as Array[int], 1)
|
||||
healer.board[Vector2i(6, 0)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
|
||||
var foe := PlayerState.new(1, "B", false)
|
||||
foe.board[Vector2i(6, 0)] = Avatar.new(Data.Role.DPS, [Data.Element.BLITZ] as Array[int], 1)
|
||||
var c := Combat.new(healer, foe, 3)
|
||||
var h: Combat.CUnit = null
|
||||
var tank: Combat.CUnit = null
|
||||
for u in c.units:
|
||||
if u.team == 0 and u.avatar.role == Data.Role.HEILER:
|
||||
h = u
|
||||
if u.team == 0 and u.avatar.role == Data.Role.TANK:
|
||||
tank = u
|
||||
check(Hex.distance(h.pos, tank.pos) > Data.HEAL_RANGE, "Tank steht außer Heilreichweite")
|
||||
var list := c._allies_by_missing_hp(0, h)
|
||||
check(list.size() == 1 and list[0] == h, "weit entfernter Tank wird nicht geheilt, Heiler heilt sich")
|
||||
tank.pos = Vector2i(1, 7)
|
||||
list = c._allies_by_missing_hp(0, h)
|
||||
check(list.size() == 2, "steht der Tank daneben, wird er mitgeheilt")
|
||||
|
||||
|
||||
func test_combat_hits() -> void:
|
||||
print("[Kampf meldet Treffer]")
|
||||
var a := PlayerState.new(0, "A", false)
|
||||
var b := PlayerState.new(1, "B", false)
|
||||
for i in 3:
|
||||
a.board[Vector2i(2 + i, 0)] = Avatar.new(i, [Data.Element.FEUER] as Array[int], 1)
|
||||
b.board[Vector2i(2 + i, 0)] = Avatar.new(i, [Data.Element.WIND] as Array[int], 1)
|
||||
var silent := Combat.new(a, b, 8)
|
||||
silent.run_to_end()
|
||||
check(silent.hits.is_empty(), "Bot-Kämpfe zeichnen nichts auf")
|
||||
var loud := Combat.new(a, b, 8)
|
||||
loud.record_hits = true
|
||||
for i in 300:
|
||||
loud.step(0.05)
|
||||
var kinds := {}
|
||||
for h in loud.hits:
|
||||
kinds[h["kind"]] = kinds.get(h["kind"], 0) + 1
|
||||
check(kinds.get("dmg", 0) > 0, "Schadenstreffer werden gemeldet (%d)" % kinds.get("dmg", 0))
|
||||
check(kinds.get("heal", 0) > 0, "Heilungen werden gemeldet (%d)" % kinds.get("heal", 0))
|
||||
check(kinds.get("cast", 0) > 0, "Zauber werden gemeldet (%d)" % kinds.get("cast", 0))
|
||||
|
||||
|
||||
func test_plan_timer() -> void:
|
||||
print("[Planungsuhr]")
|
||||
var g := Game.new()
|
||||
g.start(21)
|
||||
check(is_equal_approx(g.plan_left, Data.PLAN_SECONDS), "Runde startet mit %d s" % int(Data.PLAN_SECONDS))
|
||||
check(not g.tick_planning(10.0) and g.phase == Game.Phase.PLANNING, "nach 10 s noch Planung")
|
||||
check(g.tick_planning(Data.PLAN_SECONDS), "Ablauf startet den Kampf von selbst")
|
||||
check(g.phase == Game.Phase.COMBAT, "Phase ist Kampf")
|
||||
g.skip_combat()
|
||||
check(is_equal_approx(g.plan_left, Data.PLAN_SECONDS), "neue Runde setzt die Uhr zurück")
|
||||
g.timer_enabled = false
|
||||
check(not g.tick_planning(999.0) and g.phase == Game.Phase.PLANNING, "abgeschaltete Uhr läuft nicht ab")
|
||||
|
||||
|
||||
func test_bots_reach_three_star() -> void:
|
||||
print("[Bots erreichen 3-Star]")
|
||||
var three := 0
|
||||
var games := 4
|
||||
for run in games:
|
||||
var g := Game.new()
|
||||
g.start(7000 + run)
|
||||
var ai := BotAI.new()
|
||||
ai.setup(g.rand)
|
||||
var guard := 0
|
||||
var seen := {}
|
||||
while g.phase != Game.Phase.GAMEOVER and guard < 200:
|
||||
if g.human.alive:
|
||||
ai.take_turn(g.human, g.pool, g.rand)
|
||||
for p in g.players:
|
||||
for u in p.board_units():
|
||||
if u.star == 3:
|
||||
seen[u.id] = true
|
||||
g.start_combat()
|
||||
if g.phase == Game.Phase.COMBAT:
|
||||
g.skip_combat()
|
||||
guard += 1
|
||||
three += seen.size()
|
||||
check(three > 0, "in %d Partien entstanden %d 3-Star-Avatare" % [games, three])
|
||||
|
||||
|
||||
func test_shop_lock() -> void:
|
||||
print("[Shop einfrieren]")
|
||||
var g := Game.new()
|
||||
g.start(31)
|
||||
var p := g.human
|
||||
var sig := []
|
||||
for e in p.shop:
|
||||
sig.append([e.role, e.element])
|
||||
p.shop_locked = true
|
||||
g.start_combat()
|
||||
g.skip_combat()
|
||||
var same := p.shop.size() == sig.size()
|
||||
for i in p.shop.size():
|
||||
if same and (p.shop[i].role != sig[i][0] or p.shop[i].element != sig[i][1]):
|
||||
same = false
|
||||
check(same, "eingefrorener Shop bleibt über den Rundenwechsel gleich")
|
||||
p.gold = 10
|
||||
p.reroll(g.pool, g.rand)
|
||||
check(not p.shop_locked, "Würfeln hebt das Einfrieren auf")
|
||||
p.shop = [ShopEntry.new(0, -1)]
|
||||
p.gold = 5
|
||||
p.buy(0, g.pool, Data.Element.ERDE)
|
||||
check(p.last_element == Data.Element.ERDE, "letzte Spezialisierung wird gemerkt")
|
||||
|
||||
|
||||
func test_element_profiles() -> void:
|
||||
print("[Elemente prägen die Werte]")
|
||||
var feuer := Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 1).base_stats()
|
||||
var erde := Avatar.new(Data.Role.DPS, [Data.Element.ERDE] as Array[int], 1).base_stats()
|
||||
var wind := Avatar.new(Data.Role.DPS, [Data.Element.WIND] as Array[int], 1).base_stats()
|
||||
var blitz := Avatar.new(Data.Role.DPS, [Data.Element.BLITZ] as Array[int], 1).base_stats()
|
||||
check(feuer["atk"] > erde["atk"] * 1.2, "Feuer schlägt härter als Erde (%d vs %d)" % [int(feuer["atk"]), int(erde["atk"])])
|
||||
check(erde["hp"] > feuer["hp"] * 1.25, "Erde hält mehr aus als Feuer (%d vs %d)" % [int(erde["hp"]), int(feuer["hp"])])
|
||||
check(wind["aspd"] > erde["aspd"] * 1.25, "Wind ist schneller als Erde")
|
||||
check(blitz["crit"] > 0.0 and feuer["crit"] == 0.0, "nur Blitz bringt eigene Krit-Chance")
|
||||
var a := PlayerState.new(0, "A", false)
|
||||
a.board[Vector2i(3, 1)] = Avatar.new(Data.Role.DPS, [Data.Element.BLITZ] as Array[int], 1)
|
||||
var b := PlayerState.new(1, "B", false)
|
||||
b.board[Vector2i(3, 1)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
|
||||
var c := Combat.new(a, b, 2)
|
||||
var blitz_unit = null
|
||||
for u in c.units:
|
||||
if u.team == 0:
|
||||
blitz_unit = u
|
||||
check(blitz_unit.crit >= 0.15, "Krit-Profil kommt im Kampf an (%.2f)" % blitz_unit.crit)
|
||||
|
||||
|
||||
func test_pairing_known_early() -> void:
|
||||
print("[Gegner steht in der Planung fest]")
|
||||
var g := Game.new()
|
||||
g.start(41)
|
||||
var opp := g.human_opponent()
|
||||
check(opp != null and g.phase == Game.Phase.PLANNING, "in der Planung ist der Gegner bekannt: %s" % (opp.pname if opp else "-"))
|
||||
g.start_combat()
|
||||
var fought := g.human_opponent()
|
||||
check(fought == opp or (fought != null and fought.pname.begins_with(opp.pname)),
|
||||
"gekämpft wird gegen genau diesen Gegner")
|
||||
g.skip_combat()
|
||||
check(g.human_opponent() != null, "neue Runde, neuer Gegner bekannt")
|
||||
# Jeder Bot kennt ebenfalls seinen Gegner.
|
||||
var all_known := true
|
||||
for p in g.alive_players():
|
||||
if g.opponent_of(p) == null:
|
||||
all_known = false
|
||||
check(all_known, "jeder Spieler hat einen Gegner")
|
||||
|
||||
|
||||
func test_recap() -> void:
|
||||
print("[Rückblick]")
|
||||
var g := Game.new()
|
||||
g.start(51)
|
||||
g.human.board[Vector2i(3, 1)] = Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 2)
|
||||
g.human.board[Vector2i(2, 0)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
|
||||
g.start_combat()
|
||||
g.skip_combat()
|
||||
var r := g.human_recap
|
||||
check(not r.is_empty(), "Rückblick liegt vor")
|
||||
check(r["total"] == 2 and r["alive"] <= 2, "zählt eigene Einheiten (%d, %d überlebt)" % [r["total"], r["alive"]])
|
||||
check(r["opponent"] != "" and r.has("result") and r.has("damage"), "nennt Gegner, Ergebnis und Schaden")
|
||||
check(r["best_dealt"] > 0 or r["best_healed"] > 0, "stärkste Einheit hat etwas beigetragen (%s)" % r["best_name"])
|
||||
|
||||
|
||||
func test_bot_arrange() -> void:
|
||||
print("[Bots stellen gegen den Gegner auf]")
|
||||
var ai := BotAI.new()
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 3
|
||||
ai.setup(rng)
|
||||
var me := PlayerState.new(1, "Bot", true)
|
||||
me.level = 6
|
||||
for i in 2:
|
||||
me.board[Vector2i(i, 3)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
|
||||
for i in 3:
|
||||
me.board[Vector2i(2 + i, 0)] = Avatar.new(Data.Role.DPS, [Data.Element.FEUER] as Array[int], 1)
|
||||
var opp := PlayerState.new(2, "Gegner", true)
|
||||
# Gegnerische Schützen links (aus seiner Sicht), also rechts aus unserer.
|
||||
for i in 3:
|
||||
opp.board[Vector2i(0, i)] = Avatar.new(Data.Role.DPS, [Data.Element.BLITZ] as Array[int], 1)
|
||||
opp.board[Vector2i(6, 0)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
|
||||
ai.arrange(me, opp)
|
||||
check(me.board.size() == 5, "alle Einheiten stehen wieder (%d)" % me.board.size())
|
||||
var tanks_front := true
|
||||
var dps_back := true
|
||||
var tank_cols := []
|
||||
var dps_cols := []
|
||||
for hex in me.board:
|
||||
var u: Avatar = me.board[hex]
|
||||
if u.role == Data.Role.TANK:
|
||||
tanks_front = tanks_front and hex.y == 0
|
||||
tank_cols.append(hex.x)
|
||||
else:
|
||||
dps_back = dps_back and hex.y >= 2
|
||||
dps_cols.append(hex.x)
|
||||
check(tanks_front, "Tanks stehen vorn")
|
||||
check(dps_back, "Schützen stehen hinten")
|
||||
check(tank_cols.has(6), "ein Tank steht vor den gegnerischen Schützen (Spalte 6)")
|
||||
var avg := 0.0
|
||||
for c in dps_cols:
|
||||
avg += c
|
||||
avg /= dps_cols.size()
|
||||
check(avg > 3.0, "Schützen meiden die Seite des gegnerischen Tanks (Mittel %.1f)" % avg)
|
||||
|
||||
Reference in New Issue
Block a user