shifumi.gno
2.68 Kb ยท 120 lines
1package shifumi
2
3import (
4 "errors"
5 "std"
6 "strconv"
7
8 "gno.land/p/demo/avl"
9 "gno.land/p/demo/seqid"
10
11 "gno.land/r/sys/users"
12)
13
14const (
15 empty = iota
16 rock
17 paper
18 scissors
19 last
20)
21
22type game struct {
23 player1, player2 std.Address // shifumi is a 2 players game
24 move1, move2 int // can be empty, rock, paper, or scissors
25}
26
27var games avl.Tree
28var id seqid.ID
29
30func (g *game) play(player std.Address, move int) error {
31 if !(move > empty && move < last) {
32 return errors.New("invalid move")
33 }
34 if player != g.player1 && player != g.player2 {
35 return errors.New("invalid player")
36 }
37 if player == g.player1 && g.move1 == empty {
38 g.move1 = move
39 return nil
40 }
41 if player == g.player2 && g.move2 == empty {
42 g.move2 = move
43 return nil
44 }
45 return errors.New("already played")
46}
47
48func (g *game) winner() int {
49 if g.move1 == empty || g.move2 == empty {
50 return -1
51 }
52 if g.move1 == g.move2 {
53 return 0
54 }
55 if g.move1 == rock && g.move2 == scissors ||
56 g.move1 == paper && g.move2 == rock ||
57 g.move1 == scissors && g.move2 == paper {
58 return 1
59 }
60 return 2
61}
62
63// NewGame creates a new game where player1 is the caller and player2 the argument.
64// A new game index is returned.
65func NewGame(player std.Address) int {
66 games.Set(id.Next().String(), &game{player1: std.PreviousRealm().Address(), player2: player})
67 return int(id)
68}
69
70// Play executes a move for the game at index idx, where move can be:
71// 1 (rock), 2 (paper), 3 (scissors).
72func Play(idx, move int) {
73 v, ok := games.Get(seqid.ID(idx).String())
74 if !ok {
75 panic("game not found")
76 }
77 if err := v.(*game).play(std.PreviousRealm().Address(), move); err != nil {
78 panic(err)
79 }
80}
81
82func Render(path string) string {
83 mov1 := []string{"", " ๐ค ", " ๐ซฑ ", " ๐ "}
84 mov2 := []string{"", " ๐ค ", " ๐ซฒ ", " ๐ "}
85 win := []string{"pending", "draw", "player1", "player2"}
86
87 output := `# ๐ โ โ๏ธ Shifumi
88Actions:
89* [NewGame](shifumi$help&func=NewGame) opponentAddress
90* [Play](shifumi$help&func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)
91
92 game | player1 | | player2 | | win
93 --- | --- | --- | --- | --- | ---
94`
95 // Output the 100 most recent games.
96 maxGames := 100
97 for n := int(id); n > 0 && int(id)-n < maxGames; n-- {
98 v, ok := games.Get(seqid.ID(n).String())
99 if !ok {
100 continue
101 }
102 g := v.(*game)
103 output += strconv.Itoa(n) + " | " +
104 shortName(g.player1) + " | " + mov1[g.move1] + " | " +
105 shortName(g.player2) + " | " + mov2[g.move2] + " | " +
106 win[g.winner()+1] + "\n"
107 }
108 return output
109}
110
111func shortName(addr std.Address) string {
112 user := users.ResolveAddress(addr)
113 if user != nil {
114 return user.Name()
115 }
116 if len(addr) < 10 {
117 return string(addr)
118 }
119 return string(addr)[:10] + "..."
120}