feat: rps commands
This commit is contained in:
@@ -13,6 +13,9 @@ var AllCommands = []*discordgo.ApplicationCommand{
|
|||||||
fun.Joke,
|
fun.Joke,
|
||||||
fun.Coinflip,
|
fun.Coinflip,
|
||||||
fun.Dice,
|
fun.Dice,
|
||||||
|
fun.Rps,
|
||||||
|
fun.RpsStats,
|
||||||
|
fun.RpsLeaderboard,
|
||||||
help.Help,
|
help.Help,
|
||||||
cmdlevel.Slvl,
|
cmdlevel.Slvl,
|
||||||
cmdlevel.Rank,
|
cmdlevel.Rank,
|
||||||
@@ -25,6 +28,9 @@ var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCre
|
|||||||
"joke": fun.JokeHandler,
|
"joke": fun.JokeHandler,
|
||||||
"coinflip": fun.CoinflipHandler,
|
"coinflip": fun.CoinflipHandler,
|
||||||
"dice": fun.DiceHandler,
|
"dice": fun.DiceHandler,
|
||||||
|
"rps": fun.RpsHandler,
|
||||||
|
"rpsstats": fun.RpsStatsHandler,
|
||||||
|
"rpsleaderboard": fun.RpsLeaderboardHandler,
|
||||||
"help": help.HelpHandler,
|
"help": help.HelpHandler,
|
||||||
"slvl": cmdlevel.SlvlHandler,
|
"slvl": cmdlevel.SlvlHandler,
|
||||||
"rank": cmdlevel.RankHandler,
|
"rank": cmdlevel.RankHandler,
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/fun/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
rpssvc "velox-bot/internal/db/services/rps"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Rps = &discordgo.ApplicationCommand{
|
||||||
|
Name: "rps",
|
||||||
|
Description: "Plays Rock Paper Scissors with you",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "hand",
|
||||||
|
Description: "Choose between ✌️, ✋ or 🤜",
|
||||||
|
Required: true,
|
||||||
|
Choices: []*discordgo.ApplicationCommandOptionChoice{
|
||||||
|
{Name: "✌️ - Scissors", Value: string(rpssvc.HandScissors)},
|
||||||
|
{Name: "✋ - Paper", Value: string(rpssvc.HandPaper)},
|
||||||
|
{Name: "🤜 - Rock", Value: string(rpssvc.HandRock)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Contexts: &[]discordgo.InteractionContextType{
|
||||||
|
discordgo.InteractionContextGuild,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RpsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if i.GuildID == "" {
|
||||||
|
shared.Respond(s, i, "This command can only be used in a server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if services.Global == nil || services.Global.RPS == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "RPS service is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := i.ApplicationCommandData()
|
||||||
|
if len(data.Options) == 0 {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid hand! Please choose between ✌️, ✋ or 🤜")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handStr := data.Options[0].StringValue()
|
||||||
|
userHand, ok := rpssvc.ParseHand(handStr)
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid hand! Please choose between ✌️, ✋ or 🤜")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid guild.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := i.User
|
||||||
|
if i.Member != nil && i.Member.User != nil {
|
||||||
|
user = i.Member.User
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(user.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := services.Global.RPS.Play(context.Background(), guildID, userID, userHand)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to play RPS.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
color := 0xEF4444
|
||||||
|
switch out.Result {
|
||||||
|
case rpssvc.ResultWin:
|
||||||
|
color = 0x22C55E
|
||||||
|
case rpssvc.ResultTie:
|
||||||
|
color = 0xF59E0B
|
||||||
|
}
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: string(out.Result),
|
||||||
|
Description: user.Mention() + " vs bot",
|
||||||
|
Color: color,
|
||||||
|
Fields: []*discordgo.MessageEmbedField{
|
||||||
|
{Name: "Your hand", Value: out.UserHand.String(), Inline: true},
|
||||||
|
{Name: "Bot hand", Value: out.BotHand.String(), Inline: true},
|
||||||
|
{Name: "Score", Value: strconv.Itoa(out.Score), Inline: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Embeds: []*discordgo.MessageEmbed{embed},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/fun/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RpsLeaderboard = &discordgo.ApplicationCommand{
|
||||||
|
Name: "rpsleaderboard",
|
||||||
|
Description: "Shows the RPS leaderboard",
|
||||||
|
Contexts: &[]discordgo.InteractionContextType{
|
||||||
|
discordgo.InteractionContextGuild,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RpsLeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if i.GuildID == "" {
|
||||||
|
shared.Respond(s, i, "This command can only be used in a server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if services.Global == nil || services.Global.RPS == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "RPS service is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid guild.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
top, err := services.Global.RPS.TopScores(context.Background(), guildID, 10)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to load leaderboard.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(top) == 0 {
|
||||||
|
shared.RespondEphemeral(s, i, "No leaderboard data yet.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make([]*discordgo.MessageEmbedField, 0, len(top))
|
||||||
|
for idx, entry := range top {
|
||||||
|
display := fmt.Sprintf("<@%d>", entry.UserID)
|
||||||
|
if u, err := s.User(strconv.FormatInt(entry.UserID, 10)); err == nil && u != nil && u.Username != "" {
|
||||||
|
display = u.Username
|
||||||
|
}
|
||||||
|
fields = append(fields, &discordgo.MessageEmbedField{
|
||||||
|
Name: fmt.Sprintf("%d. %s", idx+1, display),
|
||||||
|
Value: fmt.Sprintf("Score: **%d**", entry.Score),
|
||||||
|
Inline: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: "RPS Leaderboard",
|
||||||
|
Description: fmt.Sprintf("Top %d players in this server", len(top)),
|
||||||
|
Color: 0xA855F7,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Embeds: []*discordgo.MessageEmbed{embed},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/fun/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RpsStats = &discordgo.ApplicationCommand{
|
||||||
|
Name: "rpsstats",
|
||||||
|
Description: "Shows your RPS stats",
|
||||||
|
Contexts: &[]discordgo.InteractionContextType{
|
||||||
|
discordgo.InteractionContextGuild,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RpsStatsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if i.GuildID == "" {
|
||||||
|
shared.Respond(s, i, "This command can only be used in a server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if services.Global == nil || services.Global.RPS == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "RPS service is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid guild.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := i.User
|
||||||
|
if i.Member != nil && i.Member.User != nil {
|
||||||
|
user = i.Member.User
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(user.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
score, ok, err := services.Global.RPS.GetScore(context.Background(), guildID, userID)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to load stats.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "You haven't played RPS yet!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: "RPS Stats",
|
||||||
|
Description: user.Mention(),
|
||||||
|
Color: 0x3B82F6,
|
||||||
|
Fields: []*discordgo.MessageEmbedField{
|
||||||
|
{Name: "Score", Value: strconv.Itoa(score), Inline: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Embeds: []*discordgo.MessageEmbed{embed},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -12,6 +12,9 @@ var (
|
|||||||
Joke *discordgo.ApplicationCommand = public.Joke
|
Joke *discordgo.ApplicationCommand = public.Joke
|
||||||
Coinflip *discordgo.ApplicationCommand = public.Coinflip
|
Coinflip *discordgo.ApplicationCommand = public.Coinflip
|
||||||
Dice *discordgo.ApplicationCommand = public.Dice
|
Dice *discordgo.ApplicationCommand = public.Dice
|
||||||
|
Rps *discordgo.ApplicationCommand = public.Rps
|
||||||
|
RpsStats *discordgo.ApplicationCommand = public.RpsStats
|
||||||
|
RpsLeaderboard *discordgo.ApplicationCommand = public.RpsLeaderboard
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
@@ -21,3 +24,10 @@ func CoinflipHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
public.CoinflipHandler(s, i)
|
public.CoinflipHandler(s, i)
|
||||||
}
|
}
|
||||||
func DiceHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.DiceHandler(s, i) }
|
func DiceHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.DiceHandler(s, i) }
|
||||||
|
func RpsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.RpsHandler(s, i) }
|
||||||
|
func RpsStatsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
public.RpsStatsHandler(s, i)
|
||||||
|
}
|
||||||
|
func RpsLeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
public.RpsLeaderboardHandler(s, i)
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,12 +47,36 @@ func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
Fields: []*discordgo.MessageEmbedField{
|
Fields: []*discordgo.MessageEmbedField{
|
||||||
{
|
{
|
||||||
Name: "Fun Commands",
|
Name: "Fun Commands",
|
||||||
Value: "Here are the fun commands for the bot.",
|
Value: "Small utility + games.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "/ping",
|
Name: "/ping",
|
||||||
Value: "Pong!",
|
Value: "Pong!",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "/joke [type]",
|
||||||
|
Value: "Get a random joke (optional category).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/coinflip",
|
||||||
|
Value: "Flip a coin.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/dice <expr>",
|
||||||
|
Value: "Roll dice expressions (e.g. `d20`, `2d20+1`, `2#d20+1`).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/rps <hand>",
|
||||||
|
Value: "Play Rock Paper Scissors (server only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/rpsstats",
|
||||||
|
Value: "Show your RPS score (server only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/rpsleaderboard",
|
||||||
|
Value: "Show top RPS scores (server only).",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "/help",
|
Name: "/help",
|
||||||
Value: "Get help with the bot.",
|
Value: "Get help with the bot.",
|
||||||
@@ -69,6 +93,30 @@ func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
Name: "/slvl set",
|
Name: "/slvl set",
|
||||||
Value: "Set the level of a user.",
|
Value: "Set the level of a user.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "/slvl set-channel",
|
||||||
|
Value: "Set the channel for level-up messages.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/slvl set-message",
|
||||||
|
Value: "Set the custom level-up message.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/slvl set-reward",
|
||||||
|
Value: "Set a role reward for a level.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/rank [user]",
|
||||||
|
Value: "Show a rank card (server only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/leaderboard",
|
||||||
|
Value: "Show top levels (server only).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/rewards",
|
||||||
|
Value: "List configured level rewards (server only).",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Author: &author,
|
Author: &author,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
import (
|
||||||
"velox-bot/internal/db/services/level"
|
"velox-bot/internal/db/services/level"
|
||||||
"velox-bot/internal/db/services/levelsettings"
|
"velox-bot/internal/db/services/levelsettings"
|
||||||
|
"velox-bot/internal/db/services/rps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
Level *level.Service
|
Level *level.Service
|
||||||
LevelSettings *levelsettings.Service
|
LevelSettings *levelsettings.Service
|
||||||
|
RPS *rps.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
var Global *Services
|
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{
|
s := &Services{
|
||||||
Level: level,
|
Level: level,
|
||||||
LevelSettings: levelSettings,
|
LevelSettings: levelSettings,
|
||||||
|
RPS: rps,
|
||||||
}
|
}
|
||||||
Global = s
|
Global = s
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import (
|
|||||||
"velox-bot/internal/config"
|
"velox-bot/internal/config"
|
||||||
"velox-bot/internal/db"
|
"velox-bot/internal/db"
|
||||||
"velox-bot/internal/db/repos/levelrepo"
|
"velox-bot/internal/db/repos/levelrepo"
|
||||||
|
"velox-bot/internal/db/repos/rpsrepo"
|
||||||
"velox-bot/internal/db/repos/settingsrepo"
|
"velox-bot/internal/db/repos/settingsrepo"
|
||||||
"velox-bot/internal/db/services"
|
"velox-bot/internal/db/services"
|
||||||
"velox-bot/internal/db/services/level"
|
"velox-bot/internal/db/services/level"
|
||||||
"velox-bot/internal/db/services/levelsettings"
|
"velox-bot/internal/db/services/levelsettings"
|
||||||
|
"velox-bot/internal/db/services/rps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -33,9 +35,11 @@ func main() {
|
|||||||
|
|
||||||
levelRepo := levelrepo.NewRepo(db)
|
levelRepo := levelrepo.NewRepo(db)
|
||||||
settingsRepo := settingsrepo.NewRepo(db)
|
settingsRepo := settingsrepo.NewRepo(db)
|
||||||
|
rpsRepo := rpsrepo.NewRepo(db)
|
||||||
levelService := level.New(levelRepo, settingsRepo)
|
levelService := level.New(levelRepo, settingsRepo)
|
||||||
levelSettingsService := levelsettings.New(settingsRepo)
|
levelSettingsService := levelsettings.New(settingsRepo)
|
||||||
services := services.NewServices(levelService, levelSettingsService)
|
rpsService := rps.New(rpsRepo)
|
||||||
|
services := services.NewServices(levelService, levelSettingsService, rpsService)
|
||||||
|
|
||||||
bot, err := bot.NewBot(config.BotToken, config.AppID, config.GuildID, commands.AllCommands, services)
|
bot, err := bot.NewBot(config.BotToken, config.AppID, config.GuildID, commands.AllCommands, services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user