крестики нолики c windows form

Крестики-нолики: код программы на C#

крестики нолики c windows form

Код игры «крестики-нолики» на языке программирования C#

Доброго времени суток. На данной странице я приведу пример создания всеми любимой логической игры под названием «крестики-нолики». Так как программирую я в основном на C#, то и представленная программа будет так же на данном языке программирования. Но даже если Вы не знакомы с C#, а программируете на C++, Java, Delphi или на любом другом языке, данный пост в любом случае поможет Вам, так как тут я приведу алгоритм работы такой программы.

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

Всю программу можно поделить на две большие части:

Далее я опишу стратегию игры компьютера, которого мы собираемся сделать непобедимым.

Стратегия игры «крестики-нолики»

Основополагающие правила

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

Компьютер ходит первым

Компьютер свой первый ход делает в центр. Тут другого варианта нет. Дальше ходит пользователь, у которого есть только два варианта: либо сходить в угол, либо сходить на сторону. Если пользователь ходит в угол, то компьютер отвечает ходом в противоположный угол. Если же пользователь ходит на сторону, то компьютер должен сходить в один из углов, которые расположены дальше всего от хода пользователя. Таких углов, если Вы понимаете о чем я, всего два. Разницы в какой из них сходить нет. Если эти противоположные углы заняты, компьютер ходит в любое свободное место(это условие актуально и если пользователь сходил в угол, и если он сходил на сторону). Данное правило ходить как можно дальше от предыдущего хода соперника актуально до конца игры, в которой компьютер сходил первым.

Компьютер ходит вторым

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

Вот такая стратегия игры в «крестики-нолики». Она не слишком сложная, и теперь мы попытаемся ее реализовать.

Реализация программы «крестики-нолики»

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

Проверка на возможность победы

Перед тем как писать код, необходимо уяснить смысл ее работы. Выигрышных комбинаций всего 8. Можете посмотреть скриншоты: я специально выделил и указал номер этих комбинаций(они понадобятся нам). Эти номера комбинаций будет хранить переменная cherta, которая сразу после выигрыша отдаст команду нарисовать выигрышную черту.

крестики нолики c windows form
крестики нолики c windows form
крестики нолики c windows form
Мы должны проверить каждую из этих комбинаций на предмет того, стоят ли там две наши крестики(или нолики), а так же свободно ли третья клетка. Но перед тем как мы перейдем к этому, я немного познакомлю вас с другой основой программы.

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

Далее нам необходимо будет создать специальный метод, который будет отрисовывать игровое поле, используя данные этой матрицы(метод paint, который будет мелькать в коде). А вот собственно и код данного метода:

Источник

Исправить код игры «Крестики-нолики»
игру ‘крестики нолики’ с++ с помащу функций пример(не работаєт) #include using.

«Крестики-нолики»: проверить, закончена ли игра выигрышем «крестиков»
Клеточное поле размером 5х5 является результатом игры в крестики-нолики. проверить, закончена игра.

А поиском воспользоваться?

Комментарий модератора
крестики нолики c windows formП3.3. Правил
Не отсылайте других пользователей в поиск и избегайте ссылок на поисковые системы (Google, Yandex и др.). Самостоятельно найдите ответ на вопрос и разместите его на форуме.

Алексаша, недавно писал эту игру в консоли. Смотрите код ниже.

Добавлено через 6 минут

Марков хорошо объясняет, ну всегда приятнее делать это самому.

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

Мозг плавился но за ночь все таки сумел написать. Посмотрев продолжение урока, понял что у меня ужасный код и что Денис все умудрился уместить в 140 строк. Дал себе скидку на то что С++ изучаю неделю и забил на это крестики нолики c windows form

Вот сам код если кому интересно. От оценки тоже не откажусь.

Добавлено через 12 минут
Вот алгоритм победы и ничьей:

Источник

Крестики нолики «Без границ»

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

крестики нолики c windows form

