Красиво расставляем 8 ферзей на доске
medium

Красиво расставляем 8 ферзей на доске

Веб-проект с алгоритмом поиска с возвратом

Чем больше базовых алгоритмов знает программист, тем проще ему написать код для разных ситуаций. Один из таких алгоритмов — поиск с возвратом, или бэктрекинг. Мы его подробно разбирали в прошлой статье, вот короткая версия:

  • поиск с возвратом — это алгоритм полного перебора;
  • если алгоритм понимает, что идёт по неверному пути, то все остальные варианты в этом пути тоже помечаются как неправильные и алгоритм их не рассматривает;
  • получается, что алгоритм перебирает только те варианты, в которых потенциально есть решение, — а это сильно сокращает время перебора;
  • ключевой элемент поиска с возвратом — рекурсия, то есть функция, которая вызывает сама себя.

Теперь давайте проверим его на практике и посмотрим, как быстро он будет находить нужные варианты и предлагать решения.

Что делаем

Сегодня будем решать задачу о расстановке ферзей на доске. Вкратце она звучит так:

как расставить на шахматной доске 8 ферзей так, чтобы они не пересекались по горизонтали, вертикали и диагонали?

В теории нам нужно проверить 178 триллионов комбинаций — это очень большое число даже для мощного компьютера. Посмотрим, как с этим справится алгоритм поиска с возвратом.

Чтобы было нагляднее, сделаем веб-проект: страницу с шахматной доской и кнопкой поиска вариантов. При нажатии на кнопку компьютер будет показывать новый вариант расстановки ферзей, если он будет. Для этого нам понадобится HTML, CSS для страницы и JavaScript, на котором мы напишем основной скрипт. В итоге получится такое:

Красиво расставляем 8 ферзей на доске

Готовим страницу

Страница будет состоять из трёх блоков:

  • заголовок и описание; 
  • шахматная доска;
  • кнопка со счётчиком решений.

Ещё нам понадобится нормализатор, чтобы страница выглядела одинаково во всех браузерах, и jQuery, чтобы получить доступ к элементам страницы из скрипта. Доску пока пропустим и создадим страницу с первым и последним блоками:

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Расставляем 8 ферзей на доске</title>
  <!-- подключаем нормализатор -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <!-- и свои стили -->
<link rel="stylesheet" href="style.css">

</head>
<body>
<!-- основной блок -->
<main class="container">
  <!-- шапка страницы -->
  <section class="header">
    <h1>Задача про 8 ферзей на доске</h1>
    <h3>Решение с использованием алгоритма бэктрекинга</h3>
  </section>
  <!-- раздел с доской -->
  <section class="board">
    
  </section>
  <!-- раздел с элементами управления -->
  <section class="control">
    <!-- кнопка поиска следующего решения -->
    <button class="next-btn">Следующее решение</button>
    <!-- сразу подписываем первое решение -->
    <p class="counter"> <span>Решение </span><span id="sol">1</span></p>
  </section>
</main>
  <!-- подключаем jQuery -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
  <!-- и свой скрипт -->
  <script  src="script.js"></script>

</body>
</html>

Чтобы создать доску, используем простую HTML-таблицу. Каждая ячейка этой таблицы — одна клетка на доске. В шахматах 8 рядов по 8 клеток, получается, нужно сделать таблицу из 8 строк и 8 столбцов. Так как доска чёрно-белая, сразу пометим цвет каждой ячейки. Если бы мы пользовались препроцессором, то создали бы доску за пару строк кода, но сейчас распишем всё полностью:

<table class="chessboard">
  <!-- рисуем шахматную доску -->
  <tbody>
    <tr>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
    </tr>
    <tr>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
    </tr>
    <tr>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
    </tr>
    <tr>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
    </tr>
    <tr>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
    </tr>
    <tr>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
    </tr>
    <tr>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
    </tr>
    <tr>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
      <td class="black space"></td>
      <td class="white space"></td>
    </tr>
  </tbody>
</table>

Так как у нас ещё нет стилей, то страница будет выглядеть просто — со стандартным оформлением и без шахматной доски. Дело в том, что мы хоть и создали таблицу, но не наполнили её содержимым, поэтому на странице ничего не видно:

