Logic for disconnected players
This commit is contained in:
parent
6cba9f8f68
commit
9c86fc57b7
1 changed files with 65 additions and 22 deletions
|
|
@ -155,6 +155,36 @@ func (t *Table) Stand(username string) (int64, error) {
|
|||
return stack, nil
|
||||
}
|
||||
|
||||
func (t *Table) Disconnect(username string) (bool, error) {
|
||||
t.mu.Lock()
|
||||
seat := t.findSeat(username)
|
||||
if seat == nil {
|
||||
t.mu.Unlock()
|
||||
return false, nil // not seated, nothing to do
|
||||
}
|
||||
|
||||
if t.Hand.Phase == PhaseWaiting {
|
||||
// Safe to remove immediately
|
||||
stack := seat.Stack
|
||||
*seat = Seat{}
|
||||
t.mu.Unlock()
|
||||
t.broadcastState()
|
||||
return true, nil // caller should refund stack
|
||||
}
|
||||
|
||||
// Mid-hand — mark sitout, hand will clean up at end
|
||||
seat.SitOut = true
|
||||
// If it's their turn, cancel the turn timer so the auto-fold fires immediately
|
||||
if t.Seats[t.Hand.ActionOn].Username == username {
|
||||
if t.cancelTurn != nil {
|
||||
t.cancelTurn()
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
t.broadcastState()
|
||||
return false, nil // not safe to refund yet
|
||||
}
|
||||
|
||||
// TopUp adds chips to a seated player's stack (between hands only).
|
||||
func (t *Table) TopUp(username string, amount int64) error {
|
||||
t.mu.Lock()
|
||||
|
|
@ -446,7 +476,6 @@ func (t *Table) advance() {
|
|||
}
|
||||
|
||||
func (t *Table) doShowdown() {
|
||||
// Build hands for non-folded players
|
||||
remaining := make(map[string][]string)
|
||||
for username, cards := range t.hands {
|
||||
if !t.folded[username] {
|
||||
|
|
@ -456,12 +485,10 @@ func (t *Table) doShowdown() {
|
|||
|
||||
winners := Winners(remaining, t.Hand.Community)
|
||||
|
||||
// Broadcast all remaining hands + winners
|
||||
revealedHands := make(map[string][]string)
|
||||
for _, w := range winners {
|
||||
revealedHands[w.Username] = remaining[w.Username]
|
||||
}
|
||||
// Also include any players who chose to reveal
|
||||
t.mu.RLock()
|
||||
for _, seat := range t.Seats {
|
||||
if seat.Revealed && seat.Username != "" {
|
||||
|
|
@ -477,7 +504,6 @@ func (t *Table) doShowdown() {
|
|||
winnerDescs[w.Username] = w.Desc
|
||||
}
|
||||
|
||||
// Split pot among winners
|
||||
share := t.Hand.Pot / int64(len(winners))
|
||||
remainder := t.Hand.Pot % int64(len(winners))
|
||||
t.mu.Lock()
|
||||
|
|
@ -486,7 +512,7 @@ func (t *Table) doShowdown() {
|
|||
if seat != nil {
|
||||
extra := int64(0)
|
||||
if i == 0 {
|
||||
extra = remainder // first winner gets remainder chips
|
||||
extra = remainder
|
||||
}
|
||||
seat.Stack += share + extra
|
||||
}
|
||||
|
|
@ -503,25 +529,10 @@ func (t *Table) doShowdown() {
|
|||
})
|
||||
}
|
||||
|
||||
// Brief pause then reset
|
||||
time.Sleep(5 * time.Second)
|
||||
t.mu.Lock()
|
||||
t.Hand.Phase = PhaseWaiting
|
||||
// Remove players with no chips
|
||||
for _, s := range t.Seats {
|
||||
if s.Username != "" && s.Stack == 0 {
|
||||
log.Printf("[poker] %s busted out of table %s", s.Username, t.Config.ID)
|
||||
// Return to wallet handled by manager on bust event
|
||||
if t.Broadcast != nil {
|
||||
t.Broadcast(map[string]interface{}{
|
||||
"type": "player:bust",
|
||||
"username": s.Username,
|
||||
"tableId": t.Config.ID,
|
||||
})
|
||||
}
|
||||
*s = Seat{}
|
||||
}
|
||||
}
|
||||
t.cleanupSeats()
|
||||
t.mu.Unlock()
|
||||
t.broadcastState()
|
||||
t.mu.Lock()
|
||||
|
|
@ -530,7 +541,6 @@ func (t *Table) doShowdown() {
|
|||
}
|
||||
|
||||
func (t *Table) awardPot() {
|
||||
// Find the only remaining active player
|
||||
for _, s := range t.Seats {
|
||||
if s.Username != "" && !s.Folded {
|
||||
s.Stack += t.Hand.Pot
|
||||
|
|
@ -547,7 +557,10 @@ func (t *Table) awardPot() {
|
|||
}
|
||||
t.Hand.Pot = 0
|
||||
time.Sleep(2 * time.Second)
|
||||
t.mu.Lock()
|
||||
t.Hand.Phase = PhaseWaiting
|
||||
t.cleanupSeats()
|
||||
t.mu.Unlock()
|
||||
t.broadcastState()
|
||||
t.maybeStartHand()
|
||||
}
|
||||
|
|
@ -657,6 +670,36 @@ func (t *Table) bettingComplete() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (t *Table) cleanupSeats() {
|
||||
for _, s := range t.Seats {
|
||||
if s.Username == "" {
|
||||
continue
|
||||
}
|
||||
if s.Stack == 0 {
|
||||
log.Printf("[poker] %s busted", s.Username)
|
||||
if t.Broadcast != nil {
|
||||
t.Broadcast(map[string]interface{}{
|
||||
"type": "player:bust",
|
||||
"username": s.Username,
|
||||
"tableId": t.Config.ID,
|
||||
})
|
||||
}
|
||||
*s = Seat{}
|
||||
} else if s.SitOut {
|
||||
log.Printf("[poker] standing disconnected player %s with %d chips", s.Username, s.Stack)
|
||||
if t.Broadcast != nil {
|
||||
t.Broadcast(map[string]interface{}{
|
||||
"type": "player:disconnect:stand",
|
||||
"username": s.Username,
|
||||
"stack": s.Stack,
|
||||
"tableId": t.Config.ID,
|
||||
})
|
||||
}
|
||||
*s = Seat{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── State broadcast ──────────────────────────────────────────────
|
||||
|
||||
// PublicState returns the table state safe to send to all clients.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue