Drei Helden mit Nähe-/Teilnahme-XP, Talenten und drei Itemplätzen, drei Angriffschampions mit Buchung n -> n+1, Bossgrotte mit Streitfall, ruhendem Boss, idempotenter Eroberung, einmaliger Entsendung für das Siegerteam, hohem Kopfgeld und Sperre bis Sieg+4. HUD-Reiter, Porträt aus echtem Modell, Grotten-Asset, Bots für alle Systeme. Regeln M2 73/73, M1 91/91, App M2 15/15 und M1 38/38, Bossszenarien dokumentiert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
4.9 KiB
GDScript
108 lines
4.9 KiB
GDScript
extends Node
|
|
## Boss strategy comparison from one identical mid-game state (seed replay to round 7).
|
|
## Variants in round 8 for Blau (and Rot for the contest); rounds 8-11 are then played by
|
|
## the ordinary bots without the boss-hunting profile, so only the scenario differs.
|
|
## Run: godot --headless --path . res://tools/luneraria/boss_scenarios.tscn -- --seed=3
|
|
const SCENARIOS: Array[String] = ["kein_versuch", "gescheitert", "erfolgreich", "umkaempft", "ueberinvestition"]
|
|
|
|
func _ready() -> void:
|
|
call_deferred("run")
|
|
|
|
func profile(state: LuneMatch, slot: int) -> String:
|
|
var name: String = LuneBot.profile_for(state, slot)
|
|
return "ausgewogen" if name == "bossjagd" else name
|
|
|
|
func replay(seed: int) -> LuneMatch:
|
|
var state := LuneMatch.new()
|
|
state.setup({"seed": seed, "human_slot": -1})
|
|
while state.round_number < 8 and state.phase != LuneMatch.Phase.OVER:
|
|
for slot: int in range(6):
|
|
LuneBot.plan(state, slot, profile(state, slot))
|
|
state.begin_combat()
|
|
state.finish_combat()
|
|
return state
|
|
|
|
func send(state: LuneMatch, slot: int, count: int, strongest: bool, hero: bool) -> int:
|
|
var roster: Array = (state.players[slot].draft.roster as Array).duplicate()
|
|
roster.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
|
return int(a.seq) < int(b.seq) if int(a.invested) == int(b.invested) else (int(a.invested) > int(b.invested)) == strongest)
|
|
var value: int = 0
|
|
for index: int in range(mini(count, roster.size())):
|
|
if state.submit(slot, {"type": "assign", "uid": roster[index].uid, "lane": "grotto"}).ok:
|
|
value += int(roster[index].invested)
|
|
var champion: Dictionary = state.players[slot].draft.hero
|
|
if hero and not champion.is_empty():
|
|
state.submit(slot, {"type": "assign", "uid": champion.uid, "lane": "grotto"})
|
|
return value
|
|
|
|
func building_hp(state: LuneMatch, team: int) -> float:
|
|
var total: float = 0.0
|
|
for id: String in state.building_hp:
|
|
if id.begins_with("blau" if team == 0 else "rot"):
|
|
total += float(state.building_hp[id])
|
|
return total
|
|
|
|
func team_gold(state: LuneMatch, team: int) -> int:
|
|
var total: int = 0
|
|
for slot: int in range(team * 3, team * 3 + 3):
|
|
total += int(state.players[slot].gold)
|
|
return total
|
|
|
|
func run() -> void:
|
|
var seed: int = 3
|
|
for arg: String in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--seed="):
|
|
seed = int(arg.substr(7))
|
|
var results: Array = []
|
|
for scenario: String in SCENARIOS:
|
|
var state: LuneMatch = replay(seed)
|
|
var start := {"blau_hp": building_hp(state, 0), "rot_hp": building_hp(state, 1), "blau_gold": team_gold(state, 0), "rot_gold": team_gold(state, 1)}
|
|
var row := {"scenario": scenario, "sent_value": 0, "rot_sent_value": 0}
|
|
for slot: int in range(6):
|
|
LuneBot.plan(state, slot, profile(state, slot))
|
|
match scenario:
|
|
"gescheitert": row.sent_value = send(state, 1, 3, false, false)
|
|
"erfolgreich": row.sent_value = send(state, 1, 8, true, true)
|
|
"umkaempft":
|
|
row.sent_value = send(state, 1, 8, true, true)
|
|
row.rot_sent_value = send(state, 4, 8, true, true)
|
|
"ueberinvestition":
|
|
row.sent_value = send(state, 1, 12, true, true)
|
|
row.sent_value += send(state, 0, 4, true, false) + send(state, 2, 4, true, false)
|
|
var blau_mid_before: int = state.towers_alive(0, "mid")
|
|
state.begin_combat()
|
|
state.finish_combat()
|
|
row.grotto = str(state.last_settlement.boss)
|
|
row.claim_team = int(state.boss.claim.team) if not (state.boss.claim as Dictionary).is_empty() else -1
|
|
row.blau_mid_towers_lost_r8 = blau_mid_before - state.towers_alive(0, "mid")
|
|
row.blau_hp_after_r8 = building_hp(state, 0) - float(start.blau_hp)
|
|
var rot_hp_before_r9: float = building_hp(state, 1)
|
|
var boss_bounty_rot: int = 0
|
|
for extra: int in range(3):
|
|
if state.phase == LuneMatch.Phase.OVER:
|
|
break
|
|
for slot: int in range(6):
|
|
LuneBot.plan(state, slot, profile(state, slot))
|
|
state.begin_combat()
|
|
var deployed: bool = state.combat.units.any(func(u: LuneUnit) -> bool: return u.kind == "boss" and u.zone == "lane")
|
|
state.finish_combat()
|
|
if deployed:
|
|
row.boss_round = state.combat.round_number
|
|
row.rot_hp_lost_boss_round = rot_hp_before_r9 - building_hp(state, 1)
|
|
for event: Dictionary in state.combat.events:
|
|
if event.kind == "boss" and event.zone == "lane":
|
|
boss_bounty_rot = int(event.bounty)
|
|
row.boss_killed = boss_bounty_rot > 0
|
|
row.boss_bounty_to_rot = boss_bounty_rot
|
|
row.blau_hp_delta = building_hp(state, 0) - float(start.blau_hp)
|
|
row.rot_hp_delta = building_hp(state, 1) - float(start.rot_hp)
|
|
row.blau_gold_delta = team_gold(state, 0) - int(start.blau_gold)
|
|
row.rot_gold_delta = team_gold(state, 1) - int(start.rot_gold)
|
|
row.phase = "vorbei" if state.phase == LuneMatch.Phase.OVER else "läuft"
|
|
row.winner = state.winner
|
|
results.append(row)
|
|
print("SCENARIO ", JSON.stringify(row))
|
|
var file := FileAccess.open("res://docs/luneraria/logs/m2/boss-scenarios-seed%d.json" % seed, FileAccess.WRITE)
|
|
file.store_string(JSON.stringify(results, "\t"))
|
|
file.close()
|
|
get_tree().quit()
|