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.

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}