results.gno
3.05 Kb · 124 lines
1package arena
2
3import (
4 "strings"
5
6 "gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0"
7)
8
9// result is how a game ended, kept for readers: no payment is ever computed
10// from it.
11type result struct {
12 mode string
13 stake int64
14 outcome string // settled, cancelled or refunded
15 players int64 // how many played
16 pot int64 // their stakes
17 fee int64
18 closedAt int64 // block time, Unix seconds
19 payouts []payout.Payout // who was paid what, in the order of the players
20}
21
22const (
23 outcomeSettled = "settled"
24 outcomeCancelled = "cancelled"
25 outcomeRefunded = "refunded"
26)
27
28// maxResults is how many closed games the realm remembers.
29const maxResults = 50
30
31// A result is stored as one string, its fields separated by "|", the payouts
32// last as "address:amount" separated by ",":
33//
34// mode|stake|outcome|players|pot|fee|closedAt|g1...:19500000,g1...:500000
35func (r result) encode() string {
36 var b strings.Builder
37 b.WriteString(r.mode)
38 for _, n := range []string{itoa(r.stake), r.outcome, itoa(r.players), itoa(r.pot), itoa(r.fee), itoa(r.closedAt)} {
39 b.WriteByte('|')
40 b.WriteString(n)
41 }
42 b.WriteByte('|')
43 b.WriteString(joinPayouts(r.payouts))
44 return b.String()
45}
46
47func decodeResult(s string) result {
48 f := strings.Split(s, "|")
49 if len(f) != 8 {
50 panic(errCorruptResult)
51 }
52 num := func(i int) int64 {
53 n, ok := digits(f[i])
54 if !ok {
55 panic(errCorruptResult)
56 }
57 return n
58 }
59 r := result{mode: f[0], stake: num(1), outcome: f[2], players: num(3), pot: num(4), fee: num(5), closedAt: num(6)}
60 if r.outcome != outcomeSettled && r.outcome != outcomeCancelled && r.outcome != outcomeRefunded {
61 panic(errCorruptResult)
62 }
63 if f[7] == "" {
64 return r
65 }
66 for _, entry := range strings.Split(f[7], ",") {
67 colon := indexFrom(entry, 0, ':')
68 if colon == 0 || colon == len(entry) {
69 panic(errCorruptResult)
70 }
71 amount, ok := digits(entry[colon+1:])
72 if !ok {
73 panic(errCorruptResult)
74 }
75 r.payouts = append(r.payouts, payout.Payout{Address: address(entry[:colon]), Amount: amount})
76 }
77 return r
78}
79
80// record keeps the result of a closed game, and forgets the oldest one past
81// maxResults.
82func record(id string, r result) {
83 results.Set(id, r.encode())
84 closed = append(closed, id)
85 if len(closed) > maxResults {
86 results.Remove(closed[0])
87 next := make([]string, len(closed)-1)
88 copy(next, closed[1:])
89 closed = next
90 }
91}
92
93// resultOf returns the result of a closed game, if the realm still has it.
94func resultOf(id string) (result, bool) {
95 v := results.Get(id)
96 if v == nil {
97 return result{}, false
98 }
99 return decodeResult(v.(string)), true
100}
101
102// indexFrom returns the index of the first c in s at or after i, or len(s).
103func indexFrom(s string, i int, c byte) int {
104 for i < len(s) && s[i] != c {
105 i++
106 }
107 return i
108}
109
110// digits parses a non-negative decimal of at most 18 digits.
111func digits(s string) (int64, bool) {
112 if len(s) == 0 || len(s) > 18 {
113 return 0, false
114 }
115 n := int64(0)
116 for i := 0; i < len(s); i++ {
117 c := s[i]
118 if c < '0' || c > '9' {
119 return 0, false
120 }
121 n = n*10 + int64(c-'0')
122 }
123 return n, true
124}