Files
2026-07-07 23:23:12 +03:30

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)
}