[Unity Mini Game] Sudoku (2) - Sudoku final game generation algorithm

Characteristics of Sudoku

1. Sudoku is divided into three different units: rows, columns and palaces

2. Sudoku has 9 rows, 9 columns and 9 palaces, and each unit has 9 grids.

3. Each unit needs to be filled in with numbers 1-9, and the numbers in the cells cannot be repeated.

Code logic

Here I will first break down and explain the logic of each piece of code, and finally I will post the complete code.

1. Write classes for the cells of the array

Based on the above characteristics, a class needs to be created for each unit. The class needs to contain a dictionary (used to determine whether a certain number has been placed) and an array (recording the position where the number is filled in).

    public class ShuDuGroup
    {
        //字典,用来判断单元里面是否已经放入了某个数字。Key是数字,Value是数字放入的位置
        private Dictionary<int, int> membersDic = new Dictionary<int, int>();
        //数组,用来记录数字填入的位置
        private int[] membersArr = new int[9];

        /// <summary>
        /// 这个单元是否已经填过某个数字
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool Contains(int member)
        {
            return membersDic.ContainsKey(member);
        }

        /// <summary>
        /// 计数,这个单元已填入多少个数字
        /// </summary>
        /// <returns></returns>
        public int GetCount()
        {
            return membersDic.Count;
        }

        /// <summary>
        /// 检查这个单元的指定格子是否已经填入数字
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool AnyNumberFillInIndex(int index)
        {
            return membersArr[index] != 0;
        }

        /// <summary>
        /// 获取这个单元中该数字填入的位置
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public int GetMemberIndex(int member)
        {
            if (membersDic.ContainsKey(member))
            {
                return membersDic[member];
            }
            return -1;
        }

        /// <summary>
        /// 填入数字
        /// </summary>
        /// <param name="indexInZoom"></param>
        /// <param name="number"></param>
        public void AddMember(int indexInZoom, int number)
        {
            //只能加入1-9
            if (number != 1&&
                number != 2&&
                number != 3&&
                number != 4&&
                number != 5&&
                number != 6&&
                number != 7&&
                number != 8&&
                number !=9) return;
            if (membersDic.ContainsKey(number))
            {
                GameLogger.Log("the number was existed ");
            }
            else
            {
                membersDic.Add(number, indexInZoom);
                membersArr[indexInZoom] = number;
            }
        }

        /// <summary>
        /// 移除数字
        /// </summary>
        /// <param name="number"></param>
        public void RemoveMember(int number)
        {
            if (membersDic.ContainsKey(number))
            {
                int index = membersDic[number];
                membersDic.Remove(number);
                membersArr[index] = 0;
            }
        }

        /// <summary>
        /// 清空
        /// </summary>
        public void ClearAll()
        {
            membersDic.Clear();
            for (int i = 0; i < 9; i++)
            {
                membersArr[i] = 0;
            }
        }
    }
}

2. Generate Sudoku final puzzle

To generate Sudoku, the algorithm idea I use is the backtracking method. Start with number 1 and try to fill in each palace. If it can be filled in, then continue to the next palace. If all palaces are filled in, then continue with number 2, and so on; if a certain number cannot be filled in a certain palace, then Return to the previous palace and fill it out again. In order to reduce the number of backtracking and recursion, I considered two points:

1) The relative position of each number in each house is unique

For each number, make sure that they do not repeat in each row and column, and that their position in each palace must be different. As shown in the picture below, if the number 1 is filled in the upper left corner of the first palace, then in the remaining 8 palaces, the number 1 cannot be filled in the upper left corner.

According to this rule, for each number, we mark the position where they can be filled in the palace as 0,1,2,3,4,5,6,7,8 (the sequence to be filled in). Every time we fill in, we start from Take out a position in the sequence to be filled in and try to fill it in. If the filling fails, then put the position number back to the end of the sequence to be filled in. Of course, because our Sudoku needs to be randomly generated, we need to scramble it at the beginning. The order of the sequence to be filled in.

2) Specify the order of filling in the palaces

It is stipulated that the program fills in each palace in the order shown below. This order can allow each number to constrain rows and columns earlier and reduce the number of backtracking.

After considering the two points above, enter the code to generate Sudoku.

Core recursive methods

