Как пасьянс стал любимой игрой миллионов
Первый карточный пасьянс придумали почти 200 лет назад. Вероятно, игра имеет французское происхождение (по легенде, Наполеон Бонапарт в изгнании развлекался пасьянсом), однако наибольшее распространение она получила сначала в Великобритании, а затем в США и Канаде. Англоязычное название самой известной версии пасьянса, Klondike, возможно, связано с ее популярностью среди золотоискателей во время лихорадки в Клондайке. На русском эту версию обычно называют «Косынкой» из-за характерного расклада карт в виде треугольника.
Первый программный пасьянс, FreeCell, был разработан в конце 60-х: десятилетний Пол Альфилл успешно решил главные проблемы традиционной игры – постоянное тасование карт и подсчет очков. К 1979 году Альфилл, в то время студент медицинского факультета, написал программу для сети Университета Иллинойса PLATO, которая позволяла одновременно играть до 1000 пользователей.
В начале 90-х годов Microsoft решила включить три разновидности пасьянса в Windows, чтобы облегчить пользователям освоение новой системы и передового по тем временам усторойства – мыши. Игра стала излюбленным развлечением офисных служащих: работники настолько ею увлекались, что руководство многих крупных компаний (включая Coca-Cola, Sears и Boeing) приняло решение удалить все стандартные игры из ОС. Появлялись исследования, в которых утверждалось, что Windows-игры снижают продуктивность служащих и тем самым наносят огромный ущерб экономике – до $800 трлн в год! Пасьянс послужил причиной многочисленных увольнений, служебных разбирательств и открытия первой клиники для лечения от компьютерной зависимости. Словом, это культурный феномен, заслуживающий очередного воплощения – на самом популярном ЯП современности.
Обзор проекта
Логика игры аналогична стандартному пасьянсу Windows:
- Колода состоит из 52 карт.
- Основная цель – собрать все карты в четыре стопки (по мастям) в порядке возрастания от туза до короля.
- Семь столбцов карт выкладываются слева направо. Первый столбец состоит из одной открытой карты, второй – из двух карт (одной закрытой и одной открытой сверху), третий – из трех карт (две закрытых и одна открытая сверху) и так далее до седьмого столбца, где будет семь карт (шесть закрытых и одна открытая).
- Оставшиеся карты кладутся в отдельную стопку. В стандартном режиме пересдать (перебрать) карты можно 3 раза, в тренировочном режиме пересдачи не ограничены.
В игре реализована функция визуальной подсказки:
Настройки игры можно изменить на ходу, без перезапуска расклада. Опции включают:
- Выбор способа перемещения карт – клик или перетаскивание.
- Режим игры – стандартный или тренировочный.
- Рубашка (обратная сторона карт) – темная или светлая.
- Цвет фона.
- Отображение верхнего меню и футера со статистикой.
Готовый код, изображения карт и иконки находятся в этом репозитории.
Реализация
Для разработки игр на Python обычно используют библиотеку Pygame. Однако для создания карточных игр вполне достаточно стандартной GUI-библиотеки Tkinter: в ней есть функциональность, оптимально подходящая для обработки манипуляций с картами – find_overlapping, bbox, find_withtag, gettags и addtag_withtag:
- Метод bbox используется для определения границ объекта на холсте. Он возвращает кортеж (x1, y1, x2, y2) с координатами ограничивающего прямоугольника:
- Метод find_overlapping возвращает все объекты, которые пересекаются с указанным прямоугольником и как нельзя лучше подходит для поиска карт, находящихся в определенной области:
- Метод find_withtag используется для поиска элементов, которым назначен определенный тег (или идентификатор). Возвращает кортеж с уникальными ID всех элементов, соответствующих указанному тегу:
- Метод gettags позволяет динамически проверять и анализировать свойства объектов на холсте. В этом пасьянсе он используется для управления игровыми элементами (карты, слоты и активные объекты), определяя их статус и поведение на основе тегов – например, помогает узнать, является ли карта открытой, перевернутой или активной:
- Метод canvas.addtag_withtag добавляет новый тег к объекту, который уже имеют определенный существующий тег. В игре этот метод используется для динамической модификации свойств карт:
Метод из библиотеки Pillow, ImageTk.PhotoImage, конвертирует любые изображения в объекты, которые можно отобразить в интерфейсе Tkinter. Эта функция конвертирует изображения карт и иконки для использования в навигационной панели в качестве кнопок:
Верхнее меню и футер
В верхнем меню располагаются кнопки, с помощью которых можно:
- Запустить новую игру.
- Перезапустить текущую игру.
- Отменить и повторить ход.
- Показать подсказку.
- Открыть настройки.
- Перейти в полноэкранный режим (и выйти из него).
Если в определенный момент какая-то операция недоступна (например, отмена и повтор хода до начала игры), эта кнопка будет отображаться в неактивном виде:
При наведении на кнопку всплывают подсказки (тултипы):
В tkinter.tix есть метод для создания тултипов (balloon), но он уже устарел – при его использовании вы получите предупреждение о скором прекращении поддержки. Поэтому в пасьянсе используются кастомные методы для показа/скрытия тултипов:
Если отключить верхнее меню, иконка настроек будет расположена слева над футером:
В футере выводится статистика – число оставшихся пересдач (для стандартного режима), количество карт, очки и затраченное на игру время (таймер останавливается во время автоматического перемещения карт в слоты для тузов):
Отрисовка карт
Карты визуализируются на игровом поле с помощью этих методов:
- draw_card_slots создает пустые слоты – для тузов, карт в 7 колонках и остатка колоды в верхнем левом углу.
- draw_up_cards и draw_down_cards – визуализируют открытые и перевернутые карты в 7 колонках.
- draw_remaining_cards – определяет, какие карты остались после формирования 7 колонок, и визуализирует их в виде стопки рубашкой вверх.
Перемещение карт
Обработкой перемещения карт занимается метод move_card. Когда игрок нажимает на карту, метод проверяет, находится ли карта в состоянии перемещения self.move_flag. Если нет, то все видимые карты помечаются тегом "moveable", чтобы их можно было перемещать. Затем уровень слоя выбранной карты поднимается с помощью tag_raise("moveable"), а координаты курсора event.x и event.y записываются как начальные.
При каждом движении мыши с нажатой кнопкой пересчитывается смещение курсора. Карта перемещается на это смещение относительно своих текущих координат self.canvas.move("moveable", ...), после чего вызывается подсветка допустимых мест для перемещения. Подсветка допустимых позиций highlight_available_cards проверяет пересечения текущей перемещаемой карты с другими объектами, вычисляет bbox-область вокруг карты и ищет все объекты, которые пересекаются с этой областью. При этом метод игнорирует неподходящие объекты (например, рубашкой вниз или пустые слоты). Если найдена подходящая карта или слот, создается полупрозрачный прямоугольник для подсветки, а все карты в стопке поднимаются выше подсветки self.canvas.tag_raise(card):
Когда игрок отпускает карту, вызывается метод drop_card. Он удаляет подсветку допустимых позиций и снимает с карты тег "moveable", после чего проверяет пересечения с другими объектами в текущей позиции карты. Если карта пересекается с допустимым местом, проверяется правильность хода (то есть можно ли положить карту на выбранное место в соответствии с установленными для этой стопки правилами). Если можно, то карта размещается сверху стопки, если нет – возвращается на исходную позицию.
Генерация подсказок
Функция generate_hint отвечает за генерацию подсказок в игре, показывая возможный ход для игрока. Каждая карта из доступных face_up_cards анализируется на возможность перемещения. Для этого:
- Вычисляются bbox-координаты и области пересечения.
- Проверяются правила перемещения – можно ли положить карту на другую стопку, ассоциировать с пустым слотом или слотом для туза. Кроме того, проверяется валидность хода через check_move_validity и не находится ли карта над запрещенными слотами.
- Короли и тузы рассматриваются отдельно, так как у них особые правила (только туз может быть первой картой в слоте для туза; только короля можно положить на пустой слот в одной из 7 колонок).
Визуально пара карт для возможного хода подсвечивается зеленым светом:
Автоматическое перемещение в базу
Функция send_cards_up отвечает за автоматическое перемещение всех подходящих карт на соответствующие слоты для тузов (то есть в базу/дом). Чтобы обработать все ситуации, включая освобождение новых карт для перемещения, функция использует циклы, фильтры и рекурсивный подход:
- Собирает список всех открытых карт face_up_cards.
- Исключает карты, перекрытые другими картами.
- Проверяет, можно ли переложить какую-либо карту в соответствующую ей базу.
- Рекурсивно вызывает себя, чтобы проверить, не открылись ли еще какие-нибудь подходящие для перемещения карты.
В заключение
Tkinter и Pillow предоставляют функциональность для работы с графическими объектами, которая идеально подходит для реализации пасьянса:
- Методы bbox, find_overlapping, find_withtag, gettags и addtag_withtag упрощают обработку взаимодействий между картами и их состояниями.
- ImageTk.PhotoImage помогает легко конвертировать изображения карт и иконок в удобный для отображения формат.
Эти методы превращают создание сложной карточной игры в интуитивный процесс: большая часть логики взаимодействия объектов реализуется буквально несколькими строками кода. Функциональность готовой игры при этом сопоставима с профессиональными версиями пасьянса.
Самоучитель по Python
- Особенности, сферы применения, установка, онлайн IDE
- Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
- Типы данных: преобразование и базовые операции
- Методы работы со строками
- Методы работы со списками и списковыми включениями
- Методы работы со словарями и генераторами словарей
- Методы работы с кортежами
- Методы работы со множествами
- Особенности цикла for
- Условный цикл while
- Функции с позиционными и именованными аргументами
- Анонимные функции
- Рекурсивные функции
- Функции высшего порядка, замыкания и декораторы
- Методы работы с файлами и файловой системой
- Регулярные выражения
- Основы скрапинга и парсинга
- Основы ООП: инкапсуляция и наследование
- Основы ООП – абстракция и полиморфизм
- Графический интерфейс на Tkinter
- Основы разработки игр на Pygame
- Основы работы с SQLite
- Основы веб-разработки на Flask
- Основы работы с NumPy
- Основы анализа данных с Pandas
Комментарии