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.

v0 source realm

Package gnoslinger is the on-chain lobby and referee of Gunslinger showdowns (the 1982 Avalon Hill gunfight game) of ...

Overview

Package gnoslinger is the on-chain lobby and referee of Gunslinger showdowns (the 1982 Avalon Hill gunfight game) of two to four players. Players create and join games and play their turn plans hidden: each commits the hash of its plan, then reveals it once every plan is committed (DESIGN_HIDDEN_PLANS.md); the realm resolves each turn with the pure engine package and keeps the result for the web client to animate.

Constants 3

const realmPath, DefaultTimeout, MinTimeout, MaxTimeout, MinMaxTurns, MaxMaxTurns, MaxMissed, RevealGrace, LockDelay, MaxLocked, MaxSalt, DrandGenesis, DrandPeriod

 1const (
 2	realmPath = "gno.land/r/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/v0"
 3
 4	// DefaultTimeout is how long a turn may sit without progress before
 5	// anyone can call ClaimTimeout on it, when CreateGame gets 0.
 6	DefaultTimeout = 48 * time.Hour
 7	// MinTimeout and MaxTimeout bound the timeout a creator may pick.
 8	MinTimeout = 5 * time.Minute
 9	MaxTimeout = 30 * 24 * time.Hour
10	// MinMaxTurns and MaxMaxTurns bound the game length.
11	MinMaxTurns = 10
12	MaxMaxTurns = 30
13	// MaxMissed is the turns in a row a player may let time out: each is
14	// passed for it, the last puts its character out of the fight.
15	MaxMissed = 3
16	// RevealGrace is how long after the commit deadline a claim waits
17	// before it may pass the turn of a seat whose plan is not revealed: the
18	// timelocked plans open LockDelay after the deadline, and their opener
19	// needs time for its transaction.
20	RevealGrace = 5 * time.Minute
21	// LockDelay puts the drand round the plans are timelocked to after the
22	// deadline, past any drift between the block time and drand's clock.
23	LockDelay = 2 * time.Minute
24	// MaxLocked bounds the size of a timelocked plan.
25	MaxLocked = 1024
26	// MaxSalt bounds the size of a salt (hex).
27	MaxSalt = 64
28	// The drand quicknet chain the plans are timelocked to: its genesis
29	// (unix seconds) and the seconds between two rounds.
30	DrandGenesis = 1692803367
31	DrandPeriod  = 3
32)
source

const Denom, HouseCutPercent

1const (
2	// Denom is the only coin a stake is paid in.
3	Denom = "ugnot"
4	// HouseCutPercent is the share of a pot the house keeps, for events.
5	HouseCutPercent = 5
6)
source

Functions 20

func CancelGame

crossing Action
1func CancelGame(cur realm, id string)
source

CancelGame withdraws a game whose seats are not all taken yet. Creator only. The game is deleted, not kept as finished, so the creator gets back the storage deposit CreateGame paid; the players who had joined are unindexed with it, and every stake paid goes back.

func ClaimTimeout

crossing Action
1func ClaimTimeout(cur realm, id string)
source

ClaimTimeout ends the wait of a turn past its deadline. Anyone may call it. A game waiting for players has no timeout (its creator may cancel it). While the plans are committed, past the deadline: each player who did not commit misses the turn: it is passed for them, unless that makes MaxMissed in a row, which puts the character out of the fight (out of time counts as surrendering); the game then goes on to the reveals, and with nobody's plan in, the turn resolves empty. A showdown nobody plays any more so ends after MaxMissed claims, everyone out of the fight (its stakes given back, stakes.gno). While they are revealed, RevealGrace past the deadline (the timelocked plans had time to be opened, which is how a vanished player's plan normally comes in): every character whose plan is still not in misses the turn the same way. The realm cannot tell a copy nobody could open from one nobody tried to open, so it does not punish more than a missed turn. With one character left the game ends and is scored, with more the turn resolves with the plans in and the showdown goes on.

func Commit

crossing Action
1func Commit(cur realm, id string, turn int, hash string, locked string) int
source

Commit seals the caller's plan for the given turn, the one in progress, and returns the game revision (a commit meant for another turn, say one still on its way when time was called, is refused: its hash, which covers the turn, could never be revealed in this one, and it would take the seat's commit). hash is PlanHash of a salt and the plan, which a Reveal shows once every character still in the fight committed; locked is the salt and plan timelocked to the turn's LockRound (at most MaxLocked bytes, required: encrypting needs no drand node), which anyone can open and reveal from then if the player does not. When this commit is the last one the turn waits for, the game moves on to the reveals.