Красиво расставляем 8 ферзей на доске

Создаём стили

Задача стилей — сделать так, чтобы содержимое страницы выглядело красиво. Для этого мы делаем такое:

  • выравниваем все элементы по центру;
  • настраиваем шрифт;
  • прописываем, как должна выглядеть доска;
  • указываем внешний вид кнопки.

Оформим всё, кроме доски — ей займёмся отдельно. Единственная команда, которую мы ещё не использовали в других проектах — это border-collapse: collapse. Её смысл в том, что если у двух элементов таблицы есть общая граница, то вместо двух отдельных границ браузер нарисует одну.

/* общие настройки страницы */
.container {
  /* цвет и шрифт */
  color: #333;
  font-family: "Helvetica", sans-serif;
  /* выравнивание */
  text-align: center;
  align-items: center;
  /* включаем гибкую вёрстку и указываем направление */
  display: flex;
  flex-direction: column;
}

/* настройки блока с доской */
.board .chessboard {
  /* размеры доски */
  width: 400px;
  height: 400px;
  /* границы  */
  border: 2px solid #333;
  border-radius: 3px;
  /* пусть соседние границы отображаются как одна граница */
  border-collapse: collapse;
}

/* настройки кнопки */
.next-btn {
  /* отступы */
  margin-top: 2rem;
  padding: 0.5em 1rem;
  /* цвета */
  background-color: #3286a1;
  color: white;
  /* скругление кнопки */
  border: none;
  border-radius: 2px;
}
Красиво расставляем 8 ферзей на доске

Теперь сделаем доску: пропишем цвета для классов чёрных и белых клеток и добавим общие настройки клеток. Содержимое клеток тоже пусть располагается по центру — сделаем это с помощью простого стиля для текста text-align: center. При желании можно было бы добавить диагональную штриховку, чтобы было совсем по-шахматному, но пока оставим так:

/* настройки клеток */
.board .space {
  /* высота и ширина каждой клетки */
  width: 50px;
  height: 50px;
  /* размер фигуры на клетке */
  font-size: 40px;
  /* выравниваем по центру */
  text-align: center;
  /* радиус скругления клетк */
  border-radius: 2px;
}

/* чёрная клетка */
.board .black.space {
  background-color: #a8a8a8b3;
}

/* белая клетка */
.board .white.space {
  background-color: #fff;
}
Красиво расставляем 8 ферзей на доске

Прототипы

Чтобы вся магия поиска с возвратом сработала, нам понадобятся прототипы. Если сильно упростить, то прототипы в JS в нашем случае работают так:

  1. Мы делаем класс — это инструкция по созданию объектов. 
  2. У каждого класса есть какие-то методы. Метод — это то, что этот класс умеет делать.
  3. Внутри этих методов мы вызываем другие внутренние методы класса.
  4. Когда мы создаём объект, то он умеет всё, что умеет наш класс, но может появиться проблема с вызовом внутренних методов, которые относятся именно к классу, а не к этому объекту.
  5. Чтобы объект мог всё равно вызывать нужные ему методы, используют прототипы.

Смысл прототипа в том, что, если объект не находит внутри у себя нужный метод, он через прототип обращается к классу и ищет метод там. Если находит — он его вызывает, как будто это обычный метод объекта. 

👉 Если хотите лучше узнать про методы, классы и объекты, почитайте нашу статью про ООП — там всё проще и с примерами.

Теперь покажем, как это работает на практике. Первое, что мы напишем в скрипте, — общую функцию, которая будет отвечать за все элементы решения задачи. Задача этой функции — хранить в себе базовые настройки и решения:

// общая функция с решением
var nQueensSolver = function(n) {
  // количество ферзей
  this.queens_num = n;
  // столбцы на доске
  this.board_cols = [];
  // решения
  this.solutions = [];
  // стартовые настройки
  this.init();
};

// функция со стартовыми настройками
nQueensSolver.prototype.init = function() {
  // сколько ферзей — столько шагов в цикле
  for (var i = 0; i < this.queens_num + 1; i++) {
    // обнуляем столбцы
    this.board_cols[i] = 0;
  }
};

