class_name BotAI extends RefCounted ## Einfache Bot-Strategie: bevorzugte Elemente sammeln, Board füllen, aufwerten. var pref: Array[int] = [] var greed: float = 0.5 ## wie stark der Bot Gold hortet func setup(rand: RandomNumberGenerator) -> void: var all: Array[int] = [] for e in Data.ELEMENT_COUNT: all.append(e) # Bewusst mit dem Spiel-RNG mischen, nicht mit Array.shuffle(), # damit ein Seed dieselbe Partie ergibt. for i in range(all.size() - 1, 0, -1): var j := rand.randi_range(0, i) var tmp := all[i] all[i] = all[j] all[j] = tmp pref = [all[0], all[1], all[2]] greed = rand.randf_range(0.3, 0.8) func take_turn(p: PlayerState, pool: Pool, rand: RandomNumberGenerator, round_no: int) -> void: _do_upgrades(p, pool) _do_buys(p, pool, rand, round_no) _do_placements(p, pool, round_no) _do_upgrades(p, pool) var keep := int(lerpf(2.0, 18.0, greed)) if p.gold > keep + Data.REROLL_COST and rand.randf() < 0.6: p.reroll(pool, rand) _do_buys(p, pool, rand, round_no) _do_placements(p, pool, round_no) _do_upgrades(p, pool) _free_bench(p, pool) func _wants(entry: ShopEntry, p: PlayerState) -> bool: if entry.element < 0: # Neutral wird beim Kauf spezialisiert, also immer brauchbar. return true if pref.has(entry.element): return true # Elemente, die schon auf dem Board sind, sind immer Futter. return p.trait_counts().has(entry.element) func _do_buys(p: PlayerState, pool: Pool, rand: RandomNumberGenerator, round_no: int) -> void: for i in p.shop.size(): var e: ShopEntry = p.shop[i] if e.sold: continue if p.free_bench_slot() < 0: return if not _wants(e, p): continue if p.gold - e.price() < 0: continue p.buy(i, pool, _pick_element(p) if e.element < 0 else e.element) func _do_placements(p: PlayerState, pool: Pool, round_no: int) -> void: while p.can_place(round_no): var idx := _pick_bench_for_board(p) if idx < 0: return var a: Avatar = p.bench[idx] var hex := _pick_hex(p, a.role) if hex.x < 0: return if not p.place(idx, hex, round_no): return ## Zuerst Elemente aufstellen, die noch gar nicht im Team sind. ## Alles Weitere bleibt als Futter auf der Bank. func _pick_bench_for_board(p: PlayerState) -> int: var counts := p.trait_counts() var best := -1 var best_score := -1 for i in p.bench.size(): var a = p.bench[i] if a == null: continue var score := 0 if pref.has(a.elements[0]): score = 3 if counts.get(a.elements[0], 0) == 0 else 1 else: score = 2 if counts.get(a.elements[0], 0) == 0 else 0 if score > best_score: best_score = score best = i return best if best_score > 0 else -1 func _pick_element(p: PlayerState) -> int: var counts := p.trait_counts() var best := pref[0] var best_c := -1 for e in pref: var c: int = counts.get(e, 0) if c > best_c and c < 6: best_c = c best = e return best func _pick_hex(p: PlayerState, role: int) -> Vector2i: var rows: Array match role: Data.Role.TANK: rows = [0, 1, 2, 3] Data.Role.DPS: rows = [2, 1, 3, 0] _: rows = [3, 2, 1, 0] var cols := [3, 2, 4, 1, 5, 0, 6] for r in rows: for c in cols: var h := Vector2i(c, r) if not p.board.has(h): return h return Vector2i(-1, -1) func _do_upgrades(p: PlayerState, pool: Pool) -> void: var again := true while again: again = false for hex in p.board.keys(): if p.can_upgrade(hex): var options := p.available_new_elements(hex) var choice := -1 for e in pref: if options.has(e): choice = e break if choice < 0 and not options.is_empty(): choice = options[0] if choice >= 0 and p.upgrade(hex, choice, pool): again = true break ## Bank leeren, wenn sie voll ist und nichts mehr gebraucht wird. func _free_bench(p: PlayerState, pool: Pool) -> void: if p.free_bench_slot() >= 0: return var counts := p.trait_counts() for i in p.bench.size(): var a = p.bench[i] if a == null: continue if counts.get(a.elements[0], 0) == 0 and not pref.has(a.elements[0]): p.sell_bench(i, pool) return p.sell_bench(0, pool)