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

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}