Как написать бота, которого будет нельзя обыграть в «крестики-нолики», или Знакомство с правилом «минимакс. Создание игры крестики нолики этап: не даем выиграть

Приветствую вас дорогие друзья! В этом уроке я покажу, как можно сделать браузерную игру - крестики нолики, на javascript! Все вы знаете, что это за игра и как в нее играть, но еще раз напомню:

Крестики-нолики — это логическая игра между двумя игроками на квадратном поле 3 на 3 клетки (возможно и бо́льшего размера). Один играет «крестиками», а второй — «ноликами».

P.S. как и во всех подобных уроках по javascript , в конце статьи вы можете скачать исходный файл, а также посмотреть результат работы на demo примере!

Описание создаваемой игры

Давайте рассмотрим особенности игры:

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

Логика

Я не стал придумывать сложных (универсальных) алгоритмов для игрового поля 3 на 3 клетки, я пошел другим путем - перебором! (об этом чуть позже). Я выделил три основных последовательных этапа, на которых и держится вся логика:

1 этап: проверяем - победил ли игрок?

На этом этапе мы проверяем, имеются ли 3 клетки (на одной линии), заполненные одинаковыми символами играющего (крестиками или нолями). Т.е. не смотря на то, какой этой ход (пусть даже первый) мы всегда сначала проверяем - выиграл ли играющий. Вот так выглядит победа:

2 этап: проверяем - может ли компьютер победить следующим ходом?

На этом этапе мы ищем линию, где были бы 2 клетки заполненные компьютером и одна пустая клетка - то есть пытаемся выиграть за счет невнимательности игрока. Вот так выглядит поражение (т.е. победа компьютера):

3 этап: не даем выиграть!

Здесь мы ищем такую же линию, как и на втором этапе, только 2 клетки должны быть заполнены игровыми знаками игрока, то есть на этом этапе не даем проиграть компьютеру, поставив знак в пустую клетку. Каждый из этапов представляем собой самостоятельную функцию - это вы можете увидеть в js коде ниже.

Реализация

Разметка игрового поля очень простая - в основном блоке размещена строка информации (класс - result) и 9 блоков, представляющих собой клетки (класс - block) HTML разметка клеток:

Ваш ход!

Вспомогательный класс cell необходим для того, чтобы точно идентифицировать нужную клетку на игровом поле. CSS стили для игрового поля:

