feat(state): view model action availability and story passage
This commit is contained in:
+83
-3
@@ -1,5 +1,11 @@
|
||||
import type { Content } from '../content/schema';
|
||||
import type { GameState } from '../engine/game';
|
||||
import type { GameContent } from '../content/index';
|
||||
import {
|
||||
canAffordAction,
|
||||
canUnlockAction,
|
||||
isActionAvailable,
|
||||
type GameState,
|
||||
} from '../engine/game';
|
||||
import { getAvailableChoices, getCurrentNode } from '../engine/story';
|
||||
|
||||
/**
|
||||
* Pure mapping from engine state to the view model the React shell renders.
|
||||
@@ -12,6 +18,29 @@ export interface ResourceView {
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ActionView {
|
||||
id: string;
|
||||
name: string;
|
||||
available: boolean;
|
||||
disabledReason: string | null;
|
||||
storyHint?: string;
|
||||
storyTooltip?: string;
|
||||
costsSummary: string | null;
|
||||
yieldsSummary: string | null;
|
||||
}
|
||||
|
||||
export interface StoryChoiceView {
|
||||
id: string;
|
||||
label: string;
|
||||
disabled: boolean;
|
||||
disabledReason: string | null;
|
||||
}
|
||||
|
||||
export interface StoryView {
|
||||
currentProse: string | null;
|
||||
choices: StoryChoiceView[];
|
||||
}
|
||||
|
||||
export interface GameView {
|
||||
resources: ResourceView[];
|
||||
activeActionId: string | null;
|
||||
@@ -20,9 +49,30 @@ export interface GameView {
|
||||
actionProgress: number;
|
||||
queuedActionIds: string[];
|
||||
queuedActionNames: string[];
|
||||
actions: ActionView[];
|
||||
story: StoryView;
|
||||
}
|
||||
|
||||
export function toView(state: GameState, content: Content): GameView {
|
||||
function actionDisabledReason(
|
||||
state: GameState,
|
||||
content: GameContent,
|
||||
actionId: string,
|
||||
): string | null {
|
||||
if (!canUnlockAction(state, content, actionId)) return 'Locked';
|
||||
if (!canAffordAction(state, content, actionId)) return 'Not enough resources';
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatResourceList(
|
||||
items: { resourceId: string; amount: number }[],
|
||||
content: GameContent,
|
||||
): string {
|
||||
return items
|
||||
.map((i) => `${i.amount} ${content.resourcesById[i.resourceId]?.name ?? i.resourceId}`)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
export function toView(state: GameState, content: GameContent): GameView {
|
||||
const resources: ResourceView[] = content.resources.map((resource) => ({
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
@@ -34,6 +84,34 @@ export function toView(state: GameState, content: Content): GameView {
|
||||
const queuedActionIds = [...state.actionQueue];
|
||||
const queuedActionNames = queuedActionIds.map((id) => content.actionsById[id]?.name ?? id);
|
||||
|
||||
const actions: ActionView[] = content.actions.map((a) => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
available: isActionAvailable(state, content, a.id),
|
||||
disabledReason: actionDisabledReason(state, content, a.id),
|
||||
storyHint: a.storyHint,
|
||||
storyTooltip: a.storyTooltip,
|
||||
costsSummary: a.costs.length ? formatResourceList(a.costs, content) : null,
|
||||
yieldsSummary: formatResourceList(a.yields, content),
|
||||
}));
|
||||
|
||||
const node = getCurrentNode(state, content);
|
||||
const availableChoices = getAvailableChoices(state, content);
|
||||
const allChoices = node?.choices ?? [];
|
||||
|
||||
const story: StoryView = {
|
||||
currentProse: node?.prose ?? null,
|
||||
choices: allChoices.map((choice) => {
|
||||
const available = availableChoices.some((c) => c.id === choice.id);
|
||||
return {
|
||||
id: choice.id,
|
||||
label: choice.label,
|
||||
disabled: !available,
|
||||
disabledReason: available ? null : 'Requirements not met',
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
resources,
|
||||
activeActionId: state.activeActionId,
|
||||
@@ -41,6 +119,8 @@ export function toView(state: GameState, content: Content): GameView {
|
||||
actionProgress,
|
||||
queuedActionIds,
|
||||
queuedActionNames,
|
||||
actions,
|
||||
story,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user