简单实现原神抽卡机制

简单实现原神抽卡机制


实现原理与公式可以参考这篇文章: 原神抽卡全机制总结

  1. 星级决定机制
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];
        }
    }
}
  1. 物品种类决定机制
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;
        }
    }
}
  1. 平稳机制
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;
                    }
                }
            }
        }
    }
}
  1. 测试
    模拟限定池
 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。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值