api.gno
5.48 Kb · 194 lines
1package gnodice
2
3import (
4 "strconv"
5 "strings"
6)
7
8// Lectures : ces fonctions ne modifient rien. Le site et le croupier les
9// appellent gratuitement (sans transaction) pour lire l'état du jeu en JSON.
10// Les seules chaînes écrites sont des adresses, des empreintes/graines
11// hexadécimales (vérifiées) et des statuts fixes : rien à échapper.
12
13// GetInfoJSON renvoie l'état global du jeu et les dernières parties terminées.
14func GetInfoJSON() string {
15 t := now()
16 var b strings.Builder
17 b.WriteString("{")
18 str(&b, "owner", owner.String())
19 str(&b, "pendingOwner", pendingOwner.String())
20 str(&b, "croupier", croupier.String())
21 str(&b, "realm", realmAddr.String())
22 boolean(&b, "paused", paused)
23 num(&b, "bankroll", Bankroll())
24 num(&b, "reserved", reserved)
25 num(&b, "available", available())
26 num(&b, "maxCoverableBet", MaxCoverableBet())
27 num(&b, "minBet", MinBet)
28 num(&b, "maxBet", MaxBet)
29 num(&b, "multiplier", PayoutMultiplier)
30 num(&b, "cooldown", CooldownSeconds)
31 num(&b, "historySize", HistorySize)
32 num(&b, "resolveTimeout", ResolveTimeout)
33 num(&b, "revealWindow", RevealWindow)
34 num(&b, "totalGames", totalGames)
35 num(&b, "totalWins", int64(totalWins))
36 num(&b, "totalLosses", int64(totalLosses))
37 num(&b, "totalRefunded", int64(totalRefunded))
38 num(&b, "totalExpired", int64(totalExpired))
39 num(&b, "totalWagered", totalWagered)
40 num(&b, "totalPaid", totalPaid)
41 num(&b, "totalFunded", totalFunded)
42 num(&b, "totalWithdrawn", totalWithdrawn)
43 num(&b, "openGames", int64(openGames.Size()))
44 num(&b, "dailyPayoutLimit", dailyPayoutLimit)
45 todayPaid := int64(0)
46 if t/86400 == payoutDay {
47 todayPaid = payoutToday
48 }
49 num(&b, "payoutToday", todayPaid)
50 num(&b, "lowBankroll", lowBankroll)
51 num(&b, "now", t)
52 b.WriteString(`"recent":`)
53 writeGameIDs(&b, recent)
54 b.WriteString("}")
55 return b.String()
56}
57
58// GetPlayerJSON renvoie les statistiques d'un joueur, son temps d'attente,
59// ses parties en cours et ses 10 dernières parties terminées.
60func GetPlayerJSON(addr string) string {
61 player := address(addr)
62 if !player.IsValid() {
63 return `{"error":"adresse invalide"}`
64 }
65 p := getPlayer(player)
66 if p == nil {
67 p = &playerData{}
68 }
69 t := now()
70 nextPlayAt := int64(0)
71 if p.LastPlay > 0 {
72 nextPlayAt = p.LastPlay + CooldownSeconds
73 }
74
75 var b strings.Builder
76 b.WriteString("{")
77 str(&b, "address", player.String())
78 num(&b, "played", int64(p.Played))
79 num(&b, "wins", int64(p.Wins))
80 num(&b, "wagered", p.Wagered)
81 num(&b, "paid", p.Paid)
82 num(&b, "lastPlay", p.LastPlay)
83 num(&b, "nextPlayAt", nextPlayAt)
84 num(&b, "cooldownRemaining", cooldownRemaining(p, t))
85 boolean(&b, "blocked", isBlocked(player))
86 num(&b, "now", t)
87 b.WriteString(`"open":`)
88 writePlayerOpenGames(&b, player, p.Open)
89 b.WriteString(`,"history":`)
90 writeGameIDs(&b, p.History)
91 b.WriteString("}")
92 return b.String()
93}
94
95// GetGameJSON renvoie une partie, ou {"error":...} si elle n'existe pas.
96func GetGameJSON(id int64) string {
97 g := getGame(id)
98 if g == nil {
99 return `{"error":"partie introuvable"}`
100 }
101 var b strings.Builder
102 writeGame(&b, g)
103 return b.String()
104}
105
106// GetOpenGamesJSON renvoie les parties en cours (en attente de tirage ou de
107// révélation), de la plus ancienne à la plus récente, à partir du numéro
108// `after` exclu (pagination ; 0 pour commencer). Utilisé par le croupier.
109func GetOpenGamesJSON(after int64) string {
110 var b strings.Builder
111 b.WriteString("[")
112 count := 0
113 openGames.Iterate(gameKey(after+1), "", func(key string, _ any) bool {
114 v := games.Get(key)
115 if count > 0 {
116 b.WriteString(",")
117 }
118 writeGame(&b, v.(*Game))
119 count++
120 return count >= maxPageSize
121 })
122 b.WriteString("]")
123 return b.String()
124}
125
126// --- écriture JSON ---
127
128func writePlayerOpenGames(b *strings.Builder, player address, open int) {
129 b.WriteString("[")
130 if open > 0 {
131 count := 0
132 // Les parties en cours sont peu nombreuses (cooldown de 10 min) :
133 // on parcourt la liste des parties ouvertes en partant de la plus récente.
134 openGames.ReverseIterate("", "", func(key string, _ any) bool {
135 v := games.Get(key)
136 g := v.(*Game)
137 if g.Player == player {
138 if count > 0 {
139 b.WriteString(",")
140 }
141 writeGame(b, g)
142 count++
143 }
144 return count >= open || count >= maxPageSize
145 })
146 }
147 b.WriteString("]")
148}
149
150// writeGameIDs écrit les parties de la plus récente à la plus ancienne.
151func writeGameIDs(b *strings.Builder, ids []int64) {
152 b.WriteString("[")
153 for i := len(ids) - 1; i >= 0; i-- {
154 if i != len(ids)-1 {
155 b.WriteString(",")
156 }
157 writeGame(b, mustGetGame(ids[i]))
158 }
159 b.WriteString("]")
160}
161
162func writeGame(b *strings.Builder, g *Game) {
163 b.WriteString("{")
164 num(b, "id", g.ID)
165 str(b, "player", g.Player.String())
166 str(b, "status", g.Status.String())
167 str(b, "commitment", g.Commitment)
168 str(b, "seed", g.Seed)
169 num(b, "guess", int64(g.Guess))
170 num(b, "roll", int64(g.Roll))
171 num(b, "bet", g.Bet)
172 num(b, "payout", g.Payout)
173 boolean(b, "won", g.Won())
174 num(b, "time", g.PlacedAt)
175 num(b, "height", g.PlacedHeight)
176 num(b, "rolledAt", g.RolledAt)
177 num(b, "settledAt", g.SettledAt)
178 b.WriteString(`"resolveDeadline":` + itoa64(g.PlacedAt+ResolveTimeout))
179 b.WriteString("}")
180}
181
182// Chaque champ est suivi d'une virgule ; le dernier champ d'un objet est
183// écrit à la main, sans virgule.
184func str(b *strings.Builder, key, value string) {
185 b.WriteString(`"` + key + `":"` + value + `",`)
186}
187
188func num(b *strings.Builder, key string, value int64) {
189 b.WriteString(`"` + key + `":` + strconv.FormatInt(value, 10) + `,`)
190}
191
192func boolean(b *strings.Builder, key string, value bool) {
193 b.WriteString(`"` + key + `":` + strconv.FormatBool(value) + `,`)
194}