I nomi del «?» non si sovrappongono più: ognuno cerca un posto libero vicino al suo segno

placeLabels prova sopra, sotto, più lontano e di lato, con un po' d'aria fra un nome e l'altro; una lineetta
collega il nome al segno quando finisce lontano. Un test controlla tutte le scene, con tutti gli oggetti accesi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
2026-10-24 16:32:02 +02:00
co-authored by Claude Opus 5.5
parent 80907f2fa0
commit ac1fb1538d
2 changed files with 63 additions and 10 deletions
+48 -10
View File
@@ -169,28 +169,66 @@ export function render(ctx, game, { hover, spots, debug } = {}) {
}
// Il pulsante «?»: un segno su ogni cosa che si può toccare, col suo nome. Le uscite in giallo, il resto in bianco.
// I nomi non si sovrappongono: placeLabels cerca per ognuno un posto libero vicino al suo segno.
function drawSpots(ctx, game) {
ctx.save();
ctx.font = '6px "Press Start 2P", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
for (const h of game.hotspots()) {
const [x, y, w, hh] = h.box, cx = Math.round(x + w / 2), cy = Math.round(y + hh / 2);
const color = h.exit ? '#ffd27a' : '#ffffff';
const spots = game.hotspots().map(h => {
const [x, y, w, hh] = h.box;
return { h, x: Math.round(x + w / 2), y: Math.round(y + hh / 2), w: ctx.measureText(h.name).width };
});
const labels = placeLabels(spots);
spots.forEach(({ h, x, y }, i) => {
const color = h.exit ? '#ffd27a' : '#ffffff', l = labels[i];
// Nome spostato lontano dal segno: una lineetta li collega.
if (Math.abs(l.y - LABEL_H / 2 - y) > LABEL_H * 1.6 || Math.abs(l.x - x) > 4) {
ctx.strokeStyle = 'rgba(0,0,0,0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(l.x, l.y - LABEL_H / 2);
ctx.stroke();
}
ctx.fillStyle = 'rgba(0,0,0,0.55)';
ctx.fillRect(cx - 4, cy - 4, 9, 9);
ctx.fillRect(x - 4, y - 4, 9, 9);
ctx.fillStyle = color;
ctx.fillRect(cx - 3, cy - 3, 7, 7);
ctx.fillRect(x - 3, y - 3, 7, 7);
ctx.lineWidth = 3;
ctx.strokeStyle = '#000';
// Il nome resta dentro la scena anche per le uscite sui bordi.
const half = ctx.measureText(h.name).width / 2, tx = Math.max(half + 2, Math.min(WIDTH - half - 2, cx)), ty = Math.max(10, cy - 6);
ctx.strokeText(h.name, tx, ty);
ctx.fillText(h.name, tx, ty);
}
ctx.strokeText(h.name, l.x, l.y);
ctx.fillText(h.name, l.x, l.y);
});
ctx.restore();
}
// Dove scrivere i nomi del «?»: per ogni segno { x, y, w } (centro e larghezza del testo) il punto { x, y } della base
// del testo, centrato. Si prova sopra il segno, sotto, poi sempre più lontano e di lato; vince il primo posto che non
// tocca né i nomi già messi né i segni, e sta dentro la scena. Prima i nomi dei segni più in alto.
const LABEL_H = 8;
export function placeLabels(spots, { width = WIDTH, height = HEIGHT } = {}) {
const boxes = spots.map(s => ({ l: s.x - 5, r: s.x + 5, t: s.y - 5, b: s.y + 5 }));
const hit = (a, b) => a.l < b.r && b.l < a.r && a.t < b.b && b.t < a.b;
const out = new Array(spots.length);
const order = spots.map((s, i) => i).sort((a, b) => spots[a].y - spots[b].y || spots[a].x - spots[b].x);
for (const i of order) {
const s = spots[i], half = s.w / 2 + 1;
const tries = [];
for (const dy of [-6, 13, -15, 22, -24, 31, -33, 40]) for (const dx of [0, -half - 6, half + 6]) tries.push([dx, dy]);
let best = null;
for (const [dx, dy] of tries) {
const x = Math.max(half + 2, Math.min(width - half - 2, s.x + dx)), y = Math.max(LABEL_H + 2, Math.min(height - 2, s.y + dy));
const box = { l: x - half - 3, r: x + half + 3, t: y - LABEL_H - 1, b: y + 1 }; // con un po' d'aria intorno
if (!boxes.some(b => hit(b, box))) { best = { x, y, box }; break; }
best ??= { x, y, box }; // tutto pieno: almeno il primo
}
out[i] = { x: best.x, y: best.y };
boxes.push(best.box);
}
return out;
}
// Modalità debug (?debug nell'indirizzo): area calpestabile coi vertici numerati, tutti gli hotspot col loro id
// (tratteggiati quelli spenti dal loro `when`), punti dove si va (`at`), dove si parla e dove stanno i personaggi.
// In basso le coordinate del puntatore e i punti segnati con Maiusc+clic, per scrivere scene a mano.