Эта статья про процесс разработки ИИ на javascript для игры в одну из таких вариаций крестиков-ноликов: получилось довольно много материала, но я разбавил его анимацией и картинками. В любом случае, хотя бы стоит попробовать поиграть в это.
Отличия этого варианта игры от оригинала в следующем:

Перед тем, как начнем

Вынужден извиниться заранее за объем статьи и местами не совсем доходчивое изложение мысли, однако у меня не получилось сжать стаю без потери в содержании и качестве.
Рекомендую сначала ознакомиться с результатом. Код

Горячие клавиши и команды:

Начнем

Начать нужно с реализации самой игры, т.е. написать приложение для двух игроков, пока без бота. Для своих целей я решил использовать javascript + jquery + bootstrap4, хотя он там практически не используется, но его лучше оставить – или таблица поплывет. Тут рассказывать особо нечего, материала по js, jquery и bootstrap на хабре полно. Скажу лишь, что использовал MVC. Да и вообще, объяснять абсолютно весь код я не буду – материала и без того получилось много.

Итак, игровое поле было готово. Можно устанавливать фигуры в клетки. Но победа кого-либо из игроков никак не фиксировалась.

Сканирование «конца игры»

Игра заканчивается, когда один из игроков поставит 5 фигур в ряд. «Все просто!» — подумал я. И начал сканировать абсолютно все клетки поля: сначала все горизонтали, потом вертикали и, наконец, диагонали.

Это тупой способ, но он работал. Однако, его можно было значительно улучшить, что я и сделал: Большая часть клеток будет оставаться пустой на протяжении всей игры – игровое поле слишком большое, чтоб его можно было заполнить целиком. Поскольку сканировать его нужно было каждый ход, а за один ход ставится только одна фигура — то можно сосредоточиться только на этой фигуре (клетке): просканировать только одну горизонталь, вертикаль и две диагонали клетки, которым принадлежит та самая клетка.

Плюс ко всему, не нужно сканировать все клетки линий. Поскольку конец игры – это 5 фигур в ряд, то фигуры, удаленные друг от друга на 6 клеток нас не интересуют. Достаточно сканировать по пять клеток в каждую из сторон. Не понятно? Смотри анимацию ниже.

крестики нолики c windows form

Приступим к самому боту

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

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

Терминология

Крестики и нолики – это фигуры.
Атакой будем называть несколько одинаковых фигур, стоящих рядом, на одной линии. По сути, это множество. Количество фигур в атаке – её мощность. Одна отдельная фигура – тоже атака (мощностью 1).

На соседних клетках атаки (на концах) могут быть пустые клетки или фигуры противника. Логично думать, что атаку с двумя пустыми клетками на «концах», мы можем развивать в двух направлениях, что делает ее более перспективной. Количество пустых клеток на «концах» атаки будем называть её потенциалом. Потенциал может принимать значения 0, 1 или 2.
Атаки обозначаем так: [ мощность атаки, потенциал ]. Например, атака [4:1].

крестики нолики c windows form
Рис 1. Атака [4:1]

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

Суть анализа

Представим, что на игровом поле уже есть несколько атак одного и второго игрока. Кто-то из игроков делает ход (пускай крестики). Естественно ход он делает в пустую клетку – и тем самым он может:

Суть анализа в следующем:

По сути, таким алгоритмом мы проверяем, что будет, если мы пойдем так… а что будет если так пойдет оппонент. Мы смотрим на один ход вперед и выбираем наиболее подходящую клетку – с наибольшим весом.

Если какая-то клетка имеет больший вес, нежели другая, значит она приводит к созданию более опасных атак, либо к блокировке сильных атак противника. Все логично… мне кажется.
Если зайти на страницу и написать в консоли SHOW_WEIGHTS = true, можно визуально прочувствовать работу алгоритма (Будут показаны веса клеток).

Веса атак

Пораскинул я мозгами и привел такое соответствие атак и весов:

Подобрано эмпирически – возможно это не оптимальный вариант.

Я добавил в массив атаки мощностью 5 с запредельно большим весом. Объяснить это можно тем, что бот анализирует игру, смотря на шаг вперед (подставляя фигуру в клетку). Пропуск такой атаки есть ни что иное, как поражение. Ну или победа… смотря для кого.

Атаки с большим потенциалом ценятся выше.

Атака [4:2] в большинстве случаев решает исход игры. Если игроку удалось создать такую атаку, то оппонент уже не сможет ее заблокировать. Однако это еще не победа. Противник может быстрее завершить игру, даже при наличие у нас на поле атаки [4:2], поэтому ее вес ниже, чем у атак мощностью 5. Смотри пример ниже.

крестики нолики c windows form
Рис 2. Атака [4:2]

«Рваные» атаки

В этом абзаце код не представлен. Здесь мы вводим понятие делителя атаки и объясняем суть «рваных атак».

Рассмотрим такую ситуацию: при подстановке фигуры на удалении нескольких пустых клеток, но не более 5-и, расположена еще одна.

И вроде бы, две одинаковые фигуры, на одной линии… визуально это похоже на атаку, а по факту нет. Не порядок, так как такие «рваные» атаки также несут в себе потенциальную угрозу.

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

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

Алгоритм поиска атак

Во-первых, создадим класс атаки. У атаки будет 3 атрибута, о которых я писал ранее:

И один метод, который будет возвращать вес данной атаки:

Далее. Поиск всех атак для одной клетки мы разделим на:

Однако, нам не нужно проверять всю линию целиком. Максимальная мощность атаки, которая нас интересует – 5. Безусловно, создать атаку мощностью, скажем, 6 – возможно. Но для ИИ, который анализирует игровую ситуацию следующего хода, все равно, что 6, что 5. Перспектива получить одну из этих атак говорит о конце игры на следующем ходу. Соответственно, вес анализируемой клетки будет в обоих случаях будет одинаковым.

Здесь надо остановиться, так как может возникнуть вопрос: зачем проверять 6-ую клетку, если максимальная мощность атаки – 5. Ответ – это нужно для определения потенциала удаленной от центра атаки.

Вот пример: атака мощностью 1 на картинке находится на границе сканируемой области. Чтобы узнать потенциал этой атаки нужно «заглянуть за границу».

крестики нолики c windows form
Рис. 3. Сканирование 6-ых клеток. Если не просканировать 6-ую клетку, можно неправильно определить потенциал атаки.

Для завершения некоторых атак может просто не хватать места. Посчитав attackplace мы заранее можем понять, какие из атак бесперспективны.

крестики нолики c windows form
Рис. 4. Место для атаки

1) Начнем с центральной клетки. Она должна быть пустой (мы ведь собираемся сделать в нее ход, не так ли? Однако мы не забываем, что наш ИИ должен подставлять фигуры в данную клетку для анализа следующего хода. Фигура, которую мы подставляем – this.subfig – по умолчанию крестик. Поскольку центральная клетка изначально будет содержать в себе какую-либо фигуру после подстановки, то она будет принадлежать какой-то атаке this.curAttack:

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

2) Далее, уменьшая итератор, перебираем 5 клеток с одной стороны от сканируемой. За это отвечает функция getAttacks( cellX, cellY, subFig, dx, dy ), где:

cellX, cellY – координаты проверяемой клетки
subFig – фигура, которую мы подставляем в проверяемую клетку
dx, dy – изменения координат x и y в циклах – так мы задаем направление поиска:

Обратите внимание, что если checkCell() что-то вернет, то выполнение цикла прекращается.

3) Проверяем фигуры данных клеток.
За это отвечает функция checkCell( x, y ):

Для начала запишем фигуру в переменную fig:
Model.Field – наше игровое поле.

fig может быть ‘x’, ‘o’, ‘b’ (граница), 0 (пустая клетка).

4) Если на 5-ой клетке фигура совпадает с центральной клеткой, значит атака «уперлась» в границу и для определения потенциала атаки придется «проверить границу» ( this.checkEdge = true).

Функция checkCell – готова. Однако продолжаем работать над классом checkLine.

5) После выполнения первого цикла, надо «развернуться». Переводим итератор в центр и центральную атаку, с индексом 0, убираем из массива атак и устанавливаем как текущую.

6) Далее идем в другую сторону от текущей клетки, увеличивая итератор.
Абсолютно такая же проверка фигур. (Код уже написан – функция getAttacks)

7) Все, мы собрали все атаки, что были на линии в один массив.
На этом с классом checkLine всё… закончили.

Ну а дальше все просто – создаем объект checkLine для каждой из линий (2 диагонали, горизонталь и вертикаль) и вызываем функцию getAttacks. То есть, для каждой линии — свой объект checkLine и, соответственно, свой набор атак.

Пусть за все это отвечает функция getAllAttacks() – уже отдельно от описанных выше классов;

На выходе имеем объект со всеми атаками для проверяемой клетки

Однако вы, возможно, заметили некую функцию-фильтр. Ее задача – отсеивать «бесперспективные» атаки:

Да соберем, наконец, все воедино

Итак, основной ад позади — описан выше. Пора слепить из него что-то рабочее. Функция countWeight( x, y ) — принимает на вход координаты клетки, а возвращает ее вес. Что же у нее под капотом?

Во-первых, получим массив всех атак, которым клетка принадлежит. ( getAllAttacks( x, y ) ). Перебирая все линии, мы считаем количество брейкпоинтов. Если 2 брейкпоинта – вспоминаем, что такая ситуация может решить исход игры, и увеличиваем вес клетки на 100.
Однако все брейкпоинты должны принадлежать одному игроку, поэтому пришлось реализовать проверку в 2 шага: сначала крестики, потом нолики.

Поскольку в массиве весов атак ( ATTACK_WEIGHTS[] ) я не предусмотрел атаки мощностью 6 и больше, мне пришлось заменить их на атаки мощностью 5. Разницы никакой – все они приводят к концу игры.

Ну и суммируем веса атак – к этому все и шло.

Еще небольшой момент: чтобы бот в конце игры не тупил, когда он уже построил атаку мощностью 4 и думает над текущем ходом, необходимо значительно увеличить вес клетки для завершения такой атаки. Без этого, ИИ, просто на просто, может начать защищаться от «опасных» атак оппонента, хотя игра, казалось бы выиграна. Последний ход важен.

Теперь при вызове этой функции для конкретной клетки мы получим ее вес. Проводим эту операцию для всех клеток и выбираем наилучшую (с наибольшим весом). Туда и ходим)

Остальной код вы сможете найти на github. Материала уже предостаточно, и его изложение, как я не пытался, оставляет желать лучшего. Но если ты смог дочитать до этого момента, дорогой читатель, то я тебе благодарен.

Мое мнение о полученном результате

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

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

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

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

Источник

Studhelper IT

Разработка приложений, переводы книг по программированию

Страницы

крестики нолики c windows form

пятница, 3 января 2014 г.

Крестики-нолики С++

Игра «Крестики-нолики» 3х3 с компьютером, C++ Builder 6

Один из вариантов реализации игры «Крестики-нолики» или «Tic-tac-toe», играют игрок и компьютер. Поскольку играет компьютер, то нужно разработать его тактику защиты и тактику нападения.

крестики нолики c windows form

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

крестики нолики c windows form

крестики нолики c windows form

void __fastcall TForm1::FormCreate(TObject *Sender)
<
iPict=0; //по умолчанию игрок играет крестиками
iTurn=0; //и имеет право первого хода
GameStatus=iTurn; //игровой статус – ход игрока
gamerIcon = new Graphics::TBitmap; //создание новых графич.
compIcon = new Graphics::TBitmap; //объектов
back = new Graphics::TBitmap;

back->LoadFromFile(«List1.bmp»); //загрузка рисунка для фона из файла
Form1->im1->Canvas->Draw(0,0,back); //рисунок размещается на канве
//компонента im1
BackRct=Bounds(0,0,im1->Width,im1->Height); //сохраняем область чистого поля
// для новой игры