Use FillNumber to fill in the 0th palace, and recursively fill in the next palace. If the filling of the next palace returns false, then record the position of the current palace into the dictionary of positions that cannot be filled in, put it into the queue, and take out a new one. Fill in the position and recurse to the next palace, and so on.

        /// <summary>
        /// 填入数字,用于递归  回溯算法
        /// </summary>
        /// <param name="zoomOrderIndex"></param>
        /// <param name="indexes"></param>
        /// <param name="number"></param>
        /// <returns></returns>
        private bool FillNumber(int zoomOrderIndex, Queue<int> indexes, int number)
        {
            //如果这个数字已经将所有宫都填写完,那么直接返回true
            if (zoomOrderIndex >= ZoomOrder.Length) return true;
            int cycleCount = 0;//记录循环次数,防止死循环
            //如果没有可填写位置,代表无解,返回false退回上一步
            if (indexes.Count < 1)
            {
                return false;
            }
            //从待填写位置的序列中取出第一个位置
            int indexInZoom = indexes.Dequeue();
            //计算它的行和列
            int zoomRow = ZoomOrder[zoomOrderIndex] / 3;//在这个宫的行号
            int zoomColumn = ZoomOrder[zoomOrderIndex] % 3;//在这个宫的列号
            int r = zoomRow * 3 + indexInZoom / 3;//在整个数独中的行号
            int c = zoomColumn * 3 + indexInZoom % 3;//在整个数独中的列号
            //记录该数字在每个宫中有哪些位置不可填
            //比如说第1个宫我们填在了左上角的位置,运行到下一个宫的时候,发现无解
            //那么说明我们在第1个宫不能填左上角的位置,将这个位置记录到字典中,防止算法又重新填这个位置,陷入死循环
            Dictionary<int, int> indexesNotAllow = Zoom1NotAllow;
            switch (ZoomOrder[zoomOrderIndex])
            {
                case 1:
                    indexesNotAllow = Zoom1NotAllow;
                    break;
                case 7:
                    indexesNotAllow = Zoom7NotAllow;
                    break;
                case 3:
                    indexesNotAllow = Zoom3NotAllow;
                    break;
                case 5:
                    indexesNotAllow = Zoom5NotAllow;
                    break;
                case 0:
                    indexesNotAllow = Zoom0NotAllow;
                    break;
                case 2:
                    indexesNotAllow = Zoom2NotAllow;
                    break;
                case 6:
                    indexesNotAllow = Zoom6NotAllow;
                    break;
                case 8:
                    indexesNotAllow = Zoom8NotAllow;
                    break;
            }
            //若取出的位置不能填写,那么放回队列,重新取出下一个位置,循环
            while (Zooms[ZoomOrder[zoomOrderIndex]].AnyNumberFillInIndex(indexInZoom) || Rows[r].Contains(number) || Columns[c].Contains(number) || indexesNotAllow.ContainsKey(indexInZoom))
            {
                //将这个位置记录到不允许填写的字典中
                if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
                cycleCount++;//记录循环次数
                if (cycleCount > indexes.Count)//如果整个队列都循环过,代表无解,该回溯到上一个宫
                {
                    //放回对列
                    indexes.Enqueue(indexInZoom);
                    //清空字典
                    indexesNotAllow.Clear();
                    //返回false回溯上一个宫
                    return false;
                }
                //放回队列并取出下一个位置
                indexes.Enqueue(indexInZoom);
                indexInZoom = indexes.Dequeue();
                //计算它的行列号
                zoomRow = ZoomOrder[zoomOrderIndex] / 3;
                zoomColumn = ZoomOrder[zoomOrderIndex] % 3;
                r = zoomRow * 3 + indexInZoom / 3;
                c = zoomColumn * 3 + indexInZoom % 3;
            }
            //重置循环次数
            cycleCount = 0;
            //更新行、列和宫的信息
            Zooms[ZoomOrder[zoomOrderIndex]].AddMember(indexInZoom, number);
            Rows[r].AddMember(c, number);
            Columns[c].AddMember(r, number);
            //将数字填入位置,这里的GetCellIndex方法是用宫编号、在宫中的行列号计算出这个格子在整个数独中的下标号
            ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = number;
            //进入递归,填写下一个宫
            bool nextOK = FillNumber(zoomOrderIndex + 1, indexes, number);
            //检查下一个宫是否能填写,返回true,代表下一个宫也没问题,那么返回true
            //如果返回false,代表下一个宫无解,那么这个宫不能填这个位置
            if (nextOK)
            {
                indexesNotAllow.Clear();
                return true;
            }
            else
            {
                //将这个位置加到不能填写的位置的字典中
                if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
                //删除它在对应行、列、宫中的信息
                Zooms[ZoomOrder[zoomOrderIndex]].RemoveMember(number);
                Rows[r].RemoveMember(number);
                Columns[c].RemoveMember(number);
                //清除数独这个格子填写的数字
                ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = 0;
                //将这个位置重新入队列
                indexes.Enqueue(indexInZoom);
                //重新填写这个宫,也是递归
                bool refillOK = FillNumber(zoomOrderIndex, indexes, number);
                indexesNotAllow.Clear();//只要返回了上一步,都需要清空字典。
                return refillOK;
            }
        }