Krestiki_noliki{ width: 306px; margin: 0 auto; } .krestiki_noliki .block{ width: 100px; height: 100px; border: 1px solid #ccc; cursor: pointer; float: left; text-align: center; font-size: 100px; line-height: 94px; }

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

$(document).ready(function(){ var znak_user = "O"; var znak_comp = "X"; var rand_num = Math.round((Math.random() * (9 - 1) + 1)); if(rand_num > 3){ var znak_comp = "O"; var znak_user = "X"; $(".cell"+rand_num).text(znak_comp); } var exit_flag = false; var win_user_array = ["123","456","789","147","258","369","159","357"]; //Определяем победу игрока function check_3_user(znak){ for (var i = 0; i < 8; i++) { var first = "cell" + win_user_array[i].substr(0,1); var second = "cell" + win_user_array[i].substr(1,1); var third = "cell" + win_user_array[i].substr(2,1); if($("."+first).text() == znak && $("."+second).text() == znak && $("."+third).text() == znak){ $("."+first+",."+second+",."+third).css("background-color", "#83e2c3"); $(".result").text("Вы выиграли!"); $(".krestiki_noliki .block").unbind("click"); exit_flag = true; } } } //Определяем возможность победы компьютера function check_2_comp(znak){ for (var i = 0; i < 8; i++) { var first = "cell" + win_user_array[i].substr(0,1); var second = "cell" + win_user_array[i].substr(1,1); var third = "cell" + win_user_array[i].substr(2,1); if($("."+first).text() == znak && $("."+second).text() == znak && $("."+third).text() == "" && exit_flag == false){ $("."+third).text(znak); $("."+first+",."+second+",."+third).css("background-color", "#EF7C7C"); $(".result").text("Вы проиграли!"); $(".krestiki_noliki .block").unbind("click"); exit_flag = true; } if($("."+first).text() == znak && $("."+second).text() == "" && $("."+third).text() == znak && exit_flag == false){ $("."+second).text(znak); $("."+first+",."+second+",."+third).css("background-color", "#EF7C7C"); $(".result").text("Вы проиграли!"); $(".krestiki_noliki .block").unbind("click"); exit_flag = true; } if($("."+first).text() == "" && $("."+second).text() == znak && $("."+third).text() == znak && exit_flag == false){ $("."+first).text(znak); $("."+first+",."+second+",."+third).css("background-color", "#EF7C7C"); $(".result").text("Вы проиграли!"); $(".krestiki_noliki .block").unbind("click"); exit_flag = true; } } } //Определяем ход компьютера function check_2_user(znak){ for (var i = 0; i < 8; i++) { var first = "cell" + win_user_array[i].substr(0,1); var second = "cell" + win_user_array[i].substr(1,1); var third = "cell" + win_user_array[i].substr(2,1); if(exit_flag == false){ if($("."+first).text() == znak && $("."+second).text() == znak && $("."+third).text() == ""){ $("."+third).text(znak_comp); exit_flag = true; } } if(exit_flag == false){ if($("."+first).text() == znak && $("."+second).text() == "" && $("."+third).text() == znak){ $("."+second).text(znak_comp); exit_flag = true; } } if($("."+first).text() == "" && $("."+second).text() == znak && $("."+third).text() == znak){ $("."+first).text(znak_comp); exit_flag = true; } if(exit_flag) break; } } $(".krestiki_noliki .block").click(function(){ //Если клетка пустая if($(this).text() == ""){ $(this).text(znak_user); check_3_user(znak_user); check_2_comp(znak_comp); check_2_user(znak_user); if(exit_flag == false){ for (var i = 1; i < 10; i++) { if($(".cell"+i).text() == ""){ $(".cell"+i).text(znak_comp); break; } } }else exit_flag = false; } }); });

Сначала мы объявляем переменные: znak_user - в этой переменной мы храним знак, которым будет играть пользователь (по умолчанию там хранится нолик - это английская будка "О"). znak_comp - в этой переменной мы храним знак, которым будет играть компьютер (по умолчанию там хранится крестик - это английская будка "икс").

Логика такая: если случайное число больше 3, то компьютер играет ноликами, и он же (компьютер) делает первый ход.

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

Каждый элемент массива - это строка из трех цифр, которая является выигрышной комбинацией, то есть, например, если заполнить клетки под цифрами 1, 2 и 3, то наступит победа (или поражение). Как видите, всего существует 8 победных вариантов, наша задача - перебрать все эти варианты. Далее идут 3 функции:

  1. check_3_user();
  2. check_2_comp();
  3. check_2_user();

Назначение этих функций описано (в виде трех этапов) в разделе Логика (выше). Эти функции вызываются по клику на любую из клеток поля. В каждую из функций передается параметр (znak) - это знак игрока или компьютера (крестик или нолик), например, в функцию, определяющую победу игрока (check_3_user), мы передаем знак игрока, для того, чтобы найти 3 одинаковых знака на одной линии.

После трех функций (если компьютер еще не сделал ход), компьютер заполняем одну из свободных клеток. Здесь, вы можете усложнить игровой процесс, например, сделав так, что если свободна центральная клетка (клетка под номером 5), то сначала ставить в нее, если она занята, то ставить в один из свободных углов (это клетки № 1, 3, 7 и 9) и так далее - в общем, здесь уже на ваше усмотрение.

Вот, в принципе и все, что требуется для создания подобной игры.

Теперь можете посмотреть игру на демо примере и скачать исходный файл (всего 1 файл).

08.06.2018 - спасибо за внимательность автору письма: Patvakan Baghdasaryan, исправлена ошибка, когда у компьютера было несколько возможных вариантов победы и закрашивались Все его победные ходы (от 4 до 6 клеток, вместо 3).

На этом у меня все, надеюсь, данный урок был полезен для вас, желаю вам удачи, пока!

ШАГ 1. НАСТРОЙКА ПАРАМЕТРОВ ФОРМЫ1. Для настройки формы установите ее размер
510 точек по горизонтали и 480 точек по
вертикале. Максимальный и минимальный
размер укажите равный этим же значениям.
2. Назовите форму словом «Крестики нолики».
3. Для фона формы используйте файл из папки
Images под имени фон.png, и разместите его
по центру формы.
4. Для пиктограммы в строке заглавия
используйте используйте файл из папки
Images под имени menu4.ico.
5. Цвет фона формы необходимо установить
MintCream.

ШАГ 2. ДОБАВЛЕНИЕ КНОПКИ И НАДПИСИ НА ФОРМУ

1. Для размещенных элементов изменяем
размер шрифта на 12 и фон устанавливаем
прозрачным.

1. Создаем строку меню с пунктами в
соответствии с указанными на изображении.

ШАГ 3. ДОБАВЛЕНИЕ СТРОКИ МЕНЮ НА ФОРМУ

1. В пункте меню Файл создаем команду
Выход.
2. Для
команды
Выход
прописуем
программный код аналогичный как и в
предыдущем приложении.

ШАГ 3. ДОБАВЛЕНИЕ СТРОКИ МЕНЮ НА ФОРМУ

1. В пункте меню Игра создаем команду
Новая игра.
2. Для команды Новая игра пропишем
программный код в дальнейшем через
несколько шагов.

ШАГ 3. ДОБАВЛЕНИЕ СТРОКИ МЕНЮ НА ФОРМУ

1. В пункте меню Справка создаем команду
О программе.
2. Для команды О программе создаем новую
форму и прописуем программный код
аналогичный как и в предыдущем
приложении.

1. Перетянув на форму объект PictureBox
изменяем ее размер на 100х100.
2. Задаем прозрачный фон.
3. Располагаем PictureBox как показано на
рисунке над первой клеткой поля игры.

ШАГ 4. ДОБАВЛЕНИЕ ОБЪЕКТОВ PICTUREBOX НА ФОРМУ

1
2
3
4
5
6
7
8
9
1. Над остальными ячейками располагаем
объекты PictureBox, копии первого
объекта, согласно нумерации указанной на
изображения.

1. Перед тем как написать программный код
необходимо в папку
\Visual Studio
2010\Projects\Крестики нолики \ крестики
нолики\bin\Debug\ необходимо перебросить
файлы x.png, 0.png, none.png из папки Images.
2. Кликаем два раза левой мыши по первому
PictureBox.

ШАГ 5. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ ОЪЕКТОВ PICTUREBOX

1. Создаем двухмерный массив доступный для всех элементов в
созданных в форме 3х3 элемента состоящий из целых чисел. Сразу
заполняем его при объявлении нулями. Для пустых ячеек мы
будем использовать 0, для «крестиков» - 1, а для «ноликов» - -1.

ШАГ 5. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ ОЪЕКТОВ PICTUREBOX

В процедуру при клике по первому
PictureBox, добавляем оператор
выбора
который
будет
осуществлять проверку состояния
ячейки массива. Если значение
ячейки массива будет равно 0, что
значит в ней нет ни «нуля» ни
«крестика», тогда в эту ячейку
массива записуется 1 и в
PictureBox1
отображается
изображение «крестика», а если
значение ячейки массива будет
равно 1 тогда в ней находится
«крестик» и в нее записывается 0, а
отображается пустая ячейка.

ШАГ 5. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ ОЪЕКТОВ PICTUREBOX

1
2
3
4
5
6
7
8
9



Для остальных клеток поля добавляем код
аналогично как и в первой лишь изменяя
номер объекта PictureBox и адрес ячейки
массива.
ПРИМЕР для второй ячейки:

В процедуру при клике по кнопке
добавляем
оператор
цикла
который осуществляет проверку
всех ячеек начиная от первой на
наличие пустых ячеек. И если
ячейка пустая,
то в нее
записывается «нолик», а именно в
ячейку массива записывается -1.
Для удобства в дальнейшей работе
переменные
I,
j
которые
используются для итерации циклов
объявим для всей формы.

ШАГ 6. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ КНОПКИ ХОДИТЬ

Для отображения ноликов на
игровом
поле
необходимо
добавить программный код в тело
цикла проверки ячеек на пустоту.
С помощью вложенного оператора
ветвления
будет
происходить
анализ адреса ячейки массива для
вывода нолика в правильном
PictureBox.
Также добавляем оператор break
для преждевременного окончания
цикла при нахождении пустой
ячейки.

ШАГ 6. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ КНОПКИ ХОДИТЬ

Для индикации состояния игры
используется элемент интерфейса
Label1. Так как игрок всегда ходит
первым
то
при
загрузки
приложения
необходимо
в
элементе
Label1
необходимо
отражать
словосочетания «Ваш
ход».
Для
этого
создадим
переменную
otvet
которой
присвоим это словосочетание. А
при загрузки формы переменную
необходимо присваивать элементу
Label1, для создания необходимой
процедуры
необходимо
предварительно дважды кликнуть
по форме

ШАГ 7. ДОБАВЛЕНИЕ ПРОГРАМНОГО КОДА ДЛЯ ПУНКТА МЕНЮ НОВАЯ ИГРА

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

ШАГ 8. ВЫВОД РЕЗУЛЬТАТА ИГРЫ

Для проверки результатов ходов
необходимо
анализировать
содержимое строк, столбцов и
диагоналей. Если все три элемента
равны 1 то это победа игрока, а
если три -1 то это поражение
игрока.
Проверку победы необходимо
проводить
перед
ходом
компьютера,
а
проверку
поражения после.
Последней командой процедуры
станет вывод на экран результатов
хода.

ШАГ 8. ВЫВОД РЕЗУЛЬТАТА ИГРЫ

Программный код для проверки победы пользователя:
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
if (znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1 && znacheniyeYacheyki == 1) otvet = "Вы победили";
Программный код для проверки победы пользователя:
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
if (znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1 & znacheniyeYacheyki == -1) otvet = "Вы проиграли";
label1.Text = otvet;

ШАГ 9 УЛУЧШЕНИЕ «ИГРАБЕЛЬНОСТИ»

Для улучшения «играбельности»
вместо последовательного вывода
в первые попавшиеся пустые
ячейки «ноликов», реализуем
вывод через генератор случайных
чисел.
Для этого необходимо добавить
одну логическую переменную
uslovie, и изменить тип цикла с For
на While, так как нам неизвестно
точное количество повторений
генератора случайных чисел пока
он не попадет в пустую ячейку.

МИНОБРНАУКИ РОССИИ

федеральное государственное бюджетное образовательное учреждение высшего профессионального образования

«Вологодский государственный университет»

Кафедра автоматики и вычислительной техники

Пояснительная записка к курсовому проекту по дисциплине

Программирование и основы алгоритмизации

«Крестики-нолики»

Выполнил студент группы ЭМ-21

Буторова Л.Ю.

Принял Ржеуцкая С. Ю.

ВВЕДЕНИЕ

1. АНАЛИЗ ЗАДАЧИ И ОПРЕДЕЛЕНИЕ ТРЕБОВАНИЙ К РАЗРАБАТЫВАЕМОЙ ПРОГРАММЕ

1 Назначение программы, ее пользователи, основные функции и цели, которые преследуются при разработке

2 Обзор известных программ, которые выполняют аналогичные функции

3 Теоретические основы разработки

4 Выбор инструментальных средств разработки

ПРОЕКТНАЯ ЧАСТЬ РАЗРАБОТКИ

1 Разработка пользовательского интерфейса

2.2 Разработка структур данных (во внешней и оперативной памяти)

2.3 Разработка и анализ алгоритмов

РЕАЛИЗАЦИЯ ПРОГРАММЫ НА ЯЗЫКЕ С++

1 Архитектура программы

2 Выбор стандартных визуальных и не визуальных компонентов

РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ

ЗАКЛЮЧЕНИЕ

Список используемой литературы

Приложения

ВВЕДЕНИЕ

Крестики-нолики - логическая игра между двумя противниками на квадратном поле 3 на 3 клетки или большего размера (вплоть до «бесконечного поля»). Один из игроков играет «крестиками», второй - «ноликами». Эта игра стала популярна задолго до появления компьютеров, только раньше в нее играли с помощью обычного листка бумаги и ручки. В традиционной китайской игре используются черные и белые камни.

В данной курсовой работе сохранены основные правила и стандартный размер поля игры (3х3 клетки). Для удобства игры право первого хода оставлено за пользователем, то есть «крестиками».

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

В игре присутствуют три типа: X против 0 - пользователь против пользователя, «1 уровень с компьютером» - для тех кто только осваивает азы мировой игры, и уровень «2 уровень с компьютером» - для тех, кто абсолютно уверен в своей победе. На 1 и 2 уровне возможны три исхода: «победа», «проигрыш» и «ничья». Выигрыш фиксируется, если крестиками или ноликами полностью заполняется вертикаль, горизонталь или диагональ.

Если свободные клетки поля закончились, но никто не одержал победы, то считается, что игра закончилась «ничьей».

1. АНАЛИЗ ЗАДАЧИ И ОПРЕДЕЛЕНИЕ ТРЕБОВАНИЙ К РАЗРАБАТЫВАЕМОЙ ПРОГРАММЕ

программа крестик интерфейс

1.1 Назначение программы, ее пользователи, основные функции и цели, которые преследуются при разработке

Назначение данной программы служит, прежде всего, для развлечения пользователей, для того, чтобы скрасить время ожидания человека, ведь в любой работе нужен отдых, а данная простенькая игра поможет расслабиться и отвлечься от повседневных дел. Так же «крестики - нолики» относят к классу интеллектуальных и логических игр, которые предназначены для тренировки логического мышления, позволяют сконцентрировать внимание и развивают память.

Целевой аудиторией пользователей являются дети и подростки, а так же взрослые. Главными критериями для использования продукта служит умение читать написанный в программе текст и умение выбирать с помощью кнопок необходимую задачу для компьютера.

Отсюда можно сделать вывод, что основными задачами являются: задача развлечения и задача развития логического потенциала человека.

1.2 Обзор известных программ, которые выполняют аналогичные функции

В сети Интернет можно найти большое количество работ, которые реализуют данную игру. В настоящее время существует много аналогов этой игры, которые отошли от первоначальных стандартов. Примером таким программ являются «Крестики-нолики на бесконечном поле» и «Крестики-нолики 3D». Так же во многих играх «крестики» и «нолики» заменяются на другие символы, такие как, например, «камни».

Мой курсовой проект представляет собой приложение для ПК. Игра предназначена как для одного пользователя, соперником которого является искусственный интеллект(или компьютер), так и для двух пользователей. Она представлена на классическом поле 3х3.

Наиболее интересной и необычной, по моему мнению, оказалась игра «Крестики-нолики 3D». Поэтому именно ее я выбрала для сравнение.

Трёхмерные крестики-нолики гораздо интереснее, чем на бумаге или на обычном поле. Здесь больше возможностей выиграть и проиграть, и ничья встречается реже. Играть можно одному - против компьютера - или вдвоём с другом. А самое необычное здесь то, что для выигрыша можно составить комбинацию из трех шаров своего цвета (черного или белого) не только на каком-либо одном уровне, но и по плоскости стенок и даже по диагонали всего поля (рис. 1.1).

Рис. 1.1

Среди большого разнообразия игр подобной тематики, можно выделить в каждой работе неповторимую реализацию задуманного. Каждый проект отличается от других своей индивидуальностью.

1.3 Теоретические основы разработки

Анализ

Для каждой из сторон общеизвестны алгоритмы, которые гарантируют ничью при любой игре противника, а при его ошибке позволяют выиграть. Таким образом, игра находится в состоянии «ничейной смерти» <#"877528.files/image002.gif">

Рис.1.2. Дерево игровых ситуаций

Частичное дерево игровых ситуаций, изображено на Рис.1.2 для игры крестики-нолики. Дерево игровых ситуаций для игры крестики-нолики, где игрок за «крестики» ходит первым и поступает по приведенному выше алгоритму, а игрок за «нолики» может поступать как угодно (причем приведено по одной вершине для рационального и для нерационального поступка, то есть любого другого), состоит из 50-ти узлов.

1.4 Выбор инструментальных средств разработки

Для реализации поставленных нами задачи необходима интегрированная среда разработки приложений. Поэтому разработка проекта проводилась в среде программирования Microsoft Visual Studio 2008.

Microsoft Visual Studio - линейка продуктов компании Microsoft , включающих интегрированную среду разработки программного обеспечения и ряд других инструментальных средств. Данные продукты позволяют разрабатывать как консольные приложения , так и приложения с графическим интерфейсом , в том числе с поддержкой технологии Windows Forms , а также веб-сайты , веб-службы как в родном , так и вуправляемом кодах для всех платформ, поддерживаемых Windows , Windows Mobile , Windows CE , .NET Framework , Xbox , Windows Phone .NET Compact Framework и Silverlight .

2. ПРОЕКТНАЯ ЧАСТЬ РАЗРАБОТКИ

2.1 Разработка пользовательского интерфейса

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

) Главное меню программы

Рис. 2.1 - Главное меню программы

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

) Игровое поле

Рис 2.2 - Игровое поле

Игровое поле содержит в себе непосредственную область для игры, куда игрок и компьютер ставят свои значки. Перед началом игры пользователь должен выбрать тип игры, такой как «X против 0», «1 уровень с компьютером» или «2 уровень с компьютером», иначе программа выдаст сообщение о том, что нужно сделать. Кнопка, которая поможет игроку вернуться в главное меню. В конце будут всплывать дополнительные окна, которые будут информировать участника о результатах поединка.

Рис. 2.3 - дополнительные окна исхода игры

2.2 Разработка структур данных (во внешней и оперативной памяти)

Оперативная память используется для одномерного массива, состоящего из 9 элементов, который хранит состояния игрового поля, где каждая ячейка массива соответствует клетке на игровом поле. Память так же расходуется под статические переменные: номер уровня, очередность хода.

Для её работы требуется 803 Кб свободной памяти.

.3 Разработка и анализ алгоритмов

Для реализации алгоритма мышления игры необходимо задать статический массив gcnew array (9); в котором будут храниться состояния игрового поля, где каждая ячейка массива соответствует клетке. «0» - соответствует пустой клетке, если в клетку сходил игрок, то есть «X», записывается значение «1» и если ход сделал компьютер, то есть «О», значение «2». Изначально все элементы массива равны «0». Необходимо задать статическую переменную lvl, которая хранит данные об уровне. Всего в этой игре 3 уровня: lvl принимает значение «1», если пользователь выбрал тип игры «X против О», значение «2», если «1 уровень с компьютером», и значение «3», если «2 уровень с компьютером». Переменная player - хранит очередность хода («true» - ход игрока, «false»- ход компьютера). Так как право первого хода предоставляется пользователю, то в начале игры player = true. Статическая переменная flag хранит информацию о том, есть ли пустые клетки на игровом поле или нет: если flag = true - то есть, false - пустых клеток нет. Алгоритм проверки должен содержать в себе перебор параметров массива х и выдвигать свое решение, которое будет оптимальным для дальнейшей игры. В данной программе представлено 2 уровня игры с компьютером. В 1 уровень задача компьютера не состоит в том, чтобы обыграть соперника. Поэтому данная функция возвращает рандомное значение клетки, куда будет ходить компьютер. В [Приложении 1] представлен код данного алгоритма. На Рис.2.4 представлена блок схема реализации кода.

Самый выйгрышный ход в начале игры, это ход в центр поле. В функции dif_level() в начале проверяется условие: если игрок не сходил в центральное поле, то туда ходит компьютер. Иначе, если игрок сходил в центр, то вызывается функия check(2) для проверки комбинации компьютера и, если есть возможность выиграть, то возврат номера клетки. Если же компьютер следующим ходом выиграть не может, то вызывается функция check(1): проверка комбинации игрока. Возвращается номер клетки, поставив бы в которую игрок выиграл. Если и такой комбинации нет, то вызывается функция low_level().

Рис.2.4. - Блок-схема

Рис.2.5. - Блок-схема

3. РЕАЛИЗАЦИЯ ПРОГРАММЫ НА ЯЗЫКЕ С++

.1 Архитектура программы

В данной программе реализованы 3 формы: главное меню (рис.2.1.), игровое поле (рис.2.2) и поле помощи (правила игры); 12 панелей, 9 из которых основные. Также по окончанию игры появляется pictureBox с результатом, всего их 5 (рис 2.3).

За основу можно взять обработчики нажатий панелей, которых ровно 9 на игровом поле. Каждый обработчик вызывает несколько функций. В начале идет условие, если пользователь выбирает тип игры «X против 0», просто клетки заполняются значениями 1 или 2 (крестик или нолик). Далее идут функции: индикация хода (CrossZero()), которая меняет крестик на нолик и наоборот, блокировка занятых клеток checkingArray(), нахождение победителя winner(). В функции winner() рассмотрены все возможные варианты выигрыша, поэтому, если кто-то из игроков выстроит в ряд 3 свои фигуры(крестик или нолик) по вертикали, горизонтали или диагонали, тот и победит. В противном случае, если поле заполнено, но никто из игроков не выстроил ряд, то вызывается функция (_friend())-проверка на ничью, которая проверяет, остались ли на поле свободные клетки или нет. Если fr = true, то на поле нет свободных клеток. Если значение изменилось, значит на поле нашлась свободная клетка.

Второе условие работает, если выбран второй или третий тип игры. Тогда вызывается функция, в которой осуществлен ход компьютера move(int n). В нее передается номер клетки, на которую нажал игрок. Далее идут функции: индикация хода (CrossZero()), блокировка занятых клеток checkingArray(). Затем вызывается функция winner(), которая проверяет, победил ли игрок этим ходом или нет. Если нет, то проверяется наличие свободных клеток. Если свободные клетки есть, то ходит компьютер. Далее, в зависимости от того, какой уровень выбрал игрок «1» или «2» вызываются функции соответственно: low_level(), dif_level(). Функция low_level() выбирает куда ставить нолик рандомно, а в функции dif_level() представлен специальный алгоритм победы компьютера. Далее идут функции: индикация хода (CrossZero()), блокировка занятых клеток checkingArray(). Затем вызывается функция winner(), которая проверяет, победил ли компьютер этим ходом или нет. Если нет, то проверяется наличие свободных клеток. Если свободные клетки есть, то ходит игрок.

.2 Выбор стандартных визуальных и не визуальных компонентов

Для реализации данной работы были выбраны следующие компоненты:

1) Form1, с заданными параметрами Text=Крестики-нолики, ControlBox=False

