Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion COMPANERO.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ Las compras pasan por `C.coins.spend`, que solo acepta precios enteros y positiv
| `fruta` | consumible | comida | 5 |
| `zanahoria` | consumible | comida | 8 |
| `pastel` | consumible | comida | 15 |
| `sandia` | consumible | comida | 6 |
| `galleta` | consumible | comida | 7 |
| `helado` | consumible | comida | 12 |
| `te` | consumible | té de hierbas | 9 |
| `jabon` | consumible | baño de burbujas | 10 |

Los cuatro personajes del original (zorro, búho, gato y oso) también son animales del compañero. Se compran en la tienda del Perfil, no aquí, y el compañero solo lee si ya son tuyos.
Expand All @@ -69,7 +73,8 @@ Los consumibles no suman a ese total: cuestan de 5 a 15 y son opcionales.
- **Gratis:** acariciar, cepillar y jugar.
- **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.
- **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.
- **Escenas:** comer lleva la comida a la boca, el jabón da un baño de burbujas y jugar trae una pelota que rebota. Con movimiento reducido se ve la misma escena, quieta.

## Datos

Expand Down
3 changes: 3 additions & 0 deletions design/companero/comidas/fruta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/galleta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/helado.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/jabon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/pastel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/sandia.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/te.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions design/companero/comidas/zanahoria.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions design/companero/escenas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"nota": "Escenas de cuidado (#32): comer, bañarse y jugar, encima del compañero ya vestido. boca: el punto adonde llega la comida (la boca, el pico del búho o los dientes del conejo). burbujas y pelota: comunes a los ocho, en el marco de 120 x 170 y fuera de la zona de rasgos. comidas: los consumibles que se comen; el jabón es el baño.",
"bocas": {
"fox": [60, 83],
"owl": [60, 70],
"cat": [60, 81],
"bear": [60, 83],
"capybara": [60, 85],
"conejo": [60, 86],
"panda": [60, 83],
"nutria": [60, 86]
},
"comidas": [
"fruta",
"zanahoria",
"pastel",
"sandia",
"galleta",
"helado",
"te"
],
"burbujas": [
[22, 104, 8],
[98, 100, 7],
[16, 138, 6],
[103, 134, 9],
[24, 84, 4.5],
[86, 156, 6],
[62, 110, 4],
[30, 160, 5],
[96, 80, 5]
],
"pelota": [100, 149, 12]
}
83 changes: 83 additions & 0 deletions design/companero/escenas.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Puts the care scenes of #32 (eating, a bubble bath, playing) on the eight companions, dressed,
// and checks every animal and scene in headless Chrome: nothing may leave the 120 x 170 frame, the
// bubbles and the ball may not cover the features zone, and the food must rest by the mouth.
// Usage: node design/companero/escenas.mjs (CHROME=/path/to/chrome to pick a browser)
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";
import { anclajes, chrome, componer } from "./prendas.mjs";

const here = path.dirname(new URL(import.meta.url).pathname);
const leer = (...p) => fs.readFileSync(path.join(here, ...p), "utf8");
const json = JSON.parse(leer("escenas.json"));

// The logic itself lives in the app (www/games/companero/escenas.js), one source for both
const require = createRequire(import.meta.url);
const app = require("../../www/games/companero/escenas.js");
export const comidas = [...json.comidas, "jabon"];
export const datosEscenas = {
bocas: json.bocas,
burbujas: json.burbujas,
pelota: json.pelota,
comidas: Object.fromEntries(comidas.map((id) => [id, leer("comidas", id + ".svg")])),
};
export function escena(svg, animal, tipo, comida) {
return app.escena(svg, animal, tipo, datosEscenas, { comida });
}

// Every animal with each food eaten, the bath and the ball, dressed so the scene sits over clothes
export const casos = Object.keys(anclajes).flatMap((a) => [
...json.comidas.map((c) => [a, "comer", c]),
[a, "banio"],
[a, "jugar"],
]);
const vestido = (a) => componer(a, ["gorra", "lazo", "sueter"]);

