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

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • coinflipduel.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/nt/avl/v0"14)1516// duelStatus tracks the lifecycle of a duel.17type duelStatus int1819const (20	statusPending duelStatus = iota21	statusResolved22	statusCancelled23)2425func (s duelStatus) String() string {26	switch s {27	case statusPending:28		return "pending"29	case statusResolved:30		return "resolved"31	case statusCancelled:32		return "cancelled"33	default:34		return "?"35	}36}3738// duel is one challenge from Challenger to Opponent over a single coin flip.39// The Challenger calls a side up front; the Opponent's only move is to40// accept, which triggers the flip.41type duel struct {42	ID         string43	Challenger address44	Opponent   address45	Call       string // "heads" or "tails" — the challenger's pick46	Status     duelStatus47	Result     string // "heads" or "tails" once resolved48	Winner     address49	CreatedAt  int6450	ResolvedAt int6451}5253// playerState is the persisted win/loss record for one address.54type playerState struct {55	Wins   int56	Losses int57	Duels  int58}5960var (61	duels   avl.Tree // duel ID -> *duel62	players avl.Tree // address string -> *playerState6364	nextID    int65	flipNonce int66	totalDone int6768	champion     address69	championWins int70)7172func parseCall(s string) (string, bool) {73	switch strings.ToLower(strings.TrimSpace(s)) {74	case "heads", "h":75		return "heads", true76	case "tails", "t":77		return "tails", true78	default:79		return "", false80	}81}8283// flipCoin derives a deterministic pseudo-random side from the current chain84// height, a monotonic per-realm nonce, and both duelists' addresses, so two85// duels resolved in the same block still diverge.86func flipCoin(d *duel) string {87	flipNonce++88	seed := runtime.ChainHeight() + int64(flipNonce)89	s := d.Challenger.String() + d.Opponent.String() + d.ID90	for i := 0; i < len(s); i++ {91		seed += int64(s[i])92	}93	if seed < 0 {94		seed = -seed95	}96	if seed%2 == 0 {97		return "heads"98	}99	return "tails"100}101102func getOrCreate(addr address) *playerState {103	key := addr.String()104	if v := players.Get(key); v != nil {105		return v.(*playerState)106	}107	ps := &playerState{}108	players.Set(key, ps)109	return ps110}111112func recordResult(winner, loser address) {113	w := getOrCreate(winner)114	w.Wins++115	w.Duels++116	l := getOrCreate(loser)117	l.Losses++118	l.Duels++119120	if w.Wins > championWins {121		championWins = w.Wins122		champion = winner123	}124}125126// Challenge opens a new duel against opponentAddr, locking in the127// challenger's call of "heads"/"tails" ("h"/"t" also accepted). The128// opponent settles it by calling Accept with the returned duel ID.129func Challenge(cur realm, opponentAddr string, call string) string {130	challenger := cur.Previous().Address()131132	opponent := address(strings.TrimSpace(opponentAddr))133	if !opponent.IsValid() {134		panic("invalid opponent address")135	}136	if opponent == challenger {137		panic("cannot duel yourself")138	}139140	pick, ok := parseCall(call)141	if !ok {142		panic("call must be heads or tails (h/t)")143	}144145	nextID++146	id := strconv.Itoa(nextID)147	d := &duel{148		ID:         id,149		Challenger: challenger,150		Opponent:   opponent,151		Call:       pick,152		Status:     statusPending,153		CreatedAt:  runtime.ChainHeight(),154	}155	duels.Set(id, d)156157	chain.Emit("DuelChallenged",158		"id", id,159		"challenger", challenger.String(),160		"opponent", opponent.String(),161		"call", pick,162	)163164	return "duel #" + id + " created: you called " + pick +165		", waiting for " + opponent.String() + " to accept"166}167168// Accept settles a pending duel. Only the challenged opponent may call it;169// the flip happens immediately and the result is final.170func Accept(cur realm, id string) string {171	caller := cur.Previous().Address()172173	v := duels.Get(id)174	if v == nil {175		panic("no such duel")176	}177	d := v.(*duel)178179	if d.Status != statusPending {180		panic("duel already " + d.Status.String())181	}182	if caller != d.Opponent {183		panic("only the challenged opponent can accept this duel")184	}185186	result := flipCoin(d)187	d.Result = result188	d.Status = statusResolved189	d.ResolvedAt = runtime.ChainHeight()190191	if result == d.Call {192		d.Winner = d.Challenger193		recordResult(d.Challenger, d.Opponent)194	} else {195		d.Winner = d.Opponent196		recordResult(d.Opponent, d.Challenger)197	}198199	totalDone++200201	chain.Emit("DuelResolved",202		"id", id,203		"result", result,204		"winner", d.Winner.String(),205	)206207	return "the coin landed on " + result + " -- " + d.Winner.String() + " wins duel #" + id208}209210// Cancel withdraws a still-pending duel. Only the challenger may cancel,211// and only before the opponent accepts.212func Cancel(cur realm, id string) string {213	caller := cur.Previous().Address()214215	v := duels.Get(id)216	if v == nil {217		panic("no such duel")218	}219	d := v.(*duel)220221	if d.Status != statusPending {222		panic("duel already " + d.Status.String())223	}224	if caller != d.Challenger {225		panic("only the challenger can cancel this duel")226	}227228	d.Status = statusCancelled229	return "duel #" + id + " cancelled"230}231232// escapeInline neutralizes markdown-active characters in untrusted text233// before it's embedded inline in Render output.234func escapeInline(s string) string {235	r := strings.NewReplacer(236		"\\", "\\\\",237		"`", "\\`",238		"*", "\\*",239		"_", "\\_",240		"[", "\\[",241		"]", "\\]",242		"|", "\\|",243	)244	return r.Replace(s)245}246247func renderHome() string {248	var b strings.Builder249	b.WriteString("# Coin-Flip Duel\n\n")250	b.WriteString("Challenge another address to a coin flip. You call heads or tails " +251		"up front; they accept and the block decides. No stakes -- just a record.\n\n")252	b.WriteString("- Total duels resolved: " + strconv.Itoa(totalDone) + "\n")253254	if champion.IsValid() {255		b.WriteString("- Reigning champion: `" + champion.String() +256			"` (" + strconv.Itoa(championWins) + " wins)\n")257	} else {258		b.WriteString("- No champion yet -- be the first to win a duel.\n")259	}260261	b.WriteString("\n## How to play\n\n")262	b.WriteString("1. `Challenge(opponentAddr, \"heads\"|\"tails\")` -- opens a duel, returns its ID.\n")263	b.WriteString("2. The opponent calls `Accept(id)` -- flips the coin and settles it on the spot.\n")264	b.WriteString("3. The challenger may `Cancel(id)` while it's still pending.\n\n")265	b.WriteString("View a duel at this realm's path plus its ID (e.g. `.../coinflipduel:3`), " +266		"or a player's record plus their address (e.g. `.../coinflipduel:g1youraddress...`).\n\n")267268	b.WriteString("## Pending duels\n\n")269	pending := 0270	duels.Iterate("", "", func(key string, value interface{}) bool {271		d := value.(*duel)272		if d.Status == statusPending {273			pending++274			b.WriteString("- #" + d.ID + ": `" + d.Challenger.String() + "` called " +275				d.Call + ", waiting on `" + d.Opponent.String() + "`\n")276		}277		return false278	})279	if pending == 0 {280		b.WriteString("_none right now_\n")281	}282283	return b.String()284}285286func renderDuel(id string) string {287	v := duels.Get(id)288	if v == nil {289		return "# Duel #" + escapeInline(id) + "\n\nNo such duel.\n"290	}291	d := v.(*duel)292293	var b strings.Builder294	b.WriteString("# Duel #" + d.ID + "\n\n")295	b.WriteString("- Challenger: `" + d.Challenger.String() + "` called **" + d.Call + "**\n")296	b.WriteString("- Opponent: `" + d.Opponent.String() + "`\n")297	b.WriteString("- Status: " + d.Status.String() + "\n")298299	if d.Status == statusResolved {300		b.WriteString("- Coin landed on: **" + d.Result + "**\n")301		b.WriteString("- Winner: `" + d.Winner.String() + "`\n")302	}303	return b.String()304}305306func renderPlayer(rawAddr string) string {307	addr := strings.TrimSpace(rawAddr)308	safe := escapeInline(addr)309310	v := players.Get(addr)311	if v == nil {312		return "# Player " + safe + "\n\nNo recorded duels yet.\n"313	}314	ps := v.(*playerState)315316	var b strings.Builder317	b.WriteString("# Player " + safe + "\n\n")318	b.WriteString("- Wins: " + strconv.Itoa(ps.Wins) + "\n")319	b.WriteString("- Losses: " + strconv.Itoa(ps.Losses) + "\n")320	b.WriteString("- Duels played: " + strconv.Itoa(ps.Duels) + "\n")321	return b.String()322}323324// Render shows the duel arena at "", a specific duel when path is a numeric325// ID, or one player's record when path is their bech32 address.326func Render(path string) string {327	path = strings.TrimPrefix(strings.TrimSpace(path), "/")328	if path == "" {329		return renderHome()330	}331	if _, err := strconv.Atoi(path); err == nil {332		return renderDuel(path)333	}334	return renderPlayer(path)335}336

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.