2) f2, с заданными параметрами BackColor, Text=Игра

) comboBox1 с заданными параметрами Items:

X против 0

1 уровень с компьютером

2 уровень с компьютером

4) panel, c заданными параметрами BackColor, и различными значениями параметра Visible и Enabled. Для некоторых panel были написаны такие события как Click.

5) button, c заданными параметрами Font, Fore Color, BackColor, Text, для всех button были написаны такие события как Click.

6) label, c заданными параметрами BackColor , Font, Fore Color, Text.

) pictureBox, c заданными параметрами Image, SizeMode= StretchImage.

) textBox, c заданными параметрами BackColor, Font, Fore Color, Text=” ”.

4. РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ

Проведем тестирование программы, пройдя 3 типа игры.

Пробуем действия кнопок главного меню. Кнопки работают исправно. Попытаемся начать игру, не выбрав типа игры. Программа выдает сообщение об ошибке и просит выбрать тип игры.(Рис.4.1)

Рис.4.1.

Выберем 1 тип игры - «X против 0», т.е. пользователь против пользователя. На данном этапе игры пользователь так же может играть сам с собой. (Рис.4.2)

Рис.4.2.

В процессе игры «1 уровень с компьютером» компьютер не ставит себе цель выиграть участника. Он просто ставит нолики в свободные места поля. На данном этапе пользователь может легко обыграть компьютер. Хотя на этом уровне возможны и остальные варианты развития событий.

