dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit a4a48277daa79181313a818b72fdbe7da9e541f2
parent b308f34b33bc41aadf2d4604d8a79db5ef9744a4
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Tue, 12 Dec 2023 04:54:21 -0500

keep track of chips when server restart

Diffstat:
Acmd/dkf/migrations/147.sql | 45+++++++++++++++++++++++++++++++++++++++++++++
Mpkg/actions/actions.go | 3+++
Apkg/database/tablePokerTables.go | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/web/handlers/poker/poker.go | 143++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
4 files changed, 228 insertions(+), 30 deletions(-)

diff --git a/cmd/dkf/migrations/147.sql b/cmd/dkf/migrations/147.sql @@ -0,0 +1,45 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS poker_tables ( + id INTEGER NOT NULL PRIMARY KEY, + slug VARCHAR(255) UNIQUE NOT NULL, + name VARCHAR(50) NOT NULL, + min_buy_in INTEGER NOT NULL, + max_buy_in INTEGER NOT NULL, + min_bet INTEGER NOT NULL, + is_test TINYINT(1) NOT NULL DEFAULT 1); +CREATE INDEX poker_tables_slug_idx ON poker_tables (slug); +CREATE INDEX poker_tables_is_test_idx ON poker_tables (is_test); + +INSERT INTO poker_tables (slug, name, min_buy_in, max_buy_in, min_bet, is_test) VALUES ('test', 'test', 1000, 2000, 20, 1); + +CREATE TABLE IF NOT EXISTS poker_table_accounts ( + id INTEGER NOT NULL PRIMARY KEY, + user_id INTEGER NOT NULL, + poker_table_id INTEGER NOT NULL, + amount INTEGER NOT NULL, + UNIQUE (user_id, poker_table_id), + CONSTRAINT poker_table_accounts_user_id_fk + FOREIGN KEY (user_id) + REFERENCES users (id) + ON DELETE NO ACTION + ON UPDATE CASCADE, + CONSTRAINT poker_table_accounts_poker_table_id_fk + FOREIGN KEY (poker_table_id) + REFERENCES poker_tables (id) + ON DELETE NO ACTION + ON UPDATE CASCADE); +CREATE INDEX poker_table_accounts_poker_table_id_idx ON poker_table_accounts (poker_table_id); +CREATE INDEX poker_table_accounts_user_id_idx ON poker_table_accounts (user_id); +CREATE INDEX poker_table_accounts_amount_idx ON poker_table_accounts (amount); + +CREATE TABLE IF NOT EXISTS poker_table_bets ( + poker_table_account_id INTEGER NOT NULL PRIMARY KEY, + amount INTEGER NOT NULL, + CONSTRAINT poker_table_bets_poker_table_account_id_fk + FOREIGN KEY (poker_table_account_id) + REFERENCES poker_table_accounts (id) + ON DELETE CASCADE + ON UPDATE CASCADE); +CREATE INDEX poker_table_bets_poker_table_account_id_idx ON poker_table_bets (poker_table_account_id); + +-- +migrate Down diff --git a/pkg/actions/actions.go b/pkg/actions/actions.go @@ -11,6 +11,7 @@ import ( "dkforest/pkg/utils" "dkforest/pkg/web" "dkforest/pkg/web/handlers/interceptors" + "dkforest/pkg/web/handlers/poker" "errors" "fmt" "github.com/mattn/go-colorable" @@ -82,6 +83,8 @@ func Start(c *cli.Context) error { utils.SGo(func() { xmrWatch(db) }) utils.SGo(func() { openBrowser(noBrowser, int64(port)) }) + poker.Refund(db) + interceptors.LoadFilters(db) interceptors.ChessInstance = interceptors.NewChess(db) interceptors.BattleshipInstance = interceptors.NewBattleship(db) diff --git a/pkg/database/tablePokerTables.go b/pkg/database/tablePokerTables.go @@ -0,0 +1,67 @@ +package database + +import ( + "github.com/sirupsen/logrus" +) + +type PokerTable struct { + ID int64 + Slug string + Name string + MinBuyIn int64 + MaxBuyIn int64 + MinBet int64 + IsTest bool +} + +func (d *DkfDB) GetPokerTableBySlug(slug string) (out PokerTable, err error) { + err = d.db.First(&out, "slug = ?", slug).Error + return +} + +type PokerTableAccount struct { + ID int64 + UserID UserID + PokerTableID int64 + Amount int64 +} + +func (a *PokerTableAccount) Save(db *DkfDB) error { + return db.db.Save(a).Error +} + +func (a *PokerTableAccount) DoSave(db *DkfDB) { + if err := a.Save(db); err != nil { + logrus.Error(err) + } +} + +func (d *DkfDB) GetPositivePokerTableAccounts() (out []PokerTableAccount, err error) { + err = d.db.Find(&out, "amount > 0").Error + return +} + +func (d *DkfDB) GetPokerTableAccount(userID UserID, pokerTableID int64) (out PokerTableAccount, err error) { + if err = d.db.First(&out, "user_id = ? AND poker_table_id = ?", userID, pokerTableID).Error; err != nil { + out = PokerTableAccount{UserID: userID, PokerTableID: pokerTableID} + err = d.db.Create(&out).Error + } + return +} + +func (d *DkfDB) GetPokerTableBet(accountID int64) (out PokerTableBet, err error) { + if err = d.db.First(&out, "poker_table_account_id = ?", accountID).Error; err != nil { + out = PokerTableBet{PokerTableAccountID: accountID} + err = d.db.Create(&out).Error + } + return +} + +type PokerTableBet struct { + PokerTableAccountID int64 + Amount int64 +} + +func (b *PokerTableBet) UpdateAmount(db *DkfDB, accountID, v int64) { + db.db.Model(b).Where("poker_table_account_id = ?", accountID).Update("amount", v) +} diff --git a/pkg/web/handlers/poker/poker.go b/pkg/web/handlers/poker/poker.go @@ -76,6 +76,7 @@ type PlayerEvent struct { Check bool Fold bool AllIn bool + Unsit bool Bet int } @@ -130,7 +131,14 @@ func (p *PokerPlayer) isAllIn() bool { return p.Cash == 0 } -func (p *PokerPlayer) doBet(bet int) { +func (p *PokerPlayer) doBet(db *database.DkfDB, roomID string, bet int) { + user, _ := db.GetUserByUsername(database.Username(p.Username)) + pokerTable, _ := db.GetPokerTableBySlug(roomID) + account, _ := db.GetPokerTableAccount(user.ID, pokerTable.ID) + account.Amount -= int64(bet) + account.DoSave(db) + tableBet, _ := db.GetPokerTableBet(account.ID) + tableBet.UpdateAmount(db, account.ID, tableBet.Amount+int64(bet)) p.RoundTotalBet += bet p.Bet += bet p.Cash -= bet @@ -332,18 +340,18 @@ func (g *PokerGame) incrDealerIdx() { g.bigBlindIdx = (int(dealerIdx) + 2) % nbPlayers } -func (g *PokerGame) UnSitPlayer(db *database.DkfDB, roomID, playerUsername string) error { +func (g *PokerGame) UnSitPlayer(db *database.DkfDB, roomID string, authUser *database.User, pokerTable database.PokerTable) error { g.PlayersMtx.RLock() defer g.PlayersMtx.RUnlock() for idx, p := range g.Players { - if p != nil && p.Username == playerUsername { - return g.UnSitPlayer1(db, roomID, p, idx) + if p != nil && p.Username == authUser.Username.String() { + return g.UnSitPlayer1(db, roomID, p, idx, pokerTable) } } return nil } -func (g *PokerGame) UnSitPlayer1(db *database.DkfDB, roomID string, player *PokerStandingPlayer, idx int) error { +func (g *PokerGame) UnSitPlayer1(db *database.DkfDB, roomID string, player *PokerStandingPlayer, idx int, pokerTable database.PokerTable) error { roomTopic := "room_" + roomID user, err := db.GetUserByUsername(database.Username(player.Username)) @@ -351,11 +359,22 @@ func (g *PokerGame) UnSitPlayer1(db *database.DkfDB, roomID string, player *Poke return err } - user.ChipsTest += player.Cash + account, err := db.GetPokerTableAccount(user.ID, pokerTable.ID) + if err != nil { + return err + } + + user.ChipsTest += int(account.Amount) user.DoSave(db) + account.Amount = 0 + account.DoSave(db) if g.Ongoing != nil { if player := g.Ongoing.GetPlayer(player.Username); player != nil { + select { + case g.PlayersEventCh <- PlayerEvent{Player: player.Username, Unsit: true}: + default: + } player.Folded.Store(true) player.CardsMtx.RLock() if len(player.Cards) >= 1 { @@ -375,7 +394,7 @@ func (g *PokerGame) UnSitPlayer1(db *database.DkfDB, roomID string, player *Poke return nil } -func (g *PokerGame) SitPlayer(db *database.DkfDB, authUser *database.User, pos int) error { +func (g *PokerGame) SitPlayer(authUser *database.User, pos int, chips int64) error { g.PlayersMtx.Lock() defer g.PlayersMtx.Unlock() for _, p := range g.Players { @@ -386,10 +405,7 @@ func (g *PokerGame) SitPlayer(db *database.DkfDB, authUser *database.User, pos i if g.Players[pos] != nil { return errors.New("seat already taken") } - chips := utils.MinInt(authUser.ChipsTest, 2000) - authUser.ChipsTest -= chips - authUser.DoSave(db) - g.Players[pos] = &PokerStandingPlayer{Username: authUser.Username.String(), Cash: chips, LastActionTS: time.Now()} + g.Players[pos] = &PokerStandingPlayer{Username: authUser.Username.String(), Cash: int(chips), LastActionTS: time.Now()} return nil } @@ -458,7 +474,7 @@ func setWaitTurn(g *PokerGame, roomTopic string, seatIdx int) { } // Return either or not the game ended because only 1 player left playing (or none) -func waitPlayersActionFn(g *PokerGame, roomID string, skip, minBet int) bool { +func waitPlayersActionFn(db *database.DkfDB, g *PokerGame, roomID string, skip, minBet int) bool { roomTopic := "room_" + roomID roomLogsTopic := "room_" + roomID + "_logs" dealerSeatIdx := int(g.DealerSeatIdx.Load()) @@ -537,6 +553,14 @@ OUTER: break LOOP } + if evt.Unsit { + playerAlive-- + if playerAlive == 1 { + break OUTER + } + continue + } + if evt.Player != p.Username { continue } @@ -573,7 +597,7 @@ OUTER: if bet == 0 { newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s check", p.Username)) } else { - p.doBet(bet) + p.doBet(db, roomID, bet) PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.SeatIdx, Player: p.Username, Bet: bet, TotalBet: p.Bet, Cash: p.Cash}) logMsg := fmt.Sprintf("%s call (%d)", p.Username, bet) if p.isAllIn() { @@ -589,7 +613,7 @@ OUTER: lastRaisePlayerIdx = playerToPlayIdx } minBet = p.Bet + bet - p.doBet(bet) + p.doBet(db, roomID, bet) PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.SeatIdx, Player: p.Username, Bet: bet, TotalBet: p.Bet, Cash: p.Cash}) logMsg := fmt.Sprintf("%s all-in (%d)", p.Username, bet) if p.isAllIn() { @@ -613,7 +637,7 @@ OUTER: lastRaisePlayerIdx = playerToPlayIdx } minBet = p.Bet + bet - p.doBet(bet) + p.doBet(db, roomID, bet) PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.SeatIdx, Player: p.Username, Bet: bet, TotalBet: p.Bet, Cash: p.Cash}) logMsg := fmt.Sprintf("%s bet %d", p.Username, bet) if p.isAllIn() { @@ -786,7 +810,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { p := g.Ongoing.Players[g.smallBlindIdx] bet := bigBlindBet / 2 - p.doBet(bet) + p.doBet(db, roomID, bet) PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.SeatIdx, Player: p.Username, Bet: bet, TotalBet: p.Bet, Cash: p.Cash}) newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s small blind %d", p.Username, bet)) @@ -794,7 +818,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { p = g.Ongoing.Players[g.bigBlindIdx] bet = bigBlindBet - p.doBet(bet) + p.doBet(db, roomID, bet) PokerPubSub.Pub(roomTopic, PlayerBetEvent{PlayerSeatIdx: p.SeatIdx, Player: p.Username, Bet: bet, TotalBet: p.Bet, Cash: p.Cash}) newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s big blind %d", p.Username, bet)) @@ -805,7 +829,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { // Wait for players to bet/call/check/fold... time.Sleep(time.Second) - if waitPlayersActionFn(g, roomID, 2, bigBlindBet) { + if waitPlayersActionFn(db, g, roomID, 2, bigBlindBet) { goto END } @@ -819,7 +843,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { // Wait for players to bet/call/check/fold... time.Sleep(time.Second) - if waitPlayersActionFn(g, roomID, 0, 0) { + if waitPlayersActionFn(db, g, roomID, 0, 0) { goto END } @@ -831,7 +855,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { // Wait for players to bet/call/check/fold... time.Sleep(time.Second) - if waitPlayersActionFn(g, roomID, 0, 0) { + if waitPlayersActionFn(db, g, roomID, 0, 0) { goto END } @@ -843,7 +867,7 @@ func dealerThread(db *database.DkfDB, g *PokerGame, roomID string) { // Wait for players to bet/call/check/fold... time.Sleep(time.Second) - if waitPlayersActionFn(g, roomID, 0, 0) { + if waitPlayersActionFn(db, g, roomID, 0, 0) { goto END } @@ -863,6 +887,13 @@ END: } for _, el := range playersGain { newLogEvent(g, roomLogsTopic, fmt.Sprintf("Winner #%d: %s %s -> %d", el.Group, el.Player.Username, el.HandStr, el.Gain)) + user, _ := db.GetUserByUsername(database.Username(el.Player.Username)) + pokerTable, _ := db.GetPokerTableBySlug(roomID) + account, _ := db.GetPokerTableAccount(user.ID, pokerTable.ID) + account.Amount += int64(el.Gain) + account.DoSave(db) + tableBet, _ := db.GetPokerTableBet(account.ID) + tableBet.UpdateAmount(db, account.ID, 0) winnersStr += el.Player.Username + " " el.Player.Cash += el.Gain } @@ -876,13 +907,17 @@ END: time.Sleep(MinTimeAfterGame * time.Second) // Auto unsit inactive players - for idx, p := range g.Players { - if p != nil && p.LastActionTS.Before(g.Ongoing.CreatedAt) { - if err := g.UnSitPlayer1(db, roomID, p, idx); err == nil { - PokerPubSub.Pub(roomTopic, PokerSeatLeftEvent{}) - newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s auto un-sit", p.Username)) + if pokerTable, err := db.GetPokerTableBySlug(roomID); err == nil { + for idx, p := range g.Players { + if p != nil && p.LastActionTS.Before(g.Ongoing.CreatedAt) { + if err := g.UnSitPlayer1(db, roomID, p, idx, pokerTable); err == nil { + PokerPubSub.Pub(roomTopic, PokerSeatLeftEvent{}) + newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s auto un-sit", p.Username)) + } } } + } else { + logrus.Error(err) } g.IsGameStarted.Store(false) @@ -1131,6 +1166,21 @@ func PokerFoldHandler(c echo.Context) error { return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Fold</button></form>`) } +func Refund(db *database.DkfDB) { + accounts, _ := db.GetPositivePokerTableAccounts() + for _, account := range accounts { + if user, err := db.GetUserByID(account.UserID); err == nil { + tableBet, _ := db.GetPokerTableBet(account.ID) + account.Amount += tableBet.Amount + user.ChipsTest += int(account.Amount) + user.DoSave(db) + account.Amount = 0 + account.DoSave(db) + tableBet.UpdateAmount(db, account.ID, 0) + } + } +} + func PokerDealHandler(c echo.Context) error { roomID := c.Param("roomID") authUser := c.Get("authUser").(*database.User) @@ -1151,6 +1201,11 @@ func PokerUnSitHandler(c echo.Context) error { db := c.Get("database").(*database.DkfDB) authUser := c.Get("authUser").(*database.User) roomID := c.Param("roomID") + html := hutils.HtmlCssReset + `<form method="post"><button>UnSit</button></form>` + pokerTable, err := db.GetPokerTableBySlug(roomID) + if err != nil { + return c.HTML(http.StatusOK, html) + } roomTopic := "room_" + roomID roomLogsTopic := "room_" + roomID + "_logs" g := PokerInstance.GetGame(roomID) @@ -1158,13 +1213,11 @@ func PokerUnSitHandler(c echo.Context) error { return c.NoContent(http.StatusNotFound) } if c.Request().Method == http.MethodPost { - if err := g.UnSitPlayer(db, roomID, authUser.Username.String()); err == nil { + if err := g.UnSitPlayer(db, roomID, authUser, pokerTable); err == nil { PokerPubSub.Pub(roomTopic, PokerSeatLeftEvent{}) newLogEvent(g, roomLogsTopic, fmt.Sprintf("%s un-sit", authUser.Username.String())) } } - html := hutils.HtmlCssReset - html += `<form method="post"><button>UnSit</button></form>` return c.HTML(http.StatusOK, html) } @@ -1178,14 +1231,32 @@ func PokerSitHandler(c echo.Context) error { } pos-- roomID := c.Param("roomID") + pokerTable, err := db.GetPokerTableBySlug(roomID) + if err != nil { + return c.HTML(http.StatusOK, html) + } roomTopic := "room_" + roomID roomLogsTopic := "room_" + roomID + "_logs" g := PokerInstance.GetGame(roomID) if g == nil { return c.HTML(http.StatusOK, html) } + if c.Request().Method == http.MethodPost { - if err := g.SitPlayer(db, authUser, pos); err != nil { + tableAccount, err := db.GetPokerTableAccount(authUser.ID, pokerTable.ID) + if err != nil { + logrus.Error(err) + return c.HTML(http.StatusOK, html) + } + if int64(authUser.ChipsTest)+tableAccount.Amount < pokerTable.MinBuyIn { + return c.HTML(http.StatusOK, html) + } + needed := pokerTable.MinBuyIn - tableAccount.Amount + authUser.ChipsTest -= int(needed) + authUser.DoSave(db) + tableAccount.Amount += needed + tableAccount.DoSave(db) + if err := g.SitPlayer(authUser, pos, tableAccount.Amount); err != nil { fmt.Println(err) return c.HTML(http.StatusOK, html) } @@ -1788,6 +1859,12 @@ body { func PokerLogsHandler(c echo.Context) error { roomID := c.Param("roomID") + db := c.Get("database").(*database.DkfDB) + + if _, err := db.GetPokerTableBySlug(roomID); err != nil { + return c.Redirect(http.StatusFound, "/") + } + send := func(s string) { _, _ = c.Response().Write([]byte(s)) } g := PokerInstance.GetOrCreateGame(roomID) roomLogsTopic := "room_" + roomID + "_logs" @@ -1835,6 +1912,12 @@ Loop: func PokerHandler(c echo.Context) error { roomID := c.Param("roomID") authUser := c.Get("authUser").(*database.User) + db := c.Get("database").(*database.DkfDB) + + if _, err := db.GetPokerTableBySlug(roomID); err != nil { + return c.Redirect(http.StatusFound, "/") + } + roomTopic := "room_" + roomID roomUserTopic := "room_" + roomID + "_" + authUser.Username.String() send := func(s string) { _, _ = c.Response().Write([]byte(s)) }