Skip to content

Instantly share code, notes, and snippets.

@xyberviri
Last active September 8, 2020 16:40
Show Gist options
  • Save xyberviri/3b35c4ee441c624d6cc19645940d9bdb to your computer and use it in GitHub Desktop.
Save xyberviri/3b35c4ee441c624d6cc19645940d9bdb to your computer and use it in GitHub Desktop.
/*
CC BY-SA 2.0 James Ray Velasquez Xyberviri/Dabnician
CSDPicker<String> chanceBucket = new CSDPicker<String>();
chanceBucket.AddChance("Epic", 0.005);
chanceBucket.AddChance("Legendary", 0.02);
chanceBucket.AddChance("Rare", 0.125);
chanceBucket.AddChance("UnCommon", 0.25);
chanceBucket.AddChance("Common", 0.6);
Random random = new Random();
for (int i = 0; i < 100; i++)
{
chanceBucket.GetRandom(random.NextDouble());
}
*/
/// <summary>
/// Cumulative sum distribution random object picker
/// </summary>
class CSDPicker<T>
{
private List<CsdObject<T>> pickerList = new List<CsdObject<T>>();
/// <summary>
/// Return full list of objects.
/// </summary>
/// <returns></returns>
public IEnumerable<T> GetList()
{
return pickerList.Select(x => x.GetObject());
}
/// <summary>
/// return the total sum of chances.
/// </summary>
/// <returns></returns>
public double GetBaseChance()
{
return pickerList.Sum(chance => chance.GetBaseChance());
}
/// <summary>
/// This should return 1.0 unless something is broken.
/// </summary>
/// <returns></returns>
public double GetAdjustedChance()
{
return pickerList.Sum(chance => chance.GetChance());
}
/// <summary>
/// Pass in a double between 0.0 and 1.0
/// </summary>
/// <param name="diceRoll"></param>
/// <returns></returns>
public T GetRandom(double diceRoll)
{
double cumulative = 0.0;
T selectedElement = default(T);
for (int i = 0; i < pickerList.Count(); i++)
{
cumulative += pickerList[i].GetChance();
if (diceRoll < cumulative)
{
selectedElement = pickerList[i].GetObject();
break;
}
}
return selectedElement;
}
/// <summary>
/// Adds a new item to the picker list, allows for chaining methods.
/// </summary>
/// <param name="item">the item you want returned when this is picked using <see cref="GetRandom(double)"/></param>
/// <param name="chance">Ideally this should be between 0.0 and 1.0 but values over 1.0 'should' be accounted for..</param>
/// <returns>returns this instance of <see cref="CSDPicker{T}"/></returns>
public CSDPicker<T> AddChance(T item, double chance)
{
pickerList.Add(new CsdObject<T>(item, chance));
double total = pickerList.Sum(x => x.GetBaseChance());
foreach (CsdObject<T> pick in pickerList)
{
pick.RecalculateChance(total);
}
pickerList = pickerList.OrderByDescending(o => o.GetBaseChance()).ToList();
return this;
}
private static int OrderByDescending(CsdObject<T> x, CsdObject<T> y)
{
if (x == null)
{
if (y == null)
{
// If x is null and y is null, they're
// equal.
return 0;
}
else
{
// If x is null and y is not null, y
// is greater.
return -1;
}
}
else
{
// If x is not null...
//
if (y == null)
// ...and y is null, x is greater.
{
return 1;
}
else
{
// ...and y is not null, compare the
// lengths of the two strings.
//
int retval = x.GetHashCode().CompareTo(y.GetHashCode());
if (retval != 0)
{
// If the strings are not of equal length,
// the longer string is greater.
//
return retval;
}
else
{
// If the strings are of equal length,
// sort them with ordinary string comparison.
//
return x.GetChance().CompareTo(y.GetChance());
}
}
}
}
internal class CsdObject<Ti>
{
private readonly Ti csdObject;
private readonly double baseChance;
private double adjustedChance;
public CsdObject(Ti csdObject, double baseChance)
{
this.csdObject = csdObject;
this.baseChance = baseChance;
this.adjustedChance = baseChance;
}
public void RecalculateChance(double cumulativeSum)
{
this.adjustedChance = baseChance / cumulativeSum;
}
public double GetChance()
{
return this.adjustedChance;
}
public double GetBaseChance()
{
return this.baseChance;
}
public Ti GetObject()
{
return csdObject;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment