refactor: update generator
This commit is contained in:
parent
1d749f59a6
commit
c11bceeb44
|
|
@ -1,16 +1,11 @@
|
|||
import { Mulberry32RNG, type RNG } from '@/utils/rng';
|
||||
import encounterDesertCsvAccessor, { type EncounterDesert } from '../data/encounterDesert.csv';
|
||||
import { ReadonlyRNG } from '@/utils/rng';
|
||||
import { MapNodeType, MapLayerType } from './types';
|
||||
import type { MapLayer, MapNode, PointCrawlMap, MapGenerationConfig } from './types';
|
||||
import {EncounterData} from "@/samples/slay-the-spire-like/system/types";
|
||||
|
||||
const encounterDesertCsv = encounterDesertCsvAccessor();
|
||||
|
||||
/** Pre-indexed encounters by type */
|
||||
const encountersByType = buildEncounterIndex();
|
||||
|
||||
function buildEncounterIndex(): Map<string, EncounterDesert[]> {
|
||||
const index = new Map<string, EncounterDesert[]>();
|
||||
for (const encounter of encounterDesertCsv) {
|
||||
function buildEncounterIndex(src: Iterable<EncounterData>): Map<string, EncounterData[]> {
|
||||
const index = new Map<string, EncounterData[]>();
|
||||
for (const encounter of src) {
|
||||
const type = encounter.type;
|
||||
if (!index.has(type)) {
|
||||
index.set(type, []);
|
||||
|
|
@ -63,7 +58,7 @@ const LAYER_STRUCTURE: Array<{ layerType: MapLayerType | 'start' | 'end'; count:
|
|||
* Fisher-Yates shuffle algorithm for unbiased random permutation.
|
||||
* Mutates the array in place and returns it.
|
||||
*/
|
||||
function fisherYatesShuffle<T>(array: T[], rng: RNG): T[] {
|
||||
function fisherYatesShuffle<T>(array: T[], rng: ReadonlyRNG): T[] {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = rng.nextInt(i + 1);
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
|
|
@ -75,11 +70,7 @@ function fisherYatesShuffle<T>(array: T[], rng: RNG): T[] {
|
|||
* Picks a random encounter for the given node type.
|
||||
* Returns undefined if no matching encounter exists.
|
||||
*/
|
||||
function pickEncounterForNode(type: MapNodeType, rng: RNG): EncounterDesert | undefined {
|
||||
const encounterType = NODE_TYPE_TO_ENCOUNTER[type];
|
||||
if (!encounterType) return undefined;
|
||||
|
||||
const pool = encountersByType.get(encounterType);
|
||||
function pickEncounterForNode(pool: EncounterData[] | undefined, rng: ReadonlyRNG): EncounterData | undefined {
|
||||
if (!pool || pool.length === 0) return undefined;
|
||||
|
||||
return pool[rng.nextInt(pool.length)];
|
||||
|
|
@ -98,12 +89,9 @@ const TOTAL_LAYERS = 10;
|
|||
* - Each settlement layer has at least 1 of each: camp, shop, curio
|
||||
* - Wild nodes connect to 1 wild node or 2 settlement nodes
|
||||
*
|
||||
* @param seed Random seed for reproducibility
|
||||
*/
|
||||
export function generatePointCrawlMap(seed?: number): PointCrawlMap {
|
||||
const rng = new Mulberry32RNG(seed ?? Date.now());
|
||||
const actualSeed = rng.getSeed();
|
||||
|
||||
export function generatePointCrawlMap(rng: ReadonlyRNG, src: Iterable<EncounterData>): PointCrawlMap {
|
||||
const encounters = buildEncounterIndex(src);
|
||||
const layers: MapLayer[] = [];
|
||||
const nodes = new Map<string, MapNode>();
|
||||
|
||||
|
|
@ -136,13 +124,13 @@ export function generatePointCrawlMap(seed?: number): PointCrawlMap {
|
|||
for (let j = 0; j < structure.count; j++) {
|
||||
const id = `node-${i}-${j}`;
|
||||
const type = resolveNodeType(structure.layerType, j, structure.count, rng, wildPairTypes.get(i), j, settlementTypes, j);
|
||||
const encounter = pickEncounterForNode(type, rng);
|
||||
const encounter = pickEncounterForNode(encounters.get(NODE_TYPE_TO_ENCOUNTER[type]!), rng);
|
||||
const node: MapNode = {
|
||||
id,
|
||||
layerIndex: i,
|
||||
type,
|
||||
childIds: [],
|
||||
...(encounter ? { encounter: { name: encounter.name, description: encounter.description } } : {}),
|
||||
encounter
|
||||
};
|
||||
nodes.set(id, node);
|
||||
nodeIds.push(id);
|
||||
|
|
@ -171,7 +159,7 @@ export function generatePointCrawlMap(seed?: number): PointCrawlMap {
|
|||
}
|
||||
}
|
||||
|
||||
return { layers, nodes, seed: actualSeed, parentIndex };
|
||||
return { layers, nodes, parentIndex };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -181,7 +169,7 @@ function resolveNodeType(
|
|||
layerType: MapLayerType | 'start' | 'end',
|
||||
_nodeIndex: number,
|
||||
_layerCount: number,
|
||||
rng: RNG,
|
||||
rng: ReadonlyRNG,
|
||||
preGeneratedTypes?: MapNodeType[],
|
||||
nodeIndex?: number,
|
||||
settlementTypes?: MapNodeType[],
|
||||
|
|
@ -213,7 +201,7 @@ function resolveNodeType(
|
|||
* Picks a random type for a wild node based on configured weights.
|
||||
* Default: minion: 50%, elite: 25%, event: 25%
|
||||
*/
|
||||
function pickWildNodeType(rng: RNG): MapNodeType {
|
||||
function pickWildNodeType(rng: ReadonlyRNG): MapNodeType {
|
||||
const weights = DEFAULT_CONFIG.wildNodeTypeWeights;
|
||||
const roll = rng.nextInt(100);
|
||||
|
||||
|
|
@ -226,7 +214,7 @@ function pickWildNodeType(rng: RNG): MapNodeType {
|
|||
* Generates random types for a pair of wild layers (3 nodes each).
|
||||
* Returns two arrays of 3 node types each.
|
||||
*/
|
||||
function generateWildPair(rng: RNG): [MapNodeType[], MapNodeType[]] {
|
||||
function generateWildPair(rng: ReadonlyRNG): [MapNodeType[], MapNodeType[]] {
|
||||
const layer1Types: MapNodeType[] = [];
|
||||
const layer2Types: MapNodeType[] = [];
|
||||
|
||||
|
|
@ -284,7 +272,7 @@ function countRepetitions(
|
|||
* Generates optimal wild layer pair by trying multiple attempts and selecting the one with fewest repetitions.
|
||||
*/
|
||||
function generateOptimalWildPair(
|
||||
rng: RNG,
|
||||
rng: ReadonlyRNG,
|
||||
attempts = 3
|
||||
): [MapNodeType[], MapNodeType[]] {
|
||||
let bestLayer1: MapNodeType[] = [];
|
||||
|
|
@ -313,7 +301,7 @@ function generateOptimalWildPair(
|
|||
* The 4th node is randomly chosen from the three.
|
||||
* Returns shuffled array of 4 node types.
|
||||
*/
|
||||
function generateSettlementTypes(rng: RNG): MapNodeType[] {
|
||||
function generateSettlementTypes(rng: ReadonlyRNG): MapNodeType[] {
|
||||
const requiredTypes = [MapNodeType.Camp, MapNodeType.Shop, MapNodeType.Curio];
|
||||
const randomType = requiredTypes[rng.nextInt(3)];
|
||||
const types = [...requiredTypes, randomType];
|
||||
|
|
@ -325,7 +313,7 @@ function generateSettlementTypes(rng: RNG): MapNodeType[] {
|
|||
* The 4th node is randomly chosen from the three.
|
||||
* @deprecated Use generateSettlementTypes() during node creation instead.
|
||||
*/
|
||||
function assignSettlementTypes(nodeIds: string[], nodes: MapNode[], rng: RNG): void {
|
||||
function assignSettlementTypes(nodeIds: string[], nodes: MapNode[], rng: ReadonlyRNG): void {
|
||||
// Shuffle node order to randomize which position gets which type
|
||||
const shuffledIndices = fisherYatesShuffle([0, 1, 2, 3], rng);
|
||||
|
||||
|
|
@ -347,7 +335,7 @@ function generateLayerEdges(
|
|||
sourceLayer: MapLayer,
|
||||
targetLayer: MapLayer,
|
||||
nodes: Map<string, MapNode>,
|
||||
rng: RNG
|
||||
rng: ReadonlyRNG
|
||||
): void {
|
||||
// Settlement types are now pre-generated during node creation
|
||||
// No need to assign them here anymore
|
||||
|
|
@ -402,7 +390,7 @@ function connectWildToWild(
|
|||
function connectWildToSettlement(
|
||||
wildLayer: MapLayer,
|
||||
settlementLayer: MapLayer,
|
||||
_rng: RNG
|
||||
_rng: ReadonlyRNG
|
||||
): void {
|
||||
// Non-crossing connection pattern: each wild connects to 2 adjacent settlements.
|
||||
// Pattern: w[0]→{s[0],s[1]}, w[1]→{s[1],s[2]}, w[2]→{s[2],s[3]}
|
||||
|
|
@ -427,7 +415,7 @@ function connectWildToSettlement(
|
|||
function connectSettlementToWild(
|
||||
settlementLayer: MapLayer,
|
||||
wildLayer: MapLayer,
|
||||
_rng: RNG
|
||||
_rng: ReadonlyRNG
|
||||
): void {
|
||||
// Non-crossing pattern: s0→w0, s1→w0,w1, s2→w1,w2, s3→w2
|
||||
// This pattern guarantees no crossings because when edges are sorted by
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import {EncounterData} from "@/samples/slay-the-spire-like/system/types";
|
||||
|
||||
/**
|
||||
* Types of nodes that can appear on the point crawl map.
|
||||
*/
|
||||
|
|
@ -33,10 +35,7 @@ export interface MapNode {
|
|||
/** IDs of nodes in the next layer this node connects to */
|
||||
childIds: string[];
|
||||
/** Encounter data assigned to this node (from encounter CSV) */
|
||||
encounter?: {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
encounter?: EncounterData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,8 +60,6 @@ export interface PointCrawlMap {
|
|||
layers: MapLayer[];
|
||||
/** All nodes keyed by ID */
|
||||
nodes: Map<string, MapNode>;
|
||||
/** RNG seed used for generation (for reproducibility) */
|
||||
seed: number;
|
||||
/** Reverse index: nodeId → parent node IDs (for fast getParent lookup) */
|
||||
parentIndex?: Map<string, string[]>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue