Skip to content

Commit

Permalink
feat(examples): a shifumi (rock, paper, scissors) smart contract (#2629)
Browse files Browse the repository at this point in the history
A very simple game as a smart contract. It's possible to play against
yourself, or another opponent. UI could be improved.

No provision against cheating, no gain or automatic action.

Related to #611.

<img width="1003" alt="Screenshot 2024-07-24 at 16 24 28"
src="https://github.com/user-attachments/assets/a7c115ae-26c1-4d3b-9446-8571f1d98efb">

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] Added new tests, or not needed, or not feasible
- [*] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [*] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [*] Added references to related issues and PRs
- [*] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
  • Loading branch information
mvertes authored Aug 30, 2024
1 parent e5c1152 commit 701ac17
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
7 changes: 7 additions & 0 deletions examples/gno.land/r/demo/games/shifumi/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/r/demo/games/shifumi

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/seqid v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
)
120 changes: 120 additions & 0 deletions examples/gno.land/r/demo/games/shifumi/shifumi.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package shifumi

import (
"errors"
"std"
"strconv"

"gno.land/p/demo/avl"
"gno.land/p/demo/seqid"

"gno.land/r/demo/users"
)

const (
empty = iota
rock
paper
scissors
last
)

type game struct {
player1, player2 std.Address // shifumi is a 2 players game
move1, move2 int // can be empty, rock, paper, or scissors
}

var games avl.Tree
var id seqid.ID

func (g *game) play(player std.Address, move int) error {
if !(move > empty && move < last) {
return errors.New("invalid move")
}
if player != g.player1 && player != g.player2 {
return errors.New("invalid player")
}
if player == g.player1 && g.move1 == empty {
g.move1 = move
return nil
}
if player == g.player2 && g.move2 == empty {
g.move2 = move
return nil
}
return errors.New("already played")
}

func (g *game) winner() int {
if g.move1 == empty || g.move2 == empty {
return -1
}
if g.move1 == g.move2 {
return 0
}
if g.move1 == rock && g.move2 == scissors ||
g.move1 == paper && g.move2 == rock ||
g.move1 == scissors && g.move2 == paper {
return 1
}
return 2
}

// NewGame creates a new game where player1 is the caller and player2 the argument.
// A new game index is returned.
func NewGame(player std.Address) int {
games.Set(id.Next().String(), &game{player1: std.PrevRealm().Addr(), player2: player})
return int(id)
}

// Play executes a move for the game at index idx, where move can be:
// 1 (rock), 2 (paper), 3 (scissors).
func Play(idx, move int) {
v, ok := games.Get(seqid.ID(idx).String())
if !ok {
panic("game not found")
}
if err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil {
panic(err)
}
}

func Render(path string) string {
mov1 := []string{"", " 🤜 ", " 🫱 ", " 👉 "}
mov2 := []string{"", " 🤛 ", " 🫲 ", " 👈 "}
win := []string{"pending", "draw", "player1", "player2"}

output := `# 👊 ✋ ✌️ Shifumi
Actions:
* [NewGame](shifumi?help&__func=NewGame) opponentAddress
* [Play](shifumi?help&__func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)
game | player1 | | player2 | | win
--- | --- | --- | --- | --- | ---
`
// Output the 100 most recent games.
maxGames := 100
for n := int(id); n > 0 && int(id)-n < maxGames; n-- {
v, ok := games.Get(seqid.ID(n).String())
if !ok {
continue
}
g := v.(*game)
output += strconv.Itoa(n) + " | " +
shortName(g.player1) + " | " + mov1[g.move1] + " | " +
shortName(g.player2) + " | " + mov2[g.move2] + " | " +
win[g.winner()+1] + "\n"
}
return output
}

func shortName(addr std.Address) string {
user := users.GetUserByAddress(addr)
if user != nil {
return user.Name
}
if len(addr) < 10 {
return string(addr)
}
return string(addr)[:10] + "..."
}

0 comments on commit 701ac17

Please sign in to comment.