数独游戏,随机生成只有唯一解的数独表

闲来无事时玩了一下数独,于是想着自己能不能写一个数独游戏,要做一个数独游戏,首先得生成一张完整的随机的数独表,看了很多其他人的做法,都不能保证是随机生成的,于是我研究了一下数独表,发现了其中的一些规律

从图中可以看出,满足数独规则的情况下,两个红线方框和两个绿色方框里的6个数肯定是一样的,那么我如果先随机生成1至9不重复的数填入第一个块里,那么第一行剩下的6个数就只需要从第一个块里下面那6个数里取,至于填入的时候就先对每个格子进行判断,这个各自所在的行、列、块有哪些数,把那6个数拿来与这些不能填的数取一个差集,那么获得的集合里的数就可以随便填一个了,到后面的格子做差集时就肯定只会有一个元素了,这样就保证了生成的表是随机的。

private void CreateTABLE()
        {            
            //生成第1块的9个元素
            string row1 = RandomNUM("123456789");
            for (int i = 0; i < 3; i++)    //填充第1行9个元素
            {
                for (int j = 0; j < 3; j++)
                {
                    Sodoku_table[i, j] = row1[i * 3 + j];
                }
            }
            while (CreateNUM(0, 3, false) || CreateNUM(0, 4, false) || CreateNUM(0, 5, false) || CreateNUM(0, 6, false) || CreateNUM(0, 7, false) || CreateNUM(0, 8, false) ||
                CreateNUM(1, 3, false) || CreateNUM(1, 4, false) || CreateNUM(1, 5, false) || CreateNUM(1, 6, false) || CreateNUM(1, 7, false) || CreateNUM(1, 8, false) ||
                CreateNUM(2, 3, false) || CreateNUM(2, 4, false) || CreateNUM(2, 5, false) || CreateNUM(2, 6, false) || CreateNUM(2, 7, false) || CreateNUM(2, 8, false) ||
                CreateNUM(3, 0, true) || CreateNUM(4, 0, true) || CreateNUM(5, 0, true) || CreateNUM(6, 0, true) || CreateNUM(7, 0, true) || CreateNUM(8, 0, true) ||
                CreateNUM(3, 1, true) || CreateNUM(4, 1, true) || CreateNUM(5, 1, true) || CreateNUM(6, 1, true) || CreateNUM(7, 1, true) || CreateNUM(8, 1, true) ||
                CreateNUM(3, 2, true) || CreateNUM(4, 2, true) || CreateNUM(5, 2, true) || CreateNUM(6, 2, true) || CreateNUM(7, 2, true) || CreateNUM(8, 2, true) ||
                CreateNUM(3, 3, false) || CreateNUM(3, 4, false) || CreateNUM(3, 5, false) || CreateNUM(3, 6, false) || CreateNUM(3, 7, false) || CreateNUM(3, 8, false) ||
                CreateNUM(4, 3, false) || CreateNUM(4, 4, false) || CreateNUM(4, 5, false) || CreateNUM(4, 6, false) || CreateNUM(4, 7, false) || CreateNUM(4, 8, false) ||
                CreateNUM(5, 3, false) || CreateNUM(5, 4, false) || CreateNUM(5, 5, false) || CreateNUM(5, 6, false) || CreateNUM(5, 7, false) || CreateNUM(5, 8, false) ||
                CreateNUM(6, 3, false) || CreateNUM(6, 4, false) || CreateNUM(6, 5, false) || CreateNUM(6, 6, false) || CreateNUM(6, 7, false) || CreateNUM(6, 8, false) ||
                CreateNUM(7, 3, false) || CreateNUM(7, 4, false) || CreateNUM(7, 5, false) || CreateNUM(7, 6, false) || CreateNUM(7, 7, false) || CreateNUM(7, 8, false) ||
                CreateNUM(8, 3, false) || CreateNUM(8, 4, false) || CreateNUM(8, 5, false) || CreateNUM(8, 6, false) || CreateNUM(8, 7, false) || CreateNUM(8, 8, false)
                ) ;         //如果某一个格子填入失败,就重新生成,直到完成   
        }

        private bool CreateNUM(int n, int m, bool Iscloumn)  //n为行,m为列,Iscloumn是表明按行填充格子,还是按列填充格子
        {
            if(Iscloumn)  //如果是按照列,那么就把n、m互换
            {
                int tem = n;
                n = m;
                m = tem;
            }
            List<char> Exceptnum = new List<char>();    //保存差集
            List<char> Intersectnum = new List<char>();   //保存交集
            for(int i = 0; i < m; i++)  //添加格子所在行或列的已有的元素到差集里
            {
                if(!Iscloumn)
                {
                    Exceptnum.Add(Sodoku_table[n, i]);
                }
                else
                {
                    Exceptnum.Add(Sodoku_table[i, n]);
                }
            }
            int row = n % 3;
            while (row > 0)   //添加格子所在块里已有的元素到差集
            {
                for (int i = (m / 3) * 3; i < (m / 3 == 1 ? 6 : 9); i++)
                {
                    if(!Iscloumn)
                    {
                        Exceptnum.Add(Sodoku_table[row + (n / 3) * 3 - 1, i]);
                    }
                    else
                    {
                        Exceptnum.Add(Sodoku_table[i, row + (n / 3) * 3 - 1]);
                    }
                }
                row--;
            }
            if(n / 3 > 0)   //第4行(列)或第7行(列)及以下的格子需要添加它上(左)方1至3行(列)或1至6行(列)的元素到差集
            {
                for(int i = 0; i < (n / 3) * 3; i++)
                {
                    if(!Iscloumn)
                    {
                        Exceptnum.Add(Sodoku_table[i, m]);
                    }
                    else
                    {
                        Exceptnum.Add(Sodoku_table[m, i]);
                    }
                }
            }
            for (int i = 0; i < 3; i++)   //添加元素到交集
            {
                if(!Iscloumn)
                {
                    if (n % 3 == 0)
                    {
                        Intersectnum.Add(Sodoku_table[n + 1, i]);
                        Intersectnum.Add(Sodoku_table[n + 2, i]);
                    }
                    else if (n % 3 == 1)
                    {
                        Intersectnum.Add(Sodoku_table[n - 1, i]);
                        Intersectnum.Add(Sodoku_table[n + 1, i]);
                    }
                    else if (n % 3 == 2)
                    {
                        Intersectnum.Add(Sodoku_table[n - 1, i]);
                        Intersectnum.Add(Sodoku_table[n - 2, i]);
                    }
                }
                else
                {
                    if (n % 3 == 0)
                    {
                        Intersectnum.Add(Sodoku_table[i, n + 1]);
                        Intersectnum.Add(Sodoku_table[i, n + 2]);
                    }
                    else if (n % 3 == 1)
                    {
                        Intersectnum.Add(Sodoku_table[i, n - 1]);
                        Intersectnum.Add(Sodoku_table[i, n + 1]);
                    }
                    else if (n % 3 == 2)
                    {
                        Intersectnum.Add(Sodoku_table[i, n - 1]);
                        Intersectnum.Add(Sodoku_table[i, n - 2]);
                    }
                }
                
            }
            List<char> Temp = "123456789".Except(Exceptnum).ToList();  //做差集
            try
            {
                if(!Iscloumn)  //做交集,并随机取一个数填入格子
                {
                    Sodoku_table[n, m] = Intersectnum.Intersect(Temp).ToList()[rnd.Next(Intersectnum.Intersect(Temp).ToList().Count)];   
                }
                else
                {
                    Sodoku_table[m, n] = Intersectnum.Intersect(Temp).ToList()[rnd.Next(Intersectnum.Intersect(Temp).ToList().Count)];
                }
            }
            catch
            {
                return true;
            }
            return false;
        }

        private string RandomNUM(string num)
        {
            char[] str = num.ToCharArray();//初始字符2113数5261组            
            for (int i = 0; i < str.Length; i++)//从str[0]开始随4102机交换两1653字符
            {
                char c = str[i];
                int index = rnd.Next(num.Length);
                str[i] = str[index];
                str[index] = c;
            }
            string result = new string(str);
            return result;
        }