Рис.4.3.

Тип игры «2 уровень с компьютером». На данном этапе игра анализирует все ходы и старается выбрать наиболее оптимальный ход. Здесь также возможны все три варианта развития событий, т.к. первый свой ход компьютер делает в любую свободную клетку. Чаще всего игра сводится к ничьей.

Рис.4.4.

Программа на все вариантах тестов работает успешно, без ошибок.

ЗАКЛЮЧЕНИЕ

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

В ходе работы были освоены новые методы программирования в интегральной среде разработки. Закреплены старые знания работы с языком С++. В ходе подготовки к курсовой работе были анализированы различные методы и алгоритмы реализации данной игры.

Не смотря на внешне кажущуюся простоту данной программы, он таит в себе ряд сложностей, которые реализуются с использованием всех основных приемов Visual C++.

Особенностями данной программы является:

Четко построенный алгоритм;

Интуитивно понятный интерфейс;

Простота в использовании;

Вполне понятное руководство пользователя;

Отсутствие лишних дополнений.

СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ

1. http://www.pravilaigr.ru/xo.php

2. http://2igroka.com/stuff/sportivnye/krestiki_noliki_3d/15-1-0-14

3. https://www.draw.io/

Http://pol-video.ru/QPW9QHEO2GU/uroki_s_krestiki-noliki_ch1.html

