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:
242
web/js/ui-world-map.js
Normal file
242
web/js/ui-world-map.js
Normal file
@@ -0,0 +1,242 @@
|
||||
import { getCellBiome } from "./biome-rules.js";
|
||||
import { mapServerListingToClient } from "./api-client.js";
|
||||
import { defaultAnimalWeights } from "./state.js";
|
||||
import { eggTypeLabel, animalLabel, salesPanelAriaLabel } from "./texts-fr.js";
|
||||
import { addSalesPanelSellerSection, addSalesPanelBuyerSection, addSalesPanelActiveSection } from "./ui-world-map-sales.js";
|
||||
import { renderCities } from "./ui-world-map-cities.js";
|
||||
import { renderLab, renderTruckAndNpcTrucks } from "./ui-world-map-trucks.js";
|
||||
|
||||
const EGG_EMOJI = "🥚";
|
||||
const WORLD_MAP_GRID_COLS = 12;
|
||||
const WORLD_MAP_GRID_ROWS = 8;
|
||||
|
||||
/**
|
||||
* @param {{ worldMapEl: HTMLElement, state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, animalEmoji: Record<string, string> }} ctx
|
||||
* @param {import("./api-client.js").SalesFromApi | null} api
|
||||
* @param {string} playerZooId
|
||||
*/
|
||||
function renderSalesPanel(ctx, api, playerZooId) {
|
||||
const salesPanel = document.createElement("div");
|
||||
salesPanel.className = "world-map-sales-panel";
|
||||
salesPanel.setAttribute("aria-label", salesPanelAriaLabel);
|
||||
if (api) {
|
||||
addSalesPanelSellerSection(salesPanel, api, ctx);
|
||||
addSalesPanelBuyerSection(salesPanel, api, ctx);
|
||||
addSalesPanelActiveSection(salesPanel, api, playerZooId, ctx);
|
||||
}
|
||||
if (salesPanel.childNodes.length > 0) ctx.worldMapEl.appendChild(salesPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ worldMapEl: HTMLElement }} ctx
|
||||
*/
|
||||
function renderCellsLayer(ctx) {
|
||||
const cellsLayer = document.createElement("div");
|
||||
cellsLayer.className = "world-map-cells";
|
||||
cellsLayer.setAttribute("aria-hidden", "true");
|
||||
cellsLayer.style.gridTemplateColumns = `repeat(${WORLD_MAP_GRID_COLS}, 1fr)`;
|
||||
cellsLayer.style.gridTemplateRows = `repeat(${WORLD_MAP_GRID_ROWS}, 1fr)`;
|
||||
for (let row = 0; row < WORLD_MAP_GRID_ROWS; row++) {
|
||||
for (let col = 0; col < WORLD_MAP_GRID_COLS; col++) {
|
||||
const cellDiv = document.createElement("div");
|
||||
cellDiv.className = "world-map-cell";
|
||||
const biome = getCellBiome(WORLD_MAP_GRID_COLS, WORLD_MAP_GRID_ROWS, col + 1, row + 1);
|
||||
cellDiv.classList.add(`world-map-cell-${biome.toLowerCase()}`);
|
||||
cellsLayer.appendChild(cellDiv);
|
||||
}
|
||||
}
|
||||
ctx.worldMapEl.appendChild(cellsLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} slotEl
|
||||
* @param {Array<{ animalId: string, isBaby: boolean, price: number }>} listings
|
||||
* @param {{ animalEmoji: Record<string, string> }} ctx
|
||||
*/
|
||||
function addZooSlotListings(slotEl, listings, ctx) {
|
||||
for (const listing of listings.slice(0, 3)) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "world-map-sale-listing";
|
||||
const emoji = ctx.animalEmoji[listing.animalId] ?? "🐾";
|
||||
const label = listing.isBaby ? `Bébé ${animalLabel[listing.animalId] ?? listing.animalId}` : (animalLabel[listing.animalId] ?? listing.animalId);
|
||||
el.innerHTML = `<span class="offer-emoji">${emoji}</span><span class="offer-label">${label}</span><span class="offer-price">${listing.price} 💰</span>`;
|
||||
el.title = "En vente sur la carte (phase 10)";
|
||||
slotEl.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} slotEl
|
||||
* @param {{ eggType: string, price: number }} offer
|
||||
* @param {string} zooId
|
||||
* @param {{ state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void }} ctx
|
||||
*/
|
||||
function addZooSlotNpcOffer(slotEl, offer, zooId, ctx) {
|
||||
const { setState, setError } = ctx;
|
||||
const el = document.createElement("div");
|
||||
el.className = "offer-btn world-map-offer world-map-offer-single";
|
||||
el.setAttribute("role", "button");
|
||||
el.setAttribute("tabindex", "0");
|
||||
el.setAttribute("draggable", "true");
|
||||
const name = eggTypeLabel[offer.eggType] ?? offer.eggType;
|
||||
el.innerHTML = `<span class="offer-emoji">${EGG_EMOJI}</span><span class="offer-label">${name}</span><span class="offer-price">${offer.price} pièces</span>`;
|
||||
let dragStarted = false;
|
||||
el.addEventListener("dragstart", (e) => {
|
||||
dragStarted = true;
|
||||
e.dataTransfer.setData("application/x-builazoo-eggtype", offer.eggType);
|
||||
e.dataTransfer.setData("application/x-builazoo-offer-zooid", zooId);
|
||||
e.dataTransfer.effectAllowed = "copy";
|
||||
el.classList.add("dragging");
|
||||
});
|
||||
el.addEventListener("dragend", () => {
|
||||
dragStarted = false;
|
||||
el.classList.remove("dragging");
|
||||
});
|
||||
el.addEventListener("click", () => {
|
||||
if (dragStarted) return;
|
||||
setError("Glissez l'œuf sur le camion pour l'acheter.");
|
||||
setState();
|
||||
});
|
||||
el.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
slotEl.appendChild(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} slotEl
|
||||
* @param {{ animalId: string, price: number } | null} babyOffer
|
||||
* @param {{ animalId: string, price: number } | null} animalOffer
|
||||
* @param {{ animalEmoji: Record<string, string> }} ctx
|
||||
*/
|
||||
function addZooSlotPlayerOffers(slotEl, babyOffer, animalOffer, ctx) {
|
||||
if (babyOffer) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "offer-btn world-map-offer";
|
||||
el.setAttribute("draggable", "true");
|
||||
const emoji = ctx.animalEmoji[babyOffer.animalId] ?? "🐾";
|
||||
const name = animalLabel[babyOffer.animalId] ?? babyOffer.animalId;
|
||||
el.innerHTML = `<span class="offer-emoji">${emoji}</span><span class="offer-label">Bébé ${name}</span><span class="offer-price">${babyOffer.price}</span>`;
|
||||
el.addEventListener("dragstart", (e) => {
|
||||
e.dataTransfer.setData("application/x-builazoo-baby-offer", `${babyOffer.animalId}:${babyOffer.price}`);
|
||||
e.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
slotEl.appendChild(el);
|
||||
}
|
||||
if (animalOffer) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "offer-btn world-map-offer";
|
||||
el.setAttribute("draggable", "true");
|
||||
const emoji = ctx.animalEmoji[animalOffer.animalId] ?? "🐾";
|
||||
const name = animalLabel[animalOffer.animalId] ?? animalOffer.animalId;
|
||||
el.innerHTML = `<span class="offer-emoji">${emoji}</span><span class="offer-label">${name}</span><span class="offer-price">${animalOffer.price}</span>`;
|
||||
el.addEventListener("dragstart", (e) => {
|
||||
e.dataTransfer.setData("application/x-builazoo-animal-offer", `${animalOffer.animalId}:${animalOffer.price}`);
|
||||
e.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
slotEl.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ slotEl: HTMLElement, zoo: import("./types.js").WorldZoo, isPlayer: boolean, zooListings: Array<{ animalId: string, isBaby: boolean, price: number }>, oneOffer: import("./conveyor.js").ConveyorOffer | null, playerBabyOffer: { animalId: string, price: number } | null, playerAnimalOffer: { animalId: string, price: number } | null, ctx: { animalEmoji: Record<string, string>, setState: () => void, setError: (s: string) => void } }} opts
|
||||
*/
|
||||
function fillZooSlotContent(opts) {
|
||||
const { slotEl, zoo, isPlayer, zooListings, oneOffer, playerBabyOffer, playerAnimalOffer, ctx } = opts;
|
||||
if (isPlayer && zooListings.length > 0) {
|
||||
addZooSlotListings(slotEl, zooListings, ctx);
|
||||
} else if (oneOffer) {
|
||||
addZooSlotNpcOffer(slotEl, oneOffer, zoo.id, ctx);
|
||||
} else if (isPlayer && (playerBabyOffer || playerAnimalOffer)) {
|
||||
addZooSlotPlayerOffers(slotEl, playerBabyOffer, playerAnimalOffer, ctx);
|
||||
} else {
|
||||
const iconEl = document.createElement("span");
|
||||
iconEl.className = "world-map-zoo-icon";
|
||||
iconEl.setAttribute("aria-hidden", "true");
|
||||
iconEl.textContent = "🏠";
|
||||
slotEl.appendChild(iconEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ worldMapEl: HTMLElement, state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, animalEmoji: Record<string, string> }} ctx
|
||||
* @param {import("./types.js").WorldZoo} zoo
|
||||
* @param {Array<{ animalId: string, isBaby: boolean, price: number }>} zooListingsForPlayer
|
||||
* @param {Array<import("./conveyor.js").ConveyorOffer>} offers
|
||||
*/
|
||||
function buildZooNode(ctx, zoo, zooListingsForPlayer, offers) {
|
||||
const { state, worldMapEl } = ctx;
|
||||
const isPlayer = zoo.id === "player";
|
||||
const zooOffers = offers.filter((o) => (o.zooId ?? "player") === zoo.id);
|
||||
const oneOffer = !isPlayer && zooOffers.length > 0 ? zooOffers[0] : null;
|
||||
const playerBabyOffer = isPlayer ? zooOffers.find((o) => o.type === "baby") : null;
|
||||
const playerAnimalOffer = isPlayer ? zooOffers.find((o) => o.type === "animal") : null;
|
||||
const node = document.createElement("div");
|
||||
node.className = "world-map-zoo" + (isPlayer ? " world-map-zoo-player" : "");
|
||||
node.style.left = `${zoo.x}%`;
|
||||
node.style.top = `${zoo.y}%`;
|
||||
node.dataset.zooId = zoo.id;
|
||||
const nameEl = document.createElement("div");
|
||||
nameEl.className = "world-map-zoo-name";
|
||||
nameEl.textContent = zoo.name;
|
||||
node.appendChild(nameEl);
|
||||
if (isPlayer) {
|
||||
const scoreEl = document.createElement("div");
|
||||
scoreEl.className = "world-map-zoo-reproduction-score";
|
||||
scoreEl.textContent = `Score repro: ${(state.reproductionScore ?? 0).toFixed(1)}`;
|
||||
node.appendChild(scoreEl);
|
||||
const attrEl = document.createElement("div");
|
||||
attrEl.className = "world-map-zoo-attractivity-score";
|
||||
attrEl.textContent = `Score attractivité: ${(state.attractivityScore ?? 0).toFixed(1)}`;
|
||||
node.appendChild(attrEl);
|
||||
}
|
||||
if (!isPlayer && zoo.botState) {
|
||||
const indEl = document.createElement("div");
|
||||
indEl.className = "world-map-zoo-indicators";
|
||||
indEl.textContent = `${Math.floor(zoo.botState.coins)} · Parcelle ${zoo.botState.plotLevel}`;
|
||||
node.appendChild(indEl);
|
||||
}
|
||||
const slotEl = document.createElement("div");
|
||||
slotEl.className = "world-map-zoo-slot";
|
||||
const zooListings = isPlayer ? zooListingsForPlayer : [];
|
||||
fillZooSlotContent({ slotEl, zoo, isPlayer, zooListings, oneOffer, playerBabyOffer: playerBabyOffer ?? null, playerAnimalOffer: playerAnimalOffer ?? null, ctx });
|
||||
node.appendChild(slotEl);
|
||||
worldMapEl.appendChild(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ worldMapEl: HTMLElement, state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, animalEmoji: Record<string, string> }} ctx
|
||||
* @param {Array<import("./types.js").WorldZoo>} zoos
|
||||
* @param {Array<import("./conveyor.js").ConveyorOffer>} offers
|
||||
* @param {Array<{ animalId: string, isBaby: boolean, price: number }>} zooListingsForPlayer
|
||||
*/
|
||||
function renderZoos(ctx, zoos, offers, zooListingsForPlayer) {
|
||||
for (const zoo of zoos) {
|
||||
buildZooNode(ctx, zoo, zooListingsForPlayer, offers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ worldMapEl: HTMLElement, worldMapTruckEl: HTMLElement, worldMapNpcTrucksEl: HTMLElement, state: import("./types.js").GameState, setState: () => void, setError: (s: string) => void, playSound: (s: string) => void, animalEmoji: Record<string, string>, pendingTokenByEggType: Record<string, number> }} ctx
|
||||
*/
|
||||
export function renderWorldMap(ctx) {
|
||||
ctx.worldMapEl.innerHTML = "";
|
||||
const playerZooId = ctx.state.myZooId ?? "player";
|
||||
const api = ctx.state.salesFromApi;
|
||||
const myListingsFromApi = api?.asSeller ? api.asSeller.map(mapServerListingToClient) : null;
|
||||
const zooListingsForPlayer = myListingsFromApi ?? (ctx.state.saleListings ?? []).filter((s) => s.zooId === playerZooId);
|
||||
renderSalesPanel(ctx, api, playerZooId);
|
||||
renderCellsLayer(ctx);
|
||||
const zoos = ctx.state.worldZoos ?? [{ id: "player", name: "Mon zoo", x: 25, y: 50, animalWeights: defaultAnimalWeights() }];
|
||||
const offers = ctx.state.conveyorOffers || [];
|
||||
renderZoos(ctx, zoos, offers, zooListingsForPlayer);
|
||||
renderCities(ctx);
|
||||
renderLab(ctx);
|
||||
renderTruckAndNpcTrucks(ctx, zoos);
|
||||
}
|
||||
|
||||
export { renderSalesPanel, renderCellsLayer, renderZoos, WORLD_MAP_GRID_COLS, WORLD_MAP_GRID_ROWS, EGG_EMOJI };
|
||||
export { renderCities } from "./ui-world-map-cities.js";
|
||||
Reference in New Issue
Block a user