PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/daily/rpgroom/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / rpgroom
Files
3 (README)(gnomod.toml)
Exported functions
5
Module
gno.land/r/moul/x/daily/rpgroom/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • rpgroom.gnogno
rpgroom.gnogno
1// Package rpgroom is a tiny single-room on-chain RPG. There is exactly one2// monster in the room at a time. Spawn a hero, take a swing, and see what3// happens: land the killing blow and the room's monster gets replaced by a4// tougher one while your hero gains XP, gold, and (eventually) a level; get5// hit hard enough and your hero falls until it's revived. A room-wide6// leaderboard tracks who has climbed the highest.7package rpgroom89import (10	"sort"11	"strconv"12	"strings"1314	"chain"15	"chain/runtime"1617	"gno.land/p/nt/avl/v0"18	"gno.land/p/nt/markdown/sanitize/v0"19)2021// Hero is one player's on-chain character in the room.22type Hero struct {23	Name   string24	Level  int25	XP     int26	HP     int27	MaxHP  int28	Attack int29	Kills  int30	Gold   int31	Alive  bool32}3334// Monster is the room's current resident.35type Monster struct {36	Name   string37	Level  int38	HP     int39	MaxHP  int40	Attack int41}4243var (44	heroes     avl.Tree // address string -> *Hero45	monster    *Monster46	roomLevel  int47	totalKills int48)4950var monsterNames = []string{51	"Slime", "Giant Rat", "Goblin", "Skeleton", "Cave Bat",52	"Orc Grunt", "Wraith", "Stone Troll", "Dread Wolf", "Young Dragon",53}5455func init() {56	roomLevel = 157	monster = spawnMonster(roomLevel)58}5960func spawnMonster(level int) *Monster {61	name := monsterNames[(level-1)%len(monsterNames)]62	hp := 20 + level*1563	return &Monster{64		Name:   name,65		Level:  level,66		HP:     hp,67		MaxHP:  hp,68		Attack: 3 + level*2,69	}70}7172// Spawn creates the caller's hero in the room. Each address gets exactly one73// hero; call Revive (not Spawn again) if it dies.74func Spawn(cur realm, name string) {75	if !cur.Previous().IsUserCall() {76		panic("only a direct EOA call can spawn a hero")77	}78	name = strings.TrimSpace(name)79	if name == "" {80		panic("name required")81	}82	if len(name) > 24 {83		panic("name too long (max 24 chars)")84	}85	addr := cur.Previous().Address().String()86	if heroes.Has(addr) {87		panic("you already have a hero in this room")88	}89	heroes.Set(addr, &Hero{90		Name:   name,91		Level:  1,92		HP:     30,93		MaxHP:  30,94		Attack: 5,95		Alive:  true,96	})97	chain.Emit("HeroSpawned", "addr", addr, "name", name)98}99100// Attack swings the caller's hero at the room's current monster. Killing it101// grants XP and gold and replaces it with a tougher one; otherwise the102// monster strikes back.103func Attack(cur realm) {104	if !cur.Previous().IsUserCall() {105		panic("only a direct EOA call can attack")106	}107	addr := cur.Previous().Address().String()108	v := heroes.Get(addr)109	if v == nil {110		panic("no hero found; call Spawn first")111	}112	hero := v.(*Hero)113	if !hero.Alive {114		panic("your hero is dead; call Revive first")115	}116117	dmg := hero.Attack118	if runtime.ChainHeight()%7 == 0 {119		dmg *= 2 // occasional critical hit, tied to block height parity120	}121	monster.HP -= dmg122	chain.Emit("HeroAttacked", "addr", addr, "damage", strconv.Itoa(dmg), "monsterHP", strconv.Itoa(monster.HP))123124	if monster.HP <= 0 {125		reward := monster.Level * 10126		gained := monster.Level * 20127		hero.Gold += reward128		hero.XP += gained129		hero.Kills++130		levelUp(hero)131		chain.Emit("MonsterSlain", "addr", addr, "monster", monster.Name, "level", strconv.Itoa(monster.Level))132133		totalKills++134		roomLevel++135		monster = spawnMonster(roomLevel)136		return137	}138139	hero.HP -= monster.Attack140	if hero.HP <= 0 {141		hero.HP = 0142		hero.Alive = false143		chain.Emit("HeroDied", "addr", addr, "monster", monster.Name)144	}145}146147// Rest heals the caller's hero for a quarter of its max HP.148func Rest(cur realm) {149	if !cur.Previous().IsUserCall() {150		panic("only a direct EOA call can rest")151	}152	addr := cur.Previous().Address().String()153	v := heroes.Get(addr)154	if v == nil {155		panic("no hero found; call Spawn first")156	}157	hero := v.(*Hero)158	if !hero.Alive {159		panic("your hero is dead; call Revive first")160	}161	if hero.HP >= hero.MaxHP {162		panic("already at full health")163	}164	heal := hero.MaxHP / 4165	if heal < 1 {166		heal = 1167	}168	hero.HP += heal169	if hero.HP > hero.MaxHP {170		hero.HP = hero.MaxHP171	}172	chain.Emit("HeroRested", "addr", addr, "hp", strconv.Itoa(hero.HP))173}174175// Revive brings a fallen hero back at half max HP.176func Revive(cur realm) {177	if !cur.Previous().IsUserCall() {178		panic("only a direct EOA call can revive")179	}180	addr := cur.Previous().Address().String()181	v := heroes.Get(addr)182	if v == nil {183		panic("no hero found; call Spawn first")184	}185	hero := v.(*Hero)186	if hero.Alive {187		panic("your hero is not dead")188	}189	hero.Alive = true190	hero.HP = hero.MaxHP / 2191	if hero.HP < 1 {192		hero.HP = 1193	}194	chain.Emit("HeroRevived", "addr", addr, "hp", strconv.Itoa(hero.HP))195}196197// levelUp applies every level-up a hero's current XP qualifies for. The198// threshold to reach level N+1 from level N is N*50 XP.199func levelUp(h *Hero) {200	for h.XP >= h.Level*50 {201		h.XP -= h.Level * 50202		h.Level++203		h.MaxHP += 10204		h.HP = h.MaxHP205		h.Attack += 3206	}207}208209// byRank orders heroes for the leaderboard: highest level first, then XP,210// then kills, as a tiebreaker.211type byRank []*Hero212213func (r byRank) Len() int      { return len(r) }214func (r byRank) Swap(i, j int) { r[i], r[j] = r[j], r[i] }215func (r byRank) Less(i, j int) bool {216	if r[i].Level != r[j].Level {217		return r[i].Level > r[j].Level218	}219	if r[i].XP != r[j].XP {220		return r[i].XP > r[j].XP221	}222	return r[i].Kills > r[j].Kills223}224225func collectHeroes() []*Hero {226	var out []*Hero227	heroes.Iterate("", "", func(key string, value any) bool {228		out = append(out, value.(*Hero))229		return false230	})231	return out232}233234func hpBar(hp, maxHP int) string {235	if maxHP <= 0 {236		maxHP = 1237	}238	filled := hp * 10 / maxHP239	if filled < 0 {240		filled = 0241	}242	if filled > 10 {243		filled = 10244	}245	bar := strings.Repeat("#", filled) + strings.Repeat("-", 10-filled)246	return "`" + bar + "` " + strconv.Itoa(hp) + "/" + strconv.Itoa(maxHP) + " HP"247}248249// Render shows the room overview and leaderboard at path "", or a single250// hero's detail page when path is a bech32 address.251func Render(path string) string {252	path = strings.TrimSpace(path)253	if path != "" {254		return renderHero(path)255	}256	return renderRoom()257}258259func renderRoom() string {260	var b strings.Builder261	b.WriteString("# RPG Room\n\n")262	b.WriteString("A single dungeon room with one monster in it at a time. ")263	b.WriteString("Call `Spawn(\"name\")` to enter, `Attack()` to fight the resident monster, ")264	b.WriteString("`Rest()` to heal up, and `Revive()` if you fall.\n\n")265266	b.WriteString("## Current monster\n\n")267	b.WriteString("**" + sanitize.InlineText(monster.Name) + "** — level " + strconv.Itoa(monster.Level) + "\n\n")268	b.WriteString(hpBar(monster.HP, monster.MaxHP) + "\n\n")269	b.WriteString("- Attack: " + strconv.Itoa(monster.Attack) + "\n")270	b.WriteString("- Monsters cleared so far: " + strconv.Itoa(totalKills) + "\n\n")271272	b.WriteString("## Leaderboard\n\n")273	all := collectHeroes()274	if len(all) == 0 {275		b.WriteString("_No heroes yet. Be the first: `Spawn(\"name\")`._\n")276		return b.String()277	}278	sort.Sort(byRank(all))279	b.WriteString("| # | Hero | Lvl | HP | Kills | Gold | Status |\n")280	b.WriteString("|---|---|---|---|---|---|---|\n")281	limit := len(all)282	if limit > 20 {283		limit = 20284	}285	for i := 0; i < limit; i++ {286		h := all[i]287		status := "alive"288		if !h.Alive {289			status = "fallen"290		}291		b.WriteString("| " + strconv.Itoa(i+1) + " | " + sanitize.InlineText(h.Name) + " | " +292			strconv.Itoa(h.Level) + " | " + strconv.Itoa(h.HP) + "/" + strconv.Itoa(h.MaxHP) + " | " +293			strconv.Itoa(h.Kills) + " | " + strconv.Itoa(h.Gold) + " | " + status + " |\n")294	}295	return b.String()296}297298func renderHero(addr string) string {299	v := heroes.Get(addr)300	if v == nil {301		return "> [!WARNING]\n> No hero found for `" + sanitize.InlineText(addr) + "`.\n"302	}303	h := v.(*Hero)304	var b strings.Builder305	b.WriteString("# " + sanitize.InlineText(h.Name) + "\n\n")306	b.WriteString(hpBar(h.HP, h.MaxHP) + "\n\n")307	b.WriteString("- Level: " + strconv.Itoa(h.Level) + "\n")308	b.WriteString("- XP: " + strconv.Itoa(h.XP) + " (next level at " + strconv.Itoa(h.Level*50) + ")\n")309	b.WriteString("- Attack: " + strconv.Itoa(h.Attack) + "\n")310	b.WriteString("- Kills: " + strconv.Itoa(h.Kills) + "\n")311	b.WriteString("- Gold: " + strconv.Itoa(h.Gold) + "\n")312	if !h.Alive {313		b.WriteString("- Status: fallen — call `Revive()` to return\n")314	}315	return b.String()316}317

Functions

  • Attack(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Render(path string) string

  • Rest(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Revive(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Spawn(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.