diff --git a/poker/manager.go b/poker/manager.go index 523f777..391b54c 100644 --- a/poker/manager.go +++ b/poker/manager.go @@ -413,6 +413,17 @@ func (m *Manager) HandleWSMessage(username string, msg map[string]interface{}) { case "player:bust": // Handled in table.go via broadcast, no extra routing needed + case "player:disconnect:stand": + username, _ := msg["username"].(string) + stack, _ := msg["stack"].(float64) + if username != "" && stack > 0 && m.AdjustBalance != nil { + if _, err := m.AdjustBalance(username, int64(stack)); err != nil { + log.Printf("[poker] post-hand disconnect refund error for %s: %v", username, err) + } else { + log.Printf("[poker] post-hand refunded %d chips to %s", int64(stack), username) + } + } + case "admin:position": // Admin tablet sends position so server knows where to spawn the table // Store temporarily in Redis for the next table:spawn request @@ -460,3 +471,35 @@ func jsonError(w http.ResponseWriter, msg string, code int) { w.WriteHeader(code) json.NewEncoder(w).Encode(map[string]string{"error": msg}) } + +func (m *Manager) StandDisconnected(username string) { + m.mu.RLock() + var seated *Table + for _, t := range m.tables { + t.mu.RLock() + for _, s := range t.Seats { + if s.Username == username { + seated = t + break + } + } + t.mu.RUnlock() + if seated != nil { + break + } + } + m.mu.RUnlock() + + if seated == nil { + return + } + + stack, refundNow := seated.Disconnect(username) + if refundNow && stack > 0 && m.AdjustBalance != nil { + if _, err := m.AdjustBalance(username, stack); err != nil { + log.Printf("[poker] disconnect refund error for %s: %v", username, err) + } else { + log.Printf("[poker] refunded %d chips to %s on disconnect", stack, username) + } + } +} diff --git a/poker/table.go b/poker/table.go index 7237cb7..a9505f7 100644 --- a/poker/table.go +++ b/poker/table.go @@ -155,34 +155,32 @@ 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 - } +func (t *Table) Disconnect(username string) (int64, bool) { + t.mu.Lock() + seat := t.findSeat(username) + if seat == nil { + t.mu.Unlock() + return 0, false + } - 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 - } + if t.Hand.Phase == PhaseWaiting { + stack := seat.Stack + *seat = Seat{} + t.mu.Unlock() + t.broadcastState() + return stack, true // safe to refund immediately + } - // 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 + // Mid-hand — mark sitout and let the hand clean up + seat.SitOut = true + if t.Seats[t.Hand.ActionOn].Username == username { + if t.cancelTurn != nil { + t.cancelTurn() + } + } + t.mu.Unlock() + t.broadcastState() + return 0, false // refund will happen via player:disconnect:stand after hand ends } // TopUp adds chips to a seated player's stack (between hands only).