ПРИЛОЖЕНИЕ 1

private: int low_level(){// процедура для легкого противникаr;::Random^ rand = gcnew System::Random();{= rand->Next(0,8);

} while (x[r] != 0);r;

ПРИЛОЖЕНИЕ 2

private: bool check(int n){k = -1;// проверяет все комбинации, и возвращает правильный ход(x == n) {((x == n)&&(x == 0)) k =2;((x == n)&&(x == 0)) k =1;((x == n)&&(x == 0)) k =6;((x == n)&&(x == 0)) k =3;((x == n)&&(x == 0)) k =8;((x == n)&&(x == 0)) k =4;

}(x == n) {((x == n)&&(x == 0)) k =0;((x == n)&&(x == 0)) k =7;((x == n)&&(x == 0)) k =4;

}(x == n) {((x == n)&&(x == 0)) k =4;((x == n)&&(x == 0)) k =6;((x == n)&&(x == 0)) k =8;((x == n)&&(x == 0)) k =5;

}(x == n) {((x == n)&&(x == 0)) k =0;((x == n)&&(x == 0)) k =5;((x == n)&&(x == 0)) k =4;

}(x == n) {((x == n)&&(x == 0)) k =0;((x == n)&&(x == 0)) k =3;((x == n)&&(x == 0)) k =1;((x == n)&&(x == 0)) k =2;

}(x == n) {((x == n)&&(x == 0)) k =2;

}(x == n) {((x == n)&&(x == 0)) k =8;((x == n)&&(x == 0)) k =7;

}(x == n) {((x == n)&&(x == 0)) k =6;

}(k!=-1) return true;else return false;

ПРИЛОЖЕНИЕ 3

