Transaction

3B4E9DB9B08A63…DCD9E9425842

Block 25,996 · index 0 · indexed

Summary

Hash
3B4E9DB9B08A63F53B6EBCF3C456C746277E8765D3BF9740F786DCD9E9425842
Block
25,996
Size
9013 bytes
Gas used
13,326,550 / 15,991,920
Fee
1000000ugnot
Memo
gnopublish
Status
success

Messages

Arguments · 9

  1. #1bullscows
  2. #2README.md
  3. #3# Bulls & Cows > ⚠️ **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) --- A shared on-chain [Bulls & Cows](https://en.wikipedia.org/wiki/Bulls_and_Cows) puzzle (the classic ancestor of Mastermind and Wordle-for-digits). One secret 4-digit code with no repeated digits is live at a time; anyone can call `Guess` with a candidate code and gets back **bulls** (right digit, right position) and **cows** (right digit, wrong position). First caller to land 4 bulls cracks the round, lands on the leaderboard, and a fresh secret is drawn immediately so the next round starts at once. Realm: `gno.land/r/g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz/bullscows` ## Example calls ``` Guess("1972") -> "1972 -> 1 bulls, 2 cows" Guess("7192") -> "*** SOLVED *** 7192 was it — round 3 cracked in 5 guesses. New round 4 has begun, good luck!" GiveUp() -> "round 4's code was 4082 — round 5 is live now." ``` `Render("")` shows the current round, attempts so far, the last 10 guesses with their bulls/cows, and the top-5 fastest-solve leaderboard. The secret is generated deterministically from the on-chain block height and the round counter via a small LCG (`newSecret`/`nextRand` in `bullscows.gno`) — no external randomness needed, and nobody can precompute the next code before its round actually starts. ## Toolchain status Verified locally with `gno test -v .` and `gno lint .` against a `gno.land/gnolang/gno` master checkout used as `GNOROOT` (the default installed `gno` binary had a stale/ missing `GNOROOT`, so `GNOROOT=<path-to-gnolang/gno> gno test .` was used instead). All 7 tests pass, lint is clean. This exercises current sapphire (gno 0.9) stdlib paths (`chain/runtime`, no `std` package) and the `cur realm` / `cross(cur)` crossing convention. <!-- 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. > 🧪 **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. #4bullscows.gno
  5. #5// Package bullscows is a shared on-chain Bulls & Cows puzzle: one secret // 4-digit code (distinct digits) per round, guessed cooperatively/competitively // by anyone who calls Guess. Bulls = right digit, right spot. Cows = right // digit, wrong spot. Whoever hits 4 bulls wins the round and a leaderboard // entry; a fresh secret is drawn immediately after. package bullscows import ( "chain/runtime" "strconv" "strings" ) const ( maxHistory = 10 maxRecords = 5 ) type guessLog struct { player string guess string bulls int cows int } type record struct { player string attempts int round int } var ( secret [4]int round int attempts int history []guessLog records []record ) func init() { round = 1 secret = newSecret(seedFor(round)) } // seedFor derives a per-round PRNG seed from the current chain height so // nobody can precompute the next secret before the round actually starts. func seedFor(r int) int64 { return runtime.ChainHeight()*1000003 + int64(r)*7919 + 17 } // nextRand is a minimal LCG (glibc constants) — no crypto strength needed // for a casual puzzle, just enough spread across digits. func nextRand(seed int64) int64 { return (seed*1103515245 + 12345) & 0x7fffffff } func newSecret(seed int64) [4]int { var used [10]bool var digits [4]int s := seed for i := 0; i < 4; i++ { for { s = nextRand(s) d := int(s % 10) if !used[d] { used[d] = true digits[i] = d break } } } return digits } // parseGuess validates a 4-digit, distinct-digit guess string. func parseGuess(guess string) ([4]int, bool) { var out [4]int if len(guess) != 4 { return out, false } var used [10]bool for i := 0; i < 4; i++ { c := guess[i] if c < '0' || c > '9' { return out, false } d := int(c - '0') if used[d] { return out, false } used[d] = true out[i] = d } return out, true } // score returns (bulls, cows) for guess against target, assuming both have // distinct digits (guaranteed by parseGuess / newSecret). func score(target, guess [4]int) (int, int) { bulls, cows := 0, 0 for i := 0; i < 4; i++ { if guess[i] == target[i] { bulls++ continue } for j := 0; j < 4; j++ { if i != j && guess[i] == target[j] { cows++ break } } } return bulls, cows } func addHistory(l guessLog) { history = append(history, l) if len(history) > maxHistory { history = history[len(history)-maxHistory:] } } // addRecord keeps the top maxRecords fastest solves, ascending by attempts. func addRecord(r record) { records = append(records, r) for i := len(records) - 1; i > 0 && records[i].attempts < records[i-1].attempts; i-- { records[i], records[i-1] = records[i-1], records[i] } if len(records) > maxRecords { records = records[:maxRecords] } } func startNewRound() { round++ attempts = 0 history = nil secret = newSecret(seedFor(round)) } // Guess submits a 4-distinct-digit code against the current round's secret. // Returns the bulls/cows feedback, or "solved!" text when it's a win. func Guess(cur realm, guess string) string { digits, ok := parseGuess(guess) if !ok { panic("guess must be 4 digits, 0-9, no repeats (e.g. \"1972\")") } player := cur.Previous().Address().String() attempts++ bulls, cows := score(secret, digits) addHistory(guessLog{player: player, guess: guess, bulls: bulls, cows: cows}) if bulls == 4 { wonRound := round wonAttempts := attempts addRecord(record{player: player, attempts: wonAttempts, round: wonRound}) startNewRound() return "*** SOLVED *** " + guess + " was it — round " + strconv.Itoa(wonRound) + " cracked in " + strconv.Itoa(wonAttempts) + " guesses. New round " + strconv.Itoa(round) + " has begun, good luck!" } return guess + " -> " + strconv.Itoa(bulls) + " bulls, " + strconv.Itoa(cows) + " cows" } // GiveUp reveals the current secret and starts a fresh round without // awarding a leaderboard record. func GiveUp(cur realm) string { revealed := digitsToString(secret) oldRound := round startNewRound() return "round " + strconv.Itoa(oldRound) + "'s code was " + revealed + " — round " + strconv.Itoa(round) + " is live now." } func digitsToString(d [4]int) string { var sb strings.Builder for _, v := range d { sb.WriteString(strconv.Itoa(v)) } return sb.String() } func Render(path string) string { var sb strings.Builder sb.WriteString("# Bulls & Cows\n\n") sb.WriteString("One shared secret 4-digit code (no repeated digits). Call `Guess(\"1972\")` ") sb.WriteString("to get **bulls** (right digit, right spot) and **cows** (right digit, wrong spot). ") sb.WriteString("First to 4 bulls wins the round; `GiveUp()` reveals the code and starts over.\n\n") sb.WriteString("## Round " + strconv.Itoa(round) + "\n\n") sb.WriteString("Attempts so far: **" + strconv.Itoa(attempts) + "**\n\n") if len(history) == 0 { sb.WriteString("_No guesses yet this round — be the first._\n\n") } else { sb.WriteString("### Recent guesses\n\n") sb.WriteString("| player | guess | bulls | cows |\n|---|---|---|---|\n") for i := len(history) - 1; i >= 0; i-- { h := history[i] sb.WriteString("| " + shortAddr(h.player) + " | " + h.guess + " | " + strconv.Itoa(h.bulls) + " | " + strconv.Itoa(h.cows) + " |\n") } sb.WriteString("\n") } sb.WriteString("## Leaderboard (fastest solves)\n\n") if len(records) == 0 { sb.WriteString("_Nobody has cracked a code yet._\n") } else { sb.WriteString("| rank | player | attempts | round |\n|---|---|---|---|\n") for i, r := range records { sb.WriteString("| " + strconv.Itoa(i+1) + " | " + shortAddr(r.player) + " | " + strconv.Itoa(r.attempts) + " | " + strconv.Itoa(r.round) + " |\n") } } return sb.String() } func shortAddr(a string) string { if len(a) <= 12 { return a } return a[:6] + "…" + a[len(a)-4:] }
  6. #6gnomod.toml
  7. #7module = "gno.land/r/moul/x/daily/bullscows/v0" gno = "0.9"
  8. #8/gno.MemPackageType
  9. #9 MPUserProd

Result log

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

← Back to block 25,996