func CreateGame

crossing Action
1func CreateGame(cur realm, seats int, maxTurns int, timeoutMinutes int, startHex string, facing int) string
source

CreateGame opens a showdown of seats players (engine.MinPlayers to engine.MaxPlayers) with the caller in seat 0, playing the showdown's first character (engine.Setups: the marshal), standing in startHex, one of the hexes assigned to that character, facing f (0=N 1=NE 2=SE 3=S 4=SW 5=NW); it returns the game id. The other seats take the next characters in the order the players join. maxTurns is the game length, from MinMaxTurns to MaxMaxTurns; timeoutMinutes is the time each turn gives the players, past which anyone may claim it with ClaimTimeout, 0 meaning DefaultTimeout (2 days). The game stays open until its seats fill up or its creator cancels it: the turn clock starts with the last join. The ugnot sent with the call are the showdown's stake, which every joiner must send too; none, a free showdown (stakes.gno).

func DeclareTriggers

crossing Action
1func DeclareTriggers(cur realm)
source

DeclareTriggers sets the realm's triggers in the gnotif registry. Admin only, and the first call has what the registry's Open requires: the namespace owner's own key (not a session), in a call sent straight to the realm; so the deployer sends it once the realm is deployed (on gnodev, the make notif script sends it as test1). The triggers keep their ids from one call to the next. Every call makes the registry emit each trigger again (TriggerDeclared), which is what a gnotif server reads them from: the registry emits nothing for a trigger set as it already is, so each is first set with a nudged body, then as it should be. That is for gnodev, whose reload puts the earlier call in the genesis, where no indexer serves its events: the script calls again after each reload and the triggers reach gnotifd anew.

func GameJSON

Action
1func GameJSON(id string) string
source

GameJSON is the full view of a game:

