first commit
This commit is contained in:
155
scripts/core/BotAI.gd
Normal file
155
scripts/core/BotAI.gd
Normal file
@@ -0,0 +1,155 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user