LuneMap spiegelsymmetrisch mit 16 Türmen und 2 Ancients, LuneCombat mit reihenfolgeunabhängigem Zweiphasenschritt und Gebäudesperren für jede Schadensquelle, LuneMatch mit Planungsentwürfen, atomarer Übernahme, Einmal-Belohnung und Kernsieg/Unentschieden, endlose LuneWaves, Bots über dieselbe Befehlsschnittstelle. 87 Kernprüfungen bestanden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.7 KiB
GDScript
81 lines
3.7 KiB
GDScript
class_name LuneBot
|
|
extends RefCounted
|
|
## Plans through LuneMatch.submit exactly like the human: same costs, same caps, same phase.
|
|
## Reads only public state (committed buildings, round) and its own draft, never other drafts.
|
|
|
|
const PROFILES: Dictionary = {
|
|
"ausgewogen": {"front": .30, "melee": .25, "ranged": .25, "splash": .10, "support": .10, "upgrade_from": 6, "support_lane": 0.0},
|
|
"verteidigung": {"front": .40, "melee": .15, "ranged": .20, "splash": .10, "support": .15, "upgrade_from": 5, "support_lane": 0.0},
|
|
"druck": {"front": .20, "melee": .35, "ranged": .25, "splash": .15, "support": .05, "upgrade_from": 8, "support_lane": 0.0},
|
|
"fernkampf": {"front": .25, "melee": .10, "ranged": .40, "splash": .15, "support": .10, "upgrade_from": 6, "support_lane": 0.0},
|
|
"teamhilfe": {"front": .30, "melee": .20, "ranged": .25, "splash": .10, "support": .15, "upgrade_from": 6, "support_lane": .35},
|
|
}
|
|
|
|
static func profile_for(match_state: LuneMatch, slot: int) -> String:
|
|
var names: Array = PROFILES.keys()
|
|
return names[(slot * 7 + match_state.seed_value) % names.size()]
|
|
|
|
static func plan(match_state: LuneMatch, slot: int, profile_name: String = "") -> void:
|
|
var name: String = profile_name if not profile_name.is_empty() else profile_for(match_state, slot)
|
|
var profile: Dictionary = PROFILES[name]
|
|
var player: Dictionary = match_state.players[slot]
|
|
var ids: Array[String] = match_state.base_ids(player.bund)
|
|
var roles: Dictionary = {}
|
|
for id: String in ids:
|
|
roles[match_state.role_of(id)] = id
|
|
for guard: int in range(40):
|
|
var draft: Dictionary = player.draft
|
|
var roster: Array = draft.roster
|
|
var acted: bool = false
|
|
if roster.size() >= int(profile.upgrade_from) or roster.size() >= match_state.balance.supply_cap:
|
|
acted = _try_upgrade(match_state, slot, roster)
|
|
if not acted and roster.size() < match_state.balance.supply_cap:
|
|
var role: String = _needed_role(match_state, roster, profile)
|
|
var lane: String = player.lane
|
|
if float(profile.support_lane) > 0.0 and match_state.rng.randf() < float(profile.support_lane):
|
|
lane = _weakest_lane(match_state, int(player.team))
|
|
acted = match_state.submit(slot, {"type": "buy", "type_id": roles[role], "lane": lane}).ok
|
|
if not acted and roster.size() < int(profile.upgrade_from):
|
|
acted = _try_upgrade(match_state, slot, roster)
|
|
if not acted:
|
|
break
|
|
match_state.set_ready(slot)
|
|
|
|
static func _needed_role(match_state: LuneMatch, roster: Array, profile: Dictionary) -> String:
|
|
var counts: Dictionary = {}
|
|
for entry: Dictionary in roster:
|
|
var role: String = match_state.role_of(entry.type_id)
|
|
counts[role] = int(counts.get(role, 0)) + 1
|
|
var best: String = "front"
|
|
var best_gap: float = -INF
|
|
for role: String in LuneMatch.ROW_ORDER:
|
|
var gap: float = float(profile[role]) * float(roster.size() + 1) - float(counts.get(role, 0))
|
|
if gap > best_gap + 0.0001:
|
|
best = role
|
|
best_gap = gap
|
|
return best
|
|
|
|
static func _try_upgrade(match_state: LuneMatch, slot: int, roster: Array) -> bool:
|
|
var best_uid: String = ""
|
|
var best_cost: int = 1 << 30
|
|
for entry: Dictionary in roster:
|
|
var upgrade: String = str(DataCatalog.get_unit(entry.type_id).get("upgrade_id", ""))
|
|
if upgrade.is_empty():
|
|
continue
|
|
var cost: int = int(DataCatalog.get_unit(upgrade).cost)
|
|
if cost < best_cost or (cost == best_cost and str(entry.uid) < best_uid):
|
|
best_cost = cost
|
|
best_uid = str(entry.uid)
|
|
if best_uid.is_empty():
|
|
return false
|
|
return match_state.submit(slot, {"type": "upgrade", "uid": best_uid}).ok
|
|
|
|
static func _weakest_lane(match_state: LuneMatch, team: int) -> String:
|
|
var weakest: String = "mid"
|
|
var fewest: int = 99
|
|
for lane: String in LuneMap.LANES:
|
|
var towers: int = match_state.towers_alive(team, lane)
|
|
if towers < fewest:
|
|
fewest = towers
|
|
weakest = lane
|
|
return weakest
|