Bots planen Items/Champions vor der Armee und helfen deterministisch, damit Spiegelmatches fair bleiben. Boss und Profile neu abgestimmt; Balance-, Stress- und Volks-Werkzeuge sowie Kartenprops ergänzt.
74 lines
4.1 KiB
GDScript
74 lines
4.1 KiB
GDScript
extends Node
|
|
## Balance run: random team compositions (profiles and Bunds per slot), each played twice
|
|
## with the sides swapped. A QA round limit is reported as "Testlimit", never as a result.
|
|
## Run: godot --headless --path . res://tools/luneraria/balance_run.tscn -- --compositions=20 --limit=60 --out=res://docs/luneraria/logs/m3/balance.json
|
|
## add --matchups for all pairs of pure profile teams instead of random compositions.
|
|
func _ready() -> void:
|
|
call_deferred("run")
|
|
|
|
func play(options: Dictionary, limit: int) -> Dictionary:
|
|
var state := LuneMatch.new()
|
|
state.setup(options)
|
|
var lane_losses: Array[int] = [0, 0]
|
|
while state.phase != LuneMatch.Phase.OVER and state.round_number <= limit:
|
|
for slot: int in range(6):
|
|
LuneBot.plan(state, slot)
|
|
state.begin_combat()
|
|
state.finish_combat()
|
|
for team: int in range(2):
|
|
for lane: String in LuneMap.LANES:
|
|
lane_losses[team] += int(state.towers_alive(team, lane) == 0)
|
|
var result: String = "testlimit"
|
|
if state.phase == LuneMatch.Phase.OVER:
|
|
result = "draw" if state.winner == LuneMatch.DRAW else ("blau" if state.winner == 0 else "rot")
|
|
var gold_curve: Array = state.history.map(func(h: Dictionary) -> int: return (h.gold as Array).reduce(func(a: int, b: int) -> int: return a + b, 0))
|
|
return {"result": result, "rounds": state.history.size(), "lane_losses_at_end": lane_losses,
|
|
"boss": state.boss_log.map(func(e: Dictionary) -> String: return "%d:%s:%s" % [e.round, e.event, str(e.get("team", ""))]),
|
|
"hero_levels": state.history[-1].hero_levels if not state.history.is_empty() else [],
|
|
"timeouts": state.history.filter(func(h: Dictionary) -> bool: return h.end_reason == "timeout").size(),
|
|
"max_combat": state.history.map(func(h: Dictionary) -> float: return h.combat_time).max(), "total_gold_curve": gold_curve}
|
|
|
|
func run() -> void:
|
|
var compositions: int = 20
|
|
var limit: int = 60
|
|
var matchups: bool = false
|
|
var out: String = "res://docs/luneraria/logs/m3/balance.json"
|
|
for arg: String in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--compositions="): compositions = int(arg.substr(15))
|
|
elif arg.begins_with("--limit="): limit = int(arg.substr(8))
|
|
elif arg.begins_with("--out="): out = arg.substr(6)
|
|
elif arg == "--matchups": matchups = true
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = 20260928
|
|
var names: Array = LuneBot.PROFILES.keys()
|
|
# --matchups: every pair of pure profile teams (all three slots alike) with the same
|
|
# Bund order on both sides, so only the strategy differs. Otherwise random compositions.
|
|
var setups: Array = []
|
|
if matchups:
|
|
for i: int in range(names.size()):
|
|
for j: int in range(i + 1, names.size()):
|
|
setups.append({"profiles": [names[i], names[i], names[i], names[j], names[j], names[j]],
|
|
"bunds": ["wurzelbund", "glutpakt", "wurzelbund", "wurzelbund", "glutpakt", "wurzelbund"], "seed": 2000 + setups.size()})
|
|
else:
|
|
for index: int in range(compositions):
|
|
var drawn := {"profiles": [], "bunds": [], "seed": 1000 + index}
|
|
for slot: int in range(6):
|
|
drawn.profiles.append(names[rng.randi() % names.size()])
|
|
drawn.bunds.append(DataCatalog.FACTIONS[rng.randi() % DataCatalog.FACTIONS.size()])
|
|
setups.append(drawn)
|
|
var rows: Array = []
|
|
for index: int in range(setups.size()):
|
|
var profiles: Array = setups[index].profiles
|
|
var bunds: Array = setups[index].bunds
|
|
var seed: int = int(setups[index].seed)
|
|
var normal: Dictionary = play({"seed": seed, "human_slot": -1, "profiles": profiles, "bunds": bunds}, limit)
|
|
var swapped_profiles: Array = profiles.slice(3, 6) + profiles.slice(0, 3)
|
|
var swapped_bunds: Array = bunds.slice(3, 6) + bunds.slice(0, 3)
|
|
var swapped: Dictionary = play({"seed": seed, "human_slot": -1, "profiles": swapped_profiles, "bunds": swapped_bunds}, limit)
|
|
var row := {"index": index, "seed": seed, "profiles": profiles, "bunds": bunds, "matchup": matchups, "normal": normal, "swapped": swapped}
|
|
rows.append(row)
|
|
print("BALANCE %02d %s | %s -> %s (%d) / swapped %s (%d)" % [index, str(profiles.slice(0, 3)), str(profiles.slice(3, 6)), normal.result, normal.rounds, swapped.result, swapped.rounds])
|
|
var file := FileAccess.open(out, FileAccess.WRITE)
|
|
file.store_string(JSON.stringify(rows, "\t"))
|
|
file.close()
|
|
get_tree().quit()
|