Example
 1{"id","rev","phase","turn","maxTurns","timeout" (seconds),"board",
 2 "players":[{"addr" ("" for a seat nobody took yet),"name","char","hex" ("" until the seat set up),"facing","down","delay","status","committed","revealed","locked" (its timelocked plan, "" once revealed),"missed" (turns in a row it let time out),"playedFoot","playedRun","ranLastTurn",
 3             "aim","aimAt" (the seat the AIM markers follow, -1 when they sit on a hex or there is no aim),"aimHex" (the hex they sit on, "" when they follow a seat),"endurance","serious","gunArm","otherArm","leg",
 4             "guns":[{"id","type","name","location","cocked","shells","capacity","jammed","exploded"}]}]   (one entry per seat, 2 to 4),
 5 "ground":[{"id" (ground id, "g<id>" in a plan),"hex","guns":[{..}]}],
 6 "winner","vp":[victory points per seat],"awards":[{"p","reason","points"}] (rule 15, once a played game ends),
 7 "stake" (ugnot each player puts in, 0 for a free showdown),"pot" (the stakes paid so far),"payouts":[ugnot each seat got, [] until paid],"houseCut" (stakes.gno),"endReason",
 8 "createdAt","updatedAt","timeoutAt" (when the turn in progress can be claimed, 0 unless planning or revealing),
 9 "commitUntil" (the turn's deadline),"revealUntil" (when unrevealed seats can be claimed out),"lockRound" (the drand round the plans are timelocked to) (0 unless planning or revealing),
10 "lastTurn":null|{"turn","seed","cards":[result card numbers drawn, in order],"plans":[..],
11             "events":[{"seg","p","kind","action","from","to","facing","down","n","delay","reason","card","result","endurance","target","hit","range","gun","gunId","step","chart","status"}],
12             "start":[{"hex","facing","down","delay","status","aim","aimAt","aimHex","endurance","serious","gunArm","otherArm","leg","guns":[..]},..],
13             "ground":[the weapons on the ground at the turn's start, as "ground" above]}}

Times are unix seconds; winner, events[].p and events[].target are seat indexes, -1 for none. The plans of the turn in progress are hidden until revealed: only their commits show, as flags and timelocked plans.

func GameRev

Action
1func GameRev(id string) int
source

GameRev is the cheapest way to poll a game for changes.

func GamesJSON

Action
1func GamesJSON(addr string) string
source

GamesJSON lists, for an address, the games with a free seat, the games being played (live: planning or revealing, for anyone to watch), the last endedLimit games finished (ended, for anyone to replay) and the games it plays or played, newest first (at most 50 each), with the address's registered name ("" if none). live and ended are found among the liveScan newest games, a running game being recent: a turn times out within days.

Example
1{"name","open":[summary...],"live":[summary...],"ended":[summary...],"mine":[summary...]}
2summary = {"id","rev","phase","turn","maxTurns","timeout","winner","endReason","vp","stake","pot","payouts","houseCut","createdAt","updatedAt","timeoutAt","lockRound","players":[{"addr","name","char","status","committed","revealed"}]}

lockRound (0 unless planning or revealing) tells the lobby when the timelocked plans of a seat committed and not revealed can be opened.

players has one entry per seat, "addr" "" for a seat nobody took yet.

func HistoryJSON

Action
1func HistoryJSON(id string) string
source

HistoryJSON is every resolved turn of a game, oldest first, for tests and tools (the tutorial's showdown, make gen-tutorial); no view serves it: a long game's history may cost more than a query can.

Example
1{"id","turn","phase","players":[{"addr","char"}],"turns":[turn result...]}

func JoinGame

crossing Action
1func JoinGame(cur realm, id string, startHex string, facing int)
source

JoinGame seats the caller in the first free seat of a waiting game, playing that seat's character (the kid, then the reb, then the yankee), standing in startHex, one of that character's hexes, facing f; the game starts collecting the commits of turn 1 once its last seat is taken. The call must send the showdown's stake, no more, no less.

func PlanHash

Action
1func PlanHash(id string, turn, seat int, salt, plan string) string
source

PlanHash is what a seat commits: the sha256, in hex, of its salt and plan for that game, turn and seat ("gnoslinger|id|turn|seat|salt|plan").

func PlanJSON

Action
1func PlanJSON(id, addr string) string
source

PlanJSON is where an address's plan for the turn in progress stands; the plan itself shows once revealed:

Example
1{"id","turn","seat" (-1 when addr is not a player),"committed","revealed","plan" ("" until revealed)}

func Render

1func Render(path string) string
source

Render serves the gnoweb pages ("", "help", "game/{id}") and the raw JSON views the web client reads ("json/game/{id}", "json/games/{addr}", "json/history/{id}/{turn}", "json/plan/{id}/{addr}"); the players' profiles and ranking are the profiles realm's (gno.land/r/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/profiles/v0).

func Reveal

crossing Action
1func Reveal(cur realm, id string, turn int, seat int, salt string, plan string) int
source

Reveal puts a seat's salt and plan for the given turn on chain and returns the game revision. Anyone may send it while the game collects the reveals: the salt and plan must match the seat's commit (PlanHash). The last seat to commit may also reveal straight away, without committing: the others' plans are already fixed by their hashes, so its plan in clear helps nobody; that is only accepted from the seat itself, before the deadline, once every other character still in the fight committed (a refused transaction still shows its plan). The plan is a comma-separated list of "<card><f|b>[:<choice>]" entries, e.g. "1f:ahead_left,2f:ahead,3f"; an empty string passes. The reveal that completes the turn resolves it in this same transaction and the next turn starts.

func RevealMany

crossing Action
1func RevealMany(cur realm, id string, turn int, seats string, salts string, plans string) int
source

RevealMany reveals several seats of the given turn in one transaction, as Reveal does each, in the order given: what an opener of the timelocked plans sends. seats, salts and plans are lists separated by ";", of the same length (a plan string never holds a ";", an empty plan is an empty field). A seat already revealed is skipped (its player's own reveal landed first), and so is one whose salt and plan do not match its commit or whose plan is not valid (event PlanUnopened): the opener cannot check that before opening, and one bad timelocked plan must not keep the others back; that seat stays unrevealed, for the claim.

func SetAdmin

crossing Action
1func SetAdmin(cur realm, addr address)
source

SetAdmin hands the house's funds over to another admin. Admin only.

func Surrender

crossing Action
1func Surrender(cur realm, id string)
source

Surrender gives up the showdown (rule 15.4: at the start of a turn, that is while the plans are committed; once they are revealed, surrendering after reading the others' would be the abort the hidden plans remove). The character is Out: nobody can shoot it and it plays no more. With one character left in the fight the game ends and is scored, the surrendered losing the "Surrender" points and, an enemy holding the field, being captured; with more, the showdown goes on without it (its commit is dropped), and goes on to the reveals if the others' plans are committed.

func TurnJSON

Action
1func TurnJSON(id string, turn int) string
source

TurnJSON is one resolved turn of a game (from 1), the turn result as json/game writes its last one: what replays read, a turn per query (the "json/history/{id}/{turn}" view), so that what a query costs does not grow with the game. Each turn is self-contained (its start and ground).

func WithdrawHouse

crossing Action
1func WithdrawHouse(cur realm, to address, amount int64)
source

WithdrawHouse sends amount ugnot of the house's funds to an address. Admin only.

Types 5

type Game

struct
 1type Game struct {
 2	ID        string
 3	Players   []Player
 4	State     engine.State
 5	Turn      int // 1-based
 6	MaxTurns  int
 7	Timeout   time.Duration // the time a turn gives, from TurnStart, after which ClaimTimeout succeeds
 8	Phase     Phase
 9	Commits   []Sealed      // the turn in progress, each cleared by its seat's reveal
10	Plans     []string      // the turn in progress, revealed, canonical form
11	Salts     []string      // the turn in progress, revealed; part of the deck's seed
12	Revealed  []bool        // the turn in progress: the seat's plan is in (revealed, or passed)
13	Missed    []int         // turns in a row each seat let time out, reset by a revealed plan
14	TurnStart time.Time     // when the turn in progress started: its clock, left alone by the saves within the turn
15	Turns     []*TurnResult // resolved turns, in order (at most MaxTurns)
16	Winner    int           // seat index, -1 for none: the most victory points, -1 on a tie
17	EndReason string
18	// Awards are the victory points earned and lost so far (rule 15), at
19	// the end of each turn and of the showdown; VP adds them up per seat.
20	Awards []engine.Award
21	VP     []int
22	// Stake is what each player puts in, in ugnot (0: a free showdown);
23	// Pot what the realm holds for the showdown, the stakes paid so far.
24	// Payouts is what each seat got once the showdown is over (nil before,
25	// and for a free one), HouseCut what the house kept (stakes.gno).
26	Stake     int64
27	Pot       int64
28	Payouts   []int64
29	HouseCut  int64
30	Rev       int // bumped on every change; clients poll it
31	CreatedAt time.Time
32	UpdatedAt time.Time
33}
source

Game is one showdown. Players, Commits, Plans, Salts, Revealed, Missed and VP have one entry per seat, the seats filling up in the order the players join.

type Phase

ident
1type Phase int
source

Phase is where a game stands.

Methods on Phase

func String

method on Phase
1func (p Phase) String() string
source

String returns the phase name used in JSON views.

type Player

struct
1type Player struct {
2	Addr address
3	Char string
4}
source

Player is a seat: the address that controls it (empty while nobody took it) and its character token.

type Sealed

struct
1type Sealed struct {
2	Hash   string // PlanHash of the seat's salt and plan; "" until committed
3	Locked string // the salt and plan timelocked to LockRound
4}
source

Sealed is a seat's sealed plan for the turn in progress, cleared by its reveal.

type TurnResult

struct
1type TurnResult struct {
2	Turn      int
3	Plans     []string
4	Seed      string
5	StartEnc  string // engine.EncodeChars
6	GroundEnc string // engine.EncodeGround
7	CardsEnc  string // engine.EncodeInts
8	EventsEnc string // engine.EncodeEvents
9}
source

TurnResult is the outcome of one resolved turn, kept so the client can replay it: the state the turn started from, the plans (one per seat), and what happened. Seed lets anyone re-run the resolution; Cards is the trace of the result cards drawn from the deck it shuffles, in order. The start snapshot, the weapons on the ground at the start, the cards and the events are kept in the engine's compact text encodings: as structs they cost a hundred times more storage (see engine/encode.gno). With the ground, a turn is shown from its own record, without the turns before it.

Methods on TurnResult

func Cards

method on TurnResult
1func (r *TurnResult) Cards() []int
source

Cards is the result cards drawn during the turn, in order.

func Events

method on TurnResult
1func (r *TurnResult) Events() []engine.Event
source

Events is what happened during the turn, in order.

func Ground

method on TurnResult
1func (r *TurnResult) Ground() []engine.GroundGun
source

Ground is the weapons lying on the board when the turn began.

func Start

method on TurnResult
1func (r *TurnResult) Start() []engine.Character
source

Start is the state of every seat when the turn began.

Dependencies

Source Files 9