数独游戏,随机生成只有唯一解的数独表
閑來無事時玩了一下數獨,于是想著自己能不能寫一個數獨游戲,要做一個數獨游戲,首先得生成一張完整的隨機的數獨表,看了很多其他人的做法,都不能保證是隨機生成的,于是我研究了一下數獨表,發現了其中的一些規律
從圖中可以看出,滿足數獨規則的情況下,兩個紅線方框和兩個綠色方框里的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
總結
以上是生活随笔為你收集整理的数独游戏,随机生成只有唯一解的数独表的全部內容,希望文章能夠幫你解決所遇到的問題。