Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

json.gno

6.01 Kb · 201 lines
  1package arena
  2
  3import "strings"
  4
  5// The JSON documents are built by hand. That is safe because every string
  6// that reaches them was validated against a JSON-safe charset when it was
  7// written: ids are hex, modes and nonces [a-z0-9-], tickets hex, addresses
  8// bech32. The one string that is not under the realm's control, an address
  9// typed into a query, goes through quote.
 10//
 11// Ordering is deterministic everywhere: arenas follow their id (the tree
 12// iterates in key order), a lobby and a round the order of entry, results
 13// are newest first and earnings best first.
 14
 15// ArenasJSON returns every arena someone paid into and whose game is not
 16// over, as a JSON array sorted by id:
 17//
 18//	[
 19//	  { "id": "3f2a...", "mode": "deathmatch", "stake": 10000000,
 20//	    "minSeats": 2, "maxSeats": 8, "lives": 3, "seconds": 900, "killTarget": 5,
 21//	    "creator": "g1...", "created": 1790950000, "nonce": "k3x9",
 22//	    "lobby": [ { "address": "g1...", "ticket": "9f86d0...00a08" } ],
 23//	    "round": { "startedAt": 1790950300, "players": [ "g1...", "g1..." ] } }
 24//	]
 25//
 26// The output has no whitespace. "round" is null until the game starts.
 27// The fields from "mode" to "nonce" are the arena's spec: the game server
 28// rebuilds the spec string, and so the id, from them.
 29func ArenasJSON() string {
 30	var b strings.Builder
 31	b.WriteByte('[')
 32	first := true
 33	arenas.Iterate("", "", func(id string, v any) bool {
 34		if !first {
 35			b.WriteByte(',')
 36		}
 37		first = false
 38		writeArena(&b, id, v.(*arena))
 39		return false
 40	})
 41	b.WriteByte(']')
 42	return b.String()
 43}
 44
 45// ArenaJSON returns one arena in the shape of ArenasJSON, or null.
 46func ArenaJSON(id string) string {
 47	a := getArena(id)
 48	if a == nil {
 49		return "null"
 50	}
 51	var b strings.Builder
 52	writeArena(&b, id, a)
 53	return b.String()
 54}
 55
 56func writeArena(b *strings.Builder, id string, a *arena) {
 57	b.WriteString(`{"id":"` + id + `","mode":"` + a.mode + `"`)
 58	for _, f := range []struct {
 59		k string
 60		v int64
 61	}{{"stake", a.stake}, {"minSeats", a.minSeats}, {"maxSeats", a.maxSeats}, {"lives", a.lives},
 62		{"seconds", a.seconds}, {"killTarget", a.killTarget}} {
 63		b.WriteString(`,"` + f.k + `":` + itoa(f.v))
 64	}
 65	b.WriteString(`,"creator":"` + a.creator.String() + `","created":` + itoa(a.created) + `,"nonce":"` + a.nonce + `"`)
 66	b.WriteString(`,"lobby":[`)
 67	for i, p := range a.lobby {
 68		if i > 0 {
 69			b.WriteByte(',')
 70		}
 71		b.WriteString(`{"address":"` + p.String() + `","ticket":"` + a.tickets[i] + `"}`)
 72	}
 73	b.WriteString(`],"round":`)
 74	if a.round == nil {
 75		b.WriteString("null}")
 76		return
 77	}
 78	b.WriteString(`{"startedAt":` + itoa(a.round.startedAt) + `,"players":[`)
 79	for i, p := range a.round.players {
 80		if i > 0 {
 81			b.WriteByte(',')
 82		}
 83		b.WriteString(`"` + p.String() + `"`)
 84	}
 85	b.WriteString("]}}")
 86}
 87
 88// ResultsJSON returns the last closed games, newest first, at most 50:
 89//
 90//	[ { "id": "3f2a...", "mode": "deathmatch", "stake": 10000000,
 91//	    "outcome": "settled", "players": 2, "pot": 20000000, "fee": 500000,
 92//	    "closedAt": 1790951000,
 93//	    "payouts": [ { "address": "g1...", "amount": 19500000 } ] } ]
 94func ResultsJSON() string {
 95	var b strings.Builder
 96	b.WriteByte('[')
 97	for i := len(closed) - 1; i >= 0; i-- {
 98		if i < len(closed)-1 {
 99			b.WriteByte(',')
100		}
101		r, _ := resultOf(closed[i])
102		writeResult(&b, closed[i], r)
103	}
104	b.WriteByte(']')
105	return b.String()
106}
107
108// ResultJSON returns the result of one closed game in the shape of
109// ResultsJSON, or null when the realm does not have it (still running, or
110// too old).
111func ResultJSON(id string) string {
112	r, ok := resultOf(id)
113	if !ok {
114		return "null"
115	}
116	var b strings.Builder
117	writeResult(&b, id, r)
118	return b.String()
119}
120
121func writeResult(b *strings.Builder, id string, r result) {
122	b.WriteString(`{"id":"` + id + `","mode":"` + r.mode + `","stake":` + itoa(r.stake) +
123		`,"outcome":"` + r.outcome + `","players":` + itoa(r.players) + `,"pot":` + itoa(r.pot) +
124		`,"fee":` + itoa(r.fee) + `,"closedAt":` + itoa(r.closedAt) + `,"payouts":[`)
125	for i, p := range r.payouts {
126		if i > 0 {
127			b.WriteByte(',')
128		}
129		b.WriteString(`{"address":"` + p.Address.String() + `","amount":` + itoa(p.Amount) + `}`)
130	}
131	b.WriteString("]}")
132}
133
134// EarningsJSON returns the top winners as a JSON array, best first, at most
135// 10 entries:
136//
137//	[ { "address": "g1...", "won": 19000000, "rounds": 3 } ]
138//
139// The output has no whitespace. "won" adds up, round after round, what the
140// player was paid above their own stake, never less than zero for a round.
141// "rounds" is how many settled rounds they played. With equal winnings, the
142// player who got there first stays ahead.
143func EarningsJSON() string {
144	var b strings.Builder
145	b.WriteByte('[')
146	for i, addr := range winners.addrs {
147		if i > 0 {
148			b.WriteByte(',')
149		}
150		writeTotals(&b, `"`+addr.String()+`"`, totalsOf(addr))
151	}
152	b.WriteByte(']')
153	return b.String()
154}
155
156// PlayerJSON returns one player's totals as a JSON object:
157//
158//	{ "address": "g1...", "won": 19000000, "rounds": 3 }
159//
160// The output has no whitespace. An unknown address returns the same shape
161// with zero won and zero rounds.
162func PlayerJSON(addr address) string {
163	var b strings.Builder
164	writeTotals(&b, quote(addr.String()), totalsOf(addr))
165	return b.String()
166}
167
168// writeTotals writes the totals of a player. quoted is the player's address
169// as a JSON string literal, quotes included.
170func writeTotals(b *strings.Builder, quoted string, t totals) {
171	b.WriteString(`{"address":`)
172	b.WriteString(quoted)
173	b.WriteString(`,"won":`)
174	b.WriteString(itoa(t.won))
175	b.WriteString(`,"rounds":`)
176	b.WriteString(itoa(t.rounds))
177	b.WriteByte('}')
178}
179
180// quote returns s as a JSON string literal, quotes included. It is for the
181// strings the realm did not validate. Quotes and backslashes are escaped and
182// anything outside printable ASCII is replaced by "?", so the result is
183// valid JSON whatever s holds.
184func quote(s string) string {
185	var b strings.Builder
186	b.WriteByte('"')
187	for i := 0; i < len(s); i++ {
188		c := s[i]
189		switch {
190		case c == '"' || c == '\\':
191			b.WriteByte('\\')
192			b.WriteByte(c)
193		case c < 0x20 || c > 0x7e:
194			b.WriteByte('?')
195		default:
196			b.WriteByte(c)
197		}
198	}
199	b.WriteByte('"')
200	return b.String()
201}