|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TheCult
{
/// <summary>
/// A Class representing a cult. Each person that enters gets a mark and becomes a cultist.
/// </summary>
public class Cult : ICollection<Person>, IListSource
{
private List<Cultist> _innerList;
private int _mark;
/// <summary>
/// Instantiates a new cult with no members.
/// </summary>
public Cult()
{
_innerList = new List<Cultist>();
}
/// <summary>
/// Instantiates a new cult with members.
/// </summary>
/// <param name="people">The people that will join the cult.</param>
public Cult(IEnumerable<Person> people)
: this()
{
this.AddRange(people);
}
protected List<Cultist> InnerList
{
get { return _innerList; }
}
/// <summary>
/// Calculates if a new captain should be assigned to keep the members in line.
/// </summary>
/// <returns>True if a new captain should be assigned.</returns>
protected bool NewCaptainNeeded()
{
int captains = (this.InnerList.Where(c => c.Rank == CultRanks.Captain).Count());
int noOfCultists = this.InnerList.Count;
int neededCaptains = 0;
// For every 10th member after 10 there should be a captain.
if ((noOfCultists - 10) - (captains * 10) >= 10 || noOfCultists % 10 == 0m)
{
while (noOfCultists > 0)
{
noOfCultists -= 10;
neededCaptains += 1;
}
// Needed captains - 1 because the tenth member becomes
// a general instead of captain.
return neededCaptains - 1 > captains;
}
return false;
}
/// <summary>
/// Assigns a new captain if it is absolutely necessary to keep the members in line.
/// </summary>
protected void AssignNextCaptainIfNecessary()
{
if (NewCaptainNeeded())
{
AssignNextCaptain();
}
}
/// <summary>
/// Assigns the first person who is elligible for becoming a captain.
/// </summary>
protected void AssignNextCaptain()
{
// The person who is in the cult the longest, but who is still a soldier gets the rank captain.
this.InnerList.Where(c => c.Rank == CultRanks.Soldier).First().Rank = CultRanks.Captain;
}
/// <summary>
/// Checks the highest mark of any member that is currently in the cult and creates a new mark based on that.
/// </summary>
/// <returns>A new, unique mark that is 1 higher than the current highest mark.</returns>
protected virtual int GetMark()
{
if (this.InnerList.Count > 0)
{
_mark += 1;
}
else
{
// If no cultists yet exist get a random number between 500 and 1000 because we can.
Random rnd = new Random();
_mark = rnd.Next(500, 1000);
}
return _mark;
}
/// <summary>
/// Checks if a person is eligible to join the cult.
/// </summary>
/// <param name="person">The person to check.</param>
/// <returns>True if a person can join the cult.</returns>
protected virtual bool CanJoin(Person person)
{
// A person is only able to join if no person with the same name is already in the cult.
return !this.Contains(person, new PersonNameEqualityComparer());
}
/// <summary>
/// Makes a group of people join the cult and become cultists. The new cultists will have the lowest ranks in the cult.
/// </summary>
/// <param name="people">The people who will join the cult and become cultists.</param>
public void AddRange(IEnumerable<Person> people)
{
foreach (Person p in people)
{
this.Add(p);
}
}
/// <summary>
/// Makes a person join the cult and become a cultist. The new cultist will have the lowest rank in the cult.
/// </summary>
/// <param name="item">The person who will join the cult and become a cultist.</param>
public void Add(Person item)
{
if (item == null)
throw new ArgumentNullException("Item cannot be nothing.");
// Check if the person is allowed to enter the cult.
if (CanJoin(item))
{
// Create a new cultist with the current person.
Cultist cultist = Cultist.CreateCultist(item, GetMark());
// Add the new recruit to the cult!
this.InnerList.Add(cultist);
// Check how many members the cult currently has and
// set the new recruits rank accordingly.
int count = this.InnerList.Count;
switch (count)
{
case 1:
// If the inner list has no items yet then the cultist that is
// to be added becomes the first cultist and thus gets leader status.
cultist.Rank = CultRanks.Leader;
break;
case 10:
// If the cult has 10 members then it is needed to get a second in command.
// The second person to join the cult becomes general.
this.InnerList[1].Rank = CultRanks.General;
// The to be added cultist becomes a soldier.
cultist.Rank = CultRanks.Soldier;
break;
default:
// Nothing special.
// The to be added cultist becomes a soldier.
cultist.Rank = CultRanks.Soldier;
break;
}
// If there are 20 cult members or any number dividable by 10 after that
// then it is necessary to get a new person that can keep the peace.
AssignNextCaptainIfNecessary();
}
}
/// <summary>
/// Removes all cultists from the cult.
/// </summary>
public void Clear()
{
// Clears the list of cultists (perhaps the police raided the place?).
this.InnerList.Clear();
}
/// <summary>
/// Determines whether a person is a member of the cult.
/// </summary>
/// <param name="item">The person to check if he is in the cult.</param>
/// <returns>True if the person is secretly a cultist.</returns>
public bool Contains(Person item)
{
return (this.InnerList.Where(cultist => cultist.FullName == item.FullName).FirstOrDefault() != null);
}
/// <summary>
/// Copies the entire cult following to a compatible one-dimensional array, starting at the specified index of the target array.
/// </summary>
/// <param name="array">The one-dimensional Array that is the destination of the elements copied from the cult. The Array must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
/// <exception cref="System.ArgumentNullException">Array is null.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
/// <exception cref="System.ArgumentException">The number of elements in the source System.Collections.Generic.List(Of T) is greater than the available space from arrayIndex to the end of the destination array."</exception>
public void CopyTo(Person[] array, int arrayIndex)
{
List<Person> tempList = new List<Person>();
tempList.AddRange(this.InnerList);
tempList.CopyTo(array, arrayIndex);
}
/// <summary>
/// Gets the number of cultists that are a member of the cult.
/// </summary>
/// <returns>The number of cultists actually contained in the cult.</returns>
public int Count
{
get { return this.InnerList.Count; }
}
// This is private, kind of 'not implemented', but also not quite.
/// <summary>
/// Gets a value indicating whether the cult is read-only.
/// </summary>
/// <returns>True if the cult is read-only; otherwise, false.</returns>
bool ICollection<Person>.IsReadOnly
{
get { return false; }
}
/// <summary>
/// Removes a person from the cult if this person is a member. New ranks will be assigned as necessary.
/// </summary>
/// <param name="item">Person to remove from the cult.</param>
/// <returns>True if person is successfully removed; otherwise, false. This method also returns false if person was not found in the cult.</returns>
public bool Remove(Person item)
{
// First retrieve the cultist based on the persons name.
Cultist cultist = this.InnerList.Where(c => c.FullName == item.FullName).FirstOrDefault();
// Check if a cultist with the given name exists.
if (cultist != null)
{
if (this.InnerList.Remove(cultist))
{
// If the cultist was removed the ranks have to be recalculated.
// We do not know who was removed from the cult. Maybe it was the leader
// or general, so we need some promotions.
RecalculateRanksOnRemove(cultist);
cultist.Rank = CultRanks.None;
return true;
}
}
return false;
}
/// <summary>
/// Recalculates ranks of the members if a member was removed from the cult.
/// </summary>
/// <param name="cultist">The cultist that was removed.</param>
private void RecalculateRanksOnRemove(Cultist cultist)
{
if (this.InnerList.Count > 0)
{
// Set the new ranks based on the rank of the current cultist.
switch (cultist.Rank)
{
case CultRanks.Leader:
// If the cultist was the leader then the new leader will be assigned.
// The former general will be leader so a new general will also be assigned.
// This could mean that a new captain is needed to.
this.InnerList[0].Rank = CultRanks.Leader;
if (this.InnerList.Count > 1)
{
this.InnerList[1].Rank = CultRanks.General;
AssignNextCaptainIfNecessary();
}
break;
case CultRanks.General:
// If a general was removed then assign a new general and check if there are still enough captains.
if (this.InnerList.Count > 1)
{
this.InnerList[1].Rank = CultRanks.General;
AssignNextCaptainIfNecessary();
}
break;
case CultRanks.Captain:
// If a captain was removed check if new captains are necessary.
AssignNextCaptainIfNecessary();
break;
default:
break;
// Do Nothing.
}
}
}
/// <summary>
/// Returns an Enumerator that iterates through the cultists.
/// </summary>
/// <returns>An Enumerator for the cult.</returns>
public System.Collections.Generic.IEnumerator<Person> GetEnumerator()
{
return new GenericEnumerator.GenericEnumerator<Cultist>(this.InnerList);
}
/// <summary>
/// Returns an Enumerator that iterates through the cultists.
/// </summary>
/// <returns>An Enumerator for the cult.</returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
/// <summary>
/// Gets a List(Of Cultist) rather than a List(Of People) that are contained in the cult.
/// </summary>
/// <returns>A list of cultists.</returns>
public List<Cultist> GetCultists()
{
return new List<Cultist>(this.InnerList);
}
bool IListSource.ContainsListCollection
{
get { return true; }
}
System.Collections.IList IListSource.GetList()
{
return this.InnerList;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
Sander Rossel is a Microsoft certified professional developer with experience and expertise in .NET and .NET Core (C#, ASP.NET, and Entity Framework), SQL Server, Azure, Azure DevOps, JavaScript, MongoDB, and other technologies.
He is the owner of
JUUN Software, a company specializing in custom software. JUUN Software uses modern, but proven technologies, such as .NET Core, Azure and Azure DevOps.
You can't miss
his books on Amazon and
his free e-books on Syncfusion!
He wrote a JavaScript LINQ library,
arrgh.js (works in IE8+, Edge, Firefox, Chrome, and probably everything else).
Check out his
prize-winning articles on CodeProject as well!