feat(jogos): pedra, papel e tesoura, quantos dedos e achou! com a mão

- lib/maos.ts: dedos esticados pelos 21 pontos do MediaPipe (vale em
  qualquer rotação), contagem, jogada e leitura estável (mão parada);
- lib/jogos.svelte.ts: os três jogos conversam com o Carinha por comandos
  (fala com legenda, gestos e símbolo nos olhos) e contam como brincadeira
  para o modo vida; cartão "Jogos com a mão" no Ao vivo liga a câmera e o
  rastreamento com um toque;
- servidor: comandos "dizer" (até 120 caracteres) e "olhos" (símbolos e
  números permitidos, 0,5 a 8 s); rosto mostra o símbolo nos olhos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-11 19:53:48 -03:00
co-authored by Claude Opus 5.5
parent 92701e8a16
commit 3d954efc03
10 changed files with 479 additions and 1 deletions
+17
View File
@@ -66,6 +66,8 @@ ATIVIDADES = ["ler", "cantar", "bola", "videogame", "pintar", "malhar", "dormir"
"violao", "dancar", "corda", "meditar", "telescopio", "pipa", "bolhas", "skate", "computador", "pescar"]
# Ordens internas que o rosto entende mas não viram botão.
ORDENS_INTERNAS = {"calma"}
# O que os jogos podem mostrar nos olhos (pedra, papel, tesoura, números...)
OLHOS_JOGO = {"✊", "✋", "✌️", "❓", "⭐", "❤️", "👀", "🙈"} | {str(n) for n in range(11)}
# Aparência atual da carinha (personalizacao pelo painel). Fica gravada num
# arquivo ao lado do servidor, para sobreviver a reinicios.
@@ -223,6 +225,21 @@ def ordem_de_comando(tipo, valor):
if nome not in ROSTOS:
raise ValueError("Rosto desconhecido: " + nome)
return "rosto " + nome
if tipo == "dizer": # fala curta com legenda (jogos, painel)
texto = " ".join(str(valor or "").split())[:120]
if not texto:
raise ValueError("Diga alguma coisa")
return "dizer " + texto
if tipo == "olhos": # símbolo nos olhos por alguns segundos
valor = valor if isinstance(valor, dict) else {}
simbolo = str(valor.get("simbolo") or "")
if simbolo and simbolo not in OLHOS_JOGO:
raise ValueError("Símbolo inválido para os olhos")
try:
segundos = max(0.5, min(8.0, float(valor.get("segundos", 2.5))))
except (TypeError, ValueError):
raise ValueError("Segundos inválidos") from None
return "olhos %s %.1f" % (simbolo or "-", segundos)
raise ValueError("Tipo de comando inválido")