refactor: minimize repetitions

This commit is contained in:
hyper
2026-04-13 21:18:06 +08:00
parent 1e5e4e9f7e
commit ef9557cba7
2 changed files with 171 additions and 2 deletions
@@ -105,6 +105,20 @@ export function generatePointCrawlMap(seed?: number): PointCrawlMap {
const layers: MapLayer[] = [];
const nodes = new Map<string, MapNode>();
// Pre-generate optimal types for wild layer pairs (layers 1-2, 4-5, 7-8)
const wildPairTypes = new Map<number, MapNodeType[]>();
const wildPairs = [
{ layer1: 1, layer2: 2 },
{ layer1: 4, layer2: 5 },
{ layer1: 7, layer2: 8 },
];
for (const pair of wildPairs) {
const [types1, types2] = generateOptimalWildPair(rng);
wildPairTypes.set(pair.layer1, types1);
wildPairTypes.set(pair.layer2, types2);
}
// Step 1: create layers and nodes
for (let i = 0; i < TOTAL_LAYERS; i++) {
const structure = LAYER_STRUCTURE[i];
@@ -113,7 +127,7 @@ 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);
const type = resolveNodeType(structure.layerType, j, structure.count, rng, wildPairTypes.get(i), j);
const encounter = pickEncounterForNode(type, rng);
const node: MapNode = {
id,
@@ -159,7 +173,9 @@ function resolveNodeType(
layerType: MapLayerType | 'start' | 'end',
_nodeIndex: number,
_layerCount: number,
rng: RNG
rng: RNG,
preGeneratedTypes?: MapNodeType[],
nodeIndex?: number
): MapNodeType {
switch (layerType) {
case 'start':
@@ -167,6 +183,10 @@ function resolveNodeType(
case 'end':
return MapNodeType.End;
case MapLayerType.Wild:
// Use pre-generated types if available (from optimal pair generation)
if (preGeneratedTypes && nodeIndex !== undefined) {
return preGeneratedTypes[nodeIndex];
}
return pickWildNodeType(rng);
case MapLayerType.Settlement:
// This will be overridden by assignSettlementTypes
@@ -189,6 +209,92 @@ function pickWildNodeType(rng: RNG): MapNodeType {
return MapNodeType.Event;
}
/**
* 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[]] {
const layer1Types: MapNodeType[] = [];
const layer2Types: MapNodeType[] = [];
for (let i = 0; i < 3; i++) {
layer1Types.push(pickWildNodeType(rng));
layer2Types.push(pickWildNodeType(rng));
}
return [layer1Types, layer2Types];
}
/**
* Counts repetitions in a wild layer pair.
* - sameLayer: number of duplicate types within each layer
* - adjacent: number of positions where layer1[i] === layer2[i]
* - total: sum of both
*/
function countRepetitions(
layer1Types: MapNodeType[],
layer2Types: MapNodeType[]
): { sameLayer: number; adjacent: number; total: number } {
// Count same-layer repetitions
let sameLayer = 0;
// For layer 1: count duplicates
const layer1Count = new Map<MapNodeType, number>();
for (const type of layer1Types) {
layer1Count.set(type, (layer1Count.get(type) || 0) + 1);
}
for (const count of layer1Count.values()) {
if (count > 1) sameLayer += count - 1;
}
// For layer 2: count duplicates
const layer2Count = new Map<MapNodeType, number>();
for (const type of layer2Types) {
layer2Count.set(type, (layer2Count.get(type) || 0) + 1);
}
for (const count of layer2Count.values()) {
if (count > 1) sameLayer += count - 1;
}
// Count adjacent repetitions (wild[i] → wild[i] connection)
let adjacent = 0;
for (let i = 0; i < 3; i++) {
if (layer1Types[i] === layer2Types[i]) {
adjacent++;
}
}
return { sameLayer, adjacent, total: sameLayer + adjacent };
}
/**
* Generates optimal wild layer pair by trying multiple attempts and selecting the one with fewest repetitions.
*/
function generateOptimalWildPair(
rng: RNG,
attempts = 3
): [MapNodeType[], MapNodeType[]] {
let bestLayer1: MapNodeType[] = [];
let bestLayer2: MapNodeType[] = [];
let bestScore = Infinity;
for (let i = 0; i < attempts; i++) {
const [layer1, layer2] = generateWildPair(rng);
const score = countRepetitions(layer1, layer2);
if (score.total < bestScore) {
bestScore = score.total;
bestLayer1 = layer1;
bestLayer2 = layer2;
}
// Perfect score is 0, no need to continue
if (bestScore === 0) break;
}
return [bestLayer1, bestLayer2];
}
/**
* Assigns settlement node types ensuring at least 1 of each: camp, shop, curio.
* The 4th node is randomly chosen from the three.