feat: cost implementation for card play

This commit is contained in:
2026-04-17 14:10:28 +08:00
parent 8155747cac
commit fb66ec55c4
4 changed files with 174 additions and 10 deletions
@@ -10,14 +10,47 @@ import {
onItemDiscard,
getAliveEnemies,
getCombatEntity,
canPlayCard,
payCardCost,
} from '@/samples/slay-the-spire-like/system/combat/effects';
import type { CombatEntity, CombatState, EffectTable, PlayerEntity, EnemyEntity } from '@/samples/slay-the-spire-like/system/combat/types';
import type { EffectData } from '@/samples/slay-the-spire-like/system/types';
import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/system/grid-inventory/types';
import type { GameItemMeta } from '@/samples/slay-the-spire-like/system/progress/types';
import type { ParsedShape } from '@/samples/slay-the-spire-like/system/utils/parse-shape';
import type { Transform2D } from '@/samples/slay-the-spire-like/system/utils/shape-collision';
function createEffect(id: string, lifecycle: EffectData['lifecycle']): EffectData {
return { id, name: id, description: '', lifecycle };
}
function createCard(id: string, costType: 'energy' | 'uses' | 'none', costCount: number) {
return { id, name: id, desc: '', type: 'item' as const, costType, costCount, targetType: 'none' as const, effects: [] as const };
}
function createItem(itemId: string, cardId: string, costType: 'energy' | 'uses' | 'none', costCount: number, depletion = 0): InventoryItem<GameItemMeta> {
return {
id: itemId,
shape: { id: '1x1', cells: [{ x: 0, y: 0 }] } as unknown as ParsedShape,
transform: { x: 0, y: 0, rotation: 0, flipX: false, flipY: false } as unknown as Transform2D,
meta: {
itemData: { id: itemId, type: 'weapon', name: itemId, shape: '1x1', card: createCard(cardId, costType, costCount), price: 0, description: '' },
shape: { id: '1x1', cells: [{ x: 0, y: 0 }] } as unknown as ParsedShape,
depletion: costType === 'uses' ? depletion : undefined,
},
};
}
function createInventory(items: InventoryItem<GameItemMeta>[]): GridInventory<GameItemMeta> {
const map = new Map<string, InventoryItem<GameItemMeta>>();
const occupied = new Set<string>();
for (const item of items) {
map.set(item.id, item);
occupied.add(`${item.transform.x},${item.transform.y}`);
}
return { width: 6, height: 4, items: map, occupiedCells: occupied };
}
function createCombatEntity(hp = 10, maxHp = 10): CombatEntity {
return {
effects: {},
@@ -414,4 +447,104 @@ describe('combat/effects', () => {
expect(entity).toBeUndefined();
});
});
describe('canPlayCard', () => {
it('should allow playing energy card when player has enough energy', () => {
const player = createPlayerEntity();
player.energy = 3;
const inventory = createInventory([]);
const result = canPlayCard(player, 'energy', 2, 'any', inventory);
expect(result).toBe(true);
});
it('should reject playing energy card when player lacks energy', () => {
const player = createPlayerEntity();
player.energy = 1;
const inventory = createInventory([]);
const result = canPlayCard(player, 'energy', 2, 'any', inventory);
expect(result).toBe(false);
});
it('should allow playing uses card when item has remaining uses', () => {
const player = createPlayerEntity();
const item = createItem('potion-1', 'potion-card', 'uses', 3, 1);
const inventory = createInventory([item]);
const result = canPlayCard(player, 'uses', 3, 'potion-1', inventory);
expect(result).toBe(true);
});
it('should reject playing uses card when item is depleted', () => {
const player = createPlayerEntity();
const item = createItem('potion-1', 'potion-card', 'uses', 3, 3);
const inventory = createInventory([item]);
const result = canPlayCard(player, 'uses', 3, 'potion-1', inventory);
expect(result).toBe(false);
});
it('should reject playing uses card when item not in inventory', () => {
const player = createPlayerEntity();
const inventory = createInventory([]);
const result = canPlayCard(player, 'uses', 1, 'missing', inventory);
expect(result).toBe(false);
});
it('should always allow playing none cost card', () => {
const player = createPlayerEntity();
player.energy = 0;
const inventory = createInventory([]);
const result = canPlayCard(player, 'none', 0, 'any', inventory);
expect(result).toBe(true);
});
});
describe('payCardCost', () => {
it('should deduct energy for energy cost card', () => {
const player = createPlayerEntity();
player.energy = 3;
const inventory = createInventory([]);
payCardCost(player, 'energy', 2, 'any', inventory);
expect(player.energy).toBe(1);
});
it('should increment depletion for uses cost card', () => {
const player = createPlayerEntity();
const item = createItem('potion-1', 'potion-card', 'uses', 3, 1);
const inventory = createInventory([item]);
payCardCost(player, 'uses', 3, 'potion-1', inventory);
expect(item.meta?.depletion).toBe(4);
});
it('should do nothing for none cost card', () => {
const player = createPlayerEntity();
player.energy = 3;
const inventory = createInventory([]);
payCardCost(player, 'none', 0, 'any', inventory);
expect(player.energy).toBe(3);
});
it('should handle missing item gracefully for uses cost', () => {
const player = createPlayerEntity();
const inventory = createInventory([]);
expect(() => payCardCost(player, 'uses', 1, 'missing', inventory)).not.toThrow();
});
});
});