From 3b480ea4bbc81311f0354926914ee38a56b049d6 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Wed, 10 Feb 2016 20:17:27 +0000 Subject: [PATCH] Create singletons for AttackOrFleeFuzzy rulesets. This avoids the cost of recreating the engines and rules for every AI and AI squad. --- OpenRA.Mods.Common/AI/AttackOrFleeFuzzy.cs | 240 ++++++++++--------- OpenRA.Mods.Common/AI/HackyAI.cs | 4 +- OpenRA.Mods.Common/AI/RushFuzzy.cs | 30 --- OpenRA.Mods.Common/AI/Squad.cs | 2 - OpenRA.Mods.Common/AI/States/GroundStates.cs | 4 +- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 - 6 files changed, 133 insertions(+), 148 deletions(-) delete mode 100644 OpenRA.Mods.Common/AI/RushFuzzy.cs diff --git a/OpenRA.Mods.Common/AI/AttackOrFleeFuzzy.cs b/OpenRA.Mods.Common/AI/AttackOrFleeFuzzy.cs index 63434fcb0c..655509f278 100644 --- a/OpenRA.Mods.Common/AI/AttackOrFleeFuzzy.cs +++ b/OpenRA.Mods.Common/AI/AttackOrFleeFuzzy.cs @@ -18,24 +18,118 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.AI { - class AttackOrFleeFuzzy + sealed class AttackOrFleeFuzzy { - MamdaniFuzzySystem fuzzyEngine; - - public AttackOrFleeFuzzy() + static readonly string[] DefaultRulesNormalOwnHealth = new[] { - InitializateFuzzyVariables(); - } + "if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack" + }; - protected void AddFuzzyRule(string rule) + static readonly string[] DefaultRulesInjuredOwnHealth = new[] { - fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule(rule)); - } + "if ((OwnHealth is Injured) " + + "and (EnemyHealth is NearDead) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack", - protected void InitializateFuzzyVariables() + "if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack", + + "if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Weak) " + + "and (RelativeSpeed is Slow)) " + + "then AttackOrFlee is Attack", + + "if ((OwnHealth is Injured) " + + "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee", + + "if ((OwnHealth is Injured) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + + "and (RelativeSpeed is Slow)) " + + "then AttackOrFlee is Attack" + }; + + static readonly string[] DefaultRulesNearDeadOwnHealth = new[] { - fuzzyEngine = new MamdaniFuzzySystem(); + "if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Equal) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal))) " + + "then AttackOrFlee is Attack", + "if ((OwnHealth is NearDead) " + + "and (EnemyHealth is NearDead) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee", + + "if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee", + + "if ((OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Weak) " + + "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee", + + "if (OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Equal) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee", + + "if (OwnHealth is NearDead) " + + "and (EnemyHealth is Normal) " + + "and (RelativeAttackPower is Strong) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee", + + "if (OwnHealth is NearDead) " + + "and (EnemyHealth is Injured) " + + "and (RelativeAttackPower is Equal) " + + "and (RelativeSpeed is Fast) " + + "then AttackOrFlee is Flee" + }; + + public static readonly AttackOrFleeFuzzy Default = new AttackOrFleeFuzzy(null, null, null); + public static readonly AttackOrFleeFuzzy Rush = new AttackOrFleeFuzzy(new[] + { + "if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and (RelativeAttackPower is Strong) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Attack", + + "if ((OwnHealth is Normal) " + + "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + + "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal)) " + + "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + + "then AttackOrFlee is Flee" + }, null, null); + + readonly MamdaniFuzzySystem fuzzyEngine = new MamdaniFuzzySystem(); + + public AttackOrFleeFuzzy( + IEnumerable rulesForNormalOwnHealth, + IEnumerable rulesForInjuredOwnHealth, + IEnumerable rulesForNeadDeadOwnHealth) + { var playerHealthFuzzy = new FuzzyVariable("OwnHealth", 0.0, 100.0); playerHealthFuzzy.Terms.Add(new FuzzyTerm("NearDead", new TrapezoidMembershipFunction(0, 0, 20, 40))); playerHealthFuzzy.Terms.Add(new FuzzyTerm("Injured", new TrapezoidMembershipFunction(30, 50, 50, 70))); @@ -65,112 +159,38 @@ namespace OpenRA.Mods.Common.AI attackOrFleeFuzzy.Terms.Add(new FuzzyTerm("Flee", new TrapezoidMembershipFunction(25, 35, 35, 50))); fuzzyEngine.Output.Add(attackOrFleeFuzzy); - AddingRulesForNormalOwnHealth(); - AddingRulesForInjuredOwnHealth(); - AddingRulesForNearDeadOwnHealth(); + foreach (var rule in rulesForNormalOwnHealth ?? DefaultRulesNormalOwnHealth) + AddFuzzyRule(rule); + foreach (var rule in rulesForInjuredOwnHealth ?? DefaultRulesInjuredOwnHealth) + AddFuzzyRule(rule); + foreach (var rule in rulesForNeadDeadOwnHealth ?? DefaultRulesNearDeadOwnHealth) + AddFuzzyRule(rule); } - protected virtual void AddingRulesForNormalOwnHealth() + void AddFuzzyRule(string rule) { - AddFuzzyRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack"); - } - - protected virtual void AddingRulesForInjuredOwnHealth() - { - AddFuzzyRule("if ((OwnHealth is Injured) " + - "and (EnemyHealth is NearDead) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack"); - - AddFuzzyRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack"); - - AddFuzzyRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Weak) " + - "and (RelativeSpeed is Slow)) " + - "then AttackOrFlee is Attack"); - - AddFuzzyRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if ((OwnHealth is Injured) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal) or (RelativeAttackPower is Strong)) " + - "and (RelativeSpeed is Slow)) " + - "then AttackOrFlee is Attack"); - } - - protected virtual void AddingRulesForNearDeadOwnHealth() - { - AddFuzzyRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Equal) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal))) " + - "then AttackOrFlee is Attack"); - - AddFuzzyRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is NearDead) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if ((OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Weak) " + - "and ((RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Equal) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Normal) " + - "and (RelativeAttackPower is Strong) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee"); - - AddFuzzyRule("if (OwnHealth is NearDead) " + - "and (EnemyHealth is Injured) " + - "and (RelativeAttackPower is Equal) " + - "and (RelativeSpeed is Fast) " + - "then AttackOrFlee is Flee"); + fuzzyEngine.Rules.Add(fuzzyEngine.ParseRule(rule)); } public bool CanAttack(IEnumerable ownUnits, IEnumerable enemyUnits) { + double attackChance; var inputValues = new Dictionary(); - inputValues.Add(fuzzyEngine.InputByName("OwnHealth"), NormalizedHealth(ownUnits, 100)); - inputValues.Add(fuzzyEngine.InputByName("EnemyHealth"), NormalizedHealth(enemyUnits, 100)); - inputValues.Add(fuzzyEngine.InputByName("RelativeAttackPower"), RelativePower(ownUnits, enemyUnits)); - inputValues.Add(fuzzyEngine.InputByName("RelativeSpeed"), RelativeSpeed(ownUnits, enemyUnits)); + lock (fuzzyEngine) + { + inputValues.Add(fuzzyEngine.InputByName("OwnHealth"), NormalizedHealth(ownUnits, 100)); + inputValues.Add(fuzzyEngine.InputByName("EnemyHealth"), NormalizedHealth(enemyUnits, 100)); + inputValues.Add(fuzzyEngine.InputByName("RelativeAttackPower"), RelativePower(ownUnits, enemyUnits)); + inputValues.Add(fuzzyEngine.InputByName("RelativeSpeed"), RelativeSpeed(ownUnits, enemyUnits)); + + var result = fuzzyEngine.Calculate(inputValues); + attackChance = result[fuzzyEngine.OutputByName("AttackOrFlee")]; + } - var result = fuzzyEngine.Calculate(inputValues); - var attackChance = result[fuzzyEngine.OutputByName("AttackOrFlee")]; return !double.IsNaN(attackChance) && attackChance < 30.0; } - protected static float NormalizedHealth(IEnumerable actors, float normalizeByValue) + static float NormalizedHealth(IEnumerable actors, float normalizeByValue) { var sumOfMaxHp = 0; var sumOfHp = 0; @@ -189,7 +209,7 @@ namespace OpenRA.Mods.Common.AI return (sumOfHp * normalizeByValue) / sumOfMaxHp; } - protected float RelativePower(IEnumerable own, IEnumerable enemy) + static float RelativePower(IEnumerable own, IEnumerable enemy) { return RelativeValue(own, enemy, 100, SumOfValues, a => { @@ -206,12 +226,12 @@ namespace OpenRA.Mods.Common.AI }); } - protected float RelativeSpeed(IEnumerable own, IEnumerable enemy) + static float RelativeSpeed(IEnumerable own, IEnumerable enemy) { return RelativeValue(own, enemy, 100, Average, (Actor a) => a.Info.TraitInfo().Speed); } - protected static float RelativeValue(IEnumerable own, IEnumerable enemy, float normalizeByValue, + static float RelativeValue(IEnumerable own, IEnumerable enemy, float normalizeByValue, Func, Func, float> relativeFunc, Func getValue) { if (!enemy.Any()) @@ -224,7 +244,7 @@ namespace OpenRA.Mods.Common.AI return relative.Clamp(0.0f, 999.0f); } - protected float SumOfValues(IEnumerable actors, Func getValue) where TTraitInfo : ITraitInfo + static float SumOfValues(IEnumerable actors, Func getValue) where TTraitInfo : ITraitInfo { var sum = 0; foreach (var a in actors) @@ -234,7 +254,7 @@ namespace OpenRA.Mods.Common.AI return sum; } - protected float Average(IEnumerable actors, Func getValue) where TTraitInfo : ITraitInfo + static float Average(IEnumerable actors, Func getValue) where TTraitInfo : ITraitInfo { var sum = 0; var countActors = 0; diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index a31ed06dab..e24e4d1caa 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -231,8 +231,6 @@ namespace OpenRA.Mods.Common.AI BitArray resourceTypeIndices; - RushFuzzy rushFuzzy = new RushFuzzy(); - Cache aggro = new Cache(_ => new Enemy()); List builders = new List(); @@ -776,7 +774,7 @@ namespace OpenRA.Mods.Common.AI var enemies = World.FindActorsInCircle(b.CenterPosition, WDist.FromCells(Info.RushAttackScanRadius)) .Where(unit => Player.Stances[unit.Owner] == Stance.Enemy && unit.Info.HasTraitInfo()).ToList(); - if (rushFuzzy.CanAttack(ownUnits, enemies)) + if (AttackOrFleeFuzzy.Rush.CanAttack(ownUnits, enemies)) { var target = enemies.Any() ? enemies.Random(Random) : b; var rush = GetSquadOfType(SquadType.Rush); diff --git a/OpenRA.Mods.Common/AI/RushFuzzy.cs b/OpenRA.Mods.Common/AI/RushFuzzy.cs deleted file mode 100644 index 297d96d7ec..0000000000 --- a/OpenRA.Mods.Common/AI/RushFuzzy.cs +++ /dev/null @@ -1,30 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -namespace OpenRA.Mods.Common.AI -{ - class RushFuzzy : AttackOrFleeFuzzy - { - protected override void AddingRulesForNormalOwnHealth() - { - AddFuzzyRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and (RelativeAttackPower is Strong) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Attack"); - - AddFuzzyRule("if ((OwnHealth is Normal) " + - "and ((EnemyHealth is NearDead) or (EnemyHealth is Injured) or (EnemyHealth is Normal)) " + - "and ((RelativeAttackPower is Weak) or (RelativeAttackPower is Equal)) " + - "and ((RelativeSpeed is Slow) or (RelativeSpeed is Equal) or (RelativeSpeed is Fast))) " + - "then AttackOrFlee is Flee"); - } - } -} diff --git a/OpenRA.Mods.Common/AI/Squad.cs b/OpenRA.Mods.Common/AI/Squad.cs index 9c528fe982..7bb060012b 100644 --- a/OpenRA.Mods.Common/AI/Squad.cs +++ b/OpenRA.Mods.Common/AI/Squad.cs @@ -30,8 +30,6 @@ namespace OpenRA.Mods.Common.AI internal Target Target; internal StateMachine FuzzyStateMachine; - internal AttackOrFleeFuzzy AttackOrFleeFuzzy = new AttackOrFleeFuzzy(); - public Squad(HackyAI bot, SquadType type) : this(bot, type, null) { } public Squad(HackyAI bot, SquadType type, Actor target) diff --git a/OpenRA.Mods.Common/AI/States/GroundStates.cs b/OpenRA.Mods.Common/AI/States/GroundStates.cs index a35af0c358..a7e3339d23 100644 --- a/OpenRA.Mods.Common/AI/States/GroundStates.cs +++ b/OpenRA.Mods.Common/AI/States/GroundStates.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.Common.AI { protected virtual bool ShouldFlee(Squad owner) { - return base.ShouldFlee(owner, enemies => !owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemies)); + return base.ShouldFlee(owner, enemies => !AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemies)); } } @@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.AI if (enemyUnits.Any()) { - if (owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemyUnits)) + if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits)) { foreach (var u in owner.Units) owner.Bot.QueueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.TargetActor.Location }); diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 28d3202018..a1b67b7be4 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -139,7 +139,6 @@ -