private: int dif_level(){//сложный противник

//return check(2);(x == 0) return (4);(check(2)) return k; else (check(1)) return k; else low_level();

ПРИЛОЖЕНИЕ 4

private: void CrossZero(){// меняет крестик на нолик(индикатор хода)(player) {->Visible = true;->Visible = false;

} else {->Visible = true;->Visible = false;

}: void checkingArray(){// функция проверки, есть ли в клетке что-то, если есть, то больше на эту клетку нажимать нельзя.(x == 1) {panel1->BackgroundImage = panel11->BackgroundImage;panel1->Enabled = false;}(x == 2) {panel1->BackgroundImage = panel10->BackgroundImage;panel1->Enabled = false;}(x == 1) {panel2->BackgroundImage = panel11->BackgroundImage;panel2->Enabled = false;}(x == 2) {panel2->BackgroundImage = panel10->BackgroundImage;panel2->Enabled = false;}(x == 1) {panel3->BackgroundImage = panel11->BackgroundImage;panel3->Enabled = false;}(x == 2) {panel3->BackgroundImage = panel10->BackgroundImage;panel3->Enabled = false;}(x == 1) {panel4->BackgroundImage = panel11->BackgroundImage;panel4->Enabled = false;}(x == 2) {panel4->BackgroundImage = panel10->BackgroundImage;panel4->Enabled = false;}(x == 1) {panel5->BackgroundImage = panel11->BackgroundImage;panel5->Enabled = false;}(x == 2) {panel5->BackgroundImage = panel10->BackgroundImage;panel5->Enabled = false;}(x == 1) {panel6->BackgroundImage = panel11->BackgroundImage;panel6->Enabled = false;}(x == 2) {panel6->BackgroundImage = panel10->BackgroundImage;panel6->Enabled = false;}(x == 1) {panel7->BackgroundImage = panel11->BackgroundImage;panel7->Enabled = false;}(x == 2) {panel7->BackgroundImage = panel10->BackgroundImage;panel7->Enabled = false;}(x == 1) {panel8->BackgroundImage = panel11->BackgroundImage;panel8->Enabled = false;}(x == 2) {panel8->BackgroundImage = panel10->BackgroundImage;panel8->Enabled = false;}(x == 1) {panel9->BackgroundImage = panel11->BackgroundImage;panel9->Enabled = false;}(x == 2) {panel9->BackgroundImage = panel10->BackgroundImage;panel9->Enabled = false;}

}: bool winner(){// проверка победителя и блокировка всех оставшихся клеток.

//bool flag = false;(((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2)) || ((x == x)&&(x == x)&&(x == 2))){(lvl==1) { picturePo->Visible = true;}{picturePr->Visible = true;}->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;true;

}(((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1)) || ((x == x)&&(x == x)&&(x == 1))){(lvl==1) { picturePx->Visible = true;}{picturePobeda->Visible = true;}->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;->Enabled = false;true;

}: void _friend(){fr = true;(int i = 0; i < 9; i++) if (x[i] == 0) {fr = false; break;}(fr) { pictureN->Visible = true;}

}: void move(int n){// функция хода компьютера= false;[n] = 1;= !player;();();(winner()) {} {(int i = 0; i < 9; i++) if (x[i] == 0) flag = true;(flag){(lvl == 2) = 2; = 2;= !player;();();();

}: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {// новая игра>Visible = false;>Visible = false;>Visible = false; >Visible = false; >Visible = false; = comboBox1->Text;(typeGame == ""){::Show("Сначало выбери тип игры!");

} else {(typeGame == "X против 0") lvl = 1;(typeGame == "1 уровень с компьютером") lvl = 2;(typeGame == "2 уровень с компьютером") lvl = 3;();= true;(int i = 0; i < 9; i++) x[i] = 0;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->BackgroundImage = panel12->BackgroundImage;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;->Enabled = true;

}: System::Void panel1_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 0;(lvl == 1){(player){ = 1;

}= !player;();();();

}: System::Void panel2_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 1;(lvl == 1){(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel3_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 2;(lvl == 1){(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel4_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 3;(lvl == 1){(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel5_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 4;(lvl == 1){(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel6_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 5;(lvl == 1) {(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel7_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 6;(lvl == 1) {(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel8_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 7;(lvl == 1) {(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void panel9_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {n = 8;(lvl == 1) {(player){ = 1;

}= !player;();();();

} else if ((lvl == 2)||(lvl == 3)){(n);

}: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {();

}: System::Void picturePx_Click(System::Object^ sender, System::EventArgs^ e) {>Visible = false;

}: System::Void picturePo_Click(System::Object^ sender, System::EventArgs^ e) {>Visible = false;

}: System::Void picturePobeda_Click(System::Object^ sender, System::EventArgs^ e) {>Visible = false;

}: System::Void picturePr_Click(System::Object^ sender, System::EventArgs^ e) {>Visible = false;

}: System::Void pictureN_Click(System::Object^ sender, System::EventArgs^ e) {>Visible = false;

Как написать бота, которого будет нельзя обыграть в «крестики-нолики», или Знакомство с правилом «минимакс»

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

Как и профессиональный шахматист, этот алгоритм просчитывает действия соперника на несколько ходов вперёд - до тех пор, пока не достигнет конца партии, будь то победа, поражение или ничья. Попав в это конечное состояние, ИИ начислит себе положительное количество очков (в нашем случае +10) за победу, отрицательное (-10) - за поражение, и нейтральное (0) - за ничью.

В то же время алгоритм проводит аналогичные расчёты для ходов игрока. Он будет выбирать ход с наиболее высоким баллом, если ходит ИИ, и ход с наименьшим, если ходит игрок. Используя такую стратегию, минимакс избегает поражения.

Попробуйте сыграть вот в такую игру.

Алгоритм «минимакс» проще всего описать в виде рекурсивной функции, которая:

  1. возвращает значение, если найдено конечное состояние (+10, 0, -10),
  2. проходит по всем пустым клеткам на поле,
  3. вызывает минимакс-функцию для каждой из них (рекурсия),
  4. оценивает полученные значения
  5. и возвращает наилучшее из них.

Если вы не знакомы с рекурсией, то вам стоит посмотреть эту лекцию из гарвардского курса CS50:

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

Реализация минимакса

Мы рассмотрим ситуацию, когда игра подходит к концу (смотрите картинку ниже). Поскольку минимакс проходит по всем возможным состояниям игры (а их сотни тысяч), имеет смысл рассматривать эндшпиль - так нам придётся отслеживать меньшее количество рекурсивных вызовов функции (всего 9).

Пусть ИИ играет крестиками, человек - ноликами.

Чтобы упростить работу с полем, объявим его как массив из 9 элементов со значениями, равными содержимому клеток. Заполним его крестиками и ноликами, как на картинке выше, и назовём origBoard .

Var origBoard = ["O",1,"X","X",4,"X",6,"O","O"];

Затем объявим переменные aiPlayer и huPlayer и присвоим им значения "X" и "O" соответственно.

Кроме того, нам потребуется функция, которая ищет победные комбинации и возвращает истинное значение в случае успешного поиска, и функция, которая хранит индексы доступных клеток.

/* начальное состояние доски O | | X --------- X | | X --------- | O | O */ var origBoard = [“O”,1 ,”X”,”X”,4 ,”X”, 6 ,”O”,”O”]; // человек var huPlayer = “O”; // ИИ var aiPlayer = “X”; // возвращает список индексов пустых клеток доски function emptyIndices(board){ return board.filter(s => s != "O" && s != "X"); } // победные комбинации с учётом индексов function winning(board, player){ if((board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player) || (board == player && board == player && board == player)) { return true; } else { return false; } }

Итак, давайте определим минимакс-функцию с двумя аргументами: newBoard (новое поле) и player (игрок). Затем найдём индексы свободных клеток на поле и передадим их в переменную availSpots .

// основная минимакс-функция function minimax(newBoard, player){ //доступные клетки var availSpots = emptyIndices(newBoard);

Кроме того, нам нужно отслеживать конечные состояния и возвращать соответствующие значения. Если побеждает «нолик», нужно вернуть -10 , если «крестик» - +10 . Если размер массива availSpots равен нулю, значит, свободных клеток нет, игра закончится ничьёй, и нужно вернуть ноль.

// проверка на терминальное состояние (победа / поражение / ничья) //and returning a value accordingly if (winning(newBoard, huPlayer)){ return {score:-10}; } else if (winning(newBoard, aiPlayer)){ return {score:10}; } else if (availSpots.length === 0){ return {score:0}; }

После этого нужно собрать очки с каждой из пустых клеток. Для этого создадим массив ходов moves и пройдём в цикле по всем пустым клеткам, помещая индексы и очки каждого хода в объект move .

Затем зададим индекс пустой клетки, который хранился в виде числа в origBoard , равным свойству-индексу объекта move . Потом сходим за текущего игрока на пустую клетку нового поля newBoard и вызовем функцию minimax от другого игрока и получившегося поля newBoard . После этого нужно поместить свойство score объекта, возвращённого функцией minimax , в свойство score объекта move .

Если минимакс не находит конечное состояние, он продолжает рекурсивное углубление в ход игры до тех пор, пока не достигнет терминального состояния. После этого он передаёт очки этого «уровня» рекурсии на один уровень выше.

И наконец, функция сбрасывает изменения newBoard и помещает объект move в массив moves .

// массив для хранения всех объектов var moves = ; // цикл по доступным клеткам for (var i = 0; i < availSpots.length; i++){ //create an object for each and store the index of that spot var move = {}; move.index = newBoard]; // совершить ход за текущего игрока newBoard] = player; //получить очки, заработанные после вызова минимакса от противника текущего игрока if (player == aiPlayer){ var result = minimax(newBoard, huPlayer); move.score = result.score; } else{ var result = minimax(newBoard, aiPlayer); move.score = result.score; } // очистить клетку newBoard] = move.index; // положить объект в массив moves.push(move); }

Затем минимаксу нужно выбрать наилучший ход move из массива moves . Ему нужен move с наибольшим счётом, если ходит ИИ, и с наименьшим, если это ход человека. Таким образом, если значение player равно aiPlayer , алгоритм инициализирует переменную bestScore очень маленьким числом и идёт циклом по массиву moves: если ход move приносит больше очков score , чем bestScore , алгоритм запоминает этот move . В случае ходов с одинаковыми очками алгоритм запоминает первый из них.

В случае, когда player равен huPlayer , всё аналогично - только теперь bestScore инициализируется большим числом, а минимакс ищет ход move с наименьшим количеством очков.

В итоге минимакс возвращает объект, хранящийся в bestMove .

// если это ход ИИ, пройти циклом по ходам и выбрать ход с наибольшим количеством очков var bestMove; if(player === aiPlayer){ var bestScore = -10000; for(var i = 0; i < moves.length; i++){ if(moves[i].score > bestScore){ bestScore = moves[i].score; bestMove = i; } } }else{ // иначе пройти циклом по ходам и выбрать ход с наименьшим количеством очков var bestScore = 10000; for(var i = 0; i < moves.length; i++){ if(moves[i].score < bestScore){ bestScore = moves[i].score; bestMove = i; } } } // вернуть выбранный ход (объект) из массива ходов return moves; }

В следующем разделе мы смоделируем работу нашей программы, чтобы понять, как она работает.

Минимакс в действии

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

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

  1. Алгоритму подаются origBoard и aiPlayer . Он составляет список из трёх найденных пустых клеток, проверяет конечность состояния, и проходит циклом по всем пустым клеткам. Затем алгоритм меняет newBoard , помещая aiPlayer в первую пустую клетку. После этого он вызывает сам себя от newBoard и huPlayer и ждёт, пока второй вызов вернёт значение.
  2. Пока первый вызов функции всё ещё работает, запускается второй, создавая список из двух пустых клеток, проверяя конечность состояния и проходя циклом по всем пустым клеткам. Затем второй вызов изменяет newBoard , помещая huPlayer в первую пустую клетку. После этого он вызывает сам себя от newBoard и aiPlayer и ждёт, пока третий вызов вернёт значение.

  3. Поскольку второй вызов обнаружил две пустые клетки, минимакс изменяет newBoard , помещая huPlayer во вторую свободную клетку. Затем он вызывает сам себя от newBoard и aiPlayer .

  4. Алгоритм составляет список пустых клеток и фиксирует победу игрока после проверки конечности состояния. Поэтому он возвращает объект с полем счёта, равным (-10).

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

    На этот момент первый вызов функции получил оценку хода aiPlayer в первую пустую клетку. Затем он изменяет newBoard , помещая aiPlayer во вторую пустую клетку. После этого он вызывает сам себя от newBoard и huPlayer .

  5. В пятом вызове функции алгоритм составляет список пустых клеток и фиксирует победу ИИ после проверки конечности состояния. Поэтому он возвращает объект с полем счёта, равным +10.

    После этого первый вызов изменяет newBoard , помещая aiPlayer в третью пустую клетку. Затем он вызывает сам себя от newBoard и huPlayer .

  6. Шестой вызов составляет список из двух пустых клеток, проверяет конечность состояния и идёт циклом по всем пустым клеткам. Затем он изменяет newBoard , помещая huPlayer в первую пустую клетку. Потом он вызывает сам себя от newBoard и aiPlayer и ждёт, пока седьмой вызов вернёт значение.
  7. Новый вызов составляет список из одной пустой клетки, проверяет конечность состояния и изменяет newBoard , помещая aiPlayer в пустую клетку. После этого он вызывает сам себя от newBoard и huPlayer и ждёт, пока этот вызов вернёт значение.
  8. Восьмой вызов составляет пустой список пустых клеток и фиксирует победу aiPlayer после проверки конечности состояния. Поэтому он возвращает объект с полем счёта, равным (+10), на уровень выше, седьмому вызову.

    Седьмой вызов получил лишь одно, положительное значение от нижних уровней. Поскольку это значение было получено в ход aiPlayer , алгоритм возвращает наибольшее из полученных значений. Поэтому он возвращает положительное значение (+10) на уровень выше, шестому вызову.

    Поскольку шестой вызов обнаружил две пустых клетки, минимакс изменяет newBoard , помещая huPlayer во вторую пустую клетку. Затем он вызывает сам себя от newBoard и aiPlayer .

  9. После этого алгоритм составляет список пустых клеток и фиксирует победу aiPlayer после проверки конечности состояния. Поэтому он возвращает объект с полем счёта, равным (+10), на уровень выше.

    На этом этапе шестой вызов должен выбрать между счётом (+10), который вернул седьмой вызов, и счётом (-10), который вернул девятый вызов. Поскольку ход huPlayer принёс эти два результата, алгоритм выбирает наименьший из них и возвращает его на уровень выше в виде объекта с полями счёта и индекса.

    Наконец, все три ветви первого вызова оцениваются (-10, +10, -10). Поскольку ход aiPlayer принёс эти три результата, алгоритм выбирает объект, содержащий наибольшее количество очков (+10) и его индекс (4).

В рассмотренном выше сценарии минимакс решает, что оптимальным выбором будет ход в центральную клетку поля.

Конец!

К этому моменту вы должны были понять, как устроен алгоритм минимакс. Попробуйте написать его реализацию самостоятельно или посмотрите пример на GitHub или CodePen и оптимизируйте его.

Если вас заинтересовала тема ИИ в играх, советуем почитать наши материалы по этой теме.

Внимание! Перед вами ознакомительная версия урока, материалы которого могут быть неполными.

Войдите на сайт как ученик

Войдите как ученик, чтобы получить доступ к материалам школы

Создание конфигураций 1С: пишем "Крестики-нолики" часть 1/3

Учиться мы будем играючи, а потому нашим первым проектом будет создание всем
знакомой с детства игры - "Крестики-нолики".

Вы спросите какое отношение игры имеют к 1С, бухгалтерскому учету и торговле? Почти никакого. Но начинать нужно постепенно и со временем мы дойдём и до автоматизации складов. А пока начнём с малого.

Перед тем как начать программирование игры "Крестики-Нолики" давайте поразмышляем.

Мы уже знаем, что у формы есть Элементы, одним из которых является Кнопка. Кнопки способны выполнять команды, и, в то же время, обладают свойствами, которые позволяют управлять их отображением на форме (например, заголовок).

Например, можно использовать кнопку для того, чтобы создать поле с девятью активными областями (те клетки на которые мы кликаем и фиксируем действие, одновременно отображая надписи в виде "О" и "Х"). Кнопка нам более чем подходят для этого.

Что нам понадобится? Очевидно, нам понадобится запоминать наш ход и запоминать ход компьютера. Нам также понадобится менять заголовки кнопок: при нашем клике заголовок кнопки всегда "О", при ходе компьютера - "Х".

А для начала нам понадобится создать новую базу данных, в которой мы будем творить нашу игру. Давайте же сделаем это.

Шаг №1: создание пустой базы

Создадим пустую базу "Крестики-нолики".

Подробные инструкции

Запустим ярлык 1С, чтобы открылся список информационных баз, имеющихся на компьютере. Вы читаете ознакомительную версию урока, полноценные уроки находятся . Нам требуется создание новой базы, поэтому жмём кнопку "Добавить ":

Откроется окно добавления информационной базы, в котором требуется выбрать первый пункт "Создание информационной базы " и нажать кнопку "Далее":

В следующем окне, выбираем второй пункт "Создание информационной базы без конфигурации для разработки новой конфигурации... " и снова жмём кнопку "Далее":

В следующем окне нам предлагают ввести наименование новой базы, под которым она будет отображаться в списке баз. Введём "Крестики-нолики " и нажмём кнопку "Далее":

В следующем окне необходимо указать путь к пустой папке в которой будет храниться наша база. В данном случае я создал папку "Крестики-нолики " в папке "Базы 1С" на диске D:

В следующем окне оставляем все настройки по умолчанию и нажимаем кнопку "Готово ":

После непродолжительной паузы база создана и добавлена в список. Есть два основных режима работы с базой: 1С:Предприятие и Конфигуратор :

В режиме конфигуратора мы делаем настройку и программирование базы, в режиме 1С:Предприятие смотрим, что из этого получилось.

Шаг №2: открываем конфигуратор

Нажмём кнопку "Конфигуратор ", чтобы войти в режим конфигуратора:

Шаг №3: открываем дерево конфигурации

Выполним команду меню "Конфигурация "->"Открыть конфигурацию ":

Перед нами открылось дерево конфигурации, которое содержит различные разделы конфигурации. Так как мы ещё ничего не создавали, пока эти разделы пустые:

Шаг №4: добавляем обработку

Для размещения логики нашей игры воспользуемся разделом "Обработки". Нажмём правой кнопкой на разделе "Обработки " и выберем команду "Добавить":

Перед нами открылось окно создания новой обработки. Введем имя "КрестикиНолики ". Синоним подставится сам. Этого достаточно для того, чтобы сохранить нашу обработку (пока ещё пустую) в базе. Нажмём кнопку "Закрыть":

Шаг №5: первая отладка программы

Проверить, что получилось можно из режима пользователя (1С:Предприятие ). Чтобы попасть в него прямо из конфигуратора выполним команду меню "Отладка "->"Начать отладку ":

Так как мы внесли изменение в базу нас спрашивают согласны ли мы принять это изменение. Такой вопрос нам будет задаваться постоянно в процессе разработки. Отвечаем согласием (кнопка "Да "):

База запустилась в режиме "1С:Предприятие". Вы читаете ознакомительную версию урока, полноценные уроки находятся . Но как мы видим работать с ней пока сложно - просто не из чего выбирать. Странно, ведь обработку мы уже создали и по идее она должна появиться на жёлтой панели.



 

Пожалуйста, поделитесь этим материалом в социальных сетях, если он оказался полезен!