export function comprobar() {
const html = `<!doctype html><body style="margin:0">${casos
.map(([a, t, c]) => `<div data-a="${a}" data-t="${t}" style="width:120px;height:170px">${escena(vestido(a), a, t, c)}</div>`)
.join("")}<pre id="r"></pre><script>
const out = [];
for (const d of document.querySelectorAll("div[data-a]")) {
const svg = d.querySelector("svg").getBoundingClientRect();
const capa = d.querySelector("[data-escena]");
const partes = capa ? [...capa.children] : [];
out.push({ a: d.dataset.a, t: d.dataset.t, hay: !!capa, partes: partes.map((g) => { const b = g.getBoundingClientRect(); return { x0: b.left - svg.left, y0: b.top - svg.top, x1: b.right - svg.left, y1: b.bottom - svg.top }; }) });
}
document.getElementById("r").textContent = JSON.stringify(out);
</script>`;
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "escenas-"));
const archivo = path.join(dir, "comprobar.html");
fs.writeFileSync(archivo, html);
const r = spawnSync(chrome(), ["--headless=new", "--no-sandbox", `--user-data-dir=${dir}/perfil`, "--virtual-time-budget=3000", "--dump-dom", "file://" + archivo], { encoding: "utf8" });
fs.rmSync(dir, { recursive: true, force: true });
const m = r.stdout.match(/<pre id="r">([\s\S]*?)<\/pre>/);
if (!m) throw new Error("the browser returned no measurements");
const medidas = JSON.parse(m[1].replace(/&quot;/g, '"').replace(/&amp;/g, "&"));

const fallas = [];
const corta = (b, z) => b.x0 < z.x1 && z.x0 < b.x1 && b.y0 < z.y1 && z.y0 < b.y1;
for (const s of medidas) {
const nombre = `${s.a} + ${s.t}`;
if (!s.hay || !s.partes.length) { fallas.push(`${nombre}: no dibuja la escena`); continue; }
for (const b of s.partes) {
if (b.x0 < -0.5 || b.y0 < -0.5 || b.x1 > 120.5 || b.y1 > 170.5) fallas.push(`${nombre}: se sale del marco`);
if (s.t !== "comer" && corta(b, anclajes[s.a].rasgos)) fallas.push(`${nombre}: cubre la zona de rasgos`);
}
if (s.t === "comer") {
const b = s.partes[0], [bx, by] = json.bocas[s.a];
const cerca = bx >= b.x0 - 4 && bx <= b.x1 + 4 && by >= b.y0 - 4 && by <= b.y1 + 4;
if (!cerca) fallas.push(`${nombre}: la comida no queda junto a la boca`);
}
}
return { escenas: medidas.length, esperadas: casos.length, fallas };
}

if (import.meta.url === `file://${process.argv[1]}`) {
const { escenas, esperadas, fallas } = comprobar();
fallas.forEach((f) => console.error("✗ " + f));
console.log(`${escenas} escenas, ${fallas.length} fallas`);
if (escenas !== esperadas || fallas.length) process.exit(1);
}
6 changes: 4 additions & 2 deletions design/companero/exportar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fs from "node:fs";
import path from "node:path";
import { datosPrendas } from "./prendas.mjs";
import { datosExpresiones } from "./expresiones.mjs";
import { datosEscenas } from "./escenas.mjs";

const here = path.dirname(new URL(import.meta.url).pathname);
const dir = path.join(here, "cuerpos");
Expand All @@ -27,10 +28,11 @@ fs.writeFileSync(
var ARTE = { cuerpos: ${JSON.stringify(cuerpos, null, 2).replace(/\n/g, "\n ")},
disenos: ${JSON.stringify(datos, null, 2).replace(/\n/g, "\n ")},
prendas: ${JSON.stringify(datosPrendas, null, 2).replace(/\n/g, "\n ")},
expresiones: ${JSON.stringify(datosExpresiones, null, 2).replace(/\n/g, "\n ")} };
expresiones: ${JSON.stringify(datosExpresiones, null, 2).replace(/\n/g, "\n ")},
escenas: ${JSON.stringify(datosEscenas, null, 2).replace(/\n/g, "\n ")} };
if (typeof module === 'object' && module.exports) module.exports = ARTE;
else root.CompaneroArte = ARTE;
})(this);
`,
);
console.log(`arte.js: ${Object.keys(cuerpos).length} cuerpos, ${disenos.length} diseños, ${datosPrendas.catalogo.length} prendas, ${Object.keys(datosExpresiones.animales).length} caras con expresiones`);
console.log(`arte.js: ${Object.keys(cuerpos).length} cuerpos, ${disenos.length} diseños, ${datosPrendas.catalogo.length} prendas, ${Object.keys(datosExpresiones.animales).length} caras con expresiones, ${Object.keys(datosEscenas.comidas).length} comidas`);
Loading
Loading