go_games_collection/oneohone_bot.go

141 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import "math/rand"
// oneOhOneNamePool — имена ботов в 101. Отдельный пул от других игр
// коллекции — обычные русские женские имена для разнообразия.
var oneOhOneNamePool = []string{
"Мария", "Анна", "Ольга", "Елена", "Наталья", "Светлана", "Ирина", "Татьяна",
}
// oneOhOneNamePoolEN — англоязычная версия того же пула.
var oneOhOneNamePoolEN = []string{
"Mary", "Anna", "Olivia", "Helen", "Natalie", "Sylvia", "Irene", "Diana",
}
func activeOneOhOneNamePool() []string {
if CurrentLang() == LangEN {
return oneOhOneNamePoolEN
}
return oneOhOneNamePool
}
func pickOneOhOneBotName(used map[string]bool) string {
pool := activeOneOhOneNamePool()
free := make([]string, 0, len(pool))
for _, name := range pool {
if !used[name] {
free = append(free, name)
}
}
if len(free) == 0 {
free = pool
}
return free[rand.Intn(len(free))]
}
func newOneOhOneBotNames(n int) []string {
used := make(map[string]bool, n)
names := make([]string, n)
for i := 0; i < n; i++ {
name := pickOneOhOneBotName(used)
used[name] = true
names[i] = name
}
return names
}
// OneOhOneBot — единственная (без градации уровней) стратегия бота
// для 101: старается в первую очередь избавляться от специальных и
// дорогих карт, чтобы и снизить будущий штраф, и потрепать
// соперников.
type OneOhOneBot struct{}
// DecideAction решает, что делать текущему игроку: если есть
// подходящая карта — возвращает (card, true) с наиболее выгодной
// для игры картой; если подходящей карты нет — возвращает
// (Card{}, false), и вызывающий код должен вызвать g.Draw().
func (OneOhOneBot) DecideAction(g *OneOhOneGameState) (Card, bool) {
p := g.current()
var candidates []Card
for _, card := range p.Hand {
if g.canPlay(card) {
candidates = append(candidates, card)
}
}
if len(candidates) == 0 {
return Card{}, false
}
best := candidates[0]
bestScore := oneOhOnePlayPriority(best)
for _, c := range candidates[1:] {
if s := oneOhOnePlayPriority(c); s > bestScore {
best = c
bestScore = s
}
}
return best, true
}
// oneOhOnePlayPriority оценивает, насколько выгодно сыграть карту
// прямо сейчас: специальные карты (создающие проблемы сопернику)
// ценятся выше, среди обычных карт предпочтение отдаётся более
// дорогим (чтобы не осталась в руке при чужой победе).
func oneOhOnePlayPriority(c Card) int {
skip, penalty := oneOhOneCardEffect(c)
if skip {
return 100 + penalty*10
}
if c.Rank == Queen {
return 90 // дама — хороший финальный аккорд, но и просто ценна для сброса
}
if c.Rank == Eight {
return 85 // восьмёрка создаёт сопернику проблему покрытия
}
return oneOhOneCardValue(c.Rank)
}
// DecideSuit выбирает, какую масть назвать после дамы: масть, карт
// которой у бота больше всего на руке (чтобы собственные будущие
// ходы было легче подбирать под неё).
func (OneOhOneBot) DecideSuit(g *OneOhOneGameState) Suit {
p := g.current()
counts := map[Suit]int{}
for _, c := range p.Hand {
counts[c.Suit]++
}
best := Clubs
bestCount := -1
for s := Clubs; s <= Spades; s++ {
if counts[s] > bestCount {
best = s
bestCount = counts[s]
}
}
return best
}
// PlayFullTurn исполняет один полный ход бота (в контексте 101 —
// одно решение: сыграть карту или взять из колоды, повторяя Draw
// при необходимости до тех пор, пока подходящая карта не найдётся
// или колода не иссякнет) — используется при автоматической
// прокрутке партии.
func (b OneOhOneBot) PlayFullTurn(g *OneOhOneGameState) error {
if g.Phase == OneOhOnePhaseChooseSuit {
return g.ChooseSuit(b.DecideSuit(g))
}
if g.Phase != OneOhOnePhasePlay {
return nil
}
turnPlayer := g.CurrentPlayerIdx
for g.Phase == OneOhOnePhasePlay && g.CurrentPlayerIdx == turnPlayer {
if card, ok := b.DecideAction(g); ok {
return g.PlayCard(card)
}
if err := g.Draw(); err != nil {
return err
}
}
return nil
}