pingpong.gno
2.32 Kb · 82 lines
1// Package pingpong is a two-player game where the only move is to pass the
2// turn. It exists to demonstrate gnotif: every turn emits TurnPlayed, and
3// the realm declares a verified gnotif trigger that tells the next player.
4package pingpong
5
6import (
7 "chain"
8 "strconv"
9 "strings"
10
11 "gno.land/p/nt/avl/v0"
12 "gno.land/p/nt/seqid/v0"
13
14 "gno.land/r/g1sl6fyq6mrc3wydt8xluhxx4aqsudpp9vhv6g2a/gnotif"
15)
16
17// Game is a match between First and Second. First plays turn 1.
18type Game struct {
19 ID string
20 First, Second, Next address
21 Turn int
22}
23
24var (
25 games avl.Tree // id → *Game
26 lastGame seqid.ID
27 declared bool
28)
29
30// NewGame opens a game against opponent and returns its id. The caller plays first.
31func NewGame(cur realm, opponent address) string {
32 caller := cur.Previous().Address()
33 if opponent == caller {
34 panic("cannot play against yourself")
35 }
36 g := &Game{ID: lastGame.Next().String(), First: caller, Second: opponent, Next: caller}
37 games.Set(g.ID, g)
38 chain.Emit("GameCreated", "game", g.ID, "first", g.First.String(), "second", g.Second.String())
39 return g.ID
40}
41
42// Play takes the caller's turn in game and passes it to the other player.
43func Play(cur realm, game string) {
44 v := games.Get(game)
45 if v == nil {
46 panic("unknown game")
47 }
48 g := v.(*Game)
49 if g.Next != cur.Previous().Address() {
50 panic("not your turn")
51 }
52 if g.Next == g.First {
53 g.Next = g.Second
54 } else {
55 g.Next = g.First
56 }
57 g.Turn++
58 chain.Emit("TurnPlayed", "game", g.ID, "next", g.Next.String(), "turn", strconv.Itoa(g.Turn))
59}
60
61// DeclareTriggers declares the "your turn" trigger in the gnotif registry.
62// The registry sees this realm as the caller, so the trigger is verified.
63func DeclareTriggers(cur realm) {
64 if declared {
65 panic("triggers already declared")
66 }
67 declared = true
68 gnotif.Declare(cross(cur), cur.PkgPath(), "TurnPlayed", "", "next", "Your turn", "Game {game}, turn {turn}", "/?game={game}")
69}
70
71// Render lists the games with their players, turn and next player.
72func Render(path string) string {
73 var b strings.Builder
74 b.WriteString("# pingpong\n\n")
75 games.Iterate("", "", func(_ string, v any) bool {
76 g := v.(*Game)
77 b.WriteString("- **" + g.ID + "** " + g.First.String() + " vs " + g.Second.String() +
78 " · turn " + strconv.Itoa(g.Turn) + " · next " + g.Next.String() + "\n")
79 return false
80 })
81 return b.String()
82}