package game import "testing" func TestParseCardRoundTrip(t *testing.T) { cases := []string{"AS", "10H", "2C", "KD", "JS", "QH"} for _, code := range cases { c, err := ParseCard(code) if err != nil { t.Fatalf("parse %q: %v", code, err) } if c.String() != code { t.Errorf("roundtrip %q -> %q", code, c.String()) } } } func TestBeats(t *testing.T) { trump := Hearts lead := Spades // آتو بر غیرآتو می‌برد if !beats(Card{Hearts, 2}, Card{Spades, Ace}, trump, lead) { t.Error("trump 2 should beat non-trump Ace") } // بالاترین خال زمینه بر کارت خال دیگر (غیرآتو) می‌برد if !beats(Card{Spades, 5}, Card{Diamonds, Ace}, trump, lead) { t.Error("lead-suit card should beat off-suit card") } // کارت خارج از خال زمینه و غیرآتو نمی‌برد if beats(Card{Diamonds, Ace}, Card{Spades, 2}, trump, lead) { t.Error("off-suit non-trump should not beat lead suit") } } func TestMustFollowSuit(t *testing.T) { g := NewGame(7, 0) if err := g.ChooseTrump(0, Hearts); err != nil { t.Fatal(err) } // حاکم (seat 0) شروع می‌کند. یک کارت از دستش بازی می‌کنیم. lead := g.hands[0][0] if err := g.PlayCard(0, lead); err != nil { t.Fatal(err) } // بازیکن بعدی اگر کارت هم‌خال دارد باید همان را بزند. next := g.Turn if hasSuit(g.hands[next], lead.Suit) { // یک کارت با خال متفاوت پیدا کن for _, c := range g.hands[next] { if c.Suit != lead.Suit { if err := g.PlayCard(next, c); err != ErrMustFollow { t.Fatalf("expected ErrMustFollow, got %v", err) } return } } } } // playFullGame یک بازی کامل را با یک ربات ساده تا پایان پیش می‌برد و // تمام نامتغیرها (invariants) را بررسی می‌کند. func TestFullGamePlaysOut(t *testing.T) { g := NewGame(7, FirstHakem()) for hand := 0; g.Phase != PhaseGameOver; hand++ { if hand > 100 { t.Fatal("too many hands; engine likely stuck") } // انتخاب حکم توسط حاکم if g.Phase != PhaseChooseTrump { t.Fatalf("expected choose_trump, got %s", g.Phase) } if err := g.ChooseTrump(g.Hakem, g.hands[g.Hakem][0].Suit); err != nil { t.Fatal(err) } // بررسی: هر بازیکن دقیقاً ۱۳ کارت دارد for s := 0; s < 4; s++ { if g.HandCount(s) != 13 { t.Fatalf("seat %d has %d cards after deal", s, g.HandCount(s)) } } // بازی دست‌ها تا پایان هَند for g.Phase == PhasePlaying { if g.TrickDone { if err := g.CollectTrick(); err != nil { t.Fatalf("collect trick: %v", err) } continue } seat := g.Turn card := legalMove(g, seat) if err := g.PlayCard(seat, card); err != nil { t.Fatalf("play %s by seat %d: %v", card, seat, err) } } // بعد از هَند: یا تمام شده یا hand_over if g.Phase == PhaseHandOver { r := g.LastResult if r.Points < 1 || r.Points > 2 { t.Fatalf("unexpected points %d", r.Points) } if err := g.NextHand(); err != nil { t.Fatal(err) } } } // در پایان یک تیم باید به امتیاز هدف رسیده باشد if g.Scores[0] < g.TargetScore && g.Scores[1] < g.TargetScore { t.Fatalf("game over but no team reached target: %v", g.Scores) } t.Logf("final scores: %v", g.Scores) } // TestTargetScoreRespected بررسی می‌کند بازی دقیقاً وقتی یک تیم به TargetScore // رسید تمام می‌شود (نه زودتر، نه دیرتر) — برای میزهایی مثل مبتدی (۳ دست). func TestTargetScoreRespected(t *testing.T) { for _, target := range []int{1, 3, 5, 7} { for trial := 0; trial < 20; trial++ { g := NewGame(target, FirstHakem()) for g.Phase != PhaseGameOver { switch { case g.TrickDone: if err := g.CollectTrick(); err != nil { t.Fatal(err) } case g.Phase == PhaseChooseTrump: if err := g.ChooseTrump(g.Hakem, g.hands[g.Hakem][0].Suit); err != nil { t.Fatal(err) } case g.Phase == PhaseHandOver: if err := g.NextHand(); err != nil { t.Fatal(err) } default: // playing if err := g.PlayCard(g.Turn, legalMove(g, g.Turn)); err != nil { t.Fatalf("play: %v", err) } } } w := g.Scores[0] if g.Scores[1] > w { w = g.Scores[1] } if w < target { t.Fatalf("target %d: game over but max score %d < target (scores %v)", target, w, g.Scores) } // بازنده نباید به هدف رسیده باشد (بازی باید به‌محضِ رسیدنِ یک تیم تمام شود). loser := g.Scores[0] + g.Scores[1] - w if loser >= target { t.Fatalf("target %d: both teams reached target %v", target, g.Scores) } } } } // legalMove یک حرکت مجاز برای ربات انتخاب می‌کند (follow suit در صورت امکان). func legalMove(g *Game, seat int) Card { hand := g.hands[seat] if g.leadSet { for _, c := range hand { if c.Suit == g.LeadSuit { return c } } } return hand[0] }