view API/Objects/Requirement/UnitRequiresNParentModelsForMUnitsRequirement.cs @ 490:e35fc85eda2b

Re #410: Create "N units per M models in parent unit" requirement * Add factory to create requirements * Add methods to allow things other than 1:1 values for N and M
author IBBoard <dev@ibboard.co.uk>
date Sun, 29 Jul 2012 21:02:59 +0100
parents aac77204ae0e
children a41c2bc1683a
line wrap: on
line source

// This file (UnitRequiresNoMoreThanNUnitsPerMModelsInParentUnitRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2012 IBBoard
//
// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
using System;
using IBBoard.WarFoundry.API.Objects.Requirement.Context;
using System.Collections.Generic;
using IBBoard.Collections;

namespace IBBoard.WarFoundry.API.Objects.Requirement
{
	public class UnitRequiresNParentModelsForMUnitsRequirement : AbstractUnitRequirement<UnitType>
	{
		public static readonly string REQUIREMENT_ID = "RequiresNoMoreThanNUnitsPerMModelsInParent";

		public UnitRequiresNParentModelsForMUnitsRequirement(UnitType allowedObject, params UnitType[] limitedUnitTypes) : base(allowedObject, limitedUnitTypes)
		{
			//Do nothing special
		}

		public override string RequirementID
		{
			get
			{
				return REQUIREMENT_ID;
			}
		}

		protected override bool IsApplicable(IWarFoundryObject toObject, AddingContext context)
		{
			return GetCountFromContext(context) > 0 && IsApplicable(toObject);
		}
		
		private bool IsApplicable(IWarFoundryObject toObject)
		{
			bool isApplicable = false;
			UnitType unitType = GetUnitTypeFromObject(toObject);
			
			if (unitType != null)
			{
				isApplicable = this.AllowedObject.Equals(unitType) || IsApplicable(unitType);
			}
			
			return isApplicable;
		}
		
		private bool IsApplicable(UnitType unitType)
		{
			bool isApplicable = false;
			foreach (UnitCountRequirementData requirement in ConstraintTypes)
			{
				if (requirement.UnitType.Equals(unitType))
				{
					isApplicable = true;
					break;
				}
			}
			return isApplicable;
		}

		protected override int GetObjectCountFromArmy(Army toArmy, UnitType obj)
		{
			return 0;
		}

		protected override int GetObjectCountFromObject(IWarFoundryObject wfObject)
		{
			return base.GetObjectCountFromObject(wfObject);
		}

		private int GetCountFromContext(AddingContext context)
		{
			AddingToParentContext parentContext = context as AddingToParentContext;

			if (parentContext == null || !IsUnitTypeLimited(parentContext.ParentUnit.UnitType))
			{
				return 0;
			}
			else
			{
				return parentContext.ParentUnit.Size;
			}
		}

		public override void AddUnitTypeRequirement(UnitType unitType)
		{
			AddUnitTypeRequirement(unitType, 1);
		}

		public void AddUnitTypeRequirement(UnitType unitType, int allowedCount)
		{
			AddUnitTypeRequirement(unitType, allowedCount, 1);
		}

		public void AddUnitTypeRequirement(UnitType unitType, int allowedCount, int reqsCount)
		{
			ConstraintTypes.Add(new UnitCountRequirementData(unitType, reqsCount, allowedCount));
		}

		protected override string GetFailedAddingRequirementsString(IWarFoundryObject toAdd, Army toArmy, AddingContext context)
		{
			UnitType unitType = GetUnitTypeFromObject(toAdd);
			AddingToParentContext parentContext = context as AddingToParentContext;

			if (unitType != null && parentContext != null)
			{			
				return GetFailedAddingRequirementsString(unitType, parentContext.ParentUnit, toArmy);
			}
			else
			{
				return "";
			}
		}

		private string GetFailedAddingRequirementsString(UnitType unitType, Unit parentUnit, Army toArmy)
		{
			int allowedTypeCount = GetChildCountFromUnit(parentUnit);
			
			return String.Format("Army can only contain {0} × {1} as sub-units of each {2}, would have {3}", allowedTypeCount, unitType.Name, parentUnit.UnitType.Name, (allowedTypeCount + 1));
		}

		protected override string AllowsAddingFailedMessage
		{
			get
			{
				return "{0}";
			}
		}

