0x26882bea…36e6sent to0xb7278a61…e575·#24,755,290·0x1a9e5d21…9c359a
V# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }
from genlayer import *
import json
# ─── Constants ────────────────────────────────────────────────────────────────
XP_PER_LEVEL = 100
MAX_LEVEL = 100
STAT_POINTS_START = 5 # bonus points on first registration
STAT_POINTS_PER_WIN = 3 # bonus points per win
class PlayerProfiles(gl.Contract):
"""
Sky Flock Arena — PlayerProfiles
Persistent on-chain player identity store.
Replaces localStorage as the source of truth for:
username, pigeon_type, xp, level, stat_points, wins, losses
"""
profiles: TreeMap[str, str] # wallet_address (lower) → JSON profile
def __init__(self):
self.profiles = TreeMap()
# ── Helpers ───────────────────────────────────────────────────────────────
@staticmethod
def _calc_level(xp: int) -> int:
return min(MAX_LEVEL, xp // XP_PER_LEVEL)
# ── Write: Register new player ────────────────────────────────────────────
@gl.public.write
def register_player(
self,
wallet_address: str,
username: str,
pigeon_type: str,
) -> str:
key = wallet_address.lower()
if key in self.profiles:
existing = json.loads(self.profiles[key])
return json.dumps({
"ok": False,
"error": "already_registered",
"profile": existing,
})
clean_name = username.strip()[:20] or "Pombo"
profile = {
"wallet": key,
"username": clean_name,
"pigeon_type": pigeon_type,
"xp": 0,
"level": 0,
"stat_points": STAT_POINTS_START,
"wins": 0,
"losses": 0,
}
self.profiles[key] = json.dumps(profile)
return json.dumps({"ok": True, "profile": profile})
# ── Write: Update XP + win/loss after a match ─────────────────────────────
@gl.public.write
def update_stats_after_match(
self,
wallet_address: str,
xp_gained: int,
is_win: bool,
) -> str:
key = wallet_address.lower()
if key not in self.profiles:
return json.dumps({"ok": False, "error": "not_registered"})
profile = json.loads(self.profiles[key])
old_level = profile["level"]
profile["xp"] += max(0, int(xp_gained))
if is_win:
profile["wins"] += 1
else:
profile["losses"] += 1
new_level = self._calc_level(profile["xp"])
profile["level"] = new_level
levels_gained = new_level - old_level
if levels_gained > 0:
profile["stat_points"] += levels_gained * 2
if is_win:
profile["stat_points"] += STAT_POINTS_PER_WIN
self.profiles[key] = json.dumps(profile)
return json.dumps({
"ok": True,
"profile": profile,
"levels_gained": levels_gained,
})
# ── Write: Update pigeon class (after evolution) ──────────────────────────
@gl.public.write
def update_pigeon(self, wallet_address: str, pigeon_type: str) -> str:
key = wallet_address.lower()
if key not in self.profiles:
return json.dumps({"ok": False, "error": "not_registered"})
profile = json.loads(self.profiles[key])
profile["pigeon_type"] = pigeon_type
self.profiles[key] = json.dumps(profile)
return json.dumps({"ok": True, "profile": profile})
# ── Write: Spend one stat point ───────────────────────────────────────────
@gl.public.write
def spend_stat_point(self, wallet_address: str) -> str:
key = wallet_address.lower()
if key not in self.profiles:
return json.dumps({"ok": False, "error": "not_registered"})
profile = json.loads(self.profiles[key])
if profile["stat_points"] <= 0:
return json.dumps({"ok": False, "error": "no_stat_points"})
profile["stat_points"] -= 1
self.profiles[key] = json.dumps(profile)
return json.dumps({"ok": True, "stat_points": profile["stat_points"]})
# ── View: Get profile ─────────────────────────────────────────────────────
@gl.public.view
def get_profile(self, wallet_address: str) -> str:
key = wallet_address.lower()
if key not in self.profiles:
return json.dumps({"ok": False, "error": "not_found"})
profile = json.loads(self.profiles[key])
return json.dumps({"ok": True, "profile": profile})
# ── View: Meta ────────────────────────────────────────────────────────────
@gl.public.view
def get_version(self) -> str:
return "1.0.0"
@gl.public.view
def total_players(self) -> int:
return len(self.profiles)