feat(vida): 20 atividades e necessidades no estilo The Sims
Quatorze cenas novas para o bonequinho: cochilar na caminha (cobertor respirando e Zzz), café (vapor e gole), cozinhar (vira a panqueca no fogão), regar a plantinha (cresce e dá flor), violão, dançar (globo espelhado), pular corda, meditar flutuando, telescópio (céu estrelado e estrela cadente), pipa com rabiola, bolhas de sabão, skate (com ollie), computador (código aparecendo) e pescar (o peixe pula preso na linha). Necessidades puxam comportamento, como no The Sims: - energia < 40%: às vezes cochila; < 20%: dorme de verdade com sono respeitado (som comum, movimento e PIR não acordam) até 95%; acordar de propósito (toque, painel) encerra o sono pesado; - alegria abaixo de 60%: o rosto vai ficando emburrado (zanga 0 a 1: testa franzida, boca para baixo, olhos bravos); < 30%: mal-humorado; - diversão < 45%: faz uma atividade de 2 a 5 min escolhida pelo pique (cansado prefere calmas, café dá energia); pode ser interrompido; - durante a atividade a diversão sobe e a energia cai conforme o tipo. Com o modo vida ligado o celular não adormece mais por 20 s de silêncio: quem decide o sono é a energia. A cor escolhida nunca muda com a zanga. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -212,7 +212,7 @@ function caminhoBoca(tipo) {
|
|||||||
// repouso: leve, com as microexpressões (canto que sobe, boca de lado, entreaberta)
|
// repouso: leve, com as microexpressões (canto que sobe, boca de lado, entreaberta)
|
||||||
const f = FEICAO.atual, w = l * 0.30, mx = cx + f.dx * l * 0.10;
|
const f = FEICAO.atual, w = l * 0.30, mx = cx + f.dx * l * 0.10;
|
||||||
const yE = cy - Math.max(0, f.lado) * l * 0.12, yD = cy - Math.max(0, -f.lado) * l * 0.12;
|
const yE = cy - Math.max(0, f.lado) * l * 0.12, yD = cy - Math.max(0, -f.lado) * l * 0.12;
|
||||||
const curva = l * (0.18 + f.curva * 0.35);
|
const curva = l * (0.18 + f.curva * 0.35 - ZANGA * 0.3);
|
||||||
let d = "M " + (mx - w) + "," + yE + " Q " + mx + "," + (cy + curva - f.abre * l * 0.12) + " " + (mx + w) + "," + yD;
|
let d = "M " + (mx - w) + "," + yE + " Q " + mx + "," + (cy + curva - f.abre * l * 0.12) + " " + (mx + w) + "," + yD;
|
||||||
if (f.abre > 0.04) d += " Q " + mx + "," + (cy + curva + f.abre * l * 0.30) + " " + (mx - w) + "," + yE;
|
if (f.abre > 0.04) d += " Q " + mx + "," + (cy + curva + f.abre * l * 0.30) + " " + (mx - w) + "," + yE;
|
||||||
return d;
|
return d;
|
||||||
@@ -224,7 +224,8 @@ function caminhoSobrancelha(i, esc) {
|
|||||||
const ab = i === 0 ? r.ab : (r.ab2 === null ? r.ab : r.ab2);
|
const ab = i === 0 ? r.ab : (r.ab2 === null ? r.ab : r.ab2);
|
||||||
const piscando = 1 - Math.min(1, Math.max(0, ab));
|
const piscando = 1 - Math.min(1, Math.max(0, ab));
|
||||||
const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC - (f.sob[i] - piscando * 3) * ESC;
|
const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC - (f.sob[i] - piscando * 3) * ESC;
|
||||||
const inc = ((r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : 0) + f.inc[i]) * lado * ESC;
|
const base = r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : ZANGA * 14; // zanga franze a testa
|
||||||
|
const inc = (base + f.inc[i]) * lado * ESC;
|
||||||
const ele = r.esc > 1.1 ? -15 * ESC : 0;
|
const ele = r.esc > 1.1 ? -15 * ESC : 0;
|
||||||
return 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele);
|
return 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele);
|
||||||
}
|
}
|
||||||
@@ -401,12 +402,17 @@ const LIVRES = ["feliz", "olhar_esq", "olhar_dir", "olhar_baixo",
|
|||||||
const LIVRES_HUMOR = {
|
const LIVRES_HUMOR = {
|
||||||
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
|
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
|
||||||
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
|
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
|
||||||
|
bravo: ["bravo", "olhar_baixo", "olhar_esq", "olhar_dir", "confuso"],
|
||||||
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
|
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
|
||||||
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
|
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
|
||||||
triste: ["triste", "olhar_baixo", "olhar_baixo"],
|
triste: ["triste", "olhar_baixo", "olhar_baixo"],
|
||||||
cansado: ["olhar_baixo", "piscar2", "bocejar"],
|
cansado: ["olhar_baixo", "piscar2", "bocejar"],
|
||||||
};
|
};
|
||||||
let HUMOR = "normal";
|
let HUMOR = "normal";
|
||||||
|
/* Modo vida: zanga (0 a 1) deixa o rosto emburrado aos poucos quando a alegria cai; sono profundo =
|
||||||
|
dormiu de cansaço, e aí som, movimento e câmera não acordam (como na madrugada). */
|
||||||
|
let ZANGA = 0;
|
||||||
|
let SONO_PROFUNDO = false;
|
||||||
|
|
||||||
/* Microexpressões acordado: de vez em quando a boca e as sobrancelhas mexem sozinhas (um canto
|
/* Microexpressões acordado: de vez em quando a boca e as sobrancelhas mexem sozinhas (um canto
|
||||||
da boca sobe, uma sobrancelha arqueia, um suspiro) e voltam devagar ao neutro. O humor do modo
|
da boca sobe, uma sobrancelha arqueia, um suspiro) e voltam devagar ao neutro. O humor do modo
|
||||||
@@ -431,6 +437,7 @@ const MICRO_HUMOR = {
|
|||||||
carente: ["preocupado", "preocupado", "suspiro", "meioSorriso"],
|
carente: ["preocupado", "preocupado", "suspiro", "meioSorriso"],
|
||||||
triste: ["preocupado", "suspiro", "pensativo"],
|
triste: ["preocupado", "suspiro", "pensativo"],
|
||||||
cansado: ["suspiro", "suspiro", "ladinho"],
|
cansado: ["suspiro", "suspiro", "ladinho"],
|
||||||
|
bravo: ["pensativo", "pensativo", "ladinho", "suspiro"],
|
||||||
normal: ["meioSorriso", "arquear", "sorrisinho", "ladinho", "interesse", "pensativo", "suspiro"],
|
normal: ["meioSorriso", "arquear", "sorrisinho", "ladinho", "interesse", "pensativo", "suspiro"],
|
||||||
};
|
};
|
||||||
function atualizarFeicao(s) {
|
function atualizarFeicao(s) {
|
||||||
@@ -495,11 +502,11 @@ function passo(dt) { // avanca a fila; true se ha gesto em
|
|||||||
|
|
||||||
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
||||||
let ATIV = null;
|
let ATIV = null;
|
||||||
function atividade(nome) {
|
function atividade(nome, segundos) {
|
||||||
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
||||||
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
||||||
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
||||||
if (window.MotorRosto.atividade(nome) && nome === "cantar") tocarSom("musica");
|
if (window.MotorRosto.atividade(nome, segundos) && nome === "cantar") tocarSom("musica");
|
||||||
}
|
}
|
||||||
|
|
||||||
function repouso(t) { // pose calma: olha devagar, "respira"
|
function repouso(t) { // pose calma: olha devagar, "respira"
|
||||||
@@ -526,6 +533,8 @@ function repouso(t) { // pose calma: olha devagar, "respira
|
|||||||
if (ATIV.forma) r.forma = ATIV.forma;
|
if (ATIV.forma) r.forma = ATIV.forma;
|
||||||
if (ATIV.boca) r.boca = ATIV.boca;
|
if (ATIV.boca) r.boca = ATIV.boca;
|
||||||
if (ATIV.ab) r.ab = ATIV.ab;
|
if (ATIV.ab) r.ab = ATIV.ab;
|
||||||
|
} else if (ZANGA >= 0.7) { // alegria caindo: emburrado (a cor escolhida fica)
|
||||||
|
r.forma = "bravo";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +643,13 @@ function trata(ordem) {
|
|||||||
} else if (ordem.indexOf("ritmo ") === 0) {
|
} else if (ordem.indexOf("ritmo ") === 0) {
|
||||||
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
||||||
} else if (ordem.indexOf("atividade ") === 0) {
|
} else if (ordem.indexOf("atividade ") === 0) {
|
||||||
atividade(ordem.slice(10).trim());
|
const [nome, seg] = ordem.slice(10).trim().split(/\s+/); // "atividade bola 180"
|
||||||
|
atividade(nome, parseFloat(seg) || 0);
|
||||||
|
} else if (ordem.indexOf("zanga ") === 0) {
|
||||||
|
const z = parseFloat(ordem.slice(6));
|
||||||
|
if (!isNaN(z)) ZANGA = Math.max(0, Math.min(1, z));
|
||||||
|
} else if (ordem === "sono profundo" || ordem === "sono leve") {
|
||||||
|
SONO_PROFUNDO = ordem === "sono profundo";
|
||||||
} else if (ordem === "dormir" || ordem === "sono") {
|
} else if (ordem === "dormir" || ordem === "sono") {
|
||||||
r.dormindo = true;
|
r.dormindo = true;
|
||||||
atividade(null);
|
atividade(null);
|
||||||
@@ -790,6 +805,7 @@ const MIC = {
|
|||||||
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
|
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
|
||||||
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
|
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
|
||||||
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
|
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
|
||||||
|
function sonoRespeitado() { return madrugada() || SONO_PROFUNDO; }
|
||||||
function madrugada() {
|
function madrugada() {
|
||||||
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
|
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
|
||||||
const h = new Date().getHours();
|
const h = new Date().getHours();
|
||||||
@@ -855,7 +871,7 @@ function som(nivel) {
|
|||||||
|
|
||||||
if (r.dormindo) {
|
if (r.dormindo) {
|
||||||
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
|
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
|
||||||
if (contar(CONTA.somAcordar, madrugada() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
|
if (contar(CONTA.somAcordar, sonoRespeitado() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
|
||||||
r.dormindo = false;
|
r.dormindo = false;
|
||||||
animar("surpreso");
|
animar("surpreso");
|
||||||
MIC.proxSurpresa = agora + MIC.cooldown;
|
MIC.proxSurpresa = agora + MIC.cooldown;
|
||||||
@@ -878,6 +894,7 @@ function checarSilencio(agora) {
|
|||||||
if (!MIC.ativo) return; // telinha do Pi: sem ouvido
|
if (!MIC.ativo) return; // telinha do Pi: sem ouvido
|
||||||
if (agora - MIC.visto > 2) return; // o ouvido parou de mandar dado
|
if (agora - MIC.visto > 2) return; // o ouvido parou de mandar dado
|
||||||
if (r.dormindo) return;
|
if (r.dormindo) return;
|
||||||
|
if (HUMOR !== "normal") return; // modo vida ligado: quem manda dormir é a energia
|
||||||
// movimento tambem conta como "tem alguem aqui": nao adormece com gente passando
|
// movimento tambem conta como "tem alguem aqui": nao adormece com gente passando
|
||||||
const atividade = Math.max(MIC.ultimoSom, MOV.ativo ? MOV.ultimoMov : 0);
|
const atividade = Math.max(MIC.ultimoSom, MOV.ativo ? MOV.ultimoMov : 0);
|
||||||
if (agora - atividade >= MIC.segSilencio) {
|
if (agora - atividade >= MIC.segSilencio) {
|
||||||
@@ -949,7 +966,7 @@ function rastrear(t) {
|
|||||||
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
|
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
|
||||||
if(t.atividade===true) {
|
if(t.atividade===true) {
|
||||||
MOV.ultimoMov=agora;
|
MOV.ultimoMov=agora;
|
||||||
if(r.dormindo && !madrugada() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
|
if(r.dormindo && !sonoRespeitado() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function movimento(vel, cx, cy) {
|
function movimento(vel, cx, cy) {
|
||||||
@@ -981,7 +998,7 @@ function movimento(vel, cx, cy) {
|
|||||||
const forte = v >= limiar(MOV.limiarForte);
|
const forte = v >= limiar(MOV.limiarForte);
|
||||||
if (r.dormindo) {
|
if (r.dormindo) {
|
||||||
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
|
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
|
||||||
if (!madrugada() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
|
if (!sonoRespeitado() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
|
||||||
r.dormindo = false;
|
r.dormindo = false;
|
||||||
animar("surpreso");
|
animar("surpreso");
|
||||||
MOV.proxSurpresa = agora + MOV.cooldown;
|
MOV.proxSurpresa = agora + MOV.cooldown;
|
||||||
|
|||||||
@@ -524,12 +524,26 @@
|
|||||||
// vale para qualquer rosto (SVG ou motor). O carinha.html pergunta para onde olhar
|
// vale para qualquer rosto (SVG ou motor). O carinha.html pergunta para onde olhar
|
||||||
// (olharAtividade) e chama palco() a cada quadro.
|
// (olharAtividade) e chama palco() a cada quadro.
|
||||||
const ATIVIDADES = {
|
const ATIVIDADES = {
|
||||||
ler: { nome: 'Lendo', emoji: '📖', dur: [25, 45] },
|
ler: { nome: 'Lendo', emoji: '📖', dur: [25, 45] },
|
||||||
cantar: { nome: 'Cantando', emoji: '🎤', dur: [18, 30] },
|
cantar: { nome: 'Cantando', emoji: '🎤', dur: [18, 30] },
|
||||||
bola: { nome: 'Jogando bola', emoji: '⚽', dur: [20, 35] },
|
bola: { nome: 'Jogando bola', emoji: '⚽', dur: [20, 35] },
|
||||||
videogame: { nome: 'Videogame', emoji: '🎮', dur: [25, 45] },
|
videogame: { nome: 'Videogame', emoji: '🎮', dur: [25, 45] },
|
||||||
pintar: { nome: 'Pintando', emoji: '🎨', dur: [25, 40] },
|
pintar: { nome: 'Pintando', emoji: '🎨', dur: [25, 40] },
|
||||||
malhar: { nome: 'Malhando', emoji: '🏋️', dur: [15, 25] },
|
malhar: { nome: 'Malhando', emoji: '🏋️', dur: [15, 25] },
|
||||||
|
dormir: { nome: 'Cochilando', emoji: '🛏️', dur: [40, 80] },
|
||||||
|
cafe: { nome: 'Tomando café', emoji: '☕', dur: [20, 35] },
|
||||||
|
cozinhar: { nome: 'Cozinhando', emoji: '🍳', dur: [20, 35] },
|
||||||
|
regar: { nome: 'Regando planta', emoji: '🪴', dur: [20, 30] },
|
||||||
|
violao: { nome: 'Tocando violão', emoji: '🎸', dur: [20, 35] },
|
||||||
|
dancar: { nome: 'Dançando', emoji: '💃', dur: [15, 30] },
|
||||||
|
corda: { nome: 'Pulando corda', emoji: '🪢', dur: [15, 25] },
|
||||||
|
meditar: { nome: 'Meditando', emoji: '🧘', dur: [25, 45] },
|
||||||
|
telescopio: { nome: 'Olhando estrelas', emoji: '🔭', dur: [25, 40] },
|
||||||
|
pipa: { nome: 'Soltando pipa', emoji: '🪁', dur: [20, 35] },
|
||||||
|
bolhas: { nome: 'Bolhas de sabão', emoji: '🫧', dur: [18, 30] },
|
||||||
|
skate: { nome: 'Andando de skate', emoji: '🛹', dur: [18, 30] },
|
||||||
|
computador: { nome: 'No computador', emoji: '💻', dur: [25, 45] },
|
||||||
|
pescar: { nome: 'Pescando', emoji: '🎣', dur: [30, 50] },
|
||||||
};
|
};
|
||||||
const ativ = { nome: null, t0: 0, dur: 0, fim: 0 };
|
const ativ = { nome: null, t0: 0, dur: 0, fim: 0 };
|
||||||
let palcoTela = null, pctx = null;
|
let palcoTela = null, pctx = null;
|
||||||
@@ -582,6 +596,38 @@
|
|||||||
const p = pincel(k); info.x = 16 + lado * 5 + p.x * 4; info.y = -1 + p.y * 5;
|
const p = pincel(k); info.x = 16 + lado * 5 + p.x * 4; info.y = -1 + p.y * 5;
|
||||||
} else if (ativ.nome === 'malhar') {
|
} else if (ativ.nome === 'malhar') {
|
||||||
const f = forca(k); info.x = lado * 14; info.y = 6 - f * 12; info.ab = f > 0.85 ? 0.55 : 1;
|
const f = forca(k); info.x = lado * 14; info.y = 6 - f * 12; info.ab = f > 0.85 ? 0.55 : 1;
|
||||||
|
} else if (ativ.nome === 'dormir') {
|
||||||
|
info.forma = 'dormir'; info.boca = 'sono'; info.x = lado * 6; info.y = 4;
|
||||||
|
} else if (ativ.nome === 'cafe') {
|
||||||
|
const f = (k % 5) / 5, gole = f > 0.55 && f < 0.92;
|
||||||
|
info.x = lado * 12; info.y = gole ? -3 : 3; if (gole) info.forma = 'feliz';
|
||||||
|
} else if (ativ.nome === 'cozinhar') {
|
||||||
|
const cc = (k % 2.6) / 2.6, h = cc < 0.55 ? 4 * (cc / 0.55) * (1 - cc / 0.55) : 0;
|
||||||
|
info.x = lado * 16; info.y = 4 - h * 14;
|
||||||
|
} else if (ativ.nome === 'regar') {
|
||||||
|
info.x = lado * 18; info.y = 8;
|
||||||
|
} else if (ativ.nome === 'violao') {
|
||||||
|
info.x = lado * 8 + 3 * Math.sin(k * 1.1); info.y = 6 + 1.5 * Math.sin(k * 2.2); info.boca = 'sorriso';
|
||||||
|
if (Math.sin(k * 0.7) > 0.3) info.forma = 'feliz';
|
||||||
|
} else if (ativ.nome === 'dancar') {
|
||||||
|
info.x = lado * 10 + 6 * Math.sin(k * 5); info.y = -2 + 4 * Math.abs(Math.sin(k * 5)); info.forma = 'feliz'; info.boca = 'sorriso';
|
||||||
|
} else if (ativ.nome === 'corda') {
|
||||||
|
const th = k * Math.PI * 2 * 0.85; info.x = lado * 12; info.y = 4 - Math.pow(Math.max(0, Math.cos(th)), 0.6) * 10;
|
||||||
|
} else if (ativ.nome === 'meditar') {
|
||||||
|
info.forma = 'dormir'; info.boca = 'sorriso'; info.x = lado * 6; info.y = -2 + 2 * Math.sin(k * 1.2);
|
||||||
|
} else if (ativ.nome === 'telescopio') {
|
||||||
|
info.x = lado * 16; info.y = -11;
|
||||||
|
} else if (ativ.nome === 'pipa') {
|
||||||
|
info.x = lado * 14 + 4 * Math.sin(k * 0.8); info.y = -11 + 2 * Math.cos(k * 1.1);
|
||||||
|
} else if (ativ.nome === 'bolhas') {
|
||||||
|
info.x = lado * 14 + 4 * Math.sin(k * 0.9); info.y = -8 + 3 * Math.sin(k * 0.6);
|
||||||
|
} else if (ativ.nome === 'skate') {
|
||||||
|
info.x = lado * 12 + 7 * Math.sin(k * 0.9); info.y = 6;
|
||||||
|
} else if (ativ.nome === 'computador') {
|
||||||
|
info.x = lado * 18 + Math.sin(k * 20) * 0.6; info.y = 2 + ((k * 2.5) % 1) * 1.5;
|
||||||
|
} else if (ativ.nome === 'pescar') {
|
||||||
|
const cc = k % 8; info.x = lado * 18; info.y = 7;
|
||||||
|
if (cc > 6.2 && cc < 7.6) { info.forma = 'feliz'; info.boca = 'sorriso'; info.y = 0; }
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@@ -618,8 +664,9 @@
|
|||||||
caixa(c, -s * 0.66, -s * 0.98, s * 1.32, s * 0.98, s * 0.28); c.fill(); c.stroke();
|
caixa(c, -s * 0.66, -s * 0.98, s * 1.32, s * 0.98, s * 0.28); c.fill(); c.stroke();
|
||||||
const ox = (p.olhoX || 0) * s * 0.12, oy = (p.olhoY || 0) * s * 0.1;
|
const ox = (p.olhoX || 0) * s * 0.12, oy = (p.olhoY || 0) * s * 0.1;
|
||||||
const pisca = (k % 3.7) < 0.12 ? 0.15 : 1;
|
const pisca = (k % 3.7) < 0.12 ? 0.15 : 1;
|
||||||
c.save(); c.fillStyle = css(cor);
|
c.save(); c.fillStyle = css(cor); c.strokeStyle = css(cor);
|
||||||
for (const lado of [-1, 1]) {
|
for (const lado of [-1, 1]) {
|
||||||
|
if (p.fechados) { c.lineWidth = Math.max(1.5, s * 0.07); c.beginPath(); c.arc(lado * s * 0.27, -s * 0.52, s * 0.12, 0.2, Math.PI - 0.2); c.stroke(); continue; }
|
||||||
c.beginPath(); c.ellipse(lado * s * 0.27 + ox, -s * 0.5 + oy, s * 0.11, s * 0.16 * pisca, 0, 0, Math.PI * 2); c.fill();
|
c.beginPath(); c.ellipse(lado * s * 0.27 + ox, -s * 0.5 + oy, s * 0.11, s * 0.16 * pisca, 0, 0, Math.PI * 2); c.fill();
|
||||||
}
|
}
|
||||||
c.restore(); c.restore();
|
c.restore(); c.restore();
|
||||||
@@ -644,6 +691,56 @@
|
|||||||
c.beginPath(); c.moveTo(x + s * 0.2, y - s * 0.7); c.quadraticCurveTo(x + s * 0.5, y - s * 0.55, x + s * 0.42, y - s * 0.3); c.stroke();
|
c.beginPath(); c.moveTo(x + s * 0.2, y - s * 0.7); c.quadraticCurveTo(x + s * 0.5, y - s * 0.55, x + s * 0.42, y - s * 0.3); c.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROSA_ = [255, 112, 176], AMARELO_ = [255, 218, 96], VERDE_ = [120, 255, 160], LARANJA_ = [255, 150, 70];
|
||||||
|
const SENTADO = { sentado: true, pE: [-1.35, 1.35], pD: [1.35, -1.35] };
|
||||||
|
const rnd = (i, sem) => { const a = Math.sin(i * 12.9898 + sem * 78.233) * 43758.5453; return a - Math.floor(a); };
|
||||||
|
function estrela(c, x, y, r) {
|
||||||
|
c.beginPath();
|
||||||
|
for (let i = 0; i < 10; i++) { const a = -Math.PI / 2 + i * Math.PI / 5, rr = i % 2 ? r * 0.45 : r; c.lineTo(x + Math.cos(a) * rr, y + Math.sin(a) * rr); }
|
||||||
|
c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
function vapor(c, x, y, s, k, cor) { // três fios subindo e ondulando
|
||||||
|
c.save(); c.strokeStyle = css(cor, 0.7); c.lineWidth = Math.max(1, s * 0.04);
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.5 + i / 3) % 1, bx = x + (i - 1) * s * 0.1, by = y - f * s * 0.7;
|
||||||
|
c.globalAlpha = alfa * (1 - f) * Math.min(1, f / 0.2);
|
||||||
|
c.beginPath(); c.moveTo(bx, by); c.bezierCurveTo(bx + s * 0.1, by - s * 0.12, bx - s * 0.1, by - s * 0.24, bx, by - s * 0.36); c.stroke();
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
function xicara(c, x, y, s, cor) {
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(x - s * 0.18, y - s * 0.2); c.lineTo(x + s * 0.18, y - s * 0.2); c.lineTo(x + s * 0.14, y + s * 0.08); c.lineTo(x - s * 0.14, y + s * 0.08); c.closePath(); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.arc(x + s * 0.2, y - s * 0.07, s * 0.08, -1.4, 1.4); c.stroke();
|
||||||
|
}
|
||||||
|
function chama(c, x, y, alt, s) {
|
||||||
|
c.fillStyle = css(LARANJA_);
|
||||||
|
c.beginPath(); c.moveTo(x, y - alt); c.quadraticCurveTo(x + s * 0.13, y - alt * 0.3, x, y); c.quadraticCurveTo(x - s * 0.13, y - alt * 0.3, x, y - alt); c.fill();
|
||||||
|
c.fillStyle = css(AMARELO_);
|
||||||
|
c.beginPath(); c.moveTo(x, y - alt * 0.6); c.quadraticCurveTo(x + s * 0.06, y - alt * 0.2, x, y); c.quadraticCurveTo(x - s * 0.06, y - alt * 0.2, x, y - alt * 0.6); c.fill();
|
||||||
|
}
|
||||||
|
function zzz(c, x, y, s, k, cor) { // Zs subindo
|
||||||
|
c.save(); c.strokeStyle = css(cor); c.lineCap = 'round'; c.lineJoin = 'round';
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.3 + i / 3) % 1, tam = s * (0.18 + 0.2 * f), zx = x - s * 0.5 * f, zy = y - s * 1.3 * f;
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.15) * (1 - f); c.lineWidth = Math.max(1.5, tam * 0.2);
|
||||||
|
c.beginPath(); c.moveTo(zx - tam / 2, zy - tam / 2); c.lineTo(zx + tam / 2, zy - tam / 2); c.lineTo(zx - tam / 2, zy + tam / 2); c.lineTo(zx + tam / 2, zy + tam / 2); c.stroke();
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
function notas(c, x, y, s, k, cor, dir) {
|
||||||
|
c.fillStyle = css(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.45 + i / 3) % 1;
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.15) * (1 - f);
|
||||||
|
nota(c, x + dir * s * (0.3 + 0.5 * i) + Math.sin(f * 6 + i) * s * 0.2, y - f * s * 1.6, s * 0.7);
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
|
||||||
const CENAS = {
|
const CENAS = {
|
||||||
ler(c, x, y, s, k, cor) {
|
ler(c, x, y, s, k, cor) {
|
||||||
const b = boneco(c, x, y, s, {
|
const b = boneco(c, x, y, s, {
|
||||||
@@ -786,6 +883,321 @@
|
|||||||
c.beginPath(); c.arc(b.cabeca.x + s * 0.72, b.cabeca.y + s * 0.3 + g * s * 0.5, s * 0.07, 0, Math.PI * 2); c.fill();
|
c.beginPath(); c.arc(b.cabeca.x + s * 0.72, b.cabeca.y + s * 0.3 + g * s * 0.5, s * 0.07, 0, Math.PI * 2); c.fill();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
dormir(c, x, y, s, k, cor) {
|
||||||
|
// cama com cabeceira, travesseiro e cobertor que sobe e desce com a respiração
|
||||||
|
const bx0 = x - s * 1.7, bx1 = x + s * 1.9, topo = y - s * 0.75;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(bx0 + s * 0.12, y); c.lineTo(bx0 + s * 0.12, topo); c.moveTo(bx1 - s * 0.12, y); c.lineTo(bx1 - s * 0.12, topo); c.stroke();
|
||||||
|
caixa(c, bx0 - s * 0.08, topo - s * 1.25, s * 0.24, s * 1.3, s * 0.1); c.fill(); c.stroke();
|
||||||
|
caixa(c, bx0, topo - s * 0.3, bx1 - bx0, s * 0.32, s * 0.12); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.9);
|
||||||
|
c.beginPath(); c.ellipse(bx0 + s * 0.85, topo - s * 0.5, s * 0.6, s * 0.24, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
// bonequinho deitado: desenhado em pé e girado 90°, pés à direita, cabeça no travesseiro
|
||||||
|
const e = 0.72, linha = topo - s * 0.3 - s * e * 0.5;
|
||||||
|
c.save(); c.translate(x + s * 1.5, linha); c.rotate(-Math.PI / 2);
|
||||||
|
boneco(c, 0, 0, s * e, { fechados: true, bE: [-0.1, 0], bD: [0.1, 0] }, cor, k);
|
||||||
|
c.restore();
|
||||||
|
const resp = Math.sin(k * 1.4) * s * 0.04;
|
||||||
|
c.fillStyle = css(cor.map((v) => v * 0.45)); c.strokeStyle = css(cor);
|
||||||
|
caixa(c, x - s * 0.3, linha - s * 0.42 - resp, s * 2.15, topo - s * 0.3 - (linha - s * 0.42 - resp) + s * 0.06, s * 0.18); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = css(cor, 0.5); c.beginPath(); c.moveTo(x - s * 0.1, linha - s * 0.2 - resp); c.lineTo(x + s * 1.7, linha - s * 0.2 - resp); c.stroke();
|
||||||
|
zzz(c, x - s * 0.9, linha - s * 0.9, s, k, cor);
|
||||||
|
},
|
||||||
|
cafe(c, x, y, s, k, cor) {
|
||||||
|
const f = (k % 5) / 5, gole = suave(faixaDe(f, 0.5, 0.64)) * (1 - suave(faixaDe(f, 0.84, 0.97)));
|
||||||
|
const b = boneco(c, x, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 0.2, olhoY: 0.3 - gole * 0.8, fechados: gole > 0.85, cabeca: -0.04,
|
||||||
|
bE: [0.15, 0.95], bD: [-0.15 + gole * 1.75, -0.95 - gole * 3.05],
|
||||||
|
}), cor, k);
|
||||||
|
xicara(c, b.mD.x, b.mD.y, s, cor);
|
||||||
|
if (gole < 0.3) vapor(c, b.mD.x, b.mD.y - s * 0.3, s, k, cor);
|
||||||
|
},
|
||||||
|
cozinhar(c, x, y, s, k, cor) {
|
||||||
|
const ciclo = (k % 2.6) / 2.6, voo = ciclo < 0.55 ? ciclo / 0.55 : -1, h = voo >= 0 ? 4 * voo * (1 - voo) : 0;
|
||||||
|
const fx = x + s * 1.0, fw = s * 1.7, fh = s * 1.2;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
caixa(c, fx, y - fh, fw, fh, s * 0.08); c.fill(); c.stroke();
|
||||||
|
caixa(c, fx + fw * 0.15, y - fh * 0.72, fw * 0.7, fh * 0.5, s * 0.06); c.stroke();
|
||||||
|
for (let i = 0; i < 3; i++) chama(c, fx + fw * (0.3 + 0.2 * i), y - fh, s * (0.22 + 0.07 * Math.sin(k * 17 + i * 2)), s);
|
||||||
|
const b = boneco(c, x - s * 0.45, y, s, {
|
||||||
|
olhoX: 1, olhoY: 0.3 - h, cabeca: -0.05, bE: [-0.4, 0.3],
|
||||||
|
bD: [1.2 - (voo >= 0 && voo < 0.15 ? 0.25 : 0), 0.35],
|
||||||
|
}, cor, k);
|
||||||
|
const px = b.mD.x + s * 0.62, py = b.mD.y + s * 0.02;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(2, s * 0.09);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y); c.lineTo(px - s * 0.42, py); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.ellipse(px, py, s * 0.46, s * 0.13, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.save(); c.translate(px, py - s * 0.1 - h * s * 1.6); c.rotate(voo >= 0 ? voo * Math.PI * 2 : 0);
|
||||||
|
c.fillStyle = css(AMARELO_); c.beginPath(); c.ellipse(0, 0, s * 0.32, s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
regar(c, x, y, s, k, cor) {
|
||||||
|
const cresce = Math.min(1, k / 18), px = x + s * 2.0;
|
||||||
|
// vaso
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(px - s * 0.42, y - s * 0.6); c.lineTo(px + s * 0.42, y - s * 0.6); c.lineTo(px + s * 0.3, y); c.lineTo(px - s * 0.3, y); c.closePath(); c.fill(); c.stroke();
|
||||||
|
// planta crescendo: caule, folhas e, no fim, a flor
|
||||||
|
const alt = s * (0.3 + 1.25 * cresce), base = y - s * 0.6, balanca = Math.sin(k * 1.5) * s * 0.06;
|
||||||
|
c.strokeStyle = css(VERDE_); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(px, base); c.quadraticCurveTo(px + balanca, base - alt / 2, px + balanca * 2, base - alt); c.stroke();
|
||||||
|
c.fillStyle = css(VERDE_);
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
if (cresce < i * 0.22) continue;
|
||||||
|
const yy = base - alt * i * 0.25, lado = i % 2 ? 1 : -1;
|
||||||
|
c.beginPath(); c.ellipse(px + balanca * i * 0.5 + lado * s * 0.18, yy, s * 0.2, s * 0.08, lado * -0.5, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
if (cresce > 0.75) {
|
||||||
|
const f = (cresce - 0.75) / 0.25, fx = px + balanca * 2, fy = base - alt;
|
||||||
|
c.fillStyle = css(ROSA_);
|
||||||
|
for (let i = 0; i < 5; i++) { const a = i * 1.2566 + k * 0.3; c.beginPath(); c.arc(fx + Math.cos(a) * s * 0.16 * f, fy + Math.sin(a) * s * 0.16 * f, s * 0.11 * f, 0, Math.PI * 2); c.fill(); }
|
||||||
|
c.fillStyle = css(AMARELO_); c.beginPath(); c.arc(fx, fy, s * 0.09 * f, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
const b = boneco(c, x - s * 0.7, y, s, { olhoX: 1, olhoY: 0.8, cabeca: -0.1, inclina: -0.06, bD: [1.35, 0.35], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
// regador inclinado e as gotas caindo na planta
|
||||||
|
const ang = 0.38, rx = b.mD.x + s * 0.05, ry = b.mD.y;
|
||||||
|
c.save(); c.translate(rx, ry); c.rotate(ang);
|
||||||
|
c.fillStyle = claro(cor, 0.95); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, 0, -s * 0.22, s * 0.6, s * 0.42, s * 0.08); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.moveTo(s * 0.55, -s * 0.05); c.lineTo(s * 1.0, -s * 0.32); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
const ex = rx + Math.cos(ang) * s * 1.0 + Math.sin(ang) * s * 0.32, ey = ry + Math.sin(ang) * s * 1.0 - Math.cos(ang) * s * 0.32;
|
||||||
|
c.fillStyle = claro(cor, 1);
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const f = (k * 1.6 + i / 6) % 1;
|
||||||
|
c.beginPath(); c.ellipse(ex + (px - ex) * f * 0.8, ey + (base - alt - ey + s * 0.2) * f * f, s * 0.035, s * 0.07, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
violao(c, x, y, s, k, cor) {
|
||||||
|
const toque = Math.sin(k * 11);
|
||||||
|
const b = boneco(c, x, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: -0.4, olhoY: 0.7, cabeca: 0.08 * Math.sin(k * 1.1),
|
||||||
|
bE: [-1.1, -0.9], bD: [-0.2 + toque * 0.12, -1.0],
|
||||||
|
}), cor, k);
|
||||||
|
// violão atravessado no colo: corpo perto da mão que toca, braço até a outra mão
|
||||||
|
const gx = b.quadril.x + s * 0.3, gy = b.quadril.y - s * 0.35, nx = b.mE.x, ny = b.mE.y;
|
||||||
|
const a = Math.atan2(ny - gy, nx - gx);
|
||||||
|
c.save(); c.translate(gx, gy); c.rotate(a);
|
||||||
|
const L = Math.hypot(nx - gx, ny - gy);
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, s * 0.2, -s * 0.08, L - s * 0.1, s * 0.16, s * 0.04); c.fill(); c.stroke();
|
||||||
|
caixa(c, L - s * 0.05, -s * 0.12, s * 0.3, s * 0.24, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.95);
|
||||||
|
c.beginPath(); c.arc(-s * 0.28, 0, s * 0.42, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.arc(s * 0.22, 0, s * 0.32, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.beginPath(); c.arc(s * 0.05, 0, s * 0.11, 0, Math.PI * 2); c.fill();
|
||||||
|
c.strokeStyle = css(cor, 0.7); c.lineWidth = Math.max(1, s * 0.015);
|
||||||
|
for (const d of [-0.04, 0, 0.04]) { c.beginPath(); c.moveTo(-s * 0.5, d * s); c.lineTo(L, d * s); c.stroke(); }
|
||||||
|
c.restore();
|
||||||
|
c.fillStyle = css(cor); c.beginPath(); c.arc(b.mD.x, b.mD.y, s * 0.13, 0, Math.PI * 2); c.fill();
|
||||||
|
notas(c, b.cabeca.x - s * 1.6, b.cabeca.y + s * 0.2, s, k, cor, -1);
|
||||||
|
},
|
||||||
|
dancar(c, x, y, s, k, cor) {
|
||||||
|
const u = (Math.sin(k * 5) + 1) / 2, mexe = Math.sin(k * 5) * 0.18;
|
||||||
|
// globo espelhado e luzinhas no chão
|
||||||
|
const gx = x, gy = y - s * 4.5;
|
||||||
|
c.strokeStyle = css(cor, 0.6); c.lineWidth = Math.max(1, s * 0.03);
|
||||||
|
c.beginPath(); c.moveTo(gx, gy - s * 0.9); c.lineTo(gx, gy - s * 0.35); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.9); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.arc(gx, gy, s * 0.35, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = css(cor, 0.5);
|
||||||
|
for (let i = -1; i <= 1; i++) { c.beginPath(); c.ellipse(gx, gy, s * 0.35 * Math.abs(Math.cos(k + i)), s * 0.35, 0, 0, Math.PI * 2); c.stroke(); }
|
||||||
|
const cores = [ROSA_, AMARELO_, VERDE_, cor];
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const lx = x + Math.sin(k * 1.3 + i * 1.7) * s * 2.4, alfa = c.globalAlpha;
|
||||||
|
c.globalAlpha = alfa * 0.35; c.fillStyle = css(cores[i % 4]);
|
||||||
|
c.beginPath(); c.ellipse(lx, y, s * 0.35, s * 0.07, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
boneco(c, x, y, s, {
|
||||||
|
dx: mexe, inclina: mexe * 0.5, cabeca: -mexe * 0.6, olhoY: -0.2, olhoX: mexe * 3,
|
||||||
|
bE: [-0.7 - 2.1 * u, 0.5], bD: [0.7 + 2.1 * (1 - u), -0.5],
|
||||||
|
pE: [-0.1 - u * 0.25, u * 0.4], pD: [0.1 + (1 - u) * 0.25, -(1 - u) * 0.4],
|
||||||
|
}, cor, k);
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const f = (k * 0.7 + i / 4) % 1, alfa = c.globalAlpha;
|
||||||
|
c.globalAlpha = alfa * Math.sin(f * Math.PI); c.fillStyle = css(cores[i]);
|
||||||
|
estrela(c, x + Math.cos(i * 1.9) * s * 1.6, y - s * (2 + i * 0.5) - f * s * 0.4, s * 0.12); c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
corda(c, x, y, s, k, cor) {
|
||||||
|
const th = k * Math.PI * 2 * 0.85, pulo = Math.pow(Math.max(0, Math.cos(th)), 0.6), sobe = pulo * s * 0.45;
|
||||||
|
c.save(); c.globalAlpha *= 0.3 * (1 - pulo * 0.5); c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(x, y, s * 0.6 * (1 - pulo * 0.3), s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.restore();
|
||||||
|
const b = boneco(c, x, y - sobe, s, {
|
||||||
|
olhoY: -0.2, bE: [-0.75 + 0.15 * Math.sin(th), 0.6], bD: [0.75 - 0.15 * Math.sin(th), -0.6],
|
||||||
|
pE: [-0.06, pulo * 0.3], pD: [0.06, -pulo * 0.3],
|
||||||
|
}, cor, k);
|
||||||
|
// a corda passa por baixo dos pés quando ele está no alto do pulo
|
||||||
|
const my = (b.mE.y + b.mD.y) / 2, embaixo = 2 * (y + s * 0.05) - my, emcima = 2 * (b.cabeca.y - s * 1.0) - my;
|
||||||
|
const cy = (embaixo + emcima) / 2 + Math.cos(th) * (embaixo - emcima) / 2;
|
||||||
|
c.strokeStyle = claro(cor, 1); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(b.mE.x, b.mE.y); c.quadraticCurveTo(x, cy, b.mD.x, b.mD.y); c.stroke();
|
||||||
|
},
|
||||||
|
meditar(c, x, y, s, k, cor) {
|
||||||
|
const flutua = (Math.sin(k * 1.2) + 1) / 2 * s * 0.35, cy = y - s * 1.7 - flutua;
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) { // aura
|
||||||
|
const f = (k * 0.35 + i / 3) % 1;
|
||||||
|
c.globalAlpha = alfa * 0.35 * (1 - f); c.strokeStyle = css(cor); c.lineWidth = Math.max(1, s * 0.04);
|
||||||
|
c.beginPath(); c.ellipse(x, cy, s * (1.2 + f * 1.3), s * (1.4 + f * 1.3), 0, 0, Math.PI * 2); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa * 0.3; c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(x, y, s * 0.8 * (1 - flutua / s), s * 0.09, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
boneco(c, x, y - s * 0.3 - flutua, s, {
|
||||||
|
sentado: true, fechados: true, pE: [-1.45, 2.75], pD: [1.45, -2.75], bE: [-0.75, 0.25], bD: [0.75, -0.25],
|
||||||
|
}, cor, k);
|
||||||
|
},
|
||||||
|
telescopio(c, x, y, s, k, cor) {
|
||||||
|
// céu estrelado com uma estrela cadente de vez em quando
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
c.globalAlpha = alfa * (0.35 + 0.65 * Math.abs(Math.sin(k * (1 + rnd(i, 3)) + i)));
|
||||||
|
c.fillStyle = i % 5 ? claro(cor, 1) : css(AMARELO_);
|
||||||
|
estrela(c, x + s * (0.2 + rnd(i, 1) * 3.4), y - s * (3.4 + rnd(i, 2) * 1.5), s * (0.05 + 0.05 * rnd(i, 4)));
|
||||||
|
}
|
||||||
|
const f = (k % 6) / 1.1;
|
||||||
|
if (f < 1) {
|
||||||
|
const sx = x + s * (3.6 - f * 2.2), sy = y - s * (4.9 - f * 0.8);
|
||||||
|
const g = c.createLinearGradient(sx, sy, sx + s * 0.9, sy - s * 0.33);
|
||||||
|
g.addColorStop(0, claro(cor, 1)); g.addColorStop(1, css(cor, 0));
|
||||||
|
c.globalAlpha = alfa * (1 - f); c.strokeStyle = g; c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(sx, sy); c.lineTo(sx + s * 0.9, sy - s * 0.33); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
const b = boneco(c, x - s * 0.7, y, s, { olhoX: 1, olhoY: -1, cabeca: 0.15, bD: [2.1, 0.5], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
const ox = b.cabeca.x + s * 0.78, oy = b.cabeca.y + s * 0.3, tx = ox + s * 1.6, ty = oy - s * 0.95;
|
||||||
|
const mx = (ox + tx) / 2, my = (oy + ty) / 2;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(mx, my); c.lineTo(mx - s * 0.5, y); c.moveTo(mx, my); c.lineTo(mx + s * 0.5, y); c.moveTo(mx, my); c.lineTo(mx, y); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor);
|
||||||
|
c.beginPath(); capsula(c, ox, oy, tx, ty, s * 0.15); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 1); c.beginPath(); c.arc(tx, ty, s * 0.17, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
},
|
||||||
|
pipa(c, x, y, s, k, cor) {
|
||||||
|
const kx = x + s * (2.3 + 0.45 * Math.sin(k * 0.8)), ky = y - s * (4.2 + 0.35 * Math.cos(k * 1.1)), ang = 0.25 * Math.sin(k * 1.6);
|
||||||
|
const b = boneco(c, x - s * 0.9, y, s, { olhoX: 1, olhoY: -1, cabeca: 0.12, bD: [2.55, 0.35], bE: [-0.4, 0.3] }, cor, k);
|
||||||
|
c.strokeStyle = css(cor, 0.8); c.lineWidth = Math.max(1, s * 0.025);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y);
|
||||||
|
c.quadraticCurveTo((b.mD.x + kx) / 2 + s * 0.4, (b.mD.y + ky) / 2 + s * 0.7, kx, ky + s * 0.45); c.stroke();
|
||||||
|
// rabiola com lacinhos
|
||||||
|
c.save(); c.translate(kx, ky); c.rotate(ang);
|
||||||
|
c.strokeStyle = css(cor, 0.8); c.beginPath(); c.moveTo(0, s * 0.5);
|
||||||
|
for (let i = 1; i <= 6; i++) c.lineTo(Math.sin(k * 3 + i) * s * 0.18, s * (0.5 + i * 0.22));
|
||||||
|
c.stroke();
|
||||||
|
for (let i = 2; i <= 6; i += 2) {
|
||||||
|
const lx = Math.sin(k * 3 + i) * s * 0.18, ly = s * (0.5 + i * 0.22);
|
||||||
|
c.fillStyle = css(i % 4 ? ROSA_ : AMARELO_);
|
||||||
|
c.beginPath(); c.moveTo(lx, ly); c.lineTo(lx - s * 0.1, ly - s * 0.06); c.lineTo(lx - s * 0.1, ly + s * 0.06); c.closePath(); c.fill();
|
||||||
|
c.beginPath(); c.moveTo(lx, ly); c.lineTo(lx + s * 0.1, ly - s * 0.06); c.lineTo(lx + s * 0.1, ly + s * 0.06); c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
const pts = [[0, -0.5], [0.38, 0], [0, 0.5], [-0.38, 0]];
|
||||||
|
const cores = [ROSA_, AMARELO_, cor, VERDE_];
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const [ax, ay] = pts[i], [bx, by] = pts[(i + 1) % 4];
|
||||||
|
c.fillStyle = css(cores[i]); c.beginPath(); c.moveTo(0, 0); c.lineTo(ax * s, ay * s); c.lineTo(bx * s, by * s); c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.04);
|
||||||
|
c.beginPath(); pts.forEach(([px, py], i) => (i ? c.lineTo(px * s, py * s) : c.moveTo(px * s, py * s))); c.closePath(); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
bolhas(c, x, y, s, k, cor) {
|
||||||
|
const b = boneco(c, x - s * 0.6, y, s, { olhoX: 0.8, olhoY: -0.6, bD: [1.6, -4.0], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
const wx = b.mD.x + s * 0.35, wy = b.mD.y - s * 0.25;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y); c.lineTo(wx - s * 0.1, wy + s * 0.08); c.stroke();
|
||||||
|
c.beginPath(); c.arc(wx, wy, s * 0.12, 0, Math.PI * 2); c.stroke();
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
const f = (k * 0.22 + i / 9) % 1, r = s * (0.12 + 0.18 * rnd(i, 4));
|
||||||
|
const bx = wx + f * s * (1.6 + 1.4 * rnd(i, 5)) + Math.sin(k * 2 + i) * s * 0.15, by = wy - f * s * (1.6 + rnd(i, 6));
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.08) * (f > 0.9 ? (1 - f) / 0.1 : 1);
|
||||||
|
c.fillStyle = css(cor, 0.1); c.strokeStyle = css(cor, 0.9); c.lineWidth = Math.max(1, s * 0.03);
|
||||||
|
c.beginPath(); c.arc(bx, by, r, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = claro(cor, 1); c.beginPath(); c.arc(bx, by, r * 0.65, -2.4, -1.6); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
},
|
||||||
|
skate(c, x, y, s, k, cor) {
|
||||||
|
const pos = Math.sin(k * 0.9) * 1.3, vel = Math.cos(k * 0.9), ciclo = k % 4;
|
||||||
|
const ollie = ciclo < 0.7 ? Math.sin(ciclo / 0.7 * Math.PI) : 0, sobe = ollie * s * 0.6;
|
||||||
|
const bx = x + pos * s, pe = y - s * 0.3 - sobe;
|
||||||
|
c.save(); c.globalAlpha *= 0.3; c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(bx, y, s * 0.9, s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.restore();
|
||||||
|
c.strokeStyle = css(cor, 0.4); c.lineWidth = Math.max(1, s * 0.03); // risquinhos de velocidade
|
||||||
|
for (let i = 0; i < 3; i++) { const lx = bx - Math.sign(vel) * s * (1.1 + i * 0.25); c.beginPath(); c.moveTo(lx, pe - s * (0.6 + i * 0.4)); c.lineTo(lx - Math.sign(vel) * s * 0.5 * Math.abs(vel), pe - s * (0.6 + i * 0.4)); c.stroke(); }
|
||||||
|
boneco(c, bx, pe, s, {
|
||||||
|
olhoX: vel * 0.8, inclina: -vel * 0.1, bE: [-1.35, 0.25 + 0.2 * Math.sin(k * 3)], bD: [1.35, -0.25 - 0.2 * Math.sin(k * 3)],
|
||||||
|
pE: [-0.25, 0.35], pD: [0.25, -0.35],
|
||||||
|
}, cor, k);
|
||||||
|
c.save(); c.translate(bx, pe); c.rotate(-ollie * 0.25);
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
caixa(c, -s * 0.85, 0, s * 1.7, s * 0.12, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor);
|
||||||
|
for (const lado of [-1, 1]) { c.beginPath(); c.arc(lado * s * 0.55, s * 0.2, s * 0.1, 0, Math.PI * 2); c.fill(); c.stroke(); }
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
computador(c, x, y, s, k, cor) {
|
||||||
|
const mx0 = x + s * 0.3, mx1 = x + s * 2.7, mt = y - s * 1.45;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(mx0 + s * 0.15, mt); c.lineTo(mx0 + s * 0.15, y); c.moveTo(mx1 - s * 0.15, mt); c.lineTo(mx1 - s * 0.15, y); c.stroke();
|
||||||
|
caixa(c, mx0, mt, mx1 - mx0, s * 0.12, s * 0.04); c.fill(); c.stroke();
|
||||||
|
const nx = mx0 + s * 0.6, nw = s * 1.4, nh = s * 0.95, ny = mt - s * 0.08 - nh;
|
||||||
|
caixa(c, nx - s * 0.12, mt - s * 0.08, nw + s * 0.24, s * 0.08, s * 0.03); c.fill(); c.stroke();
|
||||||
|
caixa(c, nx, ny, nw, nh, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.save(); caixa(c, nx + s * 0.06, ny + s * 0.06, nw - s * 0.12, nh - s * 0.12, s * 0.03); c.clip();
|
||||||
|
const linhas = Math.floor(k * 2.5), visiveis = 6, ini = Math.max(0, linhas - visiveis);
|
||||||
|
const tintas = [cor, VERDE_, AMARELO_, ROSA_];
|
||||||
|
for (let i = ini; i < linhas; i++) {
|
||||||
|
const yy = ny + s * 0.14 + (i - ini) * s * 0.13, recuo = (rnd(i, 7) < 0.4 ? 1 : 0) * s * 0.15;
|
||||||
|
c.fillStyle = css(tintas[i % 4], 0.85); c.fillRect(nx + s * 0.12 + recuo, yy, (nw - s * 0.4) * (0.3 + 0.6 * rnd(i, 8)), s * 0.05);
|
||||||
|
}
|
||||||
|
if (Math.floor(k * 2) % 2) { c.fillStyle = claro(cor, 1); c.fillRect(nx + s * 0.12, ny + s * 0.14 + Math.min(linhas, visiveis) * s * 0.13, s * 0.04, s * 0.08); }
|
||||||
|
c.restore();
|
||||||
|
const tecla = Math.sin(k * 20) * 0.05;
|
||||||
|
boneco(c, x - s * 0.55, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 1, olhoY: 0.2, cabeca: -0.05, bE: [0.9 + tecla, 0.9], bD: [1.35 - tecla, 0.45],
|
||||||
|
}), cor, k);
|
||||||
|
},
|
||||||
|
pescar(c, x, y, s, k, cor) {
|
||||||
|
const ciclo = k % 8, fisgou = ciclo > 6.2 && ciclo < 7.6, puxa = fisgou ? suave((ciclo - 6.2) / 0.5) : 0;
|
||||||
|
// água: ondinhas à direita do píer
|
||||||
|
c.strokeStyle = css(cor, 0.55); c.lineWidth = Math.max(1, s * 0.035);
|
||||||
|
for (let fila = 0; fila < 3; fila++) {
|
||||||
|
c.beginPath();
|
||||||
|
for (let i = 0; i <= 24; i++) {
|
||||||
|
const wx = x + s * (0.5 + i * 0.14), wy = y - s * 0.05 + fila * s * 0.16 + Math.sin(k * 2 + i * 0.8 + fila) * s * 0.04;
|
||||||
|
if (i) c.lineTo(wx, wy); else c.moveTo(wx, wy);
|
||||||
|
}
|
||||||
|
c.stroke();
|
||||||
|
}
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, x - s * 2.0, y - s * 0.22, s * 2.5, s * 0.22, s * 0.05); c.fill(); c.stroke();
|
||||||
|
const b = boneco(c, x - s * 0.8, y - s * 0.22, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 1, olhoY: 0.6 - puxa * 1.4, bD: [2.0 - puxa * 0.4, -0.9], bE: [0.5, 1.2],
|
||||||
|
}), cor, k);
|
||||||
|
const vx = b.mD.x + s * (1.4 - puxa * 0.3), vy = b.mD.y - s * (1.6 + puxa * 0.3);
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(b.mE.x, b.mE.y); c.lineTo(b.mD.x, b.mD.y); c.lineTo(vx, vy); c.stroke();
|
||||||
|
const belisca = ciclo > 5.5 && ciclo < 6.2 ? Math.sin(k * 30) * s * 0.06 : 0;
|
||||||
|
let lx = x + s * 2.6, ly = y - s * 0.05 + Math.sin(k * 2) * s * 0.04 + belisca;
|
||||||
|
if (fisgou) { // o peixe pula preso na linha
|
||||||
|
lx = x + s * (2.6 - puxa * 0.6); ly = y - s * (0.2 + Math.sin(puxa * Math.PI) * 1.3 + puxa * 0.3);
|
||||||
|
c.save(); c.translate(lx, ly + s * 0.25); c.rotate(Math.sin(k * 14) * 0.4);
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.04);
|
||||||
|
c.beginPath(); c.ellipse(0, 0, s * 0.12, s * 0.28, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.moveTo(0, s * 0.25); c.lineTo(-s * 0.14, s * 0.45); c.lineTo(s * 0.14, s * 0.45); c.closePath(); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.beginPath(); c.arc(s * 0.04, -s * 0.12, s * 0.03, 0, Math.PI * 2); c.fill();
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
c.strokeStyle = css(cor, 0.85); c.lineWidth = Math.max(1, s * 0.02);
|
||||||
|
c.beginPath(); c.moveTo(vx, vy); c.quadraticCurveTo((vx + lx) / 2, Math.max(vy, ly) - s * (fisgou ? 0.1 : 0.4), lx, ly); c.stroke();
|
||||||
|
if (!fisgou) {
|
||||||
|
c.fillStyle = css(ROSA_); c.beginPath(); c.arc(lx, ly, s * 0.09, Math.PI, 0); c.fill();
|
||||||
|
c.fillStyle = claro(cor, 1); c.beginPath(); c.arc(lx, ly, s * 0.09, 0, Math.PI); c.fill();
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Desenha a cena da atividade (camada atrás dos olhos). o = { t, cor }. */
|
/** Desenha a cena da atividade (camada atrás dos olhos). o = { t, cor }. */
|
||||||
|
|||||||
+25
-8
@@ -212,7 +212,7 @@ function caminhoBoca(tipo) {
|
|||||||
// repouso: leve, com as microexpressões (canto que sobe, boca de lado, entreaberta)
|
// repouso: leve, com as microexpressões (canto que sobe, boca de lado, entreaberta)
|
||||||
const f = FEICAO.atual, w = l * 0.30, mx = cx + f.dx * l * 0.10;
|
const f = FEICAO.atual, w = l * 0.30, mx = cx + f.dx * l * 0.10;
|
||||||
const yE = cy - Math.max(0, f.lado) * l * 0.12, yD = cy - Math.max(0, -f.lado) * l * 0.12;
|
const yE = cy - Math.max(0, f.lado) * l * 0.12, yD = cy - Math.max(0, -f.lado) * l * 0.12;
|
||||||
const curva = l * (0.18 + f.curva * 0.35);
|
const curva = l * (0.18 + f.curva * 0.35 - ZANGA * 0.3);
|
||||||
let d = "M " + (mx - w) + "," + yE + " Q " + mx + "," + (cy + curva - f.abre * l * 0.12) + " " + (mx + w) + "," + yD;
|
let d = "M " + (mx - w) + "," + yE + " Q " + mx + "," + (cy + curva - f.abre * l * 0.12) + " " + (mx + w) + "," + yD;
|
||||||
if (f.abre > 0.04) d += " Q " + mx + "," + (cy + curva + f.abre * l * 0.30) + " " + (mx - w) + "," + yE;
|
if (f.abre > 0.04) d += " Q " + mx + "," + (cy + curva + f.abre * l * 0.30) + " " + (mx - w) + "," + yE;
|
||||||
return d;
|
return d;
|
||||||
@@ -224,7 +224,8 @@ function caminhoSobrancelha(i, esc) {
|
|||||||
const ab = i === 0 ? r.ab : (r.ab2 === null ? r.ab : r.ab2);
|
const ab = i === 0 ? r.ab : (r.ab2 === null ? r.ab : r.ab2);
|
||||||
const piscando = 1 - Math.min(1, Math.max(0, ab));
|
const piscando = 1 - Math.min(1, Math.max(0, ab));
|
||||||
const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC - (f.sob[i] - piscando * 3) * ESC;
|
const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC - (f.sob[i] - piscando * 3) * ESC;
|
||||||
const inc = ((r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : 0) + f.inc[i]) * lado * ESC;
|
const base = r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : ZANGA * 14; // zanga franze a testa
|
||||||
|
const inc = (base + f.inc[i]) * lado * ESC;
|
||||||
const ele = r.esc > 1.1 ? -15 * ESC : 0;
|
const ele = r.esc > 1.1 ? -15 * ESC : 0;
|
||||||
return 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele);
|
return 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele);
|
||||||
}
|
}
|
||||||
@@ -401,12 +402,17 @@ const LIVRES = ["feliz", "olhar_esq", "olhar_dir", "olhar_baixo",
|
|||||||
const LIVRES_HUMOR = {
|
const LIVRES_HUMOR = {
|
||||||
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
|
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
|
||||||
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
|
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
|
||||||
|
bravo: ["bravo", "olhar_baixo", "olhar_esq", "olhar_dir", "confuso"],
|
||||||
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
|
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
|
||||||
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
|
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
|
||||||
triste: ["triste", "olhar_baixo", "olhar_baixo"],
|
triste: ["triste", "olhar_baixo", "olhar_baixo"],
|
||||||
cansado: ["olhar_baixo", "piscar2", "bocejar"],
|
cansado: ["olhar_baixo", "piscar2", "bocejar"],
|
||||||
};
|
};
|
||||||
let HUMOR = "normal";
|
let HUMOR = "normal";
|
||||||
|
/* Modo vida: zanga (0 a 1) deixa o rosto emburrado aos poucos quando a alegria cai; sono profundo =
|
||||||
|
dormiu de cansaço, e aí som, movimento e câmera não acordam (como na madrugada). */
|
||||||
|
let ZANGA = 0;
|
||||||
|
let SONO_PROFUNDO = false;
|
||||||
|
|
||||||
/* Microexpressões acordado: de vez em quando a boca e as sobrancelhas mexem sozinhas (um canto
|
/* Microexpressões acordado: de vez em quando a boca e as sobrancelhas mexem sozinhas (um canto
|
||||||
da boca sobe, uma sobrancelha arqueia, um suspiro) e voltam devagar ao neutro. O humor do modo
|
da boca sobe, uma sobrancelha arqueia, um suspiro) e voltam devagar ao neutro. O humor do modo
|
||||||
@@ -431,6 +437,7 @@ const MICRO_HUMOR = {
|
|||||||
carente: ["preocupado", "preocupado", "suspiro", "meioSorriso"],
|
carente: ["preocupado", "preocupado", "suspiro", "meioSorriso"],
|
||||||
triste: ["preocupado", "suspiro", "pensativo"],
|
triste: ["preocupado", "suspiro", "pensativo"],
|
||||||
cansado: ["suspiro", "suspiro", "ladinho"],
|
cansado: ["suspiro", "suspiro", "ladinho"],
|
||||||
|
bravo: ["pensativo", "pensativo", "ladinho", "suspiro"],
|
||||||
normal: ["meioSorriso", "arquear", "sorrisinho", "ladinho", "interesse", "pensativo", "suspiro"],
|
normal: ["meioSorriso", "arquear", "sorrisinho", "ladinho", "interesse", "pensativo", "suspiro"],
|
||||||
};
|
};
|
||||||
function atualizarFeicao(s) {
|
function atualizarFeicao(s) {
|
||||||
@@ -495,11 +502,11 @@ function passo(dt) { // avanca a fila; true se ha gesto em
|
|||||||
|
|
||||||
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
||||||
let ATIV = null;
|
let ATIV = null;
|
||||||
function atividade(nome) {
|
function atividade(nome, segundos) {
|
||||||
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
||||||
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
||||||
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
||||||
if (window.MotorRosto.atividade(nome) && nome === "cantar") tocarSom("musica");
|
if (window.MotorRosto.atividade(nome, segundos) && nome === "cantar") tocarSom("musica");
|
||||||
}
|
}
|
||||||
|
|
||||||
function repouso(t) { // pose calma: olha devagar, "respira"
|
function repouso(t) { // pose calma: olha devagar, "respira"
|
||||||
@@ -526,6 +533,8 @@ function repouso(t) { // pose calma: olha devagar, "respira
|
|||||||
if (ATIV.forma) r.forma = ATIV.forma;
|
if (ATIV.forma) r.forma = ATIV.forma;
|
||||||
if (ATIV.boca) r.boca = ATIV.boca;
|
if (ATIV.boca) r.boca = ATIV.boca;
|
||||||
if (ATIV.ab) r.ab = ATIV.ab;
|
if (ATIV.ab) r.ab = ATIV.ab;
|
||||||
|
} else if (ZANGA >= 0.7) { // alegria caindo: emburrado (a cor escolhida fica)
|
||||||
|
r.forma = "bravo";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +643,13 @@ function trata(ordem) {
|
|||||||
} else if (ordem.indexOf("ritmo ") === 0) {
|
} else if (ordem.indexOf("ritmo ") === 0) {
|
||||||
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
||||||
} else if (ordem.indexOf("atividade ") === 0) {
|
} else if (ordem.indexOf("atividade ") === 0) {
|
||||||
atividade(ordem.slice(10).trim());
|
const [nome, seg] = ordem.slice(10).trim().split(/\s+/); // "atividade bola 180"
|
||||||
|
atividade(nome, parseFloat(seg) || 0);
|
||||||
|
} else if (ordem.indexOf("zanga ") === 0) {
|
||||||
|
const z = parseFloat(ordem.slice(6));
|
||||||
|
if (!isNaN(z)) ZANGA = Math.max(0, Math.min(1, z));
|
||||||
|
} else if (ordem === "sono profundo" || ordem === "sono leve") {
|
||||||
|
SONO_PROFUNDO = ordem === "sono profundo";
|
||||||
} else if (ordem === "dormir" || ordem === "sono") {
|
} else if (ordem === "dormir" || ordem === "sono") {
|
||||||
r.dormindo = true;
|
r.dormindo = true;
|
||||||
atividade(null);
|
atividade(null);
|
||||||
@@ -790,6 +805,7 @@ const MIC = {
|
|||||||
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
|
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
|
||||||
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
|
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
|
||||||
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
|
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
|
||||||
|
function sonoRespeitado() { return madrugada() || SONO_PROFUNDO; }
|
||||||
function madrugada() {
|
function madrugada() {
|
||||||
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
|
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
|
||||||
const h = new Date().getHours();
|
const h = new Date().getHours();
|
||||||
@@ -855,7 +871,7 @@ function som(nivel) {
|
|||||||
|
|
||||||
if (r.dormindo) {
|
if (r.dormindo) {
|
||||||
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
|
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
|
||||||
if (contar(CONTA.somAcordar, madrugada() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
|
if (contar(CONTA.somAcordar, sonoRespeitado() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
|
||||||
r.dormindo = false;
|
r.dormindo = false;
|
||||||
animar("surpreso");
|
animar("surpreso");
|
||||||
MIC.proxSurpresa = agora + MIC.cooldown;
|
MIC.proxSurpresa = agora + MIC.cooldown;
|
||||||
@@ -878,6 +894,7 @@ function checarSilencio(agora) {
|
|||||||
if (!MIC.ativo) return; // telinha do Pi: sem ouvido
|
if (!MIC.ativo) return; // telinha do Pi: sem ouvido
|
||||||
if (agora - MIC.visto > 2) return; // o ouvido parou de mandar dado
|
if (agora - MIC.visto > 2) return; // o ouvido parou de mandar dado
|
||||||
if (r.dormindo) return;
|
if (r.dormindo) return;
|
||||||
|
if (HUMOR !== "normal") return; // modo vida ligado: quem manda dormir é a energia
|
||||||
// movimento tambem conta como "tem alguem aqui": nao adormece com gente passando
|
// movimento tambem conta como "tem alguem aqui": nao adormece com gente passando
|
||||||
const atividade = Math.max(MIC.ultimoSom, MOV.ativo ? MOV.ultimoMov : 0);
|
const atividade = Math.max(MIC.ultimoSom, MOV.ativo ? MOV.ultimoMov : 0);
|
||||||
if (agora - atividade >= MIC.segSilencio) {
|
if (agora - atividade >= MIC.segSilencio) {
|
||||||
@@ -949,7 +966,7 @@ function rastrear(t) {
|
|||||||
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
|
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
|
||||||
if(t.atividade===true) {
|
if(t.atividade===true) {
|
||||||
MOV.ultimoMov=agora;
|
MOV.ultimoMov=agora;
|
||||||
if(r.dormindo && !madrugada() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
|
if(r.dormindo && !sonoRespeitado() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function movimento(vel, cx, cy) {
|
function movimento(vel, cx, cy) {
|
||||||
@@ -981,7 +998,7 @@ function movimento(vel, cx, cy) {
|
|||||||
const forte = v >= limiar(MOV.limiarForte);
|
const forte = v >= limiar(MOV.limiarForte);
|
||||||
if (r.dormindo) {
|
if (r.dormindo) {
|
||||||
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
|
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
|
||||||
if (!madrugada() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
|
if (!sonoRespeitado() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
|
||||||
r.dormindo = false;
|
r.dormindo = false;
|
||||||
animar("surpreso");
|
animar("surpreso");
|
||||||
MOV.proxSurpresa = agora + MOV.cooldown;
|
MOV.proxSurpresa = agora + MOV.cooldown;
|
||||||
|
|||||||
+4
-3
@@ -57,7 +57,8 @@ GESTOS = ["piscar", "piscar2", "olhar_esq", "olhar_dir", "olhar_baixo", "feliz",
|
|||||||
"esconder", "espiar", "acenar", "pensar", "musica",
|
"esconder", "espiar", "acenar", "pensar", "musica",
|
||||||
"rir", "bocejar", "confuso", "tonto", "timido", "comemorar"]
|
"rir", "bocejar", "confuso", "tonto", "timido", "comemorar"]
|
||||||
# Atividades (momentos dele): o bonequinho faz algo embaixo dos olhos (rosto-motor.js).
|
# Atividades (momentos dele): o bonequinho faz algo embaixo dos olhos (rosto-motor.js).
|
||||||
ATIVIDADES = ["ler", "cantar", "bola", "videogame", "pintar", "malhar"]
|
ATIVIDADES = ["ler", "cantar", "bola", "videogame", "pintar", "malhar", "dormir", "cafe", "cozinhar", "regar",
|
||||||
|
"violao", "dancar", "corda", "meditar", "telescopio", "pipa", "bolhas", "skate", "computador", "pescar"]
|
||||||
# Ordens internas que o rosto entende mas não viram botão.
|
# Ordens internas que o rosto entende mas não viram botão.
|
||||||
ORDENS_INTERNAS = {"calma"}
|
ORDENS_INTERNAS = {"calma"}
|
||||||
|
|
||||||
@@ -530,7 +531,7 @@ def main():
|
|||||||
if tipo == "pir" and not madrugada(servidor.controle.config):
|
if tipo == "pir" and not madrugada(servidor.controle.config):
|
||||||
with servidor.controle.lock:
|
with servidor.controle.lock:
|
||||||
dormindo = bool(servidor.controle.estado.get("dormindo"))
|
dormindo = bool(servidor.controle.estado.get("dormindo"))
|
||||||
if dormindo and vida.pir_acorda():
|
if dormindo and not vida.sono_profundo and vida.pir_acorda():
|
||||||
enfileirar("acordar")
|
enfileirar("acordar")
|
||||||
|
|
||||||
def camera(ligada):
|
def camera(ligada):
|
||||||
@@ -539,7 +540,7 @@ def main():
|
|||||||
|
|
||||||
def alguem_chegou():
|
def alguem_chegou():
|
||||||
camera(True) # modo presença: o PIR liga a câmera do celular
|
camera(True) # modo presença: o PIR liga a câmera do celular
|
||||||
if not madrugada(servidor.controle.config):
|
if not madrugada(servidor.controle.config) and not vida.sono_profundo:
|
||||||
enfileirar("acordar")
|
enfileirar("acordar")
|
||||||
|
|
||||||
def ninguem_por_perto():
|
def ninguem_por_perto():
|
||||||
|
|||||||
+419
-7
@@ -524,12 +524,26 @@
|
|||||||
// vale para qualquer rosto (SVG ou motor). O carinha.html pergunta para onde olhar
|
// vale para qualquer rosto (SVG ou motor). O carinha.html pergunta para onde olhar
|
||||||
// (olharAtividade) e chama palco() a cada quadro.
|
// (olharAtividade) e chama palco() a cada quadro.
|
||||||
const ATIVIDADES = {
|
const ATIVIDADES = {
|
||||||
ler: { nome: 'Lendo', emoji: '📖', dur: [25, 45] },
|
ler: { nome: 'Lendo', emoji: '📖', dur: [25, 45] },
|
||||||
cantar: { nome: 'Cantando', emoji: '🎤', dur: [18, 30] },
|
cantar: { nome: 'Cantando', emoji: '🎤', dur: [18, 30] },
|
||||||
bola: { nome: 'Jogando bola', emoji: '⚽', dur: [20, 35] },
|
bola: { nome: 'Jogando bola', emoji: '⚽', dur: [20, 35] },
|
||||||
videogame: { nome: 'Videogame', emoji: '🎮', dur: [25, 45] },
|
videogame: { nome: 'Videogame', emoji: '🎮', dur: [25, 45] },
|
||||||
pintar: { nome: 'Pintando', emoji: '🎨', dur: [25, 40] },
|
pintar: { nome: 'Pintando', emoji: '🎨', dur: [25, 40] },
|
||||||
malhar: { nome: 'Malhando', emoji: '🏋️', dur: [15, 25] },
|
malhar: { nome: 'Malhando', emoji: '🏋️', dur: [15, 25] },
|
||||||
|
dormir: { nome: 'Cochilando', emoji: '🛏️', dur: [40, 80] },
|
||||||
|
cafe: { nome: 'Tomando café', emoji: '☕', dur: [20, 35] },
|
||||||
|
cozinhar: { nome: 'Cozinhando', emoji: '🍳', dur: [20, 35] },
|
||||||
|
regar: { nome: 'Regando planta', emoji: '🪴', dur: [20, 30] },
|
||||||
|
violao: { nome: 'Tocando violão', emoji: '🎸', dur: [20, 35] },
|
||||||
|
dancar: { nome: 'Dançando', emoji: '💃', dur: [15, 30] },
|
||||||
|
corda: { nome: 'Pulando corda', emoji: '🪢', dur: [15, 25] },
|
||||||
|
meditar: { nome: 'Meditando', emoji: '🧘', dur: [25, 45] },
|
||||||
|
telescopio: { nome: 'Olhando estrelas', emoji: '🔭', dur: [25, 40] },
|
||||||
|
pipa: { nome: 'Soltando pipa', emoji: '🪁', dur: [20, 35] },
|
||||||
|
bolhas: { nome: 'Bolhas de sabão', emoji: '🫧', dur: [18, 30] },
|
||||||
|
skate: { nome: 'Andando de skate', emoji: '🛹', dur: [18, 30] },
|
||||||
|
computador: { nome: 'No computador', emoji: '💻', dur: [25, 45] },
|
||||||
|
pescar: { nome: 'Pescando', emoji: '🎣', dur: [30, 50] },
|
||||||
};
|
};
|
||||||
const ativ = { nome: null, t0: 0, dur: 0, fim: 0 };
|
const ativ = { nome: null, t0: 0, dur: 0, fim: 0 };
|
||||||
let palcoTela = null, pctx = null;
|
let palcoTela = null, pctx = null;
|
||||||
@@ -582,6 +596,38 @@
|
|||||||
const p = pincel(k); info.x = 16 + lado * 5 + p.x * 4; info.y = -1 + p.y * 5;
|
const p = pincel(k); info.x = 16 + lado * 5 + p.x * 4; info.y = -1 + p.y * 5;
|
||||||
} else if (ativ.nome === 'malhar') {
|
} else if (ativ.nome === 'malhar') {
|
||||||
const f = forca(k); info.x = lado * 14; info.y = 6 - f * 12; info.ab = f > 0.85 ? 0.55 : 1;
|
const f = forca(k); info.x = lado * 14; info.y = 6 - f * 12; info.ab = f > 0.85 ? 0.55 : 1;
|
||||||
|
} else if (ativ.nome === 'dormir') {
|
||||||
|
info.forma = 'dormir'; info.boca = 'sono'; info.x = lado * 6; info.y = 4;
|
||||||
|
} else if (ativ.nome === 'cafe') {
|
||||||
|
const f = (k % 5) / 5, gole = f > 0.55 && f < 0.92;
|
||||||
|
info.x = lado * 12; info.y = gole ? -3 : 3; if (gole) info.forma = 'feliz';
|
||||||
|
} else if (ativ.nome === 'cozinhar') {
|
||||||
|
const cc = (k % 2.6) / 2.6, h = cc < 0.55 ? 4 * (cc / 0.55) * (1 - cc / 0.55) : 0;
|
||||||
|
info.x = lado * 16; info.y = 4 - h * 14;
|
||||||
|
} else if (ativ.nome === 'regar') {
|
||||||
|
info.x = lado * 18; info.y = 8;
|
||||||
|
} else if (ativ.nome === 'violao') {
|
||||||
|
info.x = lado * 8 + 3 * Math.sin(k * 1.1); info.y = 6 + 1.5 * Math.sin(k * 2.2); info.boca = 'sorriso';
|
||||||
|
if (Math.sin(k * 0.7) > 0.3) info.forma = 'feliz';
|
||||||
|
} else if (ativ.nome === 'dancar') {
|
||||||
|
info.x = lado * 10 + 6 * Math.sin(k * 5); info.y = -2 + 4 * Math.abs(Math.sin(k * 5)); info.forma = 'feliz'; info.boca = 'sorriso';
|
||||||
|
} else if (ativ.nome === 'corda') {
|
||||||
|
const th = k * Math.PI * 2 * 0.85; info.x = lado * 12; info.y = 4 - Math.pow(Math.max(0, Math.cos(th)), 0.6) * 10;
|
||||||
|
} else if (ativ.nome === 'meditar') {
|
||||||
|
info.forma = 'dormir'; info.boca = 'sorriso'; info.x = lado * 6; info.y = -2 + 2 * Math.sin(k * 1.2);
|
||||||
|
} else if (ativ.nome === 'telescopio') {
|
||||||
|
info.x = lado * 16; info.y = -11;
|
||||||
|
} else if (ativ.nome === 'pipa') {
|
||||||
|
info.x = lado * 14 + 4 * Math.sin(k * 0.8); info.y = -11 + 2 * Math.cos(k * 1.1);
|
||||||
|
} else if (ativ.nome === 'bolhas') {
|
||||||
|
info.x = lado * 14 + 4 * Math.sin(k * 0.9); info.y = -8 + 3 * Math.sin(k * 0.6);
|
||||||
|
} else if (ativ.nome === 'skate') {
|
||||||
|
info.x = lado * 12 + 7 * Math.sin(k * 0.9); info.y = 6;
|
||||||
|
} else if (ativ.nome === 'computador') {
|
||||||
|
info.x = lado * 18 + Math.sin(k * 20) * 0.6; info.y = 2 + ((k * 2.5) % 1) * 1.5;
|
||||||
|
} else if (ativ.nome === 'pescar') {
|
||||||
|
const cc = k % 8; info.x = lado * 18; info.y = 7;
|
||||||
|
if (cc > 6.2 && cc < 7.6) { info.forma = 'feliz'; info.boca = 'sorriso'; info.y = 0; }
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@@ -618,8 +664,9 @@
|
|||||||
caixa(c, -s * 0.66, -s * 0.98, s * 1.32, s * 0.98, s * 0.28); c.fill(); c.stroke();
|
caixa(c, -s * 0.66, -s * 0.98, s * 1.32, s * 0.98, s * 0.28); c.fill(); c.stroke();
|
||||||
const ox = (p.olhoX || 0) * s * 0.12, oy = (p.olhoY || 0) * s * 0.1;
|
const ox = (p.olhoX || 0) * s * 0.12, oy = (p.olhoY || 0) * s * 0.1;
|
||||||
const pisca = (k % 3.7) < 0.12 ? 0.15 : 1;
|
const pisca = (k % 3.7) < 0.12 ? 0.15 : 1;
|
||||||
c.save(); c.fillStyle = css(cor);
|
c.save(); c.fillStyle = css(cor); c.strokeStyle = css(cor);
|
||||||
for (const lado of [-1, 1]) {
|
for (const lado of [-1, 1]) {
|
||||||
|
if (p.fechados) { c.lineWidth = Math.max(1.5, s * 0.07); c.beginPath(); c.arc(lado * s * 0.27, -s * 0.52, s * 0.12, 0.2, Math.PI - 0.2); c.stroke(); continue; }
|
||||||
c.beginPath(); c.ellipse(lado * s * 0.27 + ox, -s * 0.5 + oy, s * 0.11, s * 0.16 * pisca, 0, 0, Math.PI * 2); c.fill();
|
c.beginPath(); c.ellipse(lado * s * 0.27 + ox, -s * 0.5 + oy, s * 0.11, s * 0.16 * pisca, 0, 0, Math.PI * 2); c.fill();
|
||||||
}
|
}
|
||||||
c.restore(); c.restore();
|
c.restore(); c.restore();
|
||||||
@@ -644,6 +691,56 @@
|
|||||||
c.beginPath(); c.moveTo(x + s * 0.2, y - s * 0.7); c.quadraticCurveTo(x + s * 0.5, y - s * 0.55, x + s * 0.42, y - s * 0.3); c.stroke();
|
c.beginPath(); c.moveTo(x + s * 0.2, y - s * 0.7); c.quadraticCurveTo(x + s * 0.5, y - s * 0.55, x + s * 0.42, y - s * 0.3); c.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROSA_ = [255, 112, 176], AMARELO_ = [255, 218, 96], VERDE_ = [120, 255, 160], LARANJA_ = [255, 150, 70];
|
||||||
|
const SENTADO = { sentado: true, pE: [-1.35, 1.35], pD: [1.35, -1.35] };
|
||||||
|
const rnd = (i, sem) => { const a = Math.sin(i * 12.9898 + sem * 78.233) * 43758.5453; return a - Math.floor(a); };
|
||||||
|
function estrela(c, x, y, r) {
|
||||||
|
c.beginPath();
|
||||||
|
for (let i = 0; i < 10; i++) { const a = -Math.PI / 2 + i * Math.PI / 5, rr = i % 2 ? r * 0.45 : r; c.lineTo(x + Math.cos(a) * rr, y + Math.sin(a) * rr); }
|
||||||
|
c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
function vapor(c, x, y, s, k, cor) { // três fios subindo e ondulando
|
||||||
|
c.save(); c.strokeStyle = css(cor, 0.7); c.lineWidth = Math.max(1, s * 0.04);
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.5 + i / 3) % 1, bx = x + (i - 1) * s * 0.1, by = y - f * s * 0.7;
|
||||||
|
c.globalAlpha = alfa * (1 - f) * Math.min(1, f / 0.2);
|
||||||
|
c.beginPath(); c.moveTo(bx, by); c.bezierCurveTo(bx + s * 0.1, by - s * 0.12, bx - s * 0.1, by - s * 0.24, bx, by - s * 0.36); c.stroke();
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
function xicara(c, x, y, s, cor) {
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(x - s * 0.18, y - s * 0.2); c.lineTo(x + s * 0.18, y - s * 0.2); c.lineTo(x + s * 0.14, y + s * 0.08); c.lineTo(x - s * 0.14, y + s * 0.08); c.closePath(); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.arc(x + s * 0.2, y - s * 0.07, s * 0.08, -1.4, 1.4); c.stroke();
|
||||||
|
}
|
||||||
|
function chama(c, x, y, alt, s) {
|
||||||
|
c.fillStyle = css(LARANJA_);
|
||||||
|
c.beginPath(); c.moveTo(x, y - alt); c.quadraticCurveTo(x + s * 0.13, y - alt * 0.3, x, y); c.quadraticCurveTo(x - s * 0.13, y - alt * 0.3, x, y - alt); c.fill();
|
||||||
|
c.fillStyle = css(AMARELO_);
|
||||||
|
c.beginPath(); c.moveTo(x, y - alt * 0.6); c.quadraticCurveTo(x + s * 0.06, y - alt * 0.2, x, y); c.quadraticCurveTo(x - s * 0.06, y - alt * 0.2, x, y - alt * 0.6); c.fill();
|
||||||
|
}
|
||||||
|
function zzz(c, x, y, s, k, cor) { // Zs subindo
|
||||||
|
c.save(); c.strokeStyle = css(cor); c.lineCap = 'round'; c.lineJoin = 'round';
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.3 + i / 3) % 1, tam = s * (0.18 + 0.2 * f), zx = x - s * 0.5 * f, zy = y - s * 1.3 * f;
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.15) * (1 - f); c.lineWidth = Math.max(1.5, tam * 0.2);
|
||||||
|
c.beginPath(); c.moveTo(zx - tam / 2, zy - tam / 2); c.lineTo(zx + tam / 2, zy - tam / 2); c.lineTo(zx - tam / 2, zy + tam / 2); c.lineTo(zx + tam / 2, zy + tam / 2); c.stroke();
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
function notas(c, x, y, s, k, cor, dir) {
|
||||||
|
c.fillStyle = css(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const f = (k * 0.45 + i / 3) % 1;
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.15) * (1 - f);
|
||||||
|
nota(c, x + dir * s * (0.3 + 0.5 * i) + Math.sin(f * 6 + i) * s * 0.2, y - f * s * 1.6, s * 0.7);
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
|
||||||
const CENAS = {
|
const CENAS = {
|
||||||
ler(c, x, y, s, k, cor) {
|
ler(c, x, y, s, k, cor) {
|
||||||
const b = boneco(c, x, y, s, {
|
const b = boneco(c, x, y, s, {
|
||||||
@@ -786,6 +883,321 @@
|
|||||||
c.beginPath(); c.arc(b.cabeca.x + s * 0.72, b.cabeca.y + s * 0.3 + g * s * 0.5, s * 0.07, 0, Math.PI * 2); c.fill();
|
c.beginPath(); c.arc(b.cabeca.x + s * 0.72, b.cabeca.y + s * 0.3 + g * s * 0.5, s * 0.07, 0, Math.PI * 2); c.fill();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
dormir(c, x, y, s, k, cor) {
|
||||||
|
// cama com cabeceira, travesseiro e cobertor que sobe e desce com a respiração
|
||||||
|
const bx0 = x - s * 1.7, bx1 = x + s * 1.9, topo = y - s * 0.75;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(bx0 + s * 0.12, y); c.lineTo(bx0 + s * 0.12, topo); c.moveTo(bx1 - s * 0.12, y); c.lineTo(bx1 - s * 0.12, topo); c.stroke();
|
||||||
|
caixa(c, bx0 - s * 0.08, topo - s * 1.25, s * 0.24, s * 1.3, s * 0.1); c.fill(); c.stroke();
|
||||||
|
caixa(c, bx0, topo - s * 0.3, bx1 - bx0, s * 0.32, s * 0.12); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.9);
|
||||||
|
c.beginPath(); c.ellipse(bx0 + s * 0.85, topo - s * 0.5, s * 0.6, s * 0.24, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
// bonequinho deitado: desenhado em pé e girado 90°, pés à direita, cabeça no travesseiro
|
||||||
|
const e = 0.72, linha = topo - s * 0.3 - s * e * 0.5;
|
||||||
|
c.save(); c.translate(x + s * 1.5, linha); c.rotate(-Math.PI / 2);
|
||||||
|
boneco(c, 0, 0, s * e, { fechados: true, bE: [-0.1, 0], bD: [0.1, 0] }, cor, k);
|
||||||
|
c.restore();
|
||||||
|
const resp = Math.sin(k * 1.4) * s * 0.04;
|
||||||
|
c.fillStyle = css(cor.map((v) => v * 0.45)); c.strokeStyle = css(cor);
|
||||||
|
caixa(c, x - s * 0.3, linha - s * 0.42 - resp, s * 2.15, topo - s * 0.3 - (linha - s * 0.42 - resp) + s * 0.06, s * 0.18); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = css(cor, 0.5); c.beginPath(); c.moveTo(x - s * 0.1, linha - s * 0.2 - resp); c.lineTo(x + s * 1.7, linha - s * 0.2 - resp); c.stroke();
|
||||||
|
zzz(c, x - s * 0.9, linha - s * 0.9, s, k, cor);
|
||||||
|
},
|
||||||
|
cafe(c, x, y, s, k, cor) {
|
||||||
|
const f = (k % 5) / 5, gole = suave(faixaDe(f, 0.5, 0.64)) * (1 - suave(faixaDe(f, 0.84, 0.97)));
|
||||||
|
const b = boneco(c, x, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 0.2, olhoY: 0.3 - gole * 0.8, fechados: gole > 0.85, cabeca: -0.04,
|
||||||
|
bE: [0.15, 0.95], bD: [-0.15 + gole * 1.75, -0.95 - gole * 3.05],
|
||||||
|
}), cor, k);
|
||||||
|
xicara(c, b.mD.x, b.mD.y, s, cor);
|
||||||
|
if (gole < 0.3) vapor(c, b.mD.x, b.mD.y - s * 0.3, s, k, cor);
|
||||||
|
},
|
||||||
|
cozinhar(c, x, y, s, k, cor) {
|
||||||
|
const ciclo = (k % 2.6) / 2.6, voo = ciclo < 0.55 ? ciclo / 0.55 : -1, h = voo >= 0 ? 4 * voo * (1 - voo) : 0;
|
||||||
|
const fx = x + s * 1.0, fw = s * 1.7, fh = s * 1.2;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
caixa(c, fx, y - fh, fw, fh, s * 0.08); c.fill(); c.stroke();
|
||||||
|
caixa(c, fx + fw * 0.15, y - fh * 0.72, fw * 0.7, fh * 0.5, s * 0.06); c.stroke();
|
||||||
|
for (let i = 0; i < 3; i++) chama(c, fx + fw * (0.3 + 0.2 * i), y - fh, s * (0.22 + 0.07 * Math.sin(k * 17 + i * 2)), s);
|
||||||
|
const b = boneco(c, x - s * 0.45, y, s, {
|
||||||
|
olhoX: 1, olhoY: 0.3 - h, cabeca: -0.05, bE: [-0.4, 0.3],
|
||||||
|
bD: [1.2 - (voo >= 0 && voo < 0.15 ? 0.25 : 0), 0.35],
|
||||||
|
}, cor, k);
|
||||||
|
const px = b.mD.x + s * 0.62, py = b.mD.y + s * 0.02;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(2, s * 0.09);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y); c.lineTo(px - s * 0.42, py); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.ellipse(px, py, s * 0.46, s * 0.13, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.save(); c.translate(px, py - s * 0.1 - h * s * 1.6); c.rotate(voo >= 0 ? voo * Math.PI * 2 : 0);
|
||||||
|
c.fillStyle = css(AMARELO_); c.beginPath(); c.ellipse(0, 0, s * 0.32, s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
regar(c, x, y, s, k, cor) {
|
||||||
|
const cresce = Math.min(1, k / 18), px = x + s * 2.0;
|
||||||
|
// vaso
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(px - s * 0.42, y - s * 0.6); c.lineTo(px + s * 0.42, y - s * 0.6); c.lineTo(px + s * 0.3, y); c.lineTo(px - s * 0.3, y); c.closePath(); c.fill(); c.stroke();
|
||||||
|
// planta crescendo: caule, folhas e, no fim, a flor
|
||||||
|
const alt = s * (0.3 + 1.25 * cresce), base = y - s * 0.6, balanca = Math.sin(k * 1.5) * s * 0.06;
|
||||||
|
c.strokeStyle = css(VERDE_); c.lineWidth = Math.max(1.5, s * 0.07);
|
||||||
|
c.beginPath(); c.moveTo(px, base); c.quadraticCurveTo(px + balanca, base - alt / 2, px + balanca * 2, base - alt); c.stroke();
|
||||||
|
c.fillStyle = css(VERDE_);
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
if (cresce < i * 0.22) continue;
|
||||||
|
const yy = base - alt * i * 0.25, lado = i % 2 ? 1 : -1;
|
||||||
|
c.beginPath(); c.ellipse(px + balanca * i * 0.5 + lado * s * 0.18, yy, s * 0.2, s * 0.08, lado * -0.5, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
if (cresce > 0.75) {
|
||||||
|
const f = (cresce - 0.75) / 0.25, fx = px + balanca * 2, fy = base - alt;
|
||||||
|
c.fillStyle = css(ROSA_);
|
||||||
|
for (let i = 0; i < 5; i++) { const a = i * 1.2566 + k * 0.3; c.beginPath(); c.arc(fx + Math.cos(a) * s * 0.16 * f, fy + Math.sin(a) * s * 0.16 * f, s * 0.11 * f, 0, Math.PI * 2); c.fill(); }
|
||||||
|
c.fillStyle = css(AMARELO_); c.beginPath(); c.arc(fx, fy, s * 0.09 * f, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
const b = boneco(c, x - s * 0.7, y, s, { olhoX: 1, olhoY: 0.8, cabeca: -0.1, inclina: -0.06, bD: [1.35, 0.35], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
// regador inclinado e as gotas caindo na planta
|
||||||
|
const ang = 0.38, rx = b.mD.x + s * 0.05, ry = b.mD.y;
|
||||||
|
c.save(); c.translate(rx, ry); c.rotate(ang);
|
||||||
|
c.fillStyle = claro(cor, 0.95); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, 0, -s * 0.22, s * 0.6, s * 0.42, s * 0.08); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.moveTo(s * 0.55, -s * 0.05); c.lineTo(s * 1.0, -s * 0.32); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
const ex = rx + Math.cos(ang) * s * 1.0 + Math.sin(ang) * s * 0.32, ey = ry + Math.sin(ang) * s * 1.0 - Math.cos(ang) * s * 0.32;
|
||||||
|
c.fillStyle = claro(cor, 1);
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const f = (k * 1.6 + i / 6) % 1;
|
||||||
|
c.beginPath(); c.ellipse(ex + (px - ex) * f * 0.8, ey + (base - alt - ey + s * 0.2) * f * f, s * 0.035, s * 0.07, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
violao(c, x, y, s, k, cor) {
|
||||||
|
const toque = Math.sin(k * 11);
|
||||||
|
const b = boneco(c, x, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: -0.4, olhoY: 0.7, cabeca: 0.08 * Math.sin(k * 1.1),
|
||||||
|
bE: [-1.1, -0.9], bD: [-0.2 + toque * 0.12, -1.0],
|
||||||
|
}), cor, k);
|
||||||
|
// violão atravessado no colo: corpo perto da mão que toca, braço até a outra mão
|
||||||
|
const gx = b.quadril.x + s * 0.3, gy = b.quadril.y - s * 0.35, nx = b.mE.x, ny = b.mE.y;
|
||||||
|
const a = Math.atan2(ny - gy, nx - gx);
|
||||||
|
c.save(); c.translate(gx, gy); c.rotate(a);
|
||||||
|
const L = Math.hypot(nx - gx, ny - gy);
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, s * 0.2, -s * 0.08, L - s * 0.1, s * 0.16, s * 0.04); c.fill(); c.stroke();
|
||||||
|
caixa(c, L - s * 0.05, -s * 0.12, s * 0.3, s * 0.24, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.95);
|
||||||
|
c.beginPath(); c.arc(-s * 0.28, 0, s * 0.42, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.arc(s * 0.22, 0, s * 0.32, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.beginPath(); c.arc(s * 0.05, 0, s * 0.11, 0, Math.PI * 2); c.fill();
|
||||||
|
c.strokeStyle = css(cor, 0.7); c.lineWidth = Math.max(1, s * 0.015);
|
||||||
|
for (const d of [-0.04, 0, 0.04]) { c.beginPath(); c.moveTo(-s * 0.5, d * s); c.lineTo(L, d * s); c.stroke(); }
|
||||||
|
c.restore();
|
||||||
|
c.fillStyle = css(cor); c.beginPath(); c.arc(b.mD.x, b.mD.y, s * 0.13, 0, Math.PI * 2); c.fill();
|
||||||
|
notas(c, b.cabeca.x - s * 1.6, b.cabeca.y + s * 0.2, s, k, cor, -1);
|
||||||
|
},
|
||||||
|
dancar(c, x, y, s, k, cor) {
|
||||||
|
const u = (Math.sin(k * 5) + 1) / 2, mexe = Math.sin(k * 5) * 0.18;
|
||||||
|
// globo espelhado e luzinhas no chão
|
||||||
|
const gx = x, gy = y - s * 4.5;
|
||||||
|
c.strokeStyle = css(cor, 0.6); c.lineWidth = Math.max(1, s * 0.03);
|
||||||
|
c.beginPath(); c.moveTo(gx, gy - s * 0.9); c.lineTo(gx, gy - s * 0.35); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 0.9); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.arc(gx, gy, s * 0.35, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = css(cor, 0.5);
|
||||||
|
for (let i = -1; i <= 1; i++) { c.beginPath(); c.ellipse(gx, gy, s * 0.35 * Math.abs(Math.cos(k + i)), s * 0.35, 0, 0, Math.PI * 2); c.stroke(); }
|
||||||
|
const cores = [ROSA_, AMARELO_, VERDE_, cor];
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const lx = x + Math.sin(k * 1.3 + i * 1.7) * s * 2.4, alfa = c.globalAlpha;
|
||||||
|
c.globalAlpha = alfa * 0.35; c.fillStyle = css(cores[i % 4]);
|
||||||
|
c.beginPath(); c.ellipse(lx, y, s * 0.35, s * 0.07, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
boneco(c, x, y, s, {
|
||||||
|
dx: mexe, inclina: mexe * 0.5, cabeca: -mexe * 0.6, olhoY: -0.2, olhoX: mexe * 3,
|
||||||
|
bE: [-0.7 - 2.1 * u, 0.5], bD: [0.7 + 2.1 * (1 - u), -0.5],
|
||||||
|
pE: [-0.1 - u * 0.25, u * 0.4], pD: [0.1 + (1 - u) * 0.25, -(1 - u) * 0.4],
|
||||||
|
}, cor, k);
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const f = (k * 0.7 + i / 4) % 1, alfa = c.globalAlpha;
|
||||||
|
c.globalAlpha = alfa * Math.sin(f * Math.PI); c.fillStyle = css(cores[i]);
|
||||||
|
estrela(c, x + Math.cos(i * 1.9) * s * 1.6, y - s * (2 + i * 0.5) - f * s * 0.4, s * 0.12); c.globalAlpha = alfa;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
corda(c, x, y, s, k, cor) {
|
||||||
|
const th = k * Math.PI * 2 * 0.85, pulo = Math.pow(Math.max(0, Math.cos(th)), 0.6), sobe = pulo * s * 0.45;
|
||||||
|
c.save(); c.globalAlpha *= 0.3 * (1 - pulo * 0.5); c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(x, y, s * 0.6 * (1 - pulo * 0.3), s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.restore();
|
||||||
|
const b = boneco(c, x, y - sobe, s, {
|
||||||
|
olhoY: -0.2, bE: [-0.75 + 0.15 * Math.sin(th), 0.6], bD: [0.75 - 0.15 * Math.sin(th), -0.6],
|
||||||
|
pE: [-0.06, pulo * 0.3], pD: [0.06, -pulo * 0.3],
|
||||||
|
}, cor, k);
|
||||||
|
// a corda passa por baixo dos pés quando ele está no alto do pulo
|
||||||
|
const my = (b.mE.y + b.mD.y) / 2, embaixo = 2 * (y + s * 0.05) - my, emcima = 2 * (b.cabeca.y - s * 1.0) - my;
|
||||||
|
const cy = (embaixo + emcima) / 2 + Math.cos(th) * (embaixo - emcima) / 2;
|
||||||
|
c.strokeStyle = claro(cor, 1); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(b.mE.x, b.mE.y); c.quadraticCurveTo(x, cy, b.mD.x, b.mD.y); c.stroke();
|
||||||
|
},
|
||||||
|
meditar(c, x, y, s, k, cor) {
|
||||||
|
const flutua = (Math.sin(k * 1.2) + 1) / 2 * s * 0.35, cy = y - s * 1.7 - flutua;
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 3; i++) { // aura
|
||||||
|
const f = (k * 0.35 + i / 3) % 1;
|
||||||
|
c.globalAlpha = alfa * 0.35 * (1 - f); c.strokeStyle = css(cor); c.lineWidth = Math.max(1, s * 0.04);
|
||||||
|
c.beginPath(); c.ellipse(x, cy, s * (1.2 + f * 1.3), s * (1.4 + f * 1.3), 0, 0, Math.PI * 2); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa * 0.3; c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(x, y, s * 0.8 * (1 - flutua / s), s * 0.09, 0, 0, Math.PI * 2); c.fill();
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
boneco(c, x, y - s * 0.3 - flutua, s, {
|
||||||
|
sentado: true, fechados: true, pE: [-1.45, 2.75], pD: [1.45, -2.75], bE: [-0.75, 0.25], bD: [0.75, -0.25],
|
||||||
|
}, cor, k);
|
||||||
|
},
|
||||||
|
telescopio(c, x, y, s, k, cor) {
|
||||||
|
// céu estrelado com uma estrela cadente de vez em quando
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
c.globalAlpha = alfa * (0.35 + 0.65 * Math.abs(Math.sin(k * (1 + rnd(i, 3)) + i)));
|
||||||
|
c.fillStyle = i % 5 ? claro(cor, 1) : css(AMARELO_);
|
||||||
|
estrela(c, x + s * (0.2 + rnd(i, 1) * 3.4), y - s * (3.4 + rnd(i, 2) * 1.5), s * (0.05 + 0.05 * rnd(i, 4)));
|
||||||
|
}
|
||||||
|
const f = (k % 6) / 1.1;
|
||||||
|
if (f < 1) {
|
||||||
|
const sx = x + s * (3.6 - f * 2.2), sy = y - s * (4.9 - f * 0.8);
|
||||||
|
const g = c.createLinearGradient(sx, sy, sx + s * 0.9, sy - s * 0.33);
|
||||||
|
g.addColorStop(0, claro(cor, 1)); g.addColorStop(1, css(cor, 0));
|
||||||
|
c.globalAlpha = alfa * (1 - f); c.strokeStyle = g; c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(sx, sy); c.lineTo(sx + s * 0.9, sy - s * 0.33); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
const b = boneco(c, x - s * 0.7, y, s, { olhoX: 1, olhoY: -1, cabeca: 0.15, bD: [2.1, 0.5], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
const ox = b.cabeca.x + s * 0.78, oy = b.cabeca.y + s * 0.3, tx = ox + s * 1.6, ty = oy - s * 0.95;
|
||||||
|
const mx = (ox + tx) / 2, my = (oy + ty) / 2;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(mx, my); c.lineTo(mx - s * 0.5, y); c.moveTo(mx, my); c.lineTo(mx + s * 0.5, y); c.moveTo(mx, my); c.lineTo(mx, y); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor);
|
||||||
|
c.beginPath(); capsula(c, ox, oy, tx, ty, s * 0.15); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = claro(cor, 1); c.beginPath(); c.arc(tx, ty, s * 0.17, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
},
|
||||||
|
pipa(c, x, y, s, k, cor) {
|
||||||
|
const kx = x + s * (2.3 + 0.45 * Math.sin(k * 0.8)), ky = y - s * (4.2 + 0.35 * Math.cos(k * 1.1)), ang = 0.25 * Math.sin(k * 1.6);
|
||||||
|
const b = boneco(c, x - s * 0.9, y, s, { olhoX: 1, olhoY: -1, cabeca: 0.12, bD: [2.55, 0.35], bE: [-0.4, 0.3] }, cor, k);
|
||||||
|
c.strokeStyle = css(cor, 0.8); c.lineWidth = Math.max(1, s * 0.025);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y);
|
||||||
|
c.quadraticCurveTo((b.mD.x + kx) / 2 + s * 0.4, (b.mD.y + ky) / 2 + s * 0.7, kx, ky + s * 0.45); c.stroke();
|
||||||
|
// rabiola com lacinhos
|
||||||
|
c.save(); c.translate(kx, ky); c.rotate(ang);
|
||||||
|
c.strokeStyle = css(cor, 0.8); c.beginPath(); c.moveTo(0, s * 0.5);
|
||||||
|
for (let i = 1; i <= 6; i++) c.lineTo(Math.sin(k * 3 + i) * s * 0.18, s * (0.5 + i * 0.22));
|
||||||
|
c.stroke();
|
||||||
|
for (let i = 2; i <= 6; i += 2) {
|
||||||
|
const lx = Math.sin(k * 3 + i) * s * 0.18, ly = s * (0.5 + i * 0.22);
|
||||||
|
c.fillStyle = css(i % 4 ? ROSA_ : AMARELO_);
|
||||||
|
c.beginPath(); c.moveTo(lx, ly); c.lineTo(lx - s * 0.1, ly - s * 0.06); c.lineTo(lx - s * 0.1, ly + s * 0.06); c.closePath(); c.fill();
|
||||||
|
c.beginPath(); c.moveTo(lx, ly); c.lineTo(lx + s * 0.1, ly - s * 0.06); c.lineTo(lx + s * 0.1, ly + s * 0.06); c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
const pts = [[0, -0.5], [0.38, 0], [0, 0.5], [-0.38, 0]];
|
||||||
|
const cores = [ROSA_, AMARELO_, cor, VERDE_];
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const [ax, ay] = pts[i], [bx, by] = pts[(i + 1) % 4];
|
||||||
|
c.fillStyle = css(cores[i]); c.beginPath(); c.moveTo(0, 0); c.lineTo(ax * s, ay * s); c.lineTo(bx * s, by * s); c.closePath(); c.fill();
|
||||||
|
}
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.04);
|
||||||
|
c.beginPath(); pts.forEach(([px, py], i) => (i ? c.lineTo(px * s, py * s) : c.moveTo(px * s, py * s))); c.closePath(); c.stroke();
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
bolhas(c, x, y, s, k, cor) {
|
||||||
|
const b = boneco(c, x - s * 0.6, y, s, { olhoX: 0.8, olhoY: -0.6, bD: [1.6, -4.0], bE: [-0.3, 0.2] }, cor, k);
|
||||||
|
const wx = b.mD.x + s * 0.35, wy = b.mD.y - s * 0.25;
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
c.beginPath(); c.moveTo(b.mD.x, b.mD.y); c.lineTo(wx - s * 0.1, wy + s * 0.08); c.stroke();
|
||||||
|
c.beginPath(); c.arc(wx, wy, s * 0.12, 0, Math.PI * 2); c.stroke();
|
||||||
|
const alfa = c.globalAlpha;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
const f = (k * 0.22 + i / 9) % 1, r = s * (0.12 + 0.18 * rnd(i, 4));
|
||||||
|
const bx = wx + f * s * (1.6 + 1.4 * rnd(i, 5)) + Math.sin(k * 2 + i) * s * 0.15, by = wy - f * s * (1.6 + rnd(i, 6));
|
||||||
|
c.globalAlpha = alfa * Math.min(1, f / 0.08) * (f > 0.9 ? (1 - f) / 0.1 : 1);
|
||||||
|
c.fillStyle = css(cor, 0.1); c.strokeStyle = css(cor, 0.9); c.lineWidth = Math.max(1, s * 0.03);
|
||||||
|
c.beginPath(); c.arc(bx, by, r, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.strokeStyle = claro(cor, 1); c.beginPath(); c.arc(bx, by, r * 0.65, -2.4, -1.6); c.stroke();
|
||||||
|
}
|
||||||
|
c.globalAlpha = alfa;
|
||||||
|
},
|
||||||
|
skate(c, x, y, s, k, cor) {
|
||||||
|
const pos = Math.sin(k * 0.9) * 1.3, vel = Math.cos(k * 0.9), ciclo = k % 4;
|
||||||
|
const ollie = ciclo < 0.7 ? Math.sin(ciclo / 0.7 * Math.PI) : 0, sobe = ollie * s * 0.6;
|
||||||
|
const bx = x + pos * s, pe = y - s * 0.3 - sobe;
|
||||||
|
c.save(); c.globalAlpha *= 0.3; c.fillStyle = css(cor);
|
||||||
|
c.beginPath(); c.ellipse(bx, y, s * 0.9, s * 0.08, 0, 0, Math.PI * 2); c.fill(); c.restore();
|
||||||
|
c.strokeStyle = css(cor, 0.4); c.lineWidth = Math.max(1, s * 0.03); // risquinhos de velocidade
|
||||||
|
for (let i = 0; i < 3; i++) { const lx = bx - Math.sign(vel) * s * (1.1 + i * 0.25); c.beginPath(); c.moveTo(lx, pe - s * (0.6 + i * 0.4)); c.lineTo(lx - Math.sign(vel) * s * 0.5 * Math.abs(vel), pe - s * (0.6 + i * 0.4)); c.stroke(); }
|
||||||
|
boneco(c, bx, pe, s, {
|
||||||
|
olhoX: vel * 0.8, inclina: -vel * 0.1, bE: [-1.35, 0.25 + 0.2 * Math.sin(k * 3)], bD: [1.35, -0.25 - 0.2 * Math.sin(k * 3)],
|
||||||
|
pE: [-0.25, 0.35], pD: [0.25, -0.35],
|
||||||
|
}, cor, k);
|
||||||
|
c.save(); c.translate(bx, pe); c.rotate(-ollie * 0.25);
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.05);
|
||||||
|
caixa(c, -s * 0.85, 0, s * 1.7, s * 0.12, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor);
|
||||||
|
for (const lado of [-1, 1]) { c.beginPath(); c.arc(lado * s * 0.55, s * 0.2, s * 0.1, 0, Math.PI * 2); c.fill(); c.stroke(); }
|
||||||
|
c.restore();
|
||||||
|
},
|
||||||
|
computador(c, x, y, s, k, cor) {
|
||||||
|
const mx0 = x + s * 0.3, mx1 = x + s * 2.7, mt = y - s * 1.45;
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(mx0 + s * 0.15, mt); c.lineTo(mx0 + s * 0.15, y); c.moveTo(mx1 - s * 0.15, mt); c.lineTo(mx1 - s * 0.15, y); c.stroke();
|
||||||
|
caixa(c, mx0, mt, mx1 - mx0, s * 0.12, s * 0.04); c.fill(); c.stroke();
|
||||||
|
const nx = mx0 + s * 0.6, nw = s * 1.4, nh = s * 0.95, ny = mt - s * 0.08 - nh;
|
||||||
|
caixa(c, nx - s * 0.12, mt - s * 0.08, nw + s * 0.24, s * 0.08, s * 0.03); c.fill(); c.stroke();
|
||||||
|
caixa(c, nx, ny, nw, nh, s * 0.06); c.fill(); c.stroke();
|
||||||
|
c.save(); caixa(c, nx + s * 0.06, ny + s * 0.06, nw - s * 0.12, nh - s * 0.12, s * 0.03); c.clip();
|
||||||
|
const linhas = Math.floor(k * 2.5), visiveis = 6, ini = Math.max(0, linhas - visiveis);
|
||||||
|
const tintas = [cor, VERDE_, AMARELO_, ROSA_];
|
||||||
|
for (let i = ini; i < linhas; i++) {
|
||||||
|
const yy = ny + s * 0.14 + (i - ini) * s * 0.13, recuo = (rnd(i, 7) < 0.4 ? 1 : 0) * s * 0.15;
|
||||||
|
c.fillStyle = css(tintas[i % 4], 0.85); c.fillRect(nx + s * 0.12 + recuo, yy, (nw - s * 0.4) * (0.3 + 0.6 * rnd(i, 8)), s * 0.05);
|
||||||
|
}
|
||||||
|
if (Math.floor(k * 2) % 2) { c.fillStyle = claro(cor, 1); c.fillRect(nx + s * 0.12, ny + s * 0.14 + Math.min(linhas, visiveis) * s * 0.13, s * 0.04, s * 0.08); }
|
||||||
|
c.restore();
|
||||||
|
const tecla = Math.sin(k * 20) * 0.05;
|
||||||
|
boneco(c, x - s * 0.55, y, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 1, olhoY: 0.2, cabeca: -0.05, bE: [0.9 + tecla, 0.9], bD: [1.35 - tecla, 0.45],
|
||||||
|
}), cor, k);
|
||||||
|
},
|
||||||
|
pescar(c, x, y, s, k, cor) {
|
||||||
|
const ciclo = k % 8, fisgou = ciclo > 6.2 && ciclo < 7.6, puxa = fisgou ? suave((ciclo - 6.2) / 0.5) : 0;
|
||||||
|
// água: ondinhas à direita do píer
|
||||||
|
c.strokeStyle = css(cor, 0.55); c.lineWidth = Math.max(1, s * 0.035);
|
||||||
|
for (let fila = 0; fila < 3; fila++) {
|
||||||
|
c.beginPath();
|
||||||
|
for (let i = 0; i <= 24; i++) {
|
||||||
|
const wx = x + s * (0.5 + i * 0.14), wy = y - s * 0.05 + fila * s * 0.16 + Math.sin(k * 2 + i * 0.8 + fila) * s * 0.04;
|
||||||
|
if (i) c.lineTo(wx, wy); else c.moveTo(wx, wy);
|
||||||
|
}
|
||||||
|
c.stroke();
|
||||||
|
}
|
||||||
|
c.fillStyle = escuro(cor); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
caixa(c, x - s * 2.0, y - s * 0.22, s * 2.5, s * 0.22, s * 0.05); c.fill(); c.stroke();
|
||||||
|
const b = boneco(c, x - s * 0.8, y - s * 0.22, s, Object.assign({}, SENTADO, {
|
||||||
|
olhoX: 1, olhoY: 0.6 - puxa * 1.4, bD: [2.0 - puxa * 0.4, -0.9], bE: [0.5, 1.2],
|
||||||
|
}), cor, k);
|
||||||
|
const vx = b.mD.x + s * (1.4 - puxa * 0.3), vy = b.mD.y - s * (1.6 + puxa * 0.3);
|
||||||
|
c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.06);
|
||||||
|
c.beginPath(); c.moveTo(b.mE.x, b.mE.y); c.lineTo(b.mD.x, b.mD.y); c.lineTo(vx, vy); c.stroke();
|
||||||
|
const belisca = ciclo > 5.5 && ciclo < 6.2 ? Math.sin(k * 30) * s * 0.06 : 0;
|
||||||
|
let lx = x + s * 2.6, ly = y - s * 0.05 + Math.sin(k * 2) * s * 0.04 + belisca;
|
||||||
|
if (fisgou) { // o peixe pula preso na linha
|
||||||
|
lx = x + s * (2.6 - puxa * 0.6); ly = y - s * (0.2 + Math.sin(puxa * Math.PI) * 1.3 + puxa * 0.3);
|
||||||
|
c.save(); c.translate(lx, ly + s * 0.25); c.rotate(Math.sin(k * 14) * 0.4);
|
||||||
|
c.fillStyle = claro(cor, 1); c.strokeStyle = css(cor); c.lineWidth = Math.max(1.5, s * 0.04);
|
||||||
|
c.beginPath(); c.ellipse(0, 0, s * 0.12, s * 0.28, 0, 0, Math.PI * 2); c.fill(); c.stroke();
|
||||||
|
c.beginPath(); c.moveTo(0, s * 0.25); c.lineTo(-s * 0.14, s * 0.45); c.lineTo(s * 0.14, s * 0.45); c.closePath(); c.fill(); c.stroke();
|
||||||
|
c.fillStyle = escuro(cor); c.beginPath(); c.arc(s * 0.04, -s * 0.12, s * 0.03, 0, Math.PI * 2); c.fill();
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
c.strokeStyle = css(cor, 0.85); c.lineWidth = Math.max(1, s * 0.02);
|
||||||
|
c.beginPath(); c.moveTo(vx, vy); c.quadraticCurveTo((vx + lx) / 2, Math.max(vy, ly) - s * (fisgou ? 0.1 : 0.4), lx, ly); c.stroke();
|
||||||
|
if (!fisgou) {
|
||||||
|
c.fillStyle = css(ROSA_); c.beginPath(); c.arc(lx, ly, s * 0.09, Math.PI, 0); c.fill();
|
||||||
|
c.fillStyle = claro(cor, 1); c.beginPath(); c.arc(lx, ly, s * 0.09, 0, Math.PI); c.fill();
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Desenha a cena da atividade (camada atrás dos olhos). o = { t, cor }. */
|
/** Desenha a cena da atividade (camada atrás dos olhos). o = { t, cor }. */
|
||||||
|
|||||||
+49
-3
@@ -100,11 +100,14 @@ class VidaTest(unittest.TestCase):
|
|||||||
self.v.celular_online = lambda: True
|
self.v.celular_online = lambda: True
|
||||||
self.v.necessidades.update(energia=80, alegria=70, social=70, diversao=10) # entediado
|
self.v.necessidades.update(energia=80, alegria=70, social=70, diversao=10) # entediado
|
||||||
self.v.rng.random = lambda: 0.0 # sorteio sempre "sim"
|
self.v.rng.random = lambda: 0.0 # sorteio sempre "sim"
|
||||||
ato = self.v.agir(DIA)
|
ato = self.v.agir(DIA) # entediado: vai fazer algo
|
||||||
self.assertTrue(ato.startswith('atividade '))
|
self.assertTrue(ato.startswith('atividade '))
|
||||||
self.assertIn(ato.split()[1], ('videogame', 'bola', 'pintar', 'ler'))
|
nome, segundos = self.ordens[-1].split()[1:]
|
||||||
self.assertEqual(self.ordens[-1], ato)
|
self.assertEqual(ato, 'atividade ' + nome)
|
||||||
|
self.assertTrue(120 <= float(segundos) <= 300) # por alguns minutos
|
||||||
self.assertEqual(self.v.diario[0]['tipo'], 'atividade')
|
self.assertEqual(self.v.diario[0]['tipo'], 'atividade')
|
||||||
|
self.v.celular_atividade = lambda: nome # fazendo: diversão sobe
|
||||||
|
self.v.passar_tempo(DIA + 180)
|
||||||
self.assertGreater(self.v.necessidades['diversao'], 10)
|
self.assertGreater(self.v.necessidades['diversao'], 10)
|
||||||
self.v.celular_atividade = lambda: 'videogame' # ocupado: não interrompe
|
self.v.celular_atividade = lambda: 'videogame' # ocupado: não interrompe
|
||||||
self.assertIsNone(self.v.agir(DIA + 60))
|
self.assertIsNone(self.v.agir(DIA + 60))
|
||||||
@@ -113,6 +116,49 @@ class VidaTest(unittest.TestCase):
|
|||||||
self.v.config['noite'] = False
|
self.v.config['noite'] = False
|
||||||
self.assertFalse(str(self.v.agir(noite)).startswith('atividade')) # de madrugada, nunca
|
self.assertFalse(str(self.v.agir(noite)).startswith('atividade')) # de madrugada, nunca
|
||||||
|
|
||||||
|
def test_exausto_dorme_e_o_sono_e_respeitado(self):
|
||||||
|
self.v.necessidades['energia'] = 15
|
||||||
|
self.assertEqual(self.v.agir(DIA), 'dormir')
|
||||||
|
self.assertTrue(self.v.sono_profundo)
|
||||||
|
self.assertIn('sono profundo', self.ordens)
|
||||||
|
self.v.necessidades['energia'] = 60
|
||||||
|
self.assertIsNone(self.v.agir(DIA + 60)) # ainda não descansou
|
||||||
|
self.v.necessidades['energia'] = 96
|
||||||
|
self.assertEqual(self.v.agir(DIA + 120), 'acordar')
|
||||||
|
self.assertFalse(self.v.sono_profundo)
|
||||||
|
self.assertIn('sono leve', self.ordens)
|
||||||
|
|
||||||
|
def test_acordado_de_proposito_encerra_o_sono_pesado(self):
|
||||||
|
self.v.necessidades['energia'] = 15
|
||||||
|
self.v.agir(DIA)
|
||||||
|
self.v.celular_dormindo = lambda: False # tocaram na tela
|
||||||
|
self.v.tick(DIA + 30)
|
||||||
|
self.assertFalse(self.v.sono_profundo)
|
||||||
|
self.assertEqual(self.v.diario[0]['texto'], 'Acordou antes de descansar')
|
||||||
|
|
||||||
|
def test_cochilo_quando_cansado(self):
|
||||||
|
self.v.celular_online = lambda: True
|
||||||
|
self.v.rng.random = lambda: 0.0
|
||||||
|
self.v.necessidades.update(energia=35, diversao=80)
|
||||||
|
self.assertEqual(self.v.agir(DIA), 'atividade dormir')
|
||||||
|
self.v.celular_atividade = lambda: 'dormir' # na caminha: recupera
|
||||||
|
antes = self.v.necessidades['energia']
|
||||||
|
self.v.passar_tempo(DIA + 300)
|
||||||
|
self.assertGreater(self.v.necessidades['energia'], antes)
|
||||||
|
|
||||||
|
def test_alegria_caindo_deixa_bravo(self):
|
||||||
|
self.v.necessidades.update(energia=80, social=80, diversao=80, alegria=100)
|
||||||
|
self.assertEqual(self.v.zanga(), 0.0)
|
||||||
|
self.v.necessidades['alegria'] = 42
|
||||||
|
self.assertEqual(self.v.zanga(), 0.4)
|
||||||
|
self.v.tick(DIA + 5)
|
||||||
|
self.assertIn('zanga 0.4', self.ordens)
|
||||||
|
self.v.necessidades['alegria'] = 20
|
||||||
|
self.assertEqual(self.v.humor(), 'bravo')
|
||||||
|
self.assertEqual(self.v.zanga(), 0.9)
|
||||||
|
self.v.dormindo_proprio = True # dormindo, sem cara feia
|
||||||
|
self.assertEqual(self.v.zanga(), 0.0)
|
||||||
|
|
||||||
def test_horario_da_madrugada(self):
|
def test_horario_da_madrugada(self):
|
||||||
from controle_pc import madrugada
|
from controle_pc import madrugada
|
||||||
h = lambda hora: time.mktime((2026, 10, 6, hora, 10, 0, 0, 0, -1))
|
h = lambda hora: time.mktime((2026, 10, 6, hora, 10, 0, 0, 0, -1))
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ CUIDADOS = {'carinho': 'Recebeu carinho', 'cutucar': 'Levou uma cutucada', 'brin
|
|||||||
|
|
||||||
HUMORES = {
|
HUMORES = {
|
||||||
'dormindo': ('😴', 'Dormindo'), 'cansado': ('🥱', 'Cansado'), 'carente': ('🥺', 'Carente'),
|
'dormindo': ('😴', 'Dormindo'), 'cansado': ('🥱', 'Cansado'), 'carente': ('🥺', 'Carente'),
|
||||||
'entediado': ('😐', 'Entediado'), 'triste': ('😢', 'Triste'), 'feliz': ('😄', 'Feliz'),
|
'entediado': ('😐', 'Entediado'), 'triste': ('😢', 'Triste'), 'bravo': ('😠', 'Mal-humorado'), 'feliz': ('😄', 'Feliz'),
|
||||||
'tranquilo': ('🙂', 'Tranquilo'),
|
'tranquilo': ('🙂', 'Tranquilo'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,18 +68,30 @@ ATOS = {
|
|||||||
'carente_perto': [('acenar', 3, 'Acenou chamando atenção'), ('espiar', 2, 'Espiou quem está aí'),
|
'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')],
|
('coracao', 2, 'Pediu carinho'), ('surpreso', 1, 'Fez cara de "oi!"'), ('timido', 1, 'Ficou tímido')],
|
||||||
'triste': [('triste', 3, 'Ficou tristinho'), ('olhar_baixo', 2, 'Olhou para baixo'), ('pensar', 1, 'Ficou pensativo')],
|
'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'),
|
'cansado': [('bocejar', 3, 'Bocejou'), ('piscar2', 1, 'Piscou devagar'), ('olhar_baixo', 2, 'Cochilou de olho aberto'),
|
||||||
('tonto', 1, 'Ficou meio tonto de sono')],
|
('tonto', 1, 'Ficou meio tonto de sono')],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Atividades (momentos dele): o bonequinho faz algo na tela. Por humor: (atividade, peso, diário, efeito)
|
# Como no The Sims, cada necessidade baixa puxa um comportamento:
|
||||||
ATIVIDADES = {
|
# energia < COCHILO: às vezes tira um cochilo (cena da cama); < SONO: dorme de verdade e o sono é
|
||||||
'feliz': [('cantar', 3, 'Foi cantar'), ('bola', 2, 'Foi jogar bola'), ('malhar', 1, 'Foi malhar')],
|
# respeitado (som, movimento e PIR não acordam) até DESCANSADO; alegria caindo: o rosto vai ficando
|
||||||
'tranquilo': [('ler', 3, 'Foi ler um livro'), ('pintar', 2, 'Foi pintar um quadro'), ('cantar', 1, 'Foi cantar')],
|
# bravo (zanga); diversão < TEDIO: vai fazer uma atividade por alguns minutos.
|
||||||
'entediado': [('videogame', 3, 'Foi jogar videogame'), ('bola', 2, 'Foi jogar bola'),
|
COCHILO, SONO, DESCANSADO, TEDIO = 40, 20, 95, 45
|
||||||
('pintar', 1, 'Foi pintar um quadro'), ('ler', 1, 'Foi ler um livro')],
|
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',
|
||||||
}
|
}
|
||||||
GASTO_ATIVIDADE = {'malhar': 8, 'bola': 5, 'cantar': 3} # energia gasta
|
# 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.
|
# Assuntos das figurinhas que ele inventa sozinho, por humor e por hora do dia.
|
||||||
TEMAS = {
|
TEMAS = {
|
||||||
@@ -88,6 +100,7 @@ TEMAS = {
|
|||||||
'entediado': ['um foguete', 'um planeta com anel', 'uma pipa', 'um dado', 'um controle de videogame', 'um pião'],
|
'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'],
|
'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'],
|
'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'],
|
'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é']),
|
PERIODOS = [(5, 12, 'de manhã', ['um sol nascendo', 'um pãozinho', 'uma xícara de café']),
|
||||||
@@ -115,6 +128,11 @@ class Vida:
|
|||||||
self.celular_online = lambda: False
|
self.celular_online = lambda: False
|
||||||
self.alguem_perto = lambda: False # PIR/movimento recente
|
self.alguem_perto = lambda: False # PIR/movimento recente
|
||||||
self.celular_atividade = lambda: None # atividade que o celular está fazendo agora
|
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.bloqueado = lambda: False # E-Stop ou modo presença com ninguém em casa
|
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.pode_gemini = lambda: bool(os.environ.get('GEMINI_API_KEY'))
|
||||||
self.madrugada = self.noite_padrao # o main troca pelo horário dos ajustes
|
self.madrugada = self.noite_padrao # o main troca pelo horário dos ajustes
|
||||||
@@ -178,6 +196,10 @@ class Vida:
|
|||||||
if not limpo['ligado']:
|
if not limpo['ligado']:
|
||||||
self.enfileirar('humor normal') # o celular volta aos gestos soltos de sempre
|
self.enfileirar('humor normal') # o celular volta aos gestos soltos de sempre
|
||||||
self.enfileirar('ritmo normal') # e à sensibilidade dos Ajustes
|
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
|
self.proximo_ato = time.time() + 5
|
||||||
if self.config['ligado'] and 'ritmo' in limpo:
|
if self.config['ligado'] and 'ritmo' in limpo:
|
||||||
self.enfileirar('ritmo ' + self.config['ritmo'])
|
self.enfileirar('ritmo ' + self.config['ritmo'])
|
||||||
@@ -212,11 +234,17 @@ class Vida:
|
|||||||
if n['diversao'] < 25:
|
if n['diversao'] < 25:
|
||||||
return 'entediado'
|
return 'entediado'
|
||||||
if n['alegria'] < 30:
|
if n['alegria'] < 30:
|
||||||
return 'triste'
|
return 'bravo'
|
||||||
if n['alegria'] >= 65 and n['social'] >= 40 and n['diversao'] >= 40:
|
if n['alegria'] >= 65 and n['social'] >= 40 and n['diversao'] >= 40:
|
||||||
return 'feliz'
|
return 'feliz'
|
||||||
return 'tranquilo'
|
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):
|
def anotar(self, tipo, texto, agora=None):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.diario.insert(0, dict(horario=agora or time.time(), tipo=tipo, texto=texto))
|
self.diario.insert(0, dict(horario=agora or time.time(), tipo=tipo, texto=texto))
|
||||||
@@ -234,6 +262,7 @@ class Vida:
|
|||||||
humor=h, emoji=HUMORES[h][0], nome_humor=HUMORES[h][1], dormindo=self.dormindo(),
|
humor=h, emoji=HUMORES[h][0], nome_humor=HUMORES[h][1], dormindo=self.dormindo(),
|
||||||
nascimento=self.nascimento, idade_dias=int((agora - self.nascimento) // 86400),
|
nascimento=self.nascimento, idade_dias=int((agora - self.nascimento) // 86400),
|
||||||
emojis_hoje=self.emojis_hoje(agora), ultimo_emoji=self.emojis.get('ultimo') or None,
|
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])
|
pode_criar=self.motivo_sem_emoji(agora) or '', diario=self.diario[:30])
|
||||||
|
|
||||||
def avisar(self):
|
def avisar(self):
|
||||||
@@ -244,13 +273,18 @@ class Vida:
|
|||||||
def passar_tempo(self, agora):
|
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
|
minutos = max(0.0, min(10.0, (agora - self.ultimo_tick) / 60)) # pausa longa (placa desligada) não zera tudo
|
||||||
self.ultimo_tick = agora
|
self.ultimo_tick = agora
|
||||||
dormindo = self.dormindo()
|
atividade = self.celular_atividade()
|
||||||
|
dormindo = self.dormindo() or atividade == 'dormir'
|
||||||
with self.lock:
|
with self.lock:
|
||||||
n = self.necessidades
|
n = self.necessidades
|
||||||
for k, q in QUEDA.items():
|
for k, q in QUEDA.items():
|
||||||
n[k] = _limitar(n[k] - q * minutos * (SONO_LENTO if dormindo else 1))
|
n[k] = _limitar(n[k] - q * minutos * (SONO_LENTO if dormindo else 1))
|
||||||
if dormindo:
|
if dormindo:
|
||||||
n['energia'] = _limitar(n['energia'] + RECUPERA_SONO * minutos)
|
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
|
# alegria acompanha devagar a média de companhia e diversão
|
||||||
alvo = (n['social'] + n['diversao']) / 2 + 10
|
alvo = (n['social'] + n['diversao']) / 2 + 10
|
||||||
n['alegria'] = _limitar(n['alegria'] + (alvo - n['alegria']) * min(1.0, 0.05 * minutos))
|
n['alegria'] = _limitar(n['alegria'] + (alvo - n['alegria']) * min(1.0, 0.05 * minutos))
|
||||||
@@ -284,18 +318,46 @@ class Vida:
|
|||||||
self.avisar()
|
self.avisar()
|
||||||
return self.status(agora)
|
return self.status(agora)
|
||||||
|
|
||||||
def dormir(self, agora, motivo):
|
def dormir(self, agora, motivo, profundo=False):
|
||||||
self.dormindo_proprio = True
|
self.dormindo_proprio = True
|
||||||
|
self.dormiu_em = agora
|
||||||
self.enfileirar('dormir')
|
self.enfileirar('dormir')
|
||||||
|
if profundo: # cansado de verdade: sono respeitado até descansar
|
||||||
|
self.sono_profundo = True
|
||||||
|
self.enfileirar('sono profundo')
|
||||||
if motivo:
|
if motivo:
|
||||||
self.anotar('sono', motivo, agora)
|
self.anotar('sono', motivo, agora)
|
||||||
|
|
||||||
def acordar(self, agora, motivo):
|
def acordar(self, agora, motivo):
|
||||||
self.dormindo_proprio = False
|
self.dormindo_proprio = False
|
||||||
|
if self.sono_profundo:
|
||||||
|
self.sono_profundo = False
|
||||||
|
self.enfileirar('sono leve')
|
||||||
self.enfileirar('acordar')
|
self.enfileirar('acordar')
|
||||||
if motivo:
|
if motivo:
|
||||||
self.anotar('sono', motivo, agora)
|
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):
|
def pir_acorda(self, agora=None):
|
||||||
"""Disparo do PIR com o rosto dormindo: acorda? No ritmo calmo precisa de vários em 2 min."""
|
"""Disparo do PIR com o rosto dormindo: acorda? No ritmo calmo precisa de vários em 2 min."""
|
||||||
agora = agora or time.time()
|
agora = agora or time.time()
|
||||||
@@ -376,30 +438,31 @@ class Vida:
|
|||||||
n = self.necessidades
|
n = self.necessidades
|
||||||
perto = self.alguem_perto()
|
perto = self.alguem_perto()
|
||||||
if self.dormindo():
|
if self.dormindo():
|
||||||
# de madrugada nunca acorda sozinho (nem com gente se mexendo na cama); de dia, quando descansou
|
# de madrugada nunca acorda sozinho (nem com gente se mexendo na cama); de dia, só descansado
|
||||||
if n['energia'] >= 95 and not self.madrugada(agora):
|
if n['energia'] >= DESCANSADO and not self.madrugada(agora):
|
||||||
self.acordar(agora, 'Acordou descansado')
|
self.acordar(agora, 'Acordou descansado')
|
||||||
return 'acordar'
|
return 'acordar'
|
||||||
return None
|
return None
|
||||||
if self.config['noite'] and self.madrugada(agora) and agora - self.interagiu_em > PEDIU_ATENCAO:
|
if self.config['noite'] and self.madrugada(agora) and agora - self.interagiu_em > PEDIU_ATENCAO:
|
||||||
self.dormir(agora, 'Ficou tarde, foi dormir')
|
self.dormir(agora, 'Ficou tarde, foi dormir')
|
||||||
return 'dormir'
|
return 'dormir'
|
||||||
if n['energia'] < 20:
|
if n['energia'] < SONO:
|
||||||
self.dormir(agora, 'Ficou cansado e foi tirar um cochilo')
|
self.dormir(agora, 'Ficou exausto e foi dormir', profundo=True)
|
||||||
return 'dormir'
|
return 'dormir'
|
||||||
humor = self.humor()
|
humor = self.humor()
|
||||||
if self.celular_atividade():
|
if self.celular_atividade():
|
||||||
return None # ocupado com uma atividade: deixa terminar
|
return None # ocupado com uma atividade: deixa terminar
|
||||||
# momentos dele: ler, cantar, jogar bola, videogame... (de dia, com o celular online)
|
dia = self.celular_online() and not self.madrugada(agora)
|
||||||
if humor in ATIVIDADES and self.celular_online() and not self.madrugada(agora) and self.rng.random() < 0.18:
|
folga = agora - self.atividade_em > 90 # um respiro entre uma atividade e outra
|
||||||
nome, texto = self.escolher(ATIVIDADES[humor])
|
# cansado: um cochilo na caminha de vez em quando (recupera energia sem o sono pesado)
|
||||||
self.enfileirar('atividade ' + nome)
|
if dia and folga and n['energia'] < COCHILO and self.rng.random() < 0.35:
|
||||||
with self.lock:
|
return self.comecar_atividade(agora, 'dormir', self.rng.uniform(120, 240))
|
||||||
n['diversao'] = _limitar(n['diversao'] + 18)
|
# entediado: vai fazer alguma coisa por alguns minutos (pode ser interrompido)
|
||||||
n['alegria'] = _limitar(n['alegria'] + 5)
|
if dia and folga and n['diversao'] < TEDIO and self.rng.random() < 0.75:
|
||||||
n['energia'] = _limitar(n['energia'] - GASTO_ATIVIDADE.get(nome, 2))
|
return self.comecar_atividade(agora, self.escolher_atividade(), self.rng.uniform(120, 300))
|
||||||
self.anotar('atividade', texto, agora)
|
# bem: de vez em quando, uma atividade curtinha por vontade própria
|
||||||
return 'atividade ' + nome
|
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)
|
# 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 not self.motivo_sem_emoji(agora) and self.celular_online() and self.rng.random() < 0.08:
|
||||||
if self.criar_figurinha(agora, humor):
|
if self.criar_figurinha(agora, humor):
|
||||||
@@ -426,12 +489,22 @@ class Vida:
|
|||||||
return None
|
return None
|
||||||
ato = None
|
ato = None
|
||||||
humor = self.humor()
|
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():
|
if not self.bloqueado():
|
||||||
# o celular escolhe os gestos soltos pelo humor; reenvia de tempos em tempos (pode ter reiniciado)
|
# o celular escolhe os gestos soltos pelo humor; reenvia de tempos em tempos (pode ter reiniciado)
|
||||||
if humor != self.humor_enviado[0] or agora - self.humor_enviado[1] > 60:
|
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('humor ' + humor)
|
||||||
self.enfileirar('ritmo ' + self.config['ritmo'])
|
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.humor_enviado = (humor, agora)
|
||||||
|
self.zanga_enviada = zanga
|
||||||
if agora >= self.proximo_ato:
|
if agora >= self.proximo_ato:
|
||||||
ato = self.agir(agora)
|
ato = self.agir(agora)
|
||||||
lo, hi = RITMOS[self.config['ritmo']]
|
lo, hi = RITMOS[self.config['ritmo']]
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ export const ATIVIDADES_INFO: Record<string, { nome: string; emoji: string }> =
|
|||||||
videogame: { nome: 'Videogame', emoji: '🎮' },
|
videogame: { nome: 'Videogame', emoji: '🎮' },
|
||||||
pintar: { nome: 'Pintar', emoji: '🎨' },
|
pintar: { nome: 'Pintar', emoji: '🎨' },
|
||||||
malhar: { nome: 'Malhar', emoji: '🏋️' },
|
malhar: { nome: 'Malhar', emoji: '🏋️' },
|
||||||
|
dormir: { nome: 'Cochilar', emoji: '🛏️' },
|
||||||
|
cafe: { nome: 'Café', emoji: '☕' },
|
||||||
|
cozinhar: { nome: 'Cozinhar', emoji: '🍳' },
|
||||||
|
regar: { nome: 'Regar planta', emoji: '🪴' },
|
||||||
|
violao: { nome: 'Violão', emoji: '🎸' },
|
||||||
|
dancar: { nome: 'Dançar', emoji: '💃' },
|
||||||
|
corda: { nome: 'Pular corda', emoji: '🪢' },
|
||||||
|
meditar: { nome: 'Meditar', emoji: '🧘' },
|
||||||
|
telescopio: { nome: 'Telescópio', emoji: '🔭' },
|
||||||
|
pipa: { nome: 'Pipa', emoji: '🪁' },
|
||||||
|
bolhas: { nome: 'Bolhas', emoji: '🫧' },
|
||||||
|
skate: { nome: 'Skate', emoji: '🛹' },
|
||||||
|
computador: { nome: 'Computador', emoji: '💻' },
|
||||||
|
pescar: { nome: 'Pescar', emoji: '🎣' },
|
||||||
};
|
};
|
||||||
export const atividade = (nome: string) => tentar(() => postar('/comando', { tipo: 'atividade', valor: nome }));
|
export const atividade = (nome: string) => tentar(() => postar('/comando', { tipo: 'atividade', valor: nome }));
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,9 @@
|
|||||||
<span class="humor" aria-hidden="true">{v.config.ligado ? v.emoji : '🤖'}</span>
|
<span class="humor" aria-hidden="true">{v.config.ligado ? v.emoji : '🤖'}</span>
|
||||||
<div class="quem">
|
<div class="quem">
|
||||||
<strong>{v.config.ligado ? v.nome_humor : 'Modo autônomo desligado'}{#if v.config.ligado}<Ajuda topico="humores" />{/if}</strong>
|
<strong>{v.config.ligado ? v.nome_humor : 'Modo autônomo desligado'}{#if v.config.ligado}<Ajuda topico="humores" />{/if}</strong>
|
||||||
<span>{idade(v.idade_dias)}{v.config.ligado && v.diario[0] ? ' · ' + v.diario[0].texto : ''}</span>
|
<span>
|
||||||
|
{idade(v.idade_dias)}{v.config.ligado && v.sono_profundo ? ' · sono pesado: só acorda descansado' : v.config.ligado && v.diario[0] ? ' · ' + v.diario[0].texto : ''}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Interruptor rotulo="Modo autônomo" checked={v.config.ligado} onchange={(l) => configurarVida({ ligado: l })} />
|
<Interruptor rotulo="Modo autônomo" checked={v.config.ligado} onchange={(l) => configurarVida({ ligado: l })} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+12
-13
@@ -42,12 +42,13 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
|||||||
emoji: '🐣',
|
emoji: '🐣',
|
||||||
onde: 'Ao vivo › Vida',
|
onde: 'Ao vivo › Vida',
|
||||||
resumo:
|
resumo:
|
||||||
'Com o modo vida ligado, o Carinha funciona como um bichinho virtual: tem necessidades que mudam com o tempo e com o que acontece em volta, e decide sozinho o que fazer pelo humor.',
|
'Com o modo vida ligado, o Carinha funciona como um Sim: tem necessidades que caem com o tempo, e cada necessidade baixa puxa um comportamento. Ele decide sozinho, mas você sempre pode interromper.',
|
||||||
itens: [
|
itens: [
|
||||||
{ emoji: '⚡', nome: 'Energia', texto: 'Cai enquanto ele está acordado e enche dormindo. Muito baixa, ele vai tirar um cochilo.' },
|
{ emoji: '⚡', nome: 'Energia', texto: 'Cai acordado e mais rápido nas atividades agitadas; o café dá um pique. Abaixo de 40%, às vezes tira um cochilo na caminha. Abaixo de 20%, dorme de verdade e o sono é respeitado: som, movimento e PIR não acordam até ele chegar a 95%.' },
|
||||||
{ emoji: '😊', nome: 'Alegria', texto: 'Acompanha devagar a companhia e a diversão.' },
|
{ emoji: '😊', nome: 'Alegria', texto: 'Acompanha a companhia e a diversão. Abaixo de 60%, o rosto vai ficando emburrado aos poucos (testa franzida, boca para baixo e, no fim, olhos bravos). Abaixo de 30%, fica mal-humorado.' },
|
||||||
{ emoji: '💬', nome: 'Companhia', texto: 'Sobe quando alguém aparece (PIR), conversa com ele ou faz carinho. Baixa, ele fica carente.' },
|
{ emoji: '💬', nome: 'Companhia', texto: 'Sobe quando alguém aparece (PIR), conversa com ele ou faz carinho. Baixa, ele fica carente.' },
|
||||||
{ emoji: '🎈', nome: 'Diversão', texto: 'Sobe com movimento em volta, brincadeiras e quando ele inventa algo. Baixa, ele fica entediado.' },
|
{ emoji: '🎈', nome: 'Diversão', texto: 'Abaixo de 45%, ele vai fazer uma atividade por 2 a 5 minutos (cansado escolhe coisas calmas; cheio de pique, coisas agitadas). Enquanto faz, a diversão sobe.' },
|
||||||
|
{ emoji: '🛌', nome: 'Sono do celular', texto: 'Com o modo vida ligado, quem decide dormir é a energia; o celular não adormece mais só porque ficou 20 s em silêncio.' },
|
||||||
{ emoji: '🎨', nome: 'Inventar figurinhas sozinho', texto: 'De vez em quando ele tem uma ideia e desenha uma figurinha nova com o Gemini, que vai para a biblioteca de Reações. No máximo 3 por dia, com 90 min entre elas.' },
|
{ emoji: '🎨', nome: 'Inventar figurinhas sozinho', texto: 'De vez em quando ele tem uma ideia e desenha uma figurinha nova com o Gemini, que vai para a biblioteca de Reações. No máximo 3 por dia, com 90 min entre elas.' },
|
||||||
{ emoji: '🌙', nome: 'Dormir à noite', texto: 'Na madrugada ele vai dormir mesmo com gente se mexendo por perto. Carinho ou conversa seguram ele acordado por 10 minutos.' },
|
{ emoji: '🌙', nome: 'Dormir à noite', texto: 'Na madrugada ele vai dormir mesmo com gente se mexendo por perto. Carinho ou conversa seguram ele acordado por 10 minutos.' },
|
||||||
{ emoji: '📖', nome: 'Diário', texto: 'Lista do que ele fez sozinho e dos cuidados que recebeu.' },
|
{ emoji: '📖', nome: 'Diário', texto: 'Lista do que ele fez sozinho e dos cuidados que recebeu.' },
|
||||||
@@ -77,7 +78,7 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
|||||||
{ emoji: '🙂', nome: 'Tranquilo', texto: 'Normal. Olha em volta, pisca devagar, meio sorriso.' },
|
{ emoji: '🙂', nome: 'Tranquilo', texto: 'Normal. Olha em volta, pisca devagar, meio sorriso.' },
|
||||||
{ emoji: '😐', nome: 'Entediado', texto: 'Diversão baixa. Fica pensativo, boca de lado, procura o que fazer e às vezes toca uma reação da biblioteca.' },
|
{ emoji: '😐', nome: 'Entediado', texto: 'Diversão baixa. Fica pensativo, boca de lado, procura o que fazer e às vezes toca uma reação da biblioteca.' },
|
||||||
{ emoji: '🥺', nome: 'Carente', texto: 'Companhia baixa. Com alguém perto, acena e pede carinho; sozinho, fica cabisbaixo.' },
|
{ emoji: '🥺', nome: 'Carente', texto: 'Companhia baixa. Com alguém perto, acena e pede carinho; sozinho, fica cabisbaixo.' },
|
||||||
{ emoji: '😢', nome: 'Triste', texto: 'Alegria baixa. Fica tristinho, olha para baixo e mexe menos.' },
|
{ emoji: '😠', nome: 'Mal-humorado', texto: 'Alegria abaixo de 30%. Testa franzida, olhos bravos, resmunga. Carinho, conversa e brincadeira resolvem.' },
|
||||||
{ emoji: '🥱', nome: 'Cansado', texto: 'Energia baixa. Boceja, suspira e logo vai dormir.' },
|
{ emoji: '🥱', nome: 'Cansado', texto: 'Energia baixa. Boceja, suspira e logo vai dormir.' },
|
||||||
{ emoji: '😴', nome: 'Dormindo', texto: 'Olhos fechados, respira devagar e recupera energia.' },
|
{ emoji: '😴', nome: 'Dormindo', texto: 'Olhos fechados, respira devagar e recupera energia.' },
|
||||||
],
|
],
|
||||||
@@ -168,15 +169,13 @@ export const TOPICOS: Record<string, TopicoGuia> = {
|
|||||||
emoji: '🎮',
|
emoji: '🎮',
|
||||||
onde: 'Ao vivo › Gestos › Atividades',
|
onde: 'Ao vivo › Gestos › Atividades',
|
||||||
resumo:
|
resumo:
|
||||||
'De vez em quando ele faz alguma coisa: os olhos sobem e encolhem, e embaixo aparece um bonequinho dele, na cor dos olhos, fazendo a atividade. Os olhos grandes acompanham (seguem a bola, leem as linhas, olham a TV).',
|
'São 20 atividades. A tela se divide: o rosto vai para a esquerda e à direita aparece um bonequinho dele, na cor dos olhos, fazendo a atividade. Os olhos grandes acompanham (seguem a bola, leem as linhas, olham a TV, a pipa, o peixe).',
|
||||||
itens: [
|
itens: [
|
||||||
{ emoji: '📖', nome: 'Ler', texto: 'Sentado com um livro; vira a página de vez em quando e os olhos leem linha a linha.' },
|
{ emoji: '⚽', nome: 'Agitadas (gastam energia)', texto: 'Jogar bola, malhar, pular corda, skate, dançar (com globo espelhado) e soltar pipa.' },
|
||||||
{ emoji: '🎤', nome: 'Cantar', texto: 'Com microfone, balançando, notas subindo e boca cantando.' },
|
{ emoji: '📖', nome: 'Calmas', texto: 'Ler, tomar café (dá energia), meditar flutuando, olhar estrelas no telescópio, pescar, computador e regar a plantinha (que cresce e dá flor).' },
|
||||||
{ emoji: '⚽', nome: 'Jogar bola', texto: 'Faz embaixadinha; os olhos seguem a bola.' },
|
{ emoji: '🎨', nome: 'Criativas', texto: 'Pintar, tocar violão, cantar, cozinhar (vira a panqueca), bolhas de sabão e videogame.' },
|
||||||
{ emoji: '🎮', nome: 'Videogame', texto: 'Sentado com o controle na frente de uma TV com um joguinho; comemora quando ganha.' },
|
{ emoji: '🛏️', nome: 'Cochilar', texto: 'Deita na caminha com cobertor e Zzz; recupera energia como dormir.' },
|
||||||
{ emoji: '🎨', nome: 'Pintar', texto: 'No cavalete; o quadro vai ganhando pinceladas coloridas.' },
|
{ emoji: '🐣', nome: 'No modo vida', texto: 'Com a diversão baixa, ele escolhe uma pelo pique e fica nela de 2 a 5 minutos; cansado, cochila. Nunca de madrugada.' },
|
||||||
{ emoji: '🏋️', nome: 'Malhar', texto: 'Levanta halteres e sua.' },
|
|
||||||
{ emoji: '🐣', nome: 'No modo vida', texto: 'Ele escolhe sozinho pelo humor: feliz canta e joga bola; tranquilo lê e pinta; entediado joga videogame. Nunca de madrugada.' },
|
|
||||||
{ emoji: '👆', nome: 'Parar', texto: 'Botão Parar no painel ou um toque na tela do celular. Dormir também encerra a atividade.' },
|
{ emoji: '👆', nome: 'Parar', texto: 'Botão Parar no painel ou um toque na tela do celular. Dormir também encerra a atividade.' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ export interface Vida {
|
|||||||
idade_dias: number;
|
idade_dias: number;
|
||||||
emojis_hoje: number;
|
emojis_hoje: number;
|
||||||
ultimo_emoji: number | null;
|
ultimo_emoji: number | null;
|
||||||
|
/** 0 a 1: o quanto está emburrado (alegria baixa). */
|
||||||
|
zanga?: number;
|
||||||
|
/** Dormindo de cansaço: som, movimento e PIR não acordam. */
|
||||||
|
sono_profundo?: boolean;
|
||||||
/** '' quando pode inventar figurinha agora; senão, o motivo. */
|
/** '' quando pode inventar figurinha agora; senão, o motivo. */
|
||||||
pode_criar: string;
|
pode_criar: string;
|
||||||
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida' | 'atividade'; texto: string }[];
|
diario: { horario: number; tipo: 'acao' | 'sono' | 'emoji' | 'cuidado' | 'vida' | 'atividade'; texto: string }[];
|
||||||
|
|||||||
Reference in New Issue
Block a user