Corrige la ligne de danger de la Corne (franchissement complet, pas au contact) et redesigne l'échelle de repère
Build and deploy / deploy (push) Successful in 36s
Build and deploy / deploy (push) Successful in 36s
La pièce devait déjà entièrement dépasser la ligne rouge (bord bas, pas juste le sommet) pour compter dans le délai de grâce avant game over. L'échelle des tailles en bas de page passe d'une seule ligne à défilement horizontal à un flex-wrap sans scroll, avec un titre — l'intérêt d'une échelle de repère est de tout voir d'un coup d'œil. Corrige aussi une violation de react-hooks/purity (Date.now() en argument direct de useRef) introduite par le mécanisme anti-triche fusionné depuis main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,14 @@ export function MelonGame({
|
||||
const pendingMergesRef = useRef<PendingMerge[]>([]);
|
||||
const mergingBodyIdsRef = useRef(new Set<number>());
|
||||
const dangerSinceRef = useRef<number | null>(null);
|
||||
const runStartedAtRef = useRef(Date.now());
|
||||
// Pas Date.now() directement en argument de useRef (impur pendant le
|
||||
// rendu, react-hooks/purity) : la vraie valeur est posée dans l'effet de
|
||||
// montage juste en dessous — resetGame() (rejouer après une partie) la
|
||||
// réécrit de toute façon à chaque nouvelle partie.
|
||||
const runStartedAtRef = useRef(0);
|
||||
useEffect(() => {
|
||||
runStartedAtRef.current = Date.now();
|
||||
}, []);
|
||||
const dropCountRef = useRef(0);
|
||||
const mergeCountRef = useRef(0);
|
||||
|
||||
@@ -326,12 +333,16 @@ export function MelonGame({
|
||||
|
||||
// Renvoie les corps qui dépassent la ligne de danger (au repos, pas un
|
||||
// simple rebond) — utilisé à la fois pour déclencher/prolonger le
|
||||
// délai de grâce et pour savoir quelles pièces doivent clignoter.
|
||||
// délai de grâce et pour savoir quelles pièces doivent clignoter. Le
|
||||
// cercle doit avoir ENTIÈREMENT franchi la ligne (son bord bas,
|
||||
// position.y + radius) plutôt que simplement y toucher (son bord haut,
|
||||
// position.y - radius) : une pièce qui dépasse juste du sommet ne doit
|
||||
// pas encore compter.
|
||||
function overflowingBodyIds(): Set<number> {
|
||||
const ids = new Set<number>();
|
||||
for (const [id, body] of bodiesRef.current) {
|
||||
const radius = body.circleRadius ?? 0;
|
||||
if (body.position.y - radius < DANGER_LINE_Y && body.speed < 0.5) {
|
||||
if (body.position.y + radius < DANGER_LINE_Y && body.speed < 0.5) {
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
@@ -528,28 +539,36 @@ export function MelonGame({
|
||||
{/* Échelle de repère : ordre de fusion complet, du plus petit au plus
|
||||
grand — taille d'affichage proportionnelle au vrai rayon du
|
||||
palier (mais réduite et plafonnée) pour que la progression se
|
||||
voie d'un coup d'œil, sans dépendre du nombre de Citoyens. Une
|
||||
seule ligne défilante (pas de flex-wrap). items-center (pas
|
||||
items-end) : aligner sur le bas avec des tailles très variables
|
||||
donnait un décalage qui grandissait avec la taille (la flèche,
|
||||
à décalage fixe, n'était plus au bon endroit pour les gros
|
||||
ronds) — centrer chaque rond sur son propre milieu reste
|
||||
symétrique quelle que soit sa taille. */}
|
||||
<div
|
||||
className="marble-surface flex w-full items-center gap-x-1 gap-y-2 overflow-x-auto rounded-lg border border-gold/25 p-2"
|
||||
style={{ maxWidth: CONTAINER_WIDTH }}
|
||||
>
|
||||
voie d'un coup d'œil, sans dépendre du nombre de Citoyens.
|
||||
flex-wrap (pas de scroll horizontal comme avant) : tout le monde
|
||||
doit être visible sans interaction, l'intérêt d'une échelle de
|
||||
repère est justement de tout voir d'un coup plutôt que de devoir
|
||||
faire défiler. items-center (pas items-end) : aligner sur le bas
|
||||
avec des tailles très variables donnait un décalage qui
|
||||
grandissait avec la taille (la flèche, à décalage fixe, n'était
|
||||
plus au bon endroit pour les gros ronds) — centrer chaque rond
|
||||
sur son propre milieu reste symétrique quelle que soit sa
|
||||
taille. Largeur plafonnée à max-w-sm (pas CONTAINER_WIDTH comme
|
||||
avant) : la page (corne-view.tsx) est bien plus large que le bac
|
||||
de jeu, la contrainte à 300px n'avait pas lieu d'être et forçait
|
||||
le scroll pour rien. */}
|
||||
<div className="marble-surface w-full max-w-sm rounded-lg border border-gold/25 p-3">
|
||||
<p className="mb-2 text-center font-heading text-[0.65rem] tracking-[0.15em] text-text-mut uppercase">
|
||||
Ordre de fusion
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-1.5 gap-y-3">
|
||||
{citizens.map((citizen, tier) => (
|
||||
<div key={citizen.id} className="flex shrink-0 items-center gap-1">
|
||||
<div key={citizen.id} className="flex items-center gap-1.5">
|
||||
<Avatar
|
||||
pseudo={citizen.pseudo}
|
||||
avatarUrl={citizen.avatar_url}
|
||||
size={Math.round(16 + (tierRadii[tier] / tierRadii[maxTier]) * 24)}
|
||||
size={Math.round(18 + (tierRadii[tier] / tierRadii[maxTier]) * 26)}
|
||||
/>
|
||||
{tier < citizens.length - 1 && <span className="text-xs text-text-mut">→</span>}
|
||||
{tier < citizens.length - 1 && <span className="text-xs text-gold/50">→</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user