Files
text-adventure-game/utils/mapLayout.js
Claude 27e1c8d440 feat: 添加可视化地图系统
- 创建地图布局工具(mapLayout.js)
  - 预定义区域位置配置
  - BFS计算可达距离和最短路径
  - 地图数据结构构建
- 添加迷你地图组件(MiniMap.vue)
  - 可视化显示所有区域节点和连接线
  - 当前位置脉冲动画高亮
  - 相邻区域虚线标记
  - 锁定区域显示🔒图标
  - 显示到各区域的距离步数
  - 点击区域显示前往路径
  - 图例说明不同区域类型

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 15:09:44 +08:00

255 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 地图布局系统 - 根据区域连接生成可视化地图
*/
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为locationIdvalue为距离
*/
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
}
}