Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

spec.gno

3.86 Kb · 153 lines
  1package arena
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"strings"
  7
  8	"gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0"
  9)
 10
 11// An arena is created off chain, by the game server, at a player's request.
 12// It is described by a spec, a string of fields separated by "|":
 13//
 14//	v1|mode|stake|minSeats|maxSeats|lives|seconds|killTarget|creator|created|nonce
 15//
 16//	v1|deathmatch|10000000|2|8|3|900|5|g1abc...|1760000000|k3x9
 17//
 18// and known by its id, the first 24 hex characters of the SHA-256 of the
 19// spec. The first player to pay for an arena hands the realm its spec; the
 20// realm checks it and stores the arena under the id it computes. Nobody can
 21// change the stake or the rules of an id: a different spec is a different
 22// id.
 23//
 24// created is the server's clock when the arena was proposed. An arena takes
 25// entries for an hour from then, and its lobby can be closed by anyone after
 26// that, see ExpireLobby.
 27
 28// Bounds of a spec, the same as the game server's.
 29const (
 30	specVersion = "v1"
 31	idLen       = 24
 32
 33	minStake      = 1000000    // ugnot, 1 GNOT
 34	maxStake      = 1000000000 // ugnot, 1000 GNOT
 35	minSeconds    = 300        // 5 minutes
 36	maxSeconds    = 1800       // 30 minutes
 37	entryWindow   = 3600       // seconds an arena takes entries after it was created
 38	clockSkew     = 300        // seconds a server clock may run ahead of the chain
 39	maxNonceLen   = 16
 40	maxSpecLength = 160
 41
 42	// feeBps is the house's share of every settled game, for the treasury.
 43	feeBps = 500
 44)
 45
 46// spec is a parsed arena spec.
 47type spec struct {
 48	mode       string
 49	stake      int64
 50	minSeats   int64
 51	maxSeats   int64
 52	lives      int64
 53	seconds    int64
 54	killTarget int64
 55	creator    address
 56	created    int64
 57	nonce      string
 58}
 59
 60// idOf returns the id of a spec string.
 61func idOf(s string) string {
 62	sum := sha256.Sum256([]byte(s))
 63	return hex.EncodeToString(sum[:])[:idLen]
 64}
 65
 66// parseSpec reads and checks a spec string. It panics on anything the realm
 67// would not play.
 68func parseSpec(s string) spec {
 69	if len(s) > maxSpecLength {
 70		panic(errInvalidSpec)
 71	}
 72	f := strings.Split(s, "|")
 73	if len(f) != 11 || f[0] != specVersion {
 74		panic(errInvalidSpec)
 75	}
 76	num := func(i int) int64 {
 77		n, ok := digits(f[i])
 78		if !ok {
 79			panic(errInvalidSpec)
 80		}
 81		return n
 82	}
 83	sp := spec{
 84		mode:       f[1],
 85		stake:      num(2),
 86		minSeats:   num(3),
 87		maxSeats:   num(4),
 88		lives:      num(5),
 89		seconds:    num(6),
 90		killTarget: num(7),
 91		creator:    address(f[8]),
 92		created:    num(9),
 93		nonce:      f[10],
 94	}
 95	if payout.Lookup(sp.mode) == nil {
 96		panic(errInvalidMode)
 97	}
 98	if sp.stake < minStake || sp.stake > maxStake {
 99		panic(errInvalidStake)
100	}
101	if sp.minSeats < payout.MinPlayers || sp.minSeats > sp.maxSeats || sp.maxSeats > payout.MaxPlayers {
102		panic(errInvalidSeats)
103	}
104	if sp.lives < payout.MinLives || sp.lives > payout.MaxLives {
105		panic(errInvalidLives)
106	}
107	if sp.seconds < minSeconds || sp.seconds > maxSeconds {
108		panic(errInvalidSeconds)
109	}
110	if sp.killTarget > payout.MaxKillTarget {
111		panic(errInvalidKillTarget)
112	}
113	// A battle royale is won by staying in the air: a kill target would end
114	// it before that.
115	if sp.mode == payout.ModeBattleRoyale && sp.killTarget != 0 {
116		panic(errRoyaleTarget)
117	}
118	if !validAddress(sp.creator) {
119		panic(errInvalidAddress)
120	}
121	if !validNonce(sp.nonce) {
122		panic(errInvalidSpec)
123	}
124	return sp
125}
126
127// validNonce reports whether s is 1 to 16 characters of [a-z0-9].
128func validNonce(s string) bool {
129	if len(s) == 0 || len(s) > maxNonceLen {
130		return false
131	}
132	for i := 0; i < len(s); i++ {
133		c := s[i]
134		if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') {
135			return false
136		}
137	}
138	return true
139}
140
141// validID reports whether s can be an arena id: 24 lowercase hex characters.
142func validID(s string) bool {
143	if len(s) != idLen {
144		return false
145	}
146	for i := 0; i < len(s); i++ {
147		c := s[i]
148		if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') {
149			return false
150		}
151	}
152	return true
153}