feat: rps commands
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user