Этот код вызывает у меня головную боль. Подводя итог, цель моей программы — создать полные доски судоку, которые можно будет использовать в будущем для игры. Я почти у цели. У меня нет повторяющихся чисел в более мелких сетках, но я все еще получаю повторяющиеся числа в более крупной сетке.
Снимок экрана моей программы, отображающий мою проблему:
Предисловие к моему коду:
Остальная часть кода объясняется программно внутри функции. Я считаю, что проблема заключается в частях «checkCol» и «checkRow», которые отмечены комментариями.
Мы очень ценим любую помощь и заранее благодарим всех комментаторов.
private void generateBtn_Click(object sender, EventArgs e){
Random r = new Random();
//Row and Col max is one less as arrays start at 0
int rowMax = 9;
int colMax = 9;
for (int i = 0; i < rowMax; i++)
{
for (int j = 0; j < colMax; j++)
{
bool checking = false;
bool foundSame = false;
while (checking == false)
{
foundSame = false;
string num = r.Next(1, 10).ToString();
Debug.WriteLine(num);
//check row
for (int k = 0; k < rowMax; k++) {
if (gameGrid[i,k] == num) { foundSame = true; break; }
if (gameGrid[i,k] == "") { break; }
}
//check col
for (int k = 0; k < colMax; k++)
{
if (gameGrid[k,j] == num) { foundSame = true; break; }
}
//check square
if (grid00.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid00, num); }
if (grid10.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid10, num); }
if (grid20.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid20, num); }
if (grid01.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid01, num); }
if (grid11.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid11, num); }
if (grid21.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid21, num); }
if (grid02.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid02, num); }
if (grid12.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid12, num); }
if (grid22.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid22, num); }
if (foundSame == false)
{
checking = true;
gameGrid[i,j] = num;
btnGrid[i,j].Text = num;
}
}
}
}}
@EpicAshman, почему у тебя if (gameGrid[i,k] == "") { break; }
в checkRow
?.
И почему число является случайным числом, я думаю, что в алгоритме доски судоку этого не должно быть random
.
Проверка того, пуста ли строка, была на ранней стадии, о которой я забыл. Я использую его для генерации случайных решений судоку, поскольку одна из моих будущих запланированных функций — добавить решение головоломок отдельно от программы, после чего станет доступно функциональное решение.
Основная проблема, по-видимому, не в частях «checkCol» и «checkRow». Вместо этого код не проверяет наличие foundSame
после двух false
циклов, прежде чем он проверит соответствующую меньшую сетку. Таким образом, например, если for
было foundSame
из-за того, что дубликат был найден в большей сетке, но он не найден в соответствующей меньшей сетке, тогда true
устанавливается в foundSame
, и, таким образом, значение устанавливается там, даже если оно повторяет значение в строке или столбце более крупной сетки. Вот почему цифры могут повторяться в более крупной сетке, но не в пределах более мелкой (обратите внимание, я предполагаю, что функция false
, которая не показана, работает правильно).
Относительно простой и легкий способ исправить это — просто добавить флажок, чтобы checkIfContains
не был установлен в операторах foundSame
для каждой из ваших меньших сеток. Таким образом, для первого варианта строка кода должна выглядеть примерно так:
if (!foundSame && grid00.Contains(btnGrid[i, j])) { foundSame = checkIfContains(grid00, num); }
Я не задумывался тщательно о том, может ли это когда-нибудь произойти, но я считаю, что потенциальная проблема заключается в том, что может возникнуть бесконечный цикл. Это произойдет, если ни одно из значений от 1 до 9 не может соответствовать требованиям для конкретной ячейки, т. е. каждое из них является дубликатом в строке или столбце более крупной сетки или в меньшей сетке. Чтобы избежать этого, один из способов — создать массив из 9 значений if
, где каждое значение изначально установлено в bool
непосредственно перед false
. В конце этого цикла, если while (checking == false)
равно foundSame
, установите значение массива, соответствующее true
, в num
. Кроме того, если все значения массива равны true
, то никакое значение не будет работать, поэтому код должен выйти из цикла, выполнив что-то вроде создания исключения (хотя вы можете сначала зарегистрировать это каким-либо образом), а затем возможно, очистите доску и попробуйте еще раз.
Наконец, как указано в комментарии Нгуен Мань Куонга, ваша строка true
в коде для проверки строк потенциально проблематична. Однако, поскольку вы не показали свой код инициализации, это сомнительно. Если пустых ячеек никогда не бывает, эта строка ничего не делает. С другой стороны, если перед ячейкой, содержащей дублирующую ячейку в строке, есть пустая ячейка, код выйдет из цикла, не устанавливая if (gameGrid[i,k] == "") { break; }
в foundSame
.
Тем не менее, я не вижу никакой реальной цели в наличии этой строки, поэтому ее, вероятно, следует удалить или хотя бы закомментировать.
Да, проверку на пустую строку я забыл взять из ранней версии. Но чтение вашего решения имеет смысл, и я не могу поверить, что это была моя оплошность, лол, большое спасибо.
@EpicAshman Всегда пожалуйста. Я только что понял, что есть еще одна потенциальная проблема, а именно возникновение бесконечного цикла. Таким образом, я обновил свой ответ в средней части, чтобы учесть эту возможность. Однако, как я уже сказал там, я не знаю, может ли это когда-нибудь произойти. Если вы уверены, что этого никогда не произойдет, вы можете проигнорировать это, хотя относительно легко сделать то, что я предлагаю, просто чтобы убедиться.
Вот подход к созданию полной доски.
var possibles = new List<int>[9, 9];
var board = new int[9, 9];
for (var i = 0; i < 9; i++)
for (var j = 0; j < 9; j++)
{
possibles[i, j] = Enumerable.Range(1, 9).ToList();
board[i, j] = 0;
}
var query =
from i in Enumerable.Range(0, 9)
from j in Enumerable.Range(0, 9)
where possibles[i, j].Any()
select new { i, j, };
var random = new Random();
for (var n = 0; n < 81; n++)
{
var least = query.MinByWithTies(x => possibles[x.i, x.j].Count).OrderBy(x => random.Next()).First();
var selected = possibles[least.i, least.j][random.Next(possibles[least.i, least.j].Count)];
possibles[least.i, least.j] = new List<int>();
board[least.i, least.j] = selected;
var ii = least.i / 3 * 3;
var jj = least.j / 3 * 3;
for (var k = 0; k < 9; k++)
{
possibles[least.i, k] = possibles[least.i, k].Except(new[] { selected }).ToList();
possibles[k, least.j] = possibles[k, least.j].Except(new[] { selected }).ToList();
possibles[ii + k / 3, jj + k % 3] = possibles[ii + k / 3, jj + k % 3].Except(new[] { selected }).ToList();
}
}
Все начинается с сетки возможностей и доски. Он находит все ячейки с наименьшим количеством вариантов и случайным образом выбирает один. Затем он заполняет эту ячейку и удаляет число из всех возможных строк, столбцов и блоков, связанных с этой ячейкой.
Всего это повторяется 81 раз.
Единственная драма заключается в том, что иногда он терпит неудачу, но достаточно часто удается просто повторить весь процесс в виде исключения. .First()
не работает в строке var least
.
Вот пример вывода действительной сетки:
Создание сетки занимает менее полсекунды.
Привет, я только что скопировал код в свой, и мой intelisense не распознает метод .IsEmpty() при просмотре списка. Есть мысли? Выплевывание CS1061
@EpicAshman — это в библиотеке, которую я использую. Я исправлю код.
Я бы начал с допустимой доски в качестве входных данных и вместо этого выполнил несколько случайных перестановок. Под перестановкой я подразумеваю перестановку двух случайных позиций в одной ячейке и выравнивание чисел в других ячейках, но у меня нет точного представления, как это сделать. Я добавил тег алгоритма.