json.gno
3.62 Kb · 144 lines
1package connect4
2
3import (
4 "strconv"
5 "strings"
6 "time"
7)
8
9// GameJSON returns {"now":<unix>,"game":<game>|null}. It is a read-only
10// accessor for clients querying over vm/qeval; now is the block time.
11func GameJSON(id int) string {
12 var b strings.Builder
13 writeNow(&b)
14 b.WriteString(`,"game":`)
15 if g := get(id); g != nil {
16 writeGame(&b, g)
17 } else {
18 b.WriteString("null")
19 }
20 b.WriteString("}")
21 return b.String()
22}
23
24// ActiveJSON returns {"now":<unix>,"fee":<ugnot>,"games":[...]}: the house fee
25// for new offers, then Open and Playing games in id order, skipping offset and
26// returning at most limit (clamped to 1-100).
27func ActiveJSON(offset, limit int) string {
28 if offset < 0 {
29 offset = 0
30 }
31 if limit < 1 {
32 limit = 1
33 }
34 if limit > 100 {
35 limit = 100
36 }
37 var b strings.Builder
38 writeNow(&b)
39 writeNum(&b, "fee", fee)
40 b.WriteString(`,"games":[`)
41 n := 0
42 active.IterateByOffset(offset, limit, func(_ string, v any) bool {
43 if n > 0 {
44 b.WriteString(",")
45 }
46 writeGame(&b, v.(*Game))
47 n++
48 return false
49 })
50 b.WriteString("]}")
51 return b.String()
52}
53
54func writeNow(b *strings.Builder) {
55 b.WriteString(`{"now":`)
56 b.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
57}
58
59// writeGame emits one game. Every stored string is validated on write
60// (addresses by IsValid, the commitment as hex), so Quote is defence in depth.
61func writeGame(b *strings.Builder, g *Game) {
62 turnPlayer := ""
63 deadline := int64(0)
64 if g.Status == Playing {
65 deadline = g.deadline().Unix()
66 if g.Turn != 0 {
67 turnPlayer = g.player(g.Turn).String()
68 }
69 }
70 b.WriteString(`{"id":`)
71 b.WriteString(strconv.Itoa(g.ID))
72 writeStr(b, "creator", g.Creator.String())
73 writeStr(b, "opponent", g.Opponent.String())
74 writeStr(b, "acceptor", g.Acceptor.String())
75 writeNum(b, "stake", g.Stake)
76 writeNum(b, "fee", g.Fee)
77 writeNum(b, "expiresAt", g.ExpiresAt.Unix())
78 writeStr(b, "commitment", g.Commitment)
79 writeStr(b, "seedCommitment", g.SeedCommitment)
80 // revealed: the creator's passphrase is out; with turn 0 the game waits for RevealSeed.
81 if g.Revealed {
82 b.WriteString(`,"revealed":true`)
83 } else {
84 b.WriteString(`,"revealed":false`)
85 }
86 b.WriteString(`,"board":"`)
87 for c := 0; c < cols; c++ {
88 for r := 0; r < rows; r++ {
89 b.WriteByte('0' + g.Board[c][r])
90 }
91 }
92 b.WriteString(`"`)
93 writeNum(b, "turn", int64(g.Turn))
94 writeStr(b, "turnPlayer", turnPlayer)
95 writeNum(b, "moves", int64(g.Moves))
96 writeNum(b, "lastCol", int64(g.LastCol))
97 writeNum(b, "lastRow", int64(g.LastRow))
98 writeNum(b, "deadline", deadline)
99 writeStr(b, "status", g.Status.String())
100 writeStr(b, "winner", g.Winner.String())
101 b.WriteString("}")
102}
103
104// writeStr and writeNum append `,"k":v`; the caller writes the first field.
105func writeStr(b *strings.Builder, k, v string) {
106 b.WriteString(`,"`)
107 b.WriteString(k)
108 b.WriteString(`":`)
109 b.WriteString(strconv.Quote(v))
110}
111
112func writeNum(b *strings.Builder, k string, v int64) {
113 b.WriteString(`,"`)
114 b.WriteString(k)
115 b.WriteString(`":`)
116 b.WriteString(strconv.FormatInt(v, 10))
117}
118
119// LeadersJSON returns {"wins":[...],"gnot":[...]}: the top-10 boards (most
120// games won, most ugnot won) kept at settlement, highest first. Each entry is
121// {"addr":<address>,"score":<int>}.
122func LeadersJSON() string {
123 var b strings.Builder
124 b.WriteString(`{"wins":`)
125 writeLeaders(&b, topWins)
126 b.WriteString(`,"gnot":`)
127 writeLeaders(&b, topGnot)
128 b.WriteString("}")
129 return b.String()
130}
131
132func writeLeaders(b *strings.Builder, board []leader) {
133 b.WriteString("[")
134 for i, l := range board {
135 if i > 0 {
136 b.WriteString(",")
137 }
138 b.WriteString(`{"addr":`)
139 b.WriteString(strconv.Quote(l.Addr.String()))
140 writeNum(b, "score", l.Score)
141 b.WriteString("}")
142 }
143 b.WriteString("]")
144}