feat: rps commands

This commit is contained in:
2026-03-17 16:44:18 +00:00
parent dee5008c60
commit 0b36bc9e93
10 changed files with 538 additions and 3 deletions
+76
View File
@@ -0,0 +1,76 @@
package rpsrepo
import (
"context"
"database/sql"
)
type Repo struct {
db *sql.DB
}
func NewRepo(db *sql.DB) *Repo {
return &Repo{db: db}
}
func (r *Repo) GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error) {
var score int
err := r.db.QueryRowContext(ctx, `
SELECT score
FROM rps
WHERE guild_id = $1 AND user_id = $2
`, guildID, userID).Scan(&score)
if err == sql.ErrNoRows {
return 0, false, nil
}
if err != nil {
return 0, false, err
}
return score, true, nil
}
func (r *Repo) SetScore(ctx context.Context, guildID int64, userID int64, score int) error {
_, err := r.db.ExecContext(ctx, `
INSERT INTO rps (guild_id, user_id, score)
VALUES ($1, $2, $3)
ON CONFLICT (guild_id, user_id)
DO UPDATE SET score = EXCLUDED.score
`, guildID, userID, score)
return err
}
type ScoreEntry struct {
UserID int64
Score int
}
func (r *Repo) TopScores(ctx context.Context, guildID int64, limit int) ([]ScoreEntry, error) {
if limit <= 0 {
limit = 10
}
rows, err := r.db.QueryContext(ctx, `
SELECT user_id, score
FROM rps
WHERE guild_id = $1
ORDER BY score DESC, user_id ASC
LIMIT $2
`, guildID, limit)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]ScoreEntry, 0, limit)
for rows.Next() {
var e ScoreEntry
if err := rows.Scan(&e.UserID, &e.Score); err != nil {
return nil, err
}
out = append(out, e)
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
+136
View File
@@ -0,0 +1,136 @@
package rps
import (
"context"
"crypto/rand"
"errors"
"math/big"
"strings"
"velox-bot/internal/db/repos/rpsrepo"
)
type Repo interface {
GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error)
SetScore(ctx context.Context, guildID int64, userID int64, score int) error
TopScores(ctx context.Context, guildID int64, limit int) ([]rpsrepo.ScoreEntry, error)
}
type Service struct {
repo Repo
}
func New(repo Repo) *Service {
return &Service{repo: repo}
}
type Hand string
const (
HandScissors Hand = "✌️"
HandPaper Hand = "✋"
HandRock Hand = "🤜"
)
func (h Hand) String() string { return string(h) }
func ParseHand(s string) (Hand, bool) {
switch strings.TrimSpace(s) {
case string(HandScissors):
return HandScissors, true
case string(HandPaper):
return HandPaper, true
case string(HandRock):
return HandRock, true
default:
return "", false
}
}
func RandomHand() (Hand, error) {
n, err := rand.Int(rand.Reader, big.NewInt(3))
if err != nil {
return "", err
}
switch n.Int64() {
case 0:
return HandScissors, nil
case 1:
return HandPaper, nil
default:
return HandRock, nil
}
}
type GameResult string
const (
ResultWin GameResult = "You won!"
ResultLose GameResult = "You lost!"
ResultTie GameResult = "It's a tie!"
)
func DetermineResult(user Hand, bot Hand) GameResult {
if user == bot {
return ResultTie
}
switch user {
case HandRock:
if bot == HandScissors {
return ResultWin
}
case HandPaper:
if bot == HandRock {
return ResultWin
}
case HandScissors:
if bot == HandPaper {
return ResultWin
}
}
return ResultLose
}
type PlayOutput struct {
UserHand Hand
BotHand Hand
Result GameResult
Score int
}
func (s *Service) Play(ctx context.Context, guildID int64, userID int64, userHand Hand) (PlayOutput, error) {
botHand, err := RandomHand()
if err != nil {
return PlayOutput{}, err
}
result := DetermineResult(userHand, botHand)
score, _, err := s.repo.GetScore(ctx, guildID, userID)
if err != nil {
return PlayOutput{}, err
}
if result == ResultWin {
score++
if err := s.repo.SetScore(ctx, guildID, userID, score); err != nil {
return PlayOutput{}, err
}
}
return PlayOutput{
UserHand: userHand,
BotHand: botHand,
Result: result,
Score: score,
}, nil
}
func (s *Service) GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error) {
return s.repo.GetScore(ctx, guildID, userID)
}
func (s *Service) TopScores(ctx context.Context, guildID int64, limit int) ([]rpsrepo.ScoreEntry, error) {
return s.repo.TopScores(ctx, guildID, limit)
}
var ErrGuildOnly = errors.New("guild only")
+4 -1
View File
@@ -3,19 +3,22 @@ package services
import (
"velox-bot/internal/db/services/level"
"velox-bot/internal/db/services/levelsettings"
"velox-bot/internal/db/services/rps"
)
type Services struct {
Level *level.Service
LevelSettings *levelsettings.Service
RPS *rps.Service
}
var Global *Services
func NewServices(level *level.Service, levelSettings *levelsettings.Service) *Services {
func NewServices(level *level.Service, levelSettings *levelsettings.Service, rps *rps.Service) *Services {
s := &Services{
Level: level,
LevelSettings: levelSettings,
RPS: rps,
}
Global = s
return s