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'); } } } }); 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); } } } }); });