feat(slay-the-spire-like): add map navigation logic

Implement `canMoveTo` and `moveToNode` for navigating the point
crawl map, and add `PointCrawlMapNavigator` type. Also reformat
map module files to use double quotes.
This commit is contained in:
hypercross 2026-04-20 11:59:52 +08:00
parent 88d31430a6
commit 423cc7c841
3 changed files with 102 additions and 51 deletions

View File

@ -1,5 +1,18 @@
export { MapNodeType, MapLayerType } from './types'; export { MapNodeType, MapLayerType } from "./types";
export type { MapNode, MapLayer, PointCrawlMap, MapGenerationConfig } from './types'; export type {
MapNode,
MapLayer,
PointCrawlMap,
MapGenerationConfig,
} from "./types";
export { generatePointCrawlMap } from './generator'; export { generatePointCrawlMap } from "./generator";
export { getNode, getChildren, getParents, hasPath, findAllPaths } from './generator'; export {
getNode,
getChildren,
getParents,
hasPath,
findAllPaths,
} from "./generator";
export { canMoveTo, moveToNode } from "./navigation";

View File

@ -0,0 +1,33 @@
import { getNode } from "./generator";
import { PointCrawlMap, PointCrawlMapNavigator } from "./types";
export function canMoveTo(
navigator: PointCrawlMapNavigator,
map: PointCrawlMap,
targetNodeId: string,
): boolean {
const currentNode = getNode(map, navigator.currentNodeId);
if (!currentNode) return false;
return currentNode.childIds.includes(targetNodeId);
}
export function moveToNode(
navigator: PointCrawlMapNavigator,
map: PointCrawlMap,
targetNodeId: string,
): boolean {
if (!canMoveTo(navigator, map, targetNodeId)) {
return false;
}
const targetNode = getNode(map, targetNodeId);
if (!targetNode) {
return false;
}
// Update current position
navigator.currentNodeId = targetNodeId;
navigator.visitedNodes.add(targetNodeId);
return true;
}

View File

@ -1,83 +1,88 @@
import {EncounterData} from "@/samples/slay-the-spire-like/system/types"; import { EncounterData } from "@/samples/slay-the-spire-like/system/types";
/** /**
* Types of nodes that can appear on the point crawl map. * Types of nodes that can appear on the point crawl map.
*/ */
export enum MapNodeType { export enum MapNodeType {
Start = 'start', Start = "start",
End = 'end', End = "end",
Minion = 'minion', Minion = "minion",
Elite = 'elite', Elite = "elite",
Event = 'event', Event = "event",
Camp = 'camp', Camp = "camp",
Shop = 'shop', Shop = "shop",
Curio = 'curio', Curio = "curio",
} }
/** /**
* Semantic type of a layer. * Semantic type of a layer.
*/ */
export enum MapLayerType { export enum MapLayerType {
Wild = 'wild', Wild = "wild",
Settlement = 'settlement', Settlement = "settlement",
} }
/** /**
* A single node on the map. * A single node on the map.
*/ */
export interface MapNode { export interface MapNode {
/** Unique identifier */ /** Unique identifier */
id: string; id: string;
/** Which layer this node belongs to */ /** Which layer this node belongs to */
layerIndex: number; layerIndex: number;
/** Semantic type of the node */ /** Semantic type of the node */
type: MapNodeType; type: MapNodeType;
/** IDs of nodes in the next layer this node connects to */ /** IDs of nodes in the next layer this node connects to */
childIds: string[]; childIds: string[];
/** Encounter data assigned to this node (from encounter CSV) */ /** Encounter data assigned to this node (from encounter CSV) */
encounter?: EncounterData; encounter?: EncounterData;
} }
/** /**
* A horizontal layer of nodes at the same progression stage. * A horizontal layer of nodes at the same progression stage.
*/ */
export interface MapLayer { export interface MapLayer {
/** Layer index (0 = start, last = end) */ /** Layer index (0 = start, last = end) */
index: number; index: number;
/** Ordered IDs of nodes in this layer */ /** Ordered IDs of nodes in this layer */
nodeIds: string[]; nodeIds: string[];
/** Semantic type of the layer */ /** Semantic type of the layer */
layerType: MapLayerType | 'start' | 'end'; layerType: MapLayerType | "start" | "end";
/** Direct references to nodes in this layer (for performance) */ /** Direct references to nodes in this layer (for performance) */
nodes: MapNode[]; nodes: MapNode[];
} }
/** /**
* A fully generated point crawl map. * A fully generated point crawl map.
*/ */
export interface PointCrawlMap { export interface PointCrawlMap {
/** Layers from start to end */ /** Layers from start to end */
layers: MapLayer[]; layers: MapLayer[];
/** All nodes keyed by ID */ /** All nodes keyed by ID */
nodes: Map<string, MapNode>; nodes: Map<string, MapNode>;
/** Reverse index: nodeId → parent node IDs (for fast getParent lookup) */ /** Reverse index: nodeId → parent node IDs (for fast getParent lookup) */
parentIndex?: Map<string, string[]>; parentIndex?: Map<string, string[]>;
}
export interface PointCrawlMapNavigator {
currentNodeId: string;
visitedNodes: Set<string>;
} }
/** /**
* Configuration for map generation. * Configuration for map generation.
*/ */
export interface MapGenerationConfig { export interface MapGenerationConfig {
/** Total number of layers (including start and end) */ /** Total number of layers (including start and end) */
totalLayers: number; totalLayers: number;
/** Number of nodes in each wild layer */ /** Number of nodes in each wild layer */
wildLayerNodeCount: number; wildLayerNodeCount: number;
/** Number of nodes in each settlement layer */ /** Number of nodes in each settlement layer */
settlementLayerNodeCount: number; settlementLayerNodeCount: number;
/** Probability weights for wild node types (should sum to 100) */ /** Probability weights for wild node types (should sum to 100) */
wildNodeTypeWeights: { wildNodeTypeWeights: {
[MapNodeType.Minion]: number; [MapNodeType.Minion]: number;
[MapNodeType.Elite]: number; [MapNodeType.Elite]: number;
[MapNodeType.Event]: number; [MapNodeType.Event]: number;
}; };
} }