141 lines
4.8 KiB
Go
141 lines
4.8 KiB
Go
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
|
||
}
|