init
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
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"` // کُت: تیم بازنده هیچ دستی نبرد
|
||||
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
|
||||
}
|
||||
g.Scores[winnerTeam] += points
|
||||
|
||||
// چرخش حاکمی: اگر تیم حاکم برد، حاکم میماند؛ وگرنه به نفر بعد میرسد.
|
||||
nextHakem := g.Hakem
|
||||
if Team(g.Hakem) != winnerTeam {
|
||||
nextHakem = (g.Hakem + 1) % 4
|
||||
}
|
||||
|
||||
g.LastResult = &HandResult{
|
||||
WinnerTeam: winnerTeam,
|
||||
Kot: kot,
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user