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.

lobby.gno

5.91 Kb · 159 lines
  1package gnoslinger
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	"gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/engine/v0"
  9	"gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/hex/v0"
 10	profiles "gno.land/r/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/profiles/v0"
 11)
 12
 13// CreateGame opens a showdown of seats players (engine.MinPlayers to
 14// engine.MaxPlayers) with the caller in seat 0, playing the showdown's
 15// first character (engine.Setups: the marshal), standing in startHex, one
 16// of the hexes assigned to that character, facing f (0=N 1=NE 2=SE 3=S
 17// 4=SW 5=NW); it returns the game id. The other seats take the next
 18// characters in the order the players join. maxTurns is the game length,
 19// from MinMaxTurns to MaxMaxTurns; timeoutMinutes is the time each turn
 20// gives the players, past which anyone may claim it with ClaimTimeout, 0
 21// meaning DefaultTimeout (2 days). The game stays open until its seats fill
 22// up or its creator cancels it: the turn clock starts with the last join.
 23// The ugnot sent with the call are the showdown's stake, which every
 24// joiner must send too; none, a free showdown (stakes.gno).
 25func CreateGame(cur realm, seats int, maxTurns int, timeoutMinutes int, startHex string, facing int) string {
 26	caller := cur.Previous().Address()
 27	stake := sentStake(cur)
 28	if seats < engine.MinPlayers || seats > engine.MaxPlayers {
 29		panic("gnoslinger: seats must be between " + strconv.Itoa(engine.MinPlayers) + " and " + strconv.Itoa(engine.MaxPlayers))
 30	}
 31	if maxTurns < MinMaxTurns || maxTurns > MaxMaxTurns {
 32		panic("gnoslinger: maxTurns must be between " + strconv.Itoa(MinMaxTurns) + " and " + strconv.Itoa(MaxMaxTurns))
 33	}
 34	timeout := DefaultTimeout
 35	if timeoutMinutes != 0 {
 36		timeout = time.Duration(timeoutMinutes) * time.Minute
 37	}
 38	if timeout < MinTimeout || timeout > MaxTimeout {
 39		panic("gnoslinger: timeoutMinutes must be between " + strconv.Itoa(int(MinTimeout/time.Minute)) + " and " + strconv.Itoa(int(MaxTimeout/time.Minute)))
 40	}
 41	charKey := engine.SeatChar(0)
 42	assertSetUp(charKey, startHex, facing)
 43	id := gameSeq.Next().String()
 44	g := &Game{
 45		ID:        id,
 46		Players:   make([]Player, seats),
 47		State:     engine.NewState(seats),
 48		Turn:      1,
 49		MaxTurns:  maxTurns,
 50		Timeout:   timeout,
 51		Phase:     PhaseWaiting,
 52		Missed:    make([]int, seats),
 53		Winner:    -1,
 54		VP:        make([]int, seats),
 55		Stake:     stake,
 56		Pot:       stake,
 57		CreatedAt: time.Now(),
 58	}
 59	g.clearTurn()
 60	g.Players[0] = Player{Addr: caller, Char: charKey}
 61	g.State.SetUp(0, startHex, hex.Dir(facing))
 62	openGames.Set(id, g)
 63	index(caller, id)
 64	profiles.Ensure(cross(cur), caller)
 65	save(g)
 66	emit("GameCreated", "id", id, "creator", caller.String(), "char", charKey, "seats", strconv.Itoa(seats),
 67		"maxTurns", strconv.Itoa(maxTurns), "timeoutMinutes", strconv.Itoa(int(timeout/time.Minute)), "hex", startHex, "facing", strconv.Itoa(facing),
 68		"stake", strconv.FormatInt(stake, 10))
 69	return id
 70}
 71
 72// JoinGame seats the caller in the first free seat of a waiting game,
 73// playing that seat's character (the kid, then the reb, then the yankee),
 74// standing in startHex, one of that character's hexes, facing f; the game
 75// starts collecting the commits of turn 1 once its last seat is taken.
 76// The call must send the showdown's stake, no more, no less.
 77func JoinGame(cur realm, id string, startHex string, facing int) {
 78	caller := cur.Previous().Address()
 79	stake := sentStake(cur)
 80	g := getGame(id)
 81	if g.Phase != PhaseWaiting {
 82		panic("gnoslinger: game is not open")
 83	}
 84	switch g.seat(caller) {
 85	case 0:
 86		panic("gnoslinger: you cannot join your own game")
 87	case -1:
 88	default:
 89		panic("gnoslinger: you are already in this game")
 90	}
 91	seat := g.freeSeat()
 92	if seat < 0 {
 93		panic("gnoslinger: every seat is taken")
 94	}
 95	charKey := engine.SeatChar(seat)
 96	assertSetUp(charKey, startHex, facing)
 97	if stake != g.Stake {
 98		panic("gnoslinger: this showdown's stake is " + strconv.FormatInt(g.Stake, 10) + Denom + ", sent " + strconv.FormatInt(stake, 10) + Denom)
 99	}
100	g.Pot += stake
101	g.Players[seat] = Player{Addr: caller, Char: charKey}
102	g.State.SetUp(seat, startHex, hex.Dir(facing))
103	index(caller, id)
104	if g.freeSeat() < 0 {
105		// The game starts: it counts as played for every seat.
106		g.Phase = PhasePlanning
107		g.startTurn()
108		g.notifyPlanDue()
109		openGames.Remove(id)
110		for _, p := range g.Players {
111			profiles.AddGamePlayed(cross(cur), p.Addr)
112		}
113	} else {
114		profiles.Ensure(cross(cur), caller)
115	}
116	save(g)
117	emit("PlayerJoined", "id", id, "player", caller.String(), "seat", strconv.Itoa(seat), "char", charKey, "hex", startHex, "facing", strconv.Itoa(facing))
118}
119
120// assertSetUp refuses a character that is not in the showdown, a hex it
121// may not set up in, or no such facing.
122func assertSetUp(charKey, startHex string, facing int) {
123	setup := engine.SetupOf(charKey)
124	if setup == nil {
125		panic("gnoslinger: " + charKey + " is not in this showdown: play " + strings.Join(engine.ShowdownChars(), " or "))
126	}
127	if !engine.CanSetUp(charKey, startHex) {
128		panic("gnoslinger: " + charKey + " sets up in " + strings.Join(setup.Hexes, ", ") + ", not in " + startHex)
129	}
130	if !hex.Dir(facing).Valid() {
131		panic("gnoslinger: facing must be between 0 (N) and 5 (NW)")
132	}
133}
134
135// CancelGame withdraws a game whose seats are not all taken yet. Creator
136// only. The game is deleted, not kept as finished, so the creator gets back
137// the storage deposit CreateGame paid; the players who had joined are
138// unindexed with it, and every stake paid goes back.
139func CancelGame(cur realm, id string) {
140	assertNoSend()
141	caller := cur.Previous().Address()
142	g := getGame(id)
143	if g.Phase != PhaseWaiting {
144		panic("gnoslinger: only a game waiting for players can be cancelled")
145	}
146	if g.Players[0].Addr != caller {
147		panic("gnoslinger: only the creator can cancel the game")
148	}
149	g.refundStakes(cur)
150	openGames.Remove(id)
151	games.Remove(id)
152	for _, p := range g.Players {
153		if p.Addr != "" {
154			unindex(p.Addr, id)
155		}
156	}
157	emit("GameEnded", "id", id, "reason", "cancelled", "winner", "")
158	g.notifyCancelled(caller)
159}