- 星级决定机制
namespace GenshinGacha
{
public enum LevelType
{
R,
SR,
SSR
}
public class LevelBuilder
{
public Dictionary<LevelType, int> initWeight = new ictionary<LevelType, int>();
//记录从哪一抽开始增加权重
public Dictionary<LevelType, int> AddRange = new Dictionary<LevelType, int>();
//每次增加权重大小
public Dictionary<LevelType, int> AddWeight = new Dictionary<LevelType, int>();
public LevelBuilder()
{
foreach (LevelType level in Enum.GetValues(typeof(LevelType)))
{
initWeight[level] = 0;
AddWeight[level] = 0;
}
initWeight[LevelType.R] = 5000;
initWeight[LevelType.SR] = 3000;
initWeight[LevelType.SSR] = 1000;
//十连抽从第9抽增加概率
AddRange[LevelType.SR] = 9;
//限定池保底
AddRange[LevelType.SSR] = 74;
}
public LevelBuilder SetRWeight(int weight)
{
initWeight[LevelType.R] = weight;
return this;
}
public LevelBuilder SetSRWeight(int weight)
{
initWeight[LevelType.SR] = weight;
return this;
}
public LevelBuilder SetSSRWeight(int weight)
{
initWeight[LevelType.SSR] = weight;
return this;
}
public LevelBuilder SetSRAddRange(int range)
{
AddRange[LevelType.SR] = range;
return this;
}
public LevelBuilder SetSSRAddRange(int range)
{
AddRange[LevelType.SSR] = range;
return this;
}
public LevelBuilder SetAddWeight(LevelType level, int weight)
{
AddWeight[level] = weight;
return this;
}
}
//决定当前单抽的星级
public class LevelDecision
{
public LevelBuilder builder;
public LevelDecision(LevelBuilder builder)
{
this.builder = builder;
//按星级降序,实现类似 10连最后一发五星,那第11发为四星的效果
levelTypeWeight.Add(LevelType.SSR, this.builder.initWeight[LevelType.SSR]);
levelTypeWeight.Add(LevelType.SR, this.builder.initWeight[LevelType.SR]);
levelTypeWeight.Add(LevelType.R, this.builder.initWeight[LevelType.R]);
levelCounter[LevelType.SR] = 1;
levelCounter[LevelType.SSR] = 1;
}
//星级 --- >权值
public Dictionary<LevelType, int> levelTypeWeight = new Dictionary<LevelType, int>();
//5,4星计数器(分别计数)
public Dictionary<LevelType, int> levelCounter = new Dictionary<LevelType, int>();
//总抽数
public int totalCount = 0;
public (LevelType, int) Decide()
{
totalCount++;
int ran = new Random(Guid.NewGuid().GetHashCode()).Next(0, 10000);
int end = 0, count = 0;
LevelType currentLevel = LevelType.R;
//假设抽卡为转轮盘,那落在哪个扇区
foreach (KeyValuePair<LevelType, int> t in levelTypeWeight)
{
end += t.Value;
if (ran < end)
{
currentLevel = t.Key;
break;
}
}
switch (currentLevel)
{
case LevelType.R:
GetR(ref count);
break;
case LevelType.SR:
GetSR(ref count);
break;
case LevelType.SSR:
GetSSR(ref count);
break;
}
return (currentLevel, count);
}
public void GetR(ref int count)
{
count = totalCount;
levelCounter[LevelType.SR]++;
levelCounter[LevelType.SSR]++;
//连续73抽没有5星,下一抽开始增加概率,90抽必出
if (levelCounter[LevelType.SSR] >= this.builder.AddRange[LevelType.SSR])
levelTypeWeight[LevelType.SSR] += builder.AddWeight[LevelType.SSR];
//10连必有4星,从第9发增加概率,10抽后必出4星
if (levelCounter[LevelType.SR] >= this.builder.AddRange[LevelType.SR])
levelTypeWeight[LevelType.SR] += builder.AddWeight[LevelType.SR];
}
public void GetSR(ref int count)
{
count = levelCounter[LevelType.SR];
levelCounter[LevelType.SR] = 1;
levelCounter[LevelType.SSR]++;
levelTypeWeight[LevelType.SR] = builder.initWeight[LevelType.SR];
if (levelCounter[LevelType.SSR] >= builder.AddRange[LevelType.SSR])
levelTypeWeight[LevelType.SSR] += builder.AddWeight[LevelType.SSR];
}
public void GetSSR(ref int count)
{
count = levelCounter[LevelType.SSR];
levelCounter[LevelType.SR]++;
levelCounter[LevelType.SSR] = 1;
levelTypeWeight[LevelType.SSR] = builder.initWeight[LevelType.SSR];
if (levelCounter[LevelType.SR] >= builder.AddRange[LevelType.SR])
levelTypeWeight[LevelType.SR] += builder.AddWeight[LevelType.SR];
}
}
}
- 物品种类决定机制
namespace GenshinGacha
{
public enum ItemType
{
RWeapon,
SRRole,
SRWeapon,
SSRRole,
SSRWeapon,
SSRRole_Up
}
public class ItemTypeDecisionBuilder
{
public Dictionary<ItemType, int> itemInitWeight = new Dictionary<ItemType, int>();
//相隔一段时间没有抽到该类物品,则增加该类的权重
public Dictionary<ItemType, int> itemSmoothRange = new Dictionary<ItemType, int>();
//增加权值大小
//eg. 146抽没有五星角色,那么从147抽每抽开始增加概率,到180抽时5星角色概率为99.83%,270发的保底5星必定为5星角色。这样270发必有5星武器或者角色
public Dictionary<ItemType, int> itemSmoothAddWeight = new Dictionary<ItemType, int>();
public ItemTypeDecisionBuilder()
{
foreach (ItemType itemType in Enum.GetValues(typeof(ItemType)))
{
itemInitWeight[itemType] = 0;
itemSmoothRange[itemType] = 0;
itemSmoothAddWeight[itemType] = 0;
}
}
public ItemTypeDecisionBuilder SetSRRoleWeight(int weight)
{
itemInitWeight[ItemType.SRRole] = weight;
itemInitWeight[ItemType.SRWeapon] = 10000 - itemInitWeight[ItemType.SRRole];
return this;
}
public ItemTypeDecisionBuilder SetSSRRoleWeight(int weight, bool isUp = true)
{
itemInitWeight[ItemType.SSRRole] = weight;
if (isUp)
itemInitWeight[ItemType.SSRRole_Up] = 10000 - itemInitWeight[ItemType.SSRRole];
else
itemInitWeight[ItemType.SSRWeapon] = 10000 - itemInitWeight[ItemType.SSRRole];
return this;
}
/// <summary>
///
/// </summary>
/// <param name="itemType"></param>
/// <param name="start">超过多少发没有抽中该类物品后触发平稳机制</param>
/// <param name="addWeight">每抽增加权值大小</param>
/// <returns></returns>
public ItemTypeDecisionBuilder SetSmooth(ItemType itemType, int start, int addWeight)
{
itemSmoothRange[itemType] = start;
itemSmoothAddWeight[itemType] = addWeight;
return this;
}
}
public class ItemTypeDecision
{
public ItemTypeDecisionBuilder builder;
public SmoothMechanism sm;
public int SSRCount = 0; //限定池大保底
public ItemTypeDecision(ItemTypeDecisionBuilder builder)
{
this.builder = builder;
sm = new SmoothMechanism(this.builder);
}
/// <summary>
/// 决定物品种类
/// </summary>
/// <param name="levelType">星级</param>
/// <param name="count">当前抽数</param>
/// <returns></returns>
public ItemType Decide(LevelType levelType, int count)
{
ItemType itemType = ItemType.RWeapon;
int ran = new Random(Guid.NewGuid().GetHashCode()).Next(0, 10000);
switch (levelType)
{
case LevelType.R:
itemType = ItemType.RWeapon;
break;
case LevelType.SR:
if (ran < builder.itemInitWeight[ItemType.SRRole])
itemType = ItemType.SRRole;
else
itemType = ItemType.SRWeapon;
break;
case LevelType.SSR:
//当前抽出来了常驻5星,那么下一5星必定为限定
if (SSRCount == 0 && ran < builder.itemInitWeight[ItemType.SSRRole])
{
SSRCount++;
itemType = ItemType.SSRRole;
}
else
{
SSRCount = 0;
itemType = ItemType.SSRRole_Up;
}
break;
}
return itemType;
}
public ItemType Decide_Changzhu(LevelType levelType,int count)
{
ItemType itemType = ItemType.RWeapon;
int ran = new Random(Guid.NewGuid().GetHashCode()).Next(0, 10000);
switch (levelType)
{
case LevelType.R:
itemType = ItemType.RWeapon;
break;
case LevelType.SR:
if (ran < sm.itemWeight[ItemType.SRWeapon])
itemType = ItemType.SRWeapon;
else
itemType = ItemType.SRRole;
break;
case LevelType.SSR:
if (ran < sm.itemWeight[ItemType.SSRWeapon])
itemType = ItemType.SSRWeapon;
else
itemType = ItemType.SSRRole;
break;
}
sm.CheckCount(itemType,levelType);
return itemType;
}
}
}
- 平稳机制
namespace GenshinGacha
{
public class SmoothMechanism
{
public Dictionary<ItemType, int> itemCounter = new Dictionary<ItemType, int>();
public ItemTypeDecisionBuilder builder;
public Dictionary<ItemType, int> itemWeight = new Dictionary<ItemType, int>();
public SmoothMechanism(ItemTypeDecisionBuilder builder)
{
this.builder = builder;
//初始化
foreach (ItemType itemType in Enum.GetValues(typeof(ItemType)))
itemCounter[itemType] = 0;
foreach (KeyValuePair<ItemType, int> kv in this.builder.itemInitWeight)
itemWeight[kv.Key] = builder.itemInitWeight[kv.Key];
}
/// <summary>
/// 当前抽数是否满足平稳机制要求并触发相关事件
/// </summary>
/// <param name="itemType">当前决策的物品类型(需判断是否应该改成同星级其他物品)</param>
public void CheckCount(ItemType itemType, LevelType level)
{
itemCounter[itemType] = 0;
//先重置权值
if (level == LevelType.SR)
{
itemWeight[ItemType.SRWeapon] = builder.itemInitWeight[ItemType.SRWeapon];
itemWeight[ItemType.SRRole] = builder.itemInitWeight[ItemType.SRRole];
}
else if (level == LevelType.SSR)
{
itemWeight[ItemType.SSRRole] = builder.itemInitWeight[ItemType.SSRRole];
itemWeight[ItemType.SSRWeapon] = builder.itemInitWeight[ItemType.SSRWeapon];
itemWeight[ItemType.SSRRole_Up] = builder.itemInitWeight[ItemType.SSRRole_Up];
}
else
{
itemWeight[itemType] = builder.itemInitWeight[itemType];
}
foreach (KeyValuePair<ItemType, int> item in itemCounter)
{
//跳过当前抽中的物品
if (item.Key == itemType) continue;
itemCounter[item.Key]++;
int start = builder.itemSmoothRange[item.Key];
int addWeight = builder.itemSmoothAddWeight[item.Key];
int initWeight = builder.itemInitWeight[item.Key];
int leftWeight = 0;
if (item.Value >= start)
{
itemWeight[item.Key] = initWeight + (item.Value - start) * addWeight;
leftWeight = 10000 - itemWeight[item.Key] >= 0 ? 10000 - itemWeight[item.Key] : 0;
//每次抽中物品,都要根据计数结果重新计算概率
switch (item.Key)
{
case ItemType.SRWeapon:
itemWeight[ItemType.SRRole] = leftWeight;
break;
case ItemType.SRRole:
itemWeight[ItemType.SRWeapon] = leftWeight;
break;
case ItemType.SSRRole:
itemWeight[ItemType.SSRWeapon] = leftWeight;
break;
case ItemType.SSRWeapon:
itemWeight[ItemType.SSRRole] = leftWeight;
break;
}
}
}
foreach (KeyValuePair<ItemType, int> item in itemCounter)
{
//跳过当前抽中的物品
if (item.Key == itemType) continue;
itemCounter[item.Key]++;
int start = builder.itemSmoothRange[item.Key];
int addWeight = builder.itemSmoothAddWeight[item.Key];
int initWeight = builder.itemInitWeight[item.Key];
int leftWeight = 0;
if (item.Value >= start)
{
itemWeight[item.Key] = initWeight + (item.Value - start) * addWeight;
leftWeight = 10000 - itemWeight[item.Key] >= 0 ? 10000 - itemWeight[item.Key] : 0;
//每次抽中物品,都要根据计数结果重新计算概率
switch (item.Key)
{
case ItemType.SRWeapon:
itemWeight[ItemType.SRRole] = leftWeight;
break;
case ItemType.SRRole:
itemWeight[ItemType.SRWeapon] = leftWeight;
break;
case ItemType.SSRRole:
itemWeight[ItemType.SSRWeapon] = leftWeight;
break;
case ItemType.SSRWeapon:
itemWeight[ItemType.SSRRole] = leftWeight;
break;
}
}
}
}
}
}
- 测试
模拟限定池
public static void Test1()
{
List<int> SRCountList = new List<int>();
List<int> SSRCountList = new List<int>();
//基础出率: R(94.3%) SR(5.1%) SSR(0.6%)
LevelDecision LD = new LevelDecision(new LevelBuilder()
.SetRWeight(9430).SetSRWeight(510).SetSSRWeight(60)
.SetAddWeight(LevelType.SR, 5100).SetAddWeight(LevelType.SSR, 600)
.SetSRAddRange(9).SetSSRAddRange(74));
ItemTypeDecision ITD = new ItemTypeDecision(new ItemTypeDecisionBuilder()
.SetSRRoleWeight(5000)
.SetSSRRoleWeight(5000, true));
int i = 0;
while (i++ < 500000)
{
//决定星级 ---> 决定物品类型
(LevelType level, int count) = LD.Decide();
ItemType itemType = ITD.Decide(level, count);
// Console.WriteLine(itemType+" "+count);
if (level == LevelType.SR)
SRCountList.Add(count);
else if (level == LevelType.SSR)
SSRCountList.Add(count);
}
Console.WriteLine("四星平均抽数" + CountAverage(SRCountList));
Console.WriteLine("五星平均抽数" + CountAverage(SSRCountList));
}
//综合出率为平均抽出该星级所需抽数的倒数.
//四星综合出率为13%,5星为1.6%。与计算结果相近.
四星平均抽数7.6416225
五星平均抽数62.26865
模拟常驻角色池
public static void Test2()
{
LevelDecision LD = new LevelDecision(new LevelBuilder()
.SetRWeight(9430).SetSRWeight(510).SetSSRWeight(60)
.SetAddWeight(LevelType.SR, 5100).SetAddWeight(LevelType.SSR, 600)
.SetSRAddRange(9).SetSSRAddRange(74));
//模拟常驻池
ItemTypeDecision ITD_Changzhu = new ItemTypeDecision(new ItemTypeDecisionBuilder()
.SetSRRoleWeight(10000).SetSSRRoleWeight(10000, false) //假设4,5星角色的概率为100%,那么抽出武器一定是平稳机制导致的
.SetSmooth(ItemType.SRWeapon, 18, 1250).SetSmooth(ItemType.SRRole, 18, 1250)
.SetSmooth(ItemType.SSRRole, 147, 151).SetSmooth(ItemType.SSRWeapon, 147, 151));
int i = 0;
int preSR = 0, curSR = 0, preSSR = 0, curSSR = 0;
int cntSR = 0, cntSSR = 0;
while (i++ < 500000)
{
(LevelType level, int count) = LD.Decide();
ItemType itemType = ITD_Changzhu.Decide_Changzhu(level, count);
// Console.WriteLine(itemType+" "+count);
if (itemType == ItemType.SRWeapon)
{
curSR = i;
cntSR = Math.Max(cntSR, (curSR - preSR));
preSR = i;
}
if (itemType == ItemType.SSRWeapon)
{
curSSR = i;
cntSSR = Math.Max(cntSSR, (curSSR - preSSR));
preSSR = i;
}
}
Console.WriteLine($"最多{cntSR}发没有四星武器");
Console.WriteLine($"最多{cntSSR}发没有五星武器");
}
最多25发没有四星武器
最多184发没有五星武器
下一篇中我将结合ET写一个抽卡功能的demo。