bourgade/src/utils.ts

219 lines
5.9 KiB
TypeScript
Raw Normal View History

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";
2024-11-05 11:59:25 +01:00
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,
2024-11-05 11:59:25 +01:00
culture: acc.culture + item.culture,
};
}
2024-11-05 11:59:25 +01:00
export function getEmptyResources(): ResourcesType {
return {
wood: 0,
stone: 0,
iron: 0,
food: 0,
2024-11-05 11:59:25 +01:00
culture: 0,
};
}
2024-11-05 11:59:25 +01:00
export function getProduction(villageState: VillageState): ResourcesType {
2024-11-05 11:35:32 +01:00
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);
}
})
2024-11-05 11:35:32 +01:00
.reduce(_reduceResources, production);
2024-11-05 11:59:25 +01:00
// Add units production and intake.
2024-11-07 10:12:29 +01:00
['philosopher', 'soldier'].forEach(type => {
2024-11-05 11:35:32 +01:00
const unit = getUnitSource(type);
const unitCount = villageState.units[type] || 0;
2024-11-05 11:59:25 +01:00
// Add food intake.
2024-11-05 11:35:32 +01:00
const intakePerMinute = unit.behavior.foodIntakePerMinute * unitCount;
production.food -= intakePerMinute;
2024-11-05 11:59:25 +01:00
// Add culture production for Philosophers.
if (type === 'philosopher') {
2024-11-07 10:12:29 +01:00
const outputPerMinute = unit.behavior.culturePerMinute || 0;
2024-11-05 11:59:25 +01:00
production.culture += outputPerMinute * unitCount;
}
2024-11-05 11:35:32 +01:00
});
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 {
2024-10-24 15:12:21 +02:00
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);
}
2024-11-07 14:20:34 +01:00
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);
2024-11-07 14:20:34 +01:00
missions.forEach(m => total -= m?.unitCount || 0);
return total;
}
export function getKeysAsNumbers(dict: Object): Array<number> {
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<T>(array: Array<T>): Array<T> {
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),
};
}