mirror of
https://github.com/unanmed/ginka-generator.git
synced 2026-05-22 02:44:51 +08:00
refactor: 删除旧版拓扑算法
This commit is contained in:
parent
ef79dd4d31
commit
01c9e1972e
@ -1,29 +0,0 @@
|
|||||||
import { buildTopologicalGraph } from './graph';
|
|
||||||
import { GinkaTopologicalGraphs } from './interface';
|
|
||||||
import { overallSimilarity } from './similarity';
|
|
||||||
|
|
||||||
const cache = new Map<string, GinkaTopologicalGraphs>();
|
|
||||||
|
|
||||||
export function getTopologicalGraph(
|
|
||||||
floorId: string,
|
|
||||||
map: number[][]
|
|
||||||
): GinkaTopologicalGraphs {
|
|
||||||
if (cache.has(floorId)) return cache.get(floorId)!;
|
|
||||||
const graphs = buildTopologicalGraph(map);
|
|
||||||
cache.set(floorId, graphs);
|
|
||||||
return graphs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compareMap(
|
|
||||||
floorId1: string,
|
|
||||||
floorId2: string,
|
|
||||||
map1: number[][],
|
|
||||||
map2: number[][]
|
|
||||||
) {
|
|
||||||
const graph1 = getTopologicalGraph(floorId1, map1);
|
|
||||||
const graph2 = getTopologicalGraph(floorId2, map2);
|
|
||||||
|
|
||||||
const kernel = overallSimilarity(graph1, graph2);
|
|
||||||
|
|
||||||
return kernel;
|
|
||||||
}
|
|
||||||
@ -1,255 +0,0 @@
|
|||||||
import {
|
|
||||||
ResourceArea,
|
|
||||||
GinkaGraph,
|
|
||||||
BranchNode,
|
|
||||||
GinkaTopologicalGraphs,
|
|
||||||
ResourceNode,
|
|
||||||
NodeType
|
|
||||||
} from './interface';
|
|
||||||
|
|
||||||
export const tileType = new Set(
|
|
||||||
Array(13)
|
|
||||||
.fill(0)
|
|
||||||
.map((_, i) => i)
|
|
||||||
);
|
|
||||||
const branchType = new Set([6, 7, 8, 9]);
|
|
||||||
const entranceType = new Set([10, 11]);
|
|
||||||
const resourceType = new Set([0, 2, 3, 4, 5, 10, 11, 12, 13]);
|
|
||||||
|
|
||||||
export const directions: [number, number][] = [
|
|
||||||
[-1, 0],
|
|
||||||
[1, 0],
|
|
||||||
[0, -1],
|
|
||||||
[0, 1]
|
|
||||||
];
|
|
||||||
|
|
||||||
function buildGraphFromEntrance(
|
|
||||||
map: number[][],
|
|
||||||
entrance: number,
|
|
||||||
resourceMap: Map<number, number>,
|
|
||||||
areaMap: ResourceArea[]
|
|
||||||
): GinkaGraph {
|
|
||||||
const width = map[0].length;
|
|
||||||
const height = map[1].length;
|
|
||||||
|
|
||||||
const visitedEntrance = new Set<number>([entrance]);
|
|
||||||
const visited = new Set<number>();
|
|
||||||
const queue: [number, number][] = [];
|
|
||||||
queue.push([entrance % width, Math.floor(entrance / width)]);
|
|
||||||
|
|
||||||
const branchNodes = new Set<number>();
|
|
||||||
|
|
||||||
// 1. BFS 检测所有分支节点
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const item = queue.shift();
|
|
||||||
if (!item) continue;
|
|
||||||
const [nx, ny] = item;
|
|
||||||
const index = ny * width + nx;
|
|
||||||
if (visited.has(index)) continue;
|
|
||||||
const tile = map[ny][nx];
|
|
||||||
|
|
||||||
if (entranceType.has(tile)) {
|
|
||||||
visitedEntrance.add(index);
|
|
||||||
}
|
|
||||||
if (branchType.has(tile)) {
|
|
||||||
branchNodes.add(index);
|
|
||||||
}
|
|
||||||
visited.add(index);
|
|
||||||
|
|
||||||
for (const [dx, dy] of directions) {
|
|
||||||
const px = dx + nx;
|
|
||||||
const py = dy + ny;
|
|
||||||
if (px < 0 || px >= width || py < 0 || py >= height) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const tile = map[py][px];
|
|
||||||
if (tile !== 1) {
|
|
||||||
// 非墙区域可通行
|
|
||||||
queue.push([px, py]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 从分支节点构建拓扑图
|
|
||||||
const graph = new Map<number, BranchNode | ResourceNode>();
|
|
||||||
branchNodes.forEach(v => {
|
|
||||||
const nx = v % width;
|
|
||||||
const ny = Math.floor(v / width);
|
|
||||||
if (!graph.get(v)) {
|
|
||||||
graph.set(v, {
|
|
||||||
type: NodeType.Branch,
|
|
||||||
neighbor: new Set(),
|
|
||||||
tile: map[ny][nx]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const node = graph.get(v)!;
|
|
||||||
for (const [dx, dy] of directions) {
|
|
||||||
const px = nx + dx;
|
|
||||||
const py = ny + dy;
|
|
||||||
if (px < 0 || px >= width || py < 0 || py >= height) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const index = py * width + px;
|
|
||||||
|
|
||||||
// 先检查临近节点是不是分支节点,是的话链接到自己
|
|
||||||
if (branchNodes.has(index)) {
|
|
||||||
node.neighbor.add(index);
|
|
||||||
} else {
|
|
||||||
// 检查是不是资源节点
|
|
||||||
const pointer = resourceMap.get(index);
|
|
||||||
if (pointer === void 0) continue;
|
|
||||||
const area = areaMap[pointer];
|
|
||||||
if (!area) continue;
|
|
||||||
area.neighbor.add(v);
|
|
||||||
area.members.forEach(v => {
|
|
||||||
node.neighbor.add(v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 把资源节点拆分成并排,并放入拓扑图
|
|
||||||
areaMap.forEach(v => {
|
|
||||||
v.members.forEach(index => {
|
|
||||||
const nx = index % width;
|
|
||||||
const ny = Math.floor(index / width);
|
|
||||||
const tile = map[ny][nx];
|
|
||||||
if (tile === 0) return;
|
|
||||||
const node: ResourceNode = {
|
|
||||||
type: NodeType.Resource,
|
|
||||||
resourceType: tile,
|
|
||||||
neighbor: v.neighbor,
|
|
||||||
resourceArea: v
|
|
||||||
};
|
|
||||||
graph.set(index, node);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return { graph, resourceMap, areaMap, visitedEntrance, visited };
|
|
||||||
}
|
|
||||||
|
|
||||||
function findResourceNodes(map: number[][]) {
|
|
||||||
const width = map[0].length;
|
|
||||||
const height = map[1].length;
|
|
||||||
|
|
||||||
const visited = new Set<number>();
|
|
||||||
const areas: ResourceArea[] = [];
|
|
||||||
const resourcesMap: Map<number, number> = new Map();
|
|
||||||
|
|
||||||
for (let ny = 0; ny < height; ny++) {
|
|
||||||
for (let nx = 0; nx < width; nx++) {
|
|
||||||
const tile = map[ny][nx];
|
|
||||||
const index = ny * width + nx;
|
|
||||||
if (visited.has(index) || !resourceType.has(tile)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const queue: [number, number][] = [];
|
|
||||||
queue.push([nx, ny]);
|
|
||||||
const area: ResourceArea = {
|
|
||||||
type: NodeType.Resource,
|
|
||||||
resources: new Map([[tile, 1]]),
|
|
||||||
members: new Set([index]),
|
|
||||||
neighbor: new Set()
|
|
||||||
};
|
|
||||||
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const item = queue.shift();
|
|
||||||
if (!item) continue;
|
|
||||||
const [nx, ny] = item;
|
|
||||||
const index = ny * width + nx;
|
|
||||||
if (visited.has(index)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const tile = map[ny][nx];
|
|
||||||
if (!resourceType.has(tile)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
visited.add(index);
|
|
||||||
|
|
||||||
const exists = area.resources.get(tile);
|
|
||||||
if (!exists) {
|
|
||||||
area.resources.set(tile, 1);
|
|
||||||
} else {
|
|
||||||
area.resources.set(tile, exists + 1);
|
|
||||||
}
|
|
||||||
area.members.add(index);
|
|
||||||
resourcesMap.set(index, areas.length);
|
|
||||||
|
|
||||||
for (const [dx, dy] of directions) {
|
|
||||||
const px = nx + dx;
|
|
||||||
const py = ny + dy;
|
|
||||||
if (px < 0 || px >= width || py < 0 || py >= height) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
queue.push([px, py]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
areas.push(area);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { areaMap: areas, resourcesMap };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildTopologicalGraph(map: number[][]): GinkaTopologicalGraphs {
|
|
||||||
const width = map[0].length;
|
|
||||||
const height = map[1].length;
|
|
||||||
|
|
||||||
// 1. 找到所有入口
|
|
||||||
const entrances = new Set<number>();
|
|
||||||
for (let ny = 0; ny < height; ny++) {
|
|
||||||
for (let nx = 0; nx < width; nx++) {
|
|
||||||
const tile = map[ny][nx];
|
|
||||||
if (entranceType.has(tile)) {
|
|
||||||
entrances.add(ny * width + nx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 找到所有的资源节点
|
|
||||||
const { areaMap, resourcesMap } = findResourceNodes(map);
|
|
||||||
|
|
||||||
// 3. 对每个入口计算拓扑图
|
|
||||||
const graphs: GinkaGraph[] = [];
|
|
||||||
const usedEntrance = new Set<number>();
|
|
||||||
const totalVisited = new Set<number>();
|
|
||||||
/** 入口位置到拓扑图的映射 */
|
|
||||||
const entranceMap = new Map<number, GinkaGraph>();
|
|
||||||
entrances.forEach(v => {
|
|
||||||
if (usedEntrance.has(v)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nx = v % width;
|
|
||||||
const ny = Math.floor(v / width);
|
|
||||||
const entranceGraph = buildGraphFromEntrance(
|
|
||||||
map,
|
|
||||||
v,
|
|
||||||
resourcesMap,
|
|
||||||
areaMap
|
|
||||||
);
|
|
||||||
const { graph, visited, visitedEntrance } = entranceGraph;
|
|
||||||
graphs.push(entranceGraph);
|
|
||||||
// 标记已经探索到的入口,并标记这个入口对应了哪个图
|
|
||||||
visitedEntrance.forEach(v => {
|
|
||||||
usedEntrance.add(v);
|
|
||||||
entranceMap.set(v, entranceGraph);
|
|
||||||
});
|
|
||||||
visited.forEach(v => {
|
|
||||||
totalVisited.add(v);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 计算不可到达区域
|
|
||||||
const unreachable = new Set<number>();
|
|
||||||
for (let ny = 0; ny < height; ny++) {
|
|
||||||
for (let nx = 0; nx < width; nx++) {
|
|
||||||
const index = ny * width + nx;
|
|
||||||
if (!totalVisited.has(index) && map[ny][nx] !== 1) {
|
|
||||||
unreachable.add(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { graphs, entranceMap, unreachable };
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
export const enum NodeType {
|
|
||||||
Branch,
|
|
||||||
Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceArea {
|
|
||||||
/** 节点类型 */
|
|
||||||
readonly type: NodeType.Resource;
|
|
||||||
/** 每种资源对应的数量 */
|
|
||||||
readonly resources: Map<number, number>;
|
|
||||||
/** 资源区域包含的所有资源图块坐标索引 */
|
|
||||||
readonly members: Set<number>;
|
|
||||||
/** 资源区域的邻居节点 */
|
|
||||||
readonly neighbor: Set<number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BranchNode {
|
|
||||||
/** 节点类型 */
|
|
||||||
readonly type: NodeType.Branch;
|
|
||||||
/** 分支节点的邻居节点 */
|
|
||||||
readonly neighbor: Set<number>;
|
|
||||||
/** 分支节点图块 */
|
|
||||||
readonly tile: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceNode {
|
|
||||||
/** 节点类型 */
|
|
||||||
readonly type: NodeType.Resource;
|
|
||||||
/** 资源类型 */
|
|
||||||
readonly resourceType: number;
|
|
||||||
/** 邻居节点 */
|
|
||||||
readonly neighbor: Set<number>;
|
|
||||||
/** 资源节点所属的资源区域 */
|
|
||||||
readonly resourceArea: ResourceArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GinkaNode = BranchNode | ResourceNode;
|
|
||||||
|
|
||||||
export interface GinkaGraph {
|
|
||||||
/** 拓扑图内容,键表示位置,值表示这一点的节点 */
|
|
||||||
readonly graph: Map<number, GinkaNode>;
|
|
||||||
/** 资源指针,键表示位置,值表示这一点对应的资源节点在 areaMap 的索引 */
|
|
||||||
readonly resourceMap: Map<number, number>;
|
|
||||||
/** 资源区域列表 */
|
|
||||||
readonly areaMap: ResourceArea[];
|
|
||||||
/** 这个拓扑图包含的入口位置 */
|
|
||||||
readonly visitedEntrance: Set<number>;
|
|
||||||
/** 这个拓扑图能够造访的所有位置 */
|
|
||||||
readonly visited: Set<number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GinkaTopologicalGraphs {
|
|
||||||
/** 这个地图包含的所有独立的图 */
|
|
||||||
readonly graphs: GinkaGraph[];
|
|
||||||
/** 每个入口对应哪个图 */
|
|
||||||
readonly entranceMap: Map<number, GinkaGraph>;
|
|
||||||
/** 这个图从入口开始的不可到达区域 */
|
|
||||||
readonly unreachable: Set<number>;
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import { cosineSimilarity } from 'src/utils';
|
|
||||||
import { GinkaGraph, GinkaTopologicalGraphs, NodeType } from './interface';
|
|
||||||
|
|
||||||
interface WLNode {
|
|
||||||
originalPos: number;
|
|
||||||
originalLabel: string;
|
|
||||||
currentLabel: string;
|
|
||||||
neighbors: WLNode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeNodeLabels(graph: GinkaGraph) {
|
|
||||||
const nodes: WLNode[] = [];
|
|
||||||
const nodeMap = new Map<number, WLNode>();
|
|
||||||
|
|
||||||
graph.graph.forEach((node, pos) => {
|
|
||||||
let label: string;
|
|
||||||
|
|
||||||
// 编码为唯一哈希值(用字符串就行,V8 会自动帮你算哈希)
|
|
||||||
if (node.type === NodeType.Branch) {
|
|
||||||
label = `B:${node.tile}`;
|
|
||||||
} else {
|
|
||||||
label = `R:${node.resourceType}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wlNode: WLNode = {
|
|
||||||
originalPos: pos,
|
|
||||||
originalLabel: label,
|
|
||||||
currentLabel: label,
|
|
||||||
neighbors: []
|
|
||||||
};
|
|
||||||
nodeMap.set(pos, wlNode);
|
|
||||||
nodes.push(wlNode);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 映射邻居节点
|
|
||||||
nodes.forEach(node => {
|
|
||||||
const ginkaNode = graph.graph.get(node.originalPos);
|
|
||||||
ginkaNode?.neighbor.forEach(v => {
|
|
||||||
const wl = nodeMap.get(v);
|
|
||||||
if (wl) node.neighbors.push(wl);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function weisfeilerLehmanIteration(
|
|
||||||
nodes: WLNode[],
|
|
||||||
iterations: number,
|
|
||||||
decay: number = 0.6 // 衰减权重,减小长距离图的权重
|
|
||||||
) {
|
|
||||||
const labelHistory: string[][] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
const newLabels: string[] = [];
|
|
||||||
|
|
||||||
// 生成新标签
|
|
||||||
nodes.forEach(node => {
|
|
||||||
const neighborLabels = node.neighbors
|
|
||||||
.map(n => n.currentLabel)
|
|
||||||
.sort();
|
|
||||||
|
|
||||||
const compositeLabel = `${node.currentLabel}|${neighborLabels.join(
|
|
||||||
','
|
|
||||||
)}`.slice(0, 8192);
|
|
||||||
|
|
||||||
newLabels.push(compositeLabel);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新节点标签并记录
|
|
||||||
nodes.forEach((node, idx) => {
|
|
||||||
node.currentLabel = newLabels[idx];
|
|
||||||
});
|
|
||||||
labelHistory.push([...newLabels]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统计每个节点的数量
|
|
||||||
let weight = 1;
|
|
||||||
const numMap = new Map<string, number>();
|
|
||||||
labelHistory.forEach(iter => {
|
|
||||||
iter.forEach(v => {
|
|
||||||
if (!numMap.has(v)) {
|
|
||||||
numMap.set(v, weight);
|
|
||||||
} else {
|
|
||||||
numMap.set(v, numMap.get(v)! + weight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
weight *= decay;
|
|
||||||
});
|
|
||||||
// 把每个节点的原始标签也加上,权重使用最远权重,可以认为是资源重复率
|
|
||||||
nodes.forEach(node => {
|
|
||||||
if (!numMap.has(node.originalLabel)) {
|
|
||||||
numMap.set(node.originalLabel, weight);
|
|
||||||
} else {
|
|
||||||
numMap.set(
|
|
||||||
node.originalLabel,
|
|
||||||
numMap.get(node.originalLabel)! + weight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return numMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
function vectorizeFeatures(features: Map<string, number>, vocab: string[]) {
|
|
||||||
const vec: number[] = new Array(vocab.length).fill(0);
|
|
||||||
|
|
||||||
features.forEach((count, label) => {
|
|
||||||
const index = vocab.indexOf(label);
|
|
||||||
if (index !== -1) {
|
|
||||||
vec[index] += count;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wlKernel(
|
|
||||||
graphA: GinkaGraph,
|
|
||||||
graphB: GinkaGraph,
|
|
||||||
iterations = 3
|
|
||||||
): number {
|
|
||||||
// 编码节点
|
|
||||||
const nodesA = encodeNodeLabels(graphA);
|
|
||||||
const nodesB = encodeNodeLabels(graphB);
|
|
||||||
|
|
||||||
// 迭代生成标签
|
|
||||||
const featuresA = weisfeilerLehmanIteration(nodesA, iterations);
|
|
||||||
const featuresB = weisfeilerLehmanIteration(nodesB, iterations);
|
|
||||||
|
|
||||||
// 构建特征向量
|
|
||||||
const vocab = [...new Set([...featuresA.keys(), ...featuresB.keys()])];
|
|
||||||
const vecA = vectorizeFeatures(featuresA, vocab);
|
|
||||||
const vecB = vectorizeFeatures(featuresB, vocab);
|
|
||||||
|
|
||||||
// 计算余弦相似度
|
|
||||||
return cosineSimilarity(vecA, vecB);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function overallSimilarity(
|
|
||||||
a: GinkaTopologicalGraphs,
|
|
||||||
b: GinkaTopologicalGraphs
|
|
||||||
) {
|
|
||||||
// 使用 Weisfeiler-Lehman Kernel 方式计算拓扑图相似度
|
|
||||||
const graphsA = a.graphs;
|
|
||||||
const graphsB = b.graphs;
|
|
||||||
|
|
||||||
let totalSimilarity = 0;
|
|
||||||
const comparedGraph = new Set<GinkaGraph>();
|
|
||||||
graphsA.forEach(ga => {
|
|
||||||
let maxSimilarity = 0;
|
|
||||||
let maxGraph: GinkaGraph | null = null;
|
|
||||||
// 图之间两两比较,找到最接近的作为相似度
|
|
||||||
for (const gb of graphsB) {
|
|
||||||
if (comparedGraph.has(gb)) continue;
|
|
||||||
// 计算迭代次数
|
|
||||||
const min = Math.min(ga.graph.size, gb.graph.size);
|
|
||||||
const iterations = Math.ceil(Math.max(1, Math.log(min)));
|
|
||||||
const similarity = wlKernel(ga, gb, iterations);
|
|
||||||
if (similarity > maxSimilarity && !isNaN(similarity)) {
|
|
||||||
maxSimilarity = similarity;
|
|
||||||
maxGraph = gb;
|
|
||||||
}
|
|
||||||
if (similarity === 1) break;
|
|
||||||
}
|
|
||||||
totalSimilarity += maxSimilarity;
|
|
||||||
if (maxGraph) comparedGraph.add(maxGraph);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 不可达区域惩罚
|
|
||||||
const reduction =
|
|
||||||
1 / (1 + Math.abs(a.unreachable.size - b.unreachable.size));
|
|
||||||
// 取根号使结果更接近线性
|
|
||||||
if (graphsA.length === 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return Math.sqrt(totalSimilarity / graphsA.length) * reduction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -43,70 +43,6 @@ export function mergeDataset<T>(
|
|||||||
return dataset;
|
return dataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cosineSimilarity(vecA: number[], vecB: number[]): number {
|
|
||||||
if (vecA.length !== vecB.length) {
|
|
||||||
throw new Error('Vectors must have same dimension');
|
|
||||||
}
|
|
||||||
|
|
||||||
let dot = 0,
|
|
||||||
normA = 0,
|
|
||||||
normB = 0;
|
|
||||||
for (let i = 0; i < vecA.length; i++) {
|
|
||||||
dot += vecA[i] * vecB[i];
|
|
||||||
normA += vecA[i] ** 2;
|
|
||||||
normB += vecB[i] ** 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseTowerInfo(
|
|
||||||
path: string,
|
|
||||||
configName: string
|
|
||||||
): Promise<TowerInfo> {
|
|
||||||
const dataFile = await readFile(join(path, 'data.js'), 'utf-8');
|
|
||||||
const data: any = JSON.parse(dataFile.split('\n').slice(1).join('\n'));
|
|
||||||
const configFile = await readFile(join(path, configName), 'utf-8');
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: path,
|
|
||||||
name: data.firstData.name as string,
|
|
||||||
floorIds: data.main.floorIds as string[],
|
|
||||||
config: JSON.parse(configFile) as BaseConfig
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mergeFloorIds(...info: TowerInfo[]) {
|
|
||||||
const ids: string[] = [];
|
|
||||||
info.forEach(v => {
|
|
||||||
ids.push(...v.floorIds.map(id => `${v.name}:${id}`));
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fromJSON(path: string) {
|
|
||||||
const file = await readFile(path, 'utf-8');
|
|
||||||
const data = JSON.parse(file) as Record<string, number[][]>;
|
|
||||||
const clip: Record<string, [number, number, number, number]> = {};
|
|
||||||
const config: BaseConfig = {
|
|
||||||
clip: {
|
|
||||||
defaults: [0, 0, 0, 0],
|
|
||||||
special: clip
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const name = (Math.random() * 12).toFixed(0);
|
|
||||||
const floorMap = new Map<string, FloorData>();
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
const floorData: FloorData = {
|
|
||||||
map: value,
|
|
||||||
id: key,
|
|
||||||
config
|
|
||||||
};
|
|
||||||
floorMap.set(`${name}:${key}`, floorData);
|
|
||||||
}
|
|
||||||
return floorMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chooseFrom<T>(arr: T[], n: number): T[] {
|
export function chooseFrom<T>(arr: T[], n: number): T[] {
|
||||||
const copy = arr.slice();
|
const copy = arr.slice();
|
||||||
for (let i = copy.length - 1; i > 0; i--) {
|
for (let i = copy.length - 1; i > 0; i--) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user