1// Package trivia is a rotating multiple-choice trivia quiz realm.2//3// One question is live at a time. Anyone can call Answer with a choice4// index; the first caller to answer correctly scores a point and the quiz5// advances to the next question. The starting question is picked6// deterministically from the block height at deploy time so repeated7// deploys don't always open on question one.8package trivia910import (11 "strconv"12 "strings"1314 "chain"15 "chain/runtime"1617 "gno.land/p/nt/avl/v0"18)1920// question is one fixed trivia entry.21type question struct {22 text string23 choices [4]string24 correctIdx int25}2627// round holds the mutable state of the currently open question.28type round struct {29 number int // 1-based round counter, increments forever30 qIdx int // index into questions for this round31 attempts int // guesses made against this round so far32 solved bool33 winner string34}3536var (37 questions = []question{38 {"What is the smallest prime number?", [4]string{"0", "1", "2", "3"}, 2},39 {"Which gas does a plant primarily absorb for photosynthesis?", [4]string{"Oxygen", "Nitrogen", "Carbon dioxide", "Hydrogen"}, 2},40 {"How many continents are there on Earth?", [4]string{"5", "6", "7", "8"}, 2},41 {"In binary, what is 1 + 1?", [4]string{"2", "10", "11", "0"}, 1},42 {"What does 'HTTP' stand for?", [4]string{"HyperText Transfer Protocol", "High Transfer Text Protocol", "Home Tool Transfer Protocol", "HyperText Transmission Path"}, 0},43 {"Which planet is known as the Red Planet?", [4]string{"Venus", "Jupiter", "Mars", "Saturn"}, 2},44 {"What is the time complexity of binary search?", [4]string{"O(n)", "O(log n)", "O(n^2)", "O(1)"}, 1},45 {"Who wrote the original Go proverb 'Don't communicate by sharing memory'?", [4]string{"Linus Torvalds", "Rob Pike", "Guido van Rossum", "Brendan Eich"}, 1},46 }4748 current *round49 scores = avl.NewTree() // addr(string) -> *int, points across all rounds5051 totalCorrect int // lifetime count of correctly-answered questions52)5354func init() {55 current = newRound(1, runtime.ChainHeight())56}5758// newRound builds a fresh round. The question index cycles through the fixed59// list, offset by a height-derived starting point so consecutive deploys60// don't all open on the same question.61func newRound(number int, height int64) *round {62 return &round{63 number: number,64 qIdx: questionIndex(number, height),65 }66}6768// questionIndex maps a round number and a height (entropy source for the69// starting offset) to a slot in the questions slice. Deterministic and pure70// so it's easy to unit test.71func questionIndex(number int, height int64) int {72 if height < 0 {73 height = -height74 }75 n := len(questions)76 offset := int(height % int64(n))77 return (offset + number - 1) % n78}7980// Answer submits a choice (0..3) for the current question. Crossing81// function: caller invokes as Answer(cross(cur), choiceIdx).82func Answer(cur realm, choiceIdx int) string {83 if !cur.IsCurrent() {84 panic("spoofed realm")85 }86 q := questions[current.qIdx]87 if choiceIdx < 0 || choiceIdx >= len(q.choices) {88 panic("choice must be in 0.." + strconv.Itoa(len(q.choices)-1))89 }90 if current.solved {91 panic("this question is already solved; the next one is already live")92 }9394 current.attempts++95 caller := cur.Previous().Address().String()9697 if choiceIdx != q.correctIdx {98 return "Wrong — try again."99 }100101 current.solved = true102 current.winner = caller103 addScore(caller)104 totalCorrect++105106 chain.Emit("QuestionSolved",107 "round", strconv.Itoa(current.number),108 "winner", caller,109 "answer", q.choices[choiceIdx],110 )111112 solvedRound := current.number113 current = newRound(solvedRound+1, runtime.ChainHeight())114115 return "Correct! The answer was \"" + q.choices[choiceIdx] + "\". A new question is now live."116}117118// addScore bumps addr's lifetime point count.119func addScore(addr string) {120 if v := scores.Get(addr); v != nil {121 p := v.(*int)122 *p++123 return124 }125 one := 1126 scores.Set(addr, &one)127}128129func scoreOf(addr string) int {130 if v := scores.Get(addr); v != nil {131 return *(v.(*int))132 }133 return 0134}135136// leaderboardEntry is a snapshot row used only for rendering, sorted by137// score descending.138type leaderboardEntry struct {139 addr string140 score int141}142143func leaderboard() []leaderboardEntry {144 var rows []leaderboardEntry145 scores.Iterate("", "", func(addr string, v any) bool {146 rows = append(rows, leaderboardEntry{addr: addr, score: *(v.(*int))})147 return false148 })149 // simple insertion sort by score desc; leaderboards here stay small.150 for i := 1; i < len(rows); i++ {151 j := i152 for j > 0 && rows[j-1].score < rows[j].score {153 rows[j-1], rows[j] = rows[j], rows[j-1]154 j--155 }156 }157 return rows158}159160// Render produces the gnoweb Markdown view of the quiz.161func Render(path string) string {162 var b strings.Builder163164 b.WriteString("# Trivia Quiz\n\n")165 b.WriteString("A rotating on-chain trivia quiz. Call `Answer(choiceIdx)` with 0-3 ")166 b.WriteString("to answer the current question — first correct answer scores the point ")167 b.WriteString("and a new question goes live.\n\n")168169 q := questions[current.qIdx]170 b.WriteString("## Round " + strconv.Itoa(current.number) + "\n\n")171 b.WriteString("**" + q.text + "**\n\n")172 letters := [4]string{"A", "B", "C", "D"}173 for i, c := range q.choices {174 b.WriteString("- **" + letters[i] + "** (" + strconv.Itoa(i) + "): " + c + "\n")175 }176 b.WriteString("\n- Attempts so far this round: " + strconv.Itoa(current.attempts) + "\n\n")177178 b.WriteString("## Leaderboard\n\n")179 rows := leaderboard()180 if len(rows) == 0 {181 b.WriteString("_No one has scored yet — be the first!_\n\n")182 } else {183 b.WriteString("| Player | Points |\n|---|---|\n")184 for _, r := range rows {185 b.WriteString("| `" + r.addr + "` | " + strconv.Itoa(r.score) + " |\n")186 }187 b.WriteString("\n")188 }189190 b.WriteString("Total questions answered correctly across all rounds: " + strconv.Itoa(totalCorrect) + "\n\n")191192 b.WriteString("## How to play\n\n")193 b.WriteString("```\n")194 b.WriteString("gnokey maketx call -pkgpath gno.land/r/g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz/trivia \\\n")195 b.WriteString(" -func Answer -args <0|1|2|3> ...\n")196 b.WriteString("```\n")197198 return b.String()199}200Answer(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}, choiceIdx int) string
Render(path string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.