codec.gno
4.13 Kb · 124 lines
1package golf
2
3import (
4 "strconv"
5 "strings"
6)
7
8// How golf's rows are kept in store: strings, never golf's own types (a value
9// of a realm's type kept in another realm would carry this realm's methods into
10// it, and would not outlive these rules). Each is its format's version, then
11// its fields, each as its length, ":" and itself (store's own rows are so): a
12// row reads back by its lengths, not by a scan of every byte (a scan, as
13// strings.Split, costs several times more gas), whatever its fields hold. A
14// later version of the rules reads every format ever written, row by row.
15const format = "1"
16
17func join(fs ...string) string {
18 var sb strings.Builder
19 sb.WriteString("1:" + format)
20 for _, f := range fs {
21 sb.WriteString(strconv.Itoa(len(f)) + ":" + f)
22 }
23 return sb.String()
24}
25
26// fields is a row's fields, its format checked: n of them, or it panics.
27func fields(s, what string, n int) []string {
28 f := make([]string, 0, n+1)
29 for rest := s; len(rest) > 0; {
30 i, l := 0, 0
31 for i < len(rest) && i < 6 && rest[i] >= '0' && rest[i] <= '9' {
32 l = l*10 + int(rest[i]-'0')
33 i++
34 }
35 if i == 0 || i == len(rest) || rest[i] != ':' || len(rest)-i-1 < l || len(f) > n {
36 panic("golf: a " + what + " not as golf keeps it: " + strconv.Quote(s))
37 }
38 f = append(f, rest[i+1:i+1+l])
39 rest = rest[i+1+l:]
40 }
41 if len(f) != n+1 || f[0] != format {
42 panic("golf: a " + what + " not as golf keeps it: " + strconv.Quote(s))
43 }
44 return f[1:]
45}
46
47func itoa(n int) string { return strconv.Itoa(n) }
48func i64(n int64) string { return strconv.FormatInt(n, 10) }
49func ftoa(x float64) string { return strconv.FormatFloat(x, 'g', -1, 64) } // (exact: it reads back to the same float)
50func btoa(b bool) string {
51 if b {
52 return "1"
53 }
54 return "0"
55}
56
57func atoi(s string) int {
58 n, err := strconv.Atoi(s)
59 if err != nil {
60 panic("golf: a number not as golf keeps it: " + strconv.Quote(s))
61 }
62 return n
63}
64
65func atoi64(s string) int64 {
66 n, err := strconv.ParseInt(s, 10, 64)
67 if err != nil {
68 panic("golf: a number not as golf keeps it: " + strconv.Quote(s))
69 }
70 return n
71}
72
73func atof(s string) float64 {
74 x, err := strconv.ParseFloat(s, 64)
75 if err != nil {
76 panic("golf: a number not as golf keeps it: " + strconv.Quote(s))
77 }
78 return x
79}
80
81// --- a version of a hole -------------------------------------------------------
82
83func encodeEntry(e *entry) string {
84 out := []string{"", "", "", "", ""} // never archived: no drain
85 if e.out != nil {
86 out = []string{"1", e.out.from[assisted], e.out.from[pro], btoa(e.out.done[assisted]), btoa(e.out.done[pro])}
87 }
88 return join(append([]string{e.id, e.name, itoa(e.par), e.world, ftoa(e.order), btoa(e.official),
89 e.slot, itoa(e.version), e.by.String(), i64(e.height), e.sha, e.note,
90 itoa(e.plays), itoa(e.best), itoa(e.proBest), e.bestBy.String(), e.proBestBy.String(), e.next}, out...)...)
91}
92
93func decodeEntry(s string) *entry {
94 f := fields(s, "hole", 23)
95 e := &entry{id: f[0], name: f[1], par: atoi(f[2]), world: f[3], order: atof(f[4]), official: f[5] == "1",
96 slot: f[6], version: atoi(f[7]), by: address(f[8]), height: atoi64(f[9]), sha: f[10], note: f[11],
97 plays: atoi(f[12]), best: atoi(f[13]), proBest: atoi(f[14]), bestBy: address(f[15]), proBestBy: address(f[16]), next: f[17]}
98 if f[18] == "1" {
99 e.out = &draining{from: [modes]string{f[19], f[20]}, done: [modes]bool{f[21] == "1", f[22] == "1"}}
100 }
101 return e
102}
103
104// --- a round under way ---------------------------------------------------------
105
106func encodeRound(r *round) string {
107 return join(ftoa(r.bx), ftoa(r.by), ftoa(r.fx), ftoa(r.fy), itoa(r.strokes), i64(r.period), r.shots, itoa(r.mode))
108}
109
110func decodeRound(s string) *round {
111 f := fields(s, "round", 8)
112 return &round{bx: atof(f[0]), by: atof(f[1]), fx: atof(f[2]), fy: atof(f[3]), strokes: atoi(f[4]), period: atoi64(f[5]), shots: f[6], mode: atoi(f[7])}
113}
114
115// --- a course standing ---------------------------------------------------------
116
117func encodeRow(r *row) string {
118 return join(r.player, itoa(r.holes), itoa(r.strokes), itoa(r.par), i64(r.height))
119}
120
121func decodeRow(s string) *row {
122 f := fields(s, "standing", 5)
123 return &row{player: f[0], holes: atoi(f[1]), strokes: atoi(f[2]), par: atoi(f[3]), height: atoi64(f[4])}
124}