Как создать игру «Память» на Python с Tkinter
Важно: код рассчитан на Python 3. На некоторых системах Tkinter уже включён в стандартную установку Python; установка через pip может быть не нужна.
Зачем и кому это полезно
Игра «Память» — отличное упражнение для развития внимания и памяти. Для разработчика это простой проект для практики работы с GUI, событийным циклом и базовыми структурами данных. Учителям и преподавателям — удобный пример для объяснения событийной модели в приложениях.
Основные понятия (в одну строку)
- Tkinter: стандартная библиотека Python для создания простых GUI.
- random.shuffle: перемешивает список элементов случайным образом.
- callback/command: функция, вызываемая при клике на кнопку.
Что в статье
- Полный код рабочей игры «Память».
- Пояснение каждой части кода.
- Тестовые сценарии и критерии приёмки.
- Варианты расширения (Pygame, изображения, сетевые режимы).
- Чеклисты для разработчика, тестировщика и преподавателя.
Требования и установка
- Python 3.6+.
- Tkinter: часто поставляется вместе с Python. Если у вас не установлен GUI-пакет, обратитесь к документации вашей ОС.
- Модуль random встроен в Python, дополнительной установки не требует.
Полный рабочий код игры
Ниже собран полный минимально необходимый скрипт. Скопируйте в файл, например memory_game.py, и запустите как python memory_game.py.
from tkinter import *
import random
from tkinter import messagebox
# Инициализация окна
root = Tk()
root.title('Memory Tile Game')
root.geometry('760x550')
# Глобальные переменные
winner = 0
matches = [
'apple','apple',
'banana','banana',
'orange','orange',
'blueberry','blueberry',
'mulberry','mulberry',
'grapes','grapes'
]
random.shuffle(matches)
my_frame = Frame(root)
my_frame.pack(pady=10)
count = 0
answer_list = []
answer_dict = {}
# Сброс состояния кнопок и метки
def reset():
global winner, matches, count, answer_list, answer_dict
winner = 0
random.shuffle(matches)
my_label.config(text='')
button_list = [b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11]
for i, button in enumerate(button_list):
button.config(text=' ', bg='SystemButtonFace', state='normal')
count = 0
answer_list = []
answer_dict = {}
# Победа
def win():
my_label.config(text='Поздравляем! Вы выиграли!')
button_list = [b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11]
for button in button_list:
button.config(bg='#90EE90')
# Обработчик клика по плитке
def button_click(b, number):
global count, answer_list, answer_dict, winner
if b['text'] == ' ' and count < 2:
b['text'] = matches[number]
answer_list.append(number)
answer_dict[b] = matches[number]
count += 1
if len(answer_list) == 2:
# Совпадение
if matches[answer_list[0]] == matches[answer_list[1]]:
my_label.config(text='Это совпадение!')
for key in list(answer_dict.keys()):
key['state'] = 'disabled'
count = 0
answer_list = []
answer_dict = {}
winner += 1
if winner == 6:
win()
else:
# Неправильная пара: вернуть в исходное состояние
count = 0
answer_list = []
messagebox.showinfo('Неправильно!', 'Пара не совпадает')
for key in list(answer_dict.keys()):
key['text'] = ' '
my_label.config(text=' ')
answer_dict = {}
# Создание кнопок (12 штук)
b0 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b0, 0), relief='groove')
b1 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b1, 1), relief='groove')
b2 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b2, 2), relief='groove')
b3 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b3, 3), relief='groove')
b4 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b4, 4), relief='groove')
b5 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b5, 5), relief='groove')
b6 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b6, 6), relief='groove')
b7 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b7, 7), relief='groove')
b8 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b8, 8), relief='groove')
b9 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b9, 9), relief='groove')
b10 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b10, 10), relief='groove')
b11 = Button(my_frame, text=' ', font=('Helvetica', 20), height=4, width=8, command=lambda: button_click(b11, 11), relief='groove')
# Размещение в сетке 3x4
b0.grid(row=0, column=0)
b1.grid(row=0, column=1)
b2.grid(row=0, column=2)
b3.grid(row=0, column=3)
b4.grid(row=1, column=0)
b5.grid(row=1, column=1)
b6.grid(row=1, column=2)
b7.grid(row=1, column=3)
b8.grid(row=2, column=0)
b9.grid(row=2, column=1)
b10.grid(row=2, column=2)
b11.grid(row=2, column=3)
# Метка для сообщений
my_label = Label(root, text='')
my_label.pack(pady=20)
# Меню с опциями
my_menu = Menu(root)
root.config(menu=my_menu)
option_menu = Menu(my_menu, tearoff=False)
my_menu.add_cascade(label='Options', menu=option_menu)
option_menu.add_command(label='Reset Game', command=reset)
option_menu.add_separator()
option_menu.add_command(label='Exit Game', command=root.quit)
root.mainloop()Примечание: В меню оставлены английские метки (Options, Reset Game), чтобы сохранить совпадение с примерами кода. Вы можете полностью перевести метки на русский.
Разбор кода по частям
- Подготовка: импорт модулей и инициализация окна. root.title и root.geometry задают заголовок и размер окна в пикселях.
- Перемешивание названий совпадений: matches — список из пар элементов. random.shuffle меняет порядок в списке.
- Управление состоянием игры: глобальные переменные count, answer_list, answer_dict и winner отслеживают промежуточные состояния.
- reset: возвращает все кнопки и внутреннее состояние в начальное положение.
- button_click: главный обработчик; показывает текст на кнопке, проверяет, совпадают ли две выбранные плитки, блокирует совпавшие плитки и вызывает win при завершении.
- Интерфейс: создание 12 кнопок, размещение в сетке, добавление метки и меню.
Визуальные примеры интерфейса

