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

earnings.gno

5.81 Kb · 191 lines
  1package arena
  2
  3import "gno.land/p/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/arena/payout/v0"
  4
  5// Earnings are what players won, added up round after round. Like the
  6// results they are a record for readers: no payment is ever computed from
  7// them.
  8//
  9// For one settled round a player's winnings are what they were paid above
 10// their own stake, and never less than zero: losing a stake does not take
 11// anything back from what was won before. Cancelled and refunded rounds
 12// count for nothing, nobody won or lost in them.
 13
 14// topSize is how many players the list of top winners holds.
 15const topSize = 10
 16
 17// totals is what the realm remembers about one player.
 18type totals struct {
 19	won    int64 // ugnot won above the stakes, over every settled round
 20	rounds int64 // settled rounds played
 21}
 22
 23// Totals are stored as a single string, "won:rounds", for the same reason
 24// as the pilots of the game realm: a struct would be one more stored object
 25// per player, and the referee pays for up to 16 new players per settlement.
 26
 27// encode returns the stored form of t.
 28func (t totals) encode() string {
 29	return itoa(t.won) + ":" + itoa(t.rounds)
 30}
 31
 32// totalsOf returns the totals of a player, zero for a player who never
 33// finished a round.
 34func totalsOf(addr address) totals {
 35	v := earnings.Get(addr.String())
 36	if v == nil {
 37		return totals{}
 38	}
 39	s := v.(string)
 40	colon := indexFrom(s, 0, ':')
 41	if colon == len(s) {
 42		panic(errCorruptEarnings)
 43	}
 44	won, ok := digits(s[:colon])
 45	if !ok {
 46		panic(errCorruptEarnings)
 47	}
 48	rounds, ok := digits(s[colon+1:])
 49	if !ok {
 50		panic(errCorruptEarnings)
 51	}
 52	return totals{won: won, rounds: rounds}
 53}
 54
 55// creditRound records a settled round for each of its players: one more
 56// round played, and what they were paid above their stake, if anything.
 57func creditRound(stake int64, players []address, payouts []payout.Payout) {
 58	for _, p := range players {
 59		gain := int64(0)
 60		for _, paid := range payouts {
 61			if paid.Address == p && paid.Amount > stake {
 62				gain = paid.Amount - stake
 63			}
 64		}
 65
 66		t := totalsOf(p)
 67		before := t.won
 68		t.won += gain
 69		t.rounds++
 70		earnings.Set(p.String(), t.encode())
 71
 72		if gain > 0 {
 73			if next, changed := winners.raise(p, before, t.won, topSize); changed {
 74				winners = next
 75			}
 76		}
 77	}
 78}
 79
 80// ranking is the list of top winners: the players who won the most, best
 81// first, at most topSize lines and one line per player. With equal winnings,
 82// the player who got there first stays ahead.
 83//
 84// It is the ranking of the game realm. It is kept sorted at write time, so
 85// reading it never has to look at every player. Winnings only going up is
 86// what makes that exact: a player outside the list can only enter it through
 87// raise.
 88//
 89// The two slices are parallel: slices of plain values are one stored object
 90// each, where a slice of structs would be one object per line.
 91type ranking struct {
 92	addrs  []address
 93	scores []int64
 94}
 95
 96// raise puts a player at their place after their score went up from old to
 97// score. It returns the ranking to store, and changed == false when it is
 98// left as it is: the player is not on a full list and still not good enough
 99// to get on.
100func (r ranking) raise(addr address, old, score int64, size int) (next ranking, changed bool) {
101	from := r.lineOf(addr, old)
102	to := r.search(score - 1) // after every line with this score or more
103	if from < 0 && to >= size {
104		return r, false
105	}
106	if from >= 0 && to > from {
107		to = from // cannot happen: a score only goes up
108	}
109	next = ranking{
110		addrs:  spliceAddrs(r.addrs, from, to, addr, size),
111		scores: spliceInts(r.scores, from, to, score, size),
112	}
113	return next, true
114}
115
116// search returns the index of the first line with a score of limit or less,
117// the number of lines when there is none. The list is sorted, so this is a
118// binary search.
119func (r ranking) search(limit int64) int {
120	lo, hi := 0, len(r.scores)
121	for lo < hi {
122		mid := (lo + hi) / 2
123		if r.scores[mid] <= limit {
124			hi = mid
125		} else {
126			lo = mid + 1
127		}
128	}
129	return lo
130}
131
132// lineOf returns the index of a player's line, -1 when they have none. old
133// is the player's score before the change being applied.
134//
135// A line always carries the current score of its player, because every
136// change of a score goes through raise. So the line can only be among those
137// with exactly old. A player whose score was zero has never been through
138// raise and has no line.
139func (r ranking) lineOf(addr address, old int64) int {
140	if old <= 0 {
141		return -1
142	}
143	for i := r.search(old); i < len(r.scores) && r.scores[i] == old; i++ {
144		if r.addrs[i] == addr {
145			return i
146		}
147	}
148	return -1
149}
150
151// spliceAddrs returns a copy of a ranking column with v inserted at index
152// to. With from >= 0 the element at that index, the player's previous line,
153// is left out; to must not be past it. With from < 0 the result is kept to
154// size elements by dropping the tail; to must be below size.
155//
156// The copy has exactly the length of its content, so that nothing else stays
157// in storage.
158func spliceAddrs(list []address, from, to int, v address, size int) []address {
159	if from < 0 {
160		if len(list) >= size {
161			list = list[:size-1]
162		}
163		out := make([]address, 0, len(list)+1)
164		out = append(out, list[:to]...)
165		out = append(out, v)
166		return append(out, list[to:]...)
167	}
168	out := make([]address, 0, len(list))
169	out = append(out, list[:to]...)
170	out = append(out, v)
171	out = append(out, list[to:from]...)
172	return append(out, list[from+1:]...)
173}
174
175// spliceInts is spliceAddrs for a column of numbers.
176func spliceInts(list []int64, from, to int, v int64, size int) []int64 {
177	if from < 0 {
178		if len(list) >= size {
179			list = list[:size-1]
180		}
181		out := make([]int64, 0, len(list)+1)
182		out = append(out, list[:to]...)
183		out = append(out, v)
184		return append(out, list[to:]...)
185	}
186	out := make([]int64, 0, len(list))
187	out = append(out, list[:to]...)
188	out = append(out, v)
189	out = append(out, list[to:from]...)
190	return append(out, list[from+1:]...)
191}