Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}