Описание запуска игры и её этапов показано на скриншотах ниже.
Типичные ошибки и как их избежать
- Проблема: после неправильной пары текст плиток остаётся видимым. Решение: в блоке else внутри button_click мы явно очищаем текст и словарь answer_dict.
- Проблема: попытки клика по уже отключённой кнопке. Решение: при совпадении ставим state=’disabled’.
- Проблема: изменение списка matches не синхронизировано со старым состоянием кнопок. Решение: в reset() мы перемешиваем matches и сбрасываем все кнопки.
Тестовые сценарии и критерии приёмки
Критерии приёмки
- При запуске отображаются 12 кнопок в сетке 3×4 и пустая метка.
- Повторный выбор уже отключённой кнопки не влияет на состояние.
- При выборе двух одинаковых значений обе кнопки отключаются и показывается подсказка.
- При выборе двух разных значений показывается окно сообщения «Неправильно!», плитки и метка возвращаются в исходное состояние.
- При успешном совпадении всех пар все плитки окрашиваются в зелёный и выводится сообщение о победе.
Тестовые случаи
- TC1: Запуск программы — интерфейс загружается без ошибок.
- TC2: Выбор двух одинаковых плиток — обе становятся disabled.
- TC3: Выбор двух разных плиток — появляется сообщение и текст скрывается.
- TC4: Полная игра до конца — срабатывает win и все плитки зелёные.
- TC5: Повторный сброс через меню — игра возвращается в начальное состояние.
Чеклисты по ролям
Разработчик
- Проверить совместимость с Python 3.6+.
- Убедиться, что random.shuffle вызывается в reset и при инициализации.
- Обработать случаи многократных кликов быстро подряд.
Тестировщик
- Выполнить все TC.
- Проверить поведение при быстром клике по двум плиткам подряд.
- Проверить локализацию меток меню.
Преподаватель
- Подготовить визуальные примеры для объяснения глобальных переменных и callback.
- Показать, как расширить игру до изображений вместо текстовых меток.
Варианты расширения и альтернативные подходы
- Заменить текст на изображения: вместо строк в matches храните пути к изображениям и используйте PhotoImage.
- Сделать интерфейс адаптивным: использовать grid_rowconfigure и grid_columnconfigure для пропорций.
- Добавить таймер и очки: хранить время и выдавать рейтинг по скорости.
- Переписать на Pygame для более сложной графики и анимаций.
- Реализовать сетевую версию с сокетами или через библиотеку websockets для игрового режима «по сети».
Когда подход не подойдёт
- Если вам нужен производительный движок с анимациями и физикой — используйте Pygame или движки уровня Godot.
- Для мобильных приложений Tkinter неприменим — выбирайте Kivy, BeeWare или собственные нативные SDK.
Ментальные модели и эвристики
- Делите задачу: UI, состояние игры, логика совпадений.
- Минимизируйте глобальные переменные, группируя состояние в классы для более масштабируемого кода.
- Предпочитайте явное сбрасывание состояния при рестарте игры.
Советы по локализации и интерфейсу для России
- Переведите метки меню и сообщения на русский: ‘Options’ → ‘Опции’, ‘Reset Game’ → ‘Новая игра’, ‘Exit Game’ → ‘Выход’.
- Форматы дат/времени и тексты подсказок локализуйте при добавлении таймера.
- Для русскоязычных пользователей используйте UTF-8 и проверяйте длину текстов в кнопках.
Производственный план: переход от прототипа к релизу
- Прототип: текущий код с текстовыми лейблами.
- Улучшение UX: добавить анимации/задержку перед скрытием неправильной пары.
- Тестирование: автоматизация тестов и ручная валидация.
- Публикация: собрать exe через pyinstaller или подготовить установщик для пользователей.
Мини-методология добавления изображений вместо текста
- Подготовьте 6 пар PNG одного размера.
- Загрузите их как PhotoImage в начале программы.
- В списке matches храните ссылки на объекты PhotoImage или ключи, а в button_click при установке текста используйте button.config(image=…).
- При скрытии картинки используйте button.config(image=’’) и храните image-ссылки, чтобы GC не удалил их.
Маленький decision flow (Mermaid)
flowchart TD
A[Начало] --> B[Клик по плитке]
B --> C{Плитка пустая и count < 2}
C -->|да| D[Показать значение и записать выбор]
D --> E{Выбрано 2 плитки?}
E -->|да| F{Значения совпадают?}
F -->|да| G[Отключить обе плитки, ++winner]
F -->|нет| H[Показать сообщение, очистить плитки]
G --> I{winner == 6?}
I -->|да| J[Победа]
I -->|нет| K[Продолжить игру]
H --> K
K --> BКраткое руководство по отладке
- Вставьте print() в button_click для вывода answer_list и answer_dict при отладке.
- Используйте try/except вокруг критичных мест, чтобы ловить неожиданное поведение, но не подавлять логику.
Короткое объявление для публикации (100–200 слов)
Игра «Память» на Python — готовый учебный проект для начинающих. В статье приведён полный код на Tkinter, по шагам объяснены ключевые части программы: создание интерфейса, логика совпадений, обработка неправильных пар и меню управления. Вы получите рабочий прототип, тестовые сценарии и идеи по расширению: изображения вместо текста, таймер, очки и портирование на Pygame. Материал пригодится начинающим программистам, преподавателям и тем, кто хочет быстро собрать настольную игру для практики.
SUMMARY
- Простой рабочий пример игры «Память» на Python с детальным объяснением.
- Полный код, тесты и критерии приёмки.
- Варианты расширения и советы по локализации.
Похожие материалы
Голосовой чат в Telegram: как запустить и управлять
Резервное копирование фото на Android — как и где хранить
Как скрыть полное имя в Slack
Bitwarden на Raspberry Pi Zero 2 W — самохостинг
Удалённый доступ к Mac: локально и через интернет