237 lines
7.5 KiB
Go
237 lines
7.5 KiB
Go
package ws
|
|
|
|
import "hakemsho/internal/game"
|
|
|
|
// هوشِ باتِ حکم — تصمیمگیریِ اکتشافی (heuristic) که فقط از دستِ خودِ بات و
|
|
// کارتهای روی زمین استفاده میکند (بدونِ دیدنِ دستِ دیگران؛ بازیِ منصفانه).
|
|
//
|
|
// راهبردها:
|
|
// - انتخابِ حکم: خالی که هم بلند است و هم کارتِ قوی دارد.
|
|
// - رهبریِ دست: آسِ غیرِحکم را نقد کن؛ وگرنه از بلندترین خالِ غیرِحکم کارتِ کم بریز.
|
|
// - دنبالکردن: اگر شریک میبرد کارتِ کم بریز؛ اگر حریف میبرد با کمترین کارتِ
|
|
// برنده ببر؛ اگر خالِ زمینه را نداری و حریف میبرد با کمترین حکم بِبُر.
|
|
|
|
// botChooseTrump خالِ حکم را بر اساسِ طول + قدرتِ کارتها انتخاب میکند.
|
|
func botChooseTrump(hand []game.Card) game.Suit {
|
|
var score [4]float64
|
|
for _, c := range hand {
|
|
score[c.Suit] += 1
|
|
switch c.Rank {
|
|
case game.Ace:
|
|
score[c.Suit] += 2.0
|
|
case game.King:
|
|
score[c.Suit] += 1.3
|
|
case game.Queen:
|
|
score[c.Suit] += 0.7
|
|
case game.Jack:
|
|
score[c.Suit] += 0.3
|
|
}
|
|
}
|
|
best, bestScore := game.Suit(0), -1.0
|
|
for s := game.Suit(0); s < 4; s++ {
|
|
if score[s] > bestScore {
|
|
bestScore, best = score[s], s
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// botPlay کارتی که بات باید بازی کند را برمیگرداند. seen حافظهی کارتهای
|
|
// بازیشدهی این هَند است (کارتشماری برای تصمیمِ بهتر).
|
|
func botPlay(hand []game.Card, trick []game.TrickCard, trump, lead game.Suit, seen [4]uint16) game.Card {
|
|
if len(trick) == 0 {
|
|
return botLead(hand, trump, seen)
|
|
}
|
|
|
|
best := trickBest(trick, trump, lead)
|
|
// جایگاهها متناوباند؛ کارتی که دو نوبت قبل انداخته شده متعلق به شریکِ باتِ
|
|
// در نوبت است. اگر آن (یا هر کارتِ شریک) بهترین کارتِ فعلی باشد، شریک میبرد.
|
|
partnerWins := false
|
|
if len(trick) >= 2 {
|
|
partnerWins = best.Seat == trick[len(trick)-2].Seat
|
|
}
|
|
|
|
followers := cardsOfSuit(hand, lead)
|
|
if len(followers) > 0 {
|
|
if partnerWins {
|
|
return lowest(followers) // شریک میبرد ⇒ کارتِ کم بریز
|
|
}
|
|
if w, ok := lowestWinner(followers, best.Card, trump, lead); ok {
|
|
return w // با کمترین کارتِ برنده ببر
|
|
}
|
|
return lowest(followers) // نمیتوانی ببری ⇒ کارتِ کم
|
|
}
|
|
|
|
// خالِ زمینه را نداریم (void).
|
|
trumps := cardsOfSuit(hand, trump)
|
|
if partnerWins || len(trumps) == 0 {
|
|
return lowestDiscard(hand, trump)
|
|
}
|
|
if w, ok := lowestWinner(trumps, best.Card, trump, lead); ok {
|
|
return w // با کمترین حکمِ برنده بِبُر
|
|
}
|
|
return lowestDiscard(hand, trump) // حکمت هم نمیبرد ⇒ دورریز
|
|
}
|
|
|
|
// botLead راهبردِ رهبریِ دست با کارتشماری:
|
|
// ۱. کارتِ «برندهی قطعی» (بالاترین کارتِ باقیماندهی یک خالِ غیرِحکم) را نقد کن.
|
|
// ۲. اگر کنترلِ حکم داری (بالاترین حکمِ باقیمانده + ۳ حکم یا بیشتر) حکم بکِش.
|
|
// ۳. وگرنه از کوتاهترین خالِ غیرِحکم کارتِ کم بریز تا زود void شوی و بعداً بِبُری
|
|
// (اگر حکم داری)؛ در نبودِ حکم از بلندترین خال کارتِ کم.
|
|
func botLead(hand []game.Card, trump game.Suit, seen [4]uint16) game.Card {
|
|
counts := suitCounts(hand)
|
|
|
|
// ۱. برندهی قطعی: کارتی از خالِ غیرِحکم که هیچ کارتِ بالاتری از آن بیرون نمانده.
|
|
var boss *game.Card
|
|
for i := range hand {
|
|
c := hand[i]
|
|
if c.Suit == trump {
|
|
continue
|
|
}
|
|
if higherOut(seen, hand, c.Suit, c.Rank) == 0 {
|
|
if boss == nil || counts[c.Suit] > counts[boss.Suit] ||
|
|
(counts[c.Suit] == counts[boss.Suit] && c.Rank > boss.Rank) {
|
|
cc := c
|
|
boss = &cc
|
|
}
|
|
}
|
|
}
|
|
if boss != nil {
|
|
return *boss
|
|
}
|
|
|
|
// ۲. کشیدنِ حکم: اگر بالاترین حکمِ باقیمانده دستِ ماست و حکمِ زیاد داریم.
|
|
trumps := cardsOfSuit(hand, trump)
|
|
if len(trumps) >= 3 {
|
|
top := highest(trumps)
|
|
if higherOut(seen, hand, trump, top.Rank) == 0 {
|
|
return top
|
|
}
|
|
}
|
|
|
|
// ۳. کوتاهترین خالِ غیرِحکم (برای void شدن) اگر حکم داریم؛ وگرنه بلندترین.
|
|
haveTrump := len(trumps) > 0
|
|
bestSuit, bestLen := game.Suit(255), -1
|
|
for s := game.Suit(0); s < 4; s++ {
|
|
if s == trump || counts[s] == 0 {
|
|
continue
|
|
}
|
|
better := false
|
|
if bestLen < 0 {
|
|
better = true
|
|
} else if haveTrump {
|
|
better = counts[s] < bestLen // کوتاهترین
|
|
} else {
|
|
better = counts[s] > bestLen // بلندترین
|
|
}
|
|
if better {
|
|
bestLen, bestSuit = counts[s], s
|
|
}
|
|
}
|
|
if bestSuit != game.Suit(255) {
|
|
return lowest(cardsOfSuit(hand, bestSuit))
|
|
}
|
|
// فقط حکم مانده ⇒ بالاترین حکم را بزن.
|
|
return highest(trumps)
|
|
}
|
|
|
|
// suitCounts تعدادِ کارتِ هر خال در دست.
|
|
func suitCounts(hand []game.Card) [4]int {
|
|
var c [4]int
|
|
for _, x := range hand {
|
|
c[x.Suit]++
|
|
}
|
|
return c
|
|
}
|
|
|
|
// higherOut تعدادِ کارتهای همخالِ بالاتر از rank که هنوز بازی نشدهاند و در دستِ
|
|
// خودِ بات هم نیستند (یعنی دستِ حریف/شریکاند). ۰ یعنی این کارت «برندهی خال» است.
|
|
func higherOut(seen [4]uint16, hand []game.Card, suit game.Suit, rank game.Rank) int {
|
|
n := 0
|
|
for r := rank + 1; r <= game.Ace; r++ {
|
|
if seen[suit]&(1<<(r-2)) != 0 {
|
|
continue // بازی شده
|
|
}
|
|
held := false
|
|
for _, c := range hand {
|
|
if c.Suit == suit && c.Rank == r {
|
|
held = true
|
|
break
|
|
}
|
|
}
|
|
if !held {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// --- کمکیها ---
|
|
|
|
func trickBest(trick []game.TrickCard, trump, lead game.Suit) game.TrickCard {
|
|
best := trick[0]
|
|
for _, tc := range trick[1:] {
|
|
if game.Beats(tc.Card, best.Card, trump, lead) {
|
|
best = tc
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func cardsOfSuit(hand []game.Card, s game.Suit) []game.Card {
|
|
var out []game.Card
|
|
for _, c := range hand {
|
|
if c.Suit == s {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func lowest(cards []game.Card) game.Card {
|
|
best := cards[0]
|
|
for _, c := range cards[1:] {
|
|
if c.Rank < best.Rank {
|
|
best = c
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func highest(cards []game.Card) game.Card {
|
|
best := cards[0]
|
|
for _, c := range cards[1:] {
|
|
if c.Rank > best.Rank {
|
|
best = c
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// lowestWinner کمترین کارتِ candidate که کارتِ فعلیِ برنده را میبرد.
|
|
func lowestWinner(candidates []game.Card, cur game.Card, trump, lead game.Suit) (game.Card, bool) {
|
|
var best game.Card
|
|
found := false
|
|
for _, c := range candidates {
|
|
if game.Beats(c, cur, trump, lead) && (!found || c.Rank < best.Rank) {
|
|
best, found = c, true
|
|
}
|
|
}
|
|
return best, found
|
|
}
|
|
|
|
// lowestDiscard کمترین کارتِ غیرِحکم را برای دورریز برمیگرداند؛ اگر فقط حکم مانده،
|
|
// کمترین حکم.
|
|
func lowestDiscard(hand []game.Card, trump game.Suit) game.Card {
|
|
var nonTrump []game.Card
|
|
for _, c := range hand {
|
|
if c.Suit != trump {
|
|
nonTrump = append(nonTrump, c)
|
|
}
|
|
}
|
|
if len(nonTrump) > 0 {
|
|
return lowest(nonTrump)
|
|
}
|
|
return lowest(hand)
|
|
}
|