255 lines
5.9 KiB
JavaScript
255 lines
5.9 KiB
JavaScript
|
|
/**
|
|||
|
|
* 地图布局系统 - 根据区域连接生成可视化地图
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { LOCATION_CONFIG } from '@/config/locations.js'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建地图图形结构
|
|||
|
|
* @returns {Object} 地图数据
|
|||
|
|
*/
|
|||
|
|
export function buildMapGraph() {
|
|||
|
|
const nodes = []
|
|||
|
|
const edges = []
|
|||
|
|
|
|||
|
|
// 首先创建所有节点
|
|||
|
|
for (const [id, config] of Object.entries(LOCATION_CONFIG)) {
|
|||
|
|
nodes.push({
|
|||
|
|
id,
|
|||
|
|
name: config.name,
|
|||
|
|
type: config.type,
|
|||
|
|
x: 0,
|
|||
|
|
y: 0
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建连接边
|
|||
|
|
for (const [id, config] of Object.entries(LOCATION_CONFIG)) {
|
|||
|
|
if (config.connections) {
|
|||
|
|
for (const connId of config.connections) {
|
|||
|
|
// 避免重复边
|
|||
|
|
const edgeExists = edges.some(e =>
|
|||
|
|
(e.from === id && e.to === connId) ||
|
|||
|
|
(e.from === connId && e.to === id)
|
|||
|
|
)
|
|||
|
|
if (!edgeExists) {
|
|||
|
|
edges.push({ from: id, to: connId })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { nodes, edges }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 简单的力导向布局算法
|
|||
|
|
* 将节点分布在合理的坐标上
|
|||
|
|
* @param {Object} graph - 地图图形结构
|
|||
|
|
* @param {Object} options - 布局选项
|
|||
|
|
* @returns {Object} 带有坐标的节点
|
|||
|
|
*/
|
|||
|
|
export function layoutMap(graph, options = {}) {
|
|||
|
|
const { nodes, edges } = graph
|
|||
|
|
const { width = 300, height = 400 } = options
|
|||
|
|
|
|||
|
|
// 创建邻接表
|
|||
|
|
const adj = {}
|
|||
|
|
for (const node of nodes) {
|
|||
|
|
adj[node.id] = []
|
|||
|
|
}
|
|||
|
|
for (const edge of edges) {
|
|||
|
|
adj[edge.from].push(edge.to)
|
|||
|
|
adj[edge.to].push(edge.from)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 找到中心节点(连接最多的节点作为起点)
|
|||
|
|
let centerNode = nodes[0]
|
|||
|
|
for (const node of nodes) {
|
|||
|
|
if (adj[node.id].length > adj[centerNode.id].length) {
|
|||
|
|
centerNode = node
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BFS 分层布局
|
|||
|
|
const levels = {}
|
|||
|
|
const visited = new Set()
|
|||
|
|
const queue = [{ id: centerNode.id, level: 0 }]
|
|||
|
|
|
|||
|
|
while (queue.length > 0) {
|
|||
|
|
const { id, level } = queue.shift()
|
|||
|
|
if (visited.has(id)) continue
|
|||
|
|
visited.add(id)
|
|||
|
|
|
|||
|
|
if (!levels[level]) levels[level] = []
|
|||
|
|
levels[level].push(id)
|
|||
|
|
|
|||
|
|
for (const neighbor of adj[id]) {
|
|||
|
|
if (!visited.has(neighbor)) {
|
|||
|
|
queue.push({ id: neighbor, level: level + 1 })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算节点位置
|
|||
|
|
const nodePositions = {}
|
|||
|
|
const levelHeight = height / (Object.keys(levels).length + 1)
|
|||
|
|
|
|||
|
|
for (const [level, nodeIds] of Object.entries(levels)) {
|
|||
|
|
const y = (parseInt(level) + 1) * levelHeight
|
|||
|
|
const levelWidth = width / (nodeIds.length + 1)
|
|||
|
|
|
|||
|
|
nodeIds.forEach((nodeId, index) => {
|
|||
|
|
nodePositions[nodeId] = {
|
|||
|
|
x: (index + 1) * levelWidth,
|
|||
|
|
y
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新节点坐标
|
|||
|
|
return nodes.map(node => ({
|
|||
|
|
...node,
|
|||
|
|
x: nodePositions[node.id]?.x || width / 2,
|
|||
|
|
y: nodePositions[node.id]?.y || height / 2
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取从当前位置可达的区域(包括多步可达)
|
|||
|
|
* @param {string} currentLocation - 当前位置ID
|
|||
|
|
* @param {number} maxDistance - 最大距离(步数)
|
|||
|
|
* @returns {Object} 可达区域信息,key为locationId,value为距离
|
|||
|
|
*/
|
|||
|
|
export function getReachableLocations(currentLocation, maxDistance = 10) {
|
|||
|
|
const distances = {}
|
|||
|
|
const queue = [{ id: currentLocation, dist: 0 }]
|
|||
|
|
|
|||
|
|
distances[currentLocation] = 0
|
|||
|
|
|
|||
|
|
while (queue.length > 0) {
|
|||
|
|
const { id, dist } = queue.shift()
|
|||
|
|
|
|||
|
|
if (dist >= maxDistance) continue
|
|||
|
|
|
|||
|
|
// 找到所有连接的节点
|
|||
|
|
const connections = LOCATION_CONFIG[id]?.connections || []
|
|||
|
|
for (const connId of connections) {
|
|||
|
|
if (distances[connId] === undefined) {
|
|||
|
|
distances[connId] = dist + 1
|
|||
|
|
queue.push({ id: connId, dist: dist + 1 })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return distances
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取地图的路径信息
|
|||
|
|
* @param {string} from - 起点
|
|||
|
|
* @param {string} to - 终点
|
|||
|
|
* @returns {Array} 路径数组
|
|||
|
|
*/
|
|||
|
|
export function getPath(from, to) {
|
|||
|
|
if (from === to) return [from]
|
|||
|
|
|
|||
|
|
const { edges } = buildMapGraph()
|
|||
|
|
const adj = {}
|
|||
|
|
for (const node of Object.keys(LOCATION_CONFIG)) {
|
|||
|
|
adj[node] = LOCATION_CONFIG[node]?.connections || []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BFS 寻找最短路径
|
|||
|
|
const queue = [[from]]
|
|||
|
|
const visited = new Set([from])
|
|||
|
|
|
|||
|
|
while (queue.length > 0) {
|
|||
|
|
const path = queue.shift()
|
|||
|
|
const current = path[path.length - 1]
|
|||
|
|
|
|||
|
|
if (current === to) {
|
|||
|
|
return path
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const neighbor of adj[current]) {
|
|||
|
|
if (!visited.has(neighbor)) {
|
|||
|
|
visited.add(neighbor)
|
|||
|
|
queue.push([...path, neighbor])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null // 无路径
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 预定义的地图布局配置
|
|||
|
|
* 为每个区域指定固定的显示位置
|
|||
|
|
*/
|
|||
|
|
export const PREDEFINED_LAYOUT = {
|
|||
|
|
// 营地为中心
|
|||
|
|
camp: { x: 150, y: 200 },
|
|||
|
|
// 营地周围
|
|||
|
|
market: { x: 50, y: 120 },
|
|||
|
|
blackmarket: { x: 250, y: 120 },
|
|||
|
|
wild1: { x: 150, y: 320 },
|
|||
|
|
// 更远的地方
|
|||
|
|
boss_lair: { x: 150, y: 440 },
|
|||
|
|
basement: { x: 150, y: 540 }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 使用预定义布局获取节点位置
|
|||
|
|
* @param {Object} graph - 地图图形结构
|
|||
|
|
* @returns {Array} 带有坐标的节点
|
|||
|
|
*/
|
|||
|
|
export function getPredefinedLayout(graph) {
|
|||
|
|
const { nodes } = graph
|
|||
|
|
|
|||
|
|
return nodes.map(node => ({
|
|||
|
|
...node,
|
|||
|
|
x: PREDEFINED_LAYOUT[node.id]?.x || 150,
|
|||
|
|
y: PREDEFINED_LAYOUT[node.id]?.y || 200
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取可视化的地图数据
|
|||
|
|
* @param {string} currentLocation - 当前位置
|
|||
|
|
* @returns {Object} 地图数据
|
|||
|
|
*/
|
|||
|
|
export function getMapData(currentLocation) {
|
|||
|
|
const graph = buildMapGraph()
|
|||
|
|
const layoutNodes = getPredefinedLayout(graph)
|
|||
|
|
|
|||
|
|
// 计算画布大小
|
|||
|
|
let maxX = 0, maxY = 0
|
|||
|
|
for (const node of layoutNodes) {
|
|||
|
|
maxX = Math.max(maxX, node.x)
|
|||
|
|
maxY = Math.max(maxY, node.y)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取可达距离
|
|||
|
|
const reachable = getReachableLocations(currentLocation, 10)
|
|||
|
|
|
|||
|
|
// 计算到每个区域的路径
|
|||
|
|
const paths = {}
|
|||
|
|
for (const node of layoutNodes) {
|
|||
|
|
const path = getPath(currentLocation, node.id)
|
|||
|
|
if (path) {
|
|||
|
|
paths[node.id] = path
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
nodes: layoutNodes,
|
|||
|
|
edges: graph.edges,
|
|||
|
|
width: maxX + 50,
|
|||
|
|
height: maxY + 50,
|
|||
|
|
currentLocation,
|
|||
|
|
reachable,
|
|||
|
|
paths
|
|||
|
|
}
|
|||
|
|
}
|