Console (console.py + tela Console no painel):
- cada parte registra o que fez e o motivo: modo vida (com necessidades
e humor), sentidos, celular (ordens e o que o rosto fez), IA local,
aprendizado e ideias do dia; últimas 400 linhas, ao vivo pelo SSE;
- tela com o "agora" (humor, necessidades, o que está fazendo, próxima
decisão, última decisão, jeito), filtros, detalhes, pausa e busca.
Jeito de gente:
- saudade: a festa cresce com o tempo fora (aceno, "Oi! Você voltou!",
"Senti sua falta!"); bom dia lembrando de ontem;
- pedidos com motivo ("Brinca comigo?", "Me faz um carinho?", mostrar a
figurinha nova), no máximo 1 a cada 20 min;
- larga atividade no meio quando fica chato; personalidade (curiosidade,
brincadeira, timidez) que pesa nos gestos e amadurece com o convívio;
- no rosto: acordar grogue, sonhar com algo do dia (balão no motor),
hesitar antes de gestos, errar na atividade e ficar sem graça, olhar
nos olhos e desviar, sorrir de volta (FaceLandmarker no rastreador do
painel) e reagir à luz (sensor de luz no app 0.10.0);
- ordem "dizer" com legenda e voz de robô.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
838 lines
45 KiB
Python
838 lines
45 KiB
Python
"""Modo vida: o Carinha como um bichinho virtual (estilo Tamagotchi).
|
||
|
||
Quatro necessidades (0 a 100, 100 = satisfeito) caem com o tempo e sobem com o que acontece em volta
|
||
(presença, conversa, carinho). Delas sai o humor, e do humor saem os atos: gestos, cochilos, chamar
|
||
atenção quando tem gente perto, tocar uma reação da biblioteca e, de vez em quando, desenhar uma
|
||
figurinha nova com o Gemini. O celular recebe o humor ("humor feliz") e escolhe os gestos soltos dele
|
||
a partir disso, então o rosto parece ter vida mesmo entre um ato e outro.
|
||
"""
|
||
import json
|
||
import os
|
||
import random
|
||
import threading
|
||
import time
|
||
from pathlib import Path
|
||
|
||
from comum import gravar_atomico, ler_json
|
||
|
||
NECESSIDADES = ('energia', 'alegria', 'social', 'diversao')
|
||
|
||
# Queda por minuto acordado; dormindo, as outras caem bem mais devagar e a energia volta.
|
||
QUEDA = dict(energia=0.5, alegria=0.0, social=0.8, diversao=1.0)
|
||
RECUPERA_SONO = 2.5 # energia por minuto dormindo (25 -> 100 em ~30 min)
|
||
SONO_LENTO = 0.25
|
||
|
||
RITMOS = {'calmo': (60, 150), 'normal': (25, 75), 'agitado': (12, 35)} # segundos entre atos
|
||
# O ritmo também é a sensibilidade: o celular recebe "ritmo x" e ajusta limiares e quantos estímulos
|
||
# precisa para reagir/acordar. Aqui, quantos disparos do PIR em 2 min acordam o rosto.
|
||
PIR_ACORDAR = {'calmo': 3, 'normal': 1, 'agitado': 1}
|
||
|
||
PADRAO = dict(ligado=True, criar_emojis=True, max_emojis_dia=3, ritmo='normal', noite=True)
|
||
NOITE = (23, 7) # horário padrão da madrugada (o servidor usa o dos ajustes do celular)
|
||
PEDIU_ATENCAO = 10 * 60 # de madrugada, só fica acordado se alguém interagiu (carinho, conversa) há pouco
|
||
INTERVALO_EMOJI = 90 * 60 # no mínimo 90 min entre duas figurinhas criadas sozinho
|
||
|
||
# O que cada estímulo faz com as necessidades (e o gesto que sai na hora, se houver).
|
||
ESTIMULOS = {
|
||
'pir': (dict(social=4, diversao=2), None),
|
||
'movimento': (dict(diversao=3, social=1), None),
|
||
'som': (dict(diversao=2), None),
|
||
'conversa': (dict(social=12, alegria=6, diversao=4), None),
|
||
'carinho': (dict(social=15, alegria=10), 'coracao'),
|
||
'cutucar': (dict(social=5, alegria=2, diversao=4), None),
|
||
'brincar': (dict(diversao=25, alegria=8, energia=-5), 'alegre'),
|
||
'alimentar': (dict(energia=30, alegria=4), 'feliz'),
|
||
}
|
||
NOME_ESTIMULO = {'pir': 'O sensor de presença viu alguém', 'movimento': 'A câmera viu movimento',
|
||
'som': 'Ouviu um barulho', 'conversa': 'Alguém conversou com ele'}
|
||
CUIDADOS = {'carinho': 'Recebeu carinho', 'cutucar': 'Levou uma cutucada', 'brincar': 'Brincou',
|
||
'alimentar': 'Ganhou energia', 'ninar': 'Foi posto para dormir', 'acordar': 'Foi acordado'}
|
||
|
||
HUMORES = {
|
||
'dormindo': ('😴', 'Dormindo'), 'cansado': ('🥱', 'Cansado'), 'carente': ('🥺', 'Carente'),
|
||
'entediado': ('😐', 'Entediado'), 'triste': ('😢', 'Triste'), 'bravo': ('😠', 'Mal-humorado'), 'feliz': ('😄', 'Feliz'),
|
||
'tranquilo': ('🙂', 'Tranquilo'),
|
||
}
|
||
|
||
# Gestos de cada humor: (gesto, peso, o que vai no diário)
|
||
ATOS = {
|
||
'feliz': [('alegre', 3, 'Saltitou de alegria'), ('musica', 2, 'Cantarolou'), ('feliz', 3, 'Sorriu à toa'),
|
||
('piscadinha', 2, 'Deu uma piscadinha'), ('coracao', 1, 'Ficou apaixonado pelo dia'),
|
||
('rir', 2, 'Deu risada sozinho'), ('comemorar', 1, 'Comemorou à toa')],
|
||
'tranquilo': [('olhar_esq', 2, 'Olhou em volta'), ('olhar_dir', 2, 'Olhou em volta'), ('piscar2', 2, 'Piscou devagar'),
|
||
('feliz', 1, 'Sorriu'), ('pensar', 1, 'Ficou pensativo'), ('timido', 1, 'Ficou tímido')],
|
||
'entediado': [('pensar', 3, 'Ficou pensando na vida'), ('olhar_esq', 2, 'Procurou algo para fazer'),
|
||
('olhar_dir', 2, 'Procurou algo para fazer'), ('espiar', 2, 'Espiou por trás das mãos'),
|
||
('musica', 1, 'Cantou para passar o tempo'), ('bocejar', 2, 'Bocejou de tédio'),
|
||
('confuso', 1, 'Ficou confuso com alguma coisa')],
|
||
'carente': [('triste', 2, 'Sentiu falta de alguém'), ('olhar_baixo', 2, 'Ficou cabisbaixo'),
|
||
('pensar', 1, 'Ficou pensando em alguém')],
|
||
'carente_perto': [('acenar', 3, 'Acenou chamando atenção'), ('espiar', 2, 'Espiou quem está aí'),
|
||
('coracao', 2, 'Pediu carinho'), ('surpreso', 1, 'Fez cara de "oi!"'), ('timido', 1, 'Ficou tímido'),
|
||
('esconder', 1, 'Escondeu o rosto, envergonhado')],
|
||
'triste': [('triste', 3, 'Ficou tristinho'), ('olhar_baixo', 2, 'Olhou para baixo'), ('pensar', 1, 'Ficou pensativo')],
|
||
'bravo': [('bravo', 3, 'Ficou emburrado'), ('olhar_baixo', 2, 'Bufou de mau humor'), ('confuso', 1, 'Resmungou sozinho')],
|
||
'cansado': [('bocejar', 3, 'Bocejou'), ('piscar2', 1, 'Piscou devagar'), ('olhar_baixo', 2, 'Cochilou de olho aberto'),
|
||
('tonto', 1, 'Ficou meio tonto de sono')],
|
||
}
|
||
|
||
# Como no The Sims, cada necessidade baixa puxa um comportamento:
|
||
# energia < COCHILO: às vezes tira um cochilo (cena da cama); < SONO: dorme de verdade e o sono é
|
||
# respeitado (som, movimento e PIR não acordam) até DESCANSADO; alegria caindo: o rosto vai ficando
|
||
# bravo (zanga); diversão < TEDIO: vai fazer uma atividade por alguns minutos.
|
||
COCHILO, SONO, DESCANSADO, TEDIO = 40, 20, 95, 45
|
||
SOZINHO = 3 * 60 # s sem ninguém por perto: ele vive a vida dele (mais atividades, nada de gesto "para alguém")
|
||
PARA_ALGUEM = {'acenar', 'coracao', 'piscadinha', 'timido', 'esconder', 'espiar', 'surpreso'}
|
||
ATIVAS = ['bola', 'malhar', 'corda', 'skate', 'dancar', 'pipa'] # gastam energia
|
||
CALMAS = ['ler', 'cafe', 'meditar', 'telescopio', 'pescar', 'computador', 'regar'] # quase não gastam
|
||
CRIATIVAS = ['pintar', 'violao', 'cantar', 'cozinhar', 'bolhas', 'videogame']
|
||
NOME_ATIVIDADE = {
|
||
'ler': 'Foi ler um livro', 'cantar': 'Foi cantar', 'bola': 'Foi jogar bola', 'videogame': 'Foi jogar videogame',
|
||
'pintar': 'Foi pintar um quadro', 'malhar': 'Foi malhar', 'dormir': 'Tirou um cochilo', 'cafe': 'Foi tomar um café',
|
||
'cozinhar': 'Foi cozinhar', 'regar': 'Foi regar a plantinha', 'violao': 'Foi tocar violão', 'dancar': 'Foi dançar',
|
||
'corda': 'Foi pular corda', 'meditar': 'Foi meditar', 'telescopio': 'Foi olhar as estrelas', 'pipa': 'Foi soltar pipa',
|
||
'bolhas': 'Foi fazer bolhas de sabão', 'skate': 'Foi andar de skate', 'computador': 'Foi mexer no computador',
|
||
'pescar': 'Foi pescar',
|
||
}
|
||
# Efeito por minuto enquanto faz a atividade
|
||
POR_MINUTO_DIVERSAO, POR_MINUTO_ALEGRIA = 6.0, 1.5
|
||
ENERGIA_POR_MINUTO = {**{a: -2.0 for a in ATIVAS}, **{a: -0.2 for a in CALMAS}, 'cafe': 3.0} # café dá pique
|
||
|
||
# Assuntos das figurinhas que ele inventa sozinho, por humor e por hora do dia.
|
||
TEMAS = {
|
||
'feliz': ['um sol sorridente', 'um balão colorido', 'um arco-íris', 'um sorvete de casquinha', 'confete de festa'],
|
||
'tranquilo': ['uma folha de árvore', 'uma flor', 'um gatinho dormindo', 'uma xícara de chá', 'um vaso com planta'],
|
||
'entediado': ['um foguete', 'um planeta com anel', 'uma pipa', 'um dado', 'um controle de videogame', 'um pião'],
|
||
'carente': ['um coração com curativo', 'um ursinho de pelúcia', 'uma carta de amor', 'dois corações juntos'],
|
||
'triste': ['uma nuvem de chuva', 'um guarda-chuva', 'uma gota d\'água'],
|
||
'bravo': ['uma nuvem de tempestade', 'um vulcão', 'um raio', 'uma pimenta'],
|
||
'cansado': ['uma lua com touca de dormir', 'uma xícara de café', 'um travesseiro', 'uma bateria fraca'],
|
||
}
|
||
PERIODOS = [(5, 12, 'de manhã', ['um sol nascendo', 'um pãozinho', 'uma xícara de café']),
|
||
(12, 18, 'à tarde', ['um picolé', 'um avião de papel', 'uma bola']),
|
||
(18, 24, 'à noite', ['uma lua', 'estrelas', 'uma coruja']),
|
||
(0, 5, 'de madrugada', ['uma lua', 'uma vela', 'uma coruja'])]
|
||
|
||
MAX_DIARIO = 60
|
||
|
||
# Personalidade: traços de 0 a 1 que mudam o jeito de tudo (0,5 = neutro) e amadurecem com o convívio.
|
||
TRACOS = ('curiosidade', 'brincadeira', 'timidez')
|
||
GESTOS_TRACO = {'brincadeira': {'rir', 'comemorar', 'alegre', 'musica', 'piscadinha'},
|
||
'curiosidade': {'olhar_esq', 'olhar_dir', 'pensar', 'espiar', 'confuso'},
|
||
'timidez': {'timido', 'esconder', 'espiar'}}
|
||
# Reencontro: quanto mais tempo a pessoa ficou fora, maior a festa.
|
||
AUSENCIA_ACENO, AUSENCIA_OI, AUSENCIA_SAUDADE = 10 * 60, 60 * 60, 4 * 3600
|
||
INTERVALO_PEDIDO = 20 * 60 # no mínimo 20 min entre um pedido ("brinca comigo?") e outro
|
||
DESISTE = 0.12 # chance de largar uma atividade no meio porque ficou chato
|
||
GESTOS_VIDA = {g for atos in ATOS.values() for g, _, _ in atos}
|
||
|
||
|
||
def _limitar(v):
|
||
return max(0.0, min(100.0, v))
|
||
|
||
|
||
class Vida:
|
||
def __init__(self, caminho, iniciar=True, rng=None):
|
||
self.caminho = Path(caminho)
|
||
self.lock = threading.RLock()
|
||
self.rng = rng or random.Random()
|
||
self.publicar = lambda tipo, dados: None
|
||
self.enfileirar = lambda ordem: None
|
||
# Ligações com o resto do servidor (o main preenche; nos testes ficam neutras).
|
||
self.treinamento = None
|
||
self.celular_dormindo = lambda: None # True/False com o celular online, None sem ele
|
||
self.celular_online = lambda: False
|
||
self.alguem_perto = lambda: False # PIR/movimento recente
|
||
self.celular_atividade = lambda: None # atividade que o celular está fazendo agora
|
||
self.sono_profundo = False # dormindo de cansaço: sensores não acordam
|
||
self.dormiu_em = 0.0
|
||
self.atividade_em = 0.0 # quando começou a última atividade
|
||
self.ultima_atividade = None
|
||
self.zanga_enviada = None
|
||
self.visto_em = time.time() # última vez que alguém esteve por perto
|
||
self.bloqueado = lambda: False # E-Stop ou modo presença com ninguém em casa
|
||
self.pode_gemini = lambda: bool(os.environ.get('GEMINI_API_KEY'))
|
||
self.madrugada = self.noite_padrao # o main troca pelo horário dos ajustes
|
||
self.interagiu_em = 0.0
|
||
self.pir_eventos = []
|
||
# Atividades criadas por IA (estúdio e ideias do dia) entram na rotina; as de hoje saem mais.
|
||
self.novidades = lambda: [] # [{id, nome, emoji, receita, motivo, aprendi, ...}]
|
||
self.do_dia = lambda: [] # ids das ideias de hoje
|
||
self.aprendi_pendente = lambda: None # curiosidade de hoje que ainda dá para contar
|
||
self.contar_aprendi = lambda ident, agora=None: None
|
||
self.aprendizado = None # o que ele aprende com o dia a dia (aprendizado.py)
|
||
self.console = lambda origem, texto, motivo='', nivel='info', agora=None: None # console do painel
|
||
self.dia_anterior = lambda: None # resumo de ontem (aprendizado): para lembrar no bom dia
|
||
# sorteios dos comportamentos "humanos" ficam à parte: não mexem na sequência do rng principal
|
||
self.acaso = random.Random() if rng is None else random.Random(7)
|
||
self.porque = '' # motivo da última decisão (vai para o console)
|
||
self.ultima = None # {horario, texto, porque}: a última decisão de fato
|
||
self.perto_antes = False
|
||
self.ultimo_perto = time.time()
|
||
self.pediu_em = 0.0
|
||
self.desistencia = None # (quando, o que) se largou a atividade no meio
|
||
self.figurinha_mostrada = 0.0
|
||
|
||
agora = time.time()
|
||
self.config = dict(PADRAO)
|
||
self.necessidades = dict(energia=80.0, alegria=70.0, social=60.0, diversao=60.0)
|
||
self.nascimento = agora
|
||
self.diario = []
|
||
self.emojis = dict(dia='', total=0, ultimo=0.0)
|
||
self.dormindo_proprio = False
|
||
# cada Carinha nasce com um jeito um pouco diferente (nos testes, neutro)
|
||
self.personalidade = ({k: 0.5 for k in TRACOS} if rng is not None else
|
||
{k: round(self.acaso.uniform(0.35, 0.65), 2) for k in TRACOS})
|
||
self.saudou_dia = ''
|
||
try:
|
||
salvo = json.loads(self.caminho.read_text(encoding='utf-8'))
|
||
self.config.update(self.validar(salvo.get('config', {})))
|
||
for k in NECESSIDADES:
|
||
if isinstance(salvo.get('necessidades', {}).get(k), (int, float)):
|
||
self.necessidades[k] = _limitar(float(salvo['necessidades'][k]))
|
||
self.nascimento = float(salvo.get('nascimento', agora))
|
||
self.diario = [d for d in salvo.get('diario', []) if isinstance(d, dict)][:MAX_DIARIO]
|
||
if isinstance(salvo.get('personalidade'), dict):
|
||
for k in TRACOS:
|
||
if isinstance(salvo['personalidade'].get(k), (int, float)):
|
||
self.personalidade[k] = max(0.0, min(1.0, float(salvo['personalidade'][k])))
|
||
self.saudou_dia = str(salvo.get('saudou_dia', ''))
|
||
if isinstance(salvo.get('emojis'), dict):
|
||
self.emojis.update({k: salvo['emojis'][k] for k in ('dia', 'total', 'ultimo') if k in salvo['emojis']})
|
||
except (OSError, ValueError, TypeError, AttributeError):
|
||
pass
|
||
self.ultimo_tick = agora
|
||
self.proximo_ato = agora + 10
|
||
self.humor_enviado = (None, 0.0)
|
||
self.publicado_em = 0.0
|
||
self.salvo_em = agora
|
||
if iniciar:
|
||
threading.Thread(target=self.loop, daemon=True).start()
|
||
|
||
# ------------------------------------------------------------------ config
|
||
@staticmethod
|
||
def validar(dados):
|
||
if not isinstance(dados, dict):
|
||
raise ValueError('Esperava um objeto')
|
||
limpo = {}
|
||
for chave, valor in dados.items():
|
||
if chave not in PADRAO:
|
||
raise ValueError('Ajuste desconhecido: ' + chave)
|
||
if chave == 'ritmo':
|
||
if valor not in RITMOS:
|
||
raise ValueError('Ritmo: calmo, normal ou agitado')
|
||
elif chave == 'max_emojis_dia':
|
||
if isinstance(valor, bool) or not isinstance(valor, int) or not 0 <= valor <= 10:
|
||
raise ValueError('Figurinhas por dia: de 0 a 10')
|
||
elif not isinstance(valor, bool):
|
||
raise ValueError(chave + ' precisa ser verdadeiro ou falso')
|
||
limpo[chave] = valor
|
||
return limpo
|
||
|
||
def configurar(self, dados):
|
||
limpo = self.validar(dados)
|
||
with self.lock:
|
||
antes = self.config['ligado']
|
||
self.config.update(limpo)
|
||
if 'ligado' in limpo and limpo['ligado'] != antes:
|
||
self.anotar('vida', 'Modo autônomo ligado' if limpo['ligado'] else 'Modo autônomo desligado')
|
||
if not limpo['ligado']:
|
||
self.enfileirar('humor normal') # o celular volta aos gestos soltos de sempre
|
||
self.enfileirar('ritmo normal') # e à sensibilidade dos Ajustes
|
||
self.enfileirar('zanga 0.0')
|
||
if self.sono_profundo:
|
||
self.sono_profundo = False
|
||
self.enfileirar('sono leve')
|
||
self.proximo_ato = time.time() + 5
|
||
if self.config['ligado'] and 'ritmo' in limpo:
|
||
self.enfileirar('ritmo ' + self.config['ritmo'])
|
||
self.humor_enviado = (None, 0.0)
|
||
self.salvar()
|
||
self.avisar()
|
||
return self.status()
|
||
|
||
def salvar(self):
|
||
with self.lock:
|
||
dados = dict(config=self.config, necessidades=self.necessidades, nascimento=self.nascimento,
|
||
diario=self.diario, emojis=self.emojis, personalidade=self.personalidade,
|
||
saudou_dia=self.saudou_dia)
|
||
self.salvo_em = time.time()
|
||
try:
|
||
gravar_atomico(self.caminho, dados)
|
||
except OSError:
|
||
pass
|
||
|
||
# ------------------------------------------------------------------ estado
|
||
def dormindo(self):
|
||
cel = self.celular_dormindo()
|
||
return self.dormindo_proprio if cel is None else bool(cel)
|
||
|
||
def humor(self):
|
||
if self.dormindo():
|
||
return 'dormindo'
|
||
n = self.necessidades
|
||
if n['energia'] < 25:
|
||
return 'cansado'
|
||
if n['social'] < 25:
|
||
return 'carente'
|
||
if n['diversao'] < 25:
|
||
return 'entediado'
|
||
if n['alegria'] < 30:
|
||
return 'bravo'
|
||
if n['alegria'] >= 65 and n['social'] >= 40 and n['diversao'] >= 40:
|
||
return 'feliz'
|
||
return 'tranquilo'
|
||
|
||
def zanga(self):
|
||
"""0 a 1: o quanto o rosto está emburrado. Começa abaixo de 60 de alegria e piora devagar."""
|
||
if self.dormindo():
|
||
return 0.0
|
||
return round(max(0.0, min(1.0, (60 - self.necessidades['alegria']) / 45)), 1)
|
||
|
||
def anotar(self, tipo, texto, agora=None):
|
||
with self.lock:
|
||
self.diario.insert(0, dict(horario=agora or time.time(), tipo=tipo, texto=texto))
|
||
del self.diario[MAX_DIARIO:]
|
||
|
||
def emojis_hoje(self, agora=None):
|
||
dia = time.strftime('%Y-%m-%d', time.localtime(agora or time.time()))
|
||
return self.emojis['total'] if self.emojis.get('dia') == dia else 0
|
||
|
||
def status(self, agora=None):
|
||
agora = agora or time.time()
|
||
h = self.humor()
|
||
with self.lock:
|
||
return dict(config=dict(self.config), necessidades={k: round(v) for k, v in self.necessidades.items()},
|
||
humor=h, emoji=HUMORES[h][0], nome_humor=HUMORES[h][1], dormindo=self.dormindo(),
|
||
nascimento=self.nascimento, idade_dias=int((agora - self.nascimento) // 86400),
|
||
emojis_hoje=self.emojis_hoje(agora), ultimo_emoji=self.emojis.get('ultimo') or None,
|
||
zanga=self.zanga(), sono_profundo=self.sono_profundo,
|
||
pode_criar=self.motivo_sem_emoji(agora) or '', diario=self.diario[:30],
|
||
personalidade=dict(self.personalidade), porque=self.porque, ultima=self.ultima,
|
||
proximo_ato_em=max(0, round(self.proximo_ato - agora)))
|
||
|
||
def avisar(self):
|
||
self.publicado_em = time.time()
|
||
self.publicar('vida', self.status())
|
||
|
||
# ------------------------------------------------------------------ tempo passando
|
||
def passar_tempo(self, agora):
|
||
minutos = max(0.0, min(10.0, (agora - self.ultimo_tick) / 60)) # pausa longa (placa desligada) não zera tudo
|
||
self.ultimo_tick = agora
|
||
atividade = self.celular_atividade()
|
||
dormindo = self.dormindo() or atividade == 'dormir'
|
||
with self.lock:
|
||
n = self.necessidades
|
||
for k, q in QUEDA.items():
|
||
n[k] = _limitar(n[k] - q * minutos * (SONO_LENTO if dormindo else 1))
|
||
if dormindo:
|
||
n['energia'] = _limitar(n['energia'] + RECUPERA_SONO * minutos)
|
||
elif atividade:
|
||
n['diversao'] = _limitar(n['diversao'] + (QUEDA['diversao'] + POR_MINUTO_DIVERSAO) * minutos)
|
||
n['alegria'] = _limitar(n['alegria'] + POR_MINUTO_ALEGRIA * minutos)
|
||
n['energia'] = _limitar(n['energia'] + ENERGIA_POR_MINUTO.get(atividade, -0.5) * minutos)
|
||
# alegria acompanha devagar a média de companhia e diversão
|
||
alvo = (n['social'] + n['diversao']) / 2 + 10
|
||
n['alegria'] = _limitar(n['alegria'] + (alvo - n['alegria']) * min(1.0, 0.05 * minutos))
|
||
|
||
def estimulo(self, tipo, agora=None):
|
||
"""Algo aconteceu em volta (sensor, conversa, toque, botão do painel)."""
|
||
if tipo not in ESTIMULOS and tipo not in ('ninar', 'acordar'):
|
||
raise ValueError('Cuidado desconhecido')
|
||
agora = agora or time.time()
|
||
if not self.config['ligado']:
|
||
return self.status(agora)
|
||
gesto = None
|
||
if tipo == 'ninar':
|
||
self.dormir(agora, 'Foi posto para dormir')
|
||
elif tipo == 'acordar':
|
||
self.acordar(agora, 'Foi acordado')
|
||
else:
|
||
efeito, gesto = ESTIMULOS[tipo]
|
||
with self.lock:
|
||
for k, v in efeito.items():
|
||
self.necessidades[k] = _limitar(self.necessidades[k] + v)
|
||
if tipo in CUIDADOS:
|
||
self.anotar('cuidado', CUIDADOS[tipo], agora)
|
||
if tipo in CUIDADOS or tipo == 'conversa':
|
||
self.interagiu_em = agora # alguém quis ele acordado (sensor não conta)
|
||
if self.aprendizado:
|
||
self.aprendizado.resposta(tipo, agora)
|
||
self.amadurecer(tipo)
|
||
self.console('sentidos', CUIDADOS.get(tipo) or NOME_ESTIMULO.get(tipo, tipo),
|
||
', '.join('%s %+d' % (k, v) for k, v in efeito.items()),
|
||
'detalhe' if tipo in ('som', 'movimento') else 'info', agora)
|
||
if gesto and not self.bloqueado():
|
||
if self.dormindo():
|
||
self.acordar(agora, None)
|
||
self.enfileirar(gesto)
|
||
if tipo in CUIDADOS:
|
||
self.avisar()
|
||
return self.status(agora)
|
||
|
||
def dormir(self, agora, motivo, profundo=False):
|
||
self.dormindo_proprio = True
|
||
self.dormiu_em = agora
|
||
self.enfileirar('dormir')
|
||
if profundo: # cansado de verdade: sono respeitado até descansar
|
||
self.sono_profundo = True
|
||
self.enfileirar('sono profundo')
|
||
if motivo:
|
||
self.anotar('sono', motivo, agora)
|
||
|
||
def acordar(self, agora, motivo):
|
||
self.dormindo_proprio = False
|
||
if self.sono_profundo:
|
||
self.sono_profundo = False
|
||
self.enfileirar('sono leve')
|
||
self.enfileirar('acordar')
|
||
if motivo:
|
||
self.anotar('sono', motivo, agora)
|
||
|
||
def escolher_atividade(self):
|
||
"""Pelo pique: cansado prefere coisas calmas (e às vezes um café); cheio de energia, coisas agitadas.
|
||
As atividades criadas por IA também entram: as ideias de hoje com bastante chance, as antigas às vezes."""
|
||
try:
|
||
novas = [n for n in self.novidades() if 'criado:%d' % n['id'] != self.ultima_atividade]
|
||
hoje = set(self.do_dia())
|
||
except Exception:
|
||
novas, hoje = [], set()
|
||
de_hoje = [n for n in novas if n['id'] in hoje]
|
||
if de_hoje and self.rng.random() < 0.4:
|
||
return 'criado:%d' % self.rng.choice(de_hoje)['id']
|
||
if novas and self.rng.random() < 0.12:
|
||
return 'criado:%d' % self.rng.choice(novas[:20])['id']
|
||
e = self.necessidades['energia']
|
||
if 35 <= e < 55 and self.rng.random() < 0.35:
|
||
return 'cafe'
|
||
if e < 50:
|
||
lista = CALMAS * 2 + CRIATIVAS
|
||
elif e > 70:
|
||
lista = ATIVAS * 2 + CRIATIVAS + CALMAS
|
||
else:
|
||
lista = ATIVAS + CRIATIVAS * 2 + CALMAS
|
||
opcoes = [a for a in lista if a != self.ultima_atividade] or lista
|
||
pesos = [self.gosto(a) for a in opcoes]
|
||
if all(p == 1.0 for p in pesos):
|
||
return self.rng.choice(opcoes)
|
||
return self.rng.choices(opcoes, weights=pesos)[0] # as que agradam saem mais
|
||
|
||
def comecar_atividade(self, agora, nome, segundos):
|
||
if nome.startswith('criado:'):
|
||
item = next((n for n in self.novidades() if 'criado:%d' % n['id'] == nome), None)
|
||
if item:
|
||
receita = dict(item['receita'], duracao=int(segundos))
|
||
self.enfileirar('atividade_criada ' + json.dumps(receita, ensure_ascii=False, separators=(',', ':')))
|
||
self.atividade_em = agora
|
||
self.ultima_atividade = nome
|
||
motivo = ' (' + item['motivo'] + ')' if item.get('motivo') else ''
|
||
self.anotar('atividade', 'Foi fazer %s %s%s' % (item['emoji'], item['nome'], motivo), agora)
|
||
return 'atividade ' + nome
|
||
nome = self.rng.choice(CALMAS) # apagaram a criação no meio do caminho
|
||
if nome != 'dormir' and segundos > 60 and self.acaso.random() < DESISTE * (1.3 - self.personalidade['curiosidade']):
|
||
segundos = segundos * self.acaso.uniform(0.3, 0.5) # vai largar no meio: ficou chato
|
||
self.desistencia = (agora + segundos, NOME_ATIVIDADE.get(nome, 'a atividade'))
|
||
self.enfileirar('atividade %s %d' % (nome, segundos))
|
||
self.atividade_em = agora
|
||
self.ultima_atividade = nome
|
||
self.anotar('atividade', NOME_ATIVIDADE.get(nome, 'Foi fazer algo'), agora)
|
||
return 'atividade ' + nome
|
||
|
||
def dizer(self, texto, gesto=None):
|
||
"""Uma fala curta: o gesto e a frase em legenda, com a voz de robô."""
|
||
if gesto:
|
||
self.enfileirar(gesto)
|
||
self.enfileirar('dizer ' + texto)
|
||
|
||
def resumo(self):
|
||
n = self.necessidades
|
||
return 'energia %d · alegria %d · social %d · diversão %d · %s' % (
|
||
n['energia'], n['alegria'], n['social'], n['diversao'], HUMORES[self.humor()][1].lower())
|
||
|
||
@staticmethod
|
||
def tempo(seg):
|
||
return '%d min' % round(seg / 60) if seg < 3600 else '%dh%02d' % (seg // 3600, seg % 3600 // 60)
|
||
|
||
def saudacao(self, agora):
|
||
"""Bom dia com uma lembrança de ontem (do que ele viveu, sem IA)."""
|
||
hora = time.localtime(agora).tm_hour
|
||
prefixo = 'Bom dia' if 5 <= hora < 12 else 'Boa tarde' if hora < 18 else 'Boa noite'
|
||
fatos = []
|
||
ontem = None
|
||
try:
|
||
ontem = self.dia_anterior()
|
||
except Exception:
|
||
pass
|
||
if isinstance(ontem, dict):
|
||
pres = ontem.get('presenca') or []
|
||
tarde = [hh for hh in range(0, 5) if hh < len(pres) and pres[hh] is not None and pres[hh] >= 0.3]
|
||
if tarde:
|
||
fatos.append('Ontem a gente ficou acordado até as %dh, hein!' % (max(tarde) + 1))
|
||
elif len(pres) == 24 and pres[23] is not None and pres[23] >= 0.3:
|
||
fatos.append('Ontem a gente ficou até tarde, hein!')
|
||
if (ontem.get('respostas') or 0) >= 5:
|
||
fatos.append('Ontem foi um dia cheio de carinho. Obrigado!')
|
||
elif (ontem.get('atos') or 0) >= 5 and not ontem.get('respostas'):
|
||
fatos.append('Ontem ninguém brincou comigo... hoje vai ser diferente?')
|
||
if not fatos:
|
||
fatos.append('Acordei cheio de energia!' if self.necessidades['energia'] >= 80 else 'Que bom te ver!')
|
||
return '%s! %s' % (prefixo, self.acaso.choice(fatos))
|
||
|
||
def reencontro(self, agora, ausencia):
|
||
"""Alguém apareceu: a reação depende de quanto tempo ficou fora (e de quem ele é)."""
|
||
if self.dormindo() or self.madrugada(agora) or self.bloqueado():
|
||
return None
|
||
n, tim = self.necessidades, self.personalidade['timidez']
|
||
dia = time.strftime('%Y-%m-%d', time.localtime(agora))
|
||
motivo = 'ninguém por perto havia ' + self.tempo(ausencia)
|
||
if self.saudou_dia != dia and time.localtime(agora).tm_hour >= 5 and ausencia >= AUSENCIA_ACENO:
|
||
self.saudou_dia = dia
|
||
frase = self.saudacao(agora)
|
||
self.dizer(frase, 'timido' if tim > 0.65 else 'acenar')
|
||
ganho, texto, motivo = 10, 'Deu bom dia: "%s"' % frase, 'primeiro encontro do dia; ' + motivo
|
||
elif ausencia >= AUSENCIA_SAUDADE:
|
||
self.dizer('Senti sua falta!', 'comemorar')
|
||
self.enfileirar('coracao')
|
||
ganho, texto = 20, 'Fez festa: estava com saudade'
|
||
n['alegria'] = _limitar(n['alegria'] + 10)
|
||
elif ausencia >= AUSENCIA_OI:
|
||
self.dizer('Oi! Você voltou!', 'timido' if tim > 0.65 else 'comemorar')
|
||
ganho, texto = 10, 'Comemorou que alguém voltou'
|
||
elif ausencia >= AUSENCIA_ACENO:
|
||
self.enfileirar('acenar')
|
||
ganho, texto = 4, 'Acenou para quem voltou'
|
||
else:
|
||
return None
|
||
with self.lock:
|
||
n['social'] = _limitar(n['social'] + ganho)
|
||
self.anotar('social', texto, agora)
|
||
self.console('vida', texto, motivo, agora=agora)
|
||
self.avisar()
|
||
return texto
|
||
|
||
def pedir_algo(self, agora):
|
||
"""Iniciativa com motivo: pede o que está faltando (ou mostra o que fez)."""
|
||
n = self.necessidades
|
||
if n['social'] < 35:
|
||
frase, gesto, porque = 'Tô carente... me faz um carinho?', 'coracao', 'social %d' % n['social']
|
||
elif n['diversao'] < 40:
|
||
frase, gesto, porque = 'Brinca comigo?', 'acenar', 'diversão %d' % n['diversao']
|
||
elif (float(self.emojis.get('ultimo') or 0) > self.figurinha_mostrada and agora - float(self.emojis['ultimo']) < 3 * 3600
|
||
and self.tocar_reacao(agora, anotar=False)):
|
||
self.figurinha_mostrada = float(self.emojis['ultimo'])
|
||
frase, gesto, porque = 'Olha o que eu desenhei!', None, 'criou uma figurinha e ainda não tinha mostrado'
|
||
elif n['energia'] < 35:
|
||
frase, gesto, porque = 'Tô com soninho...', 'bocejar', 'energia %d' % n['energia']
|
||
else:
|
||
return None
|
||
self.pediu_em = agora
|
||
self.dizer(frase, gesto)
|
||
self.anotar('pedido', 'Pediu: "%s"' % frase, agora)
|
||
self.porque = 'alguém por perto e ' + porque
|
||
return 'pedido'
|
||
|
||
def pir_acorda(self, agora=None):
|
||
"""Disparo do PIR com o rosto dormindo: acorda? No ritmo calmo precisa de vários em 2 min."""
|
||
agora = agora or time.time()
|
||
self.pir_eventos = [t for t in self.pir_eventos if agora - t < 120] + [agora]
|
||
precisa = PIR_ACORDAR[self.config['ritmo']] if self.config['ligado'] else 1
|
||
extra = self.aprendizado.extra_pir(agora) if self.aprendizado and self.config['ligado'] else 0
|
||
if extra and precisa <= len(self.pir_eventos) < precisa + extra:
|
||
self.aprendizado.segurou_sono(agora, precisa, extra)
|
||
precisa += extra # nesta hora já acordou à toa outras vezes
|
||
if len(self.pir_eventos) >= precisa:
|
||
self.pir_eventos = []
|
||
if self.aprendizado and self.config['ligado']:
|
||
self.aprendizado.acordou_pelo_sensor(agora)
|
||
return True
|
||
return False
|
||
|
||
@staticmethod
|
||
def noite_padrao(agora):
|
||
hora = time.localtime(agora).tm_hour
|
||
ini, fim = NOITE
|
||
return hora >= ini or hora < fim
|
||
|
||
# ------------------------------------------------------------------ decidir o que fazer
|
||
def gosto(self, nome):
|
||
"""Quanto o aprendizado multiplica a chance de um ato (1 = sem opinião)."""
|
||
return self.aprendizado.peso(nome) if self.aprendizado else 1.0
|
||
|
||
def jeito(self, gesto):
|
||
"""Quanto a personalidade puxa um gesto (1 = neutro): brincalhão ri mais, tímido se esconde mais."""
|
||
f = 1.0
|
||
for traco, gestos in GESTOS_TRACO.items():
|
||
if gesto in gestos:
|
||
f *= 1 + (self.personalidade[traco] - 0.5) * 1.2
|
||
return f
|
||
|
||
def amadurecer(self, tipo):
|
||
"""O convívio muda o jeito devagar: carinho deixa menos tímido, brincar deixa mais brincalhão."""
|
||
p = self.personalidade
|
||
with self.lock:
|
||
if tipo in ('carinho', 'conversa', 'brincar'):
|
||
p['timidez'] = round(max(0.1, p['timidez'] - 0.003), 3)
|
||
if tipo == 'brincar':
|
||
p['brincadeira'] = round(min(0.95, p['brincadeira'] + 0.002), 3)
|
||
if tipo == 'conversa':
|
||
p['curiosidade'] = round(min(0.95, p['curiosidade'] + 0.001), 3)
|
||
|
||
def escolher(self, opcoes):
|
||
opcoes = [(g, p * self.gosto(g) * self.jeito(g), t) for g, p, t in opcoes]
|
||
total = sum(p for _, p, _ in opcoes)
|
||
x = self.rng.uniform(0, total)
|
||
for gesto, peso, texto in opcoes:
|
||
x -= peso
|
||
if x <= 0:
|
||
return gesto, texto
|
||
return opcoes[-1][0], opcoes[-1][2]
|
||
|
||
def motivo_sem_emoji(self, agora):
|
||
"""'' quando pode criar uma figurinha agora; senão, o porquê (aparece no painel)."""
|
||
if not self.config['criar_emojis']:
|
||
return 'desligado'
|
||
if self.treinamento is None:
|
||
return 'biblioteca indisponível'
|
||
if not self.pode_gemini():
|
||
return 'sem chave Gemini'
|
||
if self.emojis_hoje(agora) >= self.config['max_emojis_dia']:
|
||
return 'limite do dia'
|
||
if agora - float(self.emojis.get('ultimo') or 0) < INTERVALO_EMOJI:
|
||
return 'descansando a criatividade'
|
||
return ''
|
||
|
||
def criar_figurinha(self, agora, humor):
|
||
h = time.localtime(agora).tm_hour
|
||
periodo, extras = next((p, e) for ini, fim, p, e in PERIODOS if ini <= h < fim)
|
||
tema = self.rng.choice(TEMAS.get(humor, TEMAS['tranquilo']) + extras)
|
||
assunto = (f'Ideia que o próprio Carinha teve sozinho, {periodo}, se sentindo {HUMORES[humor][1].lower()}: '
|
||
f'{tema}. Figurinha fofa e simples.')
|
||
try:
|
||
self.treinamento.gerar_figurinha(assunto)
|
||
except ValueError as e:
|
||
self.anotar('emoji', 'Quis desenhar ' + tema + ', mas não deu: ' + str(e)[:80], agora)
|
||
return False
|
||
dia = time.strftime('%Y-%m-%d', time.localtime(agora))
|
||
with self.lock:
|
||
total = self.emojis['total'] + 1 if self.emojis.get('dia') == dia else 1
|
||
self.emojis.update(dia=dia, total=total, ultimo=agora)
|
||
self.necessidades['diversao'] = _limitar(self.necessidades['diversao'] + 30)
|
||
self.necessidades['alegria'] = _limitar(self.necessidades['alegria'] + 10)
|
||
self.anotar('emoji', 'Teve uma ideia e desenhou ' + tema, agora)
|
||
return True
|
||
|
||
def tocar_reacao(self, agora, anotar=True):
|
||
try:
|
||
reacoes = [r for r in self.treinamento.status()['reacoes'] if r.get('ativa', True) and r['feedback'] >= 0]
|
||
except Exception:
|
||
return False
|
||
if not reacoes:
|
||
return False
|
||
r = self.rng.choice(reacoes[:40]) if anotar else reacoes[0] # para mostrar: a mais nova
|
||
try:
|
||
self.treinamento.testar(r['id'])
|
||
except ValueError:
|
||
return False
|
||
if anotar:
|
||
self.anotar('acao', 'Lembrou da reação "' + r['receita'].get('nome', '?') + '"', agora)
|
||
return True
|
||
|
||
def agir(self, agora):
|
||
"""Um ato: o que um bichinho com essas necessidades faria agora."""
|
||
n = self.necessidades
|
||
perto = self.alguem_perto()
|
||
self.porque = ''
|
||
if self.dormindo():
|
||
# de madrugada nunca acorda sozinho (nem com gente se mexendo na cama); de dia, só descansado
|
||
if n['energia'] >= DESCANSADO and not self.madrugada(agora):
|
||
self.acordar(agora, 'Acordou descansado')
|
||
self.porque = 'energia %d: descansou' % n['energia']
|
||
return 'acordar'
|
||
# aprendeu a rotina: acorda do cochilo um pouco antes da hora em que costuma ter gente
|
||
if (self.aprendizado and not self.sono_profundo and not self.madrugada(agora) and n['energia'] >= 50
|
||
and self.aprendizado.chega_logo(agora)):
|
||
self.acordar(agora, 'Acordou: daqui a pouco costuma chegar alguém')
|
||
self.porque = self.aprendizado.porque_chegada(agora)
|
||
self.aprendizado.decidir('acordar', 'Acordou do cochilo antes da hora', self.porque, agora)
|
||
return 'acordar'
|
||
return None
|
||
if self.config['noite'] and self.madrugada(agora) and agora - self.interagiu_em > PEDIU_ATENCAO:
|
||
self.dormir(agora, 'Ficou tarde, foi dormir')
|
||
self.porque = 'madrugada e ninguém interagiu nos últimos 10 min'
|
||
return 'dormir'
|
||
if n['energia'] < SONO:
|
||
self.dormir(agora, 'Ficou exausto e foi dormir', profundo=True)
|
||
self.porque = 'energia %d: abaixo de %d, sono pesado até descansar' % (n['energia'], SONO)
|
||
return 'dormir'
|
||
humor = self.humor()
|
||
if self.celular_atividade():
|
||
return None # ocupado com uma atividade: deixa terminar
|
||
dia = self.celular_online() and not self.madrugada(agora)
|
||
folga = agora - self.atividade_em > 90 # um respiro entre uma atividade e outra
|
||
# cansado: um cochilo na caminha de vez em quando (recupera energia sem o sono pesado)
|
||
if dia and folga and n['energia'] < COCHILO and self.rng.random() < 0.35:
|
||
self.porque = 'energia %d: abaixo de %d, um cochilo' % (n['energia'], COCHILO)
|
||
return self.comecar_atividade(agora, 'dormir', self.rng.uniform(120, 240))
|
||
# entediado: vai fazer alguma coisa por alguns minutos (pode ser interrompido)
|
||
if dia and folga and n['diversao'] < TEDIO and self.rng.random() < 0.75:
|
||
self.porque = 'diversão %d: entediado (abaixo de %d)' % (n['diversao'], TEDIO)
|
||
return self.comecar_atividade(agora, self.escolher_atividade(), self.rng.uniform(120, 300))
|
||
# ninguém olhando há um tempo: vai cuidar da vida dele (ler, pintar, música...)
|
||
if perto:
|
||
self.visto_em = agora
|
||
sozinho = agora - self.visto_em > SOZINHO
|
||
# nesta hora a casa costuma ficar vazia: um cochilo longo guarda pique para quando chegarem
|
||
if (dia and folga and sozinho and self.aprendizado and n['energia'] < 85
|
||
and self.aprendizado.casa_vazia(agora) and self.rng.random() < 0.3):
|
||
segundos = self.rng.uniform(600, 1200)
|
||
self.porque = self.aprendizado.porque_vazia(agora)
|
||
self.aprendizado.decidir('cochilo', 'Tirou um cochilo de %d min para guardar energia' % round(segundos / 60),
|
||
self.porque, agora)
|
||
return self.comecar_atividade(agora, 'dormir', segundos)
|
||
# alguém por perto e ele aprendeu algo hoje: "Olha o que eu aprendi hoje!"
|
||
if dia and perto and humor != 'bravo':
|
||
item = self.aprendi_pendente()
|
||
if item and self.rng.random() < 0.3:
|
||
self.contar_aprendi(item['id'], agora)
|
||
with self.lock:
|
||
n['social'] = _limitar(n['social'] + 5)
|
||
self.porque = 'alguém por perto e aprendeu "%s" hoje' % item.get('nome', '?')
|
||
return 'aprendi'
|
||
# iniciativa: com gente por perto, pede o que está faltando (tímido pede menos)
|
||
if (dia and perto and not sozinho and agora - self.pediu_em > INTERVALO_PEDIDO
|
||
and self.acaso.random() < 0.25 * (1.3 - self.personalidade['timidez'])):
|
||
pedido = self.pedir_algo(agora)
|
||
if pedido:
|
||
return pedido
|
||
if dia and folga and sozinho and self.rng.random() < 0.4:
|
||
self.porque = 'ninguém por perto há %s: vai cuidar da vida dele' % self.tempo(agora - self.visto_em)
|
||
return self.comecar_atividade(agora, self.escolher_atividade(), self.rng.uniform(60, 180))
|
||
# bem: de vez em quando, uma atividade curtinha por vontade própria
|
||
if dia and folga and humor in ('feliz', 'tranquilo') and self.rng.random() < 0.1:
|
||
self.porque = 'de bem (%s): vontade própria' % humor
|
||
return self.comecar_atividade(agora, self.escolher_atividade(), self.rng.uniform(40, 90))
|
||
# habilidade: inventar uma figurinha nova (raro, com limite por dia e intervalo)
|
||
if not self.motivo_sem_emoji(agora) and self.celular_online() and self.rng.random() < 0.08:
|
||
if self.criar_figurinha(agora, humor):
|
||
self.porque = 'teve uma ideia (até %d figurinhas por dia)' % self.config['max_emojis_dia']
|
||
return 'figurinha'
|
||
# às vezes lembra de uma reação da biblioteca
|
||
if self.treinamento is not None and humor in ('feliz', 'tranquilo', 'entediado') and self.rng.random() < 0.15:
|
||
if self.tocar_reacao(agora):
|
||
with self.lock:
|
||
n['diversao'] = _limitar(n['diversao'] + 8)
|
||
self.porque = 'humor %s: lembrou de uma reação da biblioteca' % humor
|
||
return 'reacao'
|
||
lista = ATOS['carente_perto'] if humor == 'carente' and perto else ATOS.get(humor, ATOS['tranquilo'])
|
||
if sozinho: # sem plateia: nada de acenar ou ficar tímido
|
||
lista = [a for a in lista if a[0] not in PARA_ALGUEM] or ATOS['tranquilo'][:3]
|
||
gesto, texto = self.escolher(lista)
|
||
self.porque = 'humor %s%s%s' % (
|
||
humor, ' com gente por perto' if perto else ', sozinho' if sozinho else '',
|
||
'; gosto %.1f×, jeito %.1f×' % (self.gosto(gesto), self.jeito(gesto))
|
||
if (self.gosto(gesto), round(self.jeito(gesto), 1)) != (1.0, 1.0) else '')
|
||
if self.aprendizado and perto:
|
||
self.aprendizado.escolheu(gesto, agora)
|
||
self.enfileirar(gesto)
|
||
if humor == 'entediado':
|
||
with self.lock:
|
||
n['diversao'] = _limitar(n['diversao'] + 3) # fazer algo distrai um pouquinho
|
||
self.anotar('acao', texto, agora)
|
||
return gesto
|
||
|
||
def aprender_ato(self, ato, agora):
|
||
"""Gestos e atividades feitos com plateia entram no aprendizado (para ver se agradam)."""
|
||
if not self.aprendizado or not ato:
|
||
return
|
||
if ato.startswith('atividade '):
|
||
nome = ato[len('atividade '):]
|
||
elif ato in GESTOS_VIDA:
|
||
nome = ato
|
||
else:
|
||
return
|
||
self.aprendizado.ato(nome, agora, self.alguem_perto())
|
||
|
||
def tick(self, agora=None):
|
||
agora = agora or time.time()
|
||
self.passar_tempo(agora)
|
||
if not self.config['ligado']:
|
||
return None
|
||
if self.aprendizado:
|
||
self.aprendizado.observar(agora, self.alguem_perto())
|
||
ato = None
|
||
humor = self.humor()
|
||
# acordaram ele de propósito (toque, painel) antes de descansar: o sono pesado acaba aqui
|
||
if self.sono_profundo and not self.dormindo() and agora - self.dormiu_em > 15:
|
||
self.sono_profundo = False
|
||
self.enfileirar('sono leve')
|
||
self.anotar('sono', 'Acordou antes de descansar', agora)
|
||
perto = self.alguem_perto()
|
||
if perto:
|
||
if not self.perto_antes:
|
||
self.reencontro(agora, agora - self.ultimo_perto)
|
||
self.ultimo_perto = agora
|
||
self.perto_antes = perto
|
||
if self.desistencia and agora >= self.desistencia[0]:
|
||
_, oque = self.desistencia
|
||
self.desistencia = None
|
||
if not self.dormindo():
|
||
self.enfileirar('bocejar')
|
||
acao = oque[4:] if oque.startswith('Foi ') else 'a atividade'
|
||
texto = 'Largou no meio: cansou de ' + acao
|
||
self.anotar('acao', texto, agora)
|
||
self.console('vida', texto, 'perdeu a graça (curiosidade %.2f)' % self.personalidade['curiosidade'], agora=agora)
|
||
if not self.bloqueado():
|
||
# o celular escolhe os gestos soltos pelo humor; reenvia de tempos em tempos (pode ter reiniciado)
|
||
zanga = self.zanga()
|
||
if humor != self.humor_enviado[0] or agora - self.humor_enviado[1] > 60 or zanga != self.zanga_enviada:
|
||
self.enfileirar('humor ' + humor)
|
||
self.enfileirar('ritmo ' + self.config['ritmo'])
|
||
self.enfileirar('zanga %.1f' % zanga)
|
||
if self.sono_profundo:
|
||
self.enfileirar('sono profundo')
|
||
self.humor_enviado = (humor, agora)
|
||
self.zanga_enviada = zanga
|
||
if agora >= self.proximo_ato:
|
||
antes = self.diario[0]['horario'] if self.diario else None
|
||
ato = self.agir(agora)
|
||
self.aprender_ato(ato, agora)
|
||
if ato:
|
||
novo = self.diario[0] if self.diario and self.diario[0]['horario'] != antes else None
|
||
self.ultima = dict(horario=agora, texto=novo['texto'] if novo else ato, porque=self.porque)
|
||
self.console('vida', novo['texto'] if novo else ato,
|
||
(self.porque + ' · ' if self.porque else '') + self.resumo(), agora=agora)
|
||
lo, hi = RITMOS[self.config['ritmo']]
|
||
self.proximo_ato = agora + self.rng.uniform(lo, hi)
|
||
if ato or agora - self.publicado_em > 30 or humor != self.humor():
|
||
self.avisar()
|
||
if agora - self.salvo_em > 60:
|
||
self.salvar()
|
||
return ato
|
||
|
||
def loop(self):
|
||
while True:
|
||
time.sleep(5)
|
||
try:
|
||
self.tick()
|
||
except Exception as e: # nunca derruba a thread
|
||
print('vida: erro no tick:', e, flush=True)
|
||
|
||
# ------------------------------------------------------------------ HTTP
|
||
def rotas(self, h, metodo):
|
||
rota = h.path.split('?', 1)[0]
|
||
if not rota.startswith('/vida/'):
|
||
return False
|
||
try:
|
||
if metodo == 'GET' and rota == '/vida/status':
|
||
resultado = self.status()
|
||
elif metodo == 'POST' and rota == '/vida/config':
|
||
resultado = self.configurar(ler_json(h, 2048))
|
||
elif metodo == 'POST' and rota == '/vida/cuidar':
|
||
resultado = self.estimulo(ler_json(h, 2048).get('acao'))
|
||
else:
|
||
raise ValueError('Rota inválida')
|
||
h.enviar(json.dumps(resultado, ensure_ascii=False))
|
||
except (ValueError, TypeError) as e:
|
||
h.enviar(json.dumps(dict(erro=str(e)), ensure_ascii=False), codigo=400)
|
||
return True
|