PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • rpsoracle.gnogno
rpsoracle.gnogno
1// Package rpsoracle is a rock-paper-scissors opponent that doesn't roll2// dice: it studies each player's move history and always throws the3// counter to whichever move that player has favored most. Play a fixed4// pattern and the oracle punishes it; play close to a uniform 1/3-1/3-1/35// mix and it can't out-guess you better than chance.6package rpsoracle78import (9	"strconv"10	"strings"1112	"chain"13	"chain/runtime"1415	"gno.land/p/nt/avl/v0"16)1718// Move is one of rock, paper, or scissors, ordered so that19// (winner - loser + 3) % 3 == 1 for every winning pair.20type Move int2122const (23	Rock Move = iota24	Paper25	Scissors26)2728func moveName(m Move) string {29	switch m {30	case Rock:31		return "rock"32	case Paper:33		return "paper"34	case Scissors:35		return "scissors"36	default:37		return "?"38	}39}4041func parseMove(s string) (Move, bool) {42	switch strings.ToLower(strings.TrimSpace(s)) {43	case "rock", "r":44		return Rock, true45	case "paper", "p":46		return Paper, true47	case "scissors", "s":48		return Scissors, true49	default:50		return 0, false51	}52}5354// judge returns 0 for a tie, 1 if p beats h, 2 if h beats p.55func judge(p, h Move) int {56	return (int(p) - int(h) + 3) % 357}5859// beats returns the move that defeats m.60func beats(m Move) Move {61	return Move((int(m) + 1) % 3)62}6364// playerState is the persisted record for one address.65type playerState struct {66	Counts     [3]int // history tally per move, what the oracle predicts from67	Rounds     int68	Wins       int // player beat the oracle69	Losses     int // oracle beat the player70	Draws      int71	BestStreak int72	streak     int // current player win streak against the oracle73}7475var (76	players avl.Tree // address string -> *playerState7778	nonce         int79	totalRounds   int80	oracleCorrect int // rounds the oracle won by successfully countering8182	topOutwitter     address83	topOutwitterWins int84)8586func getOrCreate(addr address) *playerState {87	key := addr.String()88	if v := players.Get(key); v != nil {89		return v.(*playerState)90	}91	ps := &playerState{}92	players.Set(key, ps)93	return ps94}9596// predict guesses the player's next move as the most-played move in their97// history so far. Ties (including a fresh player's all-zero history) fall98// back to a chain-height-derived seed so the oracle doesn't always break99// ties the same way.100func predict(ps *playerState, seed int64) Move {101	best := Rock102	bestCount := ps.Counts[Rock]103	tied := []Move{Rock}104	for _, m := range []Move{Paper, Scissors} {105		switch {106		case ps.Counts[m] > bestCount:107			bestCount = ps.Counts[m]108			best = m109			tied = []Move{m}110		case ps.Counts[m] == bestCount:111			tied = append(tied, m)112		}113	}114	if len(tied) > 1 {115		if seed < 0 {116			seed = -seed117		}118		best = tied[int(seed)%len(tied)]119	}120	return best121}122123// Play pits the caller against the oracle: it predicts your next move from124// your own move history and throws the counter. Accepts125// "rock"/"paper"/"scissors" or the single-letter shorthand "r"/"p"/"s".126func Play(cur realm, moveStr string) string {127	if !cur.IsCurrent() {128		panic("invalid realm")129	}130	caller := cur.Previous().Address()131132	playerMove, ok := parseMove(moveStr)133	if !ok {134		panic("invalid move: use rock, paper, or scissors (r/p/s)")135	}136137	ps := getOrCreate(caller)138139	nonce++140	seed := runtime.ChainHeight() + int64(nonce)141	predicted := predict(ps, seed)142	oracleMove := beats(predicted)143144	result := judge(playerMove, oracleMove)145146	ps.Counts[playerMove]++147	ps.Rounds++148	totalRounds++149150	var msg string151	switch result {152	case 1:153		ps.Wins++154		ps.streak++155		if ps.streak > ps.BestStreak {156			ps.BestStreak = ps.streak157		}158		if ps.Wins > topOutwitterWins {159			topOutwitterWins = ps.Wins160			topOutwitter = caller161		}162		msg = "you outwitted the oracle!"163	case 2:164		ps.Losses++165		ps.streak = 0166		oracleCorrect++167		msg = "the oracle read you like a book."168	default:169		ps.Draws++170		ps.streak = 0171		msg = "a draw — you and the oracle picked the same move."172	}173174	chain.Emit("RoundPlayed",175		"player", caller.String(),176		"playerMove", moveName(playerMove),177		"oraclePredicted", moveName(predicted),178		"oracleMove", moveName(oracleMove),179		"result", strconv.Itoa(result),180	)181182	return "you played " + moveName(playerMove) + ", the oracle predicted " +183		moveName(predicted) + " and threw " + moveName(oracleMove) + " -> " + msg184}185186func renderHome() string {187	var b strings.Builder188	b.WriteString("# Rock-Paper-Scissors Oracle\n\n")189	b.WriteString("An adaptive opponent: it doesn't roll dice, it studies you. ")190	b.WriteString("Every throw is logged, and the oracle always counters whichever ")191	b.WriteString("move you've played most often. Play a uniform mixed strategy and ")192	b.WriteString("it can't out-guess you; fall into a habit and it will.\n\n")193194	b.WriteString("- Total rounds played: " + strconv.Itoa(totalRounds) + "\n")195	if totalRounds > 0 {196		pct := oracleCorrect * 100 / totalRounds197		b.WriteString("- Oracle win rate: " + strconv.Itoa(pct) + "%\n")198	}199	if topOutwitter.IsValid() {200		b.WriteString("- Top outwitter: `" + topOutwitter.String() + "` (" +201			strconv.Itoa(topOutwitterWins) + " wins against the oracle)\n")202	} else {203		b.WriteString("- No one has beaten the oracle yet.\n")204	}205206	b.WriteString("\n## How to play\n\n")207	b.WriteString("Call `Play(\"rock\"|\"paper\"|\"scissors\")` (or `r`/`p`/`s`). ")208	b.WriteString("View your own record at this realm's path plus your address, ")209	b.WriteString("e.g. `.../rpsoracle:g1youraddress...`\n")210	return b.String()211}212213// escapeInline neutralizes markdown-active characters in untrusted text214// before it's embedded inline in Render output.215func escapeInline(s string) string {216	r := strings.NewReplacer(217		"\\", "\\\\",218		"`", "\\`",219		"*", "\\*",220		"_", "\\_",221		"[", "\\[",222		"]", "\\]",223		"|", "\\|",224	)225	return r.Replace(s)226}227228func renderPlayer(rawAddr string) string {229	addr := strings.TrimSpace(rawAddr)230	safe := escapeInline(addr)231232	v := players.Get(addr)233	if v == nil {234		return "# Player " + safe + "\n\nNo recorded rounds yet.\n"235	}236	ps := v.(*playerState)237238	var b strings.Builder239	b.WriteString("# Player " + safe + "\n\n")240	b.WriteString("- Rounds played: " + strconv.Itoa(ps.Rounds) + "\n")241	b.WriteString("- Beat the oracle: " + strconv.Itoa(ps.Wins) + "\n")242	b.WriteString("- Lost to the oracle: " + strconv.Itoa(ps.Losses) + "\n")243	b.WriteString("- Draws: " + strconv.Itoa(ps.Draws) + "\n")244	b.WriteString("- Best win streak vs oracle: " + strconv.Itoa(ps.BestStreak) + "\n")245	b.WriteString("- Move history — rock: " + strconv.Itoa(ps.Counts[Rock]) +246		", paper: " + strconv.Itoa(ps.Counts[Paper]) +247		", scissors: " + strconv.Itoa(ps.Counts[Scissors]) + "\n")248249	if ps.Rounds > 0 {250		maxCount := ps.Counts[Rock]251		for _, c := range ps.Counts[1:] {252			if c > maxCount {253				maxCount = c254			}255		}256		predictability := maxCount * 100 / ps.Rounds257		b.WriteString("- Predictability score: " + strconv.Itoa(predictability) +258			"% (lower is harder for the oracle to read)\n")259	}260	return b.String()261}262263// Render shows the oracle's dashboard at "", or one player's record when264// path is their bech32 address.265func Render(path string) string {266	path = strings.TrimPrefix(strings.TrimSpace(path), "/")267	if path == "" {268		return renderHome()269	}270	return renderPlayer(path)271}272

Functions

  • 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}, moveStr 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.