"""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'), } 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 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 = [] 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 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('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) 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]) 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 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.""" 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 return self.rng.choice(opcoes) def comecar_atividade(self, agora, nome, segundos): 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 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 if len(self.pir_eventos) >= precisa: self.pir_eventos = [] 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 escolher(self, 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): 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]) try: self.treinamento.testar(r['id']) except ValueError: return False 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() 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') 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') return 'dormir' if n['energia'] < SONO: self.dormir(agora, 'Ficou exausto e foi dormir', profundo=True) 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: 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: 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 if dia and folga and sozinho and self.rng.random() < 0.4: 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: 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): 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) 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.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 tick(self, agora=None): agora = agora or time.time() self.passar_tempo(agora) if not self.config['ligado']: return None 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) 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: ato = self.agir(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