attempt at fixing deadlock

This commit is contained in:
nak 2026-03-18 05:02:22 +00:00
parent 2836676ff5
commit 68f1db5427

View file

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