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}