diff --git a/poker/table.go b/poker/table.go index b1ae04a..2a40c7e 100644 --- a/poker/table.go +++ b/poker/table.go @@ -101,21 +101,23 @@ func NewTable(cfg Config) *Table { // The caller is responsible for deducting from the player's wallet first. func (t *Table) Sit(username string, seatIndex int, buyin int64) error { t.mu.Lock() - defer t.mu.Unlock() if seatIndex < 0 || seatIndex >= len(t.Seats) { + t.mu.Unlock() return errors.New("invalid seat index") } if t.Seats[seatIndex].Username != "" { + t.mu.Unlock() return errors.New("seat taken") } - // Check player not already seated for _, s := range t.Seats { if s.Username == username { + t.mu.Unlock() return errors.New("already seated") } } if buyin < t.Config.MinBuyin || buyin > t.Config.MaxBuyin { + t.mu.Unlock() return fmt.Errorf("buy-in must be between %d and %d", t.Config.MinBuyin, t.Config.MaxBuyin) } @@ -123,6 +125,7 @@ func (t *Table) Sit(username string, seatIndex int, buyin int64) error { t.Seats[seatIndex].Stack = buyin t.Seats[seatIndex].Folded = false t.Seats[seatIndex].SitOut = false + t.mu.Unlock() t.broadcastState() t.maybeStartHand() @@ -133,20 +136,21 @@ func (t *Table) Sit(username string, seatIndex int, buyin int64) error { // Cannot stand mid-hand unless folded. func (t *Table) Stand(username string) (int64, error) { t.mu.Lock() - defer t.mu.Unlock() seat := t.findSeat(username) if seat == nil { + t.mu.Unlock() return 0, errors.New("not seated") } - - // Allow standing mid-hand only if folded or hand is waiting if t.Hand.Phase != PhaseWaiting && !seat.Folded { + t.mu.Unlock() return 0, errors.New("cannot stand mid-hand while active") } stack := seat.Stack - *seat = Seat{} // clear seat + *seat = Seat{} + t.mu.Unlock() + t.broadcastState() return stack, nil } @@ -154,19 +158,23 @@ func (t *Table) Stand(username string) (int64, error) { // TopUp adds chips to a seated player's stack (between hands only). func (t *Table) TopUp(username string, amount int64) error { t.mu.Lock() - defer t.mu.Unlock() if t.Hand.Phase != PhaseWaiting { + t.mu.Unlock() return errors.New("can only top up between hands") } seat := t.findSeat(username) if seat == nil { + t.mu.Unlock() return errors.New("not seated") } if seat.Stack+amount > t.Config.MaxBuyin { + t.mu.Unlock() return fmt.Errorf("would exceed max buy-in of %d", t.Config.MaxBuyin) } seat.Stack += amount + t.mu.Unlock() + t.broadcastState() return nil } @@ -274,17 +282,19 @@ const ( // Action processes a player's action. Returns an error if invalid. func (t *Table) Action(username string, action ActionType, amount int64) error { t.mu.Lock() - defer t.mu.Unlock() if t.Hand.Phase == PhaseWaiting || t.Hand.Phase == PhaseDealing || t.Hand.Phase == PhaseShowdown { + t.mu.Unlock() return errors.New("no action in current phase") } seatIdx := t.findSeatIndex(username) if seatIdx < 0 { + t.mu.Unlock() return errors.New("not seated") } if seatIdx != t.Hand.ActionOn { + t.mu.Unlock() return errors.New("not your turn") } @@ -298,12 +308,14 @@ func (t *Table) Action(username string, action ActionType, amount int64) error { case ActionCheck: if seat.Bet < currentBet { + t.mu.Unlock() return errors.New("cannot check — must call or raise") } case ActionCall: toCall := currentBet - seat.Bet if toCall <= 0 { + t.mu.Unlock() return errors.New("nothing to call — check instead") } if toCall > seat.Stack { @@ -315,6 +327,7 @@ func (t *Table) Action(username string, action ActionType, amount int64) error { case ActionRaise: if amount < t.Hand.MinRaise { + t.mu.Unlock() return fmt.Errorf("minimum raise is %d", t.Hand.MinRaise) } toCall := currentBet - seat.Bet @@ -330,6 +343,7 @@ func (t *Table) Action(username string, action ActionType, amount int64) error { t.acted = make(map[string]bool) // raise reopens action default: + t.mu.Unlock() return fmt.Errorf("unknown action: %s", action) } @@ -344,7 +358,6 @@ func (t *Table) Action(username string, action ActionType, amount int64) error { t.mu.Unlock() t.broadcastAction(username, action, amount) t.advance() - t.mu.Lock() return nil }