156 lines
4.0 KiB
GDScript
156 lines
4.0 KiB
GDScript
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)
|