Разберём, что здесь происходит:

  1. Мы создали функцию nQueensSolver() — она на вход получает количество ферзей, которые нужно расставить на доске.
  2. Внутри этой функции появляются три переменные, с которыми она будет работать, — количество ферзей, столбцы на доске и решения с расстановками.
  3. В конце функция вызывает свой внутренний метод init(), которой мы описываем сразу ниже.
  4. Сейчас этот метод — внутренний для функции, и если мы создадим новую функцию на основе nQueensSolver()— она уже не получит доступа к этой функции.
  5. Чтобы этот внутренний метод init()можно было вызвать из другой функции на основе старой, используют прототип с помощью команды .prototype — она поможет взять этот метод из исходной функции.

⭐ Логика работы скрипта

Общая логика работы будет такой:

  1. Описываем функцию, которая добавляет нового ферзя на доску.
  2. Делаем функцию, которая сразу проверяет предложенное решение — и если оно не подходит, то мы не перебираем другие решения, которые будут продолжением этого. Проще говоря, если мы второго ферзя поставили на одну линию с первым, нам не нужно ставить дальше третьего, чтобы понять, что это решение не сработает.
  3. Так делаем до тех пор, пока не переберём все варианты. При этом мы не будем перебирать заведомо плохие решения. После этого у нас сразу получится массив с решениями.
  4. Первый элемент массива мы выводим на доску как первое решение.
  5. При нажатии на кнопку выводим следующее решение.
  6. Когда решения закончились — выводим сообщение.

Решения будем хранить так: раз на одном столбце доски может стоять только один ферзь, то позицию ферзя будем хранить в таком виде: номер столбца → номер строки в нём. Всего у нас 8 ферзей, поэтому будет 8 столбцов и каждый будет хранить всего одну цифру. 

Пишем алгоритм поиска с возвратом

Алгоритм состоит из трёх базовых частей:

  1. Функции проверки решения — подходит оно или нет.
  2. Функции-генератора, которая предлагает новые решения.
  3. Главной функции, которая запускает генератор и сохраняет найденные решения.

В первой функции нам нужно будет проверить, есть ли пересечение новой фигуры по горизонтали и диагонали с теми, что уже стоят на доске. Мы не проверяем столбцы, потому что добавляем их каждый раз на следующую колонку — это уже гарантирует нам, что они не пересекаются по вертикали.

В генераторе мы делаем так: добавляем нового ферзя в новый столбец каждый раз на новую строчку: сначала на первую клетку снизу, потом на вторую и так далее. После добавления сразу вызываем функцию проверки: если она прошла, то идём дальше, а если нет — отбрасываем это решение и сразу начинаем следующее. В конце, если ферзи закончились, добавляем найденное решение в массив.

Задача главной функции — указать стартовую клетку, запустить генератор и вернуть все найденные решения.

Теперь запишем всё это на JavaScript:

// функция, которая проверяет очередное решение, если мы добавим нового ферзя на доску
nQueensSolver.prototype.promising = function(idx) {
  // получаем доступ к столбцам
  var col = this.board_cols; 
  // перебираем все варианты от нуля до указанного
  for (var k = 0; k < idx; k++) {
    // если новый ферзь стоит на той же строке, что и один из старых
    if (col[idx] === col[k] ||
      // или стоит на той же диагонали
      Math.abs(col[idx] - col[k]) === idx - k) {
      // возвращаем, что решение не подходит
      return false;
    }
  }
  // если скрипт дошёл досюда, то все проверки прошли и предложенное решение подходит
  return true;
};

// основная функция бэктрекинга
// на вход получает номер 
nQueensSolver.prototype.backtrack = function(i) {
  // добавляем в будущее решение очередного ферзя
  // если решение срабатывает — идём дальше
  if (this.promising(i)) {
    // если это был последний ферзь
    if (i === this.queens_num) { 
      // то сохраняем решение целиком
      this.solutions.push(this.board_cols.slice(1));
    // если ферзи ещё остались
    } else {
      // перебираем оставшихся ферзей
      for (var j = 0; j < this.queens_num + 1; j++) {
        // добавляем их на доску по одному на следующую клетку в колонке
        this.board_cols[i + 1] = j;
        // и рекурсисно запускаем эту же функцию
        this.backtrack(i + 1);
      }
    }
  }
};

