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.

query.gno

7.59 Kb · 262 lines
  1package arena
  2
  3import (
  4	"encoding/base64"
  5	"strconv"
  6	"strings"
  7	"time"
  8
  9	"gno.land/p/g188vafzutzvlfw6wt7u3s62xtuvm0epmu8ufmfz/gnomepit/engine"
 10	"gno.land/p/nt/avl/v0"
 11	"gno.land/r/sys/users"
 12)
 13
 14// MatchSummary is one match as a player's list shows it. Round is the round being
 15// played, or the last one resolved once the match is over. ToMove is the address that
 16// must move next, empty when none. Deadline is the end of the current move's 24 hours,
 17// or the expiry of an open challenge, in Unix seconds. Opponent is the address an open
 18// challenge is reserved for, "" when anyone can accept it and once accepted.
 19type MatchSummary struct {
 20	ID, Phase                    string
 21	Round                        int
 22	Challenger, Acceptor         string
 23	ChallengerClan, AcceptorClan string
 24	ToMove                       string
 25	Deadline                     int64
 26	Result, Reason               string
 27	Opponent                     string
 28}
 29
 30// MatchView is everything one match screen needs. Plans holds [challenger, acceptor]
 31// for each resolved round, and for the round being played while the match runs, ""
 32// while a plan is sealed or not yet made; HP holds both sides' HP after each resolved
 33// round; Box is the challenger's encrypted pending plan, in base64.
 34type MatchView struct {
 35	MatchSummary
 36	ChallengerDeck, AcceptorDeck string
 37	Plans                        [][]string
 38	Box                          string
 39	HP                           [][]int
 40	DeadlinePassed               bool
 41}
 42
 43// SessionLock binds the session key at address Session to the match whose id is Match.
 44// Expires is the session's expiry in Unix seconds, 0 for none.
 45type SessionLock struct {
 46	Session, Match string
 47	Expires        int64
 48}
 49
 50// Standing is one player's place on the ladder. Rank is 1 for the highest rating and
 51// equal ratings share it; Rank is 0 and Rating 1000 for a player with no rated match.
 52// Name is the player's gno.land name, "" without one. MainClan is "" until their
 53// first rated match.
 54type Standing struct {
 55	Rank                   int
 56	Player, Name, MainClan string
 57	Rating                 int
 58	Wins, Losses, Draws    int
 59}
 60
 61// ClanStanding is one clan's record across rated cross-clan matches.
 62type ClanStanding struct {
 63	Rank                int
 64	Clan                string
 65	Rating              int
 66	Wins, Losses, Draws int
 67}
 68
 69// Leaderboard lists rated players, highest rating first: limit of them after skipping offset.
 70func Leaderboard(offset, limit int) []Standing {
 71	checkPage(offset, limit)
 72	rules := engine.ProofOfConcept()
 73	list := []Standing{}
 74	ladder.TraverseByOffset(offset, limit, true, true, func(n *avl.Node) bool {
 75		s := standingOf(rules, address(n.Key()[2:]))
 76		switch {
 77		case len(list) == 0:
 78			s.Rank = rankOf(s.Rating)
 79		case list[len(list)-1].Rating == s.Rating:
 80			s.Rank = list[len(list)-1].Rank
 81		default:
 82			s.Rank = offset + len(list) + 1
 83		}
 84		list = append(list, s)
 85		return false
 86	})
 87	return list
 88}
 89
 90// StandingOf returns player's standing, rated or not.
 91func StandingOf(player address) Standing {
 92	s := standingOf(engine.ProofOfConcept(), player)
 93	if s.MainClan != "" {
 94		s.Rank = rankOf(s.Rating)
 95	}
 96	return s
 97}
 98
 99// RatedPlayers counts the players on the ladder.
100func RatedPlayers() int {
101	return ladder.Size()
102}
103
104// ClanStandings lists every clan of the ruleset, highest rating first; equal ratings keep
105// ruleset order and share a rank.
106func ClanStandings() []ClanStanding {
107	rules := engine.ProofOfConcept()
108	list := []ClanStanding{}
109	for i, c := range decodeClans(clanRecords, len(rules.Clans)) {
110		list = append(list, ClanStanding{Clan: rules.Clans[i].Name, Rating: c.rating, Wins: c.wins, Losses: c.losses, Draws: c.draws})
111	}
112	for i := 1; i < len(list); i++ {
113		for j := i; j > 0 && list[j].Rating > list[j-1].Rating; j-- {
114			list[j], list[j-1] = list[j-1], list[j]
115		}
116	}
117	for i := range list {
118		if i > 0 && list[i].Rating == list[i-1].Rating {
119			list[i].Rank = list[i-1].Rank
120		} else {
121			list[i].Rank = i + 1
122		}
123	}
124	return list
125}
126
127// standingOf is player's standing without its rank.
128func standingOf(rules *engine.Ruleset, player address) Standing {
129	s := Standing{Player: player.String(), Name: nameOf(player), Rating: startRating}
130	st := loadStanding(player)
131	if st == nil {
132		return s
133	}
134	s.MainClan = clanName(rules, st.mainClan())
135	s.Rating, s.Wins, s.Losses, s.Draws = st.rating, st.wins, st.losses, st.draws
136	return s
137}
138
139// nameOf is player's gno.land name, "" when they have none.
140func nameOf(player address) string {
141	u := users.ResolveAddress(player)
142	if u.IsDeleted() {
143		return ""
144	}
145	return u.Name()
146}
147
148// Locks lists player's session locks. It can include locks whose session has expired.
149func Locks(player address) []SessionLock {
150	list := []SessionLock{}
151	for _, l := range loadPlayer(player).locks {
152		list = append(list, SessionLock{Session: l.session.String(), Match: strconv.Itoa(l.id), Expires: l.expires})
153	}
154	return list
155}
156
157// Matches lists player's matches, newest first: limit of them after skipping offset.
158func Matches(player address, offset, limit int) []MatchSummary {
159	checkPage(offset, limit)
160	rules := engine.ProofOfConcept()
161	now := time.Now().Unix()
162	list := []MatchSummary{}
163	for _, id := range loadPlayer(player).newest(offset, limit) {
164		m, ok := loadMatch(rules, id)
165		if !ok {
166			continue
167		}
168		list = append(list, m.summary(rules, id, now))
169	}
170	return list
171}
172
173// Match returns the view of match id.
174func Match(id string) MatchView {
175	rules := engine.ProofOfConcept()
176	n, m := find(rules, id)
177	return m.view(rules, n, time.Now().Unix())
178}
179
180func (m record) view(rules *engine.Ruleset, id int, now int64) MatchView {
181	v := MatchView{
182		MatchSummary:   m.summary(rules, id, now),
183		ChallengerDeck: deckText(rules, m.decks[0]),
184		AcceptorDeck:   deckText(rules, m.decks[1]),
185		Plans:          [][]string{},
186		Box:            base64.StdEncoding.EncodeToString([]byte(m.box)),
187		HP:             [][]int{},
188		DeadlinePassed: m.running() && now > m.deadline,
189	}
190	resolved := plansMade(m.plans[0])
191	rounds := resolved
192	if m.running() {
193		rounds++
194	}
195	for r := 1; r <= rounds; r++ {
196		a := planText(rules, planAt(m.plans[0], r))
197		b := planText(rules, planAt(m.plans[1], r))
198		v.Plans = append(v.Plans, []string{a, b})
199	}
200	for r := 0; r < resolved; r++ {
201		v.HP = append(v.HP, []int{m.hp[r][0], m.hp[r][1]})
202	}
203	return v
204}
205
206func (m record) summary(rules *engine.Ruleset, id int, now int64) MatchSummary {
207	resolved := plansMade(m.plans[0])
208	s := MatchSummary{
209		ID:             strconv.Itoa(id),
210		Phase:          m.phase,
211		Round:          resolved + 1,
212		Challenger:     m.challenger.String(),
213		Acceptor:       m.acceptor.String(),
214		ChallengerClan: clanName(rules, m.clans[0]),
215		AcceptorClan:   clanName(rules, m.clans[1]),
216		Result:         m.result,
217		Reason:         m.reason,
218		Opponent:       m.opponent.String(),
219	}
220	switch m.phase {
221	case phaseOpen:
222		s.Deadline = m.deadline
223		if m.expired(now) {
224			s.Phase = phaseExpired
225		}
226	case phaseReveal:
227		s.ToMove = s.Challenger
228		s.Deadline = m.deadline
229	case phasePlay:
230		s.ToMove = s.Acceptor
231		s.Deadline = m.deadline
232	case phaseEnded:
233		if resolved > 0 {
234			s.Round = resolved
235		}
236	}
237	return s
238}
239
240func (m record) running() bool {
241	return m.phase == phaseReveal || m.phase == phasePlay
242}
243
244// deckText names the cards of deck in card id order.
245func deckText(rules *engine.Ruleset, deck uint64) string {
246	var names []string
247	for i := range rules.Cards {
248		if deck&(uint64(1)<<uint(i)) != 0 {
249			names = append(names, rules.Cards[i].Name)
250		}
251	}
252	return strings.Join(names, ",")
253}
254
255// planText names the cards of p in play order.
256func planText(rules *engine.Ruleset, p engine.Plan) string {
257	names := make([]string, len(p))
258	for i, id := range p {
259		names[i] = rules.Cards[id].Name
260	}
261	return strings.Join(names, ",")
262}