Введение
В условиях постоянного развития цифровых технологий и растущего интереса школьников к игровым технологиям особую актуальность приобретают проекты, которые сочетают развлечение с формированием ценностных ориентиров. Тема сельского хозяйства является неотъемлемой частью нашего историко-культурного наследия и связана с понятием трудолюбия и бережного отношения к земле.
Актуальность данной работы обусловлена необходимостью разработки игрового продукта, способствующего патриотическому воспитанию молодежи. В ходе исследования мной были проанализированы алгоритмы классической игры «Крестики-нолики» и реализован собственный проект – игра «Битва за урожай», в которой аграрные символы Беларуси (картофель и пшеница) представлены в игровой форме.
Выбор именно игры «Крестики-нолики» обусловлен несколькими причинами. Во-первых, это одна из самых простых и понятных игр, знакомая каждому школьнику, что делает её доступной широкой аудитории. Во-вторых, она предоставляет отличную основу для изучения и реализации алгоритма искусственного интеллекта Minimax, позволяя продемонстрировать работу логики принятия решений. В-третьих, благодаря структуре игрового поля, «Крестики-нолики» легко адаптируется под различное визуальное и тематическое оформление.
Использование алгоритма Minimax позволяет создать достойного и непредсказуемого соперника, что делает игру более интересной.
Цель моей работы – разработать и проанализировать цифровую игру с патриотическим содержанием, основанную на алгоритмах классической игры «Крестики-нолики».
Для достижения поставленной цели были поставлены следующие задачи исследования:
изучить структуру и стратегические особенности игры «Крестики-нолики»;
освоить алгоритм Minimax для создания искусственного интеллекта;
спроектировать визуальный стиль и игровую механику с акцентом на белорусскую символику;
реализовать проект в среде Unity;
проанализировать полученный результат и выявить его воспитательный потенциал.
Теоретические основы игры «Крестики-нолики» и алгоритма Minimax
«Крестики-нолики» – классическая логическая игра для двух игроков, цель которой – выстроить три одинаковых символа (крестика или нолика) по горизонтали, вертикали или диагонали на поле 3×3. Она имеет строго определённое множество стратегий, т.е. её исход можно предсказать при идеальной игре.
Важным элементом в программной реализации «Крестики-нолики» с участием искусственного интеллекта (ИИ) является алгоритм Minimax – метод принятия решений, используемый в теориях игр и ИИ. Он предполагает, что один игрок стремится минимизировать проигрыш (min), а второй – максимизировать выигрыш (max). Алгоритм просматривает все возможные ходы и оценивает их исходы, выбирая оптимальный путь.
Итак, поле игры состоит из 9 клеток, расположенных в виде квадратной матрицы 3×3:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Победной считается такая комбинация, при которой игрок выставляет три одинаковых символа по горизонтали, вертикали или диагонали. Всего существует 8 возможных выигрышных комбинаций:
Горизонтали:
1-2-3 (верхний ряд) 4-5-6 (средний ряд) 7-8-9 (нижний ряд)
2. Вертикали:
1-4-7 (левый столбец) 2-5-8 (средний столбец) 3-6-9 (правый столбец)
3. Диагонали:
1-5-9 (главная диагональ) 3-5-7 (побочная диагональ)
Чтобы не проиграть, первый игрок (крестик) должен придерживаться нескольких стратегий:
Начать с центра (позиция 5) – это самая сильная начальная позиция. Она позволяет получить доступ ко всем диагоналям и вертикалям. При правильной игре почти гарантирует победу или ничью.
2. Начать с угла (позиции 1, 3, 7, 9). Менее выгодно, чем центр, но может привести к победе, если второй игрок ошибается. Может быть частью стратегии «вилка».
3. Стратегия «вилка» (fork) – создание ситуации, в которой у игрока сразу две возможности победить на следующем ходу, и противник не может перекрыть обе.
Например:
Игрок ставит крестик в угол (1), затем – в противоположный угол (9), если центр свободен. Далее возможно создание двух линий с двумя символами.
4. Блокировка соперника. Игрок всегда должен блокировать возможные победные линии противника, особенно если у него уже два символа в ряду.
Занятие боковой клетки первым (позиции 2, 4, 6, 8) – плохая стратегия для первого хода.
Если оба игрока используют идеальные стратегии, партия всегда заканчивается вничью. Именно поэтому ИИ на основе Minimax либо выигрывает, либо играет вничью, но никогда не проигрывает. Этот алгоритм я и использовал в своей игре (см. Приложение1).
2. Идея и патриотическая направленность проекта «Битва за урожай»
2025 год в Беларуси был объявлен Годом благоустройства, а вся пятилетка – Пятилеткой качества, и в этом контексте выбор сельскохозяйственной тематики для моего проекта неслучаен. Аграрная отрасль – важная часть национальной экономики и культуры. Персонажи игры – пшеница и картофель – выступают олицетворением труда, урожая и национальных ценностей.
Игра «Битва за урожай» была разработана как проект на конкурс Патриот.by, где одним из критериев выступало отражение патриотических ценностей через творчество. Главная идея игры – показать, что даже классические игровые механики могут нести воспитательную функцию, если наполнить их смыслом, близким к культуре и традициям страны.
Игра ориентирована на школьников и помогает:
– познакомиться с аграрной символикой Беларуси;
– прочитать интересные факты о пшенице, картофеле и сельском хозяйстве;
– осознать важность труда и уважения к сельской профессии.
Победа одного из символов (крестик – пшеница, нолик – картофель) сопровождается познавательным фактом, а в случае ничьей отображается сообщение «Победила дружба!» с фактом о белорусском сельском хозяйстве. Эти факты извлекаются из массивов wheatFacts, potatoFacts и agricultureFacts, представленных в программном коде игры (см. Приложение 2).
3. Техническая реализация игры на платформе Unity
Игра «Битва за урожай» реализована в среде Unity – одного из самых популярных игровых движков, подходящих для кроссплатформенной разработки.
Проект состоит из 5 сцен:
Главное меню – содержит кнопки: «Играть», «Настройки», «Интро», «Выход» (Рисунок 1).
Рисунок 1
Интро – сцена, где рассказывается о битве картофеля и пшеницы за урожай (Рисунок 2).
Рисунок 2
Сцена с выбором режима игры (Рисунок 3).
Рисунок 3
Игровая сцена режима «Человек против ИИ» (Рисунок 4).
Рисунок 4
Игровая сцена режима «Человек против человека» (Рисунок 5).
Рисунок 5
В проекте были использованы компоненты:
Canvas, Text, Image, Button, InputField – для UI-оформления.
AudioSource – для воспроизведения фоновой музыки и звуков.
Animator – для анимации персонажей в Интро.
Основной скрипт для режима «Человек против человека» – GameStateController2.cs (см. Приложение 2). Он отвечает за методы смены хода, проверки победителя и вывода случайного факта.
Основной скрипт для режима «Человек против ИИ» аналогичен предыдущему, но использует алгоритм Minimax (см. Приложение 1).
Персонажи (пшеница и картофель) нарисованы и стилизованы мною под мультяшные и узнаваемые образы в редакторе Inkscape. Фоны были взяты с сайта Freepik. Озвучку я сделал с помощью VoiceBot.
Заключение
В ходе выполнения данной работы была разработана игра «Битва за урожай» и опубликована на сайте Itch.io (https://doggerthenoob.itch.io/bulba.
Проведённый опрос показал, что игра «Битва за урожай» получила положительный отклик у школьников и обладает потенциалом для использования в воспитательном процессе. Она не только увлекает, но и способствует осмыслению роли сельского хозяйства в жизни страны, напоминая о таких ценностях, как трудолюбие и традиции.
Разработка включала в себя изучение теоретических основ, проектирование интерфейса, создание графических и звуковых ресурсов, а также программирование логики игры на языке C# в среде Unity. Добавление ИИ сделало игру интересной даже в одиночном режиме, а использование пшеницы и картофеля как игровых символов позволило наполнить игру узнаваемыми белорусскими образами.
Таким образом, поставленные цели и задачи были успешно достигнуты, а полученный результат может служить примером объединения игровых технологий и культурного содержания.
В будущем планирую доработать игру: добавить поле 6 на 6 для игровых режимов и адаптировать ее для мобильных устройств.
Список использованных источников и литературы
Бонд Д. Г. Unity и С#. Геймдев от идеи до реализации 2-е издание - СПб.: Питер, 2021 – 1002 с.
Доусон М. Изучаем C++ через программирование игр - СПб.: Питер, 2020 – 352 с.
Minimax [Электронный ресурс] // Wikipedia - the free encyclopedia. – URL: https://en.wikipedia.org/wiki/Minimax (дата обращения: 07.05.2025).
Приложение 1
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;
public class SimpleAI : MonoBehaviour
{
public System.Random rnd = new();
[Header("Component References")]
public GameStateController gameController;
private Text[] board;
void Start()
{
board = gameController.TileList;
}
private int Score(string result)
{
return result switch
{
"X" => -1,
"O" => 1,
//НИЧЬЯ
_ => 0,
};
}
private int MaxMinAlgorithm(Text[] currentboard,int depth,bool isMax)
{ // Проверка на наличие победителя или ничьей
String result = gameController.CheckWinner(currentboard);
// Если игра завершена, возвращаем соответствующую оценку
if (!result.Equals("TURN"))
{
return Score(result);
}
if (isMax)
{
int bestScore = int.MinValue;
for (int i = 0; i < currentboard.Length; i++)
{
if (currentboard[i].text.Equals(""))
{
currentboard[i].text = "O";
int tempscore = MaxMinAlgorithm(currentboard,depth + 1, false);
currentboard[i].text = "";
bestScore = Math.Max(bestScore, tempscore);
}
}
return bestScore;
}
else
{
int bestScore = int.MaxValue;
for (int i = 0; i < currentboard.Length; i++)
{
if (currentboard[i].text.Equals(""))
{
currentboard[i].text = "X";
int tempscore = MaxMinAlgorithm(currentboard, depth + 1,true);
currentboard[i].text = "";
bestScore = Math.Min(bestScore, tempscore);
}
}
return bestScore;
}
}
public async void BestMove()
{
await Task.Delay(rnd.Next(300, 800));
int bestScore = int.MinValue;
string bestTileName = "";
// Перебираем все клетки
for (int i = 0; i < board.Length; i++)
{
//если клетка пустая
if (board[i].text.Equals(""))
{
board[i].text = "O";
int tempScore = MaxMinAlgorithm(board, 0,false);
board[i].text = "";
if (tempScore > bestScore)
{
bestScore = tempScore;
bestTileName = GetTileByIndex(i);
}
}
}
// Выборклеткииэмуляцияклика
GameObject bestChoice = GameObject.Find(bestTileName);
bestChoice.GetComponent<TileController>().UpdateTile();
}
public String GetTileByIndex(int index)
{
string objectName = index switch
{
0 => "Tile_1",
1 => "Tile_2",
2 => "Tile_3",
3 => "Tile_4",
4 => "Tile_5",
5 => "Tile_6",
6 => "Tile_7",
7 => "Tile_8",
8 => "Tile_9",
_ => "",
};
return objectName;
}
}
Приложение 2
using System;
using UnityEngine;
using UnityEngine.UI;
public class GameStateController2 : MonoBehaviour
{
[Header("TitleBar References")]
public Image playerXIcon; // Иконкаигрока X
public Image playerOIcon; // Иконкаигрока O
public InputField player1InputField; // Полевводадляимениигрока 1 (X)
public InputField player2InputField; // Полевводадляимениигрока 2 (O)
public Text winnerText; // Текст для отображения победителя
public Text winnerText1; // Отображает факт о победителе
[Header("Misc References")]
public GameObject endGameState; // Контейнер для состояния конца игры и текста победителя
[Header("Asset References")]
public Sprite tilePlayerO; // Спрайтдляплитки O
public Sprite tilePlayerX; // Спрайтдляплитки X
public Sprite tileEmpty; // Спрайтдляпустойплитки
public Text[] tileList; // Списоквсехплитоквигре
[Header("GameState Settings")]
public Color inactivePlayerColor; // Цветдлянеактивногоигрока
public Color activePlayerColor; // Цветдляактивногоигрока
public string whoPlaysFirst = "X"; // Ктоходитпервым
[Header("Private Variables")]
private string playerTurn; // Ктосейчасходит (X или O)
private string player1Name; // Имяигрока 1
private string player2Name; // Имяигрока 2
private readonly string[] potatoFacts = new string[]
{
"Картофель появился в Беларуси в XVI веке, но стал популярен только к XIX веку",
"Картофель получил название «второй хлеб»",
"Беларусь — мировой лидер по потреблению картофеля на душу населения",
"Картофель используется для еды, а также в производстве крахмала",
"Около 20% всех сельхозугодий в Беларуси заняты картофелем",
"В картофеле больше калия, чем в бананах, а также он богат витаминами C и B6",
"Драники — одно из самых известных блюд Беларуси, приготовленное из картофеля",
"Официального дня картофеля нет, но в некоторых районах Беларуси его празднуют осенью",
"Белорусская селекция картофеля ценится в мире, местные сорта популярны и за рубежом",
"Беларусь экспортирует картофель в более чем 20 стран",
"Средний белорус потребляет около 170 кг картофеля в год",
"В Беларуси выращивается свыше 200 сортов картофеля",
"Картофель был первым растением, выращенным в космосе на борту NASA в 1995 году",
"Из картофеля можно сделать простую батарейку для зажигания лампочки",
"В Бельгии есть музей картофеля с картиной Ван Гога «Едоки картофеля»",
"Во время золотой лихорадки на Аляске картофель обменивали на золото",
"Самый большой памятник картофелю - двухметровый клубень в Тюменской области",
"Вареный в кожуре картофель сохраняет больше полезных веществ",
"Сырой картофель помогает облегчить боль при небольших ожогах",
"Клубень на 80% состоит из воды, как стакан молока",
"Французский сорт «La Bonnotte» — самый дорогой картофель в мире",
"Пирожное «картошка» напоминает по форме клубень",
"Картофель можно использовать для создания узоров, как штамп"
};
private readonly string[] wheatFacts = new string[]
{
"Пшеницу начали выращивать более 10 000 лет назад",
"В 2023 году Беларусь собрала около 7,3 млн тонн зерновых",
"Пшеница — главная культура для производства хлеба в Беларуси",
"Около 70% всей пшеницы в Беларуси используется для производства муки",
"Существует более 25 видов пшеницы, но основные типы — твердая и мягкая",
"Пшеница богата клетчаткой, полезной для пищеварительной системы",
"Белорусские ученые создали сорта пшеницы, устойчивые к болезням",
"Мука из твердых сортов содержит больше клейковины и используется для хлеба и пасты",
"Пшеница способна расти практически в любом климате, от холодного до жаркого",
"Во многих культурах пшеница символизирует урожай и изобилие",
"Белорусский хлеб славится качеством благодаря особой технологии обработки пшеницы",
"Пшеницу выращивают более чем в 90 странах, от США до Китая",
"Пшеница — главный источник углеводов и содержит витамины группы B и железо",
"Зерно пшеницы состоит из эндосперма, зародыша и оболочки, что помогает ему прорастать",
"Белорусские фермеры применяют севооборот для повышения урожайности пшеницы",
"Пшеница была одной из первых выращенных культур на территории Беларуси",
"Белорусская пшеница используется как для хлебопекарной муки, так и для кормов",
"В Древнем Египте пшеница считалась священной и использовалась в ритуалах",
"Пшеница — основа хлеба и ингредиент многих белорусских традиционных блюд",
"Из 1 тонны пшеницы можно получить разные сорта муки: высший, первый, второй и отруби",
"Мука из мягких сортов более рассыпчатая, содержит меньше клейковины и идет на сдобную выпечку",
"Существуют специальные сорта пшеницы, такие как спельта, с уникальными свойствами",
"Беларусь экспортирует пшеницу в страны Евросоюза и СНГ"
};
private readonly string[] agricultureFacts = new string[]
{
"Около 40% территории Беларуси используется для сельскохозяйственных нужд",
"Беларусь — один из крупнейших экспортеров льна в мире",
"В Беларуси около 300 тысяч человек заняты в агропромышленном комплексе",
"На каждого человека в Беларуси приходится около 0,7 гектара сельскохозяйственных угодий",
"Беларусь — одна из ведущих стран по производству молока на душу населения",
"Сельское хозяйство составляет около 7% ВВП Беларуси",
"Белорусские агротехнологии экспортируются в более чем 50 стран мира",
"Беларусь известна своими технологиями в производстве и переработке льна",
"Белорусские ученые создали более 500 сортов различных сельскохозяйственных культур",
"Льноводство — одна из самых традиционных отраслей сельского хозяйства в Беларуси",
"Из льна делают ткани, верёвки и даже специальные бумаги",
"Белорусская сельхозтехника известна высоким качеством и используется по всему миру",
"В 2023 году Беларусь увеличила производство овощей на 20% по сравнению с прошлым годом",
"Беларусь активно развивает органическое сельское хозяйство",
"Основные сельскохозяйственные культуры в Беларуси — это картофель, пшеница, рожь, кукуруза и сахарная свекла",
"Белорусские ученые занимаются селекцией культур, устойчивых к изменению климата",
"Сельское хозяйство в Беларуси интенсивно использует дроны и спутниковое наблюдение",
"Беларусь является крупным производителем рапса, который используется для производства масла",
"В Беларуси активно развиваются агротуристические проекты, где можно увидеть традиционные сельхозработы",
"Беларусь имеет множество международных соглашений с другими странами для обмена опытом в сельском хозяйстве",
"В Беларуси регулярно проходят ярмарки, на которых фермеры продают свежие овощи, фрукты и другие продукты"
};
private void Start()
{
// Устанавливаем, кто ходит первым, и обновляем иконки
playerTurn = whoPlaysFirst;
if (playerTurn == "X")
playerOIcon.color = inactivePlayerColor;
else
playerXIcon.color = inactivePlayerColor;
// Привязываем события к полям ввода имен
player1InputField.onValueChanged.AddListener(delegate { OnPlayer1NameChanged(); });
player2InputField.onValueChanged.AddListener(delegate { OnPlayer2NameChanged(); });
// Устанавливаем имена игроков по умолчанию
player1Name = player1InputField.text;
player2Name = player2InputField.text;
}
// Обработкаконцахода
public void EndTurn()
{
String result = CheckWinner(tileList);
if (result.Equals("TURN"))
{
ChangeTurn();
}
else
{
GameOver(result);
}
}
// Проверка на победителя
public string CheckWinner(Text[] tempBoard)
{
String result = "TURN"; // По умолчанию продолжаем игру
// Проверяем все возможные комбинации выигрыша
if (Equal3(tempBoard[0].text, tempBoard[1].text, tempBoard[2].text) ||
Equal3(tempBoard[3].text, tempBoard[4].text, tempBoard[5].text) ||
Equal3(tempBoard[6].text, tempBoard[7].text, tempBoard[8].text) ||
Equal3(tempBoard[0].text, tempBoard[3].text, tempBoard[6].text) ||
Equal3(tempBoard[1].text, tempBoard[4].text, tempBoard[7].text) ||
Equal3(tempBoard[2].text, tempBoard[5].text, tempBoard[8].text) ||
Equal3(tempBoard[0].text, tempBoard[4].text, tempBoard[8].text) ||
Equal3(tempBoard[2].text, tempBoard[4].text, tempBoard[6].text))
{
result = playerTurn; // Возвращаем X или O в зависимости от текущего игрока
}
else if (IsDraw(tempBoard))
{
result = "D"; // Ничья
}
return result;
}
private bool Equal3(string a, string b, string c)
{
return a.Equals(b) && b.Equals(c) && !a.Equals(""); // Проверка на три одинаковых символа подряд
}
private bool IsDraw(Text[] tempBoard)
{
foreach (Text each in tempBoard)
{
if (each.text.Equals("")) return false; // Если есть пустые клетки, игра продолжается
}
return true; // Все клетки заполнены — ничья
}
// Сменахода
public void ChangeTurn()
{
// Меняем текущего игрока
playerTurn = (playerTurn == "X") ? "O" : "X";
UpdatePlayerIcons();
}
// Обновление иконок игроков в зависимости от активного игрока
private void UpdatePlayerIcons()
{
if (playerTurn == "X")
{
playerXIcon.color = activePlayerColor;
playerOIcon.color = inactivePlayerColor;
}
else
{
playerXIcon.color = inactivePlayerColor;
playerOIcon.color = activePlayerColor;
}
}
// Вызывается, когда найдены условия победы или ничья
private void GameOver(string winningPlayer)
{
string randomFact;
switch (winningPlayer)
{
case "D":
winnerText.text = "Победила дружба!";
randomFact = agricultureFacts[UnityEngine.Random.Range(0, agricultureFacts.Length)];
winnerText1.text += "\nИнтересно: " + randomFact; // Добавляем факт к тексту победителя на экране
break;
case "X":
winnerText.text += "Выиграл: " + player1Name;
randomFact = wheatFacts[UnityEngine.Random.Range(0, wheatFacts.Length)];
winnerText1.text += "\nИнтересно: " + randomFact; // Добавляем факт к тексту победителя на экране
break;
case "O":
winnerText.text += "Выиграл: " + player2Name;
randomFact = potatoFacts[UnityEngine.Random.Range(0, potatoFacts.Length)];
winnerText1.text += "\nИнтересно: " + randomFact; // Добавляем факт к тексту победителя на экране
break;
}
Debug.Log(winnerText.text.ToString());
Debug.Log(winnerText1.text.ToString());
endGameState.SetActive(true);
ToggleButtonState(false);
}
// Возвращает спрайт текущего игрока (X или O)
public Sprite GetPlayerSprite()
{
if (playerTurn == "X")
return tilePlayerX; // Спрайтдля X
else
return tilePlayerO; // Спрайтдля O
}
public string GetPlayersTurn()
{
return playerTurn; // Возвращаеттекущегоигрока
}
// Перезапускигры
public void RestartGame()
{
// Сброснекоторыхсвойствигровогосостояния
if (playerTurn == "X") playerOIcon.color = inactivePlayerColor;
else playerXIcon.color = inactivePlayerColor;
// Добавляет слушатель на поля ввода имен и вызывает метод при изменении значения. Это обратный вызов.
player1InputField.onValueChanged.AddListener(delegate { OnPlayer1NameChanged(); });
player2InputField.onValueChanged.AddListener(delegate { OnPlayer2NameChanged(); });
// Устанавливает значения по умолчанию на основе текста в полях ввода
player1Name = player1InputField.text;
player2Name = player2InputField.text;
winnerText.text = "";
winnerText1.text = "";
ToggleButtonState(true);
endGameState.SetActive(false);
// Сбрасываемвсеплитки
for (int i = 0; i < tileList.Length; i++)
{
tileList[i].GetComponentInParent<TileController2>().ResetTile();
}
}
// Включение или отключение всех кнопок
private void ToggleButtonState(bool state)
{
for (int i = 0; i < tileList.Length; i++)
{
tileList[i].GetComponentInParent<Button>().interactable = state;
}
}
// Обновлениеимениигрока 1
public void OnPlayer1NameChanged()
{
player1Name = player1InputField.text;
}
// Обновлениеимениигрока 2
public void OnPlayer2NameChanged()
{
player2Name = player2InputField.text;
}
public Text[] TileList => tileList;
}