NewGame();
>

void __fastcall NewGame(void)
<
Form1->Timer1->Enabled=false;
for(int p=0;p im1->Canvas->CopyRect(BackRct,back->Canvas,BackRct); //на канве
//восстанавливается ранее сохраненная область для копирования – чистый фон

if(iPict==0) //загрузка рисунков
<
gamerIcon->LoadFromFile(«Krestik.bmp»);
compIcon->LoadFromFile(«Nolik.bmp»);
>
else
<
gamerIcon->LoadFromFile(«Nolik.bmp»);
compIcon->LoadFromFile(«Krestik.bmp»);
>
//определение прозрачности
gamerIcon->Transparent=true;
gamerIcon->TransparentColor=gamerIcon->Canvas->Pixels[1][1];
compIcon->Transparent=true;
compIcon->TransparentColor=compIcon->Canvas->Pixels[1][1];
if(iTurn==1) //если первый ход у компьютера, он на первом шаге
//занимает среднюю клетку для контроля максимального
//количества линий

<
a[4]=1;
GameStatus=0; //переход хода игроку
Form1->im1->Canvas->Draw(xPos[4]+7,yPos[4]+7,compIcon); //на клетку
//ставится значок компьютера

>
else GameStatus=0;
Form1->Timer1->Enabled=true;
>

void __fastcall Victory(int v)
<
Form1->Timer1->Enabled=false;
if(v==1)
<
ShowMessage(«Победа компьютера»);
Form1->StatusBar1->Panels->Items[0]->Text=»Победа компьютера»;
>
else if(v==2)
<
ShowMessage(«Вы победили»);
Form1->StatusBar1->Panels->Items[0]->Text=»Победа игрока»;
>
else if(v==3)
<
ShowMessage(«Ничья»);
Form1->StatusBar1->Panels->Items[0]->Text=»Ничья»;
>
>

void _fastcall Step(void)
<
int number;
number=Varning(1);//есть ли линия с двумя занятыми компьютером //клетками
if(number!=-1)//если есть, занимает свободную клетку и побеждает
<
a[number]=1;
Form1->im1->Canvas->Draw(xPos[number]+7,yPos[number]+7,compIcon);
return;
>
if(GameStatus!=0)
<
number=Varning(2); //есть ли линия с двумя занятыми игроком клетками
if(number!=-1) //если есть, занимает свободную клетку, чтобы не победил
//игрок
<
a[number]=1;
Form1->im1->Canvas->Draw(xPos[number]+7,yPos[number]+7,compIcon);
return;
>
if(a[4]==0) //если свободна средняя клетка – занять ее
<
a[4]=1;
Form1->im1->Canvas->Draw(xPos[4]+7,yPos[4]+7,compIcon);
return;
>
for(int p=0;p im1->Canvas->Draw(xPos[p]+7,yPos[p]+7,compIcon);
return;
>
>
for(int p=0;p im1->Canvas->Draw(xPos[p]+7,yPos[p]+7,compIcon);
return;
>
>
>
>

void __fastcall TForm1::im1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y) //нажатие левой кнопки мыши на игровом поле
<
int k, number;
if((!((X 230)||(Y>230)))&&(GameStatus==0)) //если нажатие в
//в пределах игрового поля и статус – ход игрока

<
//определение номера клетки, на которую нажал игрок
if(Y =yPos[6]) k=6;
else k=3;
if(X =xPos[k+2])number=k+2;
else number=k+1;
if(a[number]==0) //если эта клетка свободна
<
a[number]=2; //занимаем ее
im1->Canvas->Draw(xPos[number]+7,yPos[number]+7,gamerIcon);
//проводим проверку на победу
if(!Checking())
<
GameStatus=1;
Timer1->Enabled=true;
>
else Victory(Checking());
>
>
>

Работа с формой Form2 – нажатие кнопки OK:
Работа с формой настроек, присваиваем значения внешним переменным в зависимости от выбора пользователя и сразу начинаем новую игру с новыми настройками. Форма в режиме разработки:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *