C# 从数独解决方案中删除单元格使其成为一个难题

C# 从数独解决方案中删除单元格使其成为一个难题,c#,.net,algorithm,sudoku,C#,.net,Algorithm,Sudoku,我正在编写一个数独应用程序,目前正在研究游戏生成算法。我设法想出了如何快速生成解决方案(而不是解决方案)。然而,如何删除一些数字,使之真正成为一个谜,我感到困惑。我的第一个倾向是根据难度随机移除一定数量的单元格,但这不是正确的算法,因为它通常会呈现无法解决或有多个解决方案的谜题。它还可能生成不反映要求难度的谜题 这是我目前掌握的代码。我删除了大部分不相关的代码,但是如果您想看到下面没有实现但使用的代码,请告诉我。如果您愿意,我也可以尝试使用puzzfy方法,但我选择不立即发布,因为这显然是错误的

我正在编写一个数独应用程序,目前正在研究游戏生成算法。我设法想出了如何快速生成解决方案(而不是解决方案)。然而,如何删除一些数字,使之真正成为一个谜,我感到困惑。我的第一个倾向是根据难度随机移除一定数量的单元格,但这不是正确的算法,因为它通常会呈现无法解决或有多个解决方案的谜题。它还可能生成不反映要求难度的谜题

这是我目前掌握的代码。我删除了大部分不相关的代码,但是如果您想看到下面没有实现但使用的代码,请告诉我。如果您愿意,我也可以尝试使用
puzzfy
方法,但我选择不立即发布,因为这显然是错误的(即使它“有效”)

使用系统;
使用System.Collections.Generic;
使用System.Linq;
命名空间数独
{
公开课游戏
{
公共枚举困难
{
维耶西,
容易的,
中等,
难,,
邪恶的
}
私有只读整数?[,]_currentItems=新整数?[9,9];
私有只读int?[,]_解决方案=新int?[9,9];
私有只读整数?[,]_startingItems=新整数?[9,9];
私人只读困难(U困难);;
公共游戏(难度)
{
_困难=困难;
生成解析();
谜语();
}
私有无效生成解决方案()
{
var random=新的random();
var availableNumbers=新堆栈(81);
var x=0;
var y=0;
AvailableEnumbers.Push(AllowableNumber(_solution,0,0.ToList());
而(x<9&&y<9)
{
var currentAvailableEnumbers=AllowableNumber(_解决方案,x,y).ToList();
AvailableEnumbers.Push(CurrentAvailableEnumbers);
//如果电路板处于无效状态,则返回跟踪
while(currentAvailableEnumbers.Count==0)
{
_解[x,y]=null;
AvailableEnumbers.Pop();
CurrentAvailableEnumbers=AvailableEnumbers.Peek();
x-=y>=1?0:1;
y=y>=1?y-1:8;
}
var index=random.Next(currentAvailableEnumbers.Count);
_解决方案[x,y]=CurrentAvailableEnumbers[索引];
CurrentAvailableEnumbers.RemoveAt(索引);
x+=y<8?0:1;
y=y<8?y+1:0;
}
}
私人文件
{
复制单元(溶液、启动项目);
//从_startingItems中删除一些内容
复制单元(_startingItems,_currentItems);
}
}
}
我不是在寻找代码,而是一个算法。我该如何从解决方案中删除数字,使其成为一个难题?

下面是一个例子

我认为你需要一个数独解算器,它也会计算出可用的解的数量,然后以这样一种方式减去数字,即始终只有一个可用的解

您可以使用相同的方法将数字添加到网格中,然后检查可能的解决方案的数量,当解决方案的数量大于1时继续添加,当解决方案的数量为0时回溯。由于删除过程不是线性的,因此没有“简单”的方法从完整的数独网格中删除线索

每次删除单元格或线索后,您需要检查数独是否只有唯一的解决方案

要检查这一点,您需要运行一个可以计算所有可能解决方案的解算器(您可以在发现两种可能后停止它以节省时间)

求解数独、计算所有数独解和删除单元格时最常用的两种算法是回溯算法和舞蹈链接算法

本文很好地解释了如何在数独游戏中使用舞蹈链接算法:

下面是用JavaScript编写的数独游戏中舞蹈链接算法的另一个描述:

以下是关于舞蹈链接算法的全文:

我不确定这是否有帮助,但“适当”的数独也应该是对称的——提供(或删除)的单元格不是随机的,而是遵循从左到右、从上到下或镜像的模式。我不确定这是否会对产生工作谜题的可能性产生任何影响。我同意。虽然不对称的拼图从根本上没有错,但它在美学上令人不快。我发现随机移除通常不会产生太糟糕的对称性,但理论上,它可以使所有的给定都位于同一个角落。我实际上也在研究这个问题,但它简要描述了如何移除细胞,然后确定难度,而不是相反。重复这样做,直到达到所需难度的谜题,将产生一个非常低效的算法。
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sudoku
{
    public class Game
    {
        public enum Difficulty
        {
            VeryEasy,
            Easy,
            Medium,
            Difficult,
            Evil
        }

        private readonly int?[,] _currentItems = new int?[9,9];
        private readonly int?[,] _solution = new int?[9,9];
        private readonly int?[,] _startingItems = new int?[9,9];
        private readonly Difficulty _difficulty;

        public Game(Difficulty difficulty)
        {
            _difficulty = difficulty;
            GenerateSolution();
            Puzzlefy();
        }

        private void GenerateSolution()
        {
            var random = new Random();
            var availableNumbers = new Stack<List<int?>>(81);
            var x = 0;
            var y = 0;

            availableNumbers.Push(AllowableNumbers(_solution, 0, 0).ToList());
            while (x < 9 && y < 9)
            {
                var currentAvailableNumbers = AllowableNumbers(_solution, x, y).ToList();
                availableNumbers.Push(currentAvailableNumbers);

                // back trace if the board is in an invalid state
                while (currentAvailableNumbers.Count == 0)
                {
                    _solution[x, y] = null;
                    availableNumbers.Pop();
                    currentAvailableNumbers = availableNumbers.Peek();
                    x -= y >= 1 ? 0 : 1;
                    y = y >= 1 ? y - 1 : 8;
                }

                var index = random.Next(currentAvailableNumbers.Count);
                _solution[x, y] = currentAvailableNumbers[index];
                currentAvailableNumbers.RemoveAt(index);

                x += y < 8 ? 0 : 1;
                y = y < 8 ? y + 1 : 0;
            }
        }

        private void Puzzlefy()
        {
            CopyCells(_solution, _startingItems);

            // remove some stuff from _startingItems

            CopyCells(_startingItems, _currentItems);
        }
    }
}