This commit is contained in:
2026-08-18 02:03:34 +08:00
parent d2cd3b9fa8
commit 517c4cf46e
157 changed files with 16536 additions and 37 deletions
@@ -0,0 +1,13 @@
{
"name": "Demo2.Domain",
"rootNamespace": "Demo2.Domain",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 60f0384ea03a1314da9f3fc30edf2bc5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+150
View File
@@ -0,0 +1,150 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Demo2.Domain
{
public interface IDemo2Mod
{
string Id { get; }
string Version { get; }
void Register(Demo2ContentRegistry registry);
}
public sealed class Demo2ModManifest
{
public string Id = string.Empty;
public string Version = string.Empty;
public string ContentSha256 = string.Empty;
}
public sealed class Demo2ContentRegistry
{
private readonly Dictionary<string, Demo2RuleDefinition> _rules = new(StringComparer.Ordinal);
private readonly Dictionary<int, Demo2RecipeDefinition> _recipes = new();
private readonly Dictionary<Demo2MachineKind, Demo2MachineDefinition> _machines = new();
private readonly List<Demo2WaveDefinition> _waves = new();
public IReadOnlyDictionary<string, Demo2RuleDefinition> Rules => _rules;
public IReadOnlyDictionary<int, Demo2RecipeDefinition> Recipes => _recipes;
public IReadOnlyDictionary<Demo2MachineKind, Demo2MachineDefinition> Machines => _machines;
public IReadOnlyList<Demo2WaveDefinition> Waves => _waves;
public void AddRule(Demo2RuleDefinition definition) => _rules.Add(definition.Id, definition);
public void AddRecipe(Demo2RecipeDefinition definition) => _recipes.Add(definition.Id, definition);
public void AddMachine(Demo2MachineDefinition definition) => _machines.Add(definition.Kind, definition);
public void AddWave(Demo2WaveDefinition definition) => _waves.Add(definition);
public string ComputeContentHash()
{
var text = new StringBuilder();
foreach (var rule in _rules.Values.OrderBy(value => value.Id, StringComparer.Ordinal))
{
text.Append(rule.Id).Append('|').Append(rule.Name).Append('|').Append((int)rule.Category);
foreach (var instruction in rule.Instructions)
text.Append(':').Append((int)instruction.Opcode).Append(',').Append((int)instruction.Condition).Append(',').Append(instruction.Operand);
text.AppendLine();
}
foreach (var recipe in _recipes.Values.OrderBy(value => value.Id))
text.Append(recipe.Id).Append('|').Append(recipe.Name).Append('|')
.Append((int)recipe.InputA).Append(',').Append(recipe.InputCountA).Append('|')
.Append((int)recipe.InputB).Append(',').Append(recipe.InputCountB).Append('|')
.Append((int)recipe.Output).Append(',').Append(recipe.OutputCount).Append('|').Append(recipe.ProcessTicks).AppendLine();
foreach (var machine in _machines.Values.OrderBy(value => (int)value.Kind))
text.Append("machine|").Append((int)machine.Kind).Append('|').Append(machine.Name).Append('|')
.Append(machine.Cost).Append('|').Append(machine.RecipeId).Append('|').Append(machine.Capacity).AppendLine();
foreach (var wave in _waves.OrderBy(value => value.Cycle))
text.Append("wave|").Append(wave.Cycle).Append('|').Append(wave.TotalHealth).Append('|').Append(wave.Trait).AppendLine();
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString()));
var hex = new StringBuilder(bytes.Length * 2);
for (var i = 0; i < bytes.Length; i++) hex.Append(bytes[i].ToString("x2"));
return hex.ToString();
}
}
public static class Demo2BuiltInContent
{
public static Demo2ContentRegistry Create()
{
var registry = new Demo2ContentRegistry();
AddMachinesAndRecipes(registry);
AddWaves(registry);
foreach (var rule in BuildRules()) registry.AddRule(rule);
return registry;
}
public static IReadOnlyList<Demo2RuleDefinition> BuildRules() => new[]
{
Rule("production.triple-echo", "三连复写", "连续完成三次相同配方后复制下一件产物。", Demo2RuleCategory.Production, Demo2RuleOpcode.DuplicateNextOutput, 1, Demo2RuleCondition.ThreeSameRecipes),
Rule("production.hot-ingot", "灼热锭", "无故障时,每周期增加 120 载荷。", Demo2RuleCategory.Production, Demo2RuleOpcode.AddPayload, 120, Demo2RuleCondition.NoFaults),
Rule("production.precision-gears", "精密齿轮", "首批齿轮使载荷提高 25%。", Demo2RuleCategory.Production, Demo2RuleOpcode.MultiplyPayload, 1250, Demo2RuleCondition.FirstBatch),
Rule("production.dense-ammo", "高密弹药", "结算载荷固定增加 180。", Demo2RuleCategory.Production, Demo2RuleOpcode.AddPayload, 180),
Rule("logistics.twin-stream", "双流共振", "铁矿与燃料相邻流动时,倍率增加 0.35。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddMultiplier, 350, Demo2RuleCondition.IronFuelAdjacent),
Rule("logistics.clear-belt", "净空运输", "传送带占用低于 80% 时,倍率提高 20%。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.MultiplyMultiplier, 1200, Demo2RuleCondition.BeltBelowEightyPercent),
Rule("logistics.split-balance", "均衡分流", "稳定分流为载荷增加 90。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddPayload, 90),
Rule("logistics.long-haul", "长距惯性", "低占用且无故障时,倍率增加 0.2。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddMultiplier, 200, Demo2RuleCondition.BeltBelowEightyPercent | Demo2RuleCondition.NoFaults),
Rule("machine.overclock", "红区超频", "载荷提高 50%,并增加 1 次故障。", Demo2RuleCategory.MachineState, new[]
{
Instruction(Demo2RuleOpcode.MultiplyPayload, 1500), Instruction(Demo2RuleOpcode.AddFaults, 1)
}),
Rule("machine.preventive-maintenance", "预防维护", "存在故障时修复 1 次并增加 60 载荷。", Demo2RuleCategory.MachineState, new[]
{
Instruction(Demo2RuleOpcode.RepairFaults, 1, Demo2RuleCondition.HasFaults), Instruction(Demo2RuleOpcode.AddPayload, 60, Demo2RuleCondition.HasFaults)
}),
Rule("machine.night-shift", "夜班协议", "无故障时倍率增加 0.25。", Demo2RuleCategory.MachineState, Demo2RuleOpcode.AddMultiplier, 250, Demo2RuleCondition.NoFaults),
Rule("machine.thermal-bank", "余热银行", "剩余能量每点转化 3 载荷。", Demo2RuleCategory.MachineState, Demo2RuleOpcode.ConvertEnergyToPayload, 3, Demo2RuleCondition.HasExcessEnergy),
Rule("cycle.energy-settlement", "能量清算", "结算时剩余能量每点转化 5 载荷。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.ConvertEnergyToPayload, 5, Demo2RuleCondition.HasExcessEnergy),
Rule("cycle.excess-credit", "过量授信", "每 20 点超额火力兑换 1 资金。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.AddMoneyFromExcess, 20),
Rule("cycle.first-batch", "首批红利", "首批完成时倍率增加 0.3。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.AddMultiplier, 300, Demo2RuleCondition.FirstBatch),
Rule("cycle.reserve-capacitor", "储备电容", "剩余能量使倍率提高 15%。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.MultiplyMultiplier, 1150, Demo2RuleCondition.HasExcessEnergy),
Rule("risk.glass-core", "玻璃核心", "核心仅剩 1 点耐久时,倍率提高 65%。", Demo2RuleCategory.RiskExchange, Demo2RuleOpcode.AddMultiplier, 650, Demo2RuleCondition.CoreAtRisk),
Rule("risk.debt-leverage", "负债杠杆", "立即增加 260 载荷,但故障计数增加 2。", Demo2RuleCategory.RiskExchange, new[]
{
Instruction(Demo2RuleOpcode.AddPayload, 260), Instruction(Demo2RuleOpcode.AddFaults, 2)
})
};
private static void AddMachinesAndRecipes(Demo2ContentRegistry registry)
{
registry.AddRecipe(new Demo2RecipeDefinition { Id = 1, Name = "采集铁矿", Output = Demo2MaterialKind.IronOre, OutputCount = 1, ProcessTicks = 20 });
registry.AddRecipe(new Demo2RecipeDefinition { Id = 2, Name = "燃料电解", Output = Demo2MaterialKind.Fuel, OutputCount = 1, ProcessTicks = 30 });
registry.AddRecipe(new Demo2RecipeDefinition { Id = 3, Name = "铁锭熔炼", InputA = Demo2MaterialKind.IronOre, InputB = Demo2MaterialKind.Fuel, Output = Demo2MaterialKind.Ingot, InputCountA = 2, InputCountB = 1, OutputCount = 1, ProcessTicks = 40 });
registry.AddRecipe(new Demo2RecipeDefinition { Id = 4, Name = "齿轮装配", InputA = Demo2MaterialKind.Ingot, Output = Demo2MaterialKind.Gear, InputCountA = 2, OutputCount = 1, ProcessTicks = 50 });
registry.AddRecipe(new Demo2RecipeDefinition { Id = 5, Name = "弹药压制", InputA = Demo2MaterialKind.Gear, InputB = Demo2MaterialKind.Energy, Output = Demo2MaterialKind.Ammo, InputCountA = 1, InputCountB = 1, OutputCount = 4, ProcessTicks = 35 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Miner, Name = "采集器", Cost = 40, RecipeId = 1, Capacity = 8 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Conveyor, Name = "传送带", Cost = 5, Capacity = 4 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Splitter, Name = "分流器", Cost = 15, Capacity = 8 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Smelter, Name = "熔炼机", Cost = 80, RecipeId = 3, Capacity = 12 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Assembler, Name = "组装机", Cost = 110, RecipeId = 4, Capacity = 12 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Loader, Name = "弹药装填器", Cost = 70, RecipeId = 5, Capacity = 24 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Turret, Name = "自动炮塔", Cost = 150, Capacity = 200 });
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Generator, Name = "能量站", Cost = 60, RecipeId = 2, Capacity = 16 });
}
private static void AddWaves(Demo2ContentRegistry registry)
{
var health = 500L;
var traits = new[] { "标准装甲", "分散队列", "电磁护盾", "高速突击", "核心压制" };
for (var cycle = 1; cycle <= Demo2Protocol.CycleCount; cycle++)
{
registry.AddWave(new Demo2WaveDefinition { Cycle = cycle, TotalHealth = health, Trait = traits[cycle - 1] });
health = SaturatingMath.MultiplyScaled(health, 1800);
}
}
private static Demo2RuleDefinition Rule(string id, string name, string description, Demo2RuleCategory category, Demo2RuleOpcode opcode, long operand, Demo2RuleCondition condition = Demo2RuleCondition.None)
=> Rule(id, name, description, category, new[] { Instruction(opcode, operand, condition) });
private static Demo2RuleDefinition Rule(string id, string name, string description, Demo2RuleCategory category, IEnumerable<Demo2RuleInstructionData> instructions)
=> new() { Id = id, Name = name, Description = description, Category = category, Instructions = instructions.ToList() };
private static Demo2RuleInstructionData Instruction(Demo2RuleOpcode opcode, long operand, Demo2RuleCondition condition = Demo2RuleCondition.None)
=> new() { Opcode = opcode, Operand = operand, Condition = condition };
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 879b2677a87331d48a64a750e7d46537
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+188
View File
@@ -0,0 +1,188 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace Demo2.Domain
{
public enum Demo2Phase : byte { Title, Lobby, Build, Production, Defense, Reward, Victory, Defeat }
public enum Demo2TransportMode : byte { Tcp, Kcp }
public enum Demo2MachineKind : byte { Miner, Conveyor, Splitter, Smelter, Assembler, Loader, Turret, Generator, NightShiftSmelter = 100 }
public enum Demo2MaterialKind : byte { IronOre, Fuel, Ingot, Gear, Ammo, Energy }
public enum Demo2RuleCategory : byte { Production, Logistics, MachineState, CycleSettlement, RiskExchange }
public enum Demo2RuleOpcode : byte
{
AddPayload,
MultiplyPayload,
AddMultiplier,
MultiplyMultiplier,
ConvertEnergyToPayload,
AddMoneyFromExcess,
DuplicateNextOutput,
AddFaults,
RepairFaults
}
[Flags]
public enum Demo2RuleCondition : ushort
{
None = 0,
ThreeSameRecipes = 1 << 0,
IronFuelAdjacent = 1 << 1,
BeltBelowEightyPercent = 1 << 2,
NoFaults = 1 << 3,
HasExcessEnergy = 1 << 4,
FirstBatch = 1 << 5,
CoreAtRisk = 1 << 6,
HasFaults = 1 << 7
}
[Serializable]
public sealed class Demo2RuleInstructionData
{
public Demo2RuleOpcode Opcode;
public Demo2RuleCondition Condition;
public long Operand;
}
[Serializable]
public sealed class Demo2RuleDefinition
{
public string Id = string.Empty;
public string Name = string.Empty;
public string Description = string.Empty;
public Demo2RuleCategory Category;
public List<Demo2RuleInstructionData> Instructions = new();
}
[Serializable]
public sealed class Demo2RecipeDefinition
{
public int Id;
public string Name = string.Empty;
public Demo2MaterialKind InputA;
public Demo2MaterialKind InputB;
public Demo2MaterialKind Output;
public int InputCountA;
public int InputCountB;
public int OutputCount;
public int ProcessTicks;
}
[Serializable]
public sealed class Demo2MachineDefinition
{
public Demo2MachineKind Kind;
public string Name = string.Empty;
public int Cost;
public int RecipeId;
public int Capacity;
}
[Serializable]
public sealed class Demo2WaveDefinition
{
public int Cycle;
public long TotalHealth;
public string Trait = string.Empty;
}
[Serializable]
public sealed class Demo2BuildCommand
{
public long Sequence;
public string IdempotencyToken = string.Empty;
public string PlayerId = string.Empty;
public Demo2MachineKind Kind;
public int X;
public int Y;
public byte Rotation;
public int RecipeId;
public bool Remove;
}
[Serializable]
public sealed class Demo2CommandResult
{
public bool Accepted;
public bool Duplicate;
public string ErrorCode = string.Empty;
public int Refund;
}
[Serializable]
public sealed class Demo2SnapshotEntity
{
public int StableId;
public Demo2MachineKind MachineKind;
public Demo2MaterialKind MaterialKind;
public int X;
public int Y;
public byte Rotation;
public int RecipeId;
public int Progress;
public int Quantity;
public int Faults;
public int PendingOutputs;
public long TurretAmmo;
}
[Serializable]
public sealed class Demo2Snapshot
{
public int SimulationVersion = Demo2Protocol.SimulationVersion;
public long Tick;
public ulong Seed;
public Demo2Phase Phase;
public int Cycle;
public int CoreHealth;
public long Money;
public long Payload;
public long MultiplierMilli = SaturatingMath.Scale;
public long Energy;
public long ExcessFirepower;
public int RuleAppliedCycle;
public int SpeedMultiplier = 1;
public long Extracted;
public long Transported;
public long Processed;
public long AmmoProduced;
public int CompletedSameRecipeStreak;
public int LastRecipeId;
public int Faults;
public int BeltOccupancy;
public int BeltCapacity;
public byte IronFuelAdjacent;
public byte FirstBatch;
public int DuplicateNextOutputs;
public ulong StateHash;
public List<string> RuleIds = new();
public List<Demo2SnapshotEntity> Entities = new();
}
[Serializable]
public sealed class Demo2SaveData
{
public const int CurrentVersion = 1;
public int Version = CurrentVersion;
public int Cycle = 1;
public int CoreHealth = 3;
public long Money = 500;
public ulong Seed = 20260817;
public string BlueprintJson = string.Empty;
public List<string> RuleIds = new();
public List<string> UnlockIds = new();
public string SnapshotJson = string.Empty;
public string SnapshotHash = string.Empty;
}
public static class Demo2Protocol
{
public const string GameVersion = "1.0.0";
public const int SimulationVersion = 1;
public const int TickRate = 20;
public const int GridWidth = 32;
public const int GridHeight = 20;
public const int CycleCount = 5;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06add429ba83dfd4091aade08e445455
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace Demo2.Domain
{
[Serializable]
public sealed class Demo2NetworkEnvelope
{
public string Type = string.Empty;
public long Sequence;
public string PayloadJson = string.Empty;
}
[Serializable]
public sealed class Demo2Handshake
{
public string GameVersion = Demo2Protocol.GameVersion;
public int SimulationVersion = Demo2Protocol.SimulationVersion;
public string PlayerId = string.Empty;
public string ReconnectToken = string.Empty;
public List<Demo2ModManifest> Mods = new();
}
[Serializable]
public sealed class Demo2HandshakeResult
{
public bool Accepted;
public string ErrorCode = string.Empty;
public string Difference = string.Empty;
public string PlayerId = string.Empty;
public string ReconnectToken = string.Empty;
}
[Serializable]
public sealed class Demo2HashBroadcast
{
public long Tick;
public ulong StateHash;
}
[Serializable]
public sealed class Demo2LobbyPlayer
{
public string PlayerId = string.Empty;
public string DisplayName = string.Empty;
public bool Ready;
public bool Connected;
}
[Serializable]
public sealed class Demo2LobbyState
{
public bool IsHost;
public Demo2TransportMode Transport;
public List<Demo2LobbyPlayer> Players = new();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9755a060f8361054d9b41a7e9e3a70e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,94 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace Demo2.Domain
{
public struct Demo2RuleContext
{
public long Payload;
public long MultiplierMilli;
public long Energy;
public long Money;
public long ExcessFirepower;
public int Faults;
public bool ThreeSameRecipes;
public bool IronFuelAdjacent;
public bool BeltBelowEightyPercent;
public bool FirstBatch;
public bool CoreAtRisk;
public int DuplicatedOutputs;
public long FinalFirepower => SaturatingMath.MultiplyScaled(Payload, MultiplierMilli);
}
public static class Demo2RuleEngine
{
public static Demo2RuleContext Evaluate(Demo2RuleContext context, IReadOnlyList<Demo2RuleDefinition> orderedRules)
{
context.MultiplierMilli = context.MultiplierMilli <= 0 ? SaturatingMath.Scale : context.MultiplierMilli;
for (var ruleIndex = 0; ruleIndex < orderedRules.Count; ruleIndex++)
{
var instructions = orderedRules[ruleIndex].Instructions;
for (var instructionIndex = 0; instructionIndex < instructions.Count; instructionIndex++)
{
var instruction = instructions[instructionIndex];
if (!Matches(instruction.Condition, context)) continue;
Apply(ref context, instruction);
}
}
return context;
}
public static bool Matches(Demo2RuleCondition condition, in Demo2RuleContext context)
{
if ((condition & Demo2RuleCondition.ThreeSameRecipes) != 0 && !context.ThreeSameRecipes) return false;
if ((condition & Demo2RuleCondition.IronFuelAdjacent) != 0 && !context.IronFuelAdjacent) return false;
if ((condition & Demo2RuleCondition.BeltBelowEightyPercent) != 0 && !context.BeltBelowEightyPercent) return false;
if ((condition & Demo2RuleCondition.NoFaults) != 0 && context.Faults != 0) return false;
if ((condition & Demo2RuleCondition.HasExcessEnergy) != 0 && context.Energy <= 0) return false;
if ((condition & Demo2RuleCondition.FirstBatch) != 0 && !context.FirstBatch) return false;
if ((condition & Demo2RuleCondition.CoreAtRisk) != 0 && !context.CoreAtRisk) return false;
if ((condition & Demo2RuleCondition.HasFaults) != 0 && context.Faults <= 0) return false;
return true;
}
public static void Apply(ref Demo2RuleContext context, Demo2RuleInstructionData instruction)
{
switch (instruction.Opcode)
{
case Demo2RuleOpcode.AddPayload:
context.Payload = SaturatingMath.Add(context.Payload, instruction.Operand);
break;
case Demo2RuleOpcode.MultiplyPayload:
context.Payload = SaturatingMath.MultiplyScaled(context.Payload, instruction.Operand);
break;
case Demo2RuleOpcode.AddMultiplier:
context.MultiplierMilli = SaturatingMath.Add(context.MultiplierMilli, instruction.Operand);
break;
case Demo2RuleOpcode.MultiplyMultiplier:
context.MultiplierMilli = SaturatingMath.MultiplyScaled(context.MultiplierMilli, instruction.Operand);
break;
case Demo2RuleOpcode.ConvertEnergyToPayload:
context.Payload = SaturatingMath.Add(context.Payload, SaturatingMath.Multiply(context.Energy, instruction.Operand));
context.Energy = 0;
break;
case Demo2RuleOpcode.AddMoneyFromExcess:
context.Money = SaturatingMath.Add(context.Money, context.ExcessFirepower / Math.Max(1, instruction.Operand));
break;
case Demo2RuleOpcode.DuplicateNextOutput:
context.DuplicatedOutputs = SaturatingMath.Add(context.DuplicatedOutputs, (int)instruction.Operand) > int.MaxValue
? int.MaxValue
: context.DuplicatedOutputs + (int)instruction.Operand;
break;
case Demo2RuleOpcode.AddFaults:
context.Faults = Math.Max(0, context.Faults + (int)instruction.Operand);
break;
case Demo2RuleOpcode.RepairFaults:
context.Faults = Math.Max(0, context.Faults - (int)instruction.Operand);
break;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27fbfb173d4cab643831a132177109b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
#nullable enable
namespace Demo2.Domain
{
public struct DeterministicRandom
{
private ulong _state;
private ulong _increment;
public DeterministicRandom(ulong seed, ulong stream = 54)
{
_state = 0;
_increment = (stream << 1) | 1;
NextUInt();
_state += seed;
NextUInt();
}
public uint NextUInt()
{
var oldState = _state;
_state = unchecked(oldState * 6364136223846793005UL + _increment);
var xorShifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
var rotation = (int)(oldState >> 59);
return (xorShifted >> rotation) | (xorShifted << ((-rotation) & 31));
}
public int NextInt(int minInclusive, int maxExclusive)
{
if (maxExclusive <= minInclusive) return minInclusive;
return minInclusive + (int)(NextUInt() % (uint)(maxExclusive - minInclusive));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4946a70f5600c044b85dcc82d4d33bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
#nullable enable
using System;
namespace Demo2.Domain
{
public static class SaturatingMath
{
public const long Scale = 1000;
public static long Add(long left, long right)
{
if (right > 0 && left > long.MaxValue - right) return long.MaxValue;
if (right < 0 && left < long.MinValue - right) return long.MinValue;
return left + right;
}
public static long Multiply(long left, long right)
{
if (left == 0 || right == 0) return 0;
if (left == long.MinValue && right == -1) return long.MaxValue;
if (right == long.MinValue && left == -1) return long.MaxValue;
var negative = (left < 0) ^ (right < 0);
var a = AbsUnsigned(left);
var b = AbsUnsigned(right);
if (a > (ulong)long.MaxValue / b) return negative ? long.MinValue : long.MaxValue;
var value = (long)(a * b);
return negative ? -value : value;
}
public static long MultiplyScaled(long value, long multiplierMilli)
{
if (value == 0 || multiplierMilli == 0) return 0;
var product = Multiply(value, multiplierMilli);
if (product == long.MaxValue || product == long.MinValue) return product;
return product / Scale;
}
public static string Format(long value)
{
var abs = value == long.MinValue ? long.MaxValue : Math.Abs(value);
if (abs < 1_000_000_000) return value.ToString("N0");
var exponent = (int)Math.Floor(Math.Log10(abs));
var mantissa = value / Math.Pow(10, exponent);
return mantissa.ToString("0.##") + "e" + exponent;
}
private static ulong AbsUnsigned(long value) => value < 0 ? (ulong)(-(value + 1)) + 1UL : (ulong)value;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c00ba3036e8f4814f898673c9363d11c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: