first commit

This commit is contained in:
2026-07-21 16:50:58 +02:00
commit 256599626e
407 changed files with 30489 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
/**
* Sprites ingrédients — parsing de `ingredient-sprites.bentext` et résolution
* alias → coordonnées pixel sur la planche. Pure, sans effet de bord.
*
* Format de `ingredient-sprites.bentext` :
* --- (séparateur de blocs)
* <row> <col> (coordonnées dans la grille, taille de cellule = INGREDIENT_SPRITE_CELL)
* alias 1 | alias 2 (synonymes séparés par `|`, lignes supplémentaires acceptées)
* ---
* ...
*
* Sortie de `parseIngredientSpritesBentext` :
* - byAlias : { [aliasLower]: { x, y, X, y: y } }
* - preferredAliases : Set<aliasLower> (alias des lignes « prioritaires »)
* - csv : rendu CSV (compat / rétro pour les outils existants)
*/
export const INGREDIENT_SPRITE_CELL = 32;
/** Lignes de tuiles « prioritaires » pour la carte variante (aligné sur lancienne grille CSV). */
export const PREFERRED_VARIANT_SPRITE_ROWS = new Set([8, 9, 10, 12, 13, 14, 15, 16]);
const normalizeIngredientAliasKey = (s) => s.trim().toLowerCase().replace(/\s+/g, " ");
const foldAccents = (s) => s.normalize("NFD").replace(/\p{M}+/gu, "").toLowerCase();
/**
* Parse `ingredient-sprites.bentext` : blocs `---` puis ligne `row col`, puis alias (`|` = synonymes).
*/
const parseIngredientSpritesBentext = (raw) => {
const byAlias = {};
const preferredAliases = new Set();
const grid = new Map();
const chunks = raw
.replace(/^\uFEFF/, "")
.trim()
.split(/\r?\n---\r?\n/);
for (const chunk of chunks) {
const trimmedChunk = chunk.trim();
if (!trimmedChunk) continue;
const lines = trimmedChunk
.split(/\r?\n/)
.map((l) => l.trim())
.filter((l) => l.length > 0);
if (lines.length < 2) continue;
const coordMatch = lines[0].match(/^(\d+)\s+(\d+)/);
if (!coordMatch) {
if (lines[0] === "---") continue;
console.warn(`[build-recipes] Bloc sprite ignoré (coords invalides): ${lines[0].slice(0, 48)}`);
continue;
}
const row = Number(coordMatch[1]);
const col = Number(coordMatch[2]);
const x = col * INGREDIENT_SPRITE_CELL;
const y = row * INGREDIENT_SPRITE_CELL;
const entry = { x, y, X: x, Y: y };
const aliasLines = lines.slice(1);
const cellText = aliasLines.join("|");
const ck = `${row},${col}`;
grid.set(ck, grid.has(ck) ? `${grid.get(ck)}|${cellText}` : cellText);
const isPreferredRow = PREFERRED_VARIANT_SPRITE_ROWS.has(row);
for (const line of aliasLines) {
for (const part of line.split("|")) {
const key = normalizeIngredientAliasKey(part);
if (!key) continue;
const prev = byAlias[key];
if (!prev) {
byAlias[key] = entry;
} else if (prev.x !== x || prev.y !== y) {
console.warn(
`[build-recipes] Alias sprite « ${key} » en conflit (${prev.x},${prev.y}) vs (${x},${y}) — première définition conservée.`
);
continue;
}
if (isPreferredRow) preferredAliases.add(key);
}
}
}
let maxR = 0;
let maxC = 0;
for (const k of grid.keys()) {
const [r, c] = k.split(",").map(Number);
maxR = Math.max(maxR, r);
maxC = Math.max(maxC, c);
}
const csvLines = [];
for (let r = 0; r <= maxR; r++) {
const cols = [];
for (let c = 0; c <= maxC; c++) {
cols.push(grid.get(`${r},${c}`) ?? "");
}
csvLines.push(cols.join(","));
}
const csv = `${csvLines.join("\n")}\n`;
return { byAlias, preferredAliases, csv };
};
const resolveIconPxForIngredientName = (name, byAlias) => {
if (!name || typeof name !== "string") return null;
const n = normalizeIngredientAliasKey(name);
if (byAlias[n]) return aliasToIconPx(byAlias, n);
const nFold = foldAccents(n);
for (const key of Object.keys(byAlias)) {
if (foldAccents(key) === nFold) return aliasToIconPx(byAlias, key);
}
const segments = n.split(/[,;]|(?:\s+\/\s+)|(?:\s+et\s+)/i).map((s) => s.trim()).filter(Boolean);
for (const seg of segments) {
if (byAlias[seg]) return aliasToIconPx(byAlias, seg);
const sf = foldAccents(seg);
for (const key of Object.keys(byAlias)) {
if (foldAccents(key) === sf) return aliasToIconPx(byAlias, key);
}
}
const keysSorted = Object.keys(byAlias).sort((a, b) => b.length - a.length);
for (const k of keysSorted) {
if (k.length < 3) continue;
if (n.includes(k) || nFold.includes(foldAccents(k))) return aliasToIconPx(byAlias, k);
}
return null;
};
/** Lit les coordonnées pixel dans `byAlias` (API : `X`/`Y` ou `x`/`y`). */
const aliasToIconPx = (byAlias, alias) => {
if (!byAlias || typeof alias !== "string") return null;
const entry = byAlias[alias];
if (!entry || typeof entry !== "object") return null;
const x = typeof entry.x === "number" ? entry.x : typeof entry.X === "number" ? entry.X : NaN;
const y = typeof entry.y === "number" ? entry.y : typeof entry.Y === "number" ? entry.Y : NaN;
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
return { x, y };
};
/** Alias manifeste correspondant à des coordonnées exactes sur la planche (pour filtrer les tuiles). */
const findAliasForIconPx = (byAlias, px) => {
if (!byAlias || !px || typeof px.x !== "number" || typeof px.y !== "number") return null;
for (const [alias, entry] of Object.entries(byAlias)) {
if (!entry || typeof entry !== "object") continue;
const x = typeof entry.x === "number" ? entry.x : typeof entry.X === "number" ? entry.X : NaN;
const y = typeof entry.y === "number" ? entry.y : typeof entry.Y === "number" ? entry.Y : NaN;
if (px.x === x && px.y === y) return alias;
}
return null;
};
const isPreferredVariantAlias = (preferredSet, manifestAlias) => {
if (!manifestAlias || preferredSet.size === 0) return false;
const lo = manifestAlias.trim().toLowerCase();
if (preferredSet.has(lo)) return true;
return [...preferredSet].some((p) => p.toLowerCase() === lo);
};
const buildIngredientCoverSprite = (iconPx, sheet) => {
if (!iconPx || typeof iconPx.x !== "number" || typeof iconPx.y !== "number" || !sheet) {
return null;
}
const col = Math.round(iconPx.x / INGREDIENT_SPRITE_CELL);
const row = Math.round(iconPx.y / INGREDIENT_SPRITE_CELL);
return {
sheet: "/ingredients.png",
col,
row,
cell: INGREDIENT_SPRITE_CELL,
sheetWidth: sheet.width,
sheetHeight: sheet.height
};
};
export {
parseIngredientSpritesBentext,
resolveIconPxForIngredientName,
aliasToIconPx,
findAliasForIconPx,
isPreferredVariantAlias,
buildIngredientCoverSprite,
normalizeIngredientAliasKey,
foldAccents
};