feat: 多个塔同时输入 minamo 处理

This commit is contained in:
unanmed 2025-03-15 23:30:46 +08:00
parent 9f62be3736
commit 5a19a23518
7 changed files with 108 additions and 2898 deletions

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
__pycache__
result
node_modules
node_modules
ginka-dataset.json
ginka-eval.json
minamo-dataset.json
minamo-eval.json

View File

@ -1,19 +1,20 @@
import { readFile, writeFile } from 'fs-extra';
import { join } from 'path';
import { convertFloor } from './floor';
import { mergeDataset } from './utils';
import {
FloorData,
getAllFloors,
mergeDataset,
mergeFloorIds,
parseTowerInfo
} from './utils';
import { compareMap } from './topology/compare';
import { mirrorMapX, mirrorMapY, rotateMap } from './topology/transform';
import { directions, tileType } from './topology/graph';
import { calculateVisualSimilarity } from './vision/similarity';
import { BaseConfig, TowerInfo } from './types';
interface MinamoConfig {
clip: {
defaults: [number, number, number, number];
special: Record<string, [number, number, number, number]>;
};
// data: Record<string, Record<string, number>>;
}
interface MinamoConfig extends BaseConfig {}
interface MinamoTrainData {
map1: number[][];
@ -211,10 +212,9 @@ function generateSimilarData(id: string, map: number[][]) {
}
function generateDataset(
floors: Map<string, number[][]>,
floors: Map<string, FloorData>,
pairs: number[],
floorIds: string[],
config: MinamoConfig
floorIds: string[]
): Record<string, MinamoTrainData> {
const data: Record<string, MinamoTrainData> = {};
@ -223,8 +223,8 @@ function generateDataset(
const num2 = v % floorIds.length;
const id1 = floorIds[num1];
const id2 = floorIds[num2];
const map1 = floors.get(id1);
const map2 = floors.get(id2);
const map1 = floors.get(id1)?.map;
const map2 = floors.get(id2)?.map;
if (!map1 || !map2) return;
const [w1, h1] = [map1[0].length, map1.length];
const [w2, h2] = [map2[0].length, map2.length];
@ -281,44 +281,17 @@ function generateDataset(
return data;
}
async function parseOne(path: string): Promise<MinamoDataset> {
const dataFile = await readFile(join(path, 'data.js'), 'utf-8');
const configFile = await readFile(
join(path, 'minamo-config.json'),
'utf-8'
);
const data: any = JSON.parse(dataFile.split('\n').slice(1).join('\n'));
const config = JSON.parse(configFile) as MinamoConfig;
const floorIds = data.main.floorIds as string[];
const name = data.firstData.name as string;
const length = floorIds.length;
function parseAllData(data: Map<string, FloorData>): MinamoDataset {
const length = data.size;
const totalCount = Math.round((length * (length - 1)) / 2);
const pairs = choosePair(length);
console.log(
`${name}发现 ${length} 个楼层,共 ${totalCount} 种组合,选取 ${pairs.length} 个组合`
`✅ 共发现 ${length} 个楼层,共 ${totalCount} 种组合,选取 ${pairs.length} 个组合`
);
const floors = new Map(
await Promise.all(
floorIds.map<Promise<[string, number[][]]>>(async v => {
const file = await readFile(
join(path, 'floors', `${v}.js`),
'utf-8'
);
const data = file.split('\n').slice(1).join('\n');
const json = JSON.parse(data);
const map = json.map;
const clip = config.clip.special[v] ?? config.clip.defaults;
// 裁剪
const clipped = convertFloor(map, clip, name, v);
return [v, clipped];
})
)
);
const trainData = generateDataset(floors, pairs, floorIds, config);
const trainData = generateDataset(data, pairs, [...data.keys()]);
const dataset: MinamoDataset = {
datasetId: Math.floor(Math.random() * 1e12),
@ -329,9 +302,12 @@ async function parseOne(path: string): Promise<MinamoDataset> {
}
(async () => {
const results = await Promise.all(list.map(v => parseOne(v)));
const dataset = mergeDataset(...results);
await writeFile(output, JSON.stringify(dataset, void 0), 'utf-8');
const size = Object.keys(dataset.data).length;
const towers = await Promise.all(
list.map(v => parseTowerInfo(v, 'minamo-config.json'))
);
const floors = await getAllFloors(...towers);
const results = parseAllData(floors);
await writeFile(output, JSON.stringify(results, void 0), 'utf-8');
const size = Object.keys(results.data).length;
console.log(`✅ 已处理 ${list.length} 个塔,共 ${size} 个组合`);
})();

13
data/src/types.ts Normal file
View File

@ -0,0 +1,13 @@
export interface BaseConfig {
clip: {
defaults: [number, number, number, number];
special: Record<string, [number, number, number, number]>;
};
}
export interface TowerInfo {
path: string;
name: string;
floorIds: string[];
config: BaseConfig;
}

View File

@ -1,8 +1,18 @@
import { readFile } from 'fs-extra';
import { join } from 'path';
import { BaseConfig, TowerInfo } from './types';
import { convertFloor } from './floor';
interface DatasetMergable<T> {
datasetId: number;
data: Record<string, T>;
}
export interface FloorData {
map: number[][];
config: BaseConfig;
}
export function mergeDataset<T>(
...datasets: DatasetMergable<T>[]
): DatasetMergable<T> {
@ -38,3 +48,59 @@ export function cosineSimilarity(vecA: number[], vecB: number[]): number {
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 async function getAllFloors(...info: TowerInfo[]) {
const floorData = await Promise.all(
info.map(tower => {
return Promise.all(
tower.floorIds.map(async id => {
const floorFile = await readFile(
join(tower.path, 'floors', `${id}.js`),
'utf-8'
);
const data = JSON.parse(
floorFile.split('\n').slice(1).join('\n')
);
const map = data.map as number[][];
// 裁剪地图
const { clip } = tower.config;
const area = clip.special[id] ?? clip.defaults;
return convertFloor(map, area, tower.name, id);
})
);
})
);
const maps: Map<string, FloorData> = new Map();
floorData.forEach((tower, tid) => {
const name = info[tid].name;
tower.forEach((map, mid) => {
const floorId = info[tid].floorIds[mid];
maps.set(`${name}::${floorId}`, { map, config: info[tid].config });
});
});
return maps;
}
export function mergeFloorIds(...info: TowerInfo[]) {
const ids: string[] = [];
info.forEach(v => {
ids.push(...v.floorIds.map(id => `${v.name}:${id}`));
});
return ids;
}

View File

@ -1,175 +0,0 @@
{
"datasetId": 468713377296,
"data": {
"93529307366/MT1": {
"text": [
"左右对称结构,右侧包含一个红宝石,左侧包含一个蓝宝石,怪物全部是中等强度",
"画一个左右对称,怪物强度中等,需要开门才能上楼的地图",
"生成一个有两把黄钥匙,三个黄门,四个血瓶,四个怪物的左右对称地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 1, 10, 1, 5, 1],
[1, 8, 0, 2, 0, 8, 1],
[1, 4, 1, 6, 1, 3, 1],
[1, 8, 6, 2, 6, 8, 1],
[1, 5, 1, 10, 1, 5, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT2": {
"text": [
"画一个主干道上只有弱怪,没有宝石,包含咸鱼门的地图",
"宝石被中等怪物守护,也可以通过开咸鱼门获得,以弱怪为主的地图",
"这是一个主干道在地图边缘的地图,可以考虑开一个黄门提前获得宝石"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 7, 10, 0, 7, 1],
[1, 1, 5, 1, 1, 2, 1],
[1, 0, 7, 0, 1, 5, 1],
[1, 6, 1, 6, 1, 7, 1],
[1, 5, 1, 4, 8, 10, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT3": {
"text": [
"一个红宝石在黄门里面,门前有一个强怪守护,另一个红宝石可以打败一个中怪和两个弱怪后获得",
"画一个主干道在中间,左侧有一个强怪守护的红宝石,右侧有一个红宝石的地图",
"生成一个左侧怪物较强,右侧怪物较弱的地图,右侧有一个黄门捷径"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 3, 1, 10, 7, 3, 1],
[1, 6, 1, 5, 1, 7, 1],
[1, 9, 1, 8, 1, 6, 1],
[1, 5, 8, 0, 1, 7, 1],
[1, 2, 1, 5, 7, 10, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT4": {
"text": [
"生成一个左右异形对称,包含各种强度的怪物,有中怪守护红宝石,强怪守护蓝宝石的地图",
"防御宝石由强怪守护,攻击宝石由中怪守护,左右不完全对称的地图",
"画一个可以通过开门绕过一个中等强度怪物和两个弱怪,包含三个血瓶,攻防宝石各一个的地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 7, 10, 7, 0, 1],
[1, 8, 1, 6, 1, 8, 1],
[1, 3, 1, 5, 7, 5, 1],
[1, 1, 1, 6, 1, 1, 1],
[1, 10, 8, 0, 9, 4, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT5": {
"text": [
"画一个左上右下对称,中间全是门和宝石,周围全是怪物和血瓶的地图",
"中间的门构成一个叉号状,空余位置填充宝石,外围包含怪物和血瓶,几乎没有空地的地图",
"生成一个偏资源向的楼层,有很多资源集中在中间,但是需要开门才能获得,地图外围有怪物挡路"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 8, 5, 7, 0, 10, 1],
[1, 5, 6, 4, 6, 0, 1],
[1, 7, 3, 6, 3, 7, 1],
[1, 2, 6, 4, 6, 5, 1],
[1, 10, 2, 7, 5, 8, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT6": {
"text": [
"上楼梯在蓝门里面,需要开门才能上楼,怪物强度比较高,没有弱怪,宝石全由强怪守护,但是其中一个也可以咸门获得",
"画一个接近左右对称的地图,最好能好看点",
"生成一个有一把黄钥匙,怪物强度高的地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 6, 4, 1, 10, 1],
[1, 6, 1, 9, 1, 5, 1],
[1, 8, 0, 6, 0, 8, 1],
[1, 5, 1, 10, 1, 2, 1],
[1, 9, 3, 1, 4, 9, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT7": {
"text": [
"画一个几乎全由怪物组成,没有宝石,只有血瓶钥匙和门的地图",
"怪物强度高,但是血瓶数量多",
"可以直接开门上楼,没有宝石的地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 9, 2, 9, 5, 1],
[1, 8, 0, 10, 0, 8, 1],
[1, 5, 9, 6, 9, 5, 1],
[1, 0, 8, 10, 8, 0, 1],
[1, 6, 0, 5, 0, 6, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT8": {
"text": [
"以走廊为主,没有强怪,只有弱怪和中怪,有一定数量的宝石的地图",
"画一个蓝海风,以走廊为主的地图",
"生成一个资源比较丰富,有宝石、血瓶,需要开门才能上楼,有一些咸鱼门的地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 8, 10, 7, 2, 1],
[1, 2, 1, 5, 1, 7, 1],
[1, 3, 1, 3, 6, 4, 1],
[1, 6, 1, 6, 1, 8, 1],
[1, 10, 7, 5, 1, 5, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT9": {
"text": [
"左右基本对称,但是入口不对称,宝石种类不对称的地图",
"画一个左右基本对称,没有强怪的地图",
"生成一个有两个红宝石,一个蓝宝石,很多血瓶,没有强怪的地图"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 4, 1, 10, 1, 3, 1],
[1, 5, 7, 0, 7, 5, 1],
[1, 8, 6, 5, 6, 8, 1],
[1, 5, 8, 6, 8, 5, 1],
[1, 10, 5, 1, 5, 3, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
},
"93529307366/MT10": {
"text": [
"左右对称的 Boss 层",
"画一个 Boss 层入口在上方Boss 在下方,中间有几个弱怪挡路",
"生成一个 Boss 层,可以咸门提前拿资源"
],
"map": [
[1, 1, 1, 1, 1, 1, 1],
[1, 5, 1, 10, 1, 5, 1],
[1, 6, 7, 7, 7, 6, 1],
[1, 1, 6, 5, 6, 1, 1],
[1, 4, 5, 9, 5, 4, 1],
[1, 3, 1, 1, 1, 3, 1],
[1, 1, 1, 1, 1, 1, 1]
],
"size": [7, 7]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long