Привет, любители покодить и поностальгировать! Помните те времена, когда на каждом кнопочном телефоне и в каждой Windows были «Пятнашки»? Простая, как три копейки, но безумно залипательная головоломка.
Сегодня мы не просто вспомним детство, а создадим свою собственную версию этой игры на C#. И не абы как, а с красивым интерфейсом, таймером, подсчетом ходов и... маленькой хитростью, которая поможет нам всегда выигрывать. Спойлер: будем использовать магию чётности перестановок .
Почему «Пятнашки» — это крутой учебный проект?
Многие начинающие программисты пишут очередной «Калькулятор» или «Блокнот». Скука смертная. «Пятнашки» же дают простор для фантазии:
- Нужно придумать, как хранить состояние поля (массивы).
- Разобраться с обработкой событий мыши/клавиатуры.
- Написать логику перемещения костяшек.
- Придумать генератор случайных, но решаемых комбинаций (вот тут и начинается алгебра!).
Кстати, знали ли вы, что авторство головоломки приписывают знаменитому шахматисту Сэму Лойду? В 19 веке он предложил $1000 тому, кто соберёт комбинацию, где костяшки 14 и 15 поменяны местами. Приз так никто и не получил, потому что... это было невозможно! . Мы сегодня такой ошибки не допустим.
Проектируем интерфейс (Windows Forms)
Я буду использовать старый добрый Windows Forms. Во-первых, это наглядно. Во-вторых, код будет минимальным и понятным.
Создадим форму, накидаем на неё кнопку «Старт», панель TableLayoutPanel для нашего поля 4x4 и MenuStrip для пунктов меню .
Важный момент: Мы не будем создавать 16 кнопок вручную в дизайнере. Это моветон. Создадим их динамически, в коде.
В методе CreateGameField мы в цикле создаем кнопки, задаем им размер, шрифт, вешаем обработчик события Click и добавляем на форму.
Пишем логику игры: Swap и проверка победы
Сердце игры — это метод, который обрабатывает клик по кнопке. Когда игрок жмет на кнопку, мы должны понять: а можно ли её подвинуть? То есть, есть ли рядом с ней пустая клетка?
private void Button_Click(object sender, EventArgs e)
{
Button clickedBtn = sender as Button;
// Находим координаты нажатой кнопки в массиве
int row = -1, col = -1;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (cells[i, j] == clickedBtn)
{
row = i; col = j; break;
}
// Проверяем, соседствует ли она с пустой клеткой
if ( (Math.Abs(row - emptyRow) == 1 && col == emptyCol) ||
(Math.Abs(col - emptyCol) == 1 && row == emptyRow) )
{
// Меняем местами числа в массиве numbers
numbers[emptyRow, emptyCol] = numbers[row, col];
numbers[row, col] = 16; // 16 - это у нас пустота
// Обновляем текст на кнопках
cells[emptyRow, emptyCol].Text = numbers[emptyRow, emptyCol].ToString();
clickedBtn.Text = ""; // Старая кнопка стала пустой
// Теперь пустая клетка переместилась
emptyRow = row;
emptyCol = col;
movesCount++;
lblMoves.Text = $"Ходы: {movesCount}";
CheckVictory();
}
}
CheckVictory — проходим по массиву и проверяем, идут ли числа по порядку от 1 до 15, а на последней позиции пустота.
Секретный ингредиент: правильное перемешивание
Самая частая ошибка новичков — просто переставить кнопки в случайном порядке. В половине случаев такая головоломка будет нерешаемой .
Как сделать правильно? Нужно взять собранное поле и сделать из него N случайных ХОДОВ (а не перестановок). То есть, запустить программу, которая 100-200 раз случайно подвинет костяшки.
Вот теперь головоломка гарантированно имеет решение!
Украшаем: Добавляем таймер
Какая же игра без секундомера? Добавим на форму компонент Timer из панели элементов. Установим интервал в 1000 миллисекунд (1 секунда) и в обработчике Tick будем увеличивать счетчик времени на 1 .
Запускать таймер по кнопке «Старт», останавливать — при победе.
Бонус-уровень: Решатель для профи
Мы уже написали игру. Но, признайтесь, иногда хочется просто посмотреть на красивое решение, а не мучиться самому. Можно добавить кнопку «Решить».
Здесь нам понадобится алгоритм A* (поиск кратчайшего пути) . Звучит страшно, но суть проста: компьютер просчитывает возможные ходы и выбирает тот, который приближает нас к цели.
Мы не будем вдаваться в дебри эвристических функций в этой статье (это тема для отдельного материала), но структура класса для "узла" состояния может выглядеть так :
Чем меньше TotalCost, тем выгоднее этот путь.
Частые ошибки и как их избежать
На форумах часто жалуются на две проблемы :
- "Победа" наступает сразу после старта. Это значит, что вы забыли скрыть текст на кнопке, которая должна быть пустой. При инициализации поля кнопка с числом 16 должна иметь текст "".
- Игра вылетает при клике. Забыли проверить границы массива. Перед тем, как обращаться к элементам emptyRow-1, всегда проверяйте, что индекс больше или равен нулю.
Заключение
Мы создали полноценную игру меньше чем за 100 строк кода (без учета дизайна). У неё есть интерфейс, логика, таймер и даже «честное» перемешивание.
А если вы хотите пойти дальше:
- Попробуйте заменить цифры на картинки (разрезанное изображение).
- Добавьте сохранение рекордов в файл.
- Сделайте адаптивный дизайн, чтобы поле меняло размер при изменении окна .
Пишите в комментариях, получилось ли у вас обыграть компьютер? Или может, вы знаете более быстрый алгоритм сборки?
Не забудьте подписаться на канал, чтобы не пропустить разбор кода любимых игр!