using System; using System.Collections.Generic; namespace SLSUtilities.General { /// /// 可复现的种子随机数管理器。 /// 从一个主种子 (master seed) 派生出各子系统独立的 , /// 确保同一种子下所有随机结果完全可复现,且各子系统之间互不干扰。 /// /// 核心概念: /// /// /// Channel /// /// 通过字符串 domain 派生的固定 RNG,适合生命周期与 Randomizer 相同的子系统。 /// 同一 domain 始终返回同一个 实例。 /// /// /// /// Fork /// /// 通过任意 key 派生的一次性 RNG,每次调用都返回新实例。 /// 适合与特定数据绑定(坐标、ID 等),同一 key 始终产生相同序列。 /// /// /// /// Next /// /// 自增计数器派生的 RNG,每次调用都返回不同的实例。 /// 适合无特定绑定数据的通用场景。 /// /// /// /// /// 使用示例: /// /// /// // ═══════════════════════════════════════════════════════════ /// // 1. 创建 Randomizer(通常在一局 Run 开始时) /// // ═══════════════════════════════════════════════════════════ /// // /// // 传入种子字符串,为空则自动生成 /// var randomizer = new Randomizer("my-seed-123"); /// // randomizer.Seed → "my-seed-123" /// /// // ═══════════════════════════════════════════════════════════ /// // 2. Channel — 固定子系统 RNG(地图生成、战利品表等) /// // ═══════════════════════════════════════════════════════════ /// // /// // 首次调用创建,后续调用返回同一实例 /// Random mapRng = randomizer.Channel("Map"); /// Random lootRng = randomizer.Channel("Loot"); /// // randomizer.Channel("Map") == mapRng → true(同一引用) /// /// // ═══════════════════════════════════════════════════════════ /// // 3. Fork — 按 key 派生一次性 RNG(节点坐标、房间 ID 等) /// // ═══════════════════════════════════════════════════════════ /// // /// // 机械台/商店:同一坐标 → 同一随机序列 /// Random nodeRng = randomizer.Fork(nodePosition.x, nodePosition.y); /// mechanicalTable.Setup(nodeRng); /// /// // 也可以用字符串 key /// Random bossRng = randomizer.Fork("BossRoom", floorIndex); /// /// // ═══════════════════════════════════════════════════════════ /// // 4. Next — 自增通用 RNG(无特定绑定的临时随机) /// // ═══════════════════════════════════════════════════════════ /// // /// // 每次调用返回不同实例,但同一 seed 下第 N 次调用结果一致 /// Random rng1 = randomizer.Next(); // 第 1 次 /// Random rng2 = randomizer.Next(); // 第 2 次,与 rng1 不同 /// /// // ═══════════════════════════════════════════════════════════ /// // 5. 实际游戏场景举例 /// // ═══════════════════════════════════════════════════════════ /// // /// // Roguelite Run 开始: /// // var runRng = new Randomizer(MainGameManager.Seed); /// // RunMapData map = MapGenerator.Generate(config, runRng.Channel("Map")); /// // /// // 进入机械台节点 (3, 5): /// // mechanicalTable.Setup(runRng.Fork(3, 5)); /// // → 稀有度 Roll + 装备 Roll 都由该 Fork 驱动 /// // /// // 进入商店节点 (2, 4): /// // logisticsCenter.Setup(runRng.Fork(2, 4)); /// // → 商品列表 + 价格 Roll 都由该 Fork 驱动 /// // /// // 战斗中敌人掉落: /// // Random dropRng = runRng.Channel("EnemyDrop"); /// // → 所有敌人掉落共享一个 Channel,保持掉落顺序一致 /// // /// // 临时需要一个 RNG(不关心绑定): /// // Random tempRng = runRng.Next(); /// /// public sealed class Randomizer { /// 本实例使用的种子字符串。 public string Seed { get; } private readonly int _masterSeed; private int _counter; private readonly Dictionary _channels = new Dictionary(); /// /// 使用指定种子创建。传入 null 或空字符串则自动生成 8 位随机种子。 /// public Randomizer(string seed = null) { if (string.IsNullOrEmpty(seed)) { seed = Guid.NewGuid().ToString("N")[..8]; } Seed = seed; _masterSeed = seed.GetHashCode(); } // ─────────────────── Channel ─────────────────── /// /// 获取或创建指定 domain 的固定 Channel RNG。 /// 同一 domain 始终返回同一个 实例。 /// /// 子系统标识,如 "Map"、"Loot"、"EnemyDrop"。 public Random Channel(string domain) { if (_channels.TryGetValue(domain, out Random existing)) { return existing; } Random rng = new Random(HashCode.Combine(_masterSeed, domain)); _channels[domain] = rng; return rng; } // ─────────────────── Fork ─────────────────── /// /// 通过任意数量的 key 派生一次性 RNG。 /// 同一组 key 始终产生相同的随机序列。 /// /// 用于派生种子的 key(坐标、ID、字符串等)。 public Random Fork(params object[] keys) { int derived = _masterSeed; foreach (object key in keys) { derived = HashCode.Combine(derived, key); } return new Random(derived); } // ─────────────────── Next ─────────────────── /// /// 创建一个自增计数器派生的通用 RNG。 /// 每次调用返回不同实例,适用于无特定绑定的通用场景。 /// public Random Next() { _counter++; return new Random(HashCode.Combine(_masterSeed, _counter)); } } }