diff --git a/tests/samples/slay-the-spire-like/deck/factory.test.ts b/tests/samples/slay-the-spire-like/deck/factory.test.ts index ad19354..bff46e6 100644 --- a/tests/samples/slay-the-spire-like/deck/factory.test.ts +++ b/tests/samples/slay-the-spire-like/deck/factory.test.ts @@ -1,33 +1,57 @@ import { describe, it, expect } from 'vitest'; import { generateDeckFromInventory, - createStatusCard, + createCard, createDeckRegions, createPlayerDeck, generateCardId, -} from '@/samples/slay-the-spire-like/deck/factory'; -import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory'; -import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory'; -import type { GameItemMeta } from '@/samples/slay-the-spire-like/progress/types'; -import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision'; -import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape'; +} from '@/samples/slay-the-spire-like/system/deck/factory'; +import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/system/grid-inventory'; +import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/system/grid-inventory'; +import type { GameItemMeta } from '@/samples/slay-the-spire-like/system/progress/types'; +import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/system/utils/shape-collision'; +import { parseShapeString } from '@/samples/slay-the-spire-like/system/utils/parse-shape'; +import type { CardData, ItemData } from '@/samples/slay-the-spire-like/system/types'; + +/** + * Helper: create a minimal CardData for testing. + */ +function createTestCardData(id: string, name: string, desc: string): CardData { + return { + id, + name, + desc, + type: 'item', + costType: 'energy', + costCount: 1, + targetType: 'single', + effects: [], + }; +} + +/** + * Helper: create a minimal ItemData for testing. + */ +function createTestItemData(id: string, name: string, shapeStr: string, desc: string): ItemData { + return { + id, + type: 'weapon', + name, + shape: shapeStr, + card: createTestCardData(id, name, desc), + price: 10, + description: desc, + }; +} /** * Helper: create a minimal GameItemMeta for testing. */ function createTestMeta(name: string, desc: string, shapeStr: string): GameItemMeta { const shape = parseShapeString(shapeStr); + const itemData = createTestItemData(name.toLowerCase(), name, shapeStr, desc); return { - itemData: { - type: 'weapon', - name, - shape: shapeStr, - costType: 'energy', - costCount: 1, - targetType: 'single', - price: 10, - desc, - }, + itemData, shape, }; } @@ -70,21 +94,14 @@ describe('deck/factory', () => { }); }); - describe('createStatusCard', () => { - it('should create a card with null sourceItemId and itemData', () => { - const card = createStatusCard('wound-1', '伤口', '无法被弃牌'); - - expect(card.id).toBe('wound-1'); - expect(card.sourceItemId).toBeNull(); - expect(card.itemData).toBeNull(); - expect(card.cellKey).toBeNull(); - expect(card.displayName).toBe('伤口'); - expect(card.description).toBe('无法被弃牌'); - }); - - it('should have empty region and position', () => { - const card = createStatusCard('stun-1', '眩晕', '跳过出牌阶段'); + describe('createCard', () => { + it('should create a card with itemId and cardData', () => { + const cardData = createTestCardData('wound', '伤口', '无法被弃牌'); + const card = createCard('wound-1', cardData, 0); + expect(card.id).toBe('card-wound-1-0'); + expect(card.itemId).toBe('wound-1'); + expect(card.cardData).toBe(cardData); expect(card.regionId).toBe(''); expect(card.position).toEqual([]); }); @@ -98,10 +115,10 @@ describe('deck/factory', () => { const deck = generateDeckFromInventory(inv); expect(Object.keys(deck.cards).length).toBe(6); - expect(deck.drawPile.length).toBe(6); - expect(deck.hand).toEqual([]); - expect(deck.discardPile).toEqual([]); - expect(deck.exhaustPile).toEqual([]); + expect(deck.regions.drawPile.childIds.length).toBe(6); + expect(deck.regions.hand.childIds).toEqual([]); + expect(deck.regions.discardPile.childIds).toEqual([]); + expect(deck.regions.exhaustPile.childIds).toEqual([]); }); it('should link cards to their source items', () => { @@ -109,18 +126,18 @@ describe('deck/factory', () => { const deck = generateDeckFromInventory(inv); const daggerCards = Object.values(deck.cards).filter( - c => c.sourceItemId === 'dagger-1' + c => c.itemId === 'dagger-1' ); const shieldCards = Object.values(deck.cards).filter( - c => c.sourceItemId === 'shield-1' + c => c.itemId === 'shield-1' ); expect(daggerCards.length).toBe(2); expect(shieldCards.length).toBe(4); - // Verify item data - expect(daggerCards[0].itemData?.name).toBe('短刀'); - expect(shieldCards[0].itemData?.name).toBe('盾'); + // Verify card data + expect(daggerCards[0].cardData.name).toBe('短刀'); + expect(shieldCards[0].cardData.name).toBe('盾'); }); it('should set displayName and description from item data', () => { @@ -128,28 +145,28 @@ describe('deck/factory', () => { const deck = generateDeckFromInventory(inv); for (const card of Object.values(deck.cards)) { - expect(card.displayName).toBeTruthy(); - expect(card.description).toBeTruthy(); + expect(card.cardData.name).toBeTruthy(); + expect(card.cardData.desc).toBeTruthy(); } const daggerCard = Object.values(deck.cards).find( - c => c.itemData?.name === '短刀' + c => c.cardData.name === '短刀' ); - expect(daggerCard?.displayName).toBe('短刀'); - expect(daggerCard?.description).toBe('【攻击3】【攻击3】'); + expect(daggerCard?.cardData.name).toBe('短刀'); + expect(daggerCard?.cardData.desc).toBe('【攻击3】【攻击3】'); }); - it('should assign unique cell keys to each card from same item', () => { + it('should assign unique IDs to each card from same item', () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); const daggerCards = Object.values(deck.cards).filter( - c => c.sourceItemId === 'dagger-1' + c => c.itemId === 'dagger-1' ); - const cellKeys = daggerCards.map(c => c.cellKey); - const uniqueKeys = new Set(cellKeys); - expect(uniqueKeys.size).toBe(cellKeys.length); + const ids = daggerCards.map(c => c.id); + const uniqueIds = new Set(ids); + expect(uniqueIds.size).toBe(ids.length); }); it('should handle empty inventory', () => { @@ -157,19 +174,19 @@ describe('deck/factory', () => { const deck = generateDeckFromInventory(inv); expect(Object.keys(deck.cards).length).toBe(0); - expect(deck.drawPile).toEqual([]); + expect(deck.regions.drawPile.childIds).toEqual([]); }); it('should place all cards in draw pile initially', () => { const inv = createTestInventory(); const deck = generateDeckFromInventory(inv); - for (const cardId of deck.drawPile) { + for (const cardId of deck.regions.drawPile.childIds) { expect(deck.cards[cardId]).toBeDefined(); } // All cards are in draw pile - expect(new Set(deck.drawPile).size).toBe(Object.keys(deck.cards).length); + expect(new Set(deck.regions.drawPile.childIds).size).toBe(Object.keys(deck.cards).length); }); }); @@ -198,10 +215,10 @@ describe('deck/factory', () => { const deck = createPlayerDeck(); expect(deck.cards).toEqual({}); - expect(deck.drawPile).toEqual([]); - expect(deck.hand).toEqual([]); - expect(deck.discardPile).toEqual([]); - expect(deck.exhaustPile).toEqual([]); + expect(deck.regions.drawPile.childIds).toEqual([]); + expect(deck.regions.hand.childIds).toEqual([]); + expect(deck.regions.discardPile.childIds).toEqual([]); + expect(deck.regions.exhaustPile.childIds).toEqual([]); }); }); }); diff --git a/tests/samples/slay-the-spire-like/grid-inventory.test.ts b/tests/samples/slay-the-spire-like/grid-inventory.test.ts index 2600ce5..0ac905a 100644 --- a/tests/samples/slay-the-spire-like/grid-inventory.test.ts +++ b/tests/samples/slay-the-spire-like/grid-inventory.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape'; -import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision'; +import { parseShapeString } from '@/samples/slay-the-spire-like/system/utils/parse-shape'; +import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/system/utils/shape-collision'; import { createGridInventory, placeItem, @@ -14,7 +14,7 @@ import { validatePlacement, type GridInventory, type InventoryItem, -} from '@/samples/slay-the-spire-like/grid-inventory'; +} from '@/samples/slay-the-spire-like/system/grid-inventory'; /** * Helper: create a test inventory item. diff --git a/tests/samples/slay-the-spire-like/map/generator.test.ts b/tests/samples/slay-the-spire-like/map/generator.test.ts index 2d3d8e7..c09a7bc 100644 --- a/tests/samples/slay-the-spire-like/map/generator.test.ts +++ b/tests/samples/slay-the-spire-like/map/generator.test.ts @@ -1,15 +1,17 @@ import { describe, it, expect } from 'vitest'; -import { generatePointCrawlMap, hasPath } from '@/samples/slay-the-spire-like/map/generator'; -import { MapNodeType, MapLayerType } from '@/samples/slay-the-spire-like/map/types'; +import { generatePointCrawlMap, hasPath } from '@/samples/slay-the-spire-like/system/map/generator'; +import { MapNodeType, MapLayerType } from '@/samples/slay-the-spire-like/system/map/types'; +import { createRNG } from '@/utils/rng'; +import { encounters } from '@/samples/slay-the-spire-like/data/desert'; describe('generatePointCrawlMap', () => { it('should generate a map with 10 layers', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); expect(map.layers.length).toBe(10); }); it('should have correct layer structure', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const expectedStructure = [ 'start', MapLayerType.Wild, @@ -29,7 +31,7 @@ describe('generatePointCrawlMap', () => { }); it('should have correct node counts per layer', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const expectedCounts = [1, 3, 3, 4, 3, 3, 4, 3, 3, 1]; for (let i = 0; i < expectedCounts.length; i++) { @@ -38,7 +40,7 @@ describe('generatePointCrawlMap', () => { }); it('should have Start and End nodes with correct types', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const startNode = map.nodes.get('node-0-0'); const endNode = map.nodes.get('node-9-0'); @@ -47,7 +49,7 @@ describe('generatePointCrawlMap', () => { }); it('should have wild layers with minion/elite/event types', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const wildLayerIndices = [1, 2, 4, 5, 7, 8]; const validWildTypes = new Set([MapNodeType.Minion, MapNodeType.Elite, MapNodeType.Event]); @@ -62,7 +64,7 @@ describe('generatePointCrawlMap', () => { }); it('should have settlement layers with at least 1 camp, 1 shop, 1 curio', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const settlementLayerIndices = [3, 6]; for (const layerIdx of settlementLayerIndices) { @@ -77,7 +79,7 @@ describe('generatePointCrawlMap', () => { }); it('should have Start connected to all 3 wild nodes', () => { - const map = generatePointCrawlMap(42); + const map = generatePointCrawlMap(createRNG(42), encounters); const startNode = map.nodes.get('node-0-0'); const wildLayer = map.layers[1]; @@ -86,7 +88,7 @@ describe('generatePointCrawlMap', () => { }); it('should have each wild node connect to 1 wild node in wild→wild layers', () => { - const map = generatePointCrawlMap(42); + const map = generatePointCrawlMap(createRNG(42), encounters); const wildToWildTransitions = [ { src: 1, tgt: 2 }, { src: 4, tgt: 5 }, @@ -105,7 +107,7 @@ describe('generatePointCrawlMap', () => { }); it('should have each wild node connect to 2 settlement nodes in wild→settlement layers', () => { - const map = generatePointCrawlMap(42); + const map = generatePointCrawlMap(createRNG(42), encounters); const wildToSettlementTransitions = [ { src: 2, tgt: 3 }, { src: 5, tgt: 6 }, @@ -125,7 +127,7 @@ describe('generatePointCrawlMap', () => { }); it('should have settlement nodes connect correctly (1-2-2-1 pattern)', () => { - const map = generatePointCrawlMap(42); + const map = generatePointCrawlMap(createRNG(42), encounters); const settlementToWildTransitions = [ { src: 3, tgt: 4 }, { src: 6, tgt: 7 }, @@ -153,7 +155,7 @@ describe('generatePointCrawlMap', () => { }); it('should have all 3 wild nodes connect to End', () => { - const map = generatePointCrawlMap(42); + const map = generatePointCrawlMap(createRNG(42), encounters); const lastWildLayer = map.layers[8]; const endNode = map.nodes.get('node-9-0'); @@ -164,7 +166,7 @@ describe('generatePointCrawlMap', () => { }); it('should have all nodes reachable from Start and can reach End', () => { - const map = generatePointCrawlMap(123); + const map = generatePointCrawlMap(createRNG(123), encounters); const startId = 'node-0-0'; const endId = 'node-9-0'; @@ -176,7 +178,7 @@ describe('generatePointCrawlMap', () => { }); it('should not have crossing edges in wild→wild transitions', () => { - const map = generatePointCrawlMap(12345); + const map = generatePointCrawlMap(createRNG(12345), encounters); const wildToWildTransitions = [ { src: 1, tgt: 2 }, { src: 4, tgt: 5 }, @@ -214,7 +216,7 @@ describe('generatePointCrawlMap', () => { }); it('should not have crossing edges in wild→settlement transitions', () => { - const map = generatePointCrawlMap(12345); + const map = generatePointCrawlMap(createRNG(12345), encounters); const wildToSettlementTransitions = [ { src: 2, tgt: 3 }, { src: 5, tgt: 6 }, @@ -251,7 +253,7 @@ describe('generatePointCrawlMap', () => { }); it('should not have crossing edges in settlement→wild transitions', () => { - const map = generatePointCrawlMap(12345); + const map = generatePointCrawlMap(createRNG(12345), encounters); const settlementToWildTransitions = [ { src: 3, tgt: 4 }, { src: 6, tgt: 7 }, @@ -288,7 +290,7 @@ describe('generatePointCrawlMap', () => { }); it('should assign encounters to all non-Start/End nodes', () => { - const map = generatePointCrawlMap(456); + const map = generatePointCrawlMap(createRNG(456), encounters); for (const node of map.nodes.values()) { if (node.type === MapNodeType.Start || node.type === MapNodeType.End) { @@ -306,7 +308,7 @@ describe('generatePointCrawlMap', () => { it('should assign encounters to all nodes across multiple seeds', () => { // Test multiple seeds to ensure no random failure for (let seed = 0; seed < 20; seed++) { - const map = generatePointCrawlMap(seed); + const map = generatePointCrawlMap(createRNG(seed), encounters); for (const node of map.nodes.values()) { if (node.type === MapNodeType.Start || node.type === MapNodeType.End) { @@ -321,7 +323,7 @@ describe('generatePointCrawlMap', () => { it('should minimize same-layer repetitions in wild layer pairs', () => { // Test that wild layers in pairs (1-2, 4-5, 7-8) have minimal duplicate types within each layer - const map = generatePointCrawlMap(12345); + const map = generatePointCrawlMap(createRNG(12345), encounters); const wildPairIndices = [ [1, 2], [4, 5], @@ -351,7 +353,7 @@ describe('generatePointCrawlMap', () => { it('should minimize adjacent repetitions in wild→wild connections', () => { // Test that wild nodes connected by wild→wild edges have different types - const map = generatePointCrawlMap(12345); + const map = generatePointCrawlMap(createRNG(12345), encounters); const wildToWildPairs = [ { src: 1, tgt: 2 }, { src: 4, tgt: 5 }, diff --git a/tests/samples/slay-the-spire-like/utils/shape-utils.test.ts b/tests/samples/slay-the-spire-like/utils/shape-utils.test.ts index 0ebf72b..da260d9 100644 --- a/tests/samples/slay-the-spire-like/utils/shape-utils.test.ts +++ b/tests/samples/slay-the-spire-like/utils/shape-utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape'; +import { parseShapeString } from '@/samples/slay-the-spire-like/system/utils/parse-shape'; import { checkCollision, checkBoardCollision, @@ -8,7 +8,7 @@ import { transformShape, getOccupiedCells, IDENTITY_TRANSFORM, -} from '@/samples/slay-the-spire-like/utils/shape-collision'; +} from '@/samples/slay-the-spire-like/system/utils/shape-collision'; describe('parseShapeString', () => { it('should parse a single cell with o', () => {