package game import "errors" // Phase مرحله بازی. type Phase int const ( PhaseChooseTrump Phase = iota // حاکم باید حکم بزند PhasePlaying // در حال بازی دست‌ها (tricks) PhaseHandOver // یک هَند تمام شد PhaseGameOver // بازی تمام شد ) func (p Phase) String() string { switch p { case PhaseChooseTrump: return "choose_trump" case PhasePlaying: return "playing" case PhaseHandOver: return "hand_over" case PhaseGameOver: return "game_over" } return "unknown" } // خطاهای موتور. var ( ErrWrongPhase = errors.New("wrong phase") ErrNotYourTurn = errors.New("not your turn") ErrNotHakem = errors.New("only hakem can choose trump") ErrCardNotHeld = errors.New("card not in hand") ErrMustFollow = errors.New("must follow lead suit") ErrInvalidSeat = errors.New("invalid seat") ) // TrickCard یک کارت انداخته‌شده روی زمین به‌همراه جایگاه بازیکن. type TrickCard struct { Seat int `json:"seat"` Card Card `json:"card"` } // HandResult نتیجه یک هَند. type HandResult struct { WinnerTeam int `json:"winner_team"` Kot bool `json:"kot"` // کُت: تیم بازنده هیچ دستی نبرد HakemKot bool `json:"hakem_kot"` // حاکم‌کُت: تیمِ حاکم کُت شد (۳ امتیاز) Points int `json:"points"` // امتیاز این هَند (۱ معمولی، ۲ کُت، ۳ حاکم‌کُت) NextHakem int `json:"next_hakem"` } // Team تیم یک جایگاه را برمی‌گرداند (0&2 تیم۰، 1&3 تیم۱). func Team(seat int) int { return seat % 2 } // Game وضعیت کامل یک میز حکم؛ فقط در حافظه نگه‌داری می‌شود. type Game struct { Phase Phase Hakem int // جایگاه حاکم Trump Suit // خال حکم TrumpChosen bool // آیا حکم انتخاب شده hands [4][]Card // کارت هر بازیکن (خصوصی) deck []Card // باقیمانده دسته برای پخش پس از انتخاب حکم Turn int // جایگاه نوبت فعلی LeadSuit Suit // خال شروع‌کننده دست جاری leadSet bool // آیا خال زمینه تعیین شده Trick []TrickCard // کارت‌های دست جاری TricksWon [2]int // تعداد دست‌های برده هر تیم در هَند جاری TrickDone bool // دستِ کامل (۴ کارت) منتظر جمع‌آوری pendingWinner int // برنده‌ی دستِ کاملِ منتظر Scores [2]int // امتیاز کلی (تعداد هَند برده) TargetScore int // امتیاز لازم برای برد بازی LastResult *HandResult // نتیجه آخرین هَند (در PhaseHandOver/GameOver) } // NewGame یک میز با حاکم اولیه مشخص می‌سازد و اولین هَند را شروع می‌کند. // hakem را می‌توان با FirstHakem از روی یک دسته تعیین کرد. func NewGame(targetScore, hakem int) *Game { g := &Game{ Hakem: hakem, TargetScore: targetScore, } g.startHand() return g } // FirstHakem با پخش کارت دور میز تا اولین آس، جایگاه حاکم اول را تعیین می‌کند. func FirstHakem() int { d := NewDeck() Shuffle(d) for i, c := range d { if c.Rank == Ace { return i % 4 } } return 0 } // startHand دسته را بُر زده، ۵ کارت اول را به حاکم می‌دهد و منتظر انتخاب حکم می‌ماند. func (g *Game) startHand() { g.deck = NewDeck() Shuffle(g.deck) for i := range g.hands { g.hands[i] = nil } // حاکم ۵ کارت اول را می‌گیرد و بر اساس آن حکم را انتخاب می‌کند. g.hands[g.Hakem] = append(g.hands[g.Hakem], g.deck[:5]...) g.Phase = PhaseChooseTrump g.Trump = 0 g.TrumpChosen = false g.Turn = g.Hakem g.LeadSuit = 0 g.leadSet = false g.Trick = nil g.TricksWon = [2]int{} g.TrickDone = false } // ChooseTrump حکم را توسط حاکم تعیین کرده و بقیه کارت‌ها را پخش می‌کند. func (g *Game) ChooseTrump(seat int, s Suit) error { if g.Phase != PhaseChooseTrump { return ErrWrongPhase } if seat != g.Hakem { return ErrNotHakem } g.Trump = s g.TrumpChosen = true g.dealRemaining() g.Phase = PhasePlaying g.Turn = g.Hakem // حاکم دست اول را شروع می‌کند return nil } // dealRemaining باقی کارت‌ها را پخش می‌کند تا هر بازیکن ۱۳ کارت داشته باشد. func (g *Game) dealRemaining() { idx := 5 // ۵ کارت اول قبلاً به حاکم داده شد deal := func(seat int) { for len(g.hands[seat]) < 13 { g.hands[seat] = append(g.hands[seat], g.deck[idx]) idx++ } } deal(g.Hakem) for s := 0; s < 4; s++ { if s != g.Hakem { deal(s) } } g.deck = nil } // PlayCard یک کارت را برای بازیکن seat بازی می‌کند. func (g *Game) PlayCard(seat int, c Card) error { if g.Phase != PhasePlaying { return ErrWrongPhase } if g.TrickDone { return ErrWrongPhase // دستِ کامل هنوز جمع نشده (CollectTrick) } if seat < 0 || seat > 3 { return ErrInvalidSeat } if seat != g.Turn { return ErrNotYourTurn } idx := indexOf(g.hands[seat], c) if idx < 0 { return ErrCardNotHeld } // قانون follow: اگر خال زمینه تعیین شده و بازیکن آن خال را دارد، باید همان را بازی کند. if g.leadSet && c.Suit != g.LeadSuit && hasSuit(g.hands[seat], g.LeadSuit) { return ErrMustFollow } // حذف کارت از دست g.hands[seat] = append(g.hands[seat][:idx], g.hands[seat][idx+1:]...) if !g.leadSet { g.LeadSuit = c.Suit g.leadSet = true } g.Trick = append(g.Trick, TrickCard{Seat: seat, Card: c}) if len(g.Trick) < 4 { g.Turn = (g.Turn + 1) % 4 return nil } // دست کامل شد ⇒ برنده تعیین می‌شود ولی جمع‌آوری به CollectTrick موکول می‌شود // تا کلاینت‌ها فرصت دیدن هر ۴ کارت روی زمین را داشته باشند. g.pendingWinner = g.trickWinner() g.TrickDone = true return nil } // CollectTrick دستِ کامل را جمع کرده، برنده را ثبت و نوبت بعد را تنظیم می‌کند. // پس از PhasePlaying و وقتی TrickDone باشد فراخوانی می‌شود. func (g *Game) CollectTrick() error { if !g.TrickDone { return ErrWrongPhase } winner := g.pendingWinner g.TricksWon[Team(winner)]++ g.Trick = nil g.leadSet = false g.Turn = winner g.TrickDone = false g.checkHandOver() return nil } // trickWinner برنده دست جاری را تعیین می‌کند. func (g *Game) trickWinner() int { best := g.Trick[0] for _, tc := range g.Trick[1:] { if beats(tc.Card, best.Card, g.Trump, g.LeadSuit) { best = tc } } return best.Seat } // beats مشخص می‌کند آیا a کارت b را می‌برد (با توجه به حکم و خال زمینه). func beats(a, b Card, trump, lead Suit) bool { aTrump := a.Suit == trump bTrump := b.Suit == trump switch { case aTrump && !bTrump: return true case !aTrump && bTrump: return false case aTrump && bTrump: return a.Rank > b.Rank default: // هیچ‌کدام آتو نیستند: فقط کارت هم‌خالِ زمینه می‌تواند ببرد if a.Suit != lead { return false } if b.Suit != lead { return true } return a.Rank > b.Rank } } // checkHandOver بررسی می‌کند آیا تیمی به ۷ دست رسیده و هَند را تسویه می‌کند. func (g *Game) checkHandOver() { const tricksToWin = 7 var winnerTeam = -1 if g.TricksWon[0] >= tricksToWin { winnerTeam = 0 } else if g.TricksWon[1] >= tricksToWin { winnerTeam = 1 } if winnerTeam == -1 { return } loserTeam := 1 - winnerTeam kot := g.TricksWon[loserTeam] == 0 points := 1 if kot { points = 2 // حاکم‌کُت: اگر تیمِ بازنده همان تیمِ حاکم باشد (حاکم صفر دست برده)، // کُت سنگین‌تر است ⇒ ۳ امتیاز. if Team(g.Hakem) == loserTeam { points = 3 } } g.Scores[winnerTeam] += points // چرخش حاکمی: اگر تیم حاکم برد، حاکم می‌ماند؛ وگرنه به نفر بعد می‌رسد. nextHakem := g.Hakem if Team(g.Hakem) != winnerTeam { nextHakem = (g.Hakem + 1) % 4 } g.LastResult = &HandResult{ WinnerTeam: winnerTeam, Kot: kot, HakemKot: points == 3, Points: points, NextHakem: nextHakem, } if g.Scores[winnerTeam] >= g.TargetScore { g.Phase = PhaseGameOver } else { g.Phase = PhaseHandOver g.Hakem = nextHakem } } // NextHand هَند بعدی را شروع می‌کند (پس از PhaseHandOver). func (g *Game) NextHand() error { if g.Phase != PhaseHandOver { return ErrWrongPhase } g.startHand() return nil } // Hand کپی کارت‌های یک بازیکن را برمی‌گرداند. func (g *Game) Hand(seat int) []Card { out := make([]Card, len(g.hands[seat])) copy(out, g.hands[seat]) return out } // HandCount تعداد کارت‌های هر بازیکن. func (g *Game) HandCount(seat int) int { return len(g.hands[seat]) } // --- کمکی‌ها --- func indexOf(hand []Card, c Card) int { for i, x := range hand { if x == c { return i } } return -1 } func hasSuit(hand []Card, s Suit) bool { for _, c := range hand { if c.Suit == s { return true } } return false }