gestion_table_tournante_libre/src/geometrie/designation.test.js
Mathieu Benoit eec14bdcb0 [ADD] geometry: tables, their chairs, designating a seat
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
2026-10-07 02:32:47 -04:00

188 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © 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);
});
});