deathmatch.gno
2.49 Kb · 91 lines
1package payout
2
3// rankWeights are the shares of the top three by kills, in basis points.
4var rankWeights = [3]int64{6000, 3000, 1000}
5
6// Deathmatch pays the most kills. The pot is every stake; the fee is taken from
7// it, and the rest goes to the top three by kills: 60%, 30% and 10%.
8//
9// - Only players with at least one kill are ranked. With fewer than three
10// of them, the shares of the ranks they fill are rescaled to the whole:
11// one killer takes everything, two take 2/3 and 1/3.
12// - Players with as many kills share the ranks they hold: two tied first
13// split 60% + 30%, and the next one is third.
14// - When nobody killed anybody, every player gets their stake back and the
15// house takes nothing.
16//
17// Deaths cost nothing here beyond the lives they take. What rounding leaves
18// over goes to the fee.
19type Deathmatch struct{}
20
21var _ Rule = Deathmatch{}
22
23func (Deathmatch) Mode() string { return ModeDeathmatch }
24
25func (Deathmatch) Settle(g Game, players []address, deaths []Death) (Result, error) {
26 t, err := check(g, players, deaths)
27 if err != nil {
28 return Result{}, err
29 }
30 pot := g.Stake * int64(len(players))
31
32 // Killers, most kills first; equal kills keep the order of the players.
33 var ranked []int
34 for i := range players {
35 if t.kills[i] == 0 {
36 continue
37 }
38 at := len(ranked)
39 for at > 0 && t.kills[ranked[at-1]] < t.kills[i] {
40 at--
41 }
42 ranked = append(ranked, 0)
43 copy(ranked[at+1:], ranked[at:])
44 ranked[at] = i
45 }
46 if len(ranked) == 0 {
47 res := Result{}
48 for _, p := range players {
49 res.Payouts = append(res.Payouts, Payout{Address: p, Amount: g.Stake})
50 }
51 return res, nil
52 }
53
54 filled := len(ranked)
55 if filled > len(rankWeights) {
56 filled = len(rankWeights)
57 }
58 whole := int64(0)
59 for _, w := range rankWeights[:filled] {
60 whole += w
61 }
62
63 prize := afterFee(pot, g.FeeBps)
64 amounts := make([]int64, len(players))
65 for pos := 0; pos < filled; {
66 end := pos + 1
67 for end < len(ranked) && t.kills[ranked[end]] == t.kills[ranked[pos]] {
68 end++
69 }
70 weight := int64(0)
71 for r := pos; r < end && r < filled; r++ {
72 weight += rankWeights[r]
73 }
74 group := int64(end - pos)
75 // prize <= 8e12 and weight <= 1e4: the product stays far below 2^63.
76 each := prize * weight / (whole * group)
77 for _, i := range ranked[pos:end] {
78 amounts[i] = each
79 }
80 pos = end
81 }
82
83 res := Result{Fee: pot}
84 for i, p := range players {
85 if amounts[i] > 0 {
86 res.Payouts = append(res.Payouts, Payout{Address: p, Amount: amounts[i]})
87 res.Fee -= amounts[i]
88 }
89 }
90 return res, nil
91}