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.

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}