Skip to content

Commit

Permalink
added player lists for secret santa
Browse files Browse the repository at this point in the history
  • Loading branch information
Kesuaheli committed Nov 16, 2024
1 parent ec9dc87 commit 9fe49d1
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ cake4everybot

# configuration files for tokens, access keys and other connection data
*env.yaml

# runtime config files (as described in config.yml)
modules/secretsanta/players.json
twitch/prizes.json
twitch/times.json
4 changes: 4 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ event:
adventcalendar:
images: data/images/adventcalendar

secretsanta:
# the filepath for the players
players: modules/secretsanta/players.json

twitch_giveaway:
# The amount of points a single giveaway ticket costs.
ticket_cost: 1000
Expand Down
1 change: 1 addition & 0 deletions data/lang/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ discord.command:

msg.setup.no_reactions: Diese Nachricht hat keine Reaktionen. Nur Leute, die mit %s reagiert haben, werden eingeschlossen.
msg.setup.not_enough_reactions: Nicht genug Reaktionen um zu starten. Es werden mindestens %d Reaktionen benötigt.
msg.setup.users: Teilnehmer

module:
adventcalendar:
Expand Down
1 change: 1 addition & 0 deletions data/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ discord.command:

msg.setup.no_reactions: This message doesn't have any vote reactions. Only members who reated with %s are included.
msg.setup.not_enough_reactions: Not enough votes to start a game. At least %d votes are required.
msg.setup.users: Members

module:
adventcalendar:
Expand Down
38 changes: 38 additions & 0 deletions modules/secretsanta/handlerMessageSetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package secretsanta
import (
"cake4everybot/data/lang"
"cake4everybot/util"
"fmt"

"github.com/bwmarrin/discordgo"
)

func (cmd MsgCmd) handler() {
Expand Down Expand Up @@ -43,4 +45,40 @@ func (cmd MsgCmd) handler() {
cmd.ReplyHiddenf(lang.GetDefault(tp+"msg.setup.not_enough_reactions"), 2)
return
}

e := &discordgo.MessageEmbed{
Title: lang.GetDefault(tp + "title"),
Color: 0x690042,
}

var (
names string
players []*player = make([]*player, 0, len(users))
)
for _, u := range users {
member, err := cmd.Session.GuildMember(cmd.Interaction.GuildID, u.ID)
if member == nil {
log.Printf("WARN: Could not get member '%s' from guild '%s': %v", u.ID, cmd.Interaction.GuildID, err)
continue
}
players = append(players, &player{Member: member})
names += fmt.Sprintf("%s\n", member.Mention())
}
if len(players) < 2 {
cmd.ReplyHiddenf(lang.GetDefault(tp+"msg.setup.not_enough_reactions"), 2)
return
}
util.AddEmbedField(e, lang.GetDefault(tp+"msg.setup.users"), names, false)

players = DerangementMatch(players)

err = cmd.setPlayers(players)
if err != nil {
log.Printf("Error on set players: %v\n", err)
cmd.ReplyError()
return
}

util.SetEmbedFooter(cmd.Session, tp+"display", e)
cmd.ReplyHiddenEmbed(e)
}
152 changes: 152 additions & 0 deletions modules/secretsanta/secretsantabase.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package secretsanta

import (
"cake4everybot/util"
"encoding/json"
"fmt"
logger "log"
"os"

"github.com/bwmarrin/discordgo"
"github.com/spf13/viper"
"golang.org/x/exp/rand"
)

