gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0⧉ Package Open in gnoweb ↗
Kind Pure package
Name v0
Namespace g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd / gnofly / arena / payout
Exported functions n/a — not supported for pure packages by the node (vm/qfuncs)
Module gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0
gno 0.9 ⧉
1 package payout 2 3 import "errors" 4 5 // Death is one life lost in a round: Victim was shot down by Killer, or by 6 // nobody (a crash, a disconnect). A pilot dies up to Game.Lives times and is 7 // out after the last one. 8 not supported for pure packages by the node (vm/qfuncs)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
type Death struct {
9 Victim address
10 Killer address // "" when nobody shot the victim down
11 }
12
13 // Game is what a rule needs to know about the arena a round was played in.
14 type Game struct {
15 Stake int64 // ugnot, what each player paid
16 Lives int64 // deaths a player can take, the last one puts them out
17 KillTarget int64 // kills that end the round, 0 for the clock only
18 FeeBps int64 // the house's share, in basis points
19 }
20
21 // Payout is an amount of ugnot to send to an address.
22 type Payout struct {
23 Address address
24 Amount int64 // ugnot
25 }
26
27 // Result is what a rule decides: who gets what, and the house's cut.
28 type Result struct {
29 Payouts []Payout // in the order of the players, players paid nothing left out
30 Fee int64
31 }
32
33 // Rule turns what happened in a round into who gets what.
34 type Rule interface {
35 Mode() string
36 Settle(g Game, players []address, deaths []Death) (Result, error )
37 }
38
39 // The modes of the rules shipped with the package.
40 const (
41 ModeBattleRoyale = "battle-royale"
42 ModeDeathmatch = "deathmatch"
43 )
44
45 // Bounds every rule enforces.
46 const (
47 MinPlayers = 2
48 MaxPlayers = 8
49 MinLives = 1
50 MaxLives = 3
51 MaxKillTarget = 20
52 MinStake = 1 // ugnot
53 MaxStake = 1000000000000 // ugnot, 1e12
54 BpsScale = 10000 // basis points in a whole
55 MaxFeeBps = 5000 // the fee never takes more than half
56 MaxDeaths = MaxPlayers * MaxLives
57 )
58
59 var (
60 ErrStake = errors.New( "payout: stake must be between 1 and 1000000000000 ugnot" )
61 ErrLives = errors.New( "payout: lives must be between 1 and 3" )
62 ErrKillTarget = errors.New( "payout: the kill target must be between 0 and 20" )
63 ErrFee = errors.New( "payout: the fee must be between 0 and 5000 bps" )
64 ErrTooFewPlayers = errors.New( "payout: at least 2 players are required" )
65 ErrTooManyPlayers = errors.New( "payout: at most 8 players are allowed" )
66 ErrEmptyPlayer = errors.New( "payout: a player has no address" )
67 ErrDuplicatePlayer = errors.New( "payout: a player is listed twice" )
68 ErrUnknownVictim = errors.New( "payout: a victim is not a player of the round" )
69 ErrDeadTooOften = errors.New( "payout: a player died more often than they have lives" )
70 ErrUnknownKiller = errors.New( "payout: a killer is not a player of the round" )
71 ErrSelfKill = errors.New( "payout: a player cannot be their own killer" )
72 ErrTooManyDeaths = errors.New( "payout: more deaths than lives in the round" )
73
74 ErrNoPlayers = errors.New( "payout: players must list at least one address" )
75 ErrPlayersFormat = errors.New( "payout: players must be comma-separated addresses" )
76 ErrDeathsFormat = errors.New( `payout: deaths must be "victim:killer" entries separated by ";"` )
77 )
78
79 // Lookup returns the rule of a mode, or nil for a mode nobody knows.
80 func Lookup(mode string ) Rule {
81 switch mode {
82 case ModeBattleRoyale:
83 return BattleRoyale{}
84 case ModeDeathmatch:
85 return Deathmatch{}
86 }
87 return nil
88 }
89
90 // Modes lists the modes of the shipped rules. The slice is a fresh copy.
91 func Modes() [] string {
92 return [] string {ModeBattleRoyale, ModeDeathmatch}
93 }
94
95 // tally is what happened in a round, per player.
96 type tally struct {
97 deaths [] int64 // lives each player lost
98 kills [] int64 // deaths credited to each player
99 lastOut int // the player whose last life went last, -1 when nobody is out
100 first int // the first player to reach the kill target, -1 when nobody did
101 }
102
103 // out reports whether player i lost every life.
104 func (t tally) out(i int , lives int64 ) bool {
105 return t.deaths[i] >= lives
106 }
107
108 // check enforces what every rule requires, and counts deaths and kills.
109 //
110 // A killer may be out already: a bullet still in flight. A dead pilot cannot
111 // die again, though: a victim takes at most Lives deaths.
112 func check(g Game, players []address, deaths []Death) (tally, error ) {
113 if g.Stake < MinStake || g.Stake > MaxStake {
114 return tally{}, ErrStake
115 }
116 if g.Lives < MinLives || g.Lives > MaxLives {
117 return tally{}, ErrLives
118 }
119 if g.KillTarget < 0 || g.KillTarget > MaxKillTarget {
120 return tally{}, ErrKillTarget
121 }
122 if g.FeeBps < 0 || g.FeeBps > MaxFeeBps {
123 return tally{}, ErrFee
124 }
125 n := len(players)
126 if n < MinPlayers {
127 return tally{}, ErrTooFewPlayers
128 }
129 if n > MaxPlayers {
130 return tally{}, ErrTooManyPlayers
131 }
132 for i, p := range players {
133 if p == "" {
134 return tally{}, ErrEmptyPlayer
135 }
136 if indexOf(players[:i], p) >= 0 {
137 return tally{}, ErrDuplicatePlayer
138 }
139 }
140 if int64 (len(deaths)) > int64 (n)*g.Lives {
141 return tally{}, ErrTooManyDeaths
142 }
143
144 t := tally{deaths: make([] int64 , n), kills: make([] int64 , n), lastOut: - 1 , first: - 1 }
145 for _, d := range deaths {
146 victim := indexOf(players, d.Victim)
147 if victim < 0 {
148 return tally{}, ErrUnknownVictim
149 }
150 if t.out(victim, g.Lives) {
151 return tally{}, ErrDeadTooOften
152 }
153 if d.Killer != "" {
154 if d.Killer == d.Victim {
155 return tally{}, ErrSelfKill
156 }
157 killer := indexOf(players, d.Killer)
158 if killer < 0 {
159 return tally{}, ErrUnknownKiller
160 }
161 t.kills[killer]++
162 if g.KillTarget > 0 && t.first < 0 && t.kills[killer] >= g.KillTarget {
163 t.first = killer
164 }
165 }
166 t.deaths[victim]++
167 if t.out(victim, g.Lives) {
168 t.lastOut = victim
169 }
170 }
171 return t, nil
172 }
173
174 func indexOf(list []address, addr address) int {
175 for i, a := range list {
176 if a == addr {
177 return i
178 }
179 }
180 return - 1
181 }
182
183 // afterFee returns what is left of amount once the fee is taken.
184 func afterFee(amount, feeBps int64 ) int64 {
185 return amount * (BpsScale - feeBps) / BpsScale
186 }
187