// функция, которая решит задачу
nQueensSolver.prototype.solve = function() {
  // начинаем с первой клетки
  var starting_index = 0; 
  // запускаем бэктрекинг и ждём, пока он соберёт все решения
  this.backtrack(starting_index);
  // возвращаем все найденные решения
  return this.solutions;
};

Подготавливаем переменные

Чтобы скрипту было с чем работать, зададим нужные переменные и создадим виртуальную шахматную доску. Виртуальная доска нам нужна для того, чтобы мы могли расставить на ней ферзей, а затем легко отобразить это на странице. Здесь пригодится jQuery — с его помощью мы получим доступ ко всем клеткам таблицы и занесём их в отдельный массив.

Также мы сразу создадим новую функцию на основе старой и с её помощью сразу получим все решения — здесь как раз пригодятся прототипы, о которых мы говорили раньше. Они помогут новой функции получить доступ к методам с решениями.

Ещё одна функция будет отвечать за очистку виртуальной доски — мы будем использовать её для вывода следующего решения, чтобы перед этим убрать с доски предыдущее.

// символ ферзя
var queen_piece = '♛';
// количество ферзей
var _n = 8;
// создаём новый объект, с помощью которого решим задачу
var solver = new nQueensSolver(_n);
// получаем сразу список решений
var result = solver.solve();

// создаём виртуальную доску для работы скрипта
function makeGrid() {
  // внутренняя функция, чтобы получить доступ к конкретной клетке
  function getCell(i, j) {
    return $("table tr").eq(i).find("td").eq(j);
  }
  // тут будет храниться виртуальная доска
  var $grid = []
  // перебираем все строки
  for (var i = 0; i < _n; i++) {
    // делаем их массивом
    var $row = []
    // перебираем отдельные клетки
    for (var j = 0; j < _n; j++) {
      // отправляем все клетки в строку
      $row.push(getCell(i, j))
    }
    // добавляем новую строку в виртуальную доску
    $grid.push($row);
  }
  // возвращаем виртуальную доску
  return $grid;
}

// очищаем предыдущее решение
function clearLastSolution(last) {
  // перебираем все столбцы
  for (var i = 0; i < last.length; i++) {
    // получаем номер строки
    var j = last[i];
    // очищаем клетку с этим адресом
    $grid[j - 1][i].html('');
  }
}

Рисуем ферзей и запускаем скрипт

Чтобы при запуске страницы мы сразу увидели первое решение, выберем первое решение из найденных и расставим ферзей на доске. За расстановку отвечает функция putQueenOnSolutionPieces() — она берёт очередное решение, перебирает столбцы и ставит ферзей на те клетки, которые указаны в столбцах.

Ещё сразу привяжем обработчик нажатия к кнопке, чтобы она сама убирала старое решение с доски, брала новое и расставляла ферзей по местам.

// основная функция
function initGrid() {
  // выбираем первое решение 
  var first = result[iter]; 
  // ставим ферзей на места из первого решения
  putQueenOnSolutionPieces(first);
}
// привязываем обработчик нажатия к кнопке
$('.next-btn').click(function() {
  // если на экране последнее решение
  if (iter === result.length - 1) {
    // выводим сообщение
    alert("Решения закончились! Обновите страницу, чтобы посмотреть их заново.");
  // если решения ещё есть 
  } else {
    // получаем текущее решение
    var last = result[iter];
    // убираем его с доски
    clearLastSolution(last);
    // берём следующее решение
    var next = result[++iter];
    // меняем номер решения в тексте под кнопкой
    $('#sol').text(iter + 1);
    // ставим ферзей по местам
    putQueenOnSolutionPieces(next);
  }
});

// функция, которая ставит ферзей по нужным местам
function putQueenOnSolutionPieces(solution) {
  // перебираем все строки в решении
  for (var i = 0; i < solution.length; i++) {
    // получаем позицию в столбце
    var j = solution[i];
    // ставим ферзя на клетку с полученными координатами
    $grid[j - 1][i].html(queen_piece);
  }
}

