wordTile算法

胡牌

  /// <summary>
    /// 通用型字牌胡牌算法 by:黄敏
    /// </summary>
    public class huPaiSuanFa
    {


        /* 通用型字牌胡牌算法 解释文档 by:黄敏 2017/8/20
         一:流程
         假设 手牌为 2 7 10 2 7 10 贰 柒 拾 
         (这个手牌是已经将目标牌加入其中了,并且已经将手牌中3张以上的牌全部移除到明牌了
         因为各地字牌游戏的胡息,胡牌规则不同,所以必须将它们剔除,本算法仅仅计算各种允许的吃牌规则后产生的吃牌的坎子。也是最难的部分)
         
         首先是检查数量合法性,必须是3的倍数或者3的倍数+2才能胡牌。
         1.1:首先将所有手牌可能出现的 有效组合(手牌能满足的) 保存下来(我们叫做 手牌的组合字典 把)。如2-有 22贰 和 2710 以及 22(将牌) 三种组合
         1.2:根据他们各自组合的数量,选择最少的一个,这里是 贰 或者 柒 拾 ,我们选择 贰 吧
         1.3: 贰有2种组合-一种是 22贰和贰 柒 拾,我们遍历它们。当然,我们先实行的还是22贰-等它结束再实行贰 柒 拾,这是第一层
         1.4: 我们将 22贰 这个组合 保存起来并准备往下一层传递(如果有的话),然后从手牌列表(复制的,要保证第一层的参数不被修改)中删除
         那么我们的手牌变成了 7 10 7 10 柒 拾。
         1.5:OK,下面我们检查手牌,发现手牌还有6张牌,那么再次往下一层传递。这是 1-1层(第一个1代表第一层的第1个组合选择,第2个1代表第2层的第一个组合选择)
         1.6:然后我们来到了1-1层,再次生成 手牌的组合字典,那么这个时候 7 只有1种组合了。其他的也只有一种组合了!
         这样,我们就可以将他们加入 上一层传递下来的组合(22贰),形成一个列表,将其输出或者保存到某地,这样第2层结束了。那么我们就会返回第一层。
         1.7:回到第一层,因为第一种组合我们已经结束,那么就是第2种组合了。这一次就是 2-0层了
         1.8:…………按照以上套路走一遍就可以了。写说明好麻烦,那么我就懒得写了…………

          1-0层 -》1-1层 -》1-1-1层 -》1-1-2层 -》1-2-0层 -》1-2-1层 -》1-2-2层 -》2-0层………………就是按照这个方式递归的。有点复杂,最好是按照我上面的文档走一遍会帮助理解
        
        */

        #region @@@@@@@字牌胡牌算法接口
        /// <summary>
        /// 获得所有胡牌的组合列表,尚未做胡息计算,目标牌已经加入手牌或者明牌,需调用前手动定义
        /// </summary>
        /// <param name="handTileList">已经加入目标牌的手牌列表</param>
        /// <param name="outVar">输出的所有胡牌组合的列表</param>
        /// <returns></returns>
        public bool GetAllHuPaiMeldList(List<PaoHuZi> handTileList, out List<List<List<PaoHuZi>>> outVar)
        {

            #region ***********数据准备
            //胡牌的序列,仅手牌的序列
            outVar = new List<List<List<PaoHuZi>>>();
            //先复制一次,避免不小心修改到原始数据
            List<PaoHuZi> handTileList_Temp = new List<PaoHuZi>(handTileList);

            #endregion

            #region *************手牌数量合法性检查
            if (handTileList.Count <= 0)//没有牌被输入进来
            {
                log("错误:没有牌被输进来或者全部完结");
                return false;
            }

            //先检查手牌的数量,如果是3的倍数或者3的倍数+2,那么就继续检查,否则返回
            if (handTileList.Count % 3 != 0 && handTileList.Count % 3 != 2)
            {
                log("错误:牌的数量不能组成坎子-》" + handTileList.Count);
                return false;
            }
            #endregion

            #region *********将手牌转为字典格式,方便获取某牌的数量

            //根据手牌列表生成手牌数量的字典
            Dictionary<PaoHuZi, int> handTileCountDic = new Dictionary<PaoHuZi, int>();
            foreach (var i in handTileList_Temp)
            {
                if (handTileCountDic.ContainsKey(i))
                {
                    handTileCountDic[i]++;
                }
                else
                {
                    handTileCountDic.Add(i, 1);
                }
            }

            #endregion


            #region ********极端情况快速返回

            //极端情况,只剩下2张相同的牌了,那么就返回将牌坎子
            if (handTileList_Temp.Count == 2)
            {
                if (handTileList_Temp[0] == handTileList_Temp[1])//这2张牌是一样的牌
                {
                    log("正确,只剩下2张相同牌了-》" + handTileList_Temp[0]);
                    List<PaoHuZi> tileMeld = new List<PaoHuZi>();
                    tileMeld.Add(handTileList_Temp[0]);
                    tileMeld.Add(handTileList_Temp[1]);
                    List<List<PaoHuZi>> tempHuPaiMeldList = new List<List<PaoHuZi>>();
                    tempHuPaiMeldList.Add(tileMeld);
                    outVar.Add(tempHuPaiMeldList);
                    return true;
                }
                else//这2张牌不一样,所以无法构成牌组。
                {
                    log("错误,只剩下2张牌了,但不一样-》" + handTileList_Temp[0] + "  " + handTileList_Temp[1]);
                    outVar.Clear();
                    return false;
                }
            }

            //极端情况,只剩下3张牌了
            if (handTileList_Temp.Count == 3)
            {
                //查找这3张牌是否能够构成坎子牌
                //先得到能够和第一张牌构成坎子的所有另外2张牌 的列表,
                List<PaoHuZi[]> canChowList = GetChowRule(handTileList_Temp[0]);

                bool canChow = false;
                //检查手牌中的牌是否符合中间的某一规则
                foreach (var i in canChowList)
                {
                    int needTileCount = 1;
                    if (i[0] == i[1])//如果这2张牌是一样的那么就需要牌组里面有2张牌
                    {
                        needTileCount = 2;
                    }
                    if (handTileCountDic.ContainsKey(i[0]) && handTileCountDic[i[0]] >= needTileCount
                        && handTileCountDic.ContainsKey(i[1]) && handTileCountDic[i[1]] >= needTileCount)//如果手牌中的牌数量和类型都符合,那么,他就能构成一坎牌
                    {

                        List<PaoHuZi> tileMeld = new List<PaoHuZi>();
                        tileMeld.Add(handTileList_Temp[0]);
                        tileMeld.Add(handTileList_Temp[1]);
                        tileMeld.Add(handTileList_Temp[2]);
                        List<List<PaoHuZi>> tempHuPaiMeldList = new List<List<PaoHuZi>>();
                        tempHuPaiMeldList.Add(tileMeld);
                        outVar.Add(tempHuPaiMeldList);
                        canChow = true;
                    }
                }
                //符合的话,就返回true
                if (canChow)
                {
                    log("正确,只剩下3张牌了,并且能吃牌");
                    return true;
                }
                else
                {
                    log("错误,只剩下3张牌了,不吃牌");
                    outVar.Clear();
                    return false;
                }


            }
            #endregion


            #region ****检查普通情况

            //开始排序剩下的手牌
            if (handTileList_Temp.Count > 3)
            {

                #region ------生成字典,储存每一张手牌能够吃牌的组合,如果拥有为0的手牌,那么就立刻返回

                //生成一个字典,用来储存每一张手牌能够吃牌的组合
                Dictionary<PaoHuZi, List<PaoHuZi[]>> allTileCanChowDic = new Dictionary<PaoHuZi, List<PaoHuZi[]>>();
                //获得每一张牌能够吃牌的组合
                foreach (PaoHuZi i in handTileList_Temp)
                {
                    if (!allTileCanChowDic.ContainsKey(i))
                    {//生成一个新的手牌列表,并且将要计算的目标牌从中移除
                        List<PaoHuZi> newHandTileList_Temp = new List<PaoHuZi>(handTileList_Temp);
                        newHandTileList_Temp.Remove(i);
                        List<PaoHuZi[]> allChowMeldForTarget = GetCanChowMeldOnHandTile(i, newHandTileList_Temp);
                        allTileCanChowDic.Add(i, allChowMeldForTarget);

                    }

                }

                //找找看有没有组合数为0的牌
                int zeroCount = allTileCanChowDic.Where(x => x.Value.Count <= 0).Count();

                //如果有就返回fasl,证明无法构成胡牌列表
                if (zeroCount >= 1)
                {

                    return false;
                }

                #endregion

                #region --------开始调用递归函数进行排列

                selectMeld(new List<List<PaoHuZi>>(), outVar, handTileList_Temp);

                #endregion

                #region ---获得排列的方法后,如果发现没有一个方法,那么就代表失败
                if (outVar.Count >= 1)
                {
                    return true;
                }
                #endregion
            }

            #endregion


            return false;
        }
        #endregion

        #region @@@@@需要手动改写的辅助方法

        /// <summary>
        /// 获得根据吃牌规则,获得能与目标牌构成坎子的另外2张牌的列表-可以根据种类不同进行修改
        /// </summary>
        /// <param name="targetIndex"></param>
        /// <returns></returns>
        private List<PaoHuZi[]> GetChowRule(PaoHuZi targetTile)
        {//定义了哪些牌可以和目标牌构成 坎子。也就是吃牌的规则
            //有规律的好定义。
            //没有规律的参见最下面的 2 7 10来定义
            //所有定义的规则,不需要考虑 另外2张牌是否合法,因为合法性检查会在后面做
            List<PaoHuZi[]> returnVar = new List<PaoHuZi[]>();

            //将牌
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[1];
                chowRuleElement[0] = targetTile;
                returnVar.Add(chowRuleElement);


            }

            //如果-2 ,-1连续规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile - 2, targetTile - 1, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果-1 ,+1连续规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile + 1, targetTile - 1, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果+1 ,+2连续规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile + 1, targetTile + 2, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果+10 ,+10规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile + 10, targetTile + 10, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果+0 ,+10规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile, targetTile + 10, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果+0 ,-10规则可用
            if (true)//如果+0 ,-10规则可用
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile, targetTile - 10, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //如果-10 ,-10规则可用
            if (true)
            {
                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                if (GetChowRuleElement(targetTile - 10, targetTile - 10, out chowRuleElement))
                {
                    returnVar.Add(chowRuleElement);
                }

            }

            //特殊规则 2 7 10
            if (true)
            {


                PaoHuZi[] chowRuleElement = new PaoHuZi[2];
                switch (targetTile)
                {

                    case (PaoHuZi)2:

                        if (GetChowRuleElement((PaoHuZi)7, (PaoHuZi)10, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;
                    case (PaoHuZi)7:

                        if (GetChowRuleElement((PaoHuZi)2, (PaoHuZi)10, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;
                    case (PaoHuZi)10:

                        if (GetChowRuleElement((PaoHuZi)2, (PaoHuZi)7, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;
                    case (PaoHuZi)12:

                        if (GetChowRuleElement((PaoHuZi)17, (PaoHuZi)20, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;
                    case (PaoHuZi)17:

                        if (GetChowRuleElement((PaoHuZi)12, (PaoHuZi)20, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;
                    case (PaoHuZi)20:

                        if (GetChowRuleElement((PaoHuZi)12, (PaoHuZi)17, out chowRuleElement))
                        {
                            returnVar.Add(chowRuleElement);
                        };
                        break;



                }

            }

            return returnVar;

        }

        /// <summary>
        /// 本类的Log输出,记得改写
        /// </summary>
        /// <param name="logstring"></param>
        private void log(string logstring)
        {
            Console.WriteLine(logstring);
        }


        #endregion

        #region @@@@@@@@@@@@@@@字牌胡牌算法辅助方法

        /// <summary>
        /// 根据手牌选择能够组成胡牌 的组合。未做胡息判断,需提前移除3张以上相同牌再输入手牌
        /// </summary>
        /// <param name="allMeldCount">需要提取的坎子数量</param>
        /// <param name="alreadySelectMeldList">递归中使用</param>
        /// <param name="allMeldList">要输出的列表</param>
        /// <param name="handTile">手牌,已经移除3张以上相同牌</param>
        /// <param name="allTileCanChowDic">之前生成的吃牌组合</param>
        private void selectMeld(List<List<PaoHuZi>> alreadySelectMeldList,List< List<List<PaoHuZi>>> allMeldList,List<PaoHuZi>handTile)
        {
            #region ****数据准备-复制手牌列表并排序
            //复制手牌列表,避免修改到原始数据
            List<PaoHuZi> handTileTemp = new List<PaoHuZi>(handTile);
            handTileTemp.Sort();
            #endregion

           

            #region ******根据现有的手牌列表 获得 每张牌组合 的字典

            //生成一个字典,用来储存每一张手牌能够吃牌的组合
            Dictionary<PaoHuZi, List<PaoHuZi[]>> allTileCanChowDic = new Dictionary<PaoHuZi, List<PaoHuZi[]>>();
            //获得每一张牌能够吃牌的组合
            foreach (PaoHuZi i in handTileTemp)
            {
                if (!allTileCanChowDic.ContainsKey(i))
                {//生成一个新的手牌列表,并且将要计算的目标牌从中移除
                    List<PaoHuZi> newHandTileList_Temp = new List<PaoHuZi>(handTileTemp);
                    newHandTileList_Temp.Remove(i);
                    List<PaoHuZi[]> allChowMeldForTarget = GetCanChowMeldOnHandTile(i, newHandTileList_Temp);
                    allTileCanChowDic.Add(i, allChowMeldForTarget);

                }

            }

            #endregion

           
            #region *******选择一张牌作为目标牌,从它的组合开始计算,可以有效的减少计算量

            PaoHuZi targetTile = PaoHuZi.EPH_None;

            //查找最少组合的数量
            int minCount = allTileCanChowDic.Min(x => x.Value.Count);
            
            //如果最少组合的数量为0,代表有手牌不能组合,那么就返回
            if (minCount == 0)
            {
                return;
            }

            //找到最少组合的手牌,并以它为目标牌开始计算组合
            List<PaoHuZi> minCoutTileList= allTileCanChowDic.Where(x => x.Value.Count == minCount).Select(x => x.Key).ToList();
            if (minCoutTileList.Count >= 1)
            {
                targetTile = minCoutTileList[0];
            }
            else
            {
                return;
            }


            #endregion


            #region ******遍历目标牌的组合的可能

            //遍历目标牌的各种组合方式
            foreach (var targetTileMaybeGroup in allTileCanChowDic[targetTile])
            {
                #region  ————检查是否有将牌坎子-根据手牌的数量,并决定将牌坎子是否需要遍历

                bool needJiang = handTileTemp.Count % 3 == 2 ? true : false;
                
                //如果后面不需要将牌了,这个组合的可能就废弃,不进行计算
                if (targetTileMaybeGroup.Length == 1 && !needJiang)
                {

                    continue;
                }

                #endregion

                //如果手牌能满足这个 吃牌方式 的需求,那么就形成一个坎子
                if (AllElementHas(handTileTemp, targetTile, targetTileMaybeGroup))
                {
                    #region -----将这个 坎子 加入到复制的 坎子列表
                    //将这个 坎子 加入 已经选择的坎子列表中
                    List<PaoHuZi> targetTileMeld = targetTileMaybeGroup.ToList();
                    targetTileMeld.Add(targetTile);

                    //复制一份 已经复制的坎子列表,并将这一轮选择的坎子加入它
                    //为何要复制一份呢?就是不要修改这一层级 递归函数的 参数,当函数返回这一层及的时候,它还是输入的状态,避免下层修改
                    //如果不复制,当下层返回的时候,因为做了修改,那么这一层的 这个列表就会错误,会将下层的数据也带入这一层来计算
                    List<List<PaoHuZi>> alreadySelectMeldList_New = new List<List<PaoHuZi>>();
                    foreach (var alreadySelectMeldListElement in alreadySelectMeldList)
                    {
                        List<PaoHuZi> newElement = new List<PaoHuZi>(alreadySelectMeldListElement);
                        alreadySelectMeldList_New.Add(newElement);
                    }
                    alreadySelectMeldList_New.Add(targetTileMeld);
                    #endregion

                    #region -----复制手牌 ,并从中删除这一轮选择的坎子 组成的牌
                    //复制一份手牌,并从 复制的手牌中 移除这轮选择的坎子的所有牌
                    List<PaoHuZi> handTileTemp_New = new List<PaoHuZi>(handTileTemp);
                    foreach (var targetTileMeld_Element in targetTileMeld)
                    {
                        handTileTemp_New.Remove(targetTileMeld_Element);
                    }
                    #endregion

                    #region ---------检查手牌的数量,如果全部完毕了,那么就返回选择的列表
                    //如果全部的手牌都放到列表里面了,那么收获的时候到了,
                    if (handTileTemp_New.Count == 0)
                    {
                        if (handTileTemp_New.Count == 0 || (handTileTemp_New.Count == 2 && handTileTemp_New[0] == handTileTemp_New[1]))
                        {
                            //将所有选择的列表 作为一个 胡牌列表,加入待返回的 所有胡牌列表 集合中去。
                            allMeldList.Add(alreadySelectMeldList_New);

                            return;

                        }
                        else
                        {

                        }
                    }
                    #endregion

                    #region ----检查手牌数量,如果还没有完毕,那么就继续递归下去
                    //如果手牌还有,就继续递归下去,但是手牌列表 要是删除已经选择了的牌的 新手牌列表。
                    //而且 已经选择的组合列表 要传递一个新复制的,避免下次尝试修改到 这个列表。等它返回的时候就会出现错误
                    else
                    {

                        selectMeld(alreadySelectMeldList_New, allMeldList, handTileTemp_New);
                        // alreadySelectMeldList = new List<List<PaoHuZi>>();
                    }
                    #endregion
                }

            }

            #endregion


           


        }

        /// <summary>
        /// 判断在手牌中是否包含目标牌和其他的所有牌
        /// </summary>
        /// <param name="handTile"></param>
        /// <param name="tagetTile"></param>
        /// <param name="chowRule"></param>
        /// <returns></returns>
        private bool AllElementHas(List<PaoHuZi> handTile,PaoHuZi tagetTile,PaoHuZi[] chowRule)
        {
            bool returnVar = true;
            List<PaoHuZi> handTileTemp = new List<PaoHuZi>(handTile);
            if (handTileTemp.Contains(tagetTile))
            {
                handTileTemp.Remove(tagetTile);
                
                foreach (var tile in chowRule)
                {
                    if (handTileTemp.Contains(tile))
                    {
                        handTileTemp.Remove(tile);
                    }
                    else
                    {
                        returnVar = false;
                    }
                }
            }
            else
            {
                returnVar = false;
            }
            return returnVar;
        }

        /// <summary>
        /// 根据手牌,确定能够吃的其目标牌的所有组合
        /// </summary>
        /// <param name="targetTile"></param>
        /// <param name="handTileList"></param>
        /// <returns></returns>
        private List<PaoHuZi[]> GetCanChowMeldOnHandTile(PaoHuZi targetTile,List<PaoHuZi> handTileList)
        {
            //避免修改原始数据,先new一个
            List<PaoHuZi> handTileListTemp = new List<PaoHuZi>(handTileList);
            List<PaoHuZi[]> ChowRule = GetChowRule(targetTile);

            for (int i = ChowRule.Count - 1; i >= 0; i--)
            {
                if (ChowRule[i].Length == 1)//将牌不需要验证,只要找到手牌里面有这个牌不
                {
                    if (handTileListTemp.Contains(targetTile))
                    {

                    }
                    else
                    {
                        ChowRule.RemoveAt(i);
                    }
                }
                else//不是将牌
                {
                    
                    int needTileCout = 1;//每种牌需要的数量
                    if (ChowRule[i][0] == ChowRule[i][1])
                    {
                        needTileCout = 2;
                    }
                    //手牌中寻找这2张牌,并知道他们的数量
                    int tile1Cout = handTileListTemp.FindAll(x => x == ChowRule[i][0]).Count;
                    int tile2Cout = handTileListTemp.FindAll(x => x == ChowRule[i][1]).Count;

                    if (tile1Cout >= needTileCout && tile2Cout >= needTileCout)//代表这一组能够吃的起
                    {

                    }
                    else//吃不起就删除这个吃牌组合方式
                    {
                        ChowRule.RemoveAt(i);
                    }
                }
            }

            如果手牌%3==2的情况,这个牌有2张的话(手牌中只需要再有一张即可,因为在进来前已经移除了一张),也可以构成将
            //if (handTileList.Count % 3 == 2&& handTileList.Contains(targetTile))
            //{
            //    PaoHuZi[] temp = new PaoHuZi[2] { targetTile, targetTile };
            //    ChowRule.Add(temp);
            //}

            return ChowRule;

        }

       

        /// <summary>
        /// 获得符合吃牌规则的另外2张牌的牌组,同时判断牌是否合法
        /// </summary>
        /// <param name="chowTile1Index"></param>
        /// <param name="chowTile2Index"></param>
        /// <param name="chowRuleElement"></param>
        /// <returns></returns>
        private bool GetChowRuleElement(PaoHuZi chowTile1Index, PaoHuZi chowTile2Index,out PaoHuZi[] chowRuleElement)
        {
            chowRuleElement = new PaoHuZi[2];
            bool returnVar = false;
            if ((int)chowTile1Index > 0 &&(int) chowTile1Index <=  20 && (int)chowTile2Index > 0 && (int)chowTile2Index <= 20)
            {
                returnVar = true;
                chowRuleElement[0] = chowTile1Index;
                chowRuleElement[1] = chowTile2Index;
            }
            return returnVar;
        }

        #endregion


        #region @@@@@@@@为算法准备的类或枚举
        /// <summary>
        /// 临时为 字牌算法 的跑胡子枚举,只在算法这个类中使用
        /// </summary>
        public enum PaoHuZi
        {
            EPH_None = 0,
            EPH_SmallOne = 1,
            EPH_SmallTwo = 2,
            EPH_SmallThree = 3,
            EPH_SmallFour = 4,
            EPH_SmallFive = 5,
            EPH_SmallSix = 6,
            EPH_SmallSeven = 7,
            EPH_SmallEight = 8,
            EPH_SmallNine = 9,
            EPH_SmallTen = 10,
            EPH_BigOne = 11,
            EPH_BigTwo = 12,
            EPH_BigThree = 13,
            EPH_BigFour = 14,
            EPH_BigFive = 15,
            EPH_BigSix = 16,
            EPH_BigSeven = 17,
            EPH_BigEight = 18,
            EPH_BigNine = 19,
            EPH_BigTen = 20,

        }
        #endregion
    }


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值