commit e37f31a9f88cf2a54c4c46a3bd7303cada4aa562
parent e548cf6c2a6f63a16841ac9206e1202a2f92de85
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Mon, 4 Dec 2023 17:39:42 -0500
cleanup
Diffstat:
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
+}