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.

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}