现在完成了第一步,生成了一张随机的表,接下来需要根据难度随机抽取一定数量的格子置为空白,因为生成的是一张完整的数独表,所以肯定有解,但是怎么保证抽取这些格子之后数独是唯一解呢,于是我先用程序解一遍数独,然后判断这张表是否与原表相等,如果不等就找出第一个不等的位置,然后把这个位置固定住,不把它置空,然后再解一遍表,不等就又固定第一个开始跑偏的位置,但是这样仍然不能保证数独表的解唯一,因为在解表的时候,采取了回溯法,即先先寻找并填写那些唯一数单元格,然后判断是否填满,没有的话又顺序尝试填写有多个候选数的单元格,如果发现有的格子没有数可以填就回溯到上一个填的格子,把它改为另外一个候选数,直到格子填满就解出了表,事实上有可能某个格子有几个候选数都可能解除这张表来,所以答案就不唯一了,我前面得到与原表匹配的表可能只是运气好,等我们自己做的时候会发现某个格子填另外一个数也能把表填完,所以我在找到匹配的表之后再继续做了判断,继续回溯,找到有哪些格子的候选数不唯一,然后从最后一个候选数不唯一的格子开始,改变它数为其他候选数,这样再继续解这个表,当然现在肯定不可能得到与原表匹配的表了,如果解不出这张表就说明刚刚生成的已经是只有唯一解的表了,但是如果这样都还是能解出一张表的话,就说明刚刚抽的那些空不能保证表唯一解,那么就把这个格子的数也固定成原表的数,之后再把这个表拿去解,直到生成唯一解的表。