Generate Sudoku method

First randomly fill in the numbers 1-9 into the most central palace, and then fill in the remaining 8 palaces in order from numbers 1 to 9 (in the order specified above). The conflict will be resolved backtracking in the FillNumber method. If FillNumber eventually returns false, it means that this number cannot be filled in all palaces, and it is necessary to backtrack the filling of the previous number. I don’t want to make a particularly complicated logic here, so if you need to backtrack If you reach the previous number, just overturn the entire Sudoku and start over again.

        /// <summary>
        /// 生成数独,结果写入ShuDuMap
        /// </summary>
        //[Server]
        public void GenerateMap()
        {
            isGenerated = false;
            //初始化
            for (int i = 0; i < 9; i++)
            {
                Zooms[i] = new ShuDuGroup();
                Rows[i] = new ShuDuGroup();
                Columns[i] = new ShuDuGroup();
            }

            //这里是打乱数字0-8的顺序
            int[] pickItms = RandomTools.RandomFromGroup(9, 9);

            //先将数字1-9随机地填写入最中心的宫
            //这里因为pickItms是随机过的,直接按pickItms的顺序填写达到随机目的
            for (int i = 0; i < pickItms.Length; i++)
            {
                int number = pickItms[i] + 1;
                Zooms[4].AddMember(i, number);
                int r = 3 + i / 3;
                int c = 3 + i % 3;
                Rows[r].AddMember(i, number);
                Columns[c].AddMember(i, number);

                ShuDuMap[GetCellIndex(4, r - 3, c - 3)] = number;
            }

            //按数字1到数字9的顺序填入数独
            for (int numberOperate = 1; numberOperate <= 9; numberOperate++)
            {
                //打乱0-8的顺序,这里的0-8代表每个数字在宫里可以填的相对位置,
                //从左到右从上到下,0是左上角,8是右下角
                int[] indexRandom = RandomTools.RandomFromGroup(9, 9);
                //剔除已经在中心宫填过的位置
                int indexInMiddleZoom = Zooms[4].GetMemberIndex(numberOperate);
                //将剩下的位置入队列
                Queue<int> indexNotUsed = new Queue<int>();
                for (int i = 0; i < indexRandom.Length; i++)
                {
                    if (indexRandom[i] != indexInMiddleZoom)
                    {
                        indexNotUsed.Enqueue(indexRandom[i]);
                    }
                }

                //调用递归方法FillNumer
                bool ok = FillNumber(0, indexNotUsed, numberOperate);
                //清空这些字典
                Zoom1NotAllow.Clear();
                Zoom7NotAllow.Clear();
                Zoom3NotAllow.Clear();
                Zoom5NotAllow.Clear();
                Zoom0NotAllow.Clear();
                Zoom2NotAllow.Clear();
                Zoom6NotAllow.Clear();
                Zoom8NotAllow.Clear();
                //在FillNumber中的回溯代表的是宫无法填写,退回上一个宫
                //在这里是整个方法返回false,代表某个数字无解,需要退回上一个数字
                //为了偷懒不再写一个十分复杂的逻辑,这里如果需要退回上一个数字,那么就整个数独推翻重来
                if (!ok)
                {
                    GenerateMap();
                    return;
                }
            }

            //输出结果
            string stringDebug = "";
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    stringDebug = stringDebug + ShuDuMap[i * 9 + j] + " ";
                }
                stringDebug = stringDebug + "\n";
            }
            GameLogger.Log(stringDebug);
            isGenerated = true;//标记位
        }

3. Complete code

using System.Collections.Generic;
using UnityEngine;


namespace Logic
{
    /// <summary>
    /// 数独终盘生成算法
    /// </summary>
    public class ShuDuAnswerGenerator
    {
        //单例
        private static ShuDuAnswerGenerator s_instance;
        public static ShuDuAnswerGenerator instance
        {
            get
            {
                if (null == s_instance)
                    s_instance = new ShuDuAnswerGenerator();
                return s_instance;
            }
        }

