commit d02757a0fdb7667e5d84eb732a6426e68dcfb879
parent aa4c75e5b6a34f2a6009a0646144a5b4ec5cf514
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Fri, 22 Dec 2023 23:57:48 -0500
new poker
Diffstat:
6 files changed, 307 insertions(+), 180 deletions(-)
diff --git a/pkg/web/handlers/interceptors/slashInterceptor.go b/pkg/web/handlers/interceptors/slashInterceptor.go
@@ -96,6 +96,7 @@ var userCmdsMap = map[string]CmdHandler{
"/check": handleCheckCmd,
"/call": handleCallCmd,
"/fold": handleFoldCmd,
+ "/raise": handleRaiseCmd,
"/allin": handleAllInCmd,
"/bet": handleBetCmd,
"/deal": handleDealCmd,
@@ -1955,6 +1956,18 @@ func handleFoldCmd(c *command.Command) (handled bool) {
return false
}
+func handleRaiseCmd(c *command.Command) (handled bool) {
+ if c.Message == "/raise" {
+ roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-"))
+ if g := poker.PokerInstance.GetGame(roomID); g != nil {
+ g.Raise(c.AuthUser.ID)
+ }
+ c.Err = command.ErrRedirect
+ return true
+ }
+ return false
+}
+
func handleBetCmd(c *command.Command) (handled bool) {
if m := betRgx.FindStringSubmatch(c.Message); len(m) == 2 {
roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-"))
diff --git a/pkg/web/handlers/poker.go b/pkg/web/handlers/poker.go
@@ -437,95 +437,108 @@ Loop:
return nil
}
-func PokerCheckHandler(c echo.Context) error {
- authUser := c.Get("authUser").(*database.User)
- roomID := poker.RoomID(c.Param("roomID"))
- g := poker.PokerInstance.GetGame(roomID)
- if g == nil {
- return c.NoContent(http.StatusNotFound)
- }
- if c.Request().Method == http.MethodPost {
- g.Check(authUser.ID)
- }
- return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Check</button></form>`)
-}
-
-func itoa2(i database.PokerChip) string {
- return fmt.Sprintf("%d", i)
-}
-
func PokerBetHandler(c echo.Context) error {
- authUser := c.Get("authUser").(*database.User)
roomID := poker.RoomID(c.Param("roomID"))
+ authUser := c.Get("authUser").(*database.User)
+ send := func(s string) { _, _ = c.Response().Write([]byte(s)) }
g := poker.PokerInstance.GetGame(roomID)
if g == nil {
- return c.NoContent(http.StatusNotFound)
+ return c.Redirect(http.StatusFound, "/")
}
- bet := database.PokerChip(100)
+
if c.Request().Method == http.MethodPost {
- bet = database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("betValue")))
- betBtn := c.Request().PostFormValue("bet")
- if betBtn == "betAllIn" {
+ submitBtn := c.Request().PostFormValue("submitBtn")
+ if submitBtn == "check" {
+ g.Check(authUser.ID)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
+ } else if submitBtn == "call" {
+ g.Call(authUser.ID)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
+ } else if submitBtn == "fold" {
+ g.Fold(authUser.ID)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
+ } else if submitBtn == "allIn" {
g.AllIn(authUser.ID)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
} else {
- if strings.HasPrefix(betBtn, "bet_") {
- bet = database.PokerChip(utils.DoParseUint64(strings.TrimPrefix(betBtn, "bet_")))
- }
- if bet > 0 {
- g.Bet(authUser.ID, bet)
+ raiseBtn := c.Request().PostFormValue("raise")
+ if raiseBtn == "raise" {
+ g.Raise(authUser.ID)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
+ } else if raiseBtn == "raiseValue" {
+ raiseValue := database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("raiseValue")))
+ g.Bet(authUser.ID, raiseValue)
+ return c.Redirect(http.StatusFound, c.Request().Referer())
}
}
}
- shortcutsTable := map[database.PokerChip][]int{
- 3: {10, 20, 50},
- 20: {50, 100, 200},
- 200: {400, 800, 1500},
- }
- v, found := shortcutsTable[g.PokerTableMinBet]
- if !found {
- v = shortcutsTable[20]
+ roomUserTopic := roomID.UserTopic(authUser.ID)
+ sub := poker.PokerPubSub.Subscribe([]string{roomUserTopic})
+ defer sub.Close()
+ quit := hutils.CloseSignalChan(c)
+ hutils.SetStreamingHeaders(c)
+
+ // Keep track of users streams, so we can limit how many are open at one time per user
+ if err := usersStreamsManager.Inst.Add(authUser.ID, roomUserTopic); err != nil {
+ return nil
}
- shortcuts := fmt.Sprintf(`<button type="submit" name="bet" value="bet_%d">%d</button>
- <button type="submit" name="bet" value="bet_%d">%d</button>
- <button type="submit" name="bet" value="bet_%d">%d</button>`,
- v[0], v[0],
- v[1], v[1],
- v[2], v[2])
+ defer usersStreamsManager.Inst.Remove(authUser.ID, roomUserTopic)
- html := hutils.HtmlCssReset + `
-<form method="post">
- <input type="number" name="betValue" value="` + itoa2(bet) + `" style="width: 90px;" /><button type="submit" name="bet" value="bet">Bet</button><br />
- ` + shortcuts + `
- <button type="submit" name="bet" value="betAllIn">All-in</button>
-</form>`
- return c.HTML(http.StatusOK, html)
-}
+ send(hutils.HtmlCssReset)
-func PokerCallHandler(c echo.Context) error {
- authUser := c.Get("authUser").(*database.User)
- roomID := poker.RoomID(c.Param("roomID"))
- g := poker.PokerInstance.GetGame(roomID)
- if g == nil {
- return c.NoContent(http.StatusNotFound)
- }
- if c.Request().Method == http.MethodPost {
- g.Call(authUser.ID)
+ if player := g.OngoingPlayer(authUser.ID); player != nil {
+ canCheck := g.CanCheck(player)
+ canFold := g.CanFold(player)
+ playerBet := player.GetBet()
+ betBtnLbl := utils.Ternary(g.IsBet(), "Bet", "Raise")
+ minBet := g.MinBet()
+ minRaise := g.MinRaise()
+ send(`<form method="post">`)
+ send(` <div style="display: inline-block; margin-right: 20px;">`)
+ send(fmt.Sprintf(`<input type="number" name="raiseValue" value="%s" min="%s" style="width: 90px;" /><button type="submit" name="raise" value="raiseValue">%s</button><br />`, minRaise, minRaise, betBtnLbl))
+ send(` </div>`)
+ send(` <div style="display: inline-block; vertical-align: top;">`)
+ if canCheck {
+ send(` <button name="submitBtn" value="check">Check</button>`)
+ }
+ if minBet-playerBet > 0 {
+ send(` <button name="submitBtn" value="call">Call</button>`)
+ }
+ if canFold {
+ send(` <button name="submitBtn" value="fold">Fold</button>`)
+ }
+ send(` <button name="submitBtn" value="allIn">All-in</button>`)
+ send(` </div>`)
+ send(`</form>`)
+ send(fmt.Sprintf(`<div style="margin-top: 4px;">Min raise: %d</div>`, minRaise))
}
- return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Call</button></form>`)
-}
+ c.Response().Flush()
-func PokerFoldHandler(c echo.Context) error {
- authUser := c.Get("authUser").(*database.User)
- roomID := poker.RoomID(c.Param("roomID"))
- g := poker.PokerInstance.GetGame(roomID)
- if g == nil {
- return c.NoContent(http.StatusNotFound)
- }
- if c.Request().Method == http.MethodPost {
- g.Fold(authUser.ID)
+Loop:
+ for {
+ select {
+ case <-quit:
+ break Loop
+ default:
+ }
+
+ _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit)
+ if err != nil {
+ if errors.Is(err, pubsub.ErrCancelled) {
+ break Loop
+ }
+ continue
+ }
+
+ switch payload.(type) {
+ case poker.RefreshButtonsEvent:
+ send(fmt.Sprintf(`<meta http-equiv="refresh" content="0" />`))
+ c.Response().Flush()
+ return nil
+ }
}
- return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Fold</button></form>`)
+ return nil
}
func PokerDealHandler(c echo.Context) error {
@@ -558,7 +571,7 @@ func PokerUnSitHandler(c echo.Context) error {
}
func PokerSitHandler(c echo.Context) error {
- html := hutils.HtmlCssReset + `<form method="post"><button>Sit</button></form>`
+ html := hutils.HtmlCssReset + `<form method="post"><button style="height: 35px; width: 66px;">Sit</button></form>`
authUser := c.Get("authUser").(*database.User)
pos := utils.Clamp(utils.DoParseInt(c.Param("pos")), 1, poker.NbPlayers) - 1
roomID := poker.RoomID(c.Param("roomID"))
diff --git a/pkg/web/handlers/poker/events.go b/pkg/web/handlers/poker/events.go
@@ -46,6 +46,8 @@ type AutoActionEvent struct {
Message string
}
+type RefreshButtonsEvent struct{}
+
type ErrorMsgEvent struct {
Message string
}
diff --git a/pkg/web/handlers/poker/poker.go b/pkg/web/handlers/poker/poker.go
@@ -24,7 +24,7 @@ import (
const NbPlayers = 6
const MaxUserCountdown = 60
-const MinTimeAfterGame = 10
+const MinTimeAfterGame = 1
const BackfacingDeg = "-180deg"
const BurnStackX = 400
const BurnStackY = 30
@@ -34,7 +34,7 @@ const DealSpacing = 55
const DealerStackX = 250
const DealerStackY = 30
const NbCardsPerPlayer = 2
-const animationTime = 1000 * time.Millisecond
+const animationTime = 100 * time.Millisecond
type Poker struct {
sync.Mutex
@@ -106,6 +106,7 @@ type playerEvent struct {
Fold bool
AllIn bool
Unsit bool
+ Raise bool
Bet database.PokerChip
}
@@ -119,6 +120,8 @@ func (e playerEvent) getAction() PlayerAction {
action = CheckAction
} else if e.Bet > 0 {
action = BetAction
+ } else if e.Raise {
+ action = RaiseAction
} else if e.AllIn {
action = AllInAction
}
@@ -132,6 +135,9 @@ type ongoingGame struct {
events rwmtx.RWMtxSlice[PokerEvent]
waitTurnEvent rwmtx.RWMtx[PokerWaitTurnEvent]
autoActionEvent rwmtx.RWMtx[AutoActionEvent]
+ MinBet rwmtx.RWMtx[database.PokerChip]
+ MinRaise rwmtx.RWMtx[database.PokerChip]
+ hasBet rwmtx.RWMtx[bool]
players pokerPlayers
createdAt time.Time
mainPot atomic.Uint64
@@ -161,6 +167,12 @@ func (p seatedPlayers) get(userID database.UserID) (out *seatedPlayer) {
return
}
+func (p seatedPlayers) resetStatuses() {
+ for _, player := range p {
+ player.status.Set("")
+ }
+}
+
func (p seatedPlayers) toPokerPlayers() pokerPlayers {
players := make([]*pokerPlayer, 0)
for _, player := range p {
@@ -186,6 +198,8 @@ type seatedPlayer struct {
userID database.UserID
username database.Username
cash rwmtx.RWMtxUInt64[database.PokerChip]
+ status rwmtx.RWMtx[string]
+ hasChecked bool
lastActionTS time.Time
}
@@ -193,6 +207,10 @@ func (p *seatedPlayer) getCash() (out database.PokerChip) {
return p.cash.Get()
}
+func (p *seatedPlayer) getStatus() (out string) {
+ return p.status.Get()
+}
+
// Return either or not a player is eligible to play a game
func (p *seatedPlayer) isEligible(pokerTableMinBet database.PokerChip) bool {
return p != nil && p.getCash() >= pokerTableMinBet
@@ -209,7 +227,42 @@ type pokerPlayer struct {
countChancesToAction int
}
-func (p *pokerPlayer) getBet() (out database.PokerChip) {
+func (g *PokerGame) IsBet() (out bool) {
+ if g.ongoing != nil {
+ return !g.ongoing.hasBet.Get()
+ }
+ return
+}
+
+func (g *PokerGame) CanCheck(player *pokerPlayer) (out bool) {
+ if g.ongoing != nil {
+ return player.bet.Get() == g.ongoing.MinBet.Get()
+ }
+ return
+}
+
+func (g *PokerGame) CanFold(player *pokerPlayer) (out bool) {
+ if g.ongoing != nil {
+ return player.bet.Get() < g.ongoing.MinBet.Get()
+ }
+ return
+}
+
+func (g *PokerGame) MinBet() (out database.PokerChip) {
+ if g.ongoing != nil {
+ return g.ongoing.MinBet.Get()
+ }
+ return
+}
+
+func (p *PokerGame) MinRaise() (out database.PokerChip) {
+ if p.ongoing != nil {
+ return p.ongoing.MinRaise.Get()
+ }
+ return
+}
+
+func (p *pokerPlayer) GetBet() (out database.PokerChip) {
return p.bet.Get()
}
@@ -251,12 +304,12 @@ func (p *pokerPlayer) resetBet() (old database.PokerChip) {
}
func (p *pokerPlayer) refundBet(db *database.DkfDB, pokerTableID int64) {
- p.gain(db, pokerTableID, p.getBet())
+ p.gain(db, pokerTableID, p.GetBet())
}
func (p *pokerPlayer) doBetAndNotif(db *database.DkfDB, pokerTableID int64, bet database.PokerChip, roomTopic string) {
p.doBet(db, pokerTableID, bet)
- PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.seatIdx, Player: p.username, Bet: bet, TotalBet: p.getBet(), Cash: p.getCash()})
+ PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.seatIdx, Player: p.username, Bet: bet, TotalBet: p.GetBet(), Cash: p.getCash()})
}
type playerCard struct {
@@ -298,6 +351,10 @@ func (g *PokerGame) AllIn(userID database.UserID) {
g.sendPlayerEvent(playerEvent{UserID: userID, AllIn: true})
}
+func (g *PokerGame) Raise(userID database.UserID) {
+ g.sendPlayerEvent(playerEvent{UserID: userID, Raise: true})
+}
+
func (g *PokerGame) Bet(userID database.UserID, bet database.PokerChip) {
g.sendPlayerEvent(playerEvent{UserID: userID, Bet: bet})
}
@@ -463,7 +520,7 @@ func isRoundSettled(players []*pokerPlayer) bool {
}
arr := make([]Tmp, 0)
for _, p := range players {
- arr = append(arr, Tmp{Bet: p.getBet(), AllIn: p.isAllIn(), Folded: p.folded.Load()})
+ arr = append(arr, Tmp{Bet: p.GetBet(), AllIn: p.isAllIn(), Folded: p.folded.Load()})
}
sort.Slice(arr, func(i, j int) bool { return arr[i].Bet > arr[j].Bet })
b := arr[0].Bet
@@ -644,6 +701,7 @@ const (
CheckAction
BetAction
AllInAction
+ RaiseAction
)
func (a PlayerAction) String() string {
@@ -658,6 +716,8 @@ func (a PlayerAction) String() string {
return "check"
case BetAction:
return "bet"
+ case RaiseAction:
+ return "raise"
case AllInAction:
return "all-in"
}
@@ -700,10 +760,11 @@ func doUnsit(g *PokerGame, p *pokerPlayer, playerAlive *int) int {
return continueGetPlayerEventLoop
}
-func doTimeout(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip, playerAlive *int) int {
+func doTimeout(g *PokerGame, p *pokerPlayer, playerAlive *int) int {
pUsername := p.username
- if p.getBet() < *minBet {
+ if p.GetBet() < g.ongoing.MinBet.Get() {
foldPlayer(g, p)
+ p.status.Set("fold")
g.newLogEvent(fmt.Sprintf("%s auto fold", pUsername))
*playerAlive--
@@ -712,18 +773,33 @@ func doTimeout(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip, playerA
}
return breakGetPlayerEventLoop
}
+ p.hasChecked = true
+ p.status.Set("check")
g.newLogEvent(fmt.Sprintf("%s auto check", pUsername))
return breakGetPlayerEventLoop
}
-func doFold(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip, playerAlive *int) int {
+func doCheck(g *PokerGame, p *pokerPlayer) int {
+ if p.GetBet() < g.ongoing.MinBet.Get() {
+ msg := fmt.Sprintf("Need to bet %d", g.ongoing.MinBet.Get()-p.GetBet())
+ PokerPubSub.Pub(g.roomID.UserTopic(p.userID), ErrorMsgEvent{Message: msg})
+ return continueGetPlayerEventLoop
+ }
+ p.hasChecked = true
+ p.status.Set("check")
+ g.newLogEvent(fmt.Sprintf("%s check", p.username))
+ return doNothing
+}
+
+func doFold(g *PokerGame, p *pokerPlayer, playerAlive *int) int {
roomUserTopic := g.roomID.UserTopic(p.userID)
- if p.getBet() == *minBet {
+ if p.GetBet() == g.ongoing.MinBet.Get() {
msg := fmt.Sprintf("Cannot fold if there is no bet; check")
PokerPubSub.Pub(roomUserTopic, ErrorMsgEvent{Message: msg})
- return doCheck(g, p, minBet)
+ return doCheck(g, p)
}
foldPlayer(g, p)
+ p.status.Set("fold")
g.newLogEvent(fmt.Sprintf("%s fold", p.username))
*playerAlive--
@@ -734,84 +810,87 @@ func doFold(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip, playerAliv
return doNothing
}
-func doCheck(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip) int {
- if p.getBet() < *minBet {
- msg := fmt.Sprintf("Need to bet %d", *minBet-p.getBet())
- PokerPubSub.Pub(g.roomID.UserTopic(p.userID), ErrorMsgEvent{Message: msg})
- return continueGetPlayerEventLoop
- }
- g.newLogEvent(fmt.Sprintf("%s check", p.username))
- return doNothing
-}
-
-func doCall(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip,
- newlyAllInPlayers *[]*pokerPlayer) int {
+func doCall(g *PokerGame, p *pokerPlayer,
+ newlyAllInPlayers *[]*pokerPlayer, lastBetPlayerIdx, lastRaisePlayerIdx *int, playerToPlayIdx int) int {
pUsername := p.username
- bet := utils.MinInt(*minBet-p.getBet(), p.getCash())
+ bet := utils.MinInt(g.ongoing.MinBet.Get()-p.GetBet(), p.getCash())
if bet == 0 {
- g.newLogEvent(fmt.Sprintf("%s check", pUsername))
+ return doCheck(g, p)
+ } else if bet == p.cash.Get() {
+ return doAllIn(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx)
} else {
+ p.status.Set("call")
p.doBetAndNotif(g.db, g.pokerTableID, bet, g.roomID.Topic())
logMsg := fmt.Sprintf("%s call (%d)", pUsername, bet)
- if p.isAllIn() {
- logMsg += " (all-in)"
- *newlyAllInPlayers = append(*newlyAllInPlayers, p)
- }
g.newLogEvent(logMsg)
}
return doNothing
}
-func doAllIn(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip,
- newlyAllInPlayers *[]*pokerPlayer, lastRaisePlayerIdx *int, playerToPlayIdx int) int {
+func doAllIn(g *PokerGame, p *pokerPlayer,
+ newlyAllInPlayers *[]*pokerPlayer, lastBetPlayerIdx, lastRaisePlayerIdx *int, playerToPlayIdx int) int {
bet := p.getCash()
- if (p.getBet() + bet) > *minBet {
+ minBet := g.ongoing.MinBet.Get()
+ if (p.GetBet() + bet) > minBet {
+ *lastBetPlayerIdx = playerToPlayIdx
*lastRaisePlayerIdx = playerToPlayIdx
+ g.ongoing.MinRaise.Set(bet)
}
- *minBet = utils.MaxInt(p.getBet()+bet, *minBet)
+ g.ongoing.MinBet.Set(utils.MaxInt(p.GetBet()+bet, minBet))
p.doBetAndNotif(g.db, g.pokerTableID, bet, g.roomID.Topic())
logMsg := fmt.Sprintf("%s all-in (%d)", p.username, bet)
if p.isAllIn() {
*newlyAllInPlayers = append(*newlyAllInPlayers, p)
}
+ p.status.Set("all-in")
g.newLogEvent(logMsg)
return doNothing
}
-func doBet(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip,
- newlyAllInPlayers *[]*pokerPlayer, lastRaisePlayerIdx *int, playerToPlayIdx int, evt playerEvent) int {
+func doRaise(g *PokerGame, p *pokerPlayer,
+ newlyAllInPlayers *[]*pokerPlayer, lastBetPlayerIdx, lastRaisePlayerIdx *int, playerToPlayIdx int, evt playerEvent) int {
+ evt.Bet = g.ongoing.MinRaise.Get()
+ return doBet(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx, evt)
+}
+
+func doBet(g *PokerGame, p *pokerPlayer,
+ newlyAllInPlayers *[]*pokerPlayer, lastBetPlayerIdx, lastRaisePlayerIdx *int, playerToPlayIdx int, evt playerEvent) int {
roomTopic := g.roomID.Topic()
roomUserTopic := g.roomID.UserTopic(p.userID)
- pokerTableMinBet := g.PokerTableMinBet
- bet := evt.Bet
- playerBet := p.getBet()
+ minBet := g.ongoing.MinBet.Get()
+ minRaise := g.ongoing.MinRaise.Get()
+ playerBet := p.bet.Get()
+ bet := evt.Bet + (minBet - playerBet)
+ if bet >= p.cash.Get() {
+ return doAllIn(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx)
+ }
+ playerTotalBet := playerBet + bet
+ betLbl := utils.Ternary(g.IsBet(), "bet", "raise")
// Ensure the player cannot bet below the table minimum bet (amount of the big blind)
- if playerBet+bet != *minBet && bet < pokerTableMinBet {
- msg := fmt.Sprintf("Bet (%d) is too low. Must bet at least %d", bet, pokerTableMinBet)
+ if bet < minRaise {
+ msg := fmt.Sprintf("%s (%d) is too low. Must %s at least %d", betLbl, bet, betLbl, minRaise)
PokerPubSub.Pub(roomUserTopic, ErrorMsgEvent{Message: msg})
return continueGetPlayerEventLoop
}
- if (playerBet + bet) < *minBet {
- msg := fmt.Sprintf("Bet (%d) is too low. Must bet at least %d", bet, *minBet-playerBet)
+ if playerTotalBet < minBet+minRaise {
+ msg := fmt.Sprintf("%s (%d) is too low. Must %s at least %d", betLbl, evt.Bet, betLbl, (minBet+minRaise)-playerBet)
PokerPubSub.Pub(roomUserTopic, ErrorMsgEvent{Message: msg})
return continueGetPlayerEventLoop
}
- if bet > p.getCash() {
- msg := fmt.Sprintf("Bet (%d) is too high. Must bet at most %d", bet, p.getCash())
- PokerPubSub.Pub(roomUserTopic, ErrorMsgEvent{Message: msg})
- return continueGetPlayerEventLoop
- }
- if (playerBet + bet) > *minBet {
- *lastRaisePlayerIdx = playerToPlayIdx
- }
- *minBet = utils.MaxInt(playerBet+bet, *minBet)
+ *lastBetPlayerIdx = playerToPlayIdx
+ *lastRaisePlayerIdx = playerToPlayIdx
+ g.ongoing.MinRaise.Set(evt.Bet)
+ g.ongoing.MinBet.Set(playerTotalBet)
+
p.doBetAndNotif(g.db, g.pokerTableID, bet, roomTopic)
- logMsg := fmt.Sprintf("%s bet %d", p.username, bet)
- if p.isAllIn() {
- logMsg += " (all-in)"
- *newlyAllInPlayers = append(*newlyAllInPlayers, p)
+ g.newLogEvent(fmt.Sprintf("%s %s %d", p.username, betLbl, g.ongoing.MinRaise.Get()))
+ if p.hasChecked {
+ p.status.Set("check-" + betLbl)
+ p.hasChecked = false
+ } else {
+ p.status.Set(betLbl)
}
- g.newLogEvent(logMsg)
+ g.ongoing.hasBet.Set(true)
return doNothing
}
@@ -843,37 +922,39 @@ func handleAutoActionReceived(g *PokerGame, autoCache map[database.UserID]autoAc
return continueGetPlayerEventLoop
}
-func applyAutoAction(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip,
+func applyAutoAction(g *PokerGame, p *pokerPlayer,
newlyAllInPlayers *[]*pokerPlayer,
- lastRaisePlayerIdx, playerAlive *int, playerToPlayIdx int, autoAction autoAction,
+ lastBetPlayerIdx, lastRaisePlayerIdx, playerAlive *int, playerToPlayIdx int, autoAction autoAction,
autoCache map[database.UserID]autoAction) (actionResult int) {
pUserID := p.userID
roomUserTopic := g.roomID.UserTopic(pUserID)
if autoAction.action > NoAction {
time.Sleep(500 * time.Millisecond)
- actionResult = handlePlayerActionEvent(g, p, minBet, newlyAllInPlayers, lastRaisePlayerIdx, playerAlive, playerToPlayIdx, autoAction.evt)
+ actionResult = handlePlayerActionEvent(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerAlive, playerToPlayIdx, autoAction.evt)
}
delete(autoCache, pUserID)
setAutoAction(g, roomUserTopic, "")
return actionResult
}
-func handlePlayerActionEvent(g *PokerGame, p *pokerPlayer, minBet *database.PokerChip,
+func handlePlayerActionEvent(g *PokerGame, p *pokerPlayer,
newlyAllInPlayers *[]*pokerPlayer,
- lastRaisePlayerIdx, playerAlive *int, playerToPlayIdx int, evt playerEvent) (actionResult int) {
+ lastBetPlayerIdx, lastRaisePlayerIdx, playerAlive *int, playerToPlayIdx int, evt playerEvent) (actionResult int) {
p.lastActionTS = time.Now()
if evt.Fold {
- actionResult = doFold(g, p, minBet, playerAlive)
+ actionResult = doFold(g, p, playerAlive)
} else if evt.Check {
- actionResult = doCheck(g, p, minBet)
+ actionResult = doCheck(g, p)
} else if evt.Call {
- actionResult = doCall(g, p, minBet, newlyAllInPlayers)
+ actionResult = doCall(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx)
} else if evt.AllIn {
- actionResult = doAllIn(g, p, minBet, newlyAllInPlayers, lastRaisePlayerIdx, playerToPlayIdx)
+ actionResult = doAllIn(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx)
+ } else if evt.Raise {
+ actionResult = doRaise(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx, evt)
} else if evt.Bet > 0 {
- actionResult = doBet(g, p, minBet, newlyAllInPlayers, lastRaisePlayerIdx, playerToPlayIdx, evt)
+ actionResult = doBet(g, p, newlyAllInPlayers, lastBetPlayerIdx, lastRaisePlayerIdx, playerToPlayIdx, evt)
} else {
actionResult = continueGetPlayerEventLoop
}
@@ -882,6 +963,8 @@ func handlePlayerActionEvent(g *PokerGame, p *pokerPlayer, minBet *database.Poke
// Return either or not the game ended because only 1 player left playing (or none)
func execBettingRound(g *PokerGame, skip int, minBet database.PokerChip) bool {
+ g.ongoing.MinBet.Set(minBet)
+ g.ongoing.MinRaise.Set(g.PokerTableMinBet)
db := g.db
ongoing := g.ongoing
roomID := g.roomID
@@ -889,9 +972,18 @@ func execBettingRound(g *PokerGame, skip int, minBet database.PokerChip) bool {
_, dealerIdx := ongoing.getPlayerBySeatIdx(int(g.dealerSeatIdx.Load()))
playerToPlayIdx := (dealerIdx + skip) % len(ongoing.players)
lastRaisePlayerIdx := -1
+ lastBetPlayerIdx := -1
newlyAllInPlayers := make([]*pokerPlayer, 0)
autoCache := make(map[database.UserID]autoAction)
+ for _, p := range ongoing.players {
+ p.hasChecked = false
+ if p.canBet() {
+ p.status.Set("")
+ }
+ }
+ PokerPubSub.Pub(roomTopic, RedrawSeatsEvent{})
+
playerAlive := ongoing.countAlivePlayers()
// Avoid asking for actions if only 1 player can do so (because others are all-in)
@@ -912,16 +1004,19 @@ RoundIsSettledLoop:
pUserID := p.userID
roomUserTopic := roomID.UserTopic(pUserID)
- if playerToPlayIdx == lastRaisePlayerIdx {
+ if playerToPlayIdx == lastBetPlayerIdx {
break AllPlayersLoop
}
- if lastRaisePlayerIdx == -1 {
- lastRaisePlayerIdx = playerToPlayIdx
- }
+ lastRaisePlayerIdx = utils.Ternary(lastRaisePlayerIdx == -1, playerToPlayIdx, lastRaisePlayerIdx)
+ lastBetPlayerIdx = utils.Ternary(lastBetPlayerIdx == -1, playerToPlayIdx, lastBetPlayerIdx)
if !p.canBet() {
continue AllPlayersLoop
}
+ minBet = g.ongoing.MinBet.Get()
+
+ PokerPubSub.Pub(roomUserTopic, RefreshButtonsEvent{})
+
setWaitTurn(g, p.seatIdx)
PokerPubSub.Pub(roomUserTopic, PokerYourTurnEvent{})
@@ -933,14 +1028,14 @@ RoundIsSettledLoop:
actionResult := doNothing
// Check for pre-selected action
if autoActionVal, ok := autoCache[pUserID]; ok {
- actionResult = applyAutoAction(g, p, &minBet, &newlyAllInPlayers,
- &lastRaisePlayerIdx, &playerAlive, playerToPlayIdx, autoActionVal, autoCache)
+ actionResult = applyAutoAction(g, p, &newlyAllInPlayers,
+ &lastBetPlayerIdx, &lastRaisePlayerIdx, &playerAlive, playerToPlayIdx, autoActionVal, autoCache)
goto checkActionResult
}
select {
case evt = <-g.playersEventCh:
case <-waitCh: // Waited too long, either auto-check or auto-fold
- actionResult = doTimeout(g, p, &minBet, &playerAlive)
+ actionResult = doTimeout(g, p, &playerAlive)
goto checkActionResult
}
if evt.Unsit {
@@ -951,8 +1046,8 @@ RoundIsSettledLoop:
actionResult = handleAutoActionReceived(g, autoCache, evt)
goto checkActionResult
}
- actionResult = handlePlayerActionEvent(g, p, &minBet, &newlyAllInPlayers,
- &lastRaisePlayerIdx, &playerAlive, playerToPlayIdx, evt)
+ actionResult = handlePlayerActionEvent(g, p, &newlyAllInPlayers,
+ &lastBetPlayerIdx, &lastRaisePlayerIdx, &playerAlive, playerToPlayIdx, evt)
goto checkActionResult
checkActionResult:
@@ -966,6 +1061,7 @@ RoundIsSettledLoop:
break RoundIsSettledLoop
}
PokerPubSub.Pub(roomUserTopic, ErrorMsgEvent{Message: ""})
+ PokerPubSub.Pub(roomTopic, RedrawSeatsEvent{})
break GetPlayerEventLoop
} // End of repeat until we get an event from the player we're interested in
} // End of repeat until all players have played
@@ -999,6 +1095,7 @@ RoundIsSettled:
PokerPubSub.Pub(roomTopic, PokerMainPotUpdatedEvent{MainPot: mainPot})
ongoing.setMainPot(mainPot)
+ g.ongoing.hasBet.Set(false)
return playerAlive <= 1
}
@@ -1018,10 +1115,10 @@ func refundUncalledBet(db *database.DkfDB, ongoing *ongoingGame, pokerTableID in
}
newArray := make([]*pokerPlayer, lenPlayers)
copy(newArray, ongoing.players)
- sort.Slice(newArray, func(i, j int) bool { return newArray[i].getBet() > newArray[j].getBet() })
+ sort.Slice(newArray, func(i, j int) bool { return newArray[i].GetBet() > newArray[j].GetBet() })
firstPlayer := newArray[0]
secondPlayer := newArray[1]
- diff := firstPlayer.getBet() - secondPlayer.getBet()
+ diff := firstPlayer.GetBet() - secondPlayer.GetBet()
if diff > 0 {
firstPlayer.refundPartialBet(db, pokerTableID, diff)
PokerPubSub.Pub(roomTopic, RedrawSeatsEvent{})
@@ -1039,8 +1136,8 @@ type Seat struct {
// Positions of the dealer token for each seats
var dealerTokenPos = [][]int{
- {125, 714},
- {246, 732},
+ {140, 714},
+ {261, 732},
{384, 607},
{369, 379},
{367, 190},
@@ -1136,13 +1233,14 @@ func computeAllInMaxGain(ongoing *ongoingGame, newlyAllInPlayers []*pokerPlayer,
for _, p := range newlyAllInPlayers {
maxGain := mainPot
for _, op := range ongoing.players {
- maxGain += utils.MinInt(op.getBet(), p.getBet())
+ maxGain += utils.MinInt(op.GetBet(), p.GetBet())
}
p.allInMaxGain = maxGain
}
}
func dealerThread(g *PokerGame, eligiblePlayers seatedPlayers) {
+ eligiblePlayers.resetStatuses()
g.ongoing = newOngoing(eligiblePlayers)
roomID := g.roomID
@@ -1174,6 +1272,7 @@ func dealerThread(g *PokerGame, eligiblePlayers seatedPlayers) {
applyBigBlindBet(g, bigBlindBet, bbIdx)
time.Sleep(animationTime)
+ g.ongoing.hasBet.Set(true)
// Deal players cards
dealPlayersCards(g, seats, &idx)
@@ -1490,6 +1589,13 @@ func cardToPokerCard(name string) string {
return r.Replace(name)
}
+func (g *PokerGame) OngoingPlayer(userID database.UserID) *pokerPlayer {
+ if g.ongoing != nil {
+ return g.ongoing.players.get(userID)
+ }
+ return nil
+}
+
func (g *PokerGame) Deal(userID database.UserID) {
roomTopic := g.roomID.Topic()
roomUserTopic := g.roomID.UserTopic(userID)
@@ -1571,6 +1677,7 @@ func BuildPayloadHtml(g *PokerGame, authUser *database.User, payload any) (html
html += drawGameIsOverHtml(g)
case PlayerBetEvent:
html += drawPlayerBetEvent(evt)
+ html += drawSeatsStyle(authUser, g)
case ErrorMsgEvent:
html += drawErrorMsgEvent(evt)
case AutoActionEvent:
@@ -1703,18 +1810,13 @@ func buildActionsDiv(roomID RoomID) (html string) {
<td style="vertical-align: top;" rowspan="2">
<iframe src="/poker/{{ .RoomID }}/bet" id="betBtn"></iframe>
</td>
- <td>
- <iframe src="/poker/{{ .RoomID }}/call" id="callBtn"></iframe>
- <iframe src="/poker/{{ .RoomID }}/check" id="checkBtn"></iframe>
- <iframe src="/poker/{{ .RoomID }}/fold" id="foldBtn"></iframe>
- </td>
</tr>
<tr>
<td></td>
<td><div id="autoAction"></div></td>
</tr>
<tr>
- <td colspan="3"><div id="errorMsg"></div></td>
+ <td colspan="2"><div id="errorMsg"></div></td>
</tr>
</table>`
data := map[string]any{
@@ -1741,6 +1843,7 @@ func buildSeatsHtml(g *PokerGame, authUser *database.User) (html string) {
html += ` <iframe src="/poker/` + g.roomID.String() + `/sit/` + idxStr + `" class="takeSeat takeSeat` + idxStr + `"></iframe>`
html += ` <div class="inner"></div>`
html += ` <div id="seat` + idxStr + `_cash" class="cash"></div>`
+ html += ` <div id="seat` + idxStr + `_status" class="status"></div>`
html += `</div>`
}
html += `</div>`
@@ -1766,15 +1869,17 @@ func drawSeatsStyle(authUser *database.User, g *PokerGame) string {
}
html += fmt.Sprintf(`#seat%s .inner:before { content: "%s"; }`, idxStr, pUsername.String())
html += drawSeatCashLabel(idxStr, itoa2(p.getCash()))
+ html += drawSeatStatusLabel(idxStr, p.getStatus())
if ongoing != nil {
- if op := ongoing.players.get(pUserID); op != nil && op.getBet() > 0 {
- html += drawSeatPotLabel(idxStr, itoa2(op.getBet()))
+ if op := ongoing.players.get(pUserID); op != nil && op.GetBet() > 0 {
+ html += drawSeatPotLabel(idxStr, itoa2(op.GetBet()))
}
}
} else {
html += fmt.Sprintf(`#seat%s { border: 1px solid #333; }`, idxStr)
html += fmt.Sprintf(`#seat%s .inner:before { content: ""; }`, idxStr)
html += drawSeatCashLabel(idxStr, "")
+ html += drawSeatStatusLabel(idxStr, "")
}
}
})
@@ -1790,6 +1895,10 @@ func drawSeatCashLabel(seatIdxStr, cashStr string) string {
return fmt.Sprintf(`#seat%s_cash:before { content: "%s"; }`, seatIdxStr, cashStr)
}
+func drawSeatStatusLabel(seatIdxStr, statusStr string) string {
+ return fmt.Sprintf(`#seat%s_status:before { content: "%s"; }`, seatIdxStr, statusStr)
+}
+
func drawAutoActionMsgEvent(evt AutoActionEvent) (html string) {
display := utils.Ternary(evt.Message != "", "block", "none")
html += fmt.Sprintf(`<style>#autoAction { display: %s; } #autoAction:before { content: "%s"; }</style>`, display, evt.Message)
@@ -2116,8 +2225,8 @@ body {
border:1px solid black;
}
.takeSeat {
- width: 33px;
- height: 25px;
+ width: 65px;
+ height: 40px;
display: flex;
margin-left: auto;
margin-right: auto;
@@ -2129,11 +2238,12 @@ body {
background-color: rgba(45, 45, 45, 0.4);
padding: 1px 2px;
min-width: 80px;
- min-height: 35px;
+ min-height: 50px;
color: #ddd;
}
.seat .inner { display: flex; justify-content: center; }
.seat .cash { display: flex; justify-content: center; }
+.seat .status { display: flex; justify-content: center; }
.dev_seat1_card1 { top: 55px; left: 610px; transform: rotateZ(-95deg); width:50px; height:70px; background-color: white; position: absolute; }
.dev_seat1_card2 {}
@@ -2181,20 +2291,20 @@ body {
.takeSeat4 { }
.takeSeat5 { }
.takeSeat6 { }
-#actionsDiv { position: absolute; top: 470px; left: 130px; }
+#actionsDiv { position: absolute; top: 470px; left: 100px; }
#dealBtn { width: 80px; height: 30px; display: inline-block; vertical-align: top; }
#unSitBtn { width: 80px; height: 30px; display: inline-block; vertical-align: top; }
#checkBtn { width: 60px; height: 30px; display: inline-block; vertical-align: top; }
#foldBtn { width: 50px; height: 30px; display: inline-block; vertical-align: top; }
#callBtn { width: 50px; height: 30px; display: inline-block; vertical-align: top; }
-#betBtn { width: 145px; height: 70px; display: inline-block; vertical-align: top; }
+#betBtn { width: 300px; height: 100px; display: inline-block; vertical-align: top; }
.countdown { --duration: ` + itoa(MaxUserCountdown) + `; --size: 30; }
#countdown1 { top: 46px; left: 717px; position: absolute; display: none; z-index: 100; }
#countdown2 { top: 167px; left: 729px; position: absolute; display: none; z-index: 100; }
-#countdown3 { top: 405px; left: 668px; position: absolute; display: none; z-index: 100; }
+#countdown3 { top: 420px; left: 668px; position: absolute; display: none; z-index: 100; }
#countdown4 { top: 404px; left: 375px; position: absolute; display: none; z-index: 100; }
#countdown5 { top: 404px; left: 185px; position: absolute; display: none; z-index: 100; }
-#countdown6 { top: 404px; left: 59px; position: absolute; display: none; z-index: 100; }
+#countdown6 { top: 419px; left: 59px; position: absolute; display: none; z-index: 100; }
#mainPot { position: absolute; top: 220px; left: 250px; font-size: 20px; font-family: Arial,Helvetica,sans-serif; }
#winner { position: absolute; top: 265px; left: 250px; }
#errorMsg {
@@ -2222,7 +2332,7 @@ body {
#chat-top-bar { height: 57px; width: 100%; background-color: #222; }
#chat-content { height: 193px; width: 100%; background-color: #222; }
#eventLogs { position: absolute; bottom: 0px; right: 0px; width: 243px; height: 250px; background-color: #444; }
-#dealerToken { top: 125px; left: 714px; width: 20px; height: 20px; background-color: #ccc; border: 1px solid #333; border-radius: 11px; position: absolute; }
+#dealerToken { top: 140px; left: 714px; width: 20px; height: 20px; background-color: #ccc; border: 1px solid #333; border-radius: 11px; position: absolute; }
#dealerToken .inner { padding: 2px 4px; }
#dealerToken .inner:before { content: "D"; }
#soundsStatus {
diff --git a/pkg/web/middlewares/middlewares.go b/pkg/web/middlewares/middlewares.go
@@ -35,6 +35,7 @@ var GzipMiddleware = middleware.GzipWithConfig(
c.Path() == "/chess/:key/analyze" ||
c.Path() == "/poker/:roomID/stream" ||
c.Path() == "/poker/:roomID/logs" ||
+ c.Path() == "/poker/:roomID/bet" ||
c.Path() == "/api/v1/chat/messages/:roomName/stream" ||
c.Path() == "/uploads/:filename" ||
c.Path() == "/" {
@@ -184,9 +185,6 @@ func CSRFMiddleware() echo.MiddlewareFunc {
c.Path() == "/chess/:key" ||
c.Path() == "/poker/:roomID/sit/:pos" ||
c.Path() == "/poker/:roomID/unsit" ||
- c.Path() == "/poker/:roomID/check" ||
- c.Path() == "/poker/:roomID/fold" ||
- c.Path() == "/poker/:roomID/call" ||
c.Path() == "/poker/:roomID/bet" ||
c.Path() == "/poker/:roomID/logs" ||
c.Path() == "/poker/:roomID/deal"
@@ -302,9 +300,6 @@ func IsAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
!strings.Contains(c.Path(), "/poker/:roomID/stream") &&
!strings.Contains(c.Path(), "/poker/:roomID/sit/:pos") &&
!strings.Contains(c.Path(), "/poker/:roomID/unsit") &&
- !strings.Contains(c.Path(), "/poker/:roomID/check") &&
- !strings.Contains(c.Path(), "/poker/:roomID/fold") &&
- !strings.Contains(c.Path(), "/poker/:roomID/call") &&
!strings.Contains(c.Path(), "/poker/:roomID/bet") &&
!strings.Contains(c.Path(), "/poker/:roomID/logs") &&
!strings.Contains(c.Path(), "/poker/:roomID/deal") {
diff --git a/pkg/web/web.go b/pkg/web/web.go
@@ -102,12 +102,6 @@ func getMainServer(db *database.DkfDB, i18nBundle *i18n.Bundle, renderer *tmp.Te
authGroup.GET("/poker/:roomID", handlers.PokerTableHandler)
authGroup.GET("/poker/:roomID/stream", handlers.PokerStreamHandler)
authGroup.GET("/poker/:roomID/logs", handlers.PokerLogsHandler)
- authGroup.GET("/poker/:roomID/check", handlers.PokerCheckHandler)
- authGroup.POST("/poker/:roomID/check", handlers.PokerCheckHandler)
- authGroup.GET("/poker/:roomID/fold", handlers.PokerFoldHandler)
- authGroup.POST("/poker/:roomID/fold", handlers.PokerFoldHandler)
- authGroup.GET("/poker/:roomID/call", handlers.PokerCallHandler)
- authGroup.POST("/poker/:roomID/call", handlers.PokerCallHandler)
authGroup.GET("/poker/:roomID/bet", handlers.PokerBetHandler)
authGroup.POST("/poker/:roomID/bet", handlers.PokerBetHandler)
authGroup.GET("/poker/:roomID/deal", handlers.PokerDealHandler)