private void CreateBLANK(int num)  //num为置为空白的数量
        {
            char[,] Sodoku_tem_table = new char[9, 9];
            for (int i = 0; i < 81; i++)
            {
                blank[i] = '1';
                Sodoku_tem_table[i / 9, i % 9] = Sodoku_table[i / 9, i % 9];
            }            
            while (num > 0)  //生成空白格
            {
                int tem = rnd.Next(81);
                if (blank[tem] == '1')
                {
                    blank[tem] = '0';
                    Sodoku_tem_table[tem / 9, tem % 9] = ' ';
                    num--;
                }
            }
            bool isWhile = true;   //先寻找并填写那些唯一数单元格
            bool isFind = false;   //找到唯一数单元格
            bool is2While = false;   //顺序尝试填写有多个候选数的单元格
            bool isSolution = false;   //找到一个解
            bool isMatch = false;  //找到匹配解
            bool isOnly = false;   //找到唯一解  
            bool isMultiSolution = false;  //找到除匹配解外的解
            int number = 0;
            int MultiSolutionLocation = 0;  //记录引起多解的位置
            string Savestep = "";   //记录回溯步骤
            while (isWhile || !isSolution || !isMatch || !isOnly)
            {
                int row = number / 9;
                int colunm = number % 9;
                if (Sodoku_tem_table[row, colunm] == ' ')
                {
                    List<char> Exceptnum = new List<char>();
                    for (int i = 0; i < 9; i++)
                    {
                        if (Sodoku_tem_table[row, i] != ' ')
                        {
                            Exceptnum.Add(Sodoku_tem_table[row, i]);
                        }
                        if (Sodoku_tem_table[i, colunm] != ' ')
                        {
                            Exceptnum.Add(Sodoku_tem_table[i, colunm]);
                        }
                    }
                    for (int i = row / 3 * 3; i < row / 3 * 3 + 3; i++)
                    {
                        for (int j = colunm / 3 * 3; j < colunm / 3 * 3 + 3; j++)
                        {
                            if (Sodoku_tem_table[i, j] != ' ')
                            {
                                Exceptnum.Add(Sodoku_tem_table[i, j]);
                            }
                        }
                    }
                    if (!is2While)
                    {
                        if (("123456789").Except(Exceptnum).ToList().Count == 1)
                        {
                            Sodoku_tem_table[row, colunm] = ("123456789").Except(Exceptnum).ToList()[0];
                            isFind = true;
                        }
                    }
                    else
                    {
                        if (("123456789").Except(Exceptnum).ToList().Count == 1)
                        {
                            Savestep += ("123456789").Except(Exceptnum).ToList()[0] + "*1*" + number + "*";
                            Sodoku_tem_table[row, colunm] = ("123456789").Except(Exceptnum).ToList()[0];
                        }
                        else if (("123456789").Except(Exceptnum).ToList().Count == 0)
                        {
                            bool isBack = true;
                            while (isBack)
                            {
                                string[] Step = Savestep.Split('*');
                                int count = int.Parse(Step[Step.Length - 3]);
                                if (--count > 0)
                                {
                                    number = int.Parse(Step[Step.Length - 2]);
                                    Sodoku_tem_table[number / 9, number % 9] = Step[Step.Length - 4][count - 1];
                                    Step[Step.Length - 3] = count.ToString();
                                    Savestep = "";
                                    for (int i = 0; i < Step.Length - 1; i++)
                                    {
                                        Savestep += Step[i] + "*";
                                    }
                                    isBack = false;
                                }
                                else
                                {
                                    Sodoku_tem_table[int.Parse(Step[Step.Length - 2]) / 9, int.Parse(Step[Step.Length - 2]) % 9] = ' ';                                                                        
                                    try
                                    {
                                        Savestep = Savestep.Remove(Savestep.Length - 1 - Step[Step.Length - 2].Length - 1 - Step[Step.Length - 3].Length - 1 - Step[Step.Length - 4].Length);
                                    }
                                    catch
                                    {
                                        return;  //因为生成的是一张正确的数独表,不可能第一次就找不到解,所以找到唯一解
                                    }
                                    if (Savestep.Length <= 0)
                                    {
                                        return;  //因为生成的是一张正确的数独表,不可能第一次就找不到解,所以找到唯一解
                                    }
                                }
                            }
                        }
                        else
                        {
                            for (int i = 0; i < ("123456789").Except(Exceptnum).ToList().Count; i++)
                            {
                                Savestep += ("123456789").Except(Exceptnum).ToList()[i];
                            }
                            Savestep += "*" + ("123456789").Except(Exceptnum).ToList().Count + "*" + number + "*";
                            Sodoku_tem_table[row, colunm] = ("123456789").Except(Exceptnum).ToList()[("123456789").Except(Exceptnum).ToList().Count - 1];
                        }
                    }
                }
                number++;
                if (number == blank.Length)
                {
                    number = 0;
                    if (!is2While)
                    {
                        if (!isFind)
                        {
                            isWhile = false;
                            is2While = true;
                        }
                        isFind = false;
                    }
                    isSolution = true;
                    for (int i = 0; i < 9 && isSolution; i++)
                    {
                        for (int j = 0; j < 9 && isSolution; j++)
                        {
                            if (Sodoku_tem_table[i, j] == ' ')
                            {
                                isSolution = false;
                            }
                        }
                    }
                    if (isSolution)
                    {
                        if(isMatch)
                        {
                            isMultiSolution = true;  //找到匹配解之后还找到其他解
                        }
                        if(!isMultiSolution)
                        {
                            isMatch = true;
                            for (int i = 0; i < 9 && isMatch; i++)
                            {
                                for (int j = 0; j < 9 && isMatch; j++)
                                {
                                    if (Sodoku_tem_table[i, j] != Sodoku_table[i, j])
                                    {
                                        blank[i * 9 + j] = '1';
                                        for (int z = 0; z < 81; z++)
                                        {
                                            if (blank[z] == '1')
                                            {
                                                Sodoku_tem_table[z / 9, z % 9] = Sodoku_table[z / 9, z % 9];
                                            }
                                            else if (blank[z] == '0')
                                            {
                                                Sodoku_tem_table[z / 9, z % 9] = ' ';
                                            }
                                        }
                                        isWhile = true;
                                        is2While = false;
                                        isSolution = false;
                                        isMatch = false;                                                                    
                                    }
                                }
                            }
                        }                                          
                        if(isMatch)
                        {                            
                            isOnly = true;
                            string[] Step = Savestep.Split('*');
                            for(int i = Step.Length - 3; i > 0 && isOnly; i -= 3)
                            {
                                int count = int.Parse(Step[i]);
                                if(count-- > 1)
                                {
                                    number = MultiSolutionLocation = int.Parse(Step[i + 1]);                                    
                                    Sodoku_tem_table[number / 9, number % 9] = Step[i - 1][count - 1];
                                    Step[i] = count.ToString();
                                    Savestep = "";
                                    for (int j = 0; j <= i + 1; j++)  //重新拼接回溯步骤,去掉后面的部分
                                    {
                                        Savestep += Step[j] + "*";
                                    }
                                    for (int j = i + 4; j < Step.Length - 1; j += 3)  //把后面的填的数清除以便重新循环
                                    {
                                        count = int.Parse(Step[j]);
                                        Sodoku_tem_table[count / 9, count % 9] = ' ';
                                    }
                                    isWhile = true;
                                    is2While = false;
                                    isSolution = false;
                                    isMatch = false;                                    
                                    isOnly = false;
                                }                                
                            }
                        }
                        else
                        {
                            if(isMultiSolution)
                            {
                                blank[MultiSolutionLocation] = '1';
                                for (int z = 0; z < 81; z++)
                                {
                                    if (blank[z] == '1')
                                    {
                                        Sodoku_tem_table[z / 9, z % 9] = Sodoku_table[z / 9, z % 9];
                                    }
                                    else if (blank[z] == '0')
                                    {
                                        Sodoku_tem_table[z / 9, z % 9] = ' ';
                                    }
                                }
                                isWhile = true;
                                is2While = false;
                                isSolution = false;
                                isMatch = false;
                                isOnly = false;
                                isMultiSolution = false;
                            }
                        }
                    }
                }
            }
        }

到这里就生成了一张只有唯一解的表,接下来就只需要完善游戏的其他功能就可以啦!

我用c#完成了这个游戏,感兴趣的可以去下载https://download.csdn.net/download/qq_40636929/13134218

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cingular_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值