Lint: fix errors and remove unused variables

**Motivations:**
- Ensure lint config is not degraded and fix all lint errors for pousse workflow.

**Root causes:**
- Unused variables kept with _ prefix instead of removed (_row, _questReward, _i).
- getAnimalBlockOrigin had 5 parameters (max 4).
- use of continue statement (no-continue rule).

**Correctifs:**
- ESLint config verified; no eslint-disable in codebase.
- Removed unused variable _row (biome-rules); removed dead function _questReward (quests); removed unused map param _i (state.js).
- getAnimalBlockOrigin refactored to 4 params (pos object instead of x, y).
- Replaced continue with if (cell) block in normalizeLoadedCells (state.js).
- JSDoc param names aligned with _height, _y (biome-rules).

**Evolutions:**
- (none)

**Pages affectées:**
- web/js/biome-rules.js
- web/js/quests.js
- web/js/state.js
- web/js/placement.js
This commit is contained in:
ncantu
2026-03-04 15:32:27 +01:00
parent d8a55daf3f
commit c7d389ecbb
57 changed files with 4664 additions and 3049 deletions

257
web/js/ui-grid-handlers.js Normal file
View File

@@ -0,0 +1,257 @@
import { tryPlaceEgg, getNurseryCellKeysOrdered } from "./zoo.js";
import { tryUpgradeSchool } from "./conveyor.js";
import {
tryBuildNursery,
tryBuildSouvenirShop,
tryBuildResearch,
tryBuildBilleterie,
tryBuildFood,
tryBuildReception,
tryBuildBiomeChangeColor,
tryBuildBiomeChangeTemp,
tryUpgradeNursery,
tryUpgradeSouvenirShop,
tryUpgradeResearch,
tryUpgradeBilleterie,
tryUpgradeFood,
tryUpgradeReception,
tryUpgradeBiomeChangeColor,
tryUpgradeBiomeChangeTemp,
} from "./placement.js";
import { t, errorMessage } from "./texts-fr.js";
import { attachDragListeners, handleCellDropNurseryReceptionToken, handleCellDropEggTypeOrMove } from "./ui-grid-drag.js";
const BUILD_FNS = [
["nursery", tryBuildNursery],
["shop", tryBuildSouvenirShop],
["research", tryBuildResearch],
["billeterie", tryBuildBilleterie],
["food", tryBuildFood],
["reception", tryBuildReception],
["biomeColor", tryBuildBiomeChangeColor],
["biomeTemp", tryBuildBiomeChangeTemp],
];
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, emptyCellChoice: { x: number, y: number } | null, selectedTokenId: number | null, lastActionWasDrop: boolean, clampSelection: () => void, selected: { x: number, y: number } }} ctx
* @param {string} choice
* @param {number} x
* @param {number} y
*/
export function handleCellClickChoice(ctx, choice, x, y) {
const { state, setError } = ctx;
for (const [name, fn] of BUILD_FNS) {
if (choice === name) {
const [ok, reason] = fn(state, x, y);
if (!ok) setError(String(t.errorPrefix).replace("%s", errorMessage[reason] ?? reason));
else setError("");
ctx.emptyCellChoice.current = null;
return;
}
}
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void }} ctx
* @param {import("./types.js").GridCell} cell
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
export function handleCellClickUpgradeShops(ctx, cell, x, y) {
const { state, setState, setError, playSound } = ctx;
if (cell.kind === "souvenirShop") {
const [ok, reason] = tryUpgradeSouvenirShop(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "SouvenirShopMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
if (cell.kind === "research") {
const [ok, reason] = tryUpgradeResearch(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "ResearchMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
if (cell.kind === "billeterie") {
const [ok, reason] = tryUpgradeBilleterie(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "BilleterieMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
if (cell.kind === "food") {
const [ok, reason] = tryUpgradeFood(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "FoodMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
return false;
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void }} ctx
* @param {import("./types.js").GridCell} cell
* @param {{ x: number, y: number, key: string }} pos
* @returns {boolean}
*/
export function handleCellClickUpgradeRest(ctx, cell, pos) {
const { x, y, key } = pos;
const { state, setState, setError, playSound } = ctx;
if (cell.kind === "reception") {
const hasAnimal = (state.receptionAnimals ?? []).some((r) => r.receptionCellKey === key);
if (!hasAnimal) {
const [ok, reason] = tryUpgradeReception(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "ReceptionMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
}
setState();
return true;
}
if (cell.kind === "biomeChangeColor") {
const [ok, reason] = tryUpgradeBiomeChangeColor(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "BiomeChangeColorMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
if (cell.kind === "biomeChangeTemp") {
const [ok, reason] = tryUpgradeBiomeChangeTemp(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "BiomeChangeTempMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return true;
}
if (cell.kind === "school") {
const [ok, reason] = tryUpgradeSchool(state, x, y);
if (!ok) { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); } else { setError(""); playSound("schoolUpgrade"); }
setState();
return true;
}
return false;
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, selectedTokenId: number | null, emptyCellChoice: { x: number, y: number } | null }} ctx
* @param {boolean} empty
* @param {number} x
* @param {number} y
*/
export function handleCellClickPlaceOrSelect(ctx, empty, x, y) {
const { state, setState, setError, playSound } = ctx;
const nurseryKeys = getNurseryCellKeysOrdered(state);
let firstTokenId = null;
for (const k of nurseryKeys) {
const c = state.grid.cells[k];
if (c && c.kind === "nursery" && c.tokenId !== null && c.tokenId !== undefined) {
firstTokenId = c.tokenId;
break;
}
}
const tokenId = ctx.selectedTokenId.current ?? firstTokenId;
if (empty && tokenId !== null && tokenId !== undefined) {
const nowUnix = Math.floor(Date.now() / 1000);
const [ok, reason] = tryPlaceEgg(state, { tokenId, x, y, nowUnix });
if (ok) { ctx.selectedTokenId.current = null; setError(""); playSound("place"); } else { setError(String(t.errorPrefix).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
setState();
return;
}
if (empty) ctx.emptyCellChoice.current = { x, y };
setState();
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, gridEl: HTMLElement }} ctx
* @param {{ div: HTMLElement, cell: import("./types.js").GridCell | null | undefined, x: number, y: number, key: string }} opts
*/
export function attachCellListeners(ctx, opts) {
const { div, cell, x, y, key } = opts;
attachDragListeners(ctx, opts);
div.addEventListener("dragover", (e) => {
e.preventDefault();
const hasEggType = e.dataTransfer.types.includes("application/x-builazoo-eggtype");
const hasTokenId = e.dataTransfer.types.includes("application/x-builazoo-tokenid");
const hasNurseryKey = e.dataTransfer.types.includes("application/x-builazoo-nursery-cell-key");
const hasReceptionKey = e.dataTransfer.types.includes("application/x-builazoo-reception-cell-key");
e.dataTransfer.dropEffect = hasEggType || hasTokenId ? "copy" : "move";
if ((cell === null || cell === undefined) && (hasEggType || hasTokenId || hasNurseryKey || hasReceptionKey)) {
div.classList.add("dragover");
}
});
div.addEventListener("dragleave", () => div.classList.remove("dragover"));
div.addEventListener("drop", (e) => {
e.preventDefault();
div.classList.remove("dragover");
handleCellDrop(ctx, e, { toX: Number(div.dataset.x), toY: Number(div.dataset.y), cell });
});
div.addEventListener("click", (e) => handleCellClick(ctx, { e, cell, x, y, key }));
div.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); div.click(); }
});
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, gridEl: HTMLElement }} ctx
* @param {DragEvent} e
* @param {{ toX: number, toY: number, cell: import("./types.js").GridCell | null | undefined }} target
*/
function handleCellDrop(ctx, e, target) {
const { toX, toY, cell } = target;
const empty = cell === null || cell === undefined;
if (handleCellDropNurseryReceptionToken(ctx, e, { x: toX, y: toY, empty })) return;
handleCellDropEggTypeOrMove(ctx, e, { x: toX, y: toY, empty });
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, emptyCellChoice: { x: number, y: number } | null, selectedTokenId: number | null, lastActionWasDrop: boolean, clampSelection: () => void, selected: { x: number, y: number } }} ctx
* @param {{ e: Event, cell: import("./types.js").GridCell | null | undefined, x: number, y: number, key: string }} opts
*/
function handleCellClick(ctx, opts) {
const { e, cell, x, y, key } = opts;
const { state, setState, setError, playSound } = ctx;
if (ctx.lastActionWasDrop.current) {
ctx.lastActionWasDrop.current = false;
return;
}
const choiceBtn = e.target.closest(".cell-choice-btn");
const empty = cell === null || cell === undefined;
if (choiceBtn && empty && ctx.emptyCellChoice.current && ctx.emptyCellChoice.current.x === x && ctx.emptyCellChoice.current.y === y) {
handleCellClickChoice(ctx, choiceBtn.dataset.choice, x, y);
setState();
return;
}
if (cell !== null && cell !== undefined && cell.kind === "nursery" && cell.tokenId !== null && cell.tokenId !== undefined) {
ctx.selectedTokenId.current = cell.tokenId;
setState();
return;
}
if (cell !== null && cell !== undefined && cell.kind === "nursery" && (cell.tokenId === null || cell.tokenId === undefined)) {
const hasBaby = (state.pendingBabies ?? []).some((p) => p.nurseryCellKey === key);
if (!hasBaby) {
const [ok, reason] = tryUpgradeNursery(state, x, y);
if (ok) { setError(""); playSound("upgrade"); } else if (reason !== "NurseryMaxLevel") { setError(String(t.upgradeConveyorFailed).replace("%s", errorMessage[reason] ?? reason)); playSound("error"); }
}
setState();
return;
}
if (handleCellClickUpgrade(ctx, { cell, x, y, key })) return;
ctx.selected.x = x;
ctx.selected.y = y;
ctx.clampSelection();
if (empty && ctx.emptyCellChoice.current && ctx.emptyCellChoice.current.x === x && ctx.emptyCellChoice.current.y === y) {
ctx.emptyCellChoice.current = null;
setState();
return;
}
handleCellClickPlaceOrSelect(ctx, empty, x, y);
}
/**
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void }} ctx
* @param {{ cell: import("./types.js").GridCell | null | undefined, x: number, y: number, key: string }} opts
* @returns {boolean}
*/
function handleCellClickUpgrade(ctx, opts) {
const { cell, x, y, key } = opts;
if (cell === null || cell === undefined) return false;
if (handleCellClickUpgradeShops(ctx, cell, x, y)) return true;
if (handleCellClickUpgradeRest(ctx, cell, { x, y, key })) return true;
return false;
}
export { handleCellDrop, handleCellClick };