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

274
web/js/ui-render-dom.js Normal file
View File

@@ -0,0 +1,274 @@
import { refreshOffers, getSkillLevel } from "./conveyor.js";
import { getPlotUpgradeCost, getTruckUpgradeCost, getWorldMapUpgradeResearchCost } from "./economy.js";
import { getVisitorCount } from "./income.js";
import { getTimePhase } from "./time-weather.js";
import { canPrestige, doPrestige } from "./prestige.js";
import { playSound, isMusicEnabled } from "./audio.js";
import { questDescription, timePhaseLabel, weatherLabel, prestigeHint } from "./texts-fr.js";
import { GameConfig } from "./config.js";
import { renderWorldMap } from "./ui-world-map.js";
import { renderGrid } from "./ui-grid.js";
import { buildGameBar } from "./ui-render-gamebar.js";
import { buildWorldMapSection, buildGridSection } from "./ui-render-dom-panels.js";
/**
* @param {{ errEl: HTMLElement }} _setup
* @returns {{ tabsWrap: HTMLElement, tabContent: HTMLElement, panelZoo: HTMLElement, panelWorld: HTMLElement }}
*/
function createTabsStructure(_setup) {
const tabsWrap = document.createElement("div");
tabsWrap.className = "tabs-wrap";
tabsWrap.setAttribute("aria-label", "Carte du zoo et carte du monde");
const tabContent = document.createElement("div");
tabContent.className = "tabs-content";
const panelZoo = document.createElement("div");
panelZoo.className = "tab-panel active";
panelZoo.id = "tab-panel-zoo";
panelZoo.setAttribute("role", "tabpanel");
panelZoo.setAttribute("aria-labelledby", "view-toggle");
const panelWorld = document.createElement("div");
panelWorld.className = "tab-panel";
panelWorld.id = "tab-panel-world";
panelWorld.setAttribute("role", "tabpanel");
panelWorld.setAttribute("aria-labelledby", "view-toggle");
return { tabsWrap, tabContent, panelZoo, panelWorld };
}
/**
* @param {Array<{ descriptionKey: string, target: number, current: number, done: boolean }>} quests
* @returns {string}
*/
function formatQuestListHtml(quests) {
return (quests ?? []).map((q) => {
const desc = questDescription[q.descriptionKey];
const text = desc ? String(desc).replace("%d", String(q.target)) : q.descriptionKey;
const done = q.done ? " ✓" : "";
return `<div class="quest-item ${q.done ? "done" : ""}">${text} : ${q.current}/${q.target}${done}</div>`;
}).join("");
}
/**
* @param {object} gameBarResult
* @param {object} setup
* @returns {void}
*/
function updateStatusBody(gameBarResult, setup) {
const { state } = setup;
const { selected } = setup;
const {
statusBarCoins,
statusBarPlot,
statusBarCell,
statusBarSkill,
statusBarVisitors,
statusBarOffers,
statusBarTimeWeather,
musicBtn,
autoModeBtn,
prestigeBtn,
questListEl,
} = gameBarResult;
statusBarCoins.valueEl.textContent = String(Math.floor(state.coins));
statusBarPlot.valueEl.textContent = String(Math.floor(state.plotLevel));
statusBarCell.valueEl.textContent = `${Math.floor(selected.x)} ${Math.floor(selected.y)}`;
statusBarSkill.valueEl.textContent = String(Math.floor(getSkillLevel(state)));
const visitors = getVisitorCount(state);
statusBarVisitors.valueEl.textContent = String(Math.floor(visitors));
const offersCount = (state.conveyorOffers ?? []).length;
statusBarOffers.valueEl.textContent = String(Math.floor(offersCount));
const timePhase = getTimePhase(state.timeOfDay ?? 6);
const weatherVal = weatherLabel[state.weather] ?? state.weather;
statusBarTimeWeather.valueEl.textContent = `${timePhaseLabel[timePhase.phase]} · ${weatherVal}`;
statusBarTimeWeather.item.className = "status-bar-item status-bar-time-weather weather-" + (state.weather ?? "sun");
musicBtn.classList.toggle("muted", !isMusicEnabled());
autoModeBtn.setAttribute("aria-pressed", state.autoMode ? "true" : "false");
autoModeBtn.title = state.autoMode ? "Mode automatique (désactiver)" : "Mode automatique (activer)";
autoModeBtn.setAttribute("aria-label", state.autoMode ? "Mode automatique actif" : "Activer le mode automatique");
autoModeBtn.textContent = state.autoMode ? "🤖" : "✋";
prestigeBtn.title = String(prestigeHint).replace("%d", String(GameConfig.Prestige.MinCoinsToReset));
prestigeBtn.disabled = !canPrestige(state);
questListEl.innerHTML = formatQuestListHtml(state.quests);
}
/**
* @param {object} gameBarResult
* @param {object} setup
* @returns {() => void}
*/
function createUpdateStatus(gameBarResult, setup) {
return function updateStatus() {
updateStatusBody(gameBarResult, setup);
};
}
/**
* @param {object} opts
* @returns {() => void}
*/
function createFullRender(opts) {
const {
setup,
sellZone,
plotUpgradeZone,
worldMapUpgradeZone,
worldMapCounters,
worldMapCtx,
gridCtx,
updateStatus,
} = opts;
const { state } = setup;
return function fullRender() {
setup.clampSelection();
updateStatus();
const canUpTruck = (state.truckLevel ?? 1) < ((GameConfig.Truck && GameConfig.Truck.MaxLevel) || 5)
&& state.coins >= getTruckUpgradeCost(state.truckLevel ?? 1);
sellZone.classList.toggle("can-upgrade", canUpTruck);
const truckArrow = sellZone.querySelector(".sell-zone-upgrade-arrow");
if (truckArrow) truckArrow.style.display = canUpTruck ? "" : "none";
const plotMaxLevel = GameConfig.Plot.MaxLevel || 8;
const canUpgradePlot = (state.plotLevel ?? 1) < plotMaxLevel && state.coins >= getPlotUpgradeCost(state.plotLevel ?? 1);
plotUpgradeZone.classList.toggle("can-upgrade", canUpgradePlot);
const plotArrow = plotUpgradeZone.querySelector(".plot-upgrade-zone-arrow");
if (plotArrow) plotArrow.style.display = canUpgradePlot ? "" : "none";
updateWorldMapUpgradeAndCounters(worldMapUpgradeZone, worldMapCounters, state);
const eggPurchase = state.eggPurchaseTruck;
if (eggPurchase && eggPurchase.startAt) {
const truckLevel = state.truckLevel ?? 1;
const baseMs = (GameConfig.WorldMap && GameConfig.WorldMap.TruckAnimationMs) || 2500;
const durationMs = Math.max(1000, (baseMs * 2) / truckLevel);
if (Date.now() - eggPurchase.startAt >= durationMs) delete state.eggPurchaseTruck;
}
renderWorldMap(worldMapCtx);
renderGrid(gridCtx);
};
}
/**
* @param {HTMLElement} worldMapUpgradeZone
* @param {HTMLElement} worldMapCounters
* @param {import("./types.js").GameState} state
* @returns {void}
*/
function updateWorldMapUpgradeAndCounters(worldMapUpgradeZone, worldMapCounters, state) {
const mapCfg = GameConfig.WorldMap && GameConfig.WorldMap.MapUpgrade;
const mapMaxLevel = mapCfg ? mapCfg.MaxLevel : 5;
const currentMapLevel = state.worldMapLevel ?? 1;
const mapResearchCost = getWorldMapUpgradeResearchCost(currentMapLevel);
const canUpgradeMap = currentMapLevel < mapMaxLevel && (state.researchPoints ?? 0) >= mapResearchCost;
worldMapUpgradeZone.classList.toggle("can-upgrade", canUpgradeMap);
worldMapUpgradeZone.title = currentMapLevel < mapMaxLevel
? `Agrandir la carte (${mapResearchCost} unités de recherche)`
: "Agrandir la carte";
const mapCostEl = worldMapUpgradeZone.querySelector(".world-map-upgrade-zone-cost");
if (mapCostEl) mapCostEl.textContent = currentMapLevel < mapMaxLevel ? ` ${mapResearchCost} 🔬` : "";
const mapArrow = worldMapUpgradeZone.querySelector(".world-map-upgrade-zone-arrow");
if (mapArrow) mapArrow.style.display = canUpgradeMap ? "" : "none";
const babiesForSale = (state.saleListings ?? []).filter((s) => s.isBaby).length;
const animalsForSale = (state.saleListings ?? []).filter((s) => !s.isBaby).length;
const labsCount = GameConfig.WorldMap && GameConfig.WorldMap.Laboratory ? 1 : 0;
const zoosCount = (state.worldZoos ?? []).length;
const citiesCount = (GameConfig.WorldMap && GameConfig.WorldMap.Cities) ? GameConfig.WorldMap.Cities.length : 0;
worldMapCounters.textContent = "";
const counterEntries = [
["Bébés à vendre", babiesForSale],
["Animaux à vendre", animalsForSale],
["Laboratoires", labsCount],
["Zoos", zoosCount],
["Villes", citiesCount],
];
for (const [label, value] of counterEntries) {
const span = document.createElement("span");
span.className = "world-map-counter";
span.title = label;
span.setAttribute("aria-label", `${label}: ${value}`);
span.textContent = `${label}: ${value}`;
worldMapCounters.appendChild(span);
}
}
/**
* @param {{ setup: object, gameBarResult: object, worldMapResult: object, gridResult: object }} opts
* @returns {{ worldMapCtx: object, gridCtx: object }}
*/
function buildFinishContexts(opts) {
const { setup, worldMapResult, gridResult } = opts;
const worldMapCtx = {
worldMapEl: worldMapResult.worldMapEl,
worldMapTruckEl: worldMapResult.worldMapTruckEl,
worldMapNpcTrucksEl: worldMapResult.worldMapNpcTrucksEl,
state: setup.state,
setState: setup.setState,
setError: setup.setError,
playSound,
animalEmoji: setup.animalEmoji,
pendingTokenByEggType: setup.pendingTokenByEggType,
};
const gridCtx = {
state: setup.state,
setState: setup.setState,
setError: setup.setError,
playSound,
gridEl: gridResult.gridEl,
getHatched: setup.getHatched,
selected: setup.selected,
emptyCellChoice: setup.emptyCellChoiceRef,
selectedTokenId: setup.selectedTokenIdRef,
lastActionWasDrop: setup.lastActionWasDropRef,
clampSelection: setup.clampSelection,
animalEmoji: setup.animalEmoji,
};
return { worldMapCtx, gridCtx };
}
/**
* @param {{ root: HTMLElement, setup: object, gameBarResult: object, worldMapResult: object, gridResult: object, updateStatus: () => void }} opts
* @returns {() => void}
*/
function finishBuildUIDOM(opts) {
const { setup, gameBarResult, worldMapResult, gridResult, updateStatus } = opts;
const { worldMapCtx, gridCtx } = buildFinishContexts(opts);
renderWorldMap(worldMapCtx);
renderGrid(gridCtx);
gameBarResult.prestigeBtn.addEventListener("click", () => {
if (!canPrestige(setup.state)) return;
doPrestige(setup.state);
refreshOffers(setup.state, Math.floor(Date.now() / 1000));
setup.setError("");
playSound("upgrade");
setup.setState();
});
const fullRender = createFullRender({
setup,
sellZone: gridResult.sellZone,
plotUpgradeZone: gridResult.plotUpgradeZone,
worldMapUpgradeZone: worldMapResult.worldMapUpgradeZone,
worldMapCounters: worldMapResult.worldMapCounters,
worldMapCtx,
gridCtx,
updateStatus,
});
fullRender();
return fullRender;
}
/**
* @param {HTMLElement} root
* @param {object} setup
* @returns {() => void}
*/
export function buildUIDOM(root, setup) {
const tabs = createTabsStructure(setup);
const { panelZoo, panelWorld, tabsWrap, tabContent } = tabs;
const gameBarResult = buildGameBar(setup, panelZoo, panelWorld);
const worldMapResult = buildWorldMapSection(panelWorld, setup);
const gridResult = buildGridSection(panelZoo, setup);
tabsWrap.appendChild(setup.errEl);
tabContent.appendChild(panelZoo);
tabContent.appendChild(panelWorld);
tabsWrap.appendChild(tabContent);
gameBarResult.gameBarActions.insertBefore(gameBarResult.viewSwitcherWrap, gameBarResult.gameBarActions.firstChild);
root.appendChild(gameBarResult.gameBar);
root.appendChild(tabsWrap);
const updateStatus = createUpdateStatus(gameBarResult, setup);
return finishBuildUIDOM({ root, setup, gameBarResult, worldMapResult, gridResult, updateStatus });
}