poker.go (22048B)
1 package handlers 2 3 import ( 4 "bytes" 5 "dkforest/pkg/cache" 6 "dkforest/pkg/config" 7 "dkforest/pkg/database" 8 dutils "dkforest/pkg/database/utils" 9 "dkforest/pkg/pubsub" 10 "dkforest/pkg/utils" 11 "dkforest/pkg/web/handlers/poker" 12 hutils "dkforest/pkg/web/handlers/utils" 13 "dkforest/pkg/web/handlers/utils/stream" 14 "encoding/base64" 15 "errors" 16 "fmt" 17 "github.com/asaskevich/govalidator" 18 "github.com/labstack/echo" 19 wallet1 "github.com/monero-ecosystem/go-monero-rpc-client/wallet" 20 "github.com/sirupsen/logrus" 21 "github.com/teris-io/shortid" 22 "image" 23 "image/png" 24 "math/rand" 25 "net/http" 26 "strings" 27 "time" 28 ) 29 30 var pokerWithdrawCache = cache.NewWithKey[database.UserID, int64](10*time.Minute, time.Hour) 31 32 func PokerHomeHandler(c echo.Context) error { 33 db := c.Get("database").(*database.DkfDB) 34 authUser := c.Get("authUser").(*database.User) 35 getImgStr := func(img image.Image) string { 36 buf := bytes.NewBuffer([]byte("")) 37 _ = png.Encode(buf, img) 38 return base64.StdEncoding.EncodeToString(buf.Bytes()) 39 } 40 if authUser.PokerXmrSubAddress == "" { 41 if resp, err := config.Xmr().CreateAddress(&wallet1.RequestCreateAddress{}); err == nil { 42 authUser.SetPokerXmrSubAddress(db, resp.Address) 43 } 44 } 45 const minWithdrawAmount = 1 46 var data pokerData 47 data.RakeBackPct = poker.RakeBackPct * 100 48 data.XmrPrice = fmt.Sprintf("$%.2f", config.MoneroPrice.Load()) 49 data.Transactions, _ = db.GetUserPokerXmrTransactions(authUser.ID) 50 data.PokerXmrSubAddress = authUser.PokerXmrSubAddress 51 data.RakeBack = authUser.PokerRakeBack 52 data.ChipsTest = authUser.ChipsTest 53 data.XmrBalance = authUser.XmrBalance 54 withdrawUnique := rand.Int63() 55 data.WithdrawUnique = withdrawUnique 56 withdrawUniqueOrig, _ := pokerWithdrawCache.Get(authUser.ID) 57 pokerWithdrawCache.SetD(authUser.ID, withdrawUnique) 58 pokerTables, _ := db.GetPokerTables() 59 pxmr := database.Piconero(0) 60 data.HelperXmr = pxmr.XmrStr() 61 data.HelperChips = pxmr.ToPokerChip() 62 data.HelperpXmr = pxmr.RawString() 63 data.HelperUsd = pxmr.UsdStr() 64 userTableAccounts, _ := db.GetPokerTableAccounts(authUser.ID) 65 for _, t := range pokerTables { 66 var nbSeated int 67 if g := poker.PokerInstance.GetGame(poker.RoomID(t.Slug)); g != nil { 68 nbSeated = g.CountSeated() 69 } 70 tableBalance := database.PokerChip(0) 71 for _, a := range userTableAccounts { 72 if a.PokerTableID == t.ID { 73 tableBalance = a.Amount 74 break 75 } 76 } 77 data.Tables = append(data.Tables, TmpTable{PokerTable: t, NbSeated: nbSeated, TableBalance: tableBalance}) 78 } 79 80 if authUser.PokerXmrSubAddress != "" { 81 b, _ := authUser.GetImage() 82 data.Img = getImgStr(b) 83 } 84 85 if c.Request().Method == http.MethodGet { 86 return c.Render(http.StatusOK, "poker", data) 87 } 88 89 formName := c.Request().PostFormValue("form_name") 90 if formName == "helper" { 91 data.HelperAmount = c.Request().PostFormValue("amount") 92 data.HelperType = c.Request().PostFormValue("type") 93 switch data.HelperType { 94 case "usd": 95 amount := utils.DoParseF64(data.HelperAmount) 96 pxmr = database.Piconero(amount / config.MoneroPrice.Load() * 1_000_000_000_000) 97 case "xmr": 98 amount := utils.DoParseF64(data.HelperAmount) 99 pxmr = database.Piconero(amount * 1_000_000_000_000) 100 case "pxmr": 101 amount := utils.DoParseUint64(data.HelperAmount) 102 pxmr = database.Piconero(amount) 103 case "chips": 104 amount := utils.DoParseUint64(data.HelperAmount) 105 chips := database.PokerChip(amount) 106 pxmr = chips.ToPiconero() 107 } 108 data.HelperXmr = pxmr.XmrStr() 109 data.HelperChips = pxmr.ToPokerChip() 110 data.HelperpXmr = pxmr.RawString() 111 data.HelperUsd = pxmr.UsdStr() 112 return c.Render(http.StatusOK, "poker", data) 113 } 114 115 if formName == "join_table" { 116 pokerTableSlug := c.Request().PostFormValue("table_slug") 117 playerBuyIn := database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("buy_in"))) 118 if err := doJoinTable(db, pokerTableSlug, playerBuyIn, authUser.ID); err != nil { 119 data.ErrorTable = err.Error() 120 return c.Render(http.StatusOK, "poker", data) 121 } 122 return c.Redirect(http.StatusFound, "/poker/"+pokerTableSlug) 123 124 } else if formName == "cash_out" { 125 pokerTableSlug := c.Request().PostFormValue("table_slug") 126 if err := doCashOut(db, pokerTableSlug, authUser.ID); err != nil { 127 data.ErrorTable = err.Error() 128 return c.Render(http.StatusOK, "poker", data) 129 } 130 return c.Redirect(http.StatusFound, "/poker") 131 132 } else if formName == "reset_chips" { 133 authUser.ResetChipsTest(db) 134 return hutils.RedirectReferer(c) 135 136 } else if formName == "claim_rake_back" { 137 if err := db.ClaimRakeBack(authUser.ID); err != nil { 138 logrus.Error(err) 139 } 140 return hutils.RedirectReferer(c) 141 } 142 143 if config.PokerWithdrawEnabled.IsFalse() { 144 data.Error = "withdraw temporarily disabled" 145 return c.Render(http.StatusOK, "poker", data) 146 } 147 148 withdrawAmount := database.Piconero(utils.DoParseUint64(c.Request().PostFormValue("withdraw_amount"))) 149 data.WithdrawAmount = withdrawAmount 150 data.WithdrawAddress = c.Request().PostFormValue("withdraw_address") 151 withdrawUniqueSub := utils.DoParseInt64(c.Request().PostFormValue("withdraw_unique")) 152 153 if withdrawUniqueOrig == 0 || withdrawUniqueSub != withdrawUniqueOrig { 154 data.Error = "form submitted twice, try again" 155 return c.Render(http.StatusOK, "poker", data) 156 } 157 if len(data.WithdrawAddress) != 95 { 158 data.Error = "invalid xmr address" 159 return c.Render(http.StatusOK, "poker", data) 160 } 161 if !govalidator.Matches(data.WithdrawAddress, `^[0-9][0-9AB][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{93}$`) { 162 data.Error = "invalid xmr address" 163 return c.Render(http.StatusOK, "poker", data) 164 } 165 if data.WithdrawAddress == authUser.PokerXmrSubAddress { 166 data.Error = "cannot withdraw to the deposit address" 167 return c.Render(http.StatusOK, "poker", data) 168 } 169 if withdrawAmount < minWithdrawAmount { 170 data.Error = fmt.Sprintf("minimum withdraw amount is %d", minWithdrawAmount) 171 return c.Render(http.StatusOK, "poker", data) 172 } 173 userBalance := authUser.XmrBalance 174 if withdrawAmount > userBalance { 175 data.Error = fmt.Sprintf("maximum withdraw amount is %d (%d)", userBalance, withdrawAmount) 176 return c.Render(http.StatusOK, "poker", data) 177 } 178 withdrawAmount = utils.Clamp(withdrawAmount, minWithdrawAmount, userBalance) 179 180 lastOutTransaction, _ := db.GetLastUserWithdrawPokerXmrTransaction(authUser.ID) 181 if time.Since(lastOutTransaction.CreatedAt) < 5*time.Minute { 182 diff := time.Until(lastOutTransaction.CreatedAt.Add(5 * time.Minute)) 183 data.Error = fmt.Sprintf("Wait %s before doing a new withdraw transaction", utils.ShortDur(diff)) 184 return c.Render(http.StatusOK, "poker", data) 185 } 186 187 walletRpcClient := config.Xmr() 188 189 res, err := walletRpcClient.Transfer(&wallet1.RequestTransfer{ 190 DoNotRelay: true, 191 GetTxMetadata: true, 192 Destinations: []*wallet1.Destination{ 193 {Address: data.WithdrawAddress, 194 Amount: uint64(withdrawAmount)}}}) 195 if err != nil { 196 logrus.Error(err) 197 data.Error = err.Error() 198 return c.Render(http.StatusOK, "poker", data) 199 } 200 201 transactionFee := database.Piconero(res.Fee) 202 203 if withdrawAmount+transactionFee > authUser.XmrBalance { 204 data.Error = fmt.Sprintf("not enough funds to pay for transaction fee %d (%s xmr)", transactionFee, transactionFee.XmrStr()) 205 return c.Render(http.StatusOK, "poker", data) 206 } 207 208 dutils.RootAdminNotify(db, fmt.Sprintf("new withdraw %s xmr by %s", withdrawAmount.XmrStr(), authUser.Username)) 209 210 var pokerXmrTx database.PokerXmrTransaction 211 if err := db.WithE(func(tx *database.DkfDB) error { 212 xmrBalance, err := authUser.GetXmrBalance(tx) 213 if err != nil { 214 return err 215 } 216 if withdrawAmount+transactionFee > xmrBalance { 217 return errors.New("not enough funds") 218 } 219 if err := authUser.SubXmrBalance(tx, withdrawAmount+transactionFee); err != nil { 220 return err 221 } 222 if pokerXmrTx, err = tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil { 223 logrus.Error("failed to create poker xmr transaction", err) 224 return err 225 } 226 return nil 227 }); err != nil { 228 data.Error = err.Error() 229 return c.Render(http.StatusOK, "poker", data) 230 } 231 232 if _, err := walletRpcClient.RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil { 233 if err := db.WithE(func(tx *database.DkfDB) error { 234 if err := pokerXmrTx.SetStatus(tx, database.PokerXmrTransactionStatusFailed); err != nil { 235 return err 236 } 237 if err := authUser.IncrXmrBalance(tx, withdrawAmount+transactionFee); err != nil { 238 return err 239 } 240 return nil 241 }); err != nil { 242 logrus.Error(err) 243 } 244 logrus.Error(err) 245 data.Error = err.Error() 246 return c.Render(http.StatusOK, "poker", data) 247 } 248 249 if err := pokerXmrTx.SetStatus(db, database.PokerXmrTransactionStatusSuccess); err != nil { 250 logrus.Error(err) 251 } 252 253 pokerWithdrawCache.Delete(authUser.ID) 254 return hutils.RedirectReferer(c) 255 } 256 257 func doJoinTable(db *database.DkfDB, pokerTableSlug string, playerBuyIn database.PokerChip, userID database.UserID) error { 258 err := db.WithE(func(tx *database.DkfDB) error { 259 roomID := poker.RoomID(pokerTableSlug) 260 g := poker.PokerInstance.GetGame(roomID) 261 if g == nil { 262 pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) 263 if err != nil { 264 return errors.New("failed to get poker table") 265 } 266 g = poker.PokerInstance.CreateGame(db, roomID, pokerTable.ID, pokerTable.MinBet, pokerTable.IsTest) 267 } 268 g.Players.Lock() 269 defer g.Players.Unlock() 270 if g.IsSeatedUnsafe(userID) { 271 return errors.New("cannot buy-in while seated") 272 } 273 pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) 274 if err != nil { 275 return errors.New("table mot found") 276 } 277 if playerBuyIn < pokerTable.MinBuyIn { 278 return errors.New("buy in too small") 279 } 280 if playerBuyIn > pokerTable.MaxBuyIn { 281 return errors.New("buy in too high") 282 } 283 xmrBalance, chipsTestBalance, err := tx.GetUserBalances(userID) 284 if err != nil { 285 return errors.New("failed to get user's balance") 286 } 287 userChips := utils.Ternary(pokerTable.IsTest, chipsTestBalance, xmrBalance.ToPokerChip()) 288 if userChips < playerBuyIn { 289 return errors.New("not enough chips to buy-in") 290 } 291 tableAccount, err := tx.GetPokerTableAccount(userID, pokerTable.ID) 292 if err != nil { 293 return errors.New("failed to get table account") 294 } 295 if tableAccount.Amount+playerBuyIn > pokerTable.MaxBuyIn { 296 return errors.New("buy-in exceed table max buy-in") 297 } 298 tableAccount.Amount += playerBuyIn 299 if err := tx.DecrUserBalance(userID, pokerTable.IsTest, playerBuyIn); err != nil { 300 return errors.New("failed to update user's balance") 301 } 302 if err := tableAccount.Save(tx); err != nil { 303 return errors.New("failed to update user's table account") 304 } 305 return nil 306 }) 307 return err 308 } 309 310 func doCashOut(db *database.DkfDB, pokerTableSlug string, userID database.UserID) error { 311 err := db.WithE(func(tx *database.DkfDB) error { 312 roomID := poker.RoomID(pokerTableSlug) 313 g := poker.PokerInstance.GetGame(roomID) 314 if g == nil { 315 pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) 316 if err != nil { 317 return errors.New("failed to get poker table") 318 } 319 g = poker.PokerInstance.CreateGame(db, roomID, pokerTable.ID, pokerTable.MinBet, pokerTable.IsTest) 320 } 321 g.Players.Lock() 322 defer g.Players.Unlock() 323 if g.IsSeatedUnsafe(userID) { 324 return errors.New("cannot cash out while seated") 325 } 326 pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) 327 if err != nil { 328 return errors.New("table mot found") 329 } 330 account, err := tx.GetPokerTableAccount(userID, pokerTable.ID) 331 if err != nil { 332 return errors.New("failed to get table account") 333 } 334 if err := tx.IncrUserBalance(userID, pokerTable.IsTest, account.Amount); err != nil { 335 return errors.New("failed to update user's balance") 336 } 337 account.Amount = 0 338 if err := account.Save(tx); err != nil { 339 return errors.New("failed to update user's table account") 340 } 341 return nil 342 }) 343 return err 344 } 345 346 func PokerRakeBackHandler(c echo.Context) error { 347 authUser := c.Get("authUser").(*database.User) 348 db := c.Get("database").(*database.DkfDB) 349 350 var data pokerRakeBackData 351 data.RakeBackPct = poker.RakeBackPct * 100 352 data.ReferredCount, _ = db.GetRakeBackReferredCount(authUser.ID) 353 pokerReferralToken := authUser.PokerReferralToken 354 if pokerReferralToken != nil { 355 data.ReferralToken = *pokerReferralToken 356 data.ReferralURL = fmt.Sprintf("%s/poker?r=%s", config.DkfOnion, *pokerReferralToken) 357 } 358 359 if c.Request().Method == http.MethodGet { 360 return c.Render(http.StatusOK, "poker-rake-back", data) 361 } 362 363 formName := c.Request().PostFormValue("form_name") 364 if formName == "generate_referral_url" { 365 if pokerReferralToken != nil { 366 return hutils.RedirectReferer(c) 367 } 368 token, err := shortid.Generate() 369 if err != nil { 370 logrus.Error(err) 371 return hutils.RedirectReferer(c) 372 } 373 authUser.SetPokerReferralToken(db, &token) 374 return hutils.RedirectReferer(c) 375 376 } else if formName == "set_referrer" { 377 referralToken := c.Request().PostFormValue("referral_token") 378 if len(referralToken) != 9 { 379 data.SetReferralError = "Invalid referral token" 380 return c.Render(http.StatusOK, "poker-rake-back", data) 381 } 382 if authUser.PokerReferredBy != nil { 383 data.SetReferralError = "You are already giving your rake back" 384 return c.Render(http.StatusOK, "poker-rake-back", data) 385 } 386 if pokerReferralToken != nil && referralToken == *pokerReferralToken { 387 data.SetReferralError = "Yon can't give yourself the rake back" 388 return c.Render(http.StatusOK, "poker-rake-back", data) 389 } 390 referrer, err := db.GetUserByPokerReferralToken(referralToken) 391 if err != nil { 392 data.SetReferralError = "no user found with this referral token" 393 return c.Render(http.StatusOK, "poker-rake-back", data) 394 } 395 if referrer.ID == authUser.ID { 396 data.SetReferralError = "Yon can't give yourself the rake back" 397 return c.Render(http.StatusOK, "poker-rake-back", data) 398 } 399 authUser.SetPokerReferredBy(db, &referrer.ID) 400 return hutils.RedirectReferer(c) 401 } 402 return hutils.RedirectReferer(c) 403 } 404 405 func PokerTableHandler(c echo.Context) error { 406 roomID := c.Param("roomID") 407 var data pokerTableData 408 data.PokerTableSlug = roomID 409 return c.Render(http.StatusOK, "poker-table", data) 410 } 411 412 func PokerStreamHandler(c echo.Context) error { 413 roomID := poker.RoomID(c.Param("roomID")) 414 authUser := c.Get("authUser").(*database.User) 415 db := c.Get("database").(*database.DkfDB) 416 417 pokerTable, err := db.GetPokerTableBySlug(roomID.String()) 418 if err != nil { 419 return c.Redirect(http.StatusFound, "/") 420 } 421 422 chatRoomSlug := "general" 423 tmp := strings.ReplaceAll(roomID.String(), "-", "_") 424 if _, err := db.GetChatRoomByName(tmp); err == nil { 425 chatRoomSlug = tmp 426 } 427 428 roomTopic := roomID.Topic() 429 roomUserTopic := roomID.UserTopic(authUser.ID) 430 send := func(s string) { _, _ = c.Response().Write([]byte(s)) } 431 432 g := poker.PokerInstance.GetOrCreateGame(db, roomID, pokerTable.ID, pokerTable.MinBet, pokerTable.IsTest) 433 434 streamItem, err := stream.SetStreaming(c, authUser.ID, roomTopic) 435 if err != nil { 436 return nil 437 } 438 defer streamItem.Cleanup() 439 440 sub := poker.PubSub.Subscribe([]string{roomTopic, roomUserTopic, "refresh_loading_icon_" + string(authUser.Username)}) 441 defer sub.Close() 442 443 send(poker.BuildBaseHtml(g, authUser, chatRoomSlug)) 444 c.Response().Flush() 445 446 loop(streamItem.Quit, sub, func(topic string, payload any) error { 447 switch payload.(type) { 448 case poker.RefreshLoadingIconEvent: 449 send(hutils.MetaRefresh(1)) 450 return BreakLoopErr 451 } 452 send(poker.BuildPayloadHtml(g, authUser, payload)) 453 c.Response().Flush() 454 return nil 455 }) 456 457 return nil 458 } 459 460 func PokerLogsHandler(c echo.Context) error { 461 roomID := poker.RoomID(c.Param("roomID")) 462 authUser := c.Get("authUser").(*database.User) 463 send := func(s string) { _, _ = c.Response().Write([]byte(s)) } 464 g := poker.PokerInstance.GetGame(roomID) 465 if g == nil { 466 return c.Redirect(http.StatusFound, "/") 467 } 468 roomLogsTopic := roomID.LogsTopic() 469 sub := poker.PubSub.Subscribe([]string{roomLogsTopic, "refresh_loading_icon_" + string(authUser.Username)}) 470 defer sub.Close() 471 472 streamItem, err := stream.SetStreaming(c, authUser.ID, roomLogsTopic) 473 if err != nil { 474 return nil 475 } 476 defer streamItem.Cleanup() 477 478 send(hutils.HtmlCssReset) 479 send(`<style>body { background-color: #444; color: #ddd; padding: 3px; }</style><div style="display:flex;flex-direction:column-reverse;">`) 480 for _, evt := range g.GetLogs() { 481 send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) 482 } 483 c.Response().Flush() 484 485 loop(streamItem.Quit, sub, func(topic string, payload any) error { 486 switch evt := payload.(type) { 487 case poker.RefreshLoadingIconEvent: 488 send(hutils.MetaRefresh(1)) 489 return BreakLoopErr 490 case poker.LogEvent: 491 send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) 492 c.Response().Flush() 493 } 494 return nil 495 }) 496 497 return nil 498 } 499 500 func PokerBetHandler(c echo.Context) error { 501 roomID := poker.RoomID(c.Param("roomID")) 502 authUser := c.Get("authUser").(*database.User) 503 send := func(s string) { _, _ = c.Response().Write([]byte(s)) } 504 g := poker.PokerInstance.GetGame(roomID) 505 if g == nil { 506 return c.Redirect(http.StatusFound, "/") 507 } 508 509 roomUserTopic := roomID.UserTopic(authUser.ID) 510 sub := poker.PubSub.Subscribe([]string{roomID.Topic(), roomUserTopic, "refresh_loading_icon_" + string(authUser.Username)}) 511 defer sub.Close() 512 513 streamItem, err := stream.SetStreaming(c, authUser.ID, roomUserTopic) 514 if err != nil { 515 return nil 516 } 517 defer streamItem.Cleanup() 518 519 if c.Request().Method == http.MethodPost { 520 submitBtn := c.Request().PostFormValue("submitBtn") 521 if submitBtn == "check" { 522 g.Check(authUser.ID) 523 } else if submitBtn == "call" { 524 g.Call(authUser.ID) 525 } else if submitBtn == "fold" { 526 g.Fold(authUser.ID) 527 } else if submitBtn == "allIn" { 528 g.AllIn(authUser.ID) 529 } else { 530 raiseBtn := c.Request().PostFormValue("raise") 531 if raiseBtn == "raise" { 532 g.Raise(authUser.ID) 533 } else if raiseBtn == "raiseValue" { 534 raiseValue := database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("raiseValue"))) 535 g.Bet(authUser.ID, raiseValue) 536 } 537 } 538 send(hutils.MetaRefreshNow()) 539 c.Response().Flush() 540 return nil 541 542 } else { 543 544 send(hutils.HtmlCssReset) 545 546 if player := g.OngoingPlayer(authUser.ID); player != nil { 547 betBtnLbl := utils.Ternary(g.IsBet(), "Bet", "Raise") 548 minRaise := g.MinRaise() 549 canCheck := true 550 canFold := true 551 canCall := true 552 if g.IsYourTurn(player) { 553 playerBet := player.GetBet() 554 minBet := g.MinBet() 555 canCheck = g.CanCheck(player) 556 canFold = g.CanFold(player) 557 canCall = minBet-playerBet > 0 558 } 559 send(fmt.Sprintf(` 560 <style> 561 .raise-container { 562 display: inline-block; 563 margin-right: 20px; 564 } 565 .raise-input { 566 width: 90px; 567 -moz-appearance: textfield; 568 } 569 .raise-btn { 570 width: 51px; 571 } 572 .button-container { 573 display: inline-block; 574 vertical-align: top; 575 } 576 .min-raise-text { 577 margin-top: 4px; 578 font-family: Arial, Helvetica, sans-serif; 579 font-size: 18px; 580 } 581 </style> 582 <div> 583 <form method="post"> 584 <div class="raise-container"> 585 <input type="number" name="raiseValue" value="%s" min="%s" class="raise-input" /> 586 <button type="submit" name="raise" value="raiseValue" class="raise-btn">%s</button><br /> 587 </div> 588 <div class="button-container"> 589 <button name="submitBtn" value="check" %s>Check</button> 590 <button name="submitBtn" value="call" %s>Call</button> 591 <button name="submitBtn" value="fold" %s>Fold</button> 592 <button name="submitBtn" value="allIn">All-in</button> 593 </div> 594 </form> 595 <div class="min-raise-text">Min raise: %d</div> 596 </div> 597 `, 598 minRaise, minRaise, 599 betBtnLbl, 600 utils.TernaryOrZero(!canCheck, "disabled"), 601 utils.TernaryOrZero(!canCall, "disabled"), 602 utils.TernaryOrZero(!canFold, "disabled"), 603 minRaise)) 604 } 605 c.Response().Flush() 606 } 607 608 loop(streamItem.Quit, sub, func(topic string, payload any) error { 609 switch payload.(type) { 610 case poker.RefreshLoadingIconEvent: 611 send(hutils.MetaRefresh(1)) 612 return BreakLoopErr 613 case poker.RefreshButtonsEvent: 614 send(hutils.MetaRefreshNow()) 615 c.Response().Flush() 616 return BreakLoopErr 617 } 618 return nil 619 }) 620 621 return nil 622 } 623 624 var BreakLoopErr = errors.New("break Loop") 625 var ContinueLoopErr = errors.New("continue Loop") 626 627 func loop[T any](quit <-chan struct{}, sub *pubsub.Sub[T], clb func(topic string, payload T) error) { 628 Loop: 629 for { 630 select { 631 case <-quit: 632 break Loop 633 default: 634 } 635 636 topic, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) 637 if err != nil { 638 if errors.Is(err, pubsub.ErrCancelled) { 639 break Loop 640 } 641 continue 642 } 643 644 if err := clb(topic, payload); err != nil { 645 if errors.Is(err, BreakLoopErr) { 646 break Loop 647 } else if errors.Is(err, ContinueLoopErr) { 648 continue Loop 649 } 650 } 651 } 652 } 653 654 func PokerDealHandler(c echo.Context) error { 655 roomID := poker.RoomID(c.Param("roomID")) 656 authUser := c.Get("authUser").(*database.User) 657 g := poker.PokerInstance.GetGame(roomID) 658 if g == nil { 659 return c.NoContent(http.StatusNotFound) 660 } 661 if c.Request().Method == http.MethodPost { 662 g.Deal(authUser.ID) 663 } 664 html := hutils.HtmlCssReset 665 html += `<form method="post"><button>Deal</button></form>` 666 return c.HTML(http.StatusOK, html) 667 } 668 669 func PokerUnSitHandler(c echo.Context) error { 670 authUser := c.Get("authUser").(*database.User) 671 roomID := poker.RoomID(c.Param("roomID")) 672 html := hutils.HtmlCssReset + `<form method="post"><button>UnSit</button></form>` 673 g := poker.PokerInstance.GetGame(roomID) 674 if g == nil { 675 return c.NoContent(http.StatusNotFound) 676 } 677 if c.Request().Method == http.MethodPost { 678 g.UnSit(authUser.ID) 679 } 680 return c.HTML(http.StatusOK, html) 681 } 682 683 func PokerSitHandler(c echo.Context) error { 684 html := hutils.HtmlCssReset + `<form method="post"><button style="height: 40px; width: 65px;" title="Take seat"><img src="/public/img/throne.png" width="30" alt="sit" /></button></form>` 685 authUser := c.Get("authUser").(*database.User) 686 pos := utils.Clamp(utils.DoParseInt(c.Param("pos")), 1, poker.NbPlayers) - 1 687 roomID := poker.RoomID(c.Param("roomID")) 688 g := poker.PokerInstance.GetGame(roomID) 689 if g == nil { 690 return c.HTML(http.StatusOK, html) 691 } 692 if c.Request().Method == http.MethodPost { 693 g.Sit(authUser.ID, authUser.Username, authUser.PokerReferredBy, pos) 694 } 695 return c.HTML(http.StatusOK, html) 696 }