feat: 单独对 Apeiria 塔提供支持

This commit is contained in:
AncTe committed 2025-03-16 18:56:32 +08:00
1 parent c34756016d
commit 98f7a9cdcf
5 files changed
+220 -12

No files matched your search

+109 -2
View File
@@ -18,11 +18,48 @@ const numMap: Record<number, number> = {
53: 12 // 道具
};
export function convertFloor(
const apeiriaMap: Record<number, number> = {
0: 0, // 空地
1: 1, // 墙壁
224: 1, // 吸血鬼,视为墙壁
21: 2, // 黄钥匙
22: 2, // 蓝钥匙
23: 2, // 红钥匙
27: 3, // 红宝石
28: 4, // 蓝宝石
29: 0, // 绿宝石
31: 5, // 红血瓶
32: 5, // 蓝血瓶
33: 5, // 绿血瓶
34: 5, // 黄血瓶
81: 6, // 门
201: 7, // 弱怪
202: 8, // 中怪
203: 9, // 强怪
87: 10, // 楼梯
88: 10, // 楼梯
161: 11, // 箭头
162: 11, // 箭头
163: 11, // 箭头
164: 11, // 箭头
53: 12, // 幸运金币
50: 12, // 飞
49: 12, // 炸
47: 12 // 破
};
export interface ApeiriaEnemy {
hp: number;
atk: number;
def: number;
}
function convert(
map: number[][],
[x, y, w, h]: [number, number, number, number],
name: string,
floorId: string
floorId: string,
numMap: Record<number, number>
) {
const clipped: number[][] = [];
@@ -42,3 +79,73 @@ export function convertFloor(
return clipped;
}
function convertApeiriaEnemy(
map: number[][],
enemyMap: Record<number, ApeiriaEnemy>
) {
const width = map[0].length;
const height = map.length;
const enemy = new Set<ApeiriaEnemy>();
for (let ny = 0; ny < height; ny++) {
for (let nx = 0; nx < width; nx++) {
const tile = map[ny][nx];
if (tile > 200 && tile <= 280) {
// 这些是怪物
if (enemyMap[tile]) enemy.add(enemyMap[tile]);
}
}
}
const attrs = [...enemy].map(v => (v.atk + v.def) * v.hp);
const maxAttr = Math.max(...attrs);
const minAttr = Math.min(...attrs);
const delta = maxAttr - minAttr;
for (let ny = 0; ny < height; ny++) {
for (let nx = 0; nx < width; nx++) {
const tile = map[ny][nx];
if (tile > 200 && tile <= 280) {
// 这些是怪物
if (enemyMap[tile]) {
// 替换为弱怪/中怪/强怪
const enemy = enemyMap[tile];
const attr = (enemy.atk + enemy.def) * enemy.hp;
const ad = attr - minAttr;
if (ad < delta / 3) {
map[ny][nx] = 201;
} else if (ad < (delta * 2) / 3) {
map[ny][nx] = 202;
} else {
map[ny][nx] = 203;
}
}
}
}
}
return map;
}
export function convertFloor(
map: number[][],
clip: [number, number, number, number],
name: string,
floorId: string
) {
return convert(map, clip, name, floorId, numMap);
}
export function convertApeiriaMap(
map: number[][],
clip: [number, number, number, number],
name: string,
floorId: string,
enemyMap: Record<number, ApeiriaEnemy>
) {
return convert(
convertApeiriaEnemy(map, enemyMap),
clip,
name,
floorId,
apeiriaMap
);
}
+14 -6
View File
@@ -5,6 +5,7 @@ import { mirrorMapX, mirrorMapY, rotateMap } from './topology/transform';
import { directions, tileType } from './topology/graph';
import { calculateVisualSimilarity } from './vision/similarity';
import { BaseConfig } from './types';
import { Presets, SingleBar } from 'cli-progress';
interface MinamoConfig extends BaseConfig {}
@@ -32,9 +33,9 @@ function chooseFrom<T>(arr: T[], n: number): T[] {
return copy.slice(0, n);
}
function choosePair(n: number) {
function choosePair(n: number, max: number = 1000) {
const totalCount = Math.round((n * (n - 1)) / 2);
const count = Math.min(totalCount, 1000);
const count = Math.min(totalCount, max);
const pairs: number[] = [];
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
@@ -79,7 +80,7 @@ function generateTransformData(
}
}
// 随机抽取最多两个
const trans = chooseFrom(types, Math.floor(Math.random() * 3));
const trans = chooseFrom(types, Math.floor(Math.random() * 2));
return trans
.map(([rot, flip]) => {
const com1 = `${id1}.${rot}.${flip}:${id1}`;
@@ -153,7 +154,7 @@ function generateSimilarData(id: string, map: number[][]) {
// 生成最多五个微调地图
const width = map[0].length;
const height = map.length;
const num = Math.floor(Math.random() * 6);
const num = Math.floor(Math.random() * 3);
const res: [id: string, data: MinamoTrainData][] = [];
for (let i = 0; i < num; i++) {
@@ -210,7 +211,11 @@ function generateDataset(
): Record<string, MinamoTrainData> {
const data: Record<string, MinamoTrainData> = {};
pairs.forEach(v => {
const progress = new SingleBar({}, Presets.shades_classic);
progress.start(pairs.length, 0);
pairs.forEach((v, i) => {
const num1 = Math.floor(v / floorIds.length);
const num2 = v % floorIds.length;
const id1 = floorIds[num1];
@@ -268,8 +273,11 @@ function generateDataset(
// 地图微调训练集
Object.assign(data, Object.fromEntries(generateSimilarData(id1, map1)));
// Object.assign(data, Object.fromEntries(generateSimilarData(id2, map2)));
progress.update(i + 1);
});
progress.stop();
return data;
}
@@ -277,7 +285,7 @@ function parseAllData(data: Map<string, FloorData>): MinamoDataset {
const length = data.size;
const totalCount = Math.round((length * (length - 1)) / 2);
const pairs = choosePair(length);
const pairs = choosePair(length, 10000);
console.log(
`✅ 共发现 ${length} 个楼层,共 ${totalCount} 种组合,选取 ${pairs.length} 个组合`
+39 -4
View File
@@ -1,7 +1,7 @@
import { readFile } from 'fs-extra';
import { join } from 'path';
import { BaseConfig, TowerInfo } from './types';
import { convertFloor } from './floor';
import { convertApeiriaMap, convertFloor } from './floor';
interface DatasetMergable<T> {
datasetId: number;
@@ -67,7 +67,30 @@ export async function parseTowerInfo(
export async function getAllFloors(...info: TowerInfo[]) {
const floorData = await Promise.all(
info.map(tower => {
info.map(async tower => {
// 获取必要信息
const enemyFile = await readFile(
join(tower.path, 'enemys.js'),
'utf-8'
);
const mapFile = await readFile(
join(tower.path, 'maps.js'),
'utf-8'
);
const enemyMap = JSON.parse(
enemyFile.split('\n').slice(1).join('\n')
) as Record<string, any>;
const mapData = JSON.parse(
mapFile.split('\n').slice(1).join('\n')
) as Record<number, any>;
const enemyNumMap: Record<number, any> = {};
// 将怪物转化为数字映射
for (const [key, value] of Object.entries(mapData)) {
if (value.cls === 'enemys') {
enemyNumMap[parseInt(key)] = enemyMap[value.id];
}
}
return Promise.all(
tower.floorIds.map(async id => {
const floorFile = await readFile(
@@ -75,13 +98,25 @@ export async function getAllFloors(...info: TowerInfo[]) {
'utf-8'
);
const data = JSON.parse(
floorFile.split('\n').slice(1).join('\n')
floorFile
.replaceAll("'", '"')
.slice(floorFile.indexOf('=') + 1)
);
const map = data.map as number[][];
// 裁剪地图
const { clip } = tower.config;
const area = clip.special[id] ?? clip.defaults;
return convertFloor(map, area, tower.name, id);
if (tower.name === 'Apeiria') {
return convertApeiriaMap(
map,
area,
tower.name,
id,
enemyNumMap
);
} else {
return convertFloor(map, area, tower.name, id);
}
})
);
})