dkforest

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

commit e37f31a9f88cf2a54c4c46a3bd7303cada4aa562
parent e548cf6c2a6f63a16841ac9206e1202a2f92de85
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Mon,  4 Dec 2023 17:39:42 -0500

cleanup

Diffstat:
Mpkg/web/handlers/handlers.go | 548-------------------------------------------------------------------------------
Apkg/web/handlers/poker.go | 559+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 559 insertions(+), 548 deletions(-)

diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -8,11 +8,9 @@ import ( "dkforest/pkg/database" dutils "dkforest/pkg/database/utils" "dkforest/pkg/odometer" - "dkforest/pkg/pubsub" "dkforest/pkg/utils" hutils "dkforest/pkg/web/handlers/utils" "encoding/base64" - "errors" "fmt" humanize "github.com/dustin/go-humanize" "github.com/labstack/echo" @@ -30,9 +28,7 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" - "sync" "time" ) @@ -755,547 +751,3 @@ func BHCHandler(c echo.Context) error { data.Success = fmt.Sprintf("Good answer, go back to BHC and use '%s' as your username", username+h[:3]) return c.Render(http.StatusOK, "bhc", data) } - -type Poker struct { - sync.Mutex - Games map[string]*PokerGame -} - -func NewPoker() *Poker { - p := &Poker{} - p.Games = make(map[string]*PokerGame) - return p -} - -func (p *Poker) GetOrCreateGame(roomID string) *PokerGame { - p.Lock() - defer p.Unlock() - g, found := p.Games[roomID] - if !found { - g = &PokerGame{ - Players: make([]string, 10), - } - p.Games[roomID] = g - } - return g -} - -var PokerInstance = NewPoker() - -type Ongoing struct { - Deck []string - Players []string -} - -type PokerGame struct { - sync.Mutex - Players []string - Ongoing *Ongoing -} - -func (g *PokerGame) Deal(roomID string) { - if g.Ongoing != nil { - fmt.Println("game already ongoing") - return - } - if g.CountSeated() < 2 { - fmt.Println("need at least 2 players") - return - } - deck := []string{ - "A♠", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", - "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", - "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", - "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦", - } - utils.Shuffle(deck) - - players := make([]string, 10) - for idx := range g.Players { - players[idx] = g.Players[idx] - } - - g.Ongoing = &Ongoing{Deck: deck, Players: players} - - go func() { - myTopic := "room_" + roomID - type Seat struct { - Top int - Left int - Angle string - } - seats := []Seat{ - {Top: 50, Left: 600, Angle: "-90deg"}, - {Top: 150, Left: 574, Angle: "-80deg"}, - {Top: 250, Left: 530, Angle: "-70deg"}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - //{Top: 150, Left: 550}, - } - - type Data struct { - Top int - Left int - Angle string - Reveal bool - } - - data := []Data{ - {seats[0].Top, seats[0].Left, seats[0].Angle, false}, - {seats[1].Top, seats[1].Left, seats[1].Angle, false}, - {seats[2].Top, seats[2].Left, seats[2].Angle, false}, - {seats[0].Top + 5, seats[0].Left + 3, seats[0].Angle, false}, - {seats[1].Top + 5, seats[1].Left + 2, seats[1].Angle, false}, - {seats[2].Top + 5, seats[2].Left + 1, seats[2].Angle, false}, - } - - var card string - idx := 0 - - for _, d := range data { - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - name := "" - if d.Reveal { - name = card - } - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), - Name: name, - Idx: idx, - Top: d.Top, - Left: d.Left, - Angle: d.Angle, - Burn: !d.Reveal, - }) - } - - // Burn - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), - Name: "", - Idx: idx, - Top: 30, - Left: 400, - Burn: true, - }) - - // Flop (3 first cards) - for i := 1; i <= 3; i++ { - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), - Name: card, - Idx: idx, - Top: 150, - Left: 100 + (i * 55), - }) - } - - // Burn - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), - Name: "", - Idx: idx, - Top: 32, - Left: 404, - Burn: true, - }) - - // Turn (4th card) - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), Name: card, - Idx: idx, - Top: 150, - Left: 100 + (4 * 55), - }) - - // Burn - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), - Name: "", - Idx: idx, - Top: 34, - Left: 408, - Burn: true, - }) - - // River (5th card) - time.Sleep(time.Second) - idx++ - card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] - PokerPubSub.Pub(myTopic, PokerEvent{ - ID: "card" + strconv.Itoa(idx), Name: card, - Idx: idx, - Top: 150, - Left: 100 + (5 * 55), - }) - }() -} - -func (g *PokerGame) CountSeated() (count int) { - for _, p := range g.Players { - if p != "" { - count++ - } - } - return -} - -func (g *PokerGame) IsSeated(player string) (bool, int) { - isSeated := false - pos := 0 - for idx, p := range g.Players { - if p == player { - isSeated = true - pos = idx + 1 - break - } - } - return isSeated, pos -} - -type PokerEvent struct { - ID string - Idx int - Name string - Top int - Left int - Burn bool - Angle string -} - -type PokerSeatTakenEvent struct { -} - -type PokerSeatLeftEvent struct { - Idx int -} - -var PokerPubSub = pubsub.NewPubSub[any]() - -func PokerDealHandler(c echo.Context) error { - roomID := c.Param("roomID") - PokerInstance.Lock() - g, found := PokerInstance.Games[roomID] - PokerInstance.Unlock() - if !found { - return c.NoContent(http.StatusNotFound) - } - if c.Request().Method == http.MethodPost { - g.Deal(roomID) - return c.HTML(http.StatusOK, ``) - } - return c.HTML(http.StatusOK, `<form method="post"><button>Deal</button></form>`) -} - -func PokerUnSitHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - roomID := c.Param("roomID") - PokerInstance.Lock() - g, found := PokerInstance.Games[roomID] - PokerInstance.Unlock() - if !found { - return c.NoContent(http.StatusNotFound) - } - if c.Request().Method == http.MethodPost { - var idx int - found := false - for i, p := range g.Players { - if p == authUser.Username.String() { - g.Players[i] = "" - idx = i - found = true - break - } - } - if found { - myTopic := "room_" + roomID - PokerPubSub.Pub(myTopic, PokerSeatLeftEvent{Idx: idx + 1}) - } - } - return c.HTML(http.StatusOK, `<form method="post"><button>UnSit</button></form>`) -} - -func PokerSitHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - pos, _ := strconv.Atoi(c.Param("pos")) - pos-- - roomID := c.Param("roomID") - PokerInstance.Lock() - g, found := PokerInstance.Games[roomID] - PokerInstance.Unlock() - if !found { - return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) - } - if c.Request().Method == http.MethodPost { - if g.Players[pos] != "" { - fmt.Println("seat already taken") - return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) - } - myTopic := "room_" + roomID - g.Players[pos] = authUser.Username.String() - PokerPubSub.Pub(myTopic, PokerSeatTakenEvent{}) - } - return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) -} - -func buildTakeSeatHtml(authUser *database.User, g *PokerGame, roomID string) string { - takeSeatBtns := "" - if seated, _ := g.IsSeated(authUser.Username.String()); !seated { // if player not already seated - for i, p := range g.Players { - takeSeatBtns += `<iframe src="/poker/` + roomID + `/sit/` + strconv.Itoa(i+1) + `" class="takeSeat takeSeat` + strconv.Itoa(i+1) + `"></iframe>` - if p != "" { - takeSeatBtns += `<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>` - } - } - } - return takeSeatBtns -} - -func buildSeatsHtml(g *PokerGame) string { - seats := ` -<div>` - for i, p := range g.Players { - if p != "" { - seats += `<div id="seat` + strconv.Itoa(i+1) + `"></div>` - seats += `<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>` - } else { - seats += `<div id="seat` + strconv.Itoa(i+1) + `"></div>` - } - } - seats += ` -</div>` - return seats -} - -func PokerHandler(c echo.Context) error { - // players 2-10 - // $5 -> 1000 chips - // generate deck - // hash the deck and show hash - //return c.Render(http.StatusOK, "poker", nil) - - roomID := c.Param("roomID") - g := PokerInstance.GetOrCreateGame(roomID) - - authUser := c.Get("authUser").(*database.User) - - send := func(s string) { - _, _ = c.Response().Write([]byte(s)) - } - - deckStr := "" - if g.Ongoing != nil { - deckStr = strings.Join(g.Ongoing.Deck, "") - } - deckSha256 := utils.Sha256([]byte(deckStr)) - - quit := hutils.CloseSignalChan(c) - myTopic := "room_" + roomID - - sub := PokerPubSub.Subscribe([]string{myTopic}) - defer sub.Close() - - c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) - c.Response().WriteHeader(http.StatusOK) - c.Response().Header().Set("Transfer-Encoding", "chunked") - c.Response().Header().Set("Connection", "keep-alive") - - send(cssReset) - send(`<style> -html, body { height: 100%; width: 100%; } -body { - background:linear-gradient(135deg, #449144 33%,#008a00 95%); -} -.card-holder{ - position: absolute; - top: 0; - left: 0; - transform: translateX(250px) translateY(30px) rotateY(180deg); - transform-style: preserve-3d; - backface-visibility: hidden; - width:50px; - height:70px; - display:inline-block; - box-shadow:1px 2px 2px rgba(0,0,0,.8); - margin:2px; -} -.card { - box-shadow: inset 2px 2px 0 #fff, inset -2px -2px 0 #fff; - transform-style: preserve-3d; - position:absolute; - top:0; - left:0; - bottom:0; - right:0; - backface-visibility: hidden; - background-color:#fcfcfc; - border-radius:2%; - display:block; - width:100%; - height:100%; - border:1px solid black; -} -.card .inner { - padding: 5px; - font-size: 25px; -} -.back{ - position:absolute; - top:0; - left:0; - bottom:0; - right:0; - width:100%; - height:100%; - backface-visibility: hidden; - transform: rotateY( 180deg ); - background: #36c; - background: - linear-gradient(135deg, #f26c32 0%,#c146a1 50%,#a80077 51%,#f9703e 100%); - border-radius:2%; - box-shadow: inset 3px 3px 0 #fff, inset -3px -3px 0 #fff; - display:block; - border:1px solid black; -} -.takeSeat { - width: 40px; - height: 30px; -} -#seat1 { position: absolute; top: 80px; left: 700px; } -#seat2 { position: absolute; top: 200px; left: 670px; } -#seat3 { position: absolute; top: 300px; left: 610px; } -.takeSeat1 { position: absolute; top: 80px; left: 700px; } -.takeSeat2 { position: absolute; top: 200px; left: 670px; } -.takeSeat3 { position: absolute; top: 300px; left: 610px; } -.takeSeat4 { position: absolute; top: 300px; left: 550px; } -.takeSeat5 { position: absolute; top: 300px; left: 500px; } -.takeSeat6 { position: absolute; top: 300px; left: 450px; } -.takeSeat7 { position: absolute; top: 300px; left: 400px; } -.takeSeat8 { position: absolute; top: 300px; left: 350px; } -.takeSeat9 { position: absolute; top: 300px; left: 300px; } -.takeSeat10 { position: absolute; top: 300px; left: 250px; } -#dealBtn { position: absolute; top: 400px; left: 50px; } -#unSitBtn { position: absolute; top: 430px; left: 50px; } -</style>`) - cardsHtml := "" - for i := 52; i >= 1; i-- { - cardsHtml += `<div class="card-holder" id="card` + strconv.Itoa(i) + `"><div class="back"></div><div class="card"><div class=inner></div></div></div>` - } - send(cardsHtml) - send(buildTakeSeatHtml(authUser, g, roomID)) - send(buildSeatsHtml(g)) - turnAction := ` -<div style="position: absolute; top: 400px; left: 200px;"> - <form><input type="number" /><button>Bet</button></form> - <form><button>call</button></form> - <form><button>check</button></form> - <form><button>fold</button></form> -</div>` - send(turnAction) - actions := `<iframe src="/poker/` + roomID + `/deal" id="dealBtn"></iframe>` - actions += `<iframe src="/poker/` + roomID + `/unsit" id="unSitBtn"></iframe>` - send(actions) - deckHash := deckSha256 - send(`<div>` + deckStr + `</div>`) - send(`<div>` + deckHash + `</div>`) - c.Response().Flush() -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 - } - - if _, ok := payload.(PokerSeatLeftEvent); ok { - seated, _ := g.IsSeated(authUser.Username.String()) - for i, p := range g.Players { - if p != "" || seated { - send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>`) - } else { - send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: block; }</style>`) - } - if p != "" { - send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>`) - } else { - send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: ""; }</style>`) - } - } - c.Response().Flush() - continue - - } else if _, ok := payload.(PokerSeatTakenEvent); ok { - - seated, _ := g.IsSeated(authUser.Username.String()) - - for i, p := range g.Players { - if p != "" || seated { - send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>`) - } else { - send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: block; }</style>`) - } - if p != "" { - send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>`) - } else { - send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: ""; }</style>`) - } - } - c.Response().Flush() - continue - - } else if payload, ok := payload.(PokerEvent); ok { - color := "black" - if strings.Contains(payload.Name, "♥") || - strings.Contains(payload.Name, "♦") { - color = "red" - } - transform := `transform: translate(` + strconv.Itoa(payload.Left) + `px, ` + strconv.Itoa(payload.Top) + `px)` - if payload.Angle != "" { - transform += ` rotateZ(` + payload.Angle + `)` - } - if payload.Burn { - transform += ` rotateY(180deg)` - } - transform += ";" - send(`<style> -#` + payload.ID + ` { - z-index: ` + strconv.Itoa(payload.Idx) + `; - transition: 1s ease-in-out; - ` + transform + ` -} -#` + payload.ID + ` .card .inner:before { content: "` + payload.Name + `"; color: ` + color + `; } -</style>`) - c.Response().Flush() - } - } - return nil -} diff --git a/pkg/web/handlers/poker.go b/pkg/web/handlers/poker.go @@ -0,0 +1,559 @@ +package handlers + +import ( + "dkforest/pkg/database" + "dkforest/pkg/pubsub" + "dkforest/pkg/utils" + hutils "dkforest/pkg/web/handlers/utils" + "fmt" + "github.com/labstack/echo" + "net/http" + "strconv" + "strings" + "sync" + "time" +) + +type Poker struct { + sync.Mutex + Games map[string]*PokerGame +} + +func NewPoker() *Poker { + p := &Poker{} + p.Games = make(map[string]*PokerGame) + return p +} + +func (p *Poker) GetOrCreateGame(roomID string) *PokerGame { + p.Lock() + defer p.Unlock() + g, found := p.Games[roomID] + if !found { + g = &PokerGame{ + Players: make([]string, 10), + } + p.Games[roomID] = g + } + return g +} + +var PokerInstance = NewPoker() + +type Ongoing struct { + Deck []string + Players []string +} + +type PokerGame struct { + sync.Mutex + Players []string + Ongoing *Ongoing +} + +func (g *PokerGame) Deal(roomID string) { + if g.Ongoing != nil { + fmt.Println("game already ongoing") + return + } + if g.CountSeated() < 2 { + fmt.Println("need at least 2 players") + return + } + deck := []string{ + "A♠", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", + "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", + "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", + "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦", + } + utils.Shuffle(deck) + + players := make([]string, 10) + for idx := range g.Players { + players[idx] = g.Players[idx] + } + + g.Ongoing = &Ongoing{Deck: deck, Players: players} + + go func() { + myTopic := "room_" + roomID + type Seat struct { + Top int + Left int + Angle string + } + seats := []Seat{ + {Top: 50, Left: 600, Angle: "-90deg"}, + {Top: 150, Left: 574, Angle: "-80deg"}, + {Top: 250, Left: 530, Angle: "-70deg"}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + //{Top: 150, Left: 550}, + } + + type Data struct { + Top int + Left int + Angle string + Reveal bool + } + + data := []Data{ + {seats[0].Top, seats[0].Left, seats[0].Angle, false}, + {seats[1].Top, seats[1].Left, seats[1].Angle, false}, + {seats[2].Top, seats[2].Left, seats[2].Angle, false}, + {seats[0].Top + 5, seats[0].Left + 3, seats[0].Angle, false}, + {seats[1].Top + 5, seats[1].Left + 2, seats[1].Angle, false}, + {seats[2].Top + 5, seats[2].Left + 1, seats[2].Angle, false}, + } + + var card string + idx := 0 + + for _, d := range data { + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + name := "" + if d.Reveal { + name = card + } + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), + Name: name, + Idx: idx, + Top: d.Top, + Left: d.Left, + Angle: d.Angle, + Burn: !d.Reveal, + }) + } + + // Burn + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), + Name: "", + Idx: idx, + Top: 30, + Left: 400, + Burn: true, + }) + + // Flop (3 first cards) + for i := 1; i <= 3; i++ { + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), + Name: card, + Idx: idx, + Top: 150, + Left: 100 + (i * 55), + }) + } + + // Burn + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), + Name: "", + Idx: idx, + Top: 32, + Left: 404, + Burn: true, + }) + + // Turn (4th card) + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), Name: card, + Idx: idx, + Top: 150, + Left: 100 + (4 * 55), + }) + + // Burn + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), + Name: "", + Idx: idx, + Top: 34, + Left: 408, + Burn: true, + }) + + // River (5th card) + time.Sleep(time.Second) + idx++ + card, g.Ongoing.Deck = g.Ongoing.Deck[0], g.Ongoing.Deck[1:] + PokerPubSub.Pub(myTopic, PokerEvent{ + ID: "card" + strconv.Itoa(idx), Name: card, + Idx: idx, + Top: 150, + Left: 100 + (5 * 55), + }) + }() +} + +func (g *PokerGame) CountSeated() (count int) { + for _, p := range g.Players { + if p != "" { + count++ + } + } + return +} + +func (g *PokerGame) IsSeated(player string) (bool, int) { + isSeated := false + pos := 0 + for idx, p := range g.Players { + if p == player { + isSeated = true + pos = idx + 1 + break + } + } + return isSeated, pos +} + +type PokerEvent struct { + ID string + Idx int + Name string + Top int + Left int + Burn bool + Angle string +} + +type PokerSeatTakenEvent struct { +} + +type PokerSeatLeftEvent struct { + Idx int +} + +var PokerPubSub = pubsub.NewPubSub[any]() + +func PokerDealHandler(c echo.Context) error { + roomID := c.Param("roomID") + PokerInstance.Lock() + g, found := PokerInstance.Games[roomID] + PokerInstance.Unlock() + if !found { + return c.NoContent(http.StatusNotFound) + } + if c.Request().Method == http.MethodPost { + g.Deal(roomID) + return c.HTML(http.StatusOK, ``) + } + return c.HTML(http.StatusOK, `<form method="post"><button>Deal</button></form>`) +} + +func PokerUnSitHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + roomID := c.Param("roomID") + PokerInstance.Lock() + g, found := PokerInstance.Games[roomID] + PokerInstance.Unlock() + if !found { + return c.NoContent(http.StatusNotFound) + } + if c.Request().Method == http.MethodPost { + var idx int + found := false + for i, p := range g.Players { + if p == authUser.Username.String() { + g.Players[i] = "" + idx = i + found = true + break + } + } + if found { + myTopic := "room_" + roomID + PokerPubSub.Pub(myTopic, PokerSeatLeftEvent{Idx: idx + 1}) + } + } + return c.HTML(http.StatusOK, `<form method="post"><button>UnSit</button></form>`) +} + +func PokerSitHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + pos, _ := strconv.Atoi(c.Param("pos")) + pos-- + roomID := c.Param("roomID") + PokerInstance.Lock() + g, found := PokerInstance.Games[roomID] + PokerInstance.Unlock() + if !found { + return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) + } + if c.Request().Method == http.MethodPost { + if g.Players[pos] != "" { + fmt.Println("seat already taken") + return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) + } + myTopic := "room_" + roomID + g.Players[pos] = authUser.Username.String() + PokerPubSub.Pub(myTopic, PokerSeatTakenEvent{}) + } + return c.HTML(http.StatusOK, `<form method="post"><button>SIT</button></form>`) +} + +func buildTakeSeatHtml(authUser *database.User, g *PokerGame, roomID string) string { + takeSeatBtns := "" + if seated, _ := g.IsSeated(authUser.Username.String()); !seated { // if player not already seated + for i, p := range g.Players { + takeSeatBtns += `<iframe src="/poker/` + roomID + `/sit/` + strconv.Itoa(i+1) + `" class="takeSeat takeSeat` + strconv.Itoa(i+1) + `"></iframe>` + if p != "" { + takeSeatBtns += `<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>` + } + } + } + return takeSeatBtns +} + +func buildSeatsHtml(g *PokerGame) string { + seats := ` +<div>` + for i, p := range g.Players { + if p != "" { + seats += `<div id="seat` + strconv.Itoa(i+1) + `"></div>` + seats += `<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>` + } else { + seats += `<div id="seat` + strconv.Itoa(i+1) + `"></div>` + } + } + seats += ` +</div>` + return seats +} + +func PokerHandler(c echo.Context) error { + // players 2-10 + // $5 -> 1000 chips + // generate deck + // hash the deck and show hash + //return c.Render(http.StatusOK, "poker", nil) + + roomID := c.Param("roomID") + g := PokerInstance.GetOrCreateGame(roomID) + + authUser := c.Get("authUser").(*database.User) + + send := func(s string) { + _, _ = c.Response().Write([]byte(s)) + } + + deckStr := "" + if g.Ongoing != nil { + deckStr = strings.Join(g.Ongoing.Deck, "") + } + deckSha256 := utils.Sha256([]byte(deckStr)) + + quit := hutils.CloseSignalChan(c) + myTopic := "room_" + roomID + + sub := PokerPubSub.Subscribe([]string{myTopic}) + defer sub.Close() + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + c.Response().WriteHeader(http.StatusOK) + c.Response().Header().Set("Transfer-Encoding", "chunked") + c.Response().Header().Set("Connection", "keep-alive") + + send(cssReset) + send(`<style> +html, body { height: 100%; width: 100%; } +body { + background:linear-gradient(135deg, #449144 33%,#008a00 95%); +} +.card-holder{ + position: absolute; + top: 0; + left: 0; + transform: translateX(250px) translateY(30px) rotateY(180deg); + transform-style: preserve-3d; + backface-visibility: hidden; + width:50px; + height:70px; + display:inline-block; + box-shadow:1px 2px 2px rgba(0,0,0,.8); + margin:2px; +} +.card { + box-shadow: inset 2px 2px 0 #fff, inset -2px -2px 0 #fff; + transform-style: preserve-3d; + position:absolute; + top:0; + left:0; + bottom:0; + right:0; + backface-visibility: hidden; + background-color:#fcfcfc; + border-radius:2%; + display:block; + width:100%; + height:100%; + border:1px solid black; +} +.card .inner { + padding: 5px; + font-size: 25px; +} +.back{ + position:absolute; + top:0; + left:0; + bottom:0; + right:0; + width:100%; + height:100%; + backface-visibility: hidden; + transform: rotateY( 180deg ); + background: #36c; + background: + linear-gradient(135deg, #f26c32 0%,#c146a1 50%,#a80077 51%,#f9703e 100%); + border-radius:2%; + box-shadow: inset 3px 3px 0 #fff, inset -3px -3px 0 #fff; + display:block; + border:1px solid black; +} +.takeSeat { + width: 40px; + height: 30px; +} +#seat1 { position: absolute; top: 80px; left: 700px; } +#seat2 { position: absolute; top: 200px; left: 670px; } +#seat3 { position: absolute; top: 300px; left: 610px; } +.takeSeat1 { position: absolute; top: 80px; left: 700px; } +.takeSeat2 { position: absolute; top: 200px; left: 670px; } +.takeSeat3 { position: absolute; top: 300px; left: 610px; } +.takeSeat4 { position: absolute; top: 300px; left: 550px; } +.takeSeat5 { position: absolute; top: 300px; left: 500px; } +.takeSeat6 { position: absolute; top: 300px; left: 450px; } +.takeSeat7 { position: absolute; top: 300px; left: 400px; } +.takeSeat8 { position: absolute; top: 300px; left: 350px; } +.takeSeat9 { position: absolute; top: 300px; left: 300px; } +.takeSeat10 { position: absolute; top: 300px; left: 250px; } +#dealBtn { position: absolute; top: 400px; left: 50px; } +#unSitBtn { position: absolute; top: 430px; left: 50px; } +</style>`) + cardsHtml := "" + for i := 52; i >= 1; i-- { + cardsHtml += `<div class="card-holder" id="card` + strconv.Itoa(i) + `"><div class="back"></div><div class="card"><div class=inner></div></div></div>` + } + send(cardsHtml) + send(buildTakeSeatHtml(authUser, g, roomID)) + send(buildSeatsHtml(g)) + turnAction := ` +<div style="position: absolute; top: 400px; left: 200px;"> + <form><input type="number" /><button>Bet</button></form> + <form><button>call</button></form> + <form><button>check</button></form> + <form><button>fold</button></form> +</div>` + send(turnAction) + actions := `<iframe src="/poker/` + roomID + `/deal" id="dealBtn"></iframe>` + actions += `<iframe src="/poker/` + roomID + `/unsit" id="unSitBtn"></iframe>` + send(actions) + deckHash := deckSha256 + send(`<div>` + deckStr + `</div>`) + send(`<div>` + deckHash + `</div>`) + c.Response().Flush() +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 + } + + if _, ok := payload.(PokerSeatLeftEvent); ok { + seated, _ := g.IsSeated(authUser.Username.String()) + for i, p := range g.Players { + if p != "" || seated { + send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>`) + } else { + send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: block; }</style>`) + } + if p != "" { + send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>`) + } else { + send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: ""; }</style>`) + } + } + c.Response().Flush() + continue + + } else if _, ok := payload.(PokerSeatTakenEvent); ok { + + seated, _ := g.IsSeated(authUser.Username.String()) + + for i, p := range g.Players { + if p != "" || seated { + send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: none; }</style>`) + } else { + send(`<style>.takeSeat` + strconv.Itoa(i+1) + ` { display: block; }</style>`) + } + if p != "" { + send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: "` + p + `"; }</style>`) + } else { + send(`<style>#seat` + strconv.Itoa(i+1) + `:before { content: ""; }</style>`) + } + } + c.Response().Flush() + continue + + } else if payload, ok := payload.(PokerEvent); ok { + color := "black" + if strings.Contains(payload.Name, "♥") || + strings.Contains(payload.Name, "♦") { + color = "red" + } + transform := `transform: translate(` + strconv.Itoa(payload.Left) + `px, ` + strconv.Itoa(payload.Top) + `px)` + if payload.Angle != "" { + transform += ` rotateZ(` + payload.Angle + `)` + } + if payload.Burn { + transform += ` rotateY(180deg)` + } + transform += ";" + send(`<style> +#` + payload.ID + ` { + z-index: ` + strconv.Itoa(payload.Idx) + `; + transition: 1s ease-in-out; + ` + transform + ` +} +#` + payload.ID + ` .card .inner:before { content: "` + payload.Name + `"; color: ` + color + `; } +</style>`) + c.Response().Flush() + } + } + return nil +}