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.

sonotone.gno

5.41 Kb · 193 lines
  1// Realm (smart contract) Gno pour Sonotone.
  2// - Chaque wallet peut s'inscrire UNE fois avec un pseudo unique.
  3// - Un joueur inscrit envoie son score uniquement quand il bat son record
  4//   (ça lui évite de payer des frais à chaque partie).
  5// - Le jeu lit les données avec GetPlayerJSON / GetTopJSON (gratuit).
  6// - Render() affiche le classement sur gnoweb (la page du realm sur gno.land).
  7
  8package sonotone
  9
 10import (
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/nt/avl/v0"
 15)
 16
 17// Score maximum accepté (protection basique contre les valeurs absurdes).
 18const MaxScore = 1000000
 19
 20// Player = un joueur inscrit et son meilleur résultat.
 21type Player struct {
 22	Pseudo     string
 23	Address    address
 24	BestScore  int
 25	Level      int    // niveau max atteint lors du meilleur score
 26	Instrument string // instrument utilisé lors du meilleur score
 27}
 28
 29// Les variables globales d'un realm sont sauvegardées automatiquement
 30// sur la blockchain après chaque transaction.
 31var (
 32	players avl.Tree // clé : adresse du wallet -> *Player
 33	pseudos avl.Tree // clé : pseudo en minuscules -> adresse du wallet
 34)
 35
 36// ---------------------------------------------------------------------
 37// Fonctions appelées par les joueurs (transactions signées avec Adena)
 38// ---------------------------------------------------------------------
 39
 40// Register relie un pseudo au wallet qui signe la transaction.
 41// Le premier paramètre "cur realm" est obligatoire pour qu'un utilisateur
 42// puisse appeler la fonction et pour savoir QUI l'appelle.
 43func Register(cur realm, pseudo string) {
 44	caller := cur.Previous()
 45	if !caller.IsUser() {
 46		panic("seul un wallet peut s'inscrire")
 47	}
 48	key := caller.Address().String()
 49
 50	if players.Has(key) {
 51		panic("ce wallet a déjà un pseudo")
 52	}
 53	checkPseudo(pseudo)
 54
 55	lower := strings.ToLower(pseudo)
 56	if pseudos.Has(lower) {
 57		panic("ce pseudo est déjà pris")
 58	}
 59
 60	// On n'écrit qu'après toutes les vérifications.
 61	players.Set(key, &Player{
 62		Pseudo:  pseudo,
 63		Address: caller.Address(),
 64	})
 65	pseudos.Set(lower, key)
 66}
 67
 68// SubmitScore enregistre un nouveau record pour le wallet qui signe.
 69func SubmitScore(cur realm, score int, level int, instrument string) {
 70	key := cur.Previous().Address().String()
 71
 72	v := players.Get(key)
 73	if v == nil {
 74		panic("inscris d'abord un pseudo")
 75	}
 76	if score <= 0 || score > MaxScore {
 77		panic("score invalide")
 78	}
 79	if level < 1 || level > 1000 {
 80		panic("niveau invalide")
 81	}
 82	if !validInstrument(instrument) {
 83		panic("instrument inconnu")
 84	}
 85
 86	p := v.(*Player)
 87	if score <= p.BestScore {
 88		panic("ce n'est pas un nouveau record")
 89	}
 90	p.BestScore = score
 91	p.Level = level
 92	p.Instrument = instrument
 93}
 94
 95// ---------------------------------------------------------------------
 96// Fonctions de lecture (gratuites, utilisées par le jeu)
 97// ---------------------------------------------------------------------
 98
 99// GetPlayerJSON renvoie le profil d'une adresse en JSON, ou "null".
100func GetPlayerJSON(addr string) string {
101	v := players.Get(addr)
102	if v == nil {
103		return "null"
104	}
105	return playerToJSON(v.(*Player))
106}
107
108// GetTopJSON renvoie les n meilleurs joueurs (ayant un score) en JSON.
109func GetTopJSON(n int) string {
110	top := topPlayers(n)
111	out := "["
112	for i, p := range top {
113		if i > 0 {
114			out += ","
115		}
116		out += playerToJSON(p)
117	}
118	return out + "]"
119}
120
121// Render affiche le classement en Markdown sur gnoweb.
122func Render(_ string) string {
123	out := "# 🎧 Sonotone — Classement\n\n"
124	top := topPlayers(20)
125	if len(top) == 0 {
126		return out + "Aucun score pour l'instant.\n"
127	}
128	out += "| # | Pseudo | Score | Niveau | Instrument |\n"
129	out += "|---|--------|-------|--------|------------|\n"
130	for i, p := range top {
131		out += "| " + strconv.Itoa(i+1) +
132			" | " + p.Pseudo +
133			" | " + strconv.Itoa(p.BestScore) +
134			" | " + strconv.Itoa(p.Level) +
135			" | " + p.Instrument + " |\n"
136	}
137	return out
138}
139
140// ---------------------------------------------------------------------
141// Fonctions internes (minuscule = non accessibles de l'extérieur)
142// ---------------------------------------------------------------------
143
144// checkPseudo : 3 à 20 caractères, lettres, chiffres, _ et - uniquement.
145// (Ça évite aussi les caractères qui casseraient le JSON ou la page.)
146func checkPseudo(p string) {
147	if len(p) < 3 || len(p) > 20 {
148		panic("le pseudo doit faire entre 3 et 20 caractères")
149	}
150	for _, c := range p {
151		ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
152			(c >= '0' && c <= '9') || c == '_' || c == '-'
153		if !ok {
154			panic("pseudo : seulement lettres (sans accent), chiffres, _ et -")
155		}
156	}
157}
158
159func validInstrument(i string) bool {
160	return i == "piano" || i == "bass" || i == "guitar" || i == "organ"
161}
162
163func playerToJSON(p *Player) string {
164	return `{"pseudo":"` + p.Pseudo +
165		`","address":"` + p.Address.String() +
166		`","score":` + strconv.Itoa(p.BestScore) +
167		`,"level_max":` + strconv.Itoa(p.Level) +
168		`,"instrument":"` + p.Instrument + `"}`
169}
170
171// topPlayers trie les joueurs ayant un score, du plus grand au plus petit.
172func topPlayers(n int) []*Player {
173	var list []*Player
174	players.Iterate("", "", func(_ string, value any) bool {
175		p := value.(*Player)
176		if p.BestScore > 0 {
177			list = append(list, p)
178		}
179		return false // false = continuer à parcourir
180	})
181
182	// Tri par insertion : simple à lire, suffisant pour un petit jeu.
183	for i := 1; i < len(list); i++ {
184		for j := i; j > 0 && list[j].BestScore > list[j-1].BestScore; j-- {
185			list[j], list[j-1] = list[j-1], list[j]
186		}
187	}
188
189	if n > 0 && len(list) > n {
190		list = list[:n]
191	}
192	return list
193}