        private bool isGenerated = false; //标识位,用来标注数独是否已生成
        private int[] ShuDuMap = new int[81];//数独的结果
        private ShuDuGroup[] Zooms = new ShuDuGroup[9];//九个宫
        private ShuDuGroup[] Rows = new ShuDuGroup[9];//九行
        private ShuDuGroup[] Columns = new ShuDuGroup[9];//九列
        private int[] ZoomOrder = new int[] { 1, 7, 3, 5, 0, 2, 6, 8 };//填写Zoom的顺序


        //避免死循环,字典用来记录这个宫不允许填入某些位置 key会放入indexInZoom
        Dictionary<int, int> Zoom1NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom7NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom3NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom5NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom0NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom2NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom6NotAllow = new Dictionary<int, int>();
        Dictionary<int, int> Zoom8NotAllow = new Dictionary<int, int>();

        /// <summary>
        /// 查询数独这个位置的数
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public int GetNumberOfIndex(int index)
        {
            if (!isGenerated) GenerateMap();
            return ShuDuMap[index];
        }

        /// <summary>
        /// 生成数独,结果写入ShuDuMap
        /// </summary>
        //[Server]
        public void GenerateMap()
        {
            isGenerated = false;
            //初始化
            for (int i = 0; i < 9; i++)
            {
                Zooms[i] = new ShuDuGroup();
                Rows[i] = new ShuDuGroup();
                Columns[i] = new ShuDuGroup();
            }

            //这里是打乱数字0-8的顺序
            int[] pickItms = RandomTools.RandomFromGroup(9, 9);

            //先将数字1-9随机地填写入最中心的宫
            //这里因为pickItms是随机过的,直接按pickItms的顺序填写达到随机目的
            for (int i = 0; i < pickItms.Length; i++)
            {
                int number = pickItms[i] + 1;
                Zooms[4].AddMember(i, number);
                int r = 3 + i / 3;
                int c = 3 + i % 3;
                Rows[r].AddMember(i, number);
                Columns[c].AddMember(i, number);

                ShuDuMap[GetCellIndex(4, r - 3, c - 3)] = number;
            }

            //按数字1到数字9的顺序填入数独
            for (int numberOperate = 1; numberOperate <= 9; numberOperate++)
            {
                //打乱0-8的顺序,这里的0-8代表每个数字在宫里可以填的相对位置,
                //从左到右从上到下,0是左上角,8是右下角
                int[] indexRandom = RandomTools.RandomFromGroup(9, 9);
                //剔除已经在中心宫填过的位置
                int indexInMiddleZoom = Zooms[4].GetMemberIndex(numberOperate);
                //将剩下的位置入队列
                Queue<int> indexNotUsed = new Queue<int>();
                for (int i = 0; i < indexRandom.Length; i++)
                {
                    if (indexRandom[i] != indexInMiddleZoom)
                    {
                        indexNotUsed.Enqueue(indexRandom[i]);
                    }
                }

                //调用递归方法FillNumer
                bool ok = FillNumber(0, indexNotUsed, numberOperate);
                //清空这些字典
                Zoom1NotAllow.Clear();
                Zoom7NotAllow.Clear();
                Zoom3NotAllow.Clear();
                Zoom5NotAllow.Clear();
                Zoom0NotAllow.Clear();
                Zoom2NotAllow.Clear();
                Zoom6NotAllow.Clear();
                Zoom8NotAllow.Clear();
                //在FillNumber中的回溯代表的是宫无法填写,退回上一个宫
                //在这里是整个方法返回false,代表某个数字无解,需要退回上一个数字
                //为了偷懒不再写一个十分复杂的逻辑,这里如果需要退回上一个数字,那么就整个数独推翻重来
                if (!ok)
                {
                    GenerateMap();
                    return;
                }
            }

            //输出结果
            string stringDebug = "";
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    stringDebug = stringDebug + ShuDuMap[i * 9 + j] + " ";
                }
                stringDebug = stringDebug + "\n";
            }
            GameLogger.Log(stringDebug);
            isGenerated = true;//标记位
        }

        /// <summary>
        /// 填入数字,用于递归  回溯算法
        /// </summary>
        /// <param name="zoomOrderIndex"></param>
        /// <param name="indexes"></param>
        /// <param name="number"></param>
        /// <returns></returns>
        private bool FillNumber(int zoomOrderIndex, Queue<int> indexes, int number)
        {
            //如果这个数字已经将所有宫都填写完,那么直接返回true
            if (zoomOrderIndex >= ZoomOrder.Length) return true;
            int cycleCount = 0;//记录循环次数,防止死循环
            //如果没有可填写位置,代表无解,返回false退回上一步
            if (indexes.Count < 1)
            {
                return false;
            }
            //从待填写位置的序列中取出第一个位置
            int indexInZoom = indexes.Dequeue();
            //计算它的行和列
            int zoomRow = ZoomOrder[zoomOrderIndex] / 3;//在这个宫的行号
            int zoomColumn = ZoomOrder[zoomOrderIndex] % 3;//在这个宫的列号
            int r = zoomRow * 3 + indexInZoom / 3;//在整个数独中的行号
            int c = zoomColumn * 3 + indexInZoom % 3;//在整个数独中的列号
            //记录该数字在每个宫中有哪些位置不可填
            //比如说第1个宫我们填在了左上角的位置,运行到下一个宫的时候,发现无解
            //那么说明我们在第1个宫不能填左上角的位置,将这个位置记录到字典中,防止算法又重新填这个位置,陷入死循环
            Dictionary<int, int> indexesNotAllow = Zoom1NotAllow;
            switch (ZoomOrder[zoomOrderIndex])
            {
                case 1:
                    indexesNotAllow = Zoom1NotAllow;
                    break;
                case 7:
                    indexesNotAllow = Zoom7NotAllow;
                    break;
                case 3:
                    indexesNotAllow = Zoom3NotAllow;
                    break;
                case 5:
                    indexesNotAllow = Zoom5NotAllow;
                    break;
                case 0:
                    indexesNotAllow = Zoom0NotAllow;
                    break;
                case 2:
                    indexesNotAllow = Zoom2NotAllow;
                    break;
                case 6:
                    indexesNotAllow = Zoom6NotAllow;
                    break;
                case 8:
                    indexesNotAllow = Zoom8NotAllow;
                    break;
            }
            //若取出的位置不能填写,那么放回队列,重新取出下一个位置,循环
            while (Zooms[ZoomOrder[zoomOrderIndex]].AnyNumberFillInIndex(indexInZoom) || Rows[r].Contains(number) || Columns[c].Contains(number) || indexesNotAllow.ContainsKey(indexInZoom))
            {
                //将这个位置记录到不允许填写的字典中
                if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
                cycleCount++;//记录循环次数
                if (cycleCount > indexes.Count)//如果整个队列都循环过,代表无解,该回溯到上一个宫
                {
                    //放回对列
                    indexes.Enqueue(indexInZoom);
                    //清空字典
                    indexesNotAllow.Clear();
                    //返回false回溯上一个宫
                    return false;
                }
                //放回队列并取出下一个位置
                indexes.Enqueue(indexInZoom);
                indexInZoom = indexes.Dequeue();
                //计算它的行列号
                zoomRow = ZoomOrder[zoomOrderIndex] / 3;
                zoomColumn = ZoomOrder[zoomOrderIndex] % 3;
                r = zoomRow * 3 + indexInZoom / 3;
                c = zoomColumn * 3 + indexInZoom % 3;
            }
            //重置循环次数
            cycleCount = 0;
            //更新行、列和宫的信息
            Zooms[ZoomOrder[zoomOrderIndex]].AddMember(indexInZoom, number);
            Rows[r].AddMember(c, number);
            Columns[c].AddMember(r, number);
            //将数字填入位置,这里的GetCellIndex方法是用宫编号、在宫中的行列号计算出这个格子在整个数独中的下标号
            ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = number;
            //进入递归,填写下一个宫
            bool nextOK = FillNumber(zoomOrderIndex + 1, indexes, number);
            //检查下一个宫是否能填写,返回true,代表下一个宫也没问题,那么返回true
            //如果返回false,代表下一个宫无解,那么这个宫不能填这个位置
            if (nextOK)
            {
                indexesNotAllow.Clear();
                return true;
            }
            else
            {
                //将这个位置加到不能填写的位置的字典中
                if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
                //删除它在对应行、列、宫中的信息
                Zooms[ZoomOrder[zoomOrderIndex]].RemoveMember(number);
                Rows[r].RemoveMember(number);
                Columns[c].RemoveMember(number);
                //清除数独这个格子填写的数字
                ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = 0;
                //将这个位置重新入队列
                indexes.Enqueue(indexInZoom);
                //重新填写这个宫,也是递归
                bool refillOK = FillNumber(zoomOrderIndex, indexes, number);
                indexesNotAllow.Clear();//只要返回了上一步,都需要清空字典。
                return refillOK;
            }
        }

        /// <summary>
        /// 用它所在的区域编号和在当前区域的行列号计算出在整个数独的编号,输入及输出的下标都从0开始
        /// </summary>
        /// <returns></returns>
        private int GetCellIndex(int zoomIndex, int rowInZoom, int columnInZoom)
        {
            int globalIndex;
            int zoomRow = zoomIndex / 3;//这个区域排在9个区域的第几行
            int zoomColumn = zoomIndex % 3;//这个区域排在9个区域的第几列
            globalIndex = (rowInZoom + zoomRow * 3) * 9 + (columnInZoom + zoomColumn * 3);
            return globalIndex;
        }

    }

    /// <summary>
    /// 一行、一列或是一个9宫格区域
    /// </summary>
    public class ShuDuGroup
    {
        //字典,用来判断单元里面是否已经放入了某个数字
        private Dictionary<int, int> membersDic = new Dictionary<int, int>();
        //数组,用来记录数字填入的位置
        private int[] membersArr = new int[9];

        /// <summary>
        /// 这个单元是否已经填过某个数字
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool Contains(int member)
        {
            return membersDic.ContainsKey(member);
        }

        /// <summary>
        /// 计数,这个单元已填入多少个数字
        /// </summary>
        /// <returns></returns>
        public int GetCount()
        {
            return membersDic.Count;
        }

        /// <summary>
        /// 检查这个单元的指定格子是否已经填入数字
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool AnyNumberFillInIndex(int index)
        {
            return membersArr[index] != 0;
        }

        /// <summary>
        /// 获取这个单元中该数字填入的位置
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public int GetMemberIndex(int member)
        {
            if (membersDic.ContainsKey(member))
            {
                return membersDic[member];
            }
            return -1;
        }

        /// <summary>
        /// 填入数字
        /// </summary>
        /// <param name="indexInZoom"></param>
        /// <param name="number"></param>
        public void AddMember(int indexInZoom, int number)
        {
            //只能加入1-9
            if (number != 1&&
                number != 2&&
                number != 3&&
                number != 4&&
                number != 5&&
                number != 6&&
                number != 7&&
                number != 8&&
                number !=9) return;
            if (membersDic.ContainsKey(number))
            {
                GameLogger.Log("the number was existed ");
            }
            else
            {
                membersDic.Add(number, indexInZoom);
                membersArr[indexInZoom] = number;
            }
        }

        /// <summary>
        /// 移除数字
        /// </summary>
        /// <param name="number"></param>
        public void RemoveMember(int number)
        {
            if (membersDic.ContainsKey(number))
            {
                int index = membersDic[number];
                membersDic.Remove(number);
                membersArr[index] = 0;
            }
        }

        /// <summary>
        /// 清空
        /// </summary>
        public void ClearAll()
        {
            membersDic.Clear();
            for (int i = 0; i < 9; i++)
            {
                membersArr[i] = 0;
            }
        }
    }
}

