render.gno
6.13 Kb · 200 lines
1package planes
2
3import (
4 "strconv"
5 "strings"
6)
7
8// Render returns the state of the collection as markdown, for gnoweb:
9//
10// "" the collection: minted, price, tiers, the last mints
11// "plane/<id>" one plane: traits, owner
12// "player/<addr>" the planes of one address
13//
14// Everything printed here is a row of the table, whose charset the tests
15// pin, a setting validated when it was written, or an address the chain gave
16// the realm. The two caller-supplied values, an id and an address, are
17// printed only once parsed and found valid. Nothing needs markdown escaping.
18// No images: the planes are drawn by the game and by the website.
19func Render(path string) string {
20 switch {
21 case path == "":
22 return renderHome()
23 case strings.HasPrefix(path, "plane/"):
24 if id, err := strconv.ParseInt(strings.TrimPrefix(path, "plane/"), 10, 64); err == nil {
25 if p, ok := getPlane(id); ok {
26 return renderPlane(p)
27 }
28 }
29 case strings.HasPrefix(path, "player/"):
30 if addr := address(strings.TrimPrefix(path, "player/")); validAddress(addr) {
31 return renderPlayer(addr)
32 }
33 }
34 return "# gnofly planes\n\nPage not found. Go back to the [collection](" + link("") + ").\n"
35}
36
37// link returns the gnoweb URL of a Render path of this realm.
38func link(path string) string {
39 if path == "" {
40 return webPath
41 }
42 return webPath + ":" + path
43}
44
45func renderHome() string {
46 var b strings.Builder
47 b.WriteString("# gnofly planes\n\n")
48 b.WriteString("777 planes to fly in gnofly, a multiplayer plane game. Each one is a GRC721 token with a name,\n")
49 b.WriteString("a model, three colours, a tier and a laser, drawn from a table written once and for all.\n")
50 b.WriteString("Mint up to 7 at once with `Mint` and count times the price attached, then pick one in the hangar of the game.\n\n")
51
52 b.WriteString("| | |\n|---|---|\n")
53 b.WriteString("| Minted | " + itoa(Minted()) + " of " + itoa(supply) + " |\n")
54 b.WriteString("| Burned | " + itoa(Minted()-Token.TotalSupply()) + " |\n")
55 b.WriteString("| Price | " + gnot(price) + " |\n")
56 b.WriteString("| Mint open | " + yesNo(mintOpen) + " |\n")
57 b.WriteString("| Per wallet | " + itoa(mintCap) + " |\n")
58 b.WriteString("| Royalty | " + percent(RoyaltyBps()) + " to the treasury |\n")
59 b.WriteString("| Treasury | " + roles.Treasury().String() + " |\n\n")
60
61 b.WriteString("## Tiers\n\n")
62 b.WriteString("| Tier | Planes | Minted |\n|---|---|---|\n")
63 for i, tier := range strings.Split(tiers, ",") {
64 b.WriteString("| " + tier + " | " + itoa(tierSupply[i]) + " | " + itoa(tierMinted[i]) + " |\n")
65 }
66
67 b.WriteString("\n## Recent mints\n\n")
68 recent := 0
69 for _, id := range recentIDs() {
70 owner := ownerOf(id)
71 if owner == "" {
72 continue // burned
73 }
74 if recent == 0 {
75 b.WriteString("| Plane | Tier | Owner |\n|---|---|---|\n")
76 }
77 p := mustPlane(id)
78 b.WriteString("| " + planeLink(p) + " | " + p.tier + " | " + playerLink(owner) + " |\n")
79 recent++
80 }
81 if recent == 0 {
82 b.WriteString("No plane is minted yet.\n")
83 }
84 return b.String()
85}
86
87func renderPlane(p plane) string {
88 var b strings.Builder
89 b.WriteString("# " + p.name + "\n\n")
90 b.WriteString("Plane " + itoa(p.id) + " of " + itoa(supply) + ", " + p.tier + ".\n\n")
91
92 b.WriteString("| | |\n|---|---|\n")
93 b.WriteString("| Model | " + p.model + " |\n")
94 b.WriteString("| Pilot | " + p.pilot + " |\n")
95 b.WriteString("| Body | `" + p.body + "` |\n")
96 b.WriteString("| Wing | `" + p.wing + "` |\n")
97 b.WriteString("| Accent | `" + p.accent + "` |\n")
98 b.WriteString("| Laser | `" + p.laser + "` |\n")
99 b.WriteString("| Tier | " + p.tier + " |\n")
100 owner := ownerOf(p.id)
101 switch {
102 case owner == "" && wasMinted(p.id):
103 b.WriteString("| Owner | burned |\n")
104 case owner == "":
105 b.WriteString("| Owner | not minted yet |\n")
106 default:
107 b.WriteString("| Owner | " + playerLink(owner) + " |\n")
108 b.WriteString("| Metadata | `" + imageBase + itoa(p.id) + ".json` |\n")
109 }
110 b.WriteString("\n[Collection](" + link("") + ")\n")
111 return b.String()
112}
113
114func renderPlayer(addr address) string {
115 var b strings.Builder
116 b.WriteString("# Pilot " + addr.String() + "\n\n")
117 n, _ := Token.BalanceOf(addr)
118 if n == 0 {
119 b.WriteString("This address owns no plane.\n")
120 } else {
121 b.WriteString(itoa(n) + " plane" + plural(n) + ", minted " + itoa(mintedBy(addr)) + " of " + itoa(mintCap) + " allowed.\n\n")
122 b.WriteString("| Plane | Model | Pilot | Tier |\n|---|---|---|---|\n")
123 found := int64(0)
124 for id := int64(1); id <= supply && found < n; id++ {
125 if ownerOf(id) != addr {
126 continue
127 }
128 p := mustPlane(id)
129 b.WriteString("| " + planeLink(p) + " | " + p.model + " | " + p.pilot + " | " + p.tier + " |\n")
130 found++
131 }
132 }
133 b.WriteString("\n[Collection](" + link("") + ")\n")
134 return b.String()
135}
136
137// planeLink is "[42 Storm Saint](.../plane/42)".
138func planeLink(p plane) string {
139 return "[" + itoa(p.id) + " " + p.name + "](" + link("plane/"+itoa(p.id)) + ")"
140}
141
142// playerLink is "[g1...](.../player/g1...)".
143func playerLink(addr address) string {
144 return "[" + addr.String() + "](" + link("player/"+addr.String()) + ")"
145}
146
147// gnot formats an amount of ugnot in GNOT, without trailing zeros:
148// 77000000 gives "77 GNOT", 9500000 gives "9.5 GNOT".
149func gnot(ugnot int64) string {
150 whole, frac := ugnot/1000000, ugnot%1000000
151 if frac == 0 {
152 return itoa(whole) + " GNOT"
153 }
154 decimals := itoa(1000000 + frac)[1:] // all six of them, zeros included
155 end := len(decimals)
156 for decimals[end-1] == '0' {
157 end--
158 }
159 return itoa(whole) + "." + decimals[:end] + " GNOT"
160}
161
162// percent formats basis points as a percentage, without trailing zeros:
163// 500 gives "5%", 250 gives "2.5%", 1 gives "0.01%".
164func percent(bps int64) string {
165 whole, frac := bps/100, bps%100
166 switch {
167 case frac == 0:
168 return itoa(whole) + "%"
169 case frac%10 == 0:
170 return itoa(whole) + "." + itoa(frac/10) + "%"
171 case frac < 10:
172 return itoa(whole) + ".0" + itoa(frac) + "%"
173 }
174 return itoa(whole) + "." + itoa(frac) + "%"
175}
176
177func plural(n int64) string {
178 if n == 1 {
179 return ""
180 }
181 return "s"
182}
183
184func yesNo(b bool) string {
185 if b {
186 return "yes"
187 }
188 return "no"
189}
190
191// recentIDs returns the last ids minted, newest first.
192func recentIDs() []int64 {
193 var ids []int64
194 for _, id := range recent {
195 if id != 0 {
196 ids = append(ids, id)
197 }
198 }
199 return ids
200}