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.

stakes.gno

5.11 Kb · 169 lines
  1package gnoslinger
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime/unsafe"
  7	"strconv"
  8
  9	"gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/engine/v0"
 10)
 11
 12// Stakes (this project's, not the board game's): a showdown may be played
 13// for GNOT. The creator sends the stake with CreateGame, and every joiner
 14// sends the same with JoinGame; the realm holds them, the pot. When the
 15// showdown ends the house keeps HouseCutPercent of the pot for events, and
 16// the winner takes the rest; a draw splits it between the seats tied for
 17// the most victory points (what does not divide evenly goes to the house).
 18// A showdown that ends with nobody left in the fight, everyone out of time,
 19// or cancelled before it starts, gives every stake back in full. The admin, the deployer at
 20// first, may send the house's funds out (WithdrawHouse): a later version of
 21// the realm cannot reach this one's balance.
 22
 23const (
 24	// Denom is the only coin a stake is paid in.
 25	Denom = "ugnot"
 26	// HouseCutPercent is the share of a pot the house keeps, for events.
 27	HouseCutPercent = 5
 28)
 29
 30var (
 31	// house is what the realm holds for events: the house's cuts, less
 32	// what the admin withdrew. The rest of the realm's balance is the pots
 33	// of the showdowns not over yet.
 34	house int64
 35	// admin may withdraw the house's funds; the deployer at first.
 36	admin = unsafe.OriginCaller()
 37)
 38
 39// sentStake returns the ugnot sent with the call, 0 for none. Coins can
 40// only come straight from a user's call: a realm in between could have
 41// spent the send it reports (unsafe.OriginSend is the whole message's).
 42func sentStake(cur realm) int64 {
 43	sent := unsafe.OriginSend()
 44	if len(sent) == 0 {
 45		return 0
 46	}
 47	if !cur.Previous().IsUserCall() {
 48		panic("gnoslinger: a stake can only be sent by a direct call")
 49	}
 50	if len(sent) != 1 || sent[0].Denom != Denom || sent[0].Amount <= 0 {
 51		panic("gnoslinger: a stake is paid in " + Denom + " only")
 52	}
 53	return sent[0].Amount
 54}
 55
 56// assertNoSend refuses coins sent with a call that takes none: they would
 57// sit in the realm's balance with no showdown to pay them out.
 58func assertNoSend() {
 59	if len(unsafe.OriginSend()) > 0 {
 60		panic("gnoslinger: this call takes no coins")
 61	}
 62}
 63
 64// pay sends amount ugnot from the realm to addr.
 65func pay(cur realm, to address, amount int64) {
 66	if amount <= 0 {
 67		return
 68	}
 69	banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(cur.Address(), to, chain.Coins{chain.NewCoin(Denom, amount)})
 70}
 71
 72// refundStakes gives every seated player its stake back: the showdown
 73// was cancelled, or nobody was left to win it. Payouts records it, so a finished game says
 74// what each seat got.
 75func (g *Game) refundStakes(cur realm) {
 76	if g.Pot == 0 {
 77		return
 78	}
 79	g.Payouts = make([]int64, g.seats())
 80	for i, p := range g.Players {
 81		if p.Addr == "" {
 82			continue
 83		}
 84		pay(cur, p.Addr, g.Stake)
 85		g.Payouts[i] = g.Stake
 86		emit("StakeRefunded", "id", g.ID, "seat", strconv.Itoa(i), "player", p.Addr.String(), "amount", strconv.FormatInt(g.Stake, 10))
 87	}
 88}
 89
 90// payPot pays the pot of a finished showdown: the house's cut, then the
 91// winner, or the seats tied for the most victory points in equal shares.
 92func (g *Game) payPot(cur realm) {
 93	if g.Pot == 0 || g.Payouts != nil {
 94		return
 95	}
 96	if g.nobodyWon() {
 97		g.refundStakes(cur)
 98		return
 99	}
100	best := g.VP[0]
101	for _, v := range g.VP {
102		if v > best {
103			best = v
104		}
105	}
106	var winners []int
107	for i, v := range g.VP {
108		if v == best {
109			winners = append(winners, i)
110		}
111	}
112	cut := g.Pot * HouseCutPercent / 100
113	share := (g.Pot - cut) / int64(len(winners))
114	cut = g.Pot - share*int64(len(winners))
115	g.Payouts = make([]int64, g.seats())
116	for _, w := range winners {
117		pay(cur, g.Players[w].Addr, share)
118		g.Payouts[w] = share
119		emit("PotPaid", "id", g.ID, "seat", strconv.Itoa(w), "player", g.Players[w].Addr.String(), "amount", strconv.FormatInt(share, 10))
120	}
121	g.HouseCut = cut
122	house += cut
123}
124
125// nobodyWon tells a showdown that ended with nobody left in the fight
126// because everyone ran out of time: nobody played it to the end, nothing
127// was won.
128func (g *Game) nobodyWon() bool {
129	return g.EndReason == "timeout" && engine.AliveCount(g.State) == 0
130}
131
132// WithdrawHouse sends amount ugnot of the house's funds to an address.
133// Admin only.
134func WithdrawHouse(cur realm, to address, amount int64) {
135	assertNoSend()
136	assertAdmin(cur)
137	if !to.IsValid() {
138		panic("gnoslinger: invalid address")
139	}
140	if amount <= 0 || amount > house {
141		panic("gnoslinger: the house holds " + strconv.FormatInt(house, 10) + Denom)
142	}
143	house -= amount
144	pay(cur, to, amount)
145	emit("HouseWithdrawn", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
146}
147
148// SetAdmin hands the house's funds over to another admin. Admin only.
149func SetAdmin(cur realm, addr address) {
150	assertNoSend()
151	assertAdmin(cur)
152	if !addr.IsValid() {
153		panic("gnoslinger: invalid address")
154	}
155	admin = addr
156	emit("AdminChanged", "admin", addr.String())
157}
158
159func assertAdmin(cur realm) {
160	if prev := cur.Previous(); !prev.IsUser() || prev.Address() != admin {
161		panic("gnoslinger: admin only")
162	}
163}
164
165// House is the ugnot the house holds for events.
166func House() int64 { return house }
167
168// Admin is who may withdraw the house's funds.
169func Admin() address { return admin }