import { WORLD_MAP_HEIGHT, WORLD_MAP_WIDTH } from "./constants"; import units from "./data/units"; import { WORLDMAP_TYPES, type BuildingType, type CostType, type OasisType, type Point, type ProductionType, type ResourcesType } from "./types"; import type { VillageState } from "./village"; function _reduceResources(acc: ResourcesType, item: ResourcesType): ResourcesType { return { wood: acc.wood + item.wood, stone: acc.stone + item.stone, iron: acc.iron + item.iron, food: acc.food + item.food, culture: acc.culture + item.culture, }; } export function getEmptyResources(): ResourcesType { return { wood: 0, stone: 0, iron: 0, food: 0, culture: 0, }; } export function getProduction(villageState: VillageState): ResourcesType { let production = getEmptyResources(); // Add buildings production and intake. production = villageState.buildings .filter(b => b.behavior.production && b.level > 0) .map(b => { if (b.behavior.production) { return b.behavior.production(villageState, b); } }) .reduce(_reduceResources, production); // Add units production and intake. ['philosopher', 'soldier'].forEach(type => { const unit = getUnitSource(type); const unitCount = villageState.units[type] || 0; // Add food intake. const intakePerMinute = unit.behavior.foodIntakePerMinute * unitCount; production.food -= intakePerMinute; // Add culture production for Philosophers. if (type === 'philosopher') { const outputPerMinute = unit.behavior.culturePerMinute || 0; production.culture += outputPerMinute * unitCount; } }); return production; } export function getStorage(villageState: VillageState): ProductionType { return villageState.buildings .filter(b => b.behavior.storage && b.level > 0) .map(b => { if (b.behavior.storage) { return b.behavior.storage(villageState, b); } }) .reduce(_reduceResources, getEmptyResources()); } export function getBuilding(V: VillageState, buildingId: number): BuildingType { const building = V.buildings.find(b => b.id === buildingId); if (!building) { throw new Error(`Cannot find building with id "${buildingId}"`); } return building; } export function getUnitSource(unitType: string) { const unit = units.find(u => u.type === unitType); if (unit === undefined) { throw new Error(`Unknown unit type: "${unitType}"`); } return unit; } export function getRegionsWithMissions(V: VillageState): OasisType[] { return V.worldmap.filter(r => r.type === WORLDMAP_TYPES.OASIS); } export function getRemainingUnitCount(V: VillageState, unitType: string) { let total = V.units[unitType] || 0; const missions = getRegionsWithMissions(V) .filter(r => r.state.mission?.unitType === unitType) .map(r => r.state.mission); missions.forEach(m => total -= m?.unitCount || 0); return total; } export function getKeysAsNumbers(dict: Object): Array { return Object.keys(dict).map(i => parseInt(i)).sort((a, b) => a - b); } /** * Creates an array of shuffled values, using a version of the * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). * * @since 0.1.0 * @category Array * @param {Array} array The array to shuffle. * @returns {Array} Returns the new shuffled array. * @example * * shuffle([1, 2, 3, 4]) * // => [4, 1, 3, 2] */ export function shuffle(array: Array): Array { const length = array.length; if (!length) { return []; } let index = -1; const lastIndex = length - 1; const result = [ ...array ]; while (++index < length) { const rand = index + Math.floor(Math.random() * (lastIndex - index + 1)); [ result[rand], result[index] ] = [ result[index], result[rand] ]; } return result; } /** * Return a random integer in the range [ min, max ] (min and max can be returned). * @param min integer * @param max integer * @returns integer */ export function random(min: number, max?: number) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1.0)); } export function getBuildingUpgradeCost(_V: VillageState, building: BuildingType): CostType { const level = building.level + 1; return building.cost(level); } export function canPayBuildingCost(V: VillageState, building: BuildingType): boolean { const cost = getBuildingUpgradeCost(V, building); return !( cost.wood > V.resources.wood || cost.stone > V.resources.stone || cost.iron > V.resources.iron || cost.food > V.resources.food ); } export function getTownhall(V: VillageState): BuildingType { const townhall = V.buildings.find(b => b.type === 'townhall'); if (!townhall) { throw new Error("Unable to find the Town hall"); } return townhall; } export function assert(condition: any, msg?: string): asserts condition { if (!condition) { throw new Error(msg); } } export function getAdjacentWorldmapCells(cellIndex: number) { const cells = [ cellIndex - WORLD_MAP_WIDTH - 1, cellIndex - WORLD_MAP_WIDTH, cellIndex - WORLD_MAP_WIDTH + 1, cellIndex - 1, cellIndex + 1, cellIndex + WORLD_MAP_WIDTH - 1, cellIndex + WORLD_MAP_WIDTH, cellIndex + WORLD_MAP_WIDTH + 1, ]; return cells.filter(c => c >= 0 && c < WORLD_MAP_WIDTH * WORLD_MAP_HEIGHT); } export function distanceBetweenCells(a: Point, b: Point) { return Math.sqrt( Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2) ); } export function indexToPoint(index: number): Point { return { x: index % WORLD_MAP_WIDTH, y: Math.floor(index / WORLD_MAP_WIDTH), }; } export function getPrettyTime(milliseconds: number) { const timeInSeconds = Math.ceil(milliseconds / 1000.0); return `${timeInSeconds}s`; }