Ajout de la commande /voyage et grosse MAJK de la commande /auberge
This commit is contained in:
218
modules/travelv2/pathfinding.js
Normal file
218
modules/travelv2/pathfinding.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Algorithmes de calcul de plus court chemin pour les voyages
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classe pour le calcul de plus court chemin (algorithme de Dijkstra)
|
||||
*/
|
||||
export class PathFinder {
|
||||
/**
|
||||
* Construit un graphe à partir des données de voyage
|
||||
* @param {Array} travelData - Données de voyage
|
||||
* @param {String|Array} modes - Mode(s) de transport ('road', 'river', 'sea', ['road', 'river', 'sea'], 'water')
|
||||
* @returns {Object} Graphe avec adjacence et poids
|
||||
*/
|
||||
static buildGraph(travelData, modes = 'road') {
|
||||
const graph = {};
|
||||
|
||||
// Normaliser modes en tableau
|
||||
let modeList = [];
|
||||
if (modes === 'water') {
|
||||
modeList = ['river', 'sea'];
|
||||
} else if (modes === 'mixed') {
|
||||
modeList = ['road', 'river', 'sea'];
|
||||
} else if (Array.isArray(modes)) {
|
||||
modeList = modes;
|
||||
} else {
|
||||
modeList = [modes];
|
||||
}
|
||||
|
||||
for (const route of travelData) {
|
||||
const from = route.from;
|
||||
const to = route.to;
|
||||
|
||||
// Initialiser les nœuds
|
||||
if (!graph[from]) {
|
||||
graph[from] = [];
|
||||
}
|
||||
if (!graph[to]) {
|
||||
graph[to] = [];
|
||||
}
|
||||
|
||||
// Pour chaque mode de transport disponible
|
||||
for (const mode of modeList) {
|
||||
const distanceKey = `${mode}_distance`;
|
||||
const daysKey = `${mode}_days`;
|
||||
const distance = route[distanceKey];
|
||||
const days = route[daysKey];
|
||||
|
||||
// Ignorer les routes sans ce mode de transport
|
||||
if (!distance || distance === "" || !days || days === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ajouter les arêtes (bidirectionnelles)
|
||||
graph[from].push({
|
||||
destination: to,
|
||||
distance: parseFloat(distance),
|
||||
days: parseFloat(days),
|
||||
danger: route[`${mode}_danger`] || "",
|
||||
mode: mode
|
||||
});
|
||||
|
||||
graph[to].push({
|
||||
destination: from,
|
||||
distance: parseFloat(distance),
|
||||
days: parseFloat(days),
|
||||
danger: route[`${mode}_danger`] || "",
|
||||
mode: mode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithme de Dijkstra pour trouver le plus court chemin
|
||||
* @param {Object} graph - Graphe d'adjacence
|
||||
* @param {String} start - Ville de départ
|
||||
* @param {String} end - Ville d'arrivée
|
||||
* @param {String} metric - Métrique à minimiser ('distance' ou 'days')
|
||||
* @returns {Object|null} Chemin trouvé avec détails ou null si pas de chemin
|
||||
*/
|
||||
static dijkstra(graph, start, end, metric = 'days') {
|
||||
// Normaliser les noms de villes
|
||||
const normalizedStart = this.findCityInGraph(graph, start);
|
||||
const normalizedEnd = this.findCityInGraph(graph, end);
|
||||
|
||||
if (!normalizedStart || !normalizedEnd) {
|
||||
console.warn(`PathFinder: Ville non trouvée - start: ${start}, end: ${end}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Initialisation
|
||||
const distances = {};
|
||||
const previous = {};
|
||||
const visited = new Set();
|
||||
const queue = [];
|
||||
|
||||
// Initialiser toutes les distances à l'infini
|
||||
for (const node in graph) {
|
||||
distances[node] = Infinity;
|
||||
previous[node] = null;
|
||||
}
|
||||
|
||||
distances[normalizedStart] = 0;
|
||||
queue.push({ node: normalizedStart, distance: 0 });
|
||||
|
||||
while (queue.length > 0) {
|
||||
// Trouver le nœud avec la plus petite distance
|
||||
queue.sort((a, b) => a.distance - b.distance);
|
||||
const { node: current } = queue.shift();
|
||||
|
||||
if (visited.has(current)) continue;
|
||||
visited.add(current);
|
||||
|
||||
// Si on a atteint la destination
|
||||
if (current === normalizedEnd) {
|
||||
return this.reconstructPath(previous, normalizedStart, normalizedEnd, graph, metric);
|
||||
}
|
||||
|
||||
// Explorer les voisins
|
||||
const neighbors = graph[current] || [];
|
||||
for (const neighbor of neighbors) {
|
||||
if (visited.has(neighbor.destination)) continue;
|
||||
|
||||
const weight = neighbor[metric]; // 'distance' ou 'days'
|
||||
const newDistance = distances[current] + weight;
|
||||
|
||||
if (newDistance < distances[neighbor.destination]) {
|
||||
distances[neighbor.destination] = newDistance;
|
||||
previous[neighbor.destination] = {
|
||||
from: current,
|
||||
edge: neighbor
|
||||
};
|
||||
queue.push({ node: neighbor.destination, distance: newDistance });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pas de chemin trouvé
|
||||
console.warn(`PathFinder: Aucun chemin trouvé entre ${start} et ${end}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une ville dans le graphe (insensible à la casse)
|
||||
* @param {Object} graph - Graphe
|
||||
* @param {String} cityName - Nom de la ville
|
||||
* @returns {String|null} Nom normalisé de la ville ou null
|
||||
*/
|
||||
static findCityInGraph(graph, cityName) {
|
||||
const lowerName = cityName.toLowerCase();
|
||||
for (const city in graph) {
|
||||
if (city.toLowerCase() === lowerName) {
|
||||
return city;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruit le chemin à partir des prédécesseurs
|
||||
* @param {Object} previous - Map des prédécesseurs
|
||||
* @param {String} start - Ville de départ
|
||||
* @param {String} end - Ville d'arrivée
|
||||
* @param {Object} graph - Graphe d'adjacence
|
||||
* @param {String} metric - Métrique utilisée
|
||||
* @returns {Object} Détails du chemin
|
||||
*/
|
||||
static reconstructPath(previous, start, end, graph, metric) {
|
||||
const path = [];
|
||||
let current = end;
|
||||
let totalDistance = 0;
|
||||
let totalDays = 0;
|
||||
let maxDanger = "";
|
||||
const modesUsed = new Set();
|
||||
|
||||
// Reconstruire le chemin en remontant
|
||||
while (current !== start) {
|
||||
const prev = previous[current];
|
||||
if (!prev) break;
|
||||
|
||||
const mode = prev.edge.mode || 'road';
|
||||
modesUsed.add(mode);
|
||||
|
||||
path.unshift({
|
||||
from: prev.from,
|
||||
to: current,
|
||||
distance: prev.edge.distance,
|
||||
days: prev.edge.days,
|
||||
danger: prev.edge.danger,
|
||||
mode: mode
|
||||
});
|
||||
|
||||
totalDistance += prev.edge.distance;
|
||||
totalDays += prev.edge.days;
|
||||
|
||||
// Calculer le danger maximum
|
||||
const dangerLevel = (prev.edge.danger || "").length;
|
||||
const currentMaxLevel = maxDanger.length;
|
||||
if (dangerLevel > currentMaxLevel) {
|
||||
maxDanger = prev.edge.danger;
|
||||
}
|
||||
|
||||
current = prev.from;
|
||||
}
|
||||
|
||||
return {
|
||||
path: path,
|
||||
totalDistance: totalDistance,
|
||||
totalDays: totalDays,
|
||||
maxDanger: maxDanger,
|
||||
steps: path.length,
|
||||
modesUsed: Array.from(modesUsed)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user