		protected override Validation CheckAllowsAdding(IWarFoundryObject wfObject, Army toArmy, AddingContext context)
		{
			AddingToParentContext parentContext = context as AddingToParentContext;
			return parentContext == null ? Validation.Passed : CheckAllowsAdding(wfObject, toArmy, parentContext);
		}

		private Validation CheckAllowsAdding(IWarFoundryObject obj, Army toArmy, AddingToParentContext context)
		{
			Validation canAdd = Validation.Passed;
			Unit parentUnit = context.ParentUnit;
			//Get count and add one because we're checking if this unit can be added, so it hasn't been added yet
			int allowedTypeCount = GetChildCountFromUnit(parentUnit) + 1; 
			
			foreach (UnitCountRequirementData limit in ConstraintTypes)
			{
				int limitedTypeCount = parentUnit.Size;

				if (!IsValidByRequirement(limit, allowedTypeCount, limitedTypeCount))
				{
					canAdd = Validation.Failed;
					break;
				}
			}

			return canAdd;
		}

		private int GetChildCountFromUnit(Unit parentUnit)
		{
			int count = 0;

			foreach (Unit unit in parentUnit.ContainedUnits)
			{
				if (unit.UnitType.Equals(AllowedObject))
				{
					count++;
				}
			}

			return count;
		}
		
		private bool IsValidByRequirement(UnitCountRequirementData limit, int allowedTypeCount, int limitedTypeCount)
		{
			//Round down limited units to get only whole amounts
			double normalisedLimited = Math.Floor(limitedTypeCount / (limit.Count * 1.0));
			double normalisedAllowed = allowedTypeCount / (limit.AllowsCount * 1.0);
			return normalisedLimited >= 1 && normalisedAllowed <= normalisedLimited;
		}

		public override Validation ValidatesArmy(Army army)
		{
			bool isApplicable = false;
			Validation result = Validation.Passed;

			foreach (Unit unit in army.GetUnits(AllowedObject))
			{
				Unit parentUnit = unit.ParentUnit;
				isApplicable = isApplicable || (parentUnit != null && IsUnitTypeLimited(parentUnit.UnitType));
				if (isApplicable && !ValidatesUnitInArmy(unit, parentUnit, army))
				{
					result = Validation.Failed;
					break;
				}
			}

			return isApplicable ? result : Validation.NotApplicable;
		}

		private bool ValidatesUnitInArmy(Unit unit, Unit parentUnit, Army army)
		{			
			bool isValid = true;
			int allowedTypeCount = GetChildCountFromUnit(parentUnit); 
		
			foreach (UnitCountRequirementData limit in ConstraintTypes)
			{
				int limitedTypeCount = parentUnit.Size;
			
				if (!IsValidByRequirement(limit, allowedTypeCount, limitedTypeCount))
				{
					isValid = false;
					break;
				}
			}
			
			return isValid;
		}

		protected override string ValidationFailedMessage
		{
			get
			{
				List<String> parentsList = new List<string>();

				foreach (UnitCountRequirementData limit in ConstraintTypes)
				{
					parentsList.Add(String.Format("{0} × {1} for every {2} model in a parent unit of {3}", limit.AllowsCount, AllowedObject.Name, limit.Count, limit.UnitType.Name));
				}

				return String.Format("Army can only contain {0}: {{0}}.", String.Join(", ", parentsList.ToArray()));
			}
		}
		
		protected override string GetFailedRequirementsString(Army army)
		{
			return String.Join("; ", GetFailedValidationRequirements(army).ToArray());
		}
		
		private List<string> GetFailedValidationRequirements(Army army)
		{
			List<string> failures = new List<string>();
			SimpleSet<Unit> parentUnits = new SimpleSet<Unit>();

			foreach (Unit unit in army.GetUnits(AllowedObject))
			{
				Unit parentUnit = unit.ParentUnit;
				if (parentUnit != null && !parentUnits.Contains(parentUnit) && IsUnitTypeLimited(parentUnit.UnitType) && !ValidatesUnitInArmy(unit, parentUnit, army))
				{
					parentUnits.Add(parentUnit);
					int allowedTypeCount = GetChildCountFromUnit(parentUnit); 
					failures.Add(String.Format("{0} has {1} for {2}", parentUnit.Name, allowedTypeCount, parentUnit.Size));
				}
			}
			
			return failures;
		}

		protected override int GetObjectCountFromArmy(Army toArmy)
		{
			return 0;
		}
	}
}