From 9e4a3103fd4eeadd363ba741a7d025ee5a5dc61f Mon Sep 17 00:00:00 2001 From: cmena92 Date: Tue, 3 Nov 2026 21:23:41 -0600 Subject: [PATCH] Companion: wanting to eat, play and bathe, growing as you play (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The companion now wants things, and «wishes, never needs» still holds: its ganas grow with the games finished in the whole app, read from the core's stats and never written, and never with time away. One level more every 3 games (eat), 4 (play) and 5 (bathe), up to 3; a year with no games leaves them as they were. At 3 the bubble says it, in words of wanting («All that playing gave me an appetite!»), and the companion opens eager. Any food, play or the soap brings its gana back to 0, with +2 bond when it had reached 2. Left alone, nothing happens. Three small gauges sit under the bond, each with a name for a screen reader. Each gana keeps the game count it was last tended at, saved at once, so games played before this never count, a saved care keeps its bond, and erased stats bring the counts down instead of breaking. --- COMPANERO.md | 2 + tools/test-companero.js | 67 ++++++++++++++++++++++++++++++++-- www/games/companero/cuidado.js | 40 ++++++++++++++++++-- www/games/companero/index.html | 39 ++++++++++++++++---- 4 files changed, 134 insertions(+), 14 deletions(-) diff --git a/COMPANERO.md b/COMPANERO.md index a40e9ae9..6d1b7537 100644 --- a/COMPANERO.md +++ b/COMPANERO.md @@ -85,6 +85,7 @@ Los consumibles no suman a ese total: cuestan de 5 a 15 y son opcionales. - **Vínculo:** +1 por cada cuidado y +3 extra al cumplir el deseo de la visita. Una misma acción gratuita suma como mucho una vez cada 30 segundos, pero se puede hacer siempre. - **Cinco niveles**, a los 0, 10, 25, 50 y 100 puntos. El 100 es el máximo y se muestra desde el principio. - **Un deseo por visita**, distinto del de la visita anterior: probar una comida, un baño de burbujas, algo nuevo para ponerse, jugar o caricias. Primero se elige el tipo, todos igual de probables, y después cuál comida: así las siete comidas no acaparan los deseos. +- **Ganas (#39):** de comer, de jugar y de bañarse, de 0 a 3. Suben una cada 3, 4 y 5 partidas terminadas en toda la app (leídas de las estadísticas del núcleo, que el compañero nunca escribe). **Crecen jugando, nunca por el tiempo fuera:** un año sin jugar las deja como estaban. En 3, el globo lo dice («¡Uf, tanto mirarte jugar me abrió el apetito!») y la cara es antojada. Atenderlas las baja a 0, y si estaban en 2 o más suman +2 de vínculo. Si nadie las atiende no pasa nada. - **Tocarlo:** responde con risa, guiño, sonrojo o un giro, nunca dos veces seguidas igual, y vuelve a mostrar su deseo. Cinco toques en 3 segundos lo marean un momento: es un gesto gracioso, no un castigo. Suma vínculo con la regla de 30 segundos y no da monedas. - **Escenas:** comer lleva la comida a la boca, el jabón da un baño de burbujas y jugar trae una pelota que rebota. Al comer mastica («ñam») y en el baño se sonroja. Con movimiento reducido se ve la misma escena, quieta. @@ -102,6 +103,7 @@ Todo se guarda en el teléfono, bajo `chogan.companero.`: | Decisión | Por qué | |---|---| | Deseos en vez de necesidades | Las mascotas virtuales suelen enganchar con culpa; Capybara promete no hacerlo. | +| Ganas que crecen jugando, no esperando | El compañero reacciona a lo que hacés sin castigar a quien no entra (decisión del 2026-11-03). | | El compañero es una entrada más de `games.json` | El núcleo queda intacto y sincronizar con el original no da conflictos. | | Lógica pura en módulos propios (`economia.js`, `cuidado.js`, `disenos.js`, `prendas.js`, `expresiones.js`) | Se prueba sin navegador, con el reloj y el azar simulados. | | Una sola fuente para la app y el arte | `design/companero/` usa los mismos módulos, así lo aprobado es exactamente lo que se ve. | diff --git a/tools/test-companero.js b/tools/test-companero.js index d58375d9..807d46ae 100644 --- a/tools/test-companero.js +++ b/tools/test-companero.js @@ -318,9 +318,11 @@ function companero(o) { const x = nuevo({ saldo: o.saldo === undefined ? 500 : o.saldo, datos: o.datos }); let t = o.t || 1e12; let dado = o.dado || (() => 0); - const crearCuidado = () => K.crear({ store: x.s, economia: x.eco, ahora: () => t, azar: () => dado() }); + // the games finished in the whole app, as the core counts them (C.stats.get().plays) + let jugadas = o.partidas || 0; + const crearCuidado = () => K.crear({ store: x.s, economia: x.eco, ahora: () => t, azar: () => dado(), partidas: () => jugadas }); const c = crearCuidado(); - return Object.assign(x, { c, crearCuidado, pasar(ms) { t += ms; }, dado(f) { dado = f; } }); + return Object.assign(x, { c, crearCuidado, pasar(ms) { t += ms; }, dado(f) { dado = f; }, jugar(n) { jugadas += n; } }); } function testCuidadoLogica() { @@ -574,7 +576,7 @@ function testDeseosSinCarencia() { zh: ['饿', '伤心', '难过', '脏', '孤单', '寂寞', '想你'], fa: ['گرسنه', 'غمگین', 'ناراحت', 'کثیف', 'تنها', 'دلتنگ'], }; - const claves = (K ? K.DESEOS.map((d) => 'deseo_' + d.id) : []).concat(['deseoCumplido']); + const claves = (K ? K.DESEOS.map((d) => 'deseo_' + d.id) : []).concat(['deseoCumplido', 'gana_comer', 'gana_jugar', 'gana_banio']); for (const l of Object.keys(PROHIBIDAS)) { const textos = claves.map((k) => T[l] && T[l][k]); ok(textos.every((x) => typeof x === 'string' && x.length > 2), l + ': cada deseo tiene su texto'); @@ -1003,6 +1005,65 @@ async function testRopaNueva() { }); } +/* ------------------------------------------------ wanting things, as you play (#39) */ + +function testGanas() { + head('Ganas de comer, jugar y bañarse: crecen jugando, nunca por ausencia (#39)'); + if (!K || !K.GANAS) return ok(false, 'cuidado.js exporta GANAS'); + ok(JSON.stringify(K.GANAS) === JSON.stringify({ comer: 3, jugar: 4, banio: 5 }), 'una gana sube cada 3 partidas (comer), 4 (jugar) y 5 (bañarse)'); + const x = companero({ saldo: 200, partidas: 40 }); + ok(JSON.stringify(x.c.ganas()) === '{"comer":0,"jugar":0,"banio":0}', 'un perfil que ya jugaba empieza con las ganas en 0, no con las partidas de antes'); + x.jugar(3); + ok(x.c.ganas().comer === 1 && x.c.ganas().jugar === 0, 'tras 3 partidas: comer 1, jugar 0'); + x.jugar(2); + ok(JSON.stringify(x.c.ganas()) === '{"comer":1,"jugar":1,"banio":1}', 'tras 5: las tres en 1'); + x.jugar(30); + ok(JSON.stringify(x.c.ganas()) === '{"comer":3,"jugar":3,"banio":3}', 'tras 35: las tres en 3, el tope'); + x.pasar(365 * 864e5); + ok(JSON.stringify(x.crearCuidado().ganas()) === '{"comer":3,"jugar":3,"banio":3}', 'un año después siguen en 3: nunca bajan solas'); + + const v0 = x.c.vinculo().puntos; + x.eco.comprar('galleta'); + const r = x.c.alimentar('galleta'); + ok(x.c.ganas().comer === 0 && x.c.ganas().jugar === 3, 'cualquier comida baja «comer» a 0, y nada más'); + ok(r.atendio === 'comer' && x.c.vinculo().puntos === v0 + 1 + 2, 'atender una gana en 2 o más suma +2 de vínculo extra (' + v0 + ' → ' + x.c.vinculo().puntos + ')'); + x.c.hecho({ accion: 'jugar' }); + x.eco.comprar('jabon'); x.c.alimentar('jabon'); + ok(JSON.stringify(x.c.ganas()) === '{"comer":0,"jugar":0,"banio":0}', 'jugar baja «jugar» y el jabón, «bañarse»'); + x.jugar(3); const v1 = x.c.vinculo().puntos; + x.pasar(31000); x.eco.comprar('fruta'); x.c.alimentar('fruta'); + ok(x.c.vinculo().puntos === v1 + 1, 'una gana en 1 no da el extra'); + + const y = companero({ partidas: 0 }); + y.pasar(365 * 864e5); + ok(JSON.stringify(y.crearCuidado().ganas()) === '{"comer":0,"jugar":0,"banio":0}', 'un año sin jugar: las ganas siguen en 0'); + const z = companero({ partidas: 0 }); + z.jugar(9); + ok(z.c.ganaFuerte() === 'comer' && z.c.expresionAlAbrir() === 'antojado', 'con una gana en 3, abre antojado y la gana fuerte es la que más creció (comer)'); + ok(z.s.escritas.every((k) => k === 'inventario' || k === 'cuidado'), 'las ganas se guardan en «cuidado»: nunca escribe las estadísticas del núcleo'); + // an old saved care, from before #39, keeps its bond + const w = nuevo({ saldo: 10, datos: { cuidado: { version: 1, puntos: 42, ultimas: {}, deseo: null, ultimoDeseo: null } } }); + const cw = K.crear({ store: w.s, economia: w.eco, partidas: () => 80 }); + ok(cw.vinculo().puntos === 42 && JSON.stringify(cw.ganas()) === '{"comer":0,"jugar":0,"banio":0}', 'un cuidado guardado antes de #39 conserva su vínculo y empieza con las ganas en 0'); +} + +async function testGanasPagina() { + head('Las ganas en la página (#39)'); + await navegador(async ({ abrir, evaluar }) => { + await abrir('/games/companero/index.html'); + await evaluar(preparar({ monedas: 100 })); + await evaluar(`localStorage.setItem('chogan.app.stats', JSON.stringify({ plays: 12, timeMs: 0, byGame: {}, streak: { count: 0 } })); localStorage.setItem('chogan.companero.cuidado', JSON.stringify({ version: 1, puntos: 5, ultimas: {}, deseo: null, ultimoDeseo: null, ganas: { comer: 0, jugar: 4, banio: 12 } })); true`); + await abrir('/games/companero/index.html'); + const g = await evaluar(`[...document.querySelectorAll('.cp-gana')].map((x) => x.dataset.gana + ':' + x.dataset.nivel).join()`); + ok(g === 'comer:3,jugar:2,banio:0', 'tres indicadores con su nivel: comer 3, jugar 2, bañarse 0 (' + g + ')'); + ok(await evaluar(`[...document.querySelectorAll('.cp-gana')].every((x) => x.querySelector('svg') && x.getAttribute('aria-label'))`), 'cada indicador tiene su dibujo y un nombre para el lector de pantalla'); + ok(await evaluar(`document.querySelector('.cp-globo').dataset.gana === 'comer'`) && await evaluar(`document.querySelector('.cp-compa').dataset.expresion === 'antojado'`), 'con ganas de comer en 3, el globo lo dice y la cara es antojada'); + await evaluar(`document.querySelector('[data-consumible="fruta"]').click(); true`); await evaluar(pausa(200)); await evaluar(aceptar); await evaluar(pausa(200)); + ok(await evaluar(`document.querySelector('[data-gana="comer"]').dataset.nivel`) === '0', 'al darle de comer, la gana baja a 0'); + ok(!(await evaluar(`localStorage.getItem('chogan.app.stats')`)).includes('companero'), 'las estadísticas del núcleo quedan intactas'); + }); +} + /* ------------------------------------------------ run */ const GROUPS = [...fs.readFileSync(__filename, 'utf8').matchAll(/^(?:async )?function (test[A-Za-z0-9]+)\(\) \{/gm)].map((m) => m[1]); diff --git a/www/games/companero/cuidado.js b/www/games/companero/cuidado.js index d616ab33..e828a21f 100644 --- a/www/games/companero/cuidado.js +++ b/www/games/companero/cuidado.js @@ -35,6 +35,10 @@ var POR_CUIDADO = 1, POR_DESEO = 3; // the same free action adds to the bond at most once in this long; it is never blocked var ESPERA_MS = 30000; + // Wanting things (#39): one level more every this many games finished in the whole app, up to 3. + // Games, never time: a year away with no games leaves them as they were. + var GANAS = { comer: 3, jugar: 4, banio: 5 }; + var TOPE = 3, POR_GANA = 2; function vacio() { return { version: VERSION, puntos: 0, ultimas: {}, deseo: null, ultimoDeseo: null }; } function sano(d) { @@ -58,9 +62,28 @@ var store = opts.store, eco = opts.economia; var ahora = opts.ahora || function () { return Date.now(); }; var azar = opts.azar || Math.random; + var partidas = function () { var n = opts.partidas ? opts.partidas() : 0; return typeof n === 'number' && n >= 0 ? Math.floor(n) : 0; }; var d = store.get('cuidado', null); if (!sano(d)) d = vacio(); + // each gana keeps the game count it was last tended at; a missing one starts now, so the games + // played before #39 never count, and a count above the core's (its stats were erased) comes down + if (!d.ganas || typeof d.ganas !== 'object') d.ganas = {}; + var nuevas = false; + Object.keys(GANAS).forEach(function (g) { + var b = d.ganas[g]; + if (typeof b !== 'number' || b < 0 || b > partidas()) { d.ganas[g] = partidas(); nuevas = true; } + }); function guardar() { store.set('cuidado', d); } + // kept at once: else the next opening would start them over from its own count + if (nuevas) guardar(); + function nivel(g) { return Math.min(TOPE, Math.floor((partidas() - d.ganas[g]) / GANAS[g])); } + function ganas() { var r = {}; Object.keys(GANAS).forEach(function (g) { r[g] = nivel(g); }); return r; } + // the gana at the top, if any, in a fixed order: the bubble says it and the face shows it + function ganaFuerte() { + var gs = Object.keys(GANAS); + for (var i = 0; i < gs.length; i++) if (nivel(gs[i]) >= TOPE) return gs[i]; + return null; + } // A visit is each time the space opens: one wish, never the same as the last visit's function visitar() { @@ -86,9 +109,17 @@ } else if (ev.usar) suma += POR_CUIDADO; var cumplio = !!(d.deseo && !d.deseo.cumplido && cumple(d.deseo, ev)); if (cumplio) { d.deseo.cumplido = true; suma += POR_DESEO; } + // tending a gana brings it back to 0, with a little extra when it had grown + var gana = ev.usar === 'jabon' ? 'banio' : ev.usar ? 'comer' : ev.accion === 'jugar' || ev.juego ? 'jugar' : null; + var atendio = null; + if (gana) { + if (nivel(gana) >= 2) suma += POR_GANA; + if (nivel(gana) > 0) atendio = gana; + d.ganas[gana] = partidas(); + } d.puntos = Math.min(MAXIMO, d.puntos + suma); guardar(); - return { ok: true, suma: suma, cumplioDeseo: cumplio }; + return { ok: true, suma: suma, cumplioDeseo: cumplio, atendio: atendio }; } // Feeding or a bubble bath: uses one unit from the economy, then counts as care @@ -105,10 +136,11 @@ } // The face on opening depends only on the visit's wish, never on time away - function expresionAlAbrir() { return d.deseo && !d.deseo.cumplido ? 'antojado' : 'contento'; } + function expresionAlAbrir() { return (d.deseo && !d.deseo.cumplido) || ganaFuerte() ? 'antojado' : 'contento'; } - return { visitar: visitar, deseo: deseo, hecho: hecho, alimentar: alimentar, vinculo: vinculo, expresionAlAbrir: expresionAlAbrir }; + return { visitar: visitar, deseo: deseo, hecho: hecho, alimentar: alimentar, vinculo: vinculo, expresionAlAbrir: expresionAlAbrir, + ganas: ganas, ganaFuerte: ganaFuerte }; } - return { VERSION: VERSION, DESEOS: DESEOS, ACCIONES: ACCIONES, NIVELES: NIVELES, MAXIMO: MAXIMO, crear: crear }; + return { VERSION: VERSION, DESEOS: DESEOS, ACCIONES: ACCIONES, NIVELES: NIVELES, MAXIMO: MAXIMO, GANAS: GANAS, crear: crear }; }); diff --git a/www/games/companero/index.html b/www/games/companero/index.html index fc9286e7..6cb8c236 100644 --- a/www/games/companero/index.html +++ b/www/games/companero/index.html @@ -47,6 +47,12 @@ @media (prefers-reduced-motion: reduce) { .cp-globo { transition: none; } } .cp-globo[data-cumplido="1"] { border: 2px solid var(--game); } .cp-vinculo { margin: 0; text-align: center; font-size: .9rem; } + /* three small gauges of what it wants (#39): an icon and three dots */ + .cp-ganas { display: flex; gap: var(--s-3); justify-content: center; } + .cp-gana { display: flex; align-items: center; gap: 3px; } + .cp-gana svg { width: 18px; height: 18px; } + .cp-punto { width: 6px; height: 6px; border-radius: 50%; background: var(--c-line); } + .cp-punto--on { background: var(--game); } .cp-corazones { color: #D7263D; letter-spacing: 2px; } .cp-accion { min-height: 56px; justify-content: center; font-weight: 600; } @keyframes cp-salto { 0%, 100% { transform: translateY(0); } 40% { transform: translateY(-8%); } } @@ -89,35 +95,35 @@ /* ==== TEXTOS START ==== */ var TEXTOS = { - fa: { p_diadema: 'تل سر', p_mono: 'پاپیون بزرگ', p_perlas: 'گردنبند مروارید', p_vestido: 'پیراهن گل‌دار', p_gala: 'لباس مهمانی', p_falda: 'دامن', p_zapatillas: 'کفش ورزشی', p_botas: 'چکمه', p_sandalias: 'صندل', e_pies: 'پا', c_sandia: 'هندوانه', c_galleta: 'کلوچه', c_helado: 'بستنی', c_te: 'دمنوش', deseo_sandia: 'دوست دارم هندوانه امتحان کنم!', deseo_galleta: 'یک کلوچه چه خوب می‌شد!', deseo_helado: 'یک بستنی چه کیفی دارد!', deseo_te: 'یک دمنوش بنوشیم؟', verDeseo: 'دیدن آرزویش', acariciar: 'نوازش', cepillar: 'برس زدن', jugar: 'بازی', c_fruta: 'میوه', c_zanahoria: 'هویج', c_pastel: 'کیک', c_jabon: 'صابون حباب', cantidad: '×{n}', vinculo: 'پیوند: سطح {n} از {m}', deseoCumplido: 'ممنون! خیلی دوستش داشتم.', deseo_fruta: 'دوست دارم میوه امتحان کنم!', deseo_zanahoria: 'دوست دارم هویج امتحان کنم!', deseo_pastel: 'یک کیک چه خوب می‌شد!', deseo_banio: 'یک حمام کف چه کیفی دارد!', deseo_ropa: 'با یک لباس تازه قشنگ می‌شوم!', deseo_jugar: 'بیا کمی بازی کنیم!', deseo_caricias: 'نوازشم می‌کنی؟', + fa: { gana_comer: 'این همه بازی، اشتهایم را باز کرد!', gana_jugar: 'دیدن بازی‌ات، من را هم هوس بازی انداخت!', gana_banio: 'یک حمام کف الان چه می‌چسبد!', ganaNivel_comer: 'میل به خوردن: {n} از ۳', ganaNivel_jugar: 'میل به بازی: {n} از ۳', ganaNivel_banio: 'میل به حمام: {n} از ۳', p_diadema: 'تل سر', p_mono: 'پاپیون بزرگ', p_perlas: 'گردنبند مروارید', p_vestido: 'پیراهن گل‌دار', p_gala: 'لباس مهمانی', p_falda: 'دامن', p_zapatillas: 'کفش ورزشی', p_botas: 'چکمه', p_sandalias: 'صندل', e_pies: 'پا', c_sandia: 'هندوانه', c_galleta: 'کلوچه', c_helado: 'بستنی', c_te: 'دمنوش', deseo_sandia: 'دوست دارم هندوانه امتحان کنم!', deseo_galleta: 'یک کلوچه چه خوب می‌شد!', deseo_helado: 'یک بستنی چه کیفی دارد!', deseo_te: 'یک دمنوش بنوشیم؟', verDeseo: 'دیدن آرزویش', acariciar: 'نوازش', cepillar: 'برس زدن', jugar: 'بازی', c_fruta: 'میوه', c_zanahoria: 'هویج', c_pastel: 'کیک', c_jabon: 'صابون حباب', cantidad: '×{n}', vinculo: 'پیوند: سطح {n} از {m}', deseoCumplido: 'ممنون! خیلی دوستش داشتم.', deseo_fruta: 'دوست دارم میوه امتحان کنم!', deseo_zanahoria: 'دوست دارم هویج امتحان کنم!', deseo_pastel: 'یک کیک چه خوب می‌شد!', deseo_banio: 'یک حمام کف چه کیفی دارد!', deseo_ropa: 'با یک لباس تازه قشنگ می‌شوم!', deseo_jugar: 'بیا کمی بازی کنیم!', deseo_caricias: 'نوازشم می‌کنی؟', p_gorra: 'کلاه', p_corona: 'تاج', p_lentes: 'عینک', p_lazo: 'پاپیون', p_panuelo: 'دستمال گردن', p_sueter: 'پلیور', p_capa: 'شنل', e_cabeza: 'سر', e_cuello: 'گردن', e_cuerpo: 'بدن', ninguna: 'هیچ‌کدام', ariaPrecio: '{nombre}، {precio}', ariaTuyo: '{nombre}، مال توست', ariaEnUso: '{nombre}، در حال استفاده', ariaPerfil: '{nombre}، در نمایه باز می‌شود', d_natural: 'طبیعی', d_medianoche: 'نیمه‌شب', d_arcoiris: 'رنگین‌کمان', d_manchitas: 'خال‌خالی', d_rayas: 'راه‌راه', d_dorado: 'طلایی', desbloquear: 'با {n} سکه باز شود؟', desbloquearBtn: 'باز کردن', faltan: '{n} سکه کم داری', jugarParaGanar: 'یک بازی کن تا سکه بگیری.', enPerfil: 'در نمایه باز می‌شود', precio: '{n} سکه', tuyo: 'مال توست', monedas: 'سکه', animales: 'حیوان‌ها', colores: 'رنگ‌ها', ropa: 'لباس‌ها', cuidado: 'مراقبت', cargando: 'همراهت را آماده می‌کنیم…', vacio: 'هنوز اینجا چیزی نیست.', sinMonedas: 'هنوز سکه نداری. یک بازی کن تا سکه بگیری.', equipado: 'در حال استفاده', a_fox: 'روباه', a_owl: 'جغد', a_cat: 'گربه', a_bear: 'خرس', a_capybara: 'کاپیبارا', a_conejo: 'خرگوش', a_panda: 'پاندا', a_nutria: 'سمور آبی' }, - en: { p_diadema: 'Headband', p_mono: 'Big bow', p_perlas: 'Pearls', p_vestido: 'Flower dress', p_gala: 'Party dress', p_falda: 'Skirt', p_zapatillas: 'Sneakers', p_botas: 'Boots', p_sandalias: 'Sandals', e_pies: 'Feet', c_sandia: 'Watermelon', c_galleta: 'Cookie', c_helado: 'Ice cream', c_te: 'Herbal tea', deseo_sandia: 'I would love to try some watermelon!', deseo_galleta: 'A cookie would be lovely!', deseo_helado: 'I would love an ice cream!', deseo_te: 'Shall we have some herbal tea?', verDeseo: 'See its wish', acariciar: 'Pet', cepillar: 'Brush', jugar: 'Play', c_fruta: 'Fruit', c_zanahoria: 'Carrot', c_pastel: 'Cake', c_jabon: 'Bubble soap', cantidad: '×{n}', vinculo: 'Bond: level {n} of {m}', deseoCumplido: 'Thank you! I loved it.', deseo_fruta: 'I would love to try some fruit!', deseo_zanahoria: 'I would love to try the carrot!', deseo_pastel: 'A cake would be lovely!', deseo_banio: 'I would love a bubble bath!', deseo_ropa: 'Something new to wear would look nice on me!', deseo_jugar: 'Let us play for a bit!', deseo_caricias: 'Could I have a cuddle?', + en: { gana_comer: 'All that playing gave me an appetite!', gana_jugar: 'Watching you play makes me want to play too!', gana_banio: 'A bubble bath would feel so nice!', ganaNivel_comer: 'Wants to eat: {n} of 3', ganaNivel_jugar: 'Wants to play: {n} of 3', ganaNivel_banio: 'Wants a bath: {n} of 3', p_diadema: 'Headband', p_mono: 'Big bow', p_perlas: 'Pearls', p_vestido: 'Flower dress', p_gala: 'Party dress', p_falda: 'Skirt', p_zapatillas: 'Sneakers', p_botas: 'Boots', p_sandalias: 'Sandals', e_pies: 'Feet', c_sandia: 'Watermelon', c_galleta: 'Cookie', c_helado: 'Ice cream', c_te: 'Herbal tea', deseo_sandia: 'I would love to try some watermelon!', deseo_galleta: 'A cookie would be lovely!', deseo_helado: 'I would love an ice cream!', deseo_te: 'Shall we have some herbal tea?', verDeseo: 'See its wish', acariciar: 'Pet', cepillar: 'Brush', jugar: 'Play', c_fruta: 'Fruit', c_zanahoria: 'Carrot', c_pastel: 'Cake', c_jabon: 'Bubble soap', cantidad: '×{n}', vinculo: 'Bond: level {n} of {m}', deseoCumplido: 'Thank you! I loved it.', deseo_fruta: 'I would love to try some fruit!', deseo_zanahoria: 'I would love to try the carrot!', deseo_pastel: 'A cake would be lovely!', deseo_banio: 'I would love a bubble bath!', deseo_ropa: 'Something new to wear would look nice on me!', deseo_jugar: 'Let us play for a bit!', deseo_caricias: 'Could I have a cuddle?', p_gorra: 'Cap', p_corona: 'Crown', p_lentes: 'Glasses', p_lazo: 'Bow', p_panuelo: 'Bandana', p_sueter: 'Sweater', p_capa: 'Cape', e_cabeza: 'Head', e_cuello: 'Neck', e_cuerpo: 'Body', ninguna: 'None', ariaPrecio: '{nombre}, {precio}', ariaTuyo: '{nombre}, yours', ariaEnUso: '{nombre}, in use', ariaPerfil: '{nombre}, unlocked in your profile', d_natural: 'Natural', d_medianoche: 'Midnight', d_arcoiris: 'Soft rainbow', d_manchitas: 'Spots', d_rayas: 'Stripes', d_dorado: 'Golden', desbloquear: 'Unlock for {n} coins?', desbloquearBtn: 'Unlock', faltan: 'You need {n} more coins', jugarParaGanar: 'Play a game to earn some.', enPerfil: 'Unlocked in your profile', precio: '{n} coins', tuyo: 'Yours', monedas: 'Coins', animales: 'Animals', colores: 'Colours', ropa: 'Clothes', cuidado: 'Care', cargando: 'Getting your companion ready…', vacio: 'Nothing here yet.', sinMonedas: 'You have no coins yet. Play a game to earn some.', equipado: 'In use', a_fox: 'Fox', a_owl: 'Owl', a_cat: 'Cat', a_bear: 'Bear', a_capybara: 'Capybara', a_conejo: 'Rabbit', a_panda: 'Panda', a_nutria: 'Otter' }, - zh: { p_diadema: '发箍', p_mono: '大蝴蝶结', p_perlas: '珍珠项链', p_vestido: '碎花裙', p_gala: '礼服裙', p_falda: '短裙', p_zapatillas: '运动鞋', p_botas: '靴子', p_sandalias: '凉鞋', e_pies: '脚', c_sandia: '西瓜', c_galleta: '饼干', c_helado: '冰淇淋', c_te: '花草茶', deseo_sandia: '我想尝尝西瓜!', deseo_galleta: '来块饼干就太好了!', deseo_helado: '我好想吃冰淇淋!', deseo_te: '我们来喝杯花草茶吧?', verDeseo: '看看它的心愿', acariciar: '抚摸', cepillar: '梳毛', jugar: '玩耍', c_fruta: '水果', c_zanahoria: '胡萝卜', c_pastel: '蛋糕', c_jabon: '泡泡皂', cantidad: '×{n}', vinculo: '亲密度:第 {n} 级,共 {m} 级', deseoCumplido: '谢谢!我好喜欢。', deseo_fruta: '我想尝尝水果!', deseo_zanahoria: '我想尝尝胡萝卜!', deseo_pastel: '来块蛋糕就太好了!', deseo_banio: '我想泡个泡泡浴!', deseo_ropa: '穿点新衣服一定很好看!', deseo_jugar: '我们来玩一会儿吧!', deseo_caricias: '可以摸摸我吗?', + zh: { gana_comer: '看你玩得这么开心,我也想吃点东西啦!', gana_jugar: '看你玩,我也想玩啦!', gana_banio: '好想泡个泡泡浴呀!', ganaNivel_comer: '想吃东西:{n}/3', ganaNivel_jugar: '想玩耍:{n}/3', ganaNivel_banio: '想洗澡:{n}/3', p_diadema: '发箍', p_mono: '大蝴蝶结', p_perlas: '珍珠项链', p_vestido: '碎花裙', p_gala: '礼服裙', p_falda: '短裙', p_zapatillas: '运动鞋', p_botas: '靴子', p_sandalias: '凉鞋', e_pies: '脚', c_sandia: '西瓜', c_galleta: '饼干', c_helado: '冰淇淋', c_te: '花草茶', deseo_sandia: '我想尝尝西瓜!', deseo_galleta: '来块饼干就太好了!', deseo_helado: '我好想吃冰淇淋!', deseo_te: '我们来喝杯花草茶吧?', verDeseo: '看看它的心愿', acariciar: '抚摸', cepillar: '梳毛', jugar: '玩耍', c_fruta: '水果', c_zanahoria: '胡萝卜', c_pastel: '蛋糕', c_jabon: '泡泡皂', cantidad: '×{n}', vinculo: '亲密度:第 {n} 级,共 {m} 级', deseoCumplido: '谢谢!我好喜欢。', deseo_fruta: '我想尝尝水果!', deseo_zanahoria: '我想尝尝胡萝卜!', deseo_pastel: '来块蛋糕就太好了!', deseo_banio: '我想泡个泡泡浴!', deseo_ropa: '穿点新衣服一定很好看!', deseo_jugar: '我们来玩一会儿吧!', deseo_caricias: '可以摸摸我吗?', p_gorra: '鸭舌帽', p_corona: '王冠', p_lentes: '眼镜', p_lazo: '蝴蝶结', p_panuelo: '领巾', p_sueter: '毛衣', p_capa: '斗篷', e_cabeza: '头部', e_cuello: '颈部', e_cuerpo: '身体', ninguna: '不穿', ariaPrecio: '{nombre},{precio}', ariaTuyo: '{nombre},已拥有', ariaEnUso: '{nombre},使用中', ariaPerfil: '{nombre},在个人资料中解锁', d_natural: '自然', d_medianoche: '午夜', d_arcoiris: '柔和彩虹', d_manchitas: '斑点', d_rayas: '条纹', d_dorado: '金色', desbloquear: '花 {n} 金币解锁?', desbloquearBtn: '解锁', faltan: '还差 {n} 金币', jugarParaGanar: '玩一局就能赚到。', enPerfil: '在个人资料中解锁', precio: '{n} 金币', tuyo: '已拥有', monedas: '金币', animales: '动物', colores: '颜色', ropa: '服装', cuidado: '照顾', cargando: '正在准备你的伙伴……', vacio: '这里还没有东西。', sinMonedas: '你还没有金币。玩一局就能赚到。', equipado: '使用中', a_fox: '狐狸', a_owl: '猫头鹰', a_cat: '猫', a_bear: '熊', a_capybara: '水豚', a_conejo: '兔子', a_panda: '熊猫', a_nutria: '水獭' }, - de: { p_diadema: 'Haarreif', p_mono: 'Große Schleife', p_perlas: 'Perlen', p_vestido: 'Blumen\u00ADkleid', p_gala: 'Fest\u00ADkleid', p_falda: 'Rock', p_zapatillas: 'Turn\u00ADschuhe', p_botas: 'Stiefel', p_sandalias: 'Sandalen', e_pies: 'Füße', c_sandia: 'Wasser\u00ADmelone', c_galleta: 'Keks', c_helado: 'Eis', c_te: 'Kräuter\u00ADtee', deseo_sandia: 'Ich würde gern Wassermelone probieren!', deseo_galleta: 'Ein Keks wäre toll!', deseo_helado: 'Ein Eis wäre herrlich!', deseo_te: 'Trinken wir einen Kräutertee?', verDeseo: 'Seinen Wunsch zeigen', acariciar: 'Streicheln', cepillar: 'Bürsten', jugar: 'Spielen', c_fruta: 'Obst', c_zanahoria: 'Karotte', c_pastel: 'Kuchen', c_jabon: 'Schaumseife', cantidad: '×{n}', vinculo: 'Bindung: Stufe {n} von {m}', deseoCumplido: 'Danke! Das hat mir gefallen.', deseo_fruta: 'Ich würde gern ein Stück Obst probieren!', deseo_zanahoria: 'Ich würde gern die Karotte probieren!', deseo_pastel: 'Ein Kuchen wäre toll!', deseo_banio: 'Ein Schaumbad wäre herrlich!', deseo_ropa: 'Etwas Neues zum Anziehen stünde mir gut!', deseo_jugar: 'Lass uns ein bisschen spielen!', deseo_caricias: 'Streichelst du mich ein wenig?', + de: { gana_comer: 'Vom Zuschauen hab ich Appetit bekommen!', gana_jugar: 'Beim Zuschauen will ich auch spielen!', gana_banio: 'Ein Schaumbad wäre jetzt schön!', ganaNivel_comer: 'Lust auf Essen: {n} von 3', ganaNivel_jugar: 'Lust zu spielen: {n} von 3', ganaNivel_banio: 'Lust auf ein Bad: {n} von 3', p_diadema: 'Haarreif', p_mono: 'Große Schleife', p_perlas: 'Perlen', p_vestido: 'Blumen\u00ADkleid', p_gala: 'Fest\u00ADkleid', p_falda: 'Rock', p_zapatillas: 'Turn\u00ADschuhe', p_botas: 'Stiefel', p_sandalias: 'Sandalen', e_pies: 'Füße', c_sandia: 'Wasser\u00ADmelone', c_galleta: 'Keks', c_helado: 'Eis', c_te: 'Kräuter\u00ADtee', deseo_sandia: 'Ich würde gern Wassermelone probieren!', deseo_galleta: 'Ein Keks wäre toll!', deseo_helado: 'Ein Eis wäre herrlich!', deseo_te: 'Trinken wir einen Kräutertee?', verDeseo: 'Seinen Wunsch zeigen', acariciar: 'Streicheln', cepillar: 'Bürsten', jugar: 'Spielen', c_fruta: 'Obst', c_zanahoria: 'Karotte', c_pastel: 'Kuchen', c_jabon: 'Schaumseife', cantidad: '×{n}', vinculo: 'Bindung: Stufe {n} von {m}', deseoCumplido: 'Danke! Das hat mir gefallen.', deseo_fruta: 'Ich würde gern ein Stück Obst probieren!', deseo_zanahoria: 'Ich würde gern die Karotte probieren!', deseo_pastel: 'Ein Kuchen wäre toll!', deseo_banio: 'Ein Schaumbad wäre herrlich!', deseo_ropa: 'Etwas Neues zum Anziehen stünde mir gut!', deseo_jugar: 'Lass uns ein bisschen spielen!', deseo_caricias: 'Streichelst du mich ein wenig?', p_gorra: 'Kappe', p_corona: 'Krone', p_lentes: 'Brille', p_lazo: 'Schleife', p_panuelo: 'Halstuch', p_sueter: 'Pullover', p_capa: 'Umhang', e_cabeza: 'Kopf', e_cuello: 'Hals', e_cuerpo: 'Körper', ninguna: 'Nichts', ariaPrecio: '{nombre}, {precio}', ariaTuyo: '{nombre}, gehört dir', ariaEnUso: '{nombre}, in Gebrauch', ariaPerfil: '{nombre}, im Profil freischaltbar', d_natural: 'Natur', d_medianoche: 'Mitternacht', d_arcoiris: 'Sanfter Regenbogen', d_manchitas: 'Tupfen', d_rayas: 'Streifen', d_dorado: 'Golden', desbloquear: 'Für {n} Münzen freischalten?', desbloquearBtn: 'Freischalten', faltan: 'Dir fehlen {n} Münzen', jugarParaGanar: 'Spiel eine Runde, um welche zu verdienen.', enPerfil: 'Im Profil freischaltbar', precio: '{n} Münzen', tuyo: 'Deins', monedas: 'Münzen', animales: 'Tiere', colores: 'Farben', ropa: 'Kleidung', cuidado: 'Pflege', cargando: 'Dein Begleiter wird vorbereitet …', vacio: 'Hier ist noch nichts.', sinMonedas: 'Du hast noch keine Münzen. Spiel eine Runde, um welche zu verdienen.', equipado: 'In Gebrauch', a_fox: 'Fuchs', a_owl: 'Eule', a_cat: 'Katze', a_bear: 'Bär', a_capybara: 'Wasser\u00ADschwein', a_conejo: 'Hase', a_panda: 'Panda', a_nutria: 'Otter' }, - es: { p_diadema: 'Diadema', p_mono: 'Moño', p_perlas: 'Perlas', p_vestido: 'Vestido de flores', p_gala: 'Vestido de fiesta', p_falda: 'Falda', p_zapatillas: 'Zapatillas', p_botas: 'Botas', p_sandalias: 'Sandalias', e_pies: 'Pies', c_sandia: 'Sandía', c_galleta: 'Galleta', c_helado: 'Helado', c_te: 'Té de hierbas', deseo_sandia: '¡Me gustaría probar la sandía!', deseo_galleta: '¡Qué rica sería una galleta!', deseo_helado: '¡Me encantaría un helado!', deseo_te: '¿Tomamos un té de hierbas?', verDeseo: 'Ver su deseo', acariciar: 'Acariciar', cepillar: 'Cepillar', jugar: 'Jugar', c_fruta: 'Fruta', c_zanahoria: 'Zanahoria', c_pastel: 'Pastel', c_jabon: 'Jabón de burbujas', cantidad: '×{n}', vinculo: 'Vínculo: nivel {n} de {m}', deseoCumplido: '¡Gracias! Me encantó.', deseo_fruta: '¡Me gustaría probar una fruta!', deseo_zanahoria: '¡Me gustaría probar la zanahoria!', deseo_pastel: '¡Qué rico sería un pastel!', deseo_banio: '¡Me encantaría un baño de burbujas!', deseo_ropa: '¡Me vería bien con algo nuevo!', deseo_jugar: '¡Juguemos un rato!', deseo_caricias: '¿Me das unas caricias?', + es: { gana_comer: '¡Uf, tanto mirarte jugar me abrió el apetito!', gana_jugar: '¡Mirarte jugar me dio ganas de jugar a mí!', gana_banio: '¡Qué bien me vendría un baño de burbujas!', ganaNivel_comer: 'Ganas de comer: {n} de 3', ganaNivel_jugar: 'Ganas de jugar: {n} de 3', ganaNivel_banio: 'Ganas de un baño: {n} de 3', p_diadema: 'Diadema', p_mono: 'Moño', p_perlas: 'Perlas', p_vestido: 'Vestido de flores', p_gala: 'Vestido de fiesta', p_falda: 'Falda', p_zapatillas: 'Zapatillas', p_botas: 'Botas', p_sandalias: 'Sandalias', e_pies: 'Pies', c_sandia: 'Sandía', c_galleta: 'Galleta', c_helado: 'Helado', c_te: 'Té de hierbas', deseo_sandia: '¡Me gustaría probar la sandía!', deseo_galleta: '¡Qué rica sería una galleta!', deseo_helado: '¡Me encantaría un helado!', deseo_te: '¿Tomamos un té de hierbas?', verDeseo: 'Ver su deseo', acariciar: 'Acariciar', cepillar: 'Cepillar', jugar: 'Jugar', c_fruta: 'Fruta', c_zanahoria: 'Zanahoria', c_pastel: 'Pastel', c_jabon: 'Jabón de burbujas', cantidad: '×{n}', vinculo: 'Vínculo: nivel {n} de {m}', deseoCumplido: '¡Gracias! Me encantó.', deseo_fruta: '¡Me gustaría probar una fruta!', deseo_zanahoria: '¡Me gustaría probar la zanahoria!', deseo_pastel: '¡Qué rico sería un pastel!', deseo_banio: '¡Me encantaría un baño de burbujas!', deseo_ropa: '¡Me vería bien con algo nuevo!', deseo_jugar: '¡Juguemos un rato!', deseo_caricias: '¿Me das unas caricias?', p_gorra: 'Gorra', p_corona: 'Corona', p_lentes: 'Lentes', p_lazo: 'Lazo', p_panuelo: 'Pañuelo', p_sueter: 'Suéter', p_capa: 'Capa', e_cabeza: 'Cabeza', e_cuello: 'Cuello', e_cuerpo: 'Cuerpo', ninguna: 'Ninguna', ariaPrecio: '{nombre}, {precio}', ariaTuyo: '{nombre}, tuyo', ariaEnUso: '{nombre}, en uso', ariaPerfil: '{nombre}, se desbloquea en tu perfil', d_natural: 'Natural', d_medianoche: 'Medianoche', d_arcoiris: 'Arcoíris suave', d_manchitas: 'Manchitas', d_rayas: 'Rayas', d_dorado: 'Dorado', desbloquear: '¿Desbloquear por {n} monedas?', desbloquearBtn: 'Desbloquear', faltan: 'Te faltan {n} monedas', jugarParaGanar: 'Juega una partida para ganarlas.', enPerfil: 'Se desbloquea en tu perfil', precio: '{n} monedas', tuyo: 'Tuyo', @@ -147,7 +153,9 @@ nucleo: function () { return { unlocks: C.state.unlocks, mascot: C.state.settings.mascot }; } }); var ARTE = window.CompaneroArte; - var K = window.CompaneroCuidado.crear({ store: ctx.store, economia: E }); + // the games finished in the whole app, read from the core and never written (#39) + var K = window.CompaneroCuidado.crear({ store: ctx.store, economia: E, + partidas: function () { var st = C.stats && C.stats.get ? C.stats.get() : null; return st && st.plays; } }); K.visitar(); // each opening is a visit: one kind wish, never the last visit's var CONSUMIBLES = window.CompaneroEconomia.CATALOGO.filter(function (a) { return a.tipo === 'consumible'; }).map(function (a) { return a.id; }); // the face of the moment: the opening one, or a short-lived one after something happens @@ -204,6 +212,13 @@ compa.appendChild(globo); compa.appendChild(lienzo); compa.appendChild(vinculo); + var ganasEl = el('div', { class: 'cp-ganas' }); + compa.appendChild(ganasEl); + var ICONO_GANA = { + comer: '', + jugar: '', + banio: '' + }; var raiz = el('div', { class: 'cp' }, [ el('div', { class: 'cp-escenario' }, [estado, compa]), sinMonedas, @@ -224,12 +239,22 @@ lienzo.innerHTML = dibujo; compa.dataset.expresion = cara; - var deseo = K.deseo(); + var deseo = K.deseo(), fuerte = K.ganaFuerte(); if (deseo) { globo.dataset.deseo = deseo.id; globo.dataset.cumplido = deseo.cumplido ? '1' : '0'; globo.textContent = deseo.cumplido ? C.t('deseoCumplido') : C.t('deseo_' + deseo.id); } + // a gana at its top speaks instead of the visit's wish, which stays as it is + globo.dataset.gana = fuerte || ''; + if (fuerte && !(deseo && deseo.cumplido)) globo.textContent = C.t('gana_' + fuerte); + var gs = K.ganas(); + ganasEl.textContent = ''; + Object.keys(gs).forEach(function (g) { + var x = el('span', { class: 'cp-gana', 'data-gana': g, 'data-nivel': String(gs[g]), role: 'img', 'aria-label': C.t('ganaNivel_' + g, { n: C.num(gs[g]) }), html: ICONO_GANA[g] }); + for (var i = 0; i < 3; i++) x.appendChild(el('span', { class: 'cp-punto' + (i < gs[g] ? ' cp-punto--on' : '') })); + ganasEl.appendChild(x); + }); var v = K.vinculo(); vinculo.dataset.vinculo = String(v.puntos); vinculo.textContent = '';