#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 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; } } } }