public class RandomTools
{
    /// <summary>
    /// 从totalAmount中抽取pickItemCount个不重复项目,返回下标值
    /// </summary>
    /// <param name="totalAmount"></param>
    /// <param name="pickItemCount"></param>
    /// <returns></returns>
    public static int[] RandomFromGroup(int totalAmount, int pickItemCount)
    {
        int[] totalArr = new int[totalAmount];
        for (int i = 0; i < totalAmount; i++)
        {
            totalArr[i] = i;//令每个元素等于下标
        }
        int[] pickIndex = new int[pickItemCount];//抽取出来的序号的数组
        for (int j = 0; j < pickItemCount; j++)//抽取j次
        {
            //从未抽取的部分随机抽取一项
            int ranIndex = Mathf.FloorToInt(Random.value * (float)(totalAmount - j));
            if (ranIndex == (totalAmount - j)) ranIndex = totalAmount - j - 1;
            pickIndex[j] = totalArr[ranIndex];//将一项放到结果数组里面
            //被抽出来的项目,和数组末尾交换,因为我们不会再读取到末尾,所以只需要把当前项目改掉就行
            totalArr[ranIndex] = totalArr[totalAmount - j - 1];
        }
        return pickIndex;
    }


}

Guess you like

Origin blog.csdn.net/weixin_61427881/article/details/134410674