133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
package main
|
||
|
||
// containsCard проверяет наличие конкретной карты в срезе.
|
||
func containsCard(cards []Card, target Card) bool {
|
||
for _, c := range cards {
|
||
if c == target {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// wouldFormMeld проверяет, образует ли candidate валидную
|
||
// комбинацию (сет или сиквенс) вместе с картами руки.
|
||
func wouldFormMeld(hand []Card, candidate Card) bool {
|
||
trial := append(append([]Card{}, hand...), candidate)
|
||
for _, s := range FindAllSets(trial) {
|
||
if containsCard(s, candidate) {
|
||
return true
|
||
}
|
||
}
|
||
for _, s := range FindAllSequences(trial) {
|
||
if containsCard(s, candidate) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// cardPotential — грубая эвристика полезности карты для будущих
|
||
// комбинаций: сколько других карт руки поддерживают её как часть
|
||
// сета (тот же ранг) или сиквенса (та же масть, ранг отличается не
|
||
// больше чем на 2 — то есть в пределах будущей связки).
|
||
func cardPotential(hand []Card, card Card) int {
|
||
support := 0
|
||
for _, other := range hand {
|
||
if other == card {
|
||
continue
|
||
}
|
||
if other.Rank == card.Rank {
|
||
support++
|
||
}
|
||
if other.Suit == card.Suit {
|
||
diff := int(other.Rank) - int(card.Rank)
|
||
if diff < 0 {
|
||
diff = -diff
|
||
}
|
||
if diff <= 2 {
|
||
support++
|
||
}
|
||
}
|
||
}
|
||
return support
|
||
}
|
||
|
||
// deadestCard находит карту руки с наименьшим потенциалом
|
||
// (см. cardPotential); при равенстве потенциала предпочитает карту
|
||
// с наибольшей очковой стоимостью — чтобы избавляться от тяжёлого
|
||
// "мёртвого груза" в первую очередь.
|
||
func deadestCard(hand []Card) Card {
|
||
best := hand[0]
|
||
bestPotential := cardPotential(hand, best)
|
||
for _, c := range hand[1:] {
|
||
p := cardPotential(hand, c)
|
||
if p < bestPotential || (p == bestPotential && c.Value() > best.Value()) {
|
||
best = c
|
||
bestPotential = p
|
||
}
|
||
}
|
||
return best
|
||
}
|
||
|
||
// layAllAvailableMelds выкладывает на стол все комбинации, которые
|
||
// может собрать текущий игрок из своей руки, повторяя поиск после
|
||
// каждой выкладки (так как рука меняется). Возвращает количество
|
||
// выложенных комбинаций.
|
||
func layAllAvailableMelds(g *GameState) int {
|
||
laid := 0
|
||
for {
|
||
hand := g.current().Hand
|
||
sets := FindAllSets(hand)
|
||
seqs := FindAllSequences(hand)
|
||
|
||
var best []Card
|
||
for _, s := range sets {
|
||
if len(s) > len(best) {
|
||
best = s
|
||
}
|
||
}
|
||
for _, s := range seqs {
|
||
if len(s) > len(best) {
|
||
best = s
|
||
}
|
||
}
|
||
if best == nil {
|
||
return laid
|
||
}
|
||
if err := g.LayNewMeld(best); err != nil {
|
||
return laid
|
||
}
|
||
laid++
|
||
if g.Phase == PhaseGameOver {
|
||
return laid
|
||
}
|
||
}
|
||
}
|
||
|
||
// tryExtendTableMelds пытается подложить карты руки к уже
|
||
// существующим на столе комбинациям (в том числе чужим). Полезно
|
||
// боту любого уровня, чтобы не копить карты, которые уже подходят
|
||
// под стол.
|
||
func tryExtendTableMelds(g *GameState) {
|
||
for {
|
||
extended := false
|
||
for i := range g.Table {
|
||
for _, card := range g.current().Hand {
|
||
if g.AddToMeld(i, card) == nil {
|
||
extended = true
|
||
break
|
||
}
|
||
}
|
||
if g.Phase == PhaseGameOver {
|
||
return
|
||
}
|
||
if extended {
|
||
break
|
||
}
|
||
}
|
||
if !extended {
|
||
return
|
||
}
|
||
}
|
||
}
|