Files
builazoo/web/js/main.js
ncantu c7d389ecbb 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
2026-03-04 15:32:27 +01:00

202 lines
6.7 KiB
JavaScript

import { defaultState, saveState } from "./state.js";
import { refreshOffers } from "./conveyor.js";
import { render } from "./ui.js";
import { startGameLoop } from "./game-loop.js";
import { playSound, setMusicEnabled, setMusicGetState } from "./audio.js";
import { resolveIncident, INCIDENT_EMOJI } from "./visitor-incidents.js";
import { getAttractionCenter, getVisitorPosition } from "./visitor-attraction.js";
import { incidentLabel, incidentBubbleAria } from "./texts-fr.js";
import { getApiBase, loadZoos, saveMyZoo, setApiBaseUrl } from "./api-client.js";
import { bootstrapFromApi, applyWorldZoos } from "./main-bootstrap.js";
const root = document.getElementById("root");
if (!root) throw new Error("Missing #root");
let state = null;
let myZooId = null;
function setMyZooId(id) {
myZooId = id;
}
async function runBootNoBase(rootEl) {
rootEl.innerHTML = "<div class=\"boot-panel\"><h1>Construis un zoo</h1>" +
"<p>Connectez-vous à un serveur pour jouer (compte et sauvegarde en base).</p>" +
"<div style=\"margin-top: 1rem;\"><label for=\"boot-api-url\">URL du serveur</label><input id=\"boot-api-url\" type=\"text\" placeholder=\"https://...\" style=\"display:block;margin-top:4px;width:100%;\" /></div>" +
"<button id=\"boot-connect\" type=\"button\" style=\"margin-top: 8px;\">Se connecter</button>" +
"<p id=\"boot-err\" class=\"boot-err\"></p></div>";
const urlInput = document.getElementById("boot-api-url");
try {
const stored = localStorage.getItem("builazoo_api_url");
if (stored) urlInput.value = stored;
} catch (_) {
// ignore localStorage
}
const errEl = document.getElementById("boot-err");
await new Promise((resolve) => {
document.getElementById("boot-connect").addEventListener("click", () => {
const url = urlInput.value.trim();
if (!url) {
errEl.textContent = "Indiquez l'URL du serveur.";
return;
}
setApiBaseUrl(url);
resolve();
});
});
rootEl.innerHTML = "";
}
async function runBootWithBase(rootEl) {
rootEl.innerHTML = "<div class=\"boot-panel\"><p>Chargement…</p></div>";
while (true) {
try {
state = await bootstrapFromApi(setMyZooId, rootEl);
break;
} catch (e) {
console.error("bootstrapFromApi failed", e);
rootEl.innerHTML = "<div class=\"boot-panel\"><h1>Construis un zoo</h1><p class=\"boot-err\">Erreur de connexion au serveur.</p><button id=\"boot-retry\" type=\"button\">Réessayer</button></div>";
const errP = rootEl.querySelector(".boot-err");
if (errP && e && e.message) errP.textContent = e.message;
await new Promise((resolve) => {
document.getElementById("boot-retry").addEventListener("click", () => resolve());
});
}
}
rootEl.innerHTML = "";
}
(async () => {
let base = getApiBase();
if (!base) {
await runBootNoBase(root);
base = getApiBase();
}
if (base) {
await runBootWithBase(root);
}
if (state) {
try {
if (localStorage.getItem("builazoo_music") === "1") setMusicEnabled(true);
} catch (_) {
// ignore localStorage
}
let lastHatched = [];
let fullRender = () => {};
function getState() {
return state;
}
function doRestart() {
state = defaultState();
const nowUnix = Math.floor(Date.now() / 1000);
refreshOffers(state, nowUnix);
saveState(state);
fullRender();
}
fullRender = render(root, {
state: getState(),
setState: () => fullRender(),
getLastHatched: () => lastHatched,
onRestart: doRestart,
updateState: (partial) => {
Object.assign(state, partial);
fullRender();
},
});
let lastApiSaveAt = 0;
const MIN_API_SAVE_INTERVAL_MS = 5000;
function saveStateFn(s) {
saveState(s);
if (getApiBase()) {
const now = Date.now();
if (now - lastApiSaveAt >= MIN_API_SAVE_INTERVAL_MS) {
lastApiSaveAt = now;
saveMyZoo(s).catch((e) => console.warn("saveMyZoo failed", e));
}
}
}
setMusicGetState(getState);
startGameLoop(getState, (s, payload) => {
if (payload?.lastHatched?.length) {
lastHatched = payload.lastHatched;
playSound("hatch");
}
fullRender();
setTimeout(() => { lastHatched = []; fullRender(); }, 1800);
}, saveStateFn);
const ZOOS_REFETCH_INTERVAL_MS = 30 * 1000;
setInterval(() => {
loadZoos().then((zoosData) => {
applyWorldZoos(state, { zoosData, playerZooId: state.myZooId ?? myZooId, playerName: state.playerName ?? "Mon zoo", playerX: state.playerX ?? 25, playerY: state.playerY ?? 50 });
fullRender();
}).catch(() => {});
}, ZOOS_REFETCH_INTERVAL_MS);
let visitorAnimTime = 0;
function syncVisitorBubble(el, visitor, index) {
const incidentType = visitor && (visitor.incidentType === null || visitor.incidentType === undefined)
? null
: (visitor && visitor.incidentType);
let bubble = el.querySelector(".visitor-incident-bubble");
if (incidentType) {
if (!bubble) {
bubble = document.createElement("span");
bubble.className = "visitor-incident-bubble";
bubble.setAttribute("role", "button");
bubble.setAttribute("tabindex", "0");
bubble.setAttribute("aria-label", incidentBubbleAria);
bubble.addEventListener("click", () => {
if (resolveIncident(getState(), index)) fullRender();
});
el.appendChild(bubble);
}
bubble.textContent = INCIDENT_EMOJI[incidentType] ?? "❓";
bubble.title = incidentLabel[incidentType] ?? incidentType;
} else if (bubble) {
bubble.remove();
}
}
function updateVisitors() {
const layer = root.querySelector(".visitors-layer");
if (!layer) {
requestAnimationFrame(updateVisitors);
return;
}
const currentState = getState();
const arrivals = currentState.visitorArrivals ?? [];
const n = arrivals.length;
const w = currentState.grid.width;
const h = currentState.grid.height;
while (layer.children.length < n) {
const el = document.createElement("div");
el.className = "visitor-sprite";
el.setAttribute("aria-hidden", "true");
el.textContent = "👤";
layer.appendChild(el);
}
while (layer.children.length > n) {
const last = layer.lastChild;
if (last) last.remove();
}
const { centerX, centerY } = getAttractionCenter(currentState, w, h);
for (let i = 0; i < n; i++) {
const el = layer.children[i];
const { px, py } = getVisitorPosition({
i, n, t: visitorAnimTime, centerX, centerY, gridWidth: w, gridHeight: h,
});
el.style.left = `${px}px`;
el.style.top = `${py}px`;
syncVisitorBubble(el, arrivals[i], i);
}
visitorAnimTime += 0.016;
requestAnimationFrame(updateVisitors);
}
requestAnimationFrame(updateVisitors);
}
})();