2026-04-13 11:55:57 +08:00
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
|
import { generatePointCrawlMap } from '@/samples/slay-the-spire-like/map/generator';
|
|
|
|
|
import { MapNodeType } from '@/samples/slay-the-spire-like/map/types';
|
|
|
|
|
|
|
|
|
|
describe('generatePointCrawlMap', () => {
|
|
|
|
|
it('should generate a map with 13 layers', () => {
|
|
|
|
|
const map = generatePointCrawlMap(123);
|
|
|
|
|
expect(map.layers.length).toBe(13);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should have correct fixed layer types', () => {
|
|
|
|
|
const map = generatePointCrawlMap(123);
|
|
|
|
|
const startNode = map.nodes.get('node-0-0');
|
|
|
|
|
const bossNode = map.nodes.get('node-12-0');
|
|
|
|
|
|
|
|
|
|
expect(startNode?.type).toBe(MapNodeType.Start);
|
|
|
|
|
expect(bossNode?.type).toBe(MapNodeType.Boss);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should assign encounters to nodes based on encounterDesert.csv', () => {
|
|
|
|
|
const map = generatePointCrawlMap(456);
|
|
|
|
|
|
|
|
|
|
// Check that nodes have encounters assigned
|
|
|
|
|
let combatWithEncounter = 0;
|
|
|
|
|
let eliteWithEncounter = 0;
|
|
|
|
|
let bossWithEncounter = 0;
|
|
|
|
|
let eventWithEncounter = 0;
|
|
|
|
|
let npcWithEncounter = 0;
|
|
|
|
|
let shelterWithEncounter = 0;
|
|
|
|
|
|
|
|
|
|
for (const node of map.nodes.values()) {
|
|
|
|
|
if (node.type === MapNodeType.Combat && node.encounter) {
|
|
|
|
|
combatWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
if (node.type === MapNodeType.Elite && node.encounter) {
|
|
|
|
|
eliteWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
if (node.type === MapNodeType.Boss && node.encounter) {
|
|
|
|
|
bossWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
if (node.type === MapNodeType.Event && node.encounter) {
|
|
|
|
|
eventWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
if (node.type === MapNodeType.NPC && node.encounter) {
|
|
|
|
|
npcWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
if (node.type === MapNodeType.Shelter && node.encounter) {
|
|
|
|
|
shelterWithEncounter++;
|
|
|
|
|
expect(node.encounter.name).toBeTruthy();
|
|
|
|
|
expect(node.encounter.description).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should have assigned at least some encounters
|
|
|
|
|
const totalWithEncounters =
|
|
|
|
|
combatWithEncounter +
|
|
|
|
|
eliteWithEncounter +
|
|
|
|
|
bossWithEncounter +
|
|
|
|
|
eventWithEncounter +
|
|
|
|
|
npcWithEncounter +
|
|
|
|
|
shelterWithEncounter;
|
|
|
|
|
|
|
|
|
|
expect(totalWithEncounters).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should use correct encounter types for each node type', () => {
|
|
|
|
|
const map = generatePointCrawlMap(789);
|
|
|
|
|
|
|
|
|
|
for (const node of map.nodes.values()) {
|
|
|
|
|
if (node.encounter) {
|
|
|
|
|
// Encounter should match node type conceptually
|
|
|
|
|
// Combat nodes should have enemy encounters, elites should have elite encounters, etc.
|
|
|
|
|
if (node.type === MapNodeType.Boss) {
|
|
|
|
|
expect(node.encounter.description).toContain('Boss');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-13 12:46:11 +08:00
|
|
|
|
|
|
|
|
it('should only connect nodes to nearby nodes to avoid crossing paths', () => {
|
|
|
|
|
const map = generatePointCrawlMap(42);
|
|
|
|
|
|
|
|
|
|
// Check each edge between consecutive layers
|
|
|
|
|
for (let i = 0; i < map.layers.length - 1; i++) {
|
|
|
|
|
const sourceLayer = map.layers[i];
|
|
|
|
|
const targetLayer = map.layers[i + 1];
|
|
|
|
|
|
|
|
|
|
for (const srcId of sourceLayer.nodeIds) {
|
|
|
|
|
const srcNode = map.nodes.get(srcId);
|
|
|
|
|
expect(srcNode).toBeDefined();
|
|
|
|
|
|
|
|
|
|
const srcIndex = sourceLayer.nodeIds.indexOf(srcId);
|
|
|
|
|
|
|
|
|
|
for (const tgtId of srcNode!.childIds) {
|
|
|
|
|
const tgtIndex = targetLayer.nodeIds.indexOf(tgtId);
|
|
|
|
|
|
|
|
|
|
// Calculate the "scaled" source index to compare with target index
|
|
|
|
|
// This accounts for layers with different widths
|
|
|
|
|
const scaledSrcIndex = srcIndex * (targetLayer.nodeIds.length / sourceLayer.nodeIds.length);
|
|
|
|
|
const distance = Math.abs(tgtIndex - scaledSrcIndex);
|
|
|
|
|
|
|
|
|
|
// The distance should be within a reasonable radius
|
|
|
|
|
// Allow some tolerance for edge cases when covering uncovered targets
|
|
|
|
|
const maxAllowedDistance = Math.max(2, Math.floor(targetLayer.nodeIds.length / 2));
|
|
|
|
|
expect(distance).toBeLessThanOrEqual(maxAllowedDistance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not have crossing edges between consecutive layers', () => {
|
|
|
|
|
const map = generatePointCrawlMap(12345);
|
|
|
|
|
|
|
|
|
|
// Check each pair of consecutive layers for crossing edges
|
|
|
|
|
for (let i = 0; i < map.layers.length - 1; i++) {
|
|
|
|
|
const sourceLayer = map.layers[i];
|
|
|
|
|
const targetLayer = map.layers[i + 1];
|
|
|
|
|
|
|
|
|
|
// Collect all edges as pairs of indices
|
|
|
|
|
const edges: Array<{ srcIndex: number; tgtIndex: number }> = [];
|
|
|
|
|
for (let s = 0; s < sourceLayer.nodeIds.length; s++) {
|
|
|
|
|
const srcNode = map.nodes.get(sourceLayer.nodeIds[s]);
|
|
|
|
|
for (const tgtId of srcNode!.childIds) {
|
|
|
|
|
const t = targetLayer.nodeIds.indexOf(tgtId);
|
|
|
|
|
edges.push({ srcIndex: s, tgtIndex: t });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for crossings: edge (s1, t1) and (s2, t2) cross if
|
|
|
|
|
// s1 < s2 but t1 > t2 (or vice versa)
|
|
|
|
|
for (let e1 = 0; e1 < edges.length; e1++) {
|
|
|
|
|
for (let e2 = e1 + 1; e2 < edges.length; e2++) {
|
|
|
|
|
const { srcIndex: s1, tgtIndex: t1 } = edges[e1];
|
|
|
|
|
const { srcIndex: s2, tgtIndex: t2 } = edges[e2];
|
|
|
|
|
|
|
|
|
|
// Skip if they share a source (not a crossing)
|
|
|
|
|
if (s1 === s2) continue;
|
|
|
|
|
|
|
|
|
|
const crosses = (s1 < s2 && t1 > t2) || (s1 > s2 && t1 < t2);
|
|
|
|
|
expect(crosses).toBe(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-13 11:55:57 +08:00
|
|
|
});
|