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/coinflipduel/v1

Realm
Open in gnoweb ↗

Overview

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

Files (5)

  • README.mdmarkdown
  • gnomod.tomltoml
  • coinflipduel.gnogno
  • coinflipduel_test.gnogno
  • render_example_test.gnogno
coinflipduel.gnogno
1// Package coinflipduel is a one-on-one coin-flip duel: challenge another2// address, they call heads or tails, and the block settles it. No stakes,3// just bragging rights tracked in an on-chain win/loss record.4package coinflipduel56import (7	"strconv"8	"strings"910	"chain"11	"chain/runtime"1213	"gno.land/p/moul/kit/ui/v0"14	"gno.land/p/nt/avl/v0"15)1617// duelStatus tracks the lifecycle of a duel.18type duelStatus int1920const (21	statusPending duelStatus = iota22	statusResolved23	statusCancelled24)2526func (s duelStatus) String() string {27	switch s {28	case statusPending:29		return "pending"30	case statusResolved:31		return "resolved"32	case statusCancelled:33		return "cancelled"34	default:35		return "?"36	}37}3839// duel is one challenge from Challenger to Opponent over a single coin flip.40// The Challenger calls a side up front; the Opponent's only move is to41// accept, which triggers the flip.42type duel struct {43	ID         string44	Challenger address45	Opponent   address46	Call       string // "heads" or "tails" — the challenger's pick47	Status     duelStatus48	Result     string // "heads" or "tails" once resolved49	Winner     address50	CreatedAt  int6451	ResolvedAt int6452}5354// playerState is the persisted win/loss record for one address.55type playerState struct {56	Wins   int57	Losses int58	Duels  int59}6061var (62	duels   avl.Tree // duel ID -> *duel63	players avl.Tree // address string -> *playerState6465	nextID    int66	flipNonce int67	totalDone int6869	champion     address70	championWins int71)7273func parseCall(s string) (string, bool) {74	switch strings.ToLower(strings.TrimSpace(s)) {75	case "heads", "h":76		return "heads", true77	case "tails", "t":78		return "tails", true79	default:80		return "", false81	}82}8384// flipCoin derives a deterministic pseudo-random side from the current chain85// height, a monotonic per-realm nonce, and both duelists' addresses, so two86// duels resolved in the same block still diverge.87func flipCoin(d *duel) string {88	flipNonce++89	seed := runtime.ChainHeight() + int64(flipNonce)90	s := d.Challenger.String() + d.Opponent.String() + d.ID91	for i := 0; i < len(s); i++ {92		seed += int64(s[i])93	}94	if seed < 0 {95		seed = -seed96	}97	if seed%2 == 0 {98		return "heads"99	}100	return "tails"101}102103func getOrCreate(addr address) *playerState {104	key := addr.String()105	if v := players.Get(key); v != nil {106		return v.(*playerState)107	}108	ps := &playerState{}109	players.Set(key, ps)110	return ps111}112113func recordResult(winner, loser address) {114	w := getOrCreate(winner)115	w.Wins++116	w.Duels++117	l := getOrCreate(loser)118	l.Losses++119	l.Duels++120121	if w.Wins > championWins {122		championWins = w.Wins123		champion = winner124	}125}126127// Challenge opens a new duel against opponentAddr, locking in the128// challenger's call of "heads"/"tails" ("h"/"t" also accepted). The129// opponent settles it by calling Accept with the returned duel ID.130func Challenge(cur realm, opponentAddr string, call string) string {131	challenger := cur.Previous().Address()132133	opponent := address(strings.TrimSpace(opponentAddr))134	if !opponent.IsValid() {135		panic("invalid opponent address")136	}137	if opponent == challenger {138		panic("cannot duel yourself")139	}140141	pick, ok := parseCall(call)142	if !ok {143		panic("call must be heads or tails (h/t)")144	}145146	nextID++147	id := strconv.Itoa(nextID)148	d := &duel{149		ID:         id,150		Challenger: challenger,151		Opponent:   opponent,152		Call:       pick,153		Status:     statusPending,154		CreatedAt:  runtime.ChainHeight(),155	}156	duels.Set(id, d)157158	chain.Emit("DuelChallenged",159		"id", id,160		"challenger", challenger.String(),161		"opponent", opponent.String(),162		"call", pick,163	)164165	return "duel #" + id + " created: you called " + pick +166		", waiting for " + opponent.String() + " to accept"167}168169// Accept settles a pending duel. Only the challenged opponent may call it;170// the flip happens immediately and the result is final.171func Accept(cur realm, id string) string {172	caller := cur.Previous().Address()173174	v := duels.Get(id)175	if v == nil {176		panic("no such duel")177	}178	d := v.(*duel)179180	if d.Status != statusPending {181		panic("duel already " + d.Status.String())182	}183	if caller != d.Opponent {184		panic("only the challenged opponent can accept this duel")185	}186187	result := flipCoin(d)188	d.Result = result189	d.Status = statusResolved190	d.ResolvedAt = runtime.ChainHeight()191192	if result == d.Call {193		d.Winner = d.Challenger194		recordResult(d.Challenger, d.Opponent)195	} else {196		d.Winner = d.Opponent197		recordResult(d.Opponent, d.Challenger)198	}199200	totalDone++201202	chain.Emit("DuelResolved",203		"id", id,204		"result", result,205		"winner", d.Winner.String(),206	)207208	return "the coin landed on " + result + " -- " + d.Winner.String() + " wins duel #" + id209}210211// Cancel withdraws a still-pending duel. Only the challenger may cancel,212// and only before the opponent accepts.213func Cancel(cur realm, id string) string {214	caller := cur.Previous().Address()215216	v := duels.Get(id)217	if v == nil {218		panic("no such duel")219	}220	d := v.(*duel)221222	if d.Status != statusPending {223		panic("duel already " + d.Status.String())224	}225	if caller != d.Challenger {226		panic("only the challenger can cancel this duel")227	}228229	d.Status = statusCancelled230	return "duel #" + id + " cancelled"231}232233func renderHome() string {234	var b strings.Builder235	b.WriteString("# Coin-Flip Duel\n\n")236	b.WriteString("Challenge another address to a coin flip. You call heads or tails " +237		"up front; they accept and the block decides. No stakes -- just a record.\n\n")238	b.WriteString("- Total duels resolved: " + strconv.Itoa(totalDone) + "\n")239240	if champion.IsValid() {241		b.WriteString("- Reigning champion: `" + champion.String() +242			"` (" + strconv.Itoa(championWins) + " wins)\n")243	} else {244		b.WriteString("- No champion yet -- be the first to win a duel.\n")245	}246247	b.WriteString("\n## How to play\n\n")248	b.WriteString("1. `Challenge(opponentAddr, \"heads\"|\"tails\")` -- opens a duel, returns its ID.\n")249	b.WriteString("2. The opponent calls `Accept(id)` -- flips the coin and settles it on the spot.\n")250	b.WriteString("3. The challenger may `Cancel(id)` while it's still pending.\n\n")251	b.WriteString("View a duel at this realm's path plus its ID (e.g. `.../coinflipduel:3`), " +252		"or a player's record plus their address (e.g. `.../coinflipduel:g1youraddress...`).\n\n")253254	b.WriteString("## Pending duels\n\n")255	pending := 0256	duels.Iterate("", "", func(key string, value interface{}) bool {257		d := value.(*duel)258		if d.Status == statusPending {259			pending++260			b.WriteString("- #" + d.ID + ": `" + d.Challenger.String() + "` called " +261				d.Call + ", waiting on `" + d.Opponent.String() + "`\n")262		}263		return false264	})265	if pending == 0 {266		b.WriteString("_none right now_\n")267	}268269	return b.String()270}271272func renderDuel(id string) string {273	v := duels.Get(id)274	if v == nil {275		return "# Duel #" + ui.Inline(id) + "\n\nNo such duel.\n"276	}277	d := v.(*duel)278279	var b strings.Builder280	b.WriteString("# Duel #" + d.ID + "\n\n")281	b.WriteString("- Challenger: `" + d.Challenger.String() + "` called **" + d.Call + "**\n")282	b.WriteString("- Opponent: `" + d.Opponent.String() + "`\n")283	b.WriteString("- Status: " + d.Status.String() + "\n")284285	if d.Status == statusResolved {286		b.WriteString("- Coin landed on: **" + d.Result + "**\n")287		b.WriteString("- Winner: `" + d.Winner.String() + "`\n")288	}289	return b.String()290}291292func renderPlayer(rawAddr string) string {293	addr := strings.TrimSpace(rawAddr)294	safe := ui.Inline(addr)295296	v := players.Get(addr)297	if v == nil {298		return "# Player " + safe + "\n\nNo recorded duels yet.\n"299	}300	ps := v.(*playerState)301302	var b strings.Builder303	b.WriteString("# Player " + safe + "\n\n")304	b.WriteString("- Wins: " + strconv.Itoa(ps.Wins) + "\n")305	b.WriteString("- Losses: " + strconv.Itoa(ps.Losses) + "\n")306	b.WriteString("- Duels played: " + strconv.Itoa(ps.Duels) + "\n")307	return b.String()308}309310// Render shows the duel arena at "", a specific duel when path is a numeric311// ID, or one player's record when path is their bech32 address.312func Render(path string) string {313	path = strings.TrimPrefix(strings.TrimSpace(path), "/")314	if path == "" {315		return renderHome()316	}317	if _, err := strconv.Atoi(path); err == nil {318		return renderDuel(path)319	}320	return renderPlayer(path)321}322

Functions

  • Accept(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}, id string) string

  • Cancel(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}, id string) string

  • Challenge(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}, opponentAddr string, call string) string

  • Render(path 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.