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/tamagotchi/v0

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • tamagotchi.gnogno
tamagotchi.gnogno
1// Package tamagotchi is an on-chain virtual pet. Hatch one, then keep it2// alive by Feeding, Playing and letting it Sleep — hunger, happiness and3// energy all decay with block height, and neglect too long kills the pet.4// Hatching again after a death starts a fresh pet but keeps your lifetime5// stats, so the leaderboard rewards long-term care, not just luck.6package tamagotchi78import (9	"sort"10	"strconv"1112	"chain/runtime"1314	"gno.land/p/nt/avl/v0"15)1617// pet is the persisted record for one owner's tamagotchi.18type pet struct {19	owner      address20	name       string21	born       int6422	lastUpdate int6423	hunger     int // 0 (full) .. 100 (starving)24	happiness  int // 0 (miserable) .. 100 (joyful)25	energy     int // 0 (exhausted) .. 100 (energetic)26	alive      bool27	deaths     int28	feeds      int29	plays      int30}3132var pets avl.Tree // owner address string -> *pet3334func clamp(v, lo, hi int) int {35	if v < lo {36		return lo37	}38	if v > hi {39		return hi40	}41	return v42}4344func get(owner address) (*pet, bool) {45	v := pets.Get(owner.String())46	if v == nil {47		return nil, false48	}49	return v.(*pet), true50}5152// tick applies stat decay for blocks elapsed since the pet's last update and53// kills it if any stat has bottomed/topped out. Mutates and persists.54func (p *pet) tick(height int64) {55	if !p.alive {56		return57	}58	elapsed := int(height - p.lastUpdate)59	if elapsed <= 0 {60		return61	}62	p.hunger = clamp(p.hunger+elapsed, 0, 100)63	p.happiness = clamp(p.happiness-elapsed/2, 0, 100)64	p.energy = clamp(p.energy-elapsed/3, 0, 100)65	p.lastUpdate = height66	if p.hunger >= 100 || p.happiness <= 0 || p.energy <= 0 {67		p.alive = false68		p.deaths++69	}70}7172// project computes display stats as of height without mutating the pet, so73// Render can show a live-looking view without writing state on a query.74func project(p *pet, height int64) (hunger, happiness, energy int, alive bool) {75	if !p.alive {76		return p.hunger, p.happiness, p.energy, false77	}78	elapsed := int(height - p.lastUpdate)79	if elapsed < 0 {80		elapsed = 081	}82	hunger = clamp(p.hunger+elapsed, 0, 100)83	happiness = clamp(p.happiness-elapsed/2, 0, 100)84	energy = clamp(p.energy-elapsed/3, 0, 100)85	alive = hunger < 100 && happiness > 0 && energy > 086	return87}8889func ageStageName(born, height int64) string {90	switch age := height - born; {91	case age < 10:92		return "🥚 Egg"93	case age < 50:94		return "🐣 Baby"95	case age < 200:96		return "🐤 Teen"97	default:98		return "🐓 Adult"99	}100}101102// requireLivingPet ticks and fetches the caller's pet, panicking with a103// friendly message if there is none or it has died.104func requireLivingPet(owner address, height int64) *pet {105	p, ok := get(owner)106	if !ok {107		panic("you don't have a pet yet — call Hatch(name) first")108	}109	p.tick(height)110	if !p.alive {111		panic(p.name + " has died of neglect. Call Hatch(name) to start over.")112	}113	return p114}115116// hatch is the non-crossing core of Hatch, kept separate so unit tests can117// exercise its panics directly without going through a realm-crossing call.118func hatch(owner address, name string, height int64) string {119	if name == "" {120		panic("name cannot be empty")121	}122123	deaths, feeds, plays := 0, 0, 0124	if existing, ok := get(owner); ok {125		if existing.alive {126			existing.tick(height)127		}128		if existing.alive {129			panic("you already have a living pet: " + existing.name)130		}131		deaths, feeds, plays = existing.deaths, existing.feeds, existing.plays132	}133134	pets.Set(owner.String(), &pet{135		owner:      owner,136		name:       name,137		born:       height,138		lastUpdate: height,139		hunger:     20,140		happiness:  80,141		energy:     80,142		alive:      true,143		deaths:     deaths,144		feeds:      feeds,145		plays:      plays,146	})147	return "🥚 " + name + " has hatched!"148}149150// Hatch creates a new pet for the caller. Lifetime feeds/plays/deaths carry151// over from any previous pet so the leaderboard tracks long-term care.152func Hatch(cur realm, name string) string {153	if !cur.IsCurrent() {154		panic("spoofed realm")155	}156	return hatch(cur.Previous().Address(), name, runtime.ChainHeight())157}158159// feed is the non-crossing core of Feed.160func feed(owner address, height int64) string {161	p := requireLivingPet(owner, height)162	p.hunger = clamp(p.hunger-30, 0, 100)163	p.energy = clamp(p.energy+5, 0, 100)164	p.feeds++165	return p.name + " munches happily. Hunger: " + strconv.Itoa(p.hunger) + "/100"166}167168// Feed reduces hunger and gives a small energy boost.169func Feed(cur realm) string {170	if !cur.IsCurrent() {171		panic("spoofed realm")172	}173	return feed(cur.Previous().Address(), runtime.ChainHeight())174}175176// play is the non-crossing core of Play.177func play(owner address, height int64) string {178	p := requireLivingPet(owner, height)179	if p.energy < 10 {180		return p.name + " is too tired to play — try Sleep() first."181	}182	p.happiness = clamp(p.happiness+20, 0, 100)183	p.energy = clamp(p.energy-10, 0, 100)184	p.hunger = clamp(p.hunger+5, 0, 100)185	p.plays++186	return p.name + " had a blast! Happiness: " + strconv.Itoa(p.happiness) + "/100"187}188189// Play boosts happiness at the cost of energy and a bit of hunger. Refuses190// to run a tired pet into the ground — rest first.191func Play(cur realm) string {192	if !cur.IsCurrent() {193		panic("spoofed realm")194	}195	return play(cur.Previous().Address(), runtime.ChainHeight())196}197198// sleep is the non-crossing core of Sleep.199func sleep(owner address, height int64) string {200	p := requireLivingPet(owner, height)201	p.energy = clamp(p.energy+40, 0, 100)202	p.hunger = clamp(p.hunger+5, 0, 100)203	return p.name + " takes a nap. Energy: " + strconv.Itoa(p.energy) + "/100"204}205206// Sleep restores energy at the cost of a little hunger.207func Sleep(cur realm) string {208	if !cur.IsCurrent() {209		panic("spoofed realm")210	}211	return sleep(cur.Previous().Address(), runtime.ChainHeight())212}213214// byAliveThenOwner ranks living pets before dead ones, then orders each215// group by owner address so the leaderboard is fully deterministic.216type byAliveThenOwner []*pet217218func (r byAliveThenOwner) Len() int      { return len(r) }219func (r byAliveThenOwner) Swap(i, j int) { r[i], r[j] = r[j], r[i] }220func (r byAliveThenOwner) Less(i, j int) bool {221	if r[i].alive != r[j].alive {222		return r[i].alive223	}224	return r[i].owner.String() < r[j].owner.String()225}226227func bar(v int) string {228	filled := v / 10229	out := ""230	for i := 0; i < 10; i++ {231		if i < filled {232			out += "█"233		} else {234			out += "░"235		}236	}237	return out238}239240func shortAddr(a address) string {241	s := a.String()242	if len(s) > 12 {243		return s[:8] + "…" + s[len(s)-4:]244	}245	return s246}247248func renderCard(p *pet, height int64) string {249	hunger, happiness, energy, alive := project(p, height)250	out := "## " + p.name + " — owned by `" + shortAddr(p.owner) + "`\n\n"251	if !alive {252		out += "💀 **Deceased.** Owner can `Hatch(name)` a new pet.\n\n"253		return out254	}255	out += ageStageName(p.born, height) + " · age **" + strconv.Itoa(int(height-p.born)) + "** blocks\n\n"256	out += "- Hunger:    " + bar(100-hunger) + " (" + strconv.Itoa(hunger) + "/100 — lower is better)\n"257	out += "- Happiness: " + bar(happiness) + " (" + strconv.Itoa(happiness) + "/100)\n"258	out += "- Energy:    " + bar(energy) + " (" + strconv.Itoa(energy) + "/100)\n\n"259	return out260}261262// Render shows either every pet ever hatched (path == "") or a single pet's263// detail card when path is a bech32 owner address.264func Render(path string) string {265	height := runtime.ChainHeight()266267	out := "# 🐣 Tamagotchi\n\n"268	out += "A tiny on-chain pet. `Hatch(\"name\")` to start, then keep it alive with " +269		"`Feed()`, `Play()` and `Sleep()` — stats decay every block, so neglect kills it.\n\n"270271	if path != "" {272		owner := address(path)273		p, ok := get(owner)274		if !ok {275			return out + "_No pet found for `" + path + "`._\n"276		}277		return out + renderCard(p, height)278	}279280	var rows []*pet281	pets.Iterate("", "", func(_ string, v any) bool {282		rows = append(rows, v.(*pet))283		return false284	})285	if len(rows) == 0 {286		out += "_No pets hatched yet. Be the first!_\n"287		return out288	}289	sort.Stable(byAliveThenOwner(rows))290291	out += "| Pet | Owner | Status | Feeds | Plays | Deaths |\n"292	out += "| :--- | :--- | :--- | ---: | ---: | ---: |\n"293	for _, p := range rows {294		_, _, _, alive := project(p, height)295		status := "🐤 alive"296		if !alive {297			status = "💀 dead"298		}299		out += "| " + p.name + " | `" + shortAddr(p.owner) + "` | " + status +300			" | " + strconv.Itoa(p.feeds) + " | " + strconv.Itoa(p.plays) +301			" | " + strconv.Itoa(p.deaths) + " |\n"302	}303	out += "\n_View a single pet at `?<owner-address>`._\n"304	return out305}306

Functions

  • Feed(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}) string

  • Hatch(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) string

  • Play(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}) string

  • Render(path string) string

  • Sleep(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}) 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.