Arcade/kiosk name flow, free cursor in menu and portable Linux export
Every game over unlocks the profile and frees the name, so the menu always asks for a new player name: the field is empty at every boot, restart or game over and the game does not start without a name (no anonymous scores). The player identity is derived from the entered name (deterministic FNV-1a, 16 hex chars valid for the public API) and the profile keeps the best score per name. Backend untouched. Mouse: player._ready() captured the pointer before the menu existed, so the start screen was not clickable. The cursor now always starts visible; the capture happens only in player.begin_play(), called by start_game(). New `playing` flag gates Esc and the Web click capture to the actual match. Linux export preset: single-file x86_64 binary with the PCK embedded, no libstdc++ dependency, official 4.7.1 template (glibc >= 2.28). Notes and the export_presets.cfg `#`-is-not-a-comment trap documented in docs/KIOSK-ARCADE.md. Tests: profile 30/30, start screen 15/15 (cursor free at menu, captured only in game), boss 50/50, rebalance 14/14, touch 14 PASS, API 42/42, all other suites pass.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
extends SceneTree
|
||||
## Verifica le regole del profilo locale (nome, id stabile, blocco del nome) su un file
|
||||
## temporaneo, senza toccare il profilo reale del gioco.
|
||||
## Verifica le regole del profilo locale in modalita' arcade/kiosk (nome mai dato per
|
||||
## scontato, id derivato dal nome, miglior punteggio per nome) su un file temporaneo,
|
||||
## senza toccare il profilo reale del gioco.
|
||||
## Avviare con: --headless --path . --script res://tools/test_player_profile.gd
|
||||
const Profile = preload("res://scripts/player_profile.gd")
|
||||
const TEST_PATH := "user://test_profile_check.cfg"
|
||||
@@ -22,19 +23,25 @@ func check(label: String, condition: bool, detail: String = "") -> void:
|
||||
func run_test() -> void:
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(TEST_PATH))
|
||||
|
||||
# 1) file assente: valori predefiniti, id generato, nome non bloccato
|
||||
# 1) file assente: nessun nome e nessun id dati per scontato
|
||||
var fresh := Profile.load_profile(TEST_PATH)
|
||||
check("profilo nuovo: nome predefinito e nome non bloccato",
|
||||
fresh["name"] == Profile.DEFAULT_NAME and fresh["locked"] == false,
|
||||
check("profilo nuovo: campo del nome libero (nessun nome presunto)",
|
||||
fresh["name"] == "" and fresh["locked"] == false,
|
||||
"nome=%s bloccato=%s" % [str(fresh["name"]), str(fresh["locked"])])
|
||||
check("profilo nuovo: id del giocatore generato (32 esadecimali)",
|
||||
fresh["player_id"].length() == 32 and Profile.is_valid_player_id(fresh["player_id"]),
|
||||
"id=%s" % str(fresh["player_id"]))
|
||||
check("profilo nuovo: nessun id assegnato prima del nome inserito",
|
||||
str(fresh["player_id"]) == "", "id=%s" % str(fresh["player_id"]))
|
||||
|
||||
# 2) l'id resta lo stesso a ogni lettura (i punteggi vecchi restano collegati)
|
||||
var again := Profile.load_profile(TEST_PATH)
|
||||
check("l'id del giocatore e' stabile tra una lettura e l'altra",
|
||||
again["player_id"] == fresh["player_id"], "%s vs %s" % [str(again["player_id"]), str(fresh["player_id"])])
|
||||
# 2) identita' derivata dal nome della partita: deterministica e valida per il server
|
||||
var id_a := Profile.player_id_for_name("Mat")
|
||||
var id_b := Profile.player_id_for_name("Mat")
|
||||
check("l'id derivato dal nome e' stabile e accettato dal server",
|
||||
id_a == id_b and id_a.length() == 16 and Profile.is_valid_player_id(id_a),
|
||||
"id=%s vs %s" % [id_a, id_b])
|
||||
check("nomi diversi = identita' diverse",
|
||||
Profile.player_id_for_name("Lu") != id_a, "%s vs %s" % [str(Profile.player_id_for_name("Lu")), id_a])
|
||||
check("nome vuoto: id di ripiego generato e valido",
|
||||
Profile.is_valid_player_id(Profile.player_id_for_name("")),
|
||||
"id=%s" % Profile.player_id_for_name(""))
|
||||
|
||||
# 3) nome scelto e punteggio migliore salvati e riletti
|
||||
var saved := fresh.duplicate()
|
||||
@@ -45,26 +52,34 @@ func run_test() -> void:
|
||||
check("nome e miglior punteggio sopravvivono alla riscrittura",
|
||||
reloaded["name"] == "Mat" and int(reloaded["best"]) == 300,
|
||||
"nome=%s best=%s" % [str(reloaded["name"]), str(reloaded["best"])])
|
||||
check("un nome scelto blocca il nome (non si richiede piu')",
|
||||
reloaded["locked"] == true, "bloccato=%s" % str(reloaded["locked"]))
|
||||
check("regola arcade: il nome scelto NON blocca, viene richiesto di nuovo",
|
||||
reloaded["locked"] == false, "bloccato=%s" % str(reloaded["locked"]))
|
||||
check("il miglior punteggio e' registrato per nome (record per persona)",
|
||||
Profile.best_for_name(reloaded, "Mat") == 300 and Profile.best_for_name(reloaded, "Lu") == 0,
|
||||
"mat=%s lu=%s" % [str(Profile.best_for_name(reloaded, "Mat")), str(Profile.best_for_name(reloaded, "Lu"))])
|
||||
check("game over: profilo sbloccato e nome liberato per la partita nuova",
|
||||
Profile.unlock_name(reloaded)["locked"] == false and str(reloaded["name"]) == "",
|
||||
"nome=%s bloccato=%s" % [str(reloaded["name"]), str(reloaded["locked"])])
|
||||
|
||||
# 4) il nome scelto e' persistito anche nel file, non solo in memoria
|
||||
# 4) nome, record per nome e sblocco sono scritti nel file, non solo in memoria
|
||||
var config := ConfigFile.new()
|
||||
var wrote := config.load(TEST_PATH) == OK
|
||||
check("il blocco del nome e' scritto nel file del profilo",
|
||||
wrote and bool(config.get_value("player", "locked", false)),
|
||||
"letto=%s" % str(wrote))
|
||||
var stored_bests: Variant = config.get_value("player", "bests", {})
|
||||
check("il file conserva il record del nome e il nome non bloccato",
|
||||
wrote and bool(config.get_value("player", "locked", true)) == false
|
||||
and stored_bests is Dictionary and int(stored_bests.get("Mat", 0)) == 300,
|
||||
"letto=%s bests=%s" % [str(wrote), str(stored_bests)])
|
||||
|
||||
# 5) profili vecchi (senza il campo locked) con nome personalizzato risultano bloccati
|
||||
check("profilo vecchio con nome personalizzato: nome considerato scelto",
|
||||
Profile.is_name_locked({"name": "Vecchio", "player_id": "x"}) == true)
|
||||
check("profilo vecchio con nome predefinito: nome non considerato scelto",
|
||||
# 5) i profili delle versioni precedenti arrivano "bloccati": la regola arcade li ignora
|
||||
check("profilo vecchio con nome personalizzato: comunque non bloccato",
|
||||
Profile.is_name_locked({"name": "Vecchio", "player_id": "x", "locked": true}) == false)
|
||||
check("profilo nuovo: il nome non e' bloccato",
|
||||
Profile.is_name_locked({"name": Profile.DEFAULT_NAME, "player_id": "x"}) == false)
|
||||
|
||||
# 6) pulizia del nome secondo le regole del server
|
||||
check("il nome viene ripulito da apici, emoji e simboli",
|
||||
Profile.sanitize_name(" Ma't 😀 <b> ") == "Mat b", Profile.sanitize_name(" Ma't 😀 <b> "))
|
||||
check("il nome vuoto diventa quello predefinito",
|
||||
check("il nome vuoto resta vuoto per il campo, predefinito solo lato server",
|
||||
Profile.sanitize_name(" ") == Profile.DEFAULT_NAME, Profile.sanitize_name(" "))
|
||||
check("il nome e' limitato a 16 caratteri",
|
||||
Profile.sanitize_name("abcdefghijklmnopqrstuvwxyz").length() == Profile.NAME_MAX,
|
||||
@@ -101,13 +116,14 @@ func run_test() -> void:
|
||||
and from_browser["player_id"] == "abcdef0123456789",
|
||||
"nome=%s bloccato=%s" % [str(from_browser["name"]), str(from_browser["locked"])])
|
||||
Profile.apply_web_json(from_browser, '{"n":"Mat","i":"024a956d532357c426ceb08ff86124e1","b":"800","l":"1"}')
|
||||
check("i valori del browser vengono applicati al profilo",
|
||||
from_browser["name"] == "Mat" and int(from_browser["best"]) == 800 and from_browser["locked"] == true
|
||||
check("i valori del browser vengono applicati, il nome resta libero (regola arcade)",
|
||||
from_browser["name"] == "Mat" and int(from_browser["best"]) == 800 and from_browser["locked"] == false
|
||||
and from_browser["player_id"] == "024a956d532357c426ceb08ff86124e1",
|
||||
"nome=%s best=%s" % [str(from_browser["name"]), str(from_browser["best"])])
|
||||
"nome=%s best=%s bloccato=%s" % [str(from_browser["name"]), str(from_browser["best"]),
|
||||
str(from_browser["locked"])])
|
||||
Profile.apply_web_json(from_browser, '{"n":"null","i":"corto","b":"molto","l":"1"}')
|
||||
check("valori non validi dal browser: nome riparato, id e punteggio intatti",
|
||||
from_browser["name"] == Profile.DEFAULT_NAME and from_browser["locked"] == false
|
||||
check("valori non validi dal browser: campo del nome liberato, id e punteggio intatti",
|
||||
from_browser["name"] == "" and from_browser["locked"] == false
|
||||
and from_browser["player_id"] == "024a956d532357c426ceb08ff86124e1"
|
||||
and int(from_browser["best"]) == 800,
|
||||
"nome=%s id=%s best=%s bloccato=%s" % [str(from_browser["name"]),
|
||||
@@ -125,15 +141,15 @@ func run_test() -> void:
|
||||
junk.set_value("player", "locked", true)
|
||||
junk.save(junk_path)
|
||||
var repaired := Profile.load_profile(junk_path)
|
||||
check("un profilo con nome \"null\" viene riparato (nome predefinito, sbloccato)",
|
||||
repaired["name"] == Profile.DEFAULT_NAME and repaired["locked"] == false
|
||||
check("un profilo con nome \"null\" viene riparato (campo libero, sbloccato)",
|
||||
repaired["name"] == "" and repaired["locked"] == false
|
||||
and int(repaired["best"]) == 500,
|
||||
"nome=%s bloccato=%s best=%s" % [str(repaired["name"]), str(repaired["locked"]),
|
||||
str(repaired["best"])])
|
||||
var junk_after := ConfigFile.new()
|
||||
junk_after.load(junk_path)
|
||||
check("la riparazione viene riscritta nel file",
|
||||
str(junk_after.get_value("player", "name", "")) == Profile.DEFAULT_NAME
|
||||
str(junk_after.get_value("player", "name", "x")) == ""
|
||||
and bool(junk_after.get_value("player", "locked", true)) == false,
|
||||
"%s / %s" % [str(junk_after.get_value("player", "name", "")),
|
||||
str(junk_after.get_value("player", "locked", true))])
|
||||
@@ -147,9 +163,9 @@ func run_test() -> void:
|
||||
"best": 850, "locked": true}, legacy_path)
|
||||
var old_hash := FileAccess.get_sha256(legacy_path)
|
||||
var migrated := Profile.load_profile(migrated_path, legacy_path)
|
||||
check("rinomina: nome, id, record e blocco migrati senza perdite",
|
||||
check("rinomina: nome, id e record migrati senza perdite (nome libero)",
|
||||
migrated.name == "Legacy" and migrated.player_id == "abcdef0123456789"
|
||||
and migrated.best == 850 and migrated.locked)
|
||||
and int(migrated.best) == 850 and migrated.locked == false)
|
||||
check("rinomina: il vecchio file rimane byte-identico",
|
||||
FileAccess.get_sha256(legacy_path) == old_hash)
|
||||
check("rinomina: il profilo viene scritto nella nuova posizione",
|
||||
|
||||
Reference in New Issue
Block a user