mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
239 lines
8.1 KiB
JavaScript
239 lines
8.1 KiB
JavaScript
'use strict';
|
|
|
|
window.Trade = (function () {
|
|
const defineCenters = () => {
|
|
TIME && console.time('defineCenters');
|
|
pack.trade = {centers: [], deals: []};
|
|
const {burgs, trade} = pack;
|
|
|
|
// min distance between trade centers
|
|
let minSpacing = (((graphWidth + graphHeight) * 2) / burgs.length ** 0.7) | 0;
|
|
|
|
const tradeScore = burgs.map(({i, removed, capital, port, population, produced}) => {
|
|
if (!i || removed) return {i: 0, score: 0};
|
|
const totalProduction = d3.sum(Object.values(produced));
|
|
let score = Math.round(totalProduction - population);
|
|
if (capital) score *= 2;
|
|
if (port) score *= 2;
|
|
return {i, score};
|
|
});
|
|
|
|
const candidatesSorted = tradeScore.sort((a, b) => b.score - a.score);
|
|
const centersTree = d3.quadtree();
|
|
|
|
for (const {i: burgId} of candidatesSorted) {
|
|
if (!burgId) continue;
|
|
const burg = burgs[burgId];
|
|
const {x, y} = burg;
|
|
|
|
const tradeCenter = centersTree.find(x, y, minSpacing);
|
|
|
|
if (tradeCenter) {
|
|
const tradeCenterId = tradeCenter[2];
|
|
burg.tradeCenter = tradeCenterId;
|
|
trade.centers[tradeCenterId].burgs.push({i: burgId});
|
|
} else {
|
|
const tradeCenterId = trade.centers.length;
|
|
trade.centers.push({i: tradeCenterId, burg: burgId, burgs: [{i: burgId}]});
|
|
centersTree.add([x, y, tradeCenterId]);
|
|
burg.tradeCenter = tradeCenterId;
|
|
}
|
|
|
|
minSpacing += 1;
|
|
}
|
|
|
|
// TODO: remove debug rendering
|
|
for (const burg of burgs) {
|
|
const {i, x: x1, y: y1, tradeCenter} = burg;
|
|
if (!i) continue;
|
|
|
|
const center = trade.centers[tradeCenter];
|
|
const {x: x2, y: y2} = burgs[center.burg];
|
|
debug.append('line').attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2).attr('stroke', 'black').attr('stroke-width', 0.2);
|
|
}
|
|
for (const {i, score} of candidatesSorted) {
|
|
if (!i) continue;
|
|
const {x, y, capital} = burgs[i];
|
|
debug
|
|
.append('text')
|
|
.attr('x', x)
|
|
.attr('y', y)
|
|
.style('font-size', capital ? 5 : 3)
|
|
.style('fill', 'blue')
|
|
.text(score);
|
|
}
|
|
for (const tradeCenter of trade.centers) {
|
|
const {x, y} = burgs[tradeCenter.burg];
|
|
debug
|
|
.append('circle')
|
|
.attr('cx', x - 4)
|
|
.attr('cy', y - 4)
|
|
.attr('r', 2)
|
|
.style('stroke', '#000')
|
|
.style('stroke-width', 0.2)
|
|
.style('fill', 'white');
|
|
debug
|
|
.append('text')
|
|
.attr('x', x - 4)
|
|
.attr('y', y - 4)
|
|
.style('font-size', 3)
|
|
.text(tradeCenter.i);
|
|
}
|
|
|
|
TIME && console.timeEnd('defineCenters');
|
|
};
|
|
|
|
const calculateDistances = () => {
|
|
TIME && console.time('calculateDistances');
|
|
const {cells, burgs, trade} = pack;
|
|
const {centers} = trade;
|
|
|
|
const getCost = (dist, sameFeature, sameFeaturePorts) => {
|
|
if (sameFeaturePorts) return dist / 2;
|
|
if (sameFeature) return dist;
|
|
return dist * 1.5;
|
|
};
|
|
|
|
const costs = new Array(centers.length);
|
|
for (let i = 0; i < centers.length; i++) {
|
|
costs[i] = new Array(centers.length);
|
|
const {x: x1, y: y1, port: port1, cell: cell1} = burgs[centers[i].burg];
|
|
|
|
for (let j = i + 1; j < centers.length; j++) {
|
|
const {x: x2, y: y2, port: port2, cell: cell2} = burgs[centers[j].burg];
|
|
const distance = Math.hypot(x1 - x2, y1 - y2);
|
|
const sameFeature = cells.f[cell1] === cells.f[cell2];
|
|
const sameFeaturePorts = port1 && port2 && port1 === port2;
|
|
costs[i][j] = getCost(distance, sameFeature, sameFeaturePorts) | 0;
|
|
}
|
|
}
|
|
|
|
for (const center of centers) {
|
|
// nearers trade centers
|
|
center.nearest = centers.map(({i}) => {
|
|
const cost = center.i < i ? costs[center.i][i] : costs[i][center.i];
|
|
return {i, cost: cost || 0};
|
|
});
|
|
center.nearest.sort((a, b) => a.cost - b.cost);
|
|
|
|
// distance cost to burgs
|
|
const {x: x1, y: y1, port: port1, cell: cell1} = burgs[center.burg];
|
|
center.burgs = center.burgs.map(({i: burgId}) => {
|
|
const {x: x2, y: y2, port: port2, cell: cell2} = burgs[burgId];
|
|
|
|
const distance = Math.hypot(x1 - x2, y1 - y2);
|
|
const sameFeature = cells.f[cell1] === cells.f[cell2];
|
|
const sameFeaturePorts = port1 && port2 && port1 === port2;
|
|
const cost = getCost(distance, sameFeature, sameFeaturePorts) | 0;
|
|
return {i: burgId, cost};
|
|
});
|
|
}
|
|
|
|
TIME && console.timeEnd('calculateDistances');
|
|
};
|
|
|
|
const exportGoods = () => {
|
|
const {burgs, states, trade} = pack;
|
|
const DEFAULT_TRANSPORT_DIST = (graphWidth + graphHeight) / 20;
|
|
|
|
for (const tradeCenter of trade.centers) {
|
|
const {i: centerId, burgs: centerBurgs} = tradeCenter;
|
|
const tradeCenterGoods = {};
|
|
|
|
for (const {i: burgId, cost: distanceCost} of centerBurgs) {
|
|
const burg = burgs[burgId];
|
|
const {i, removed, produced, population, state} = burg;
|
|
if (!i || removed) continue;
|
|
const consumption = Math.ceil(population);
|
|
const exportPool = {};
|
|
|
|
const transportFee = (distanceCost / DEFAULT_TRANSPORT_DIST) ** 0.8 || 0.02;
|
|
const salesTax = states[state].salesTax || 0;
|
|
let income = 0;
|
|
|
|
const categorized = {};
|
|
for (const resourceId in produced) {
|
|
const {category} = Resources.get(+resourceId);
|
|
if (!categorized[category]) categorized[category] = {};
|
|
categorized[category][resourceId] = produced[resourceId];
|
|
}
|
|
|
|
for (const category in categorized) {
|
|
const categoryProduction = d3.sum(Object.values(categorized[category]));
|
|
const exportQuantity = categoryProduction - consumption;
|
|
if (exportQuantity <= 0) continue;
|
|
|
|
for (const resourceId in categorized[category]) {
|
|
const production = categorized[category][resourceId];
|
|
const quantity = Math.round((production / categoryProduction) * exportQuantity);
|
|
if (quantity <= 0) continue;
|
|
|
|
const {value, name} = Resources.get(+resourceId);
|
|
|
|
const basePrice = value * quantity;
|
|
const transportCost = rn((value * quantity) ** 0.5 * transportFee, 1);
|
|
const netPrice = basePrice - transportCost;
|
|
|
|
const stateIncome = rn(netPrice * salesTax, 1);
|
|
const burgIncome = rn(netPrice - stateIncome, 1);
|
|
|
|
if (burgIncome < 1 || burgIncome < basePrice / 4) continue;
|
|
|
|
trade.deals.push({resourceId: +resourceId, name, quantity, exporter: i, tradeCenter: centerId, basePrice, transportCost, stateIncome, burgIncome});
|
|
income += burgIncome;
|
|
|
|
if (!exportPool[resourceId]) exportPool[resourceId] = quantity;
|
|
else exportPool[resourceId] += quantity;
|
|
|
|
if (!tradeCenterGoods[resourceId]) tradeCenterGoods[resourceId] = quantity;
|
|
else tradeCenterGoods[resourceId] += quantity;
|
|
}
|
|
}
|
|
|
|
burg.exported = exportPool;
|
|
burg.income = income;
|
|
}
|
|
|
|
tradeCenter.supply = tradeCenterGoods;
|
|
}
|
|
};
|
|
|
|
const importGoods = () => {
|
|
const {resources, burgs, states, trade} = pack;
|
|
|
|
for (const burg of burgs) {
|
|
const {i, removed, tradeCenter: localTradeCenterId, x, y, produced, population} = burg;
|
|
if (!i || removed) continue;
|
|
|
|
const importPool = {};
|
|
const localTradeCenter = trade.centers[localTradeCenterId];
|
|
|
|
let demand = Math.ceil(population);
|
|
|
|
for (const resource of resources) {
|
|
const {i: resourceId, value, category} = resource;
|
|
if (produced[resourceId]) continue;
|
|
|
|
// check for resource supply on markets starting from closest
|
|
for (const {i: tradeCenterId, cost: transportCost} of localTradeCenter.nearest) {
|
|
const tradeCenter = trade.centers[tradeCenterId];
|
|
const stored = tradeCenter.supply[resourceId];
|
|
if (!stored) continue;
|
|
|
|
const quantity = Math.min(demand, stored);
|
|
importPool[resourceId] = quantity;
|
|
|
|
break;
|
|
|
|
tradeCenter.supply[resourceId] -= quantity;
|
|
demand -= quantity;
|
|
if (demand <= 0) break;
|
|
}
|
|
}
|
|
|
|
burg.imported = importPool;
|
|
}
|
|
};
|
|
|
|
return {defineCenters, calculateDistances, exportGoods, importGoods};
|
|
})();
|