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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}