Added !poker and auth entry point

This commit is contained in:
bryce
2025-07-15 20:24:16 +12:00
parent 049051a7e1
commit b0be1f99c8
6 changed files with 271 additions and 5 deletions

View File

@@ -4,9 +4,10 @@ import (
"log" "log"
"os" "os"
"streambot_twitch/internal/auth" auth "streambot_twitch/internal/auth"
"streambot_twitch/internal/chat" "streambot_twitch/internal/chat"
"streambot_twitch/internal/commands" "streambot_twitch/internal/commands"
"streambot_twitch/internal/points"
twitchapi "streambot_twitch/internal/twitchapi" twitchapi "streambot_twitch/internal/twitchapi"
tests "streambot_twitch/internal/tests" tests "streambot_twitch/internal/tests"
twitch "github.com/gempir/go-twitch-irc/v4" twitch "github.com/gempir/go-twitch-irc/v4"
@@ -14,7 +15,7 @@ import (
func main() { func main() {
go func() { go func() {
if err := oauth.StartOAuthServer(":8080"); err != nil { if err := auth.StartOAuthServer(":8080"); err != nil {
log.Fatalf("OAuth server error: %v", err) log.Fatalf("OAuth server error: %v", err)
} }
}() }()
@@ -22,9 +23,9 @@ func main() {
log.Println("OAuth server started on port 8080") log.Println("OAuth server started on port 8080")
botUsername := os.Getenv("nz_chatterbot") botUsername := os.Getenv("nz_chatterbot")
botOAuth :+ os.Getenv("BOT_OAUTH") botOAuth := os.Getenv("BOT_OAUTH")
channel := os Getenv("brycefromnz101") channel := os Getenv("brycefromnz101")
clientID := os.Getenv("TWITCH_CLIENTID") clientID := os.Getenv("TWITCH_CLIENT_ID")
clientSecret := os.Getenv("TWITCH_CLIENT_SECRET") clientSecret := os.Getenv("TWITCH_CLIENT_SECRET")
botChannel := botUsername botChannel := botUsername
@@ -52,6 +53,14 @@ log.Println("OAuth server started on port 8080")
log.Println("All startup tests passed. Starting bot. . .") log.Println("All startup tests passed. Starting bot. . .")
// END of TESTS // END of TESTS
// load chat points
if err := points.LoadPoints(); err != nil {
log.Fatalf("Failed to load points: %v", err)
}
// make sure points are saved on exit
defer points.SavePoints()
// load custom commands // load custom commands
if err := commands.LoadCommands(); err != nil { if err := commands.LoadCommands(); err != nil {
log.Fatalf("Failed to Load Custom Commands: %v", err) log.Fatalf("Failed to Load Custom Commands: %v", err)

13
cmd/oauth/main.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"log"
auth "streambot_twitch/internal/auth"
)
func main() {
if err := auth.StartOAuthServer(":8080"); err != nil {
log.Fatalf("OAuth server error: %v", err)
}
}

View File

@@ -1,4 +1,4 @@
package oauth package auth
import ( import (
"encoding/json" "encoding/json"

View File

@@ -91,6 +91,12 @@ func HandleMessage(client *twitch.Client, apiClient interface{}, message twitch.
return return
} }
// dispatch poker/table commands
if strings.HasPrefix(strings.ToLower(msg), "!poker") || strings.HasPrefix(strings.ToLower(msg), "!table") {
HandlePokerCommand(client, message, user, channel, msg)
return
}
// Dispatch custom/default commands // Dispatch custom/default commands
HandleCustomCommands(client, message, user, channel, botUsername) HandleCustomCommands(client, message, user, channel, botUsername)
} }

154
internal/chat/poker.go Normal file
View File

@@ -0,0 +1,154 @@
package chat
import (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
twitch "github.com/gempir/go-twitch-irc/v4"
"streambot_twitch/internal/points"
)
// card
type Card struct {
Rank, Suit string
}
var (
suits = []string{" of Hearts", " of Diamnods", " of Clubs", " of Spades"}
ranks = []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
baseDeck []Card
)
func init() {
// build 2 decks (no Jokers) 104 cards
for d := 0; d < 2; d++ {
for _, s := range suits {
for _, r := range ranks {
baseDeck = append(baseDeck, Card{Rank: r, Suit: s})
}
}
}
rand.Seed(time.Now().UnixNano())
}
// impliment !poker or !table <bet>
func HandlePokerCommand(
client *twitch.Client,
message twitch.PrivateMessage,
user twitch.User,
channel, msg string,
) {
parts := strings.Fields(msg)
if len(parts) < 2 {
client.Say(channel, fmt.Sprintf("@%s Usage: !poker <bet>", user.DisplayName))
return
}
bet, err := strconv.Atoi(parts[1])
if err != nil || bet <= 0 {
client.Say(channel, fmt.Sprintf("@%s Invalid bet amount", user.DisplayName))
return
}
login := strings.ToLower(user.Name)
// load or init user points
balance := points.GetPoints(login)
if bet > ballance {
client.Say(channel, fmt.Sprintf("@%s You have %d points, bet lower", user.DisplayName, balance))
return
}
// Deduct the bet
balance, err = points.AddPoints(login, -bet)
if err != nil {
client.Say(channel, fmt.Sprintf("@%s Error updating points: %v", user.DisplayName, err))
return
}
// shuffle deck
deck := make([]Card, len(baseDeck))
copy(deck, baseDeck)
rand.Shuffle(len(deck), func(i, j int) {deck[i], deck[j] = deck[j], deck[i]})
// deal 2 cards each
player := []Card{deck[0], deck[1]}
dealer := []Card{deck[2], deck[3]}
// compute hand values
pv := handValue(player)
dv := handValue(dealer)
// Determine outcome and payout multiplier
var net int
var outcome string
switch {
case pv > 21:
outcome = "FBPenalty Bust! You lose"
net = 0
case pv == 21:
outcome = "BLACKJACK! GoatEmotey"
net = bet * 2
case dv > 21:
outcome = "GoldPLZ Dealer bust! You win"
net = int(float64(bet) * 1.5)
case pv > dv:
outcome = "OhMyDog You Win"
net int(float64(bet) * 1.3)
case pv < dv:
outcome = "cmonBruh You lose"
net = 0
default:
outcome = "PunchTrees Push"
net = bet
}
// Add winnings
balance, err = points.AddPoints(login, net)
if err != nil {
client.Say(channel, fmt.Sprintf("@%s Error updating Points: %v", user.DisplayName, err))
return
}
// format a hand for display
fmtHand := func(h []Card) string {
var parts []string
for _, c := range h {
parts = append(parts, c.Rank+c.Suit)
}
return strings.Join(parts, ",")
}
// post result
client.Say(channel, fmt.Sprintf("@%s You: [%s]=%d Dealer: [%s]=%d => %s | Bet: %d Net: %d Ballance: %d", user.DisplayName,
fmtHand(player), pv,
fmtHand(dealer), dv,
outcome,
bet, net, balance,
))
}
// compute BlackJack style value (A = 11 or 1)
func handValue(hand []Card) int {
total := 0
aces := 0
for _, c := range hand {
switch c.Rank {
case "J", "Q", "K":
total += 10
case "A":
total += 11
aces++
default:
v, _ := strconv.Atoi(c.Rank)
total += v
}
}
// convert A from 11 to 1 while busting
for total > 21 && aces > 0 {
total -= 10
aces--
}
return total
}

84
internal/points/points.go Normal file
View File

@@ -0,0 +1,84 @@
package points
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
)
const (
dataDir = "internal/storage"
pointsFile = "points.json"
)
var (
mu sync.Mutex
pts map[string]int
)
const DefaultPoints = 100000
// init or load points from disk
func LoadPoints() error {
mu.Lock()
defer mu.Unlock()
pts = make(map[string]int)
path := filepath.Join(dataDir, pointsFile)
data, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return json.Unmarshal(data, &pts)
}
// save balances to disk
func SavePoints() error {
mu.Lock()
data, err := json.MarshalIndent(pts, "", " ")
mu.Unlock()
if err != nil {
return err
}
if err := os.MkdirAll(dataDir, 0755); err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(dataDir, pointsFile), data, 0644)
}
// return point balance for user, init if needed
func GetPoints(user string) int {
mu.Lock()
defer mu.Unlock()
key := strings.ToLower(user)
bal, ok := pts[key]
if !ok {
pts[key] = DefaultPoints
bal = DefaultPoints
}
return bal
}
// add points to user's bal, save & return new bal
func AddPoints(user string, delta int) (int error) {
mu.Lock()
key := strings.ToLower(user)
bal, ok := pts[key]
if !ok {
bal = DefaultPoints
}
bal += delta
pts[key] = bal
mu.Unlock()
if err := SavePoints(); err != nil {
return bal, err
}
return bal, nil
}