A table's tabletop, chairs, footprint and handle derive from its number of places, chairs numbered clockwise from the top, in drawing centimetres. The seat a pointer designates is read from the same chairs the drawing places, through one implementation, so what is drawn and where a drop lands cannot disagree. No table rotates. Checked: 1681 node tests. --- FR --- [ADD] géométrie : les tables, leurs chaises, la désignation d'un siège Le plateau, les chaises, l'emprise et la poignée d'une table dérivent de son nombre de places, chaises numérotées depuis le haut dans le sens horaire, en centimètres de dessin. Le siège que désigne un pointeur se lit sur les chaises mêmes que le dessin pose, par une seule implémentation : ce qui est dessiné et l'endroit où tombe un dépôt ne peuvent diverger. Aucune table ne tourne. Vérifié : 1681 épreuves node. Assisted-by: Claude Opus 5.5
188 lines
8.1 KiB
JavaScript
188 lines
8.1 KiB
JavaScript
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||
|
||
// Épreuves de la désignation (§ 7.3, § 14.10) : le régime des cibles, décidé
|
||
// table par table sur le pas de chacune, et le siège que désigne un point de
|
||
// dessin, qui est celui que geometrieTable pose — sur les quatre
|
||
// démonstrations livrées et sur des carrées, à un zoom où les sièges sont
|
||
// des cibles et à un zoom où la table l'est.
|
||
import assert from 'node:assert/strict';
|
||
import { describe, test } from '../../test/lanceur.js';
|
||
import { LIVREES } from '../demo/livrees.js';
|
||
import { analyser, capacite } from '../stockage/document.js';
|
||
import { CIBLE_MIN_PX, designer, regimeCibles, surLeSol } from './designation.js';
|
||
import { englobant, geometrieTable } from './tables.js';
|
||
|
||
const table = (id, forme, x, y) => ({ id, numero: id, sieges: null, forme, position: { x, y } });
|
||
|
||
const milieu = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
|
||
|
||
describe('regimeCibles (§ 7.3)', () => {
|
||
test('le seuil est de 24 px', () => {
|
||
assert.equal(CIBLE_MIN_PX, 24);
|
||
});
|
||
|
||
test('ronde de 8, pas 60 : 0,40 px/cm et au-dessus, les sièges', () => {
|
||
const g = geometrieTable(table(1, 'ronde', 0, 0), 8);
|
||
assert.equal(regimeCibles(0.41, g), 'sieges');
|
||
assert.equal(regimeCibles(0.4, g), 'sieges');
|
||
assert.equal(regimeCibles(0.39, g), 'table');
|
||
});
|
||
|
||
test('ronde de 3, pas 70π/3 : bascule entre 0,33 et 0,32', () => {
|
||
const g = geometrieTable(table(1, 'ronde', 0, 0), 3);
|
||
assert.equal(regimeCibles(0.33, g), 'sieges');
|
||
assert.equal(regimeCibles(0.32, g), 'table');
|
||
});
|
||
|
||
test('carrée de 3, pas 70 : bascule entre 0,35 et 0,34', () => {
|
||
const g = geometrieTable(table(1, 'carree', 0, 0), 3);
|
||
assert.equal(regimeCibles(0.35, g), 'sieges');
|
||
assert.equal(regimeCibles(0.34, g), 'table');
|
||
});
|
||
|
||
test('ronde de 7, pas 60 exactement : sièges à 0,40', () => {
|
||
assert.equal(regimeCibles(0.4, geometrieTable(table(1, 'ronde', 0, 0), 7)), 'sieges');
|
||
});
|
||
});
|
||
|
||
describe('l’emprise d’une carrée est un carré', () => {
|
||
// Carrée de 12 au centre (0, 0) : côté 180, emprise 90 + 44 = 134 ; le
|
||
// siège 6, dernier du côté droit, est en (112, 60).
|
||
const geometries = [geometrieTable(table(7, 'carree', 0, 0), 12)];
|
||
|
||
test('(125, 125) : hors du plateau et des chaises, dans le coin de l’emprise', () => {
|
||
const point = { x: 125, y: 125 };
|
||
assert.deepEqual(designer(point, geometries, 0.2), { table: 7 });
|
||
assert.equal(designer(point, geometries, 1), null);
|
||
assert.equal(surLeSol(point, geometries), false);
|
||
});
|
||
|
||
test('(130, 60) : dans la chaise du siège 6, à 143 cm du centre', () => {
|
||
const point = { x: 130, y: 60 };
|
||
assert.deepEqual(designer(point, geometries, 0.2), { table: 7 });
|
||
assert.deepEqual(designer(point, geometries, 1), { table: 7, siege: 6 });
|
||
assert.equal(surLeSol(point, geometries), false);
|
||
});
|
||
|
||
test('(140, 0) est le sol', () => {
|
||
const point = { x: 140, y: 0 };
|
||
assert.equal(designer(point, geometries, 0.2), null);
|
||
assert.equal(designer(point, geometries, 1), null);
|
||
assert.equal(surLeSol(point, geometries), true);
|
||
});
|
||
});
|
||
|
||
// Les géométries de chaque démonstration livrée, telles que le dessin les
|
||
// pose, puis trois carrées de 3, 8 et 12 : les démonstrations n'ont que des
|
||
// rondes.
|
||
const JEUX = [
|
||
...LIVREES.map(({ cle, texte }) => {
|
||
const { charge } = analyser(texte);
|
||
return { nom: cle, geometries: charge.tables.map((t) => geometrieTable(t, capacite(charge, t))) };
|
||
}),
|
||
{
|
||
nom: 'carrées',
|
||
geometries: [
|
||
geometrieTable(table(1, 'carree', 0, 0), 3),
|
||
geometrieTable(table(2, 'carree', 1000, 0), 8),
|
||
geometrieTable(table(3, 'carree', 2000, 0), 12),
|
||
],
|
||
},
|
||
];
|
||
|
||
describe('le siège désigné est celui que le dessin pose (§ 7.3, § 14.10)', () => {
|
||
const ATTENDUES = { grande: 260, 'grande-sans-exception': 264, petite: 12, 'petite-conflit': 12, 'carrées': 23 };
|
||
|
||
test('cinq jeux, dans l’ordre', () => {
|
||
assert.deepEqual(JEUX.map(({ nom }) => nom), ['grande', 'grande-sans-exception', 'petite', 'petite-conflit', 'carrées']);
|
||
});
|
||
|
||
for (const { nom, geometries } of JEUX) {
|
||
test(`${nom}, k = 1 : chaque chaise rend sa table et son siège`, () => {
|
||
let chaises = 0;
|
||
for (const g of geometries) {
|
||
assert.equal(regimeCibles(1, g), 'sieges');
|
||
for (const chaise of g.chaises) {
|
||
assert.deepEqual(designer(chaise, geometries, 1), { table: g.id, siege: chaise.siege });
|
||
chaises += 1;
|
||
}
|
||
}
|
||
assert.ok(ATTENDUES[nom] > 0);
|
||
assert.equal(chaises, ATTENDUES[nom]);
|
||
});
|
||
|
||
test(`${nom}, k = 1 : ni le milieu de deux chaises voisines, ni le centre d’une table ne désigne rien`, () => {
|
||
let milieux = 0;
|
||
for (const g of geometries) {
|
||
const n = g.chaises.length;
|
||
for (let i = 0; i < n; i += 1) {
|
||
const point = milieu(g.chaises[i], g.chaises[(i + 1) % n]);
|
||
assert.equal(designer(point, geometries, 1), null, `table ${g.id}, sièges ${i + 1} et ${((i + 1) % n) + 1}`);
|
||
assert.equal(surLeSol(point, geometries), false);
|
||
milieux += 1;
|
||
}
|
||
assert.equal(designer(g.centre, geometries, 1), null, `centre de la table ${g.id}`);
|
||
assert.equal(surLeSol(g.centre, geometries), false);
|
||
}
|
||
assert.equal(milieux, ATTENDUES[nom]);
|
||
});
|
||
|
||
test(`${nom} : un point à 10 m de tout est le sol`, () => {
|
||
const e = englobant(geometries);
|
||
const loin = { x: e.maxX + 1000, y: e.maxY + 1000 };
|
||
assert.equal(surLeSol(loin, geometries), true);
|
||
assert.equal(designer(loin, geometries, 1), null);
|
||
assert.equal(designer(loin, geometries, 0.2), null);
|
||
});
|
||
|
||
test(`${nom}, k = 0,2 : chaque chaise, chaque milieu et chaque centre rendent la table, jamais un siège`, () => {
|
||
let points = 0;
|
||
for (const g of geometries) {
|
||
assert.equal(regimeCibles(0.2, g), 'table');
|
||
const n = g.chaises.length;
|
||
const candidats = [g.centre, ...g.chaises, ...g.chaises.map((c, i) => milieu(c, g.chaises[(i + 1) % n]))];
|
||
for (const point of candidats) {
|
||
assert.deepEqual(designer(point, geometries, 0.2), { table: g.id });
|
||
points += 1;
|
||
}
|
||
}
|
||
assert.equal(points, 2 * ATTENDUES[nom] + geometries.length);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe('tables qui se recouvrent : la dernière reçue, peinte au-dessus', () => {
|
||
test('deux tables au même centre : la seconde', () => {
|
||
const geometries = [geometrieTable(table(1, 'ronde', 0, 0), 8), geometrieTable(table(2, 'ronde', 0, 0), 8)];
|
||
const chaise = geometries[0].chaises[2];
|
||
assert.deepEqual(designer(chaise, geometries, 1), { table: 2, siege: 3 });
|
||
assert.deepEqual(designer({ x: 0, y: 0 }, geometries, 0.2), { table: 2 });
|
||
});
|
||
|
||
test('régime sièges : le plateau du dessus cache la chaise de dessous', () => {
|
||
// Rondes de 8 en (0, 0) et (100, 0) : le siège 3 de la première, en
|
||
// (98,394 ; 0), tombe dans le plateau de la seconde, de rayon 76,394.
|
||
const premiere = geometrieTable(table(1, 'ronde', 0, 0), 8);
|
||
const seconde = geometrieTable(table(2, 'ronde', 100, 0), 8);
|
||
const cachee = premiere.chaises[2];
|
||
assert.equal(designer(cachee, [premiere, seconde], 1), null);
|
||
assert.deepEqual(designer(cachee, [seconde, premiere], 1), { table: 1, siege: 3 });
|
||
assert.deepEqual(designer(premiere.chaises[6], [premiere, seconde], 1), { table: 1, siege: 7 });
|
||
});
|
||
|
||
test('régimes différents : chaque table se juge sur son propre pas', () => {
|
||
// À k = 0,35, la ronde de 8 (pas 60) est en régime table, la carrée de 3
|
||
// (pas 70) en régime sièges.
|
||
const ronde = geometrieTable(table(1, 'ronde', 0, 0), 8);
|
||
const carree = geometrieTable(table(2, 'carree', 1000, 0), 3);
|
||
const geometries = [ronde, carree];
|
||
assert.deepEqual(designer(ronde.chaises[0], geometries, 0.35), { table: 1 });
|
||
assert.deepEqual(designer(carree.chaises[0], geometries, 0.35), { table: 2, siege: 1 });
|
||
});
|
||
|
||
test('sans table, aucun point n’est désigné et tout est le sol', () => {
|
||
assert.equal(designer({ x: 0, y: 0 }, [], 1), null);
|
||
assert.equal(surLeSol({ x: 0, y: 0 }, []), true);
|
||
});
|
||
});
|