fix: rule game for kot and hakem kot
This commit is contained in:
@@ -46,3 +46,12 @@ func (g gameSettler) RecordGame(room, playersJSON string, winnerTeam int, kot bo
|
|||||||
slog.Error("record game", "room", room, "err", err)
|
slog.Error("record game", "room", room, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TargetHands تعداد هَندِ لازم برای برد بازی در این tier (مثلاً مبتدی=۳).
|
||||||
|
func (g gameSettler) TargetHands(tier string) int {
|
||||||
|
t := economy.FindTier(tier)
|
||||||
|
if t == nil || t.Hands <= 0 {
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
return t.Hands
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ type TrickCard struct {
|
|||||||
// HandResult نتیجه یک هَند.
|
// HandResult نتیجه یک هَند.
|
||||||
type HandResult struct {
|
type HandResult struct {
|
||||||
WinnerTeam int `json:"winner_team"`
|
WinnerTeam int `json:"winner_team"`
|
||||||
Kot bool `json:"kot"` // کُت: تیم بازنده هیچ دستی نبرد
|
Kot bool `json:"kot"` // کُت: تیم بازنده هیچ دستی نبرد
|
||||||
Points int `json:"points"` // امتیاز این هَند (۱ معمولی، ۲ کُت)
|
HakemKot bool `json:"hakem_kot"` // حاکمکُت: تیمِ حاکم کُت شد (۳ امتیاز)
|
||||||
|
Points int `json:"points"` // امتیاز این هَند (۱ معمولی، ۲ کُت، ۳ حاکمکُت)
|
||||||
NextHakem int `json:"next_hakem"`
|
NextHakem int `json:"next_hakem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +270,11 @@ func (g *Game) checkHandOver() {
|
|||||||
points := 1
|
points := 1
|
||||||
if kot {
|
if kot {
|
||||||
points = 2
|
points = 2
|
||||||
|
// حاکمکُت: اگر تیمِ بازنده همان تیمِ حاکم باشد (حاکم صفر دست برده)،
|
||||||
|
// کُت سنگینتر است ⇒ ۳ امتیاز.
|
||||||
|
if Team(g.Hakem) == loserTeam {
|
||||||
|
points = 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g.Scores[winnerTeam] += points
|
g.Scores[winnerTeam] += points
|
||||||
|
|
||||||
@@ -281,6 +287,7 @@ func (g *Game) checkHandOver() {
|
|||||||
g.LastResult = &HandResult{
|
g.LastResult = &HandResult{
|
||||||
WinnerTeam: winnerTeam,
|
WinnerTeam: winnerTeam,
|
||||||
Kot: kot,
|
Kot: kot,
|
||||||
|
HakemKot: points == 3,
|
||||||
Points: points,
|
Points: points,
|
||||||
NextHakem: nextHakem,
|
NextHakem: nextHakem,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,49 @@ func TestFullGamePlaysOut(t *testing.T) {
|
|||||||
t.Logf("final scores: %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 در صورت امکان).
|
// legalMove یک حرکت مجاز برای ربات انتخاب میکند (follow suit در صورت امکان).
|
||||||
func legalMove(g *Game, seat int) Card {
|
func legalMove(g *Game, seat int) Card {
|
||||||
hand := g.hands[seat]
|
hand := g.hands[seat]
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ type handOverMsg struct {
|
|||||||
Type string `json:"type"` // "hand_over"
|
Type string `json:"type"` // "hand_over"
|
||||||
WinnerTeam int `json:"winner_team"`
|
WinnerTeam int `json:"winner_team"`
|
||||||
Kot bool `json:"kot"`
|
Kot bool `json:"kot"`
|
||||||
|
HakemKot bool `json:"hakem_kot"`
|
||||||
Points int `json:"points"`
|
Points int `json:"points"`
|
||||||
Scores [2]int `json:"scores"`
|
Scores [2]int `json:"scores"`
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -101,7 +101,12 @@ func (r *Room) run() {
|
|||||||
|
|
||||||
// start بازی را ساخته، matched را برای انسانها فرستاده و وضعیت اولیه را پخش میکند.
|
// start بازی را ساخته، matched را برای انسانها فرستاده و وضعیت اولیه را پخش میکند.
|
||||||
func (r *Room) start() {
|
func (r *Room) start() {
|
||||||
r.game = game.NewGame(targetScore, game.FirstHakem())
|
// تعداد هَندِ لازم برای برد بازی از روی نوعِ میز (tier) تعیین میشود.
|
||||||
|
target := r.settler.TargetHands(r.tier)
|
||||||
|
if target <= 0 {
|
||||||
|
target = targetScore
|
||||||
|
}
|
||||||
|
r.game = game.NewGame(target, game.FirstHakem())
|
||||||
for seat, s := range r.seats {
|
for seat, s := range r.seats {
|
||||||
if s.client != nil {
|
if s.client != nil {
|
||||||
s.client.trySend(mustJSON(r.matchedFor(seat)))
|
s.client.trySend(mustJSON(r.matchedFor(seat)))
|
||||||
@@ -127,7 +132,7 @@ func (r *Room) react() {
|
|||||||
res := r.game.LastResult
|
res := r.game.LastResult
|
||||||
r.broadcast(mustJSON(handOverMsg{
|
r.broadcast(mustJSON(handOverMsg{
|
||||||
Type: "hand_over", WinnerTeam: res.WinnerTeam, Kot: res.Kot,
|
Type: "hand_over", WinnerTeam: res.WinnerTeam, Kot: res.Kot,
|
||||||
Points: res.Points, Scores: r.game.Scores,
|
HakemKot: res.HakemKot, Points: res.Points, Scores: r.game.Scores,
|
||||||
}))
|
}))
|
||||||
if err := r.game.NextHand(); err != nil {
|
if err := r.game.NextHand(); err != nil {
|
||||||
slog.Error("next hand", "room", r.ID, "err", err)
|
slog.Error("next hand", "room", r.ID, "err", err)
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ type Settler interface {
|
|||||||
Refund(userID int64, tier string) // بازگرداندن ورودی در صورت لغو
|
Refund(userID int64, tier string) // بازگرداندن ورودی در صورت لغو
|
||||||
AwardWinner(userID int64, tier string) // جایزه/XP/جام به برنده
|
AwardWinner(userID int64, tier string) // جایزه/XP/جام به برنده
|
||||||
RecordGame(room, playersJSON string, winnerTeam int, kot bool)
|
RecordGame(room, playersJSON string, winnerTeam int, kot bool)
|
||||||
|
// TargetHands تعداد هَندِ لازم برای برد بازی در این tier (مثلاً مبتدی=۳).
|
||||||
|
TargetHands(tier string) int
|
||||||
}
|
}
|
||||||
|
|
||||||
// noopSettler پیشفرض (برای تستها و حالت بدون اقتصاد).
|
// noopSettler پیشفرض (برای تستها و حالت بدون اقتصاد).
|
||||||
type noopSettler struct{}
|
type noopSettler struct{}
|
||||||
|
|
||||||
func (noopSettler) ChargeEntry(int64, string) error { return nil }
|
func (noopSettler) ChargeEntry(int64, string) error { return nil }
|
||||||
func (noopSettler) Refund(int64, string) {}
|
func (noopSettler) Refund(int64, string) {}
|
||||||
func (noopSettler) AwardWinner(int64, string) {}
|
func (noopSettler) AwardWinner(int64, string) {}
|
||||||
func (noopSettler) RecordGame(string, string, int, bool) {}
|
func (noopSettler) RecordGame(string, string, int, bool) {}
|
||||||
|
func (noopSettler) TargetHands(string) int { return 7 }
|
||||||
|
|||||||
@@ -346,6 +346,9 @@ func (s *recordingSettler) RecordGame(string, string, int, bool) {
|
|||||||
s.records++
|
s.records++
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// برای سرعتِ تست، بازی پس از ۲ هَند تمام میشود.
|
||||||
|
func (s *recordingSettler) TargetHands(string) int { return 2 }
|
||||||
func (s *recordingSettler) snapshot() (c, r, a, rec int) {
|
func (s *recordingSettler) snapshot() (c, r, a, rec int) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user