Transaction

D17D0C0EC823EF…AF746DB36702

Block 26,355 · index 0 · indexed

Summary

Hash
D17D0C0EC823EF104B7353B2054D3BB6641D57FEF010B4E61D77AF746DB36702
Block
26,355
Size
10635 bytes
Gas used
15,672,515 / 18,807,078
Fee
1000000ugnot
Memo
gnopublish
Status
success

Messages

#1AddPackagegno.land/r/moul/x/daily/hangman/v09 arguments

Arguments · 9

  1. #1hangman
  2. #2README.md
  3. #3# 🪢 Hangman > ⚠️ **Experimental — generated with no human supervision.** This realm was > produced automatically by an MCP-driven agent to exercise the gno MCP server > and tooling, and to generate test content for gno compilers, linters and > formatters. **Not audited. Not for production.** Full context & folder README: > [r/moul/x/daily](https://github.com/moul/gno-contracts/blob/main/r/moul/x/daily/README.md) --- Classic hangman as a gno.land realm. A secret word is chosen **deterministically per day** from a built-in list using the current block height (`runtime.ChainHeight()`), so everyone hitting the realm on the same day plays the **same shared game**: guessed letters and the wrong-count are common state. Guess a letter to reveal every matching position, or lose one of six lives — solve the word before the gallows fills up. **Realm path:** `gno.land/r/REPLACE_ADDR/hangman` ## Example calls ```sh # Guess a letter (tx — mutates the shared daily game) gnokey maketx call -pkgpath "gno.land/r/REPLACE_ADDR/hangman" \ -func Guess -args "e" -gas-fee 1000000ugnot -gas-wanted 2000000 \ -broadcast -chainid sapphire-1 KEY # View the current board (masked word, guesses, gallows, status) gnokey query "vm/qrender" -data "gno.land/r/REPLACE_ADDR/hangman:" ``` ## Rules - Each `Guess(cur, letter)` takes a single letter `a`–`z` (case-insensitive). - A hit reveals all matching positions; a miss increments the wrong-count. - Six wrong guesses = game over. Guessing every letter = win. - Re-guessing an already-tried letter, or playing after the game is over, aborts. - The word rotates roughly every day (a fixed span of blocks). `Render` returns Markdown showing the ASCII gallows stage for the current wrong-count, the masked word (`_` for hidden letters), the sorted list of guessed letters, the lives remaining, and the win/lose/in-progress status. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/daily/hangman/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/daily/hangman/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4gnomod.toml
  5. #5module = "gno.land/r/moul/x/daily/hangman/v0" gno = "0.9"
  6. #6hangman.gno
  7. #7// Package hangman is a classic hangman game for gno.land. // // The secret word rotates every ~"day" (a fixed span of blocks) and is chosen // deterministically from a built-in word list using runtime.ChainHeight(). All // players share a single game per day: guesses and the wrong-count are common // state. Guess a letter to reveal every matching position, or lose a life. Six // wrong guesses ends the game. package hangman import ( "chain" "chain/runtime" "strings" "gno.land/p/nt/avl/v0" ) // blocksPerDay is the number of blocks that constitutes one "day" / one round. // The secret word is fixed for the whole span, then rotates. const blocksPerDay = 7200 // maxWrong is the number of wrong guesses allowed before the game is lost. const maxWrong = 6 // words is the built-in dictionary. Kept lowercase, letters only. var words = []string{ "gnoland", "blockchain", "realm", "crossing", "deterministic", "contract", "validator", "consensus", "package", "namespace", "immutable", "transaction", "signature", "gopher", "hangman", } // round holds the shared state for a single day's game. type round struct { word string // the secret word for this round (lowercase) guessed *avl.Tree // letter (string) -> struct{}; ordered set of guesses wrong int // number of wrong guesses so far } // rounds maps a day index (decimal string) to its round. Persisted across calls. var rounds = avl.NewTree() // dayIndex returns the current day index derived from block height. func dayIndex() int64 { return runtime.ChainHeight() / blocksPerDay } // wordForDay picks a word deterministically from the list for the given day. func wordForDay(day int64) string { n := int64(len(words)) idx := day % n if idx < 0 { idx += n } return words[idx] } // dayKey formats a day index as a stable decimal string key. func dayKey(day int64) string { neg := day < 0 if neg { day = -day } if day == 0 { return "0" } var b []byte for day > 0 { b = append([]byte{byte('0' + day%10)}, b...) day /= 10 } if neg { b = append([]byte{'-'}, b...) } return string(b) } // current returns (creating if needed) the round for the current day. func current() *round { day := dayIndex() key := dayKey(day) if v := rounds.Get(key); v != nil { return v.(*round) } r := &round{ word: wordForDay(day), guessed: avl.NewTree(), wrong: 0, } rounds.Set(key, r) return r } // normalizeLetter lowercases and validates a single a-z letter, returning it // and true on success. func normalizeLetter(letter string) (string, bool) { if len(letter) != 1 { return "", false } c := letter[0] if c >= 'A' && c <= 'Z' { c = c - 'A' + 'a' } if c < 'a' || c > 'z' { return "", false } return string(c), true } // isWon reports whether every letter of the word has been guessed. func (r *round) isWon() bool { for i := 0; i < len(r.word); i++ { if !r.guessed.Has(string(r.word[i])) { return false } } return true } // isLost reports whether the wrong-count has reached the limit. func (r *round) isLost() bool { return r.wrong >= maxWrong } // over reports whether the round is finished either way. func (r *round) over() bool { return r.isWon() || r.isLost() } // Guess submits a single letter for the current day's shared game. It reveals // matching letters or costs a life. Aborts on bad input or when the game is // already over. func Guess(cur realm, letter string) { c, ok := normalizeLetter(letter) if !ok { panic("guess must be a single letter a-z") } r := current() if r.over() { panic("today's game is already over") } if r.guessed.Has(c) { panic("letter already guessed: " + c) } r.guessed.Set(c, struct{}{}) hit := strings.Contains(r.word, c) if !hit { r.wrong++ } caller := unsafeCaller(cur) chain.Emit( "Guess", "letter", c, "hit", boolStr(hit), "wrong", itoa(int64(r.wrong)), "by", caller, ) if r.isWon() { chain.Emit("Win", "day", dayKey(dayIndex()), "word", r.word) } else if r.isLost() { chain.Emit("Lose", "day", dayKey(dayIndex()), "word", r.word) } } // unsafeCaller returns the caller address as a string, preferring the crossing // convention when available. func unsafeCaller(cur realm) string { if cur.IsCurrent() { return cur.Previous().Address().String() } return "" } // boolStr renders a bool compactly. func boolStr(b bool) string { if b { return "true" } return "false" } // itoa renders an int64 as decimal (avl-free tiny helper reused by Render). func itoa(n int64) string { return dayKey(n) } // masked returns the word with un-guessed letters shown as underscores. func (r *round) masked() string { var sb strings.Builder for i := 0; i < len(r.word); i++ { ch := string(r.word[i]) if r.guessed.Has(ch) { sb.WriteString(ch) } else { sb.WriteString("_") } if i != len(r.word)-1 { sb.WriteString(" ") } } return sb.String() } // guessedList returns the guessed letters in sorted order, space-joined. func (r *round) guessedList() string { var out []string r.guessed.Iterate("", "", func(key string, _ interface{}) bool { out = append(out, key) return false }) return strings.Join(out, " ") } // gallows renders the ASCII stage for the given wrong-count (0..6). func gallows(wrong int) string { stages := []string{ // 0 " +---+\n | |\n |\n |\n |\n |\n=========", // 1 " +---+\n | |\n O |\n |\n |\n |\n=========", // 2 " +---+\n | |\n O |\n | |\n |\n |\n=========", // 3 " +---+\n | |\n O |\n /| |\n |\n |\n=========", // 4 " +---+\n | |\n O |\n /|\\ |\n |\n |\n=========", // 5 " +---+\n | |\n O |\n /|\\ |\n / |\n |\n=========", // 6 " +---+\n | |\n O |\n /|\\ |\n / \\ |\n |\n=========", } if wrong < 0 { wrong = 0 } if wrong >= len(stages) { wrong = len(stages) - 1 } return stages[wrong] } // Render shows the current day's game: gallows, masked word, guesses, status. func Render(path string) string { r := current() var sb strings.Builder sb.WriteString("# 🪢 Hangman\n\n") sb.WriteString("A new secret word every day. Everyone shares the same game — guess a letter and see it revealed for all.\n\n") sb.WriteString("Day: `" + dayKey(dayIndex()) + "` · Block: `" + itoa(runtime.ChainHeight()) + "`\n\n") sb.WriteString("```\n") sb.WriteString(gallows(r.wrong)) sb.WriteString("\n```\n\n") sb.WriteString("## Word\n\n") sb.WriteString("`" + r.masked() + "`\n\n") sb.WriteString("Wrong: **" + itoa(int64(r.wrong)) + " / " + itoa(int64(maxWrong)) + "** · Lives left: **" + itoa(int64(maxWrong-r.wrong)) + "**\n\n") guesses := r.guessedList() if guesses == "" { guesses = "_(none yet)_" } sb.WriteString("Guessed letters: " + guesses + "\n\n") sb.WriteString("## Status\n\n") switch { case r.isWon(): sb.WriteString("🎉 **Solved!** The word was **" + r.word + "**.\n\n") case r.isLost(): sb.WriteString("💀 **Game over.** The word was **" + r.word + "**. Try again next day.\n\n") default: sb.WriteString("Game in progress. Call `Guess(\"a\")` with a letter.\n\n") } sb.WriteString("---\n\n") sb.WriteString("### How to play\n\n") sb.WriteString("- `Guess(cur, \"e\")` — guess a single letter `a`–`z`.\n") sb.WriteString("- A hit reveals every matching position; a miss costs one of 6 lives.\n") sb.WriteString("- The word rotates every ~" + itoa(int64(blocksPerDay)) + " blocks.\n") return sb.String() }
  8. #8/gno.MemPackageType
  9. #9 MPUserProd

Result log

msg:0,success:true,log:,events:[]

← Back to block 26,355