Последнее, что осталось сделать, — создать виртуальную доску и запустить скрипт:

// получаем виртуальную доску
var $grid = makeGrid();
// номер текущего решения
var iter = 0;
// запускаем основную функцию
initGrid();

Обновляем страницу и смотрим, как быстро алгоритм нашёл все возможные варианты расстановок:

Красиво расставляем 8 ферзей на доске

Посмотреть работу алгоритма на странице проекта.

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Расставляем 8 ферзей на доске</title>
  <!-- подключаем нормализатор -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <!-- и свои стили -->
<link rel="stylesheet" href="style.css">

</head>
<body>
<!-- основной блок -->
<main class="container">
  <!-- шапка страницы -->
  <section class="header">
    <h1>Задача про 8 ферзей на доске</h1>
    <h3>Решение с использованием алгоритма бэктрекинга</h3>
  </section>
  <!-- раздел с доской -->
  <section class="board">
    <table class="chessboard">
      <!-- рисуем шахматную доску -->
      <tbody>
        <tr>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
        </tr>
        <tr>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
        </tr>
        <tr>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
        </tr>
        <tr>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
        </tr>
        <tr>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
        </tr>
        <tr>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
        </tr>
        <tr>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
        </tr>
        <tr>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
          <td class="black space"></td>
          <td class="white space"></td>
        </tr>
      </tbody>
    </table>
  </section>
  <!-- раздел с элементами управления -->
  <section class="control">
    <!-- кнопка поиска следующего решения -->
    <button class="next-btn">Следующее решение</button>
    <!-- сразу подписываем первое решение -->
    <p class="counter"> <span>Решение </span><span id="sol">1</span></p>
  </section>
</main>
  <!-- подключаем jQuery -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
  <!-- и свой скрипт -->
  <script  src="script.js"></script>

</body>
</html>

/* общие настройки страницы */
.container {
  /* цвет и шрифт */
  color: #333;
  font-family: "Helvetica", sans-serif;
  /* выравнивание */
  text-align: center;
  align-items: center;
  /* включаем гибкую вёрстку и указываем направление */
  display: flex;
  flex-direction: column;
}

/* настройки блока с доской */
.board .chessboard {
  /* размеры доски */
  width: 400px;
  height: 400px;
  /* границы  */
  border: 2px solid #333;
  border-radius: 3px;
  /* пусть соседние границы отображаются как одна граница */
  border-collapse: collapse;
}

/* настройки кнопки */
.next-btn {
  /* отступы */
  margin-top: 2rem;
  padding: 0.5em 1rem;
  /* цвета */
  background-color: #3286a1;
  color: white;
  /* скругление кнопки */
  border: none;
  border-radius: 2px;
}

/* настройки клеток */
.board .space {
  /* высота и ширина каждой клетки */
  width: 50px;
  height: 50px;
  /* размер фигуры на клетке */
  font-size: 40px;
  /* выравниваем по центру */
  text-align: center;
  /* радиус скругления клетк */
  border-radius: 2px;
}

/* чёрная клетка */
.board .black.space {
  background-color: #a8a8a8b3;
}

/* белая клетка */
.board .white.space {
  background-color: #fff;
}

// общая функция с решением
var nQueensSolver = function(n) {
  // количество ферзей
  this.queens_num = n;
  // столбцы на доске
  this.board_cols = [];
  // решения
  this.solutions = [];
  // стартовые настройки
  this.init();
};

// функция со стартовыми настройками
nQueensSolver.prototype.init = function() {
  // сколько ферзей — столько шагов в цикле
  for (var i = 0; i < this.queens_num + 1; i++) {
    // обнуляем столбцы
    this.board_cols[i] = 0;
  }
};

// функция, которая проверяет очередное решение, если мы добавим нового ферзя на доску
nQueensSolver.prototype.promising = function(idx) {
  // получаем доступ к столбцам
  var col = this.board_cols; 
  // перебираем все варианты от нуля до указанного
  for (var k = 0; k < idx; k++) {
    // если новый ферзь стоит на той же строке, что и один из старых
    if (col[idx] === col[k] ||
      // или стоит на той же диагонали
      Math.abs(col[idx] - col[k]) === idx - k) {
      // возвращаем, что решение не подходит
      return false;
    }
  }
  // если скрипт дошёл досюда, то все проверки прошли и предложенное решение подходит
  return true;
};

// основная функция бэктрекинга
// на вход получает номер 
nQueensSolver.prototype.backtrack = function(i) {
  // добавляем в будущее решение очередного ферзя
  // если решение срабатывает — идём дальше
  if (this.promising(i)) {
    // если это был последний ферзь
    if (i === this.queens_num) { 
      // то сохраняем решение целиком
      this.solutions.push(this.board_cols.slice(1));
    // если ферзи ещё остались
    } else {
      // перебираем оставшихся ферзей
      for (var j = 0; j < this.queens_num + 1; j++) {
        // добавляем их на доску по одному на следующую клетку в колонке
        this.board_cols[i + 1] = j;
        // и рекурсисно запускаем эту же функцию
        this.backtrack(i + 1);
      }
    }
  }
};

// функция, которая решит задачу
nQueensSolver.prototype.solve = function() {
  // начинаем с первой клетки
  var starting_index = 0; 
  // запускаем бэктрекинг и ждём, пока он соберёт все решения
  this.backtrack(starting_index);
  // возвращаем все найденные решения
  return this.solutions;
};

// символ ферзя
var queen_piece = '♛';
// количество ферзей
var _n = 8;
// создаём новый объект, с помощью которого решим задачу
var solver = new nQueensSolver(_n);
// получаем сразу список решений
var result = solver.solve();

// создаём виртуальную доску для работы скрипта
function makeGrid() {
  // внутренняя функция, чтобы получить доступ к конкретной клетке
  function getCell(i, j) {
    return $("table tr").eq(i).find("td").eq(j);
  }
  // тут будет храниться виртуальная доска
  var $grid = []
  // перебираем все строки
  for (var i = 0; i < _n; i++) {
    // делаем их массивом
    var $row = []
    // перебираем отдельные клетки
    for (var j = 0; j < _n; j++) {
      // отправляем все клетки в строку
      $row.push(getCell(i, j))
    }
    // добавляем новую строку в виртуальную доску
    $grid.push($row);
  }
  // возвращаем виртуальную доску
  return $grid;
}

// очищаем предыдущее решение
function clearLastSolution(last) {
  // перебираем все столбцы
  for (var i = 0; i < last.length; i++) {
    // получаем номер строки
    var j = last[i];
    // очищаем клетку с этим адресом
    $grid[j - 1][i].html('');
  }
}

// основная функция
function initGrid() {
  // выбираем первое решение 
  var first = result[iter]; 
  // ставим ферзей на места из первого решения
  putQueenOnSolutionPieces(first);
}
// привязываем обработчик нажатия к кнопке
$('.next-btn').click(function() {
  // если на экране последнее решение
  if (iter === result.length - 1) {
    // выводим сообщение
    alert("Решения закончились! Обновите страницу, чтобы посмотреть их заново.");
  // если решения ещё есть 
  } else {
    // получаем текущее решение
    var last = result[iter];
    // убираем его с доски
    clearLastSolution(last);
    // берём следующее решение
    var next = result[++iter];
    // меняем номер решения в тексте под кнопкой
    $('#sol').text(iter + 1);
    // ставим ферзей по местам
    putQueenOnSolutionPieces(next);
  }
});

// функция, которая ставит ферзей по нужным местам
function putQueenOnSolutionPieces(solution) {
  // перебираем все строки в решении
  for (var i = 0; i < solution.length; i++) {
    // получаем позицию в столбце
    var j = solution[i];
    // ставим ферзя на клетку с полученными координатами
    $grid[j - 1][i].html(queen_piece);
  }
}

// получаем виртуальную доску
var $grid = makeGrid();
// номер текущего решения
var iter = 0;
// запускаем основную функцию
initGrid();

Корректор:

Ира Михеева

Художник:

Алексей Сухов

Вёрстка:

Кирилл Климентьев

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
medium