← Back to Realms

Realm

gno.land/r/moul/x/daily/eightball/v1

Overview

Realm Path
gno.land/r/moul/x/daily/eightball/v1
Exported Functions
3
State Entries
3
Source Files
4
Total Package Entries
7

Exported Functions

3 exported functions

State

3 state entries

Source Code

FILES
eightball.gno
go
1// Package eightball is a Magic 8-Ball realm: ask a yes/no question and the
2// block height decides your fate. Deterministic (no real randomness on-chain).
3package eightball
4
5import (
6	"chain"
7	"chain/runtime"
8	"chain/runtime/unsafe"
9	"strconv"
10	"strings"
11
12	"gno.land/p/moul/kit/ui/v0"
13)
14
15// The 20 classic Magic 8-Ball answers.
16var answers = []string{
17	"It is certain.", "It is decidedly so.", "Without a doubt.",
18	"Yes definitely.", "You may rely on it.", "As I see it, yes.",
19	"Most likely.", "Outlook good.", "Yes.", "Signs point to yes.",
20	"Reply hazy, try again.", "Ask again later.", "Better not tell you now.",
21	"Cannot predict now.", "Concentrate and ask again.",
22	"Don't count on it.", "My reply is no.", "My sources say no.",
23	"Outlook not so good.", "Very doubtful.",
24}
25
26type shake struct {
27	asker    string
28	question string
29	answer   string
30	height   int64
31}
32
33var history []shake
34
35// answerIndex maps (block height, prior shakes) to an answer slot. Pure and
36// deterministic so it can be tested without touching realm state.
37func answerIndex(height int64, n int) int {
38	i := (height + int64(n)) % int64(len(answers))
39	if i < 0 {
40		i += int64(len(answers))
41	}
42	return int(i)
43}
44
45// Ask poses a question to the Magic 8-Ball and returns its answer. Crossing
46// function: it mutates persistent state, so it takes `cur realm` (gno 0.9).
47func Ask(cur realm, question string) string {
48	question = strings.TrimSpace(question)
49	if question == "" {
50		panic("question must not be empty")
51	}
52	if len(question) > 200 {
53		panic("question too long (max 200 chars)")
54	}
55	h := runtime.ChainHeight()
56	ans := answers[answerIndex(h, len(history))]
57	history = append(history, shake{
58		asker:    unsafe.PreviousRealm().Address().String(),
59		question: question,
60		answer:   ans,
61		height:   h,
62	})
63	chain.Emit("Shaken", "answer", ans, "height", strconv.FormatInt(h, 10))
64	return ans
65}
66
67// Asked returns how many questions have been asked. Read-only.
68func Asked() int { return len(history) }
69
70// Render is the gnoweb Markdown view.
71func Render(path string) string {
72	var b strings.Builder
73	b.WriteString("# 🎱 Magic 8-Ball\n\n")
74	b.WriteString("> ⚠️ Experimental, auto-generated with no human supervision (MCP test corpus). ")
75	b.WriteString("Not audited. See [r/moul/x/daily](https://github.com/moul/gno-contracts/blob/main/r/moul/x/daily/README.md).\n\n")
76	b.WriteString("Ask a yes/no question — the block height decides your fate.\n\n")
77	b.WriteString("**Questions asked:** ")
78	b.WriteString(strconv.Itoa(len(history)))
79	b.WriteString("\n\n")
80
81	if len(history) == 0 {
82		b.WriteString("_No questions yet. Call `Ask(\"will it rain tomorrow?\")`._\n")
83		return b.String()
84	}
85
86	last := history[len(history)-1]
87	b.WriteString("Last shake: 🎱 _")
88	b.WriteString(last.answer)
89	b.WriteString("_\n\n| # | asker | question | answer | height |\n")
90	b.WriteString("|---|---|---|---|---|\n")
91
92	start := len(history) - 1
93	end := start - 14
94	if end < 0 {
95		end = 0
96	}
97	for i := start; i >= end; i-- {
98		e := history[i]
99		b.WriteString("| ")
100		b.WriteString(strconv.Itoa(i + 1))
101		b.WriteString(" | ")
102		b.WriteString(ui.AddrOf(e.asker))
103		b.WriteString(" | ")
104		b.WriteString(e.question)
105		b.WriteString(" | ")
106		b.WriteString(e.answer)
107		b.WriteString(" | ")
108		b.WriteString(strconv.FormatInt(e.height, 10))
109		b.WriteString(" |\n")
110	}
111	return b.String()
112}
113

Raw Package Data

Raw JSON data