Skip to main content
Glama
engine.test.ts7.12 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { CombatEngine, CombatParticipant } from '../../src/engine/combat/engine'; describe('CombatEngine', () => { let engine: CombatEngine; let participants: CombatParticipant[]; beforeEach(() => { engine = new CombatEngine('combat-test-seed'); participants = [ { id: 'fighter', name: 'Fighter', initiativeBonus: 2, hp: 30, maxHp: 30, conditions: [] }, { id: 'wizard', name: 'Wizard', initiativeBonus: 3, hp: 20, maxHp: 20, conditions: [] }, { id: 'rogue', name: 'Rogue', initiativeBonus: 4, hp: 25, maxHp: 25, conditions: [] }, { id: 'cleric', name: 'Cleric', initiativeBonus: 1, hp: 28, maxHp: 28, conditions: [] } ]; }); describe('Encounter Initialization', () => { it('should start an encounter and roll initiative', () => { const state = engine.startEncounter(participants); expect(state.participants).toHaveLength(4); expect(state.turnOrder).toHaveLength(4); expect(state.currentTurnIndex).toBe(0); expect(state.round).toBe(1); }); it('should order participants by initiative', () => { const state = engine.startEncounter(participants); // turnOrder should have IDs sorted by initiative expect(state.turnOrder).toEqual(expect.arrayContaining(['fighter', 'wizard', 'rogue', 'cleric'])); }); it('should be deterministic with same seed', () => { const engine1 = new CombatEngine('same-seed'); const engine2 = new CombatEngine('same-seed'); const state1 = engine1.startEncounter([...participants]); const state2 = engine2.startEncounter([...participants]); expect(state1.turnOrder).toEqual(state2.turnOrder); }); }); describe('Turn Management', () => { beforeEach(() => { engine.startEncounter(participants); }); it('should get the current participant', () => { const current = engine.getCurrentParticipant(); expect(current).not.toBeNull(); expect(current?.id).toBeDefined(); }); it('should advance to next turn', () => { const first = engine.getCurrentParticipant(); const second = engine.nextTurn(); expect(second).not.toBeNull(); expect(second?.id).not.toBe(first?.id); }); it('should cycle through all participants', () => { const state = engine.getState(); if (!state) throw new Error('State is null'); const encounteredIds = new Set<string>(); // Go through all participants for (let i = 0; i < state.turnOrder.length; i++) { const current = engine.getCurrentParticipant(); if (current) encounteredIds.add(current.id); engine.nextTurn(); } expect(encounteredIds.size).toBe(4); }); it('should increment round after full cycle', () => { const state = engine.getState(); if (!state) throw new Error('State is null'); expect(state.round).toBe(1); // Advance through all participants for (let i = 0; i < state.turnOrder.length; i++) { engine.nextTurn(); } const newState = engine.getState(); expect(newState?.round).toBe(2); }); it('should reset to first participant after full cycle', () => { const state = engine.getState(); if (!state) throw new Error('State is null'); const firstParticipant = engine.getCurrentParticipant(); // Advance through all participants for (let i = 0; i < state.turnOrder.length; i++) { engine.nextTurn(); } const cycledBack = engine.getCurrentParticipant(); expect(cycledBack?.id).toBe(firstParticipant?.id); }); }); describe('Pathfinder 2e Degrees of Success', () => { it('should make checks with degrees of success', () => { const degree = engine.makeCheck(5, 15); expect(['critical-failure', 'failure', 'success', 'critical-success']).toContain(degree); }); it('should be deterministic for same engine state', () => { const engine1 = new CombatEngine('degree-seed'); const engine2 = new CombatEngine('degree-seed'); const degree1 = engine1.makeCheck(3, 12); const degree2 = engine2.makeCheck(3, 12); expect(degree1).toBe(degree2); }); }); describe('Damage and Healing', () => { beforeEach(() => { engine.startEncounter(participants); }); it('should apply damage to a participant', () => { const state = engine.getState(); const fighterHp = state?.participants.find(p => p.id === 'fighter')?.hp; engine.applyDamage('fighter', 10); const newState = engine.getState(); const newFighterHp = newState?.participants.find(p => p.id === 'fighter')?.hp; expect(newFighterHp).toBe((fighterHp || 0) - 10); }); it('should not reduce HP below 0', () => { engine.applyDamage('wizard', 100); const state = engine.getState(); const wizardHp = state?.participants.find(p => p.id === 'wizard')?.hp; expect(wizardHp).toBe(0); }); it('should heal a participant', () => { engine.applyDamage('rogue', 15); const damagedHp = engine.getState()?.participants.find(p => p.id === 'rogue')?.hp; engine.heal('rogue', 10); const healedHp = engine.getState()?.participants.find(p => p.id === 'rogue')?.hp; expect(healedHp).toBe((damagedHp || 0) + 10); }); it('should not heal above max HP', () => { engine.heal('cleric', 100); const state = engine.getState(); const clericHp = state?.participants.find(p => p.id === 'cleric')?.hp; const clericMaxHp = state?.participants.find(p => p.id === 'cleric')?.maxHp; expect(clericHp).toBe(clericMaxHp); }); }); describe('Participant Status', () => { beforeEach(() => { engine.startEncounter(participants); }); it('should check if participant is conscious', () => { expect(engine.isConscious('fighter')).toBe(true); engine.applyDamage('fighter', 100); expect(engine.isConscious('fighter')).toBe(false); }); it('should count conscious participants', () => { expect(engine.getConsciousCount()).toBe(4); engine.applyDamage('fighter', 100); expect(engine.getConsciousCount()).toBe(3); engine.applyDamage('wizard', 100); expect(engine.getConsciousCount()).toBe(2); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/rpg-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server