Initial commit

**Motivations:**
- Initialisation du versionning git pour le projet

**Root causes:**
- N/A (Nouveau projet)

**Correctifs:**
- N/A

**Evolutions:**
- Structure initiale du projet
- Ajout du .gitignore

**Pages affectées:**
- Tous les fichiers
This commit is contained in:
2026-03-03 22:24:17 +01:00
commit e031c9a1d2
155 changed files with 22334 additions and 0 deletions

213
web/js/conveyor.js Normal file
View File

@@ -0,0 +1,213 @@
import { GameConfig } from "./config.js";
import { LootTables, getAnimalToEggTypeMap } from "./loot-tables.js";
import { defaultAnimalWeights } from "./state.js";
import { getZooSkillLevel } from "./bot-zoo.js";
import { getConveyorUpgradeCost, getSchoolUpgradeCost, getTruckUpgradeCost } from "./economy.js";
import { pickId } from "./weighted-random.js";
import { cellKey } from "./grid-utils.js";
const ANIMAL_TO_EGG_TYPE = getAnimalToEggTypeMap();
const DEFAULT_ZOO_WEIGHTS = defaultAnimalWeights();
/**
* Skill level = max level among school cells, or state.conveyorLevel for backward compat.
* @param {import("./types.js").GameState} state
* @returns {number}
*/
export function getSkillLevel(state) {
let maxSchool = 0;
for (const cell of Object.values(state.grid.cells)) {
if (cell.kind === "school") maxSchool = Math.max(maxSchool, cell.level);
}
return maxSchool || state.conveyorLevel || 1;
}
/**
* @param {number} conveyorLevel
* @returns {Array<{ id: string, weight: number }>}
*/
function getEligibleEggTypes(conveyorLevel) {
const entries = [];
for (const [eggType, def] of Object.entries(LootTables.EggTypes)) {
if (conveyorLevel >= def.minConveyorLevel)
entries.push({ id: eggType, weight: 100 - def.minConveyorLevel * 8 });
}
return entries;
}
/**
* Player zoo weights from grid: more animals of a type => more of that egg type at own zoo.
* @param {import("./types.js").GameState} state
* @returns {Record<string, number>}
*/
export function getPlayerZooWeights(state) {
const colorKeys = Object.keys(LootTables.EggTypes);
const w = Object.fromEntries(colorKeys.map((k) => [k, 0]));
for (const cell of Object.values(state.grid.cells)) {
if (cell.kind === "animal") {
const eggType = ANIMAL_TO_EGG_TYPE[cell.id];
if (eggType) w[eggType] = (w[eggType] ?? 0) + 1;
}
}
return w;
}
/**
* Zoos that can offer this egg type (skill level allows it and zoo has weight for it).
* @param {import("./types.js").GameState} state
* @param {string} eggType
* @returns {Array<{ id: string, weight: number }>}
*/
function getZoosForEggType(state, eggType) {
const zoos = state.worldZoos ?? [{ id: "player", name: "Mon zoo", x: 25, y: 50, animalWeights: DEFAULT_ZOO_WEIGHTS }];
const eggDef = LootTables.EggTypes[eggType];
const minLevel = eggDef ? eggDef.minConveyorLevel : 1;
const playerWeights = getPlayerZooWeights(state);
const entries = [];
for (const zoo of zoos) {
const skillLevel = zoo.id === "player" ? getSkillLevel(state) : getZooSkillLevel(zoo);
if (skillLevel >= minLevel) {
const weights = zoo.id === "player" ? playerWeights : (zoo.animalWeights ?? {});
const w = weights[eggType] ?? 0;
if (w > 0) entries.push({ id: zoo.id, weight: w });
}
}
if (entries.length === 0) entries.push({ id: "player", weight: 1 });
return entries;
}
/**
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
*/
export function refreshOffers(state, nowUnix) {
const rng = () => Math.random();
const skillLevel = getSkillLevel(state);
const pool = getEligibleEggTypes(skillLevel);
const offers = [];
for (let i = 0; i < GameConfig.Conveyor.OfferCount; i++) {
const eggType = pickId(rng, pool);
const eggDef = LootTables.EggTypes[eggType];
const zooPool = getZoosForEggType(state, eggType);
const zooId = zooPool.length ? pickId(rng, zooPool) : "player";
offers.push({ eggType, price: eggDef.price, zooId });
}
const animalIds = Object.keys(LootTables.Animals ?? {});
if (animalIds.length > 0) {
const babyAnimalId = animalIds[Math.floor(rng() * animalIds.length)];
const babyDef = LootTables.Animals[babyAnimalId];
const babyPrice = babyDef ? Math.floor(50 + (babyDef.rarityLevel ?? 1) * 30) : 80;
offers.push({ type: "baby", animalId: babyAnimalId, price: babyPrice, zooId: "player" });
const adultAnimalId = animalIds[Math.floor(rng() * animalIds.length)];
const adultDef = LootTables.Animals[adultAnimalId];
const adultPrice = adultDef ? Math.floor(80 + (adultDef.rarityLevel ?? 1) * 40) : 120;
offers.push({ type: "animal", animalId: adultAnimalId, price: adultPrice, zooId: "player" });
}
state.conveyorOffers = offers;
state.lastOfferRefreshAt = nowUnix;
}
/**
* @param {import("./types.js").GameState} state
* @param {number} nowUnix
* @returns {boolean}
*/
export function shouldRefresh(state, nowUnix) {
return nowUnix - state.lastOfferRefreshAt >= GameConfig.Conveyor.RefreshSeconds;
}
/**
* Upgrade school at cell (x,y). Returns [ok, reason].
* @param {import("./types.js").GameState} state
* @param {number} x
* @param {number} y
* @returns {[boolean, string?]}
*/
export function tryUpgradeSchool(state, x, y) {
const key = cellKey(x, y);
const cell = state.grid.cells[key];
if (cell === null || cell === undefined || cell.kind !== "school") return [false, "NoSchool"];
const maxLevel = (GameConfig.School && GameConfig.School.MaxLevel) || GameConfig.Conveyor.MaxLevel;
if (cell.level >= maxLevel) return [false, "ConveyorMaxLevel"];
const cost = getSchoolUpgradeCost(cell.level);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
cell.level += 1;
state.conveyorLevel = getSkillLevel(state);
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.conveyorUpgrades = (state.stats.conveyorUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* Upgrade truck. Returns [ok, reason].
* @param {import("./types.js").GameState} state
* @returns {[boolean, string?]}
*/
export function tryUpgradeTruck(state) {
const level = state.truckLevel ?? 1;
const maxLevel = (GameConfig.Truck && GameConfig.Truck.MaxLevel) || 5;
if (level >= maxLevel) return [false, "TruckMaxLevel"];
const cost = getTruckUpgradeCost(level);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
state.truckLevel = level + 1;
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.truckUpgrades = (state.stats.truckUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* @param {import("./types.js").GameState} state
* @returns {[boolean, string?]}
*/
export function tryUpgrade(state) {
if (state.conveyorLevel >= GameConfig.Conveyor.MaxLevel) return [false, "ConveyorMaxLevel"];
const cost = getConveyorUpgradeCost(state.conveyorLevel);
if (state.coins < cost) return [false, "NotEnoughCoins"];
state.coins -= cost;
state.conveyorLevel += 1;
state.lastEvolutionAt = Math.floor(Date.now() / 1000);
if (state.stats) state.stats.conveyorUpgrades = (state.stats.conveyorUpgrades ?? 0) + 1;
return [true, undefined];
}
/**
* @param {import("./types.js").GameState} state
* @param {string} eggType
* @returns {{ eggType: string, price: number, zooId?: string } | null}
*/
export function findOffer(state, eggType) {
return state.conveyorOffers.find((o) => o.eggType === eggType) ?? null;
}
/**
* @param {import("./types.js").GameState} state
* @param {string} [animalId]
* @returns {{ type: "baby", animalId: string, price: number, zooId?: string } | null}
*/
export function findBabyOffer(state, animalId) {
const o = state.conveyorOffers.find((x) => x.type === "baby" && (animalId === null || animalId === undefined || x.animalId === animalId));
return o && o.type === "baby" ? o : null;
}
/**
* @param {import("./types.js").GameState} state
* @param {string} [animalId]
* @returns {{ type: "animal", animalId: string, price: number, zooId?: string } | null}
*/
export function findAnimalOffer(state, animalId) {
const o = state.conveyorOffers.find((x) => x.type === "animal" && (animalId === null || animalId === undefined || x.animalId === animalId));
return o && o.type === "animal" ? o : null;
}
/**
* Pick another zoo (not player) for truck sale animation.
* @param {import("./types.js").GameState} state
* @returns {string}
*/
export function pickSaleTargetZoo(state) {
const zoos = (state.worldZoos ?? []).filter((z) => z.id !== "player");
if (zoos.length === 0) return "player";
return zoos[Math.floor(Math.random() * zoos.length)].id;
}