chore: pass PR1 verification gate (format, lint, coverage)
This commit is contained in:
@@ -16,9 +16,11 @@ describe('M1 stub content pack', () => {
|
|||||||
it('can simulate a costed action without throwing', () => {
|
it('can simulate a costed action without throwing', () => {
|
||||||
const state = createGameState(content);
|
const state = createGameState(content);
|
||||||
const trade = content.actions.find((a) => a.costs.length > 0);
|
const trade = content.actions.find((a) => a.costs.length > 0);
|
||||||
expect(trade).toBeDefined();
|
if (!trade) {
|
||||||
enqueueAction(state, content, trade!.id);
|
throw new Error('expected at least one costed action');
|
||||||
tickGame(state, content, trade!.durationMs);
|
}
|
||||||
|
enqueueAction(state, content, trade.id);
|
||||||
|
tickGame(state, content, trade.durationMs);
|
||||||
expect(state.activeActionId).toBeNull();
|
expect(state.activeActionId).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,9 +4,42 @@ export const resourceDefs = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const actionDefs = [
|
export const actionDefs = [
|
||||||
{ id: 'gather_supplies', name: 'Gather supplies', durationMs: 3000, yields: [{ resourceId: 'supplies', amount: 2 }] },
|
{
|
||||||
{ id: 'scout_path', name: 'Scout the path', durationMs: 5000, costs: [{ resourceId: 'supplies', amount: 2 }], yields: [{ resourceId: 'coin', amount: 1 }] },
|
id: 'gather_supplies',
|
||||||
{ id: 'trade_supplies', name: 'Trade at camp', durationMs: 4000, costs: [{ resourceId: 'supplies', amount: 3 }], yields: [{ resourceId: 'coin', amount: 2 }], unlock: { minResources: { coin: 1 } } },
|
name: 'Gather supplies',
|
||||||
{ id: 'fortify_camp', name: 'Fortify camp', durationMs: 8000, costs: [{ resourceId: 'supplies', amount: 5 }, { resourceId: 'coin', amount: 2 }], yields: [{ resourceId: 'supplies', amount: 4 }], unlock: { minResources: { supplies: 8 } } },
|
durationMs: 3000,
|
||||||
{ id: 'rest', name: 'Rest briefly', durationMs: 2000, yields: [{ resourceId: 'supplies', amount: 1 }] },
|
yields: [{ resourceId: 'supplies', amount: 2 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'scout_path',
|
||||||
|
name: 'Scout the path',
|
||||||
|
durationMs: 5000,
|
||||||
|
costs: [{ resourceId: 'supplies', amount: 2 }],
|
||||||
|
yields: [{ resourceId: 'coin', amount: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trade_supplies',
|
||||||
|
name: 'Trade at camp',
|
||||||
|
durationMs: 4000,
|
||||||
|
costs: [{ resourceId: 'supplies', amount: 3 }],
|
||||||
|
yields: [{ resourceId: 'coin', amount: 2 }],
|
||||||
|
unlock: { minResources: { coin: 1 } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fortify_camp',
|
||||||
|
name: 'Fortify camp',
|
||||||
|
durationMs: 8000,
|
||||||
|
costs: [
|
||||||
|
{ resourceId: 'supplies', amount: 5 },
|
||||||
|
{ resourceId: 'coin', amount: 2 },
|
||||||
|
],
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 4 }],
|
||||||
|
unlock: { minResources: { supplies: 8 } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rest',
|
||||||
|
name: 'Rest briefly',
|
||||||
|
durationMs: 2000,
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 1 }],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -68,16 +68,12 @@ export function buildContent(input: { resources: unknown[]; actions: unknown[] }
|
|||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const y of action.yields) {
|
for (const y of action.yields) {
|
||||||
if (!resourcesById[y.resourceId]) {
|
if (!resourcesById[y.resourceId]) {
|
||||||
throw new Error(
|
throw new Error(`Action "${action.id}" yields unknown resource "${y.resourceId}"`);
|
||||||
`Action "${action.id}" yields unknown resource "${y.resourceId}"`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const c of action.costs) {
|
for (const c of action.costs) {
|
||||||
if (!resourcesById[c.resourceId]) {
|
if (!resourcesById[c.resourceId]) {
|
||||||
throw new Error(
|
throw new Error(`Action "${action.id}" cost references unknown resource "${c.resourceId}"`);
|
||||||
`Action "${action.id}" cost references unknown resource "${c.resourceId}"`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import { buildContent } from '../../content/schema';
|
import { buildContent } from '../../content/schema';
|
||||||
import {
|
import {
|
||||||
cancelQueuedAction,
|
cancelQueuedAction,
|
||||||
|
canUnlockAction,
|
||||||
clearQueue,
|
clearQueue,
|
||||||
createGameState,
|
createGameState,
|
||||||
enqueueAction,
|
enqueueAction,
|
||||||
@@ -13,7 +14,12 @@ function testContent() {
|
|||||||
return buildContent({
|
return buildContent({
|
||||||
resources: [{ id: 'gold', name: 'Gold', startAmount: 5 }],
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 5 }],
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'forage', name: 'Forage', durationMs: 300, yields: [{ resourceId: 'gold', amount: 2 }] },
|
{
|
||||||
|
id: 'forage',
|
||||||
|
name: 'Forage',
|
||||||
|
durationMs: 300,
|
||||||
|
yields: [{ resourceId: 'gold', amount: 2 }],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -180,6 +186,24 @@ describe('unlock conditions', () => {
|
|||||||
enqueueAction(state, content, 'scout');
|
enqueueAction(state, content, 'scout');
|
||||||
expect(state.activeActionId).toBe('scout');
|
expect(state.activeActionId).toBe('scout');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects actions until required story flags are set', () => {
|
||||||
|
const content = buildContent({
|
||||||
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'secret',
|
||||||
|
name: 'Secret',
|
||||||
|
durationMs: 100,
|
||||||
|
yields: [{ resourceId: 'gold', amount: 1 }],
|
||||||
|
unlock: { requireStoryFlags: ['path_scouted'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const state = createGameState(content);
|
||||||
|
expect(() => enqueueAction(state, content, 'secret')).toThrow(/cannot enqueue/i);
|
||||||
|
expect(canUnlockAction(state, content, 'secret', { path_scouted: true })).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('completion advances queue', () => {
|
describe('completion advances queue', () => {
|
||||||
@@ -203,6 +227,41 @@ describe('completion advances queue', () => {
|
|||||||
expect(state.actionElapsedMs).toBe(0);
|
expect(state.actionElapsedMs).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skips queued actions that are unaffordable when their turn arrives', () => {
|
||||||
|
const content = buildContent({
|
||||||
|
resources: [{ id: 'supplies', name: 'Supplies', startAmount: 0 }],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'cheap',
|
||||||
|
name: 'Cheap',
|
||||||
|
durationMs: 100,
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dear',
|
||||||
|
name: 'Dear',
|
||||||
|
durationMs: 100,
|
||||||
|
costs: [{ resourceId: 'supplies', amount: 6 }],
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'free',
|
||||||
|
name: 'Free',
|
||||||
|
durationMs: 100,
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 1 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const state = createGameState(content);
|
||||||
|
state.activeActionId = 'cheap';
|
||||||
|
state.actionElapsedMs = 99;
|
||||||
|
state.actionQueue = ['dear', 'free'];
|
||||||
|
state.resources.supplies = 3;
|
||||||
|
tickGame(state, content, 1);
|
||||||
|
expect(state.activeActionId).toBe('free');
|
||||||
|
expect(state.actionQueue).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('grants all yields on completion', () => {
|
it('grants all yields on completion', () => {
|
||||||
const content = buildContent({
|
const content = buildContent({
|
||||||
resources: [
|
resources: [
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ function testContent() {
|
|||||||
return buildContent({
|
return buildContent({
|
||||||
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'forage', name: 'Forage', durationMs: 3000, yields: [{ resourceId: 'gold', amount: 1 }] },
|
{
|
||||||
|
id: 'forage',
|
||||||
|
name: 'Forage',
|
||||||
|
durationMs: 3000,
|
||||||
|
yields: [{ resourceId: 'gold', amount: 1 }],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -99,7 +99,10 @@ function beginAction(state: GameState, content: Content, actionId: string): void
|
|||||||
|
|
||||||
function startNextFromQueue(state: GameState, content: Content): void {
|
function startNextFromQueue(state: GameState, content: Content): void {
|
||||||
while (state.actionQueue.length > 0) {
|
while (state.actionQueue.length > 0) {
|
||||||
const nextId = state.actionQueue.shift()!;
|
const nextId = state.actionQueue.shift();
|
||||||
|
if (!nextId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (isActionAvailable(state, content, nextId)) {
|
if (isActionAvailable(state, content, nextId)) {
|
||||||
beginAction(state, content, nextId);
|
beginAction(state, content, nextId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ function testContent() {
|
|||||||
return buildContent({
|
return buildContent({
|
||||||
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'forage', name: 'Forage', durationMs: 3000, yields: [{ resourceId: 'gold', amount: 1 }] },
|
{
|
||||||
|
id: 'forage',
|
||||||
|
name: 'Forage',
|
||||||
|
durationMs: 3000,
|
||||||
|
yields: [{ resourceId: 'gold', amount: 1 }],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ function testContent() {
|
|||||||
return buildContent({
|
return buildContent({
|
||||||
resources: [{ id: 'gold', name: 'Gold', startAmount: 4 }],
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 4 }],
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'forage', name: 'Forage', durationMs: 200, yields: [{ resourceId: 'gold', amount: 1 }] },
|
{
|
||||||
|
id: 'forage',
|
||||||
|
name: 'Forage',
|
||||||
|
durationMs: 200,
|
||||||
|
yields: [{ resourceId: 'gold', amount: 1 }],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user