const (
Expand All @@ -20,3 +25,150 @@ type secretSantaBase struct {
member *discordgo.Member
user *discordgo.User
}

// getPlayers returns the list of players for the current guild. If it is the first time, it loads
// the players from the file or creates an empty file.
func (ssb secretSantaBase) getPlayers() ([]*player, error) {
if allPlayers != nil {
return allPlayers[ssb.Interaction.GuildID], nil
}

log.Println("First time getting players. Loading from file...")
playersPath := viper.GetString("event.secretsanta.players")
playersData, err := os.ReadFile(playersPath)
if err != nil {
if !os.IsNotExist(err) {
return nil, fmt.Errorf("read players file: %v\n", err)

Check failure on line 41 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
allPlayers = make(AllPlayers)
playersData, err = json.Marshal(allPlayers)
if err != nil {
return nil, fmt.Errorf("marshal players file: %v\n", err)

Check failure on line 46 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
err = os.WriteFile(playersPath, playersData, 0644)
if err != nil {
return nil, fmt.Errorf("write players file: %v\n", err)

Check failure on line 50 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
log.Printf("Created players file: %s\n", playersPath)
return []*player{}, nil
}
allPlayersUnresolved := AllPlayersUnresolved{}
err = json.Unmarshal(playersData, &allPlayersUnresolved)
if err != nil {
return nil, fmt.Errorf("unmarshal players file: %v\n", err)

Check failure on line 58 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
err = allPlayersUnresolved.Resolve(ssb.Session)
if err != nil {
return nil, fmt.Errorf("resolve players file: %v\n", err)

Check failure on line 62 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
log.Printf("Got %d guilds from file", len(allPlayers))

return allPlayers[ssb.Interaction.GuildID], nil
}

// setPlayers sets the players for the current guild.
func (ssb secretSantaBase) setPlayers(players []*player) (err error) {
if _, err = ssb.getPlayers(); err != nil {
return err
}

allPlayers[ssb.Interaction.GuildID] = players
playersData, err := json.Marshal(allPlayers)
if err != nil {
return fmt.Errorf("marshal players file: %v\n", err)

Check failure on line 78 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
err = os.WriteFile(viper.GetString("event.secretsanta.players"), playersData, 0644)
if err != nil {
return fmt.Errorf("write players file: %v\n", err)

Check failure on line 82 in modules/secretsanta/secretsantabase.go

View workflow job for this annotation

GitHub Actions / Test

error strings should not end with punctuation or newlines (ST1005)
}
return nil
}

// player is a player in the secret santa game
type player struct {
*discordgo.Member

// Match is the matched player
Match *player
// Address is the address of the player
Address string
}

type playerUnresolved struct {
ID string `json:"id"`
MatchID string `json:"match"`
Address string `json:"address"`
}

// AllPlayers is a map from guild ID to a list of players
type AllPlayers map[string][]*player

// allPlayers is the current state of all players.
// See [AllPlayers]
var allPlayers AllPlayers

// MarshalJSON implements json.Marshaler
func (allPlayers AllPlayers) MarshalJSON() ([]byte, error) {
m := make(AllPlayersUnresolved)
for guildID, players := range allPlayers {
for _, player := range players {
m[guildID] = append(m[guildID], &playerUnresolved{
ID: player.User.ID,
MatchID: player.Match.User.ID,
Address: player.Address,
})
}

}
return json.Marshal(m)
}

// AllPlayersUnresolved is a map from guild ID to a list of unresolved players.
// Unresolved players have no member but only an ID
type AllPlayersUnresolved map[string][]*playerUnresolved

// Resolve resolves allPlayersUnresolved into allPlayers
func (allPlayersUnresolved AllPlayersUnresolved) Resolve(s *discordgo.Session) (err error) {
allPlayers = make(AllPlayers)
for guildID, unresolvedPlayers := range allPlayersUnresolved {
resolvedPlayers := map[string]*player{}
for _, up := range unresolvedPlayers {
member, err := s.GuildMember(guildID, up.ID)
if err != nil {
return fmt.Errorf("failed to get guild member %s/%s: %v", guildID, up.ID, err)
}
resolvedPlayers[up.ID] = &player{
Member: member,
Match: resolvedPlayers[up.MatchID],
Address: up.Address,
}
}
for _, rp := range resolvedPlayers {
if rp.Match != nil {
continue
}
rp.Match = resolvedPlayers[rp.User.ID]

allPlayers[guildID] = append(allPlayers[guildID], rp)
}
}
return nil
}

// DerangementMatch matches the players in a way that no one gets matched to themselves.
func DerangementMatch(players []*player) []*player {
n := len(players)
players2 := make([]*player, n)
copy(players2, players)

for i := 0; i < n-1; i++ {
j := i + rand.Intn(n-i-1) + 1
players2[i], players2[j] = players2[j], players2[i]
}

for i, item := range players {
item.Match = players2[i]
}

return players
}

0 comments on commit 9fe49d1

Please sign in to comment.