@@ -2,9 +2,14 @@ package secretsanta
2
2
3
3
import (
4
4
"cake4everybot/util"
5
+ "encoding/json"
6
+ "fmt"
5
7
logger "log"
8
+ "os"
6
9
7
10
"github.com/bwmarrin/discordgo"
11
+ "github.com/spf13/viper"
12
+ "golang.org/x/exp/rand"
8
13
)
9
14
10
15
const (
@@ -20,3 +25,150 @@ type secretSantaBase struct {
20
25
member * discordgo.Member
21
26
user * discordgo.User
22
27
}
28
+
29
+ // getPlayers returns the list of players for the current guild. If it is the first time, it loads
30
+ // the players from the file or creates an empty file.
31
+ func (ssb secretSantaBase ) getPlayers () ([]* player , error ) {
32
+ if allPlayers != nil {
33
+ return allPlayers [ssb .Interaction .GuildID ], nil
34
+ }
35
+
36
+ log .Println ("First time getting players. Loading from file..." )
37
+ playersPath := viper .GetString ("event.secretsanta.players" )
38
+ playersData , err := os .ReadFile (playersPath )
39
+ if err != nil {
40
+ if ! os .IsNotExist (err ) {
41
+ return nil , fmt .Errorf ("read players file: %v" , err )
42
+ }
43
+ allPlayers = make (AllPlayers )
44
+ playersData , err = json .Marshal (allPlayers )
45
+ if err != nil {
46
+ return nil , fmt .Errorf ("marshal players file: %v" , err )
47
+ }
48
+ err = os .WriteFile (playersPath , playersData , 0644 )
49
+ if err != nil {
50
+ return nil , fmt .Errorf ("write players file: %v" , err )
51
+ }
52
+ log .Printf ("Created players file: %s\n " , playersPath )
53
+ return []* player {}, nil
54
+ }
55
+ allPlayersUnresolved := AllPlayersUnresolved {}
56
+ err = json .Unmarshal (playersData , & allPlayersUnresolved )
57
+ if err != nil {
58
+ return nil , fmt .Errorf ("unmarshal players file: %v" , err )
59
+ }
60
+ err = allPlayersUnresolved .Resolve (ssb .Session )
61
+ if err != nil {
62
+ return nil , fmt .Errorf ("resolve players file: %v" , err )
63
+ }
64
+ log .Printf ("Got %d guilds from file" , len (allPlayers ))
65
+
66
+ return allPlayers [ssb .Interaction .GuildID ], nil
67
+ }
68
+
69
+ // setPlayers sets the players for the current guild.
70
+ func (ssb secretSantaBase ) setPlayers (players []* player ) (err error ) {
71
+ if _ , err = ssb .getPlayers (); err != nil {
72
+ return err
73
+ }
74
+
75
+ allPlayers [ssb .Interaction .GuildID ] = players
76
+ playersData , err := json .Marshal (allPlayers )
77
+ if err != nil {
78
+ return fmt .Errorf ("marshal players file: %v" , err )
79
+ }
80
+ err = os .WriteFile (viper .GetString ("event.secretsanta.players" ), playersData , 0644 )
81
+ if err != nil {
82
+ return fmt .Errorf ("write players file: %v" , err )
83
+ }
84
+ return nil
85
+ }
86
+
87
+ // player is a player in the secret santa game
88
+ type player struct {
89
+ * discordgo.Member
90
+
91
+ // Match is the matched player
92
+ Match * player
93
+ // Address is the address of the player
94
+ Address string
95
+ }
96
+
97
+ type playerUnresolved struct {
98
+ ID string `json:"id"`
99
+ MatchID string `json:"match"`
100
+ Address string `json:"address"`
101
+ }
102
+
103
+ // AllPlayers is a map from guild ID to a list of players
104
+ type AllPlayers map [string ][]* player
105
+
106
+ // allPlayers is the current state of all players.
107
+ // See [AllPlayers]
108
+ var allPlayers AllPlayers
109
+
110
+ // MarshalJSON implements json.Marshaler
111
+ func (allPlayers AllPlayers ) MarshalJSON () ([]byte , error ) {
112
+ m := make (AllPlayersUnresolved )
113
+ for guildID , players := range allPlayers {
114
+ for _ , player := range players {
115
+ m [guildID ] = append (m [guildID ], & playerUnresolved {
116
+ ID : player .User .ID ,
117
+ MatchID : player .Match .User .ID ,
118
+ Address : player .Address ,
119
+ })
120
+ }
121
+
122
+ }
123
+ return json .Marshal (m )
124
+ }
125
+
126
+ // AllPlayersUnresolved is a map from guild ID to a list of unresolved players.
127
+ // Unresolved players have no member but only an ID
128
+ type AllPlayersUnresolved map [string ][]* playerUnresolved
129
+
130
+ // Resolve resolves allPlayersUnresolved into allPlayers
131
+ func (allPlayersUnresolved AllPlayersUnresolved ) Resolve (s * discordgo.Session ) (err error ) {
132
+ allPlayers = make (AllPlayers )
133
+ for guildID , unresolvedPlayers := range allPlayersUnresolved {
134
+ resolvedPlayers := map [string ]* player {}
135
+ for _ , up := range unresolvedPlayers {
136
+ member , err := s .GuildMember (guildID , up .ID )
137
+ if err != nil {
138
+ return fmt .Errorf ("failed to get guild member %s/%s: %v" , guildID , up .ID , err )
139
+ }
140
+ resolvedPlayers [up .ID ] = & player {
141
+ Member : member ,
142
+ Match : resolvedPlayers [up .MatchID ],
143
+ Address : up .Address ,
144
+ }
145
+ }
146
+ for _ , rp := range resolvedPlayers {
147
+ if rp .Match != nil {
148
+ continue
149
+ }
150
+ rp .Match = resolvedPlayers [rp .User .ID ]
151
+
152
+ allPlayers [guildID ] = append (allPlayers [guildID ], rp )
153
+ }
154
+ }
155
+ return nil
156
+ }
157
+
158
+ // DerangementMatch matches the players in a way that no one gets matched to themselves.
159
+ func DerangementMatch (players []* player ) []* player {
160
+ n := len (players )
161
+ players2 := make ([]* player , n )
162
+ copy (players2 , players )
163
+
164
+ for i := 0 ; i < n - 1 ; i ++ {
165
+ j := i + rand .Intn (n - i - 1 ) + 1
166
+ players2 [i ], players2 [j ] = players2 [j ], players2 [i ]
167
+ }
168
+
169
+ for i , item := range players {
170
+ item .Match = players2 [i ]
171
+ }
172
+
173
+ return players
174
+ }
0 commit comments