287 lines
11 KiB
Go
287 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"math/rand"
|
||
"testing"
|
||
)
|
||
|
||
func newTestPokerGame(numPlayers, stack int) *PokerGameState {
|
||
rnd := rand.New(rand.NewSource(1))
|
||
return NewPokerGame(numPlayers, stack, 10, 20, rnd)
|
||
}
|
||
|
||
func TestNewPokerGamePostsBlindsCorrectly(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
sbIdx := g.nextActiveTournamentSeat(g.DealerIdx)
|
||
bbIdx := g.nextActiveTournamentSeat(sbIdx)
|
||
if g.Players[sbIdx].CurrentBet != 10 {
|
||
t.Errorf("малый блайнд должен был поставить 10, получено %d", g.Players[sbIdx].CurrentBet)
|
||
}
|
||
if g.Players[bbIdx].CurrentBet != 20 {
|
||
t.Errorf("большой блайнд должен был поставить 20, получено %d", g.Players[bbIdx].CurrentBet)
|
||
}
|
||
if g.Players[sbIdx].Stack != 990 || g.Players[bbIdx].Stack != 980 {
|
||
t.Errorf("стеки после блайндов неверны: SB=%d BB=%d", g.Players[sbIdx].Stack, g.Players[bbIdx].Stack)
|
||
}
|
||
}
|
||
|
||
func TestHeadsUpDealerPostsSmallBlindHimself(t *testing.T) {
|
||
g := newTestPokerGame(2, 1000)
|
||
other := g.nextActiveTournamentSeat(g.DealerIdx)
|
||
if g.Players[g.DealerIdx].CurrentBet != g.SmallBlind {
|
||
t.Errorf("в игре один на один дилер должен сам поставить малый блайнд, получено %d", g.Players[g.DealerIdx].CurrentBet)
|
||
}
|
||
if g.Players[other].CurrentBet != g.BigBlind {
|
||
t.Errorf("в игре один на один второй игрок должен поставить большой блайнд, получено %d", g.Players[other].CurrentBet)
|
||
}
|
||
if g.CurrentPlayerIdx != g.DealerIdx {
|
||
t.Errorf("в игре один на один префлоп-ход первым должен ходить дилер (малый блайнд), получено игрока %d", g.CurrentPlayerIdx)
|
||
}
|
||
}
|
||
|
||
func TestEachPlayerGetsTwoHoleCards(t *testing.T) {
|
||
g := newTestPokerGame(4, 1000)
|
||
seen := map[Card]bool{}
|
||
for _, p := range g.Players {
|
||
if p.HoleCards[0] == p.HoleCards[1] {
|
||
t.Fatalf("две карты в руке одного игрока не должны совпадать: %v", p.HoleCards)
|
||
}
|
||
for _, c := range p.HoleCards {
|
||
if seen[c] {
|
||
t.Fatalf("карта %v оказалась у двух разных игроков", c)
|
||
}
|
||
seen[c] = true
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestPreflopActionStartsAfterBigBlind(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
bbIdx := g.nextActiveTournamentSeat(g.nextActiveTournamentSeat(g.DealerIdx))
|
||
want := g.nextToAct(bbIdx)
|
||
if g.CurrentPlayerIdx != want {
|
||
t.Errorf("первый ход префлопа должен быть у игрока после большого блайнда, ожидалось %d, получено %d", want, g.CurrentPlayerIdx)
|
||
}
|
||
}
|
||
|
||
func TestCheckRejectedWhenBetIsOutstanding(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
if err := g.Check(); err != ErrPokerCannotCheck {
|
||
t.Errorf("ожидалась ошибка недопустимого чека против ставки большого блайнда, получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestCallMatchesHighestBet(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
actor := g.CurrentPlayerIdx
|
||
if err := g.Call(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Players[actor].CurrentBet != 20 {
|
||
t.Errorf("после колла ставка должна была стать 20, получено %d", g.Players[actor].CurrentBet)
|
||
}
|
||
}
|
||
|
||
func TestBetBelowMinimumRejected(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
if err := g.Bet(25); err != ErrPokerBetTooLow {
|
||
t.Errorf("ожидалась ошибка слишком маленькой ставки (минимум 40 = 20+20), получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestBetAboveStackRejected(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
if err := g.Bet(100000); err != ErrPokerNotEnoughChips {
|
||
t.Errorf("ожидалась ошибка нехватки фишек, получено: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestCallWithInsufficientStackGoesAllIn(t *testing.T) {
|
||
g := newTestPokerGame(3, 15)
|
||
actor := g.CurrentPlayerIdx
|
||
if err := g.Call(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if !g.Players[actor].AllIn {
|
||
t.Errorf("колл с недостаточным стеком должен был перевести игрока в олл-ин")
|
||
}
|
||
if g.Players[actor].Stack != 0 {
|
||
t.Errorf("после олл-ина стек должен был обнулиться, получено %d", g.Players[actor].Stack)
|
||
}
|
||
}
|
||
|
||
func TestFoldToOnePlayerEndsHandImmediately(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
for g.Phase != PokerPhaseShowdown && g.countInHand() > 1 {
|
||
if err := g.Fold(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
}
|
||
if g.Phase != PokerPhaseShowdown {
|
||
t.Fatalf("раздача должна была завершиться после того, как остался один игрок")
|
||
}
|
||
if g.Result == nil || g.Result.WentToShowdown {
|
||
t.Errorf("завершение сбросами не должно доходить до шоудауна с показом карт")
|
||
}
|
||
}
|
||
|
||
func TestBettingRoundAdvancesToFlopWhenAllCall(t *testing.T) {
|
||
g := newTestPokerGame(3, 1000)
|
||
for g.Phase == PokerPhasePreflop {
|
||
if err := g.Call(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
}
|
||
if g.Phase != PokerPhaseFlop {
|
||
t.Fatalf("после того как все уравняли, должен был начаться флоп, получено %v", g.Phase)
|
||
}
|
||
if len(g.Board) != 3 {
|
||
t.Errorf("на флопе должно быть 3 общие карты, получено %d", len(g.Board))
|
||
}
|
||
}
|
||
|
||
func TestFullHandProgressesThroughAllStreets(t *testing.T) {
|
||
g := newTestPokerGame(2, 1000)
|
||
steps := 0
|
||
for g.Phase != PokerPhaseShowdown && steps < 100 {
|
||
steps++
|
||
if err := g.Check(); err != nil {
|
||
g.Call()
|
||
}
|
||
}
|
||
if g.Phase != PokerPhaseShowdown {
|
||
t.Fatalf("раздача не дошла до шоудауна за %d шагов", steps)
|
||
}
|
||
if len(g.Board) != 5 {
|
||
t.Errorf("на шоудауне должно быть 5 общих карт, получено %d", len(g.Board))
|
||
}
|
||
}
|
||
|
||
func TestAllInSkipsRemainingBettingRounds(t *testing.T) {
|
||
g := newTestPokerGame(2, 100)
|
||
if err := g.AllIn(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if err := g.Call(); err != nil {
|
||
t.Fatalf("неожиданная ошибка: %v", err)
|
||
}
|
||
if g.Phase != PokerPhaseShowdown {
|
||
t.Fatalf("после олл-ина и колла (никто больше не может действовать) должен был сразу настать шоудаун, получено %v", g.Phase)
|
||
}
|
||
if len(g.Board) != 5 {
|
||
t.Errorf("на доске должно было оказаться все 5 карт (доигранных без торгов), получено %d", len(g.Board))
|
||
}
|
||
}
|
||
|
||
func TestShowdownAwardsPotToBestHand(t *testing.T) {
|
||
g := newTestPokerGame(2, 1000)
|
||
g.Players[0].HoleCards = [2]Card{c(Ace, Spades), c(Ace, Hearts)}
|
||
g.Players[1].HoleCards = [2]Card{c(Two, Clubs), c(Seven, Diamonds)}
|
||
g.Board = []Card{c(Ace, Clubs), c(King, Spades), c(Queen, Hearts), c(Four, Diamonds), c(Nine, Clubs)}
|
||
g.Players[0].TotalContributed = 100
|
||
g.Players[1].TotalContributed = 100
|
||
g.resolveShowdown()
|
||
|
||
if g.Result.Winners[0] <= 0 {
|
||
t.Errorf("игрок 0 с тройкой тузов должен был выиграть банк, получено %+v", g.Result.Winners)
|
||
}
|
||
if _, lost := g.Result.Winners[1]; lost {
|
||
t.Errorf("игрок 1 не должен был получить долю банка")
|
||
}
|
||
}
|
||
|
||
func TestShowdownSplitsPotOnTie(t *testing.T) {
|
||
g := newTestPokerGame(2, 1000)
|
||
g.Players[0].HoleCards = [2]Card{c(Two, Clubs), c(Three, Diamonds)}
|
||
g.Players[1].HoleCards = [2]Card{c(Two, Hearts), c(Three, Spades)}
|
||
g.Board = []Card{c(Ace, Clubs), c(King, Spades), c(Queen, Hearts), c(Jack, Diamonds), c(Nine, Clubs)}
|
||
g.Players[0].TotalContributed = 100
|
||
g.Players[1].TotalContributed = 100
|
||
g.resolveShowdown()
|
||
|
||
if g.Result.Winners[0] != 100 || g.Result.Winners[1] != 100 {
|
||
t.Errorf("одинаковые по силе руки (игра по доске) должны были поровну разделить банк 200, получено %+v", g.Result.Winners)
|
||
}
|
||
}
|
||
|
||
func TestSidePotsWithTwoDifferentAllInAmounts(t *testing.T) {
|
||
players := []*PokerPlayer{
|
||
{TotalContributed: 50},
|
||
{TotalContributed: 100},
|
||
{TotalContributed: 100},
|
||
}
|
||
pots := computeSidePots(players)
|
||
if len(pots) != 2 {
|
||
t.Fatalf("ожидалось 2 банка (основной + один сайд-пот), получено %d: %+v", len(pots), pots)
|
||
}
|
||
if pots[0].Amount != 150 {
|
||
t.Errorf("основной банк должен быть 150 (50 от каждого из трёх), получено %d", pots[0].Amount)
|
||
}
|
||
if len(pots[0].EligiblePlayers) != 3 {
|
||
t.Errorf("на основной банк претендуют все трое, получено %d", len(pots[0].EligiblePlayers))
|
||
}
|
||
if pots[1].Amount != 100 {
|
||
t.Errorf("сайд-пот должен быть 100 (по 50 от двоих, кто пошёл дальше), получено %d", pots[1].Amount)
|
||
}
|
||
if len(pots[1].EligiblePlayers) != 2 {
|
||
t.Errorf("на сайд-пот претендуют только двое, получено %d", len(pots[1].EligiblePlayers))
|
||
}
|
||
}
|
||
|
||
func TestSidePotsExcludeFoldedPlayersFromEligibilityButKeepTheirMoney(t *testing.T) {
|
||
players := []*PokerPlayer{
|
||
{TotalContributed: 100, Folded: true},
|
||
{TotalContributed: 100},
|
||
{TotalContributed: 100},
|
||
}
|
||
pots := computeSidePots(players)
|
||
if len(pots) != 1 {
|
||
t.Fatalf("ожидался один общий банк, получено %d", len(pots))
|
||
}
|
||
if pots[0].Amount != 300 {
|
||
t.Errorf("деньги сбросившего игрока должны были остаться в банке, ожидалось 300, получено %d", pots[0].Amount)
|
||
}
|
||
if len(pots[0].EligiblePlayers) != 2 {
|
||
t.Errorf("сбросивший игрок не должен претендовать на банк, ожидалось 2 претендента, получено %d", len(pots[0].EligiblePlayers))
|
||
}
|
||
}
|
||
|
||
func TestPlayerEliminatedWhenStackHitsZero(t *testing.T) {
|
||
g := newTestPokerGame(2, 1000)
|
||
g.Players[0].HoleCards = [2]Card{c(Two, Clubs), c(Seven, Diamonds)}
|
||
g.Players[1].HoleCards = [2]Card{c(Ace, Spades), c(Ace, Hearts)}
|
||
g.Board = []Card{c(Ace, Clubs), c(King, Spades), c(Queen, Hearts), c(Four, Diamonds), c(Nine, Clubs)}
|
||
g.Players[0].Stack = 0
|
||
g.Players[0].TotalContributed = 1000
|
||
g.Players[1].TotalContributed = 1000
|
||
g.resolveShowdown()
|
||
|
||
if !g.Players[0].Eliminated {
|
||
t.Errorf("проигравший всё игрок должен был выбыть из турнира")
|
||
}
|
||
}
|
||
|
||
func TestTournamentOverWhenOnePlayerRemains(t *testing.T) {
|
||
rnd := rand.New(rand.NewSource(1))
|
||
g := newTestPokerGame(2, 1000)
|
||
g.Players[0].Eliminated = true
|
||
g.Players[0].Stack = 0
|
||
g.Phase = PokerPhaseShowdown
|
||
g.NextHand(rnd)
|
||
if g.Phase != PokerPhaseTournamentOver {
|
||
t.Errorf("турнир должен был завершиться при одном оставшемся игроке, получено %v", g.Phase)
|
||
}
|
||
}
|
||
|
||
func TestDealerButtonRotatesEachHand(t *testing.T) {
|
||
rnd := rand.New(rand.NewSource(1))
|
||
g := newTestPokerGame(3, 1000)
|
||
firstDealer := g.DealerIdx
|
||
for g.Phase != PokerPhaseShowdown {
|
||
g.Fold()
|
||
}
|
||
g.NextHand(rnd)
|
||
if g.DealerIdx == firstDealer {
|
||
t.Errorf("дилер должен был смениться на следующей раздаче")
|
||
}
|
||
}
|