rps.gno
1.44 Kb · 56 lines
1// Package rps is the rock-paper-scissors move: parsing it, naming it, and
2// deciding a throw. It holds no state, so every realm that plays the game
3// agrees on what "r" means and who wins.
4//
5// Imported by [r/moul/x/daily/rps](/r/moul/x/daily/rps/v1),
6// [r/moul/x/daily/rpsmatch](/r/moul/x/daily/rpsmatch/v1),
7// [r/moul/x/daily/rpsoracle](/r/moul/x/daily/rpsoracle/v1) and
8// [r/moul/x/daily/rpsduel](/r/moul/x/daily/rpsduel/v1).
9package rps
10
11import "strings"
12
13// Move is one of rock, paper, or scissors, ordered so that
14// (winner - loser + 3) % 3 == 1 for every winning pair.
15type Move int
16
17const (
18 Rock Move = iota
19 Paper
20 Scissors
21)
22
23// String returns "rock", "paper" or "scissors", and "?" for anything else.
24func (m Move) String() string {
25 switch m {
26 case Rock:
27 return "rock"
28 case Paper:
29 return "paper"
30 case Scissors:
31 return "scissors"
32 default:
33 return "?"
34 }
35}
36
37// Parse reads a move, case-insensitively and ignoring surrounding spaces.
38// The full name and its first letter are both accepted.
39func Parse(s string) (Move, bool) {
40 switch strings.ToLower(strings.TrimSpace(s)) {
41 case "rock", "r":
42 return Rock, true
43 case "paper", "p":
44 return Paper, true
45 case "scissors", "s":
46 return Scissors, true
47 default:
48 return 0, false
49 }
50}
51
52// Beats decides a throw: 0 for a tie, 1 if a beats b, 2 if b beats a.
53// The three values are (a - b) mod 3, which is why Move is ordered as it is.
54func Beats(a, b Move) int {
55 return (int(a) - int(b) + 3) % 3
56}