Руководство по созданию игры "Найди пары" с использованием Flet#
В этом руководстве мы покажем, как шаг за шагом создать игру "Найди пары" на Python с использованием фреймворка Flet. Игра представляет собой консольное приложение из 172 строк кода (с форматированием), которое работает на десктопе, мобильных устройствах и в вебе, предоставляя интерактивный и отзывчивый интерфейс.
Зачем создавать игру "Найди пары"?#
Игра "Найди пары" идеально подходит для изучения основ Flet, так как она охватывает ключевые аспекты разработки приложений: создание макета страницы, добавление элементов управления, обработка событий, работа со списками и публикация приложения для разных платформ.
Содержание руководства#
- Начало работы с Flet
- Создание игрового поля
- Добавление логики игры
- Добавление кнопки "Начать игру"
- Финальные штрихи
- Публикация приложения
1. Начало работы с Flet#
Для создания мультиплатформенного приложения с Flet не требуется знание HTML, CSS или JavaScript, но необходимы базовые знания Python и объектно-ориентированного программирования.
Установка окружения#
Убедитесь, что у вас установлен Python 3.9 или выше, а также пакет flet
. Установите его с помощью команды:
pip install flet
Создайте файл hello.py
для проверки работы Flet:
import flet as ft
def main(page: ft.Page):
page.add(ft.Text(value="Привет, мир!"))
ft.app(main)
Запустите файл, и вы увидите окно с текстом "Привет, мир!".
2. Создание игрового поля#
Теперь создадим основу игры "Найди пары". Игровое поле будет состоять из карточек, которые нужно переворачивать, чтобы найти пары одинаковых изображений.
Создайте файл memo_game.py
с базовой структурой:
import flet as ft
import random
def main(page: ft.Page):
page.title = "Игра Найди пары"
page.window_height = 710
page.window_width = 910
number_of_single_cards = 20
idx_of_cards = [f"{i}_A" for i in range(number_of_single_cards)] + \
[f"{i}_B" for i in range(number_of_single_cards)]
random.shuffle(idx_of_cards)
row_board = ft.Row(wrap=True, controls=[
ft.Container(
content=None,
width=100,
height=100,
bgcolor=ft.Colors.AMBER,
border_radius=ft.border_radius.all(10),
) for _ in range(len(idx_of_cards))
])
page.add(row_board)
ft.app(main)
Этот код создаёт окно с заголовком "Игра Найди пары" и сеткой из 40 карточек (20 пар), каждая из которых пока отображается как жёлтый прямоугольник.
3. Добавление логики игры#
Теперь добавим интерактивность: карточки будут открываться при клике, и мы будем проверять, совпадают ли они. Если пара найдена, карточки исчезают; если нет — они переворачиваются обратно.
Обновите memo_game.py
, добавив функцию для создания карточек и обработчик кликов:
import flet as ft
import random
import time
def main(page: ft.Page):
page.title = "Игра Найди пары"
page.window_height = 710
page.window_width = 910
number_of_single_cards = 20
image_page = "https://picsum.photos/id/"
def make_cards(number_of_single_cards):
card_deck_with_duplicates = \
[f"{i}_A" for i in range(number_of_single_cards)] + \
[f"{i}_B" for i in range(number_of_single_cards)]
random.shuffle(card_deck_with_duplicates)
return card_deck_with_duplicates
idx_of_cards = make_cards(number_of_single_cards)
list_of_pairs = []
find = []
def match_pairs(e):
if len(list_of_pairs) == 2:
return
num = e.control.data
idx = idx_of_cards[num].split('_')[0]
index_of_image = idx_of_cards[num].split("_")[0]
if len(list_of_pairs) < 2:
e.control.content = ft.Image(src=f"{image_page+'1'+index_of_image}/100",
border_radius=ft.border_radius.all(10))
if idx_of_cards[num] not in list_of_pairs:
list_of_pairs.append(idx_of_cards[num])
page.update()
if len(list_of_pairs) == 2:
a = int(list_of_pairs[0].split('_')[0])
b = int(list_of_pairs[1].split('_')[0])
if a == b:
time.sleep(0.4)
index_a = idx_of_cards.index(str(idx)+'_A')
index_b = idx_of_cards.index(str(idx)+'_B')
for index in (index_a, index_b):
row_board.controls[index].disabled = True
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.BLACK
find.append(index_a)
page.update()
list_of_pairs.clear()
else:
time.sleep(1)
flip_a = idx_of_cards.index(list_of_pairs[0])
flip_b = idx_of_cards.index(list_of_pairs[1])
for index in (flip_a, flip_b):
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.AMBER
list_of_pairs.clear()
page.update()
def board(idx_of_cards):
card_images = []
for num in range(len(idx_of_cards)):
index_of_image = idx_of_cards[num].split("_")[0]
card_images.append(
ft.Container(
content=None,
width=100,
height=100,
bgcolor=ft.Colors.AMBER,
border_radius=ft.border_radius.all(10),
data=num,
on_click=match_pairs
)
)
return card_images
row_board = ft.Row(wrap=True, controls=board(idx_of_cards))
page.add(row_board)
ft.app(main)
Что мы сделали:#
- Создали функцию
make_cards
для генерации и перемешивания карточек. - Добавили обработчик
match_pairs
, который открывает карточки, проверяет совпадения и либо убирает пары, либо переворачивает карточки обратно. - Использовали изображения с
picsum.photos
для визуализации карточек.
4. Добавление кнопки "Начать игру"#
Добавим кнопку, чтобы начать игру, и отобразим время, за которое игрок нашёл все пары.
Обновите memo_game.py
, добавив кнопку и отслеживание времени:
import flet as ft
import random
import time
def main(page: ft.Page):
page.title = "Игра Найди пары"
page.window_height = 710
page.window_width = 910
number_of_single_cards = 20
image_page = "https://picsum.photos/id/"
def make_cards(number_of_single_cards):
card_deck_with_duplicates = \
[f"{i}_A" for i in range(number_of_single_cards)] + \
[f"{i}_B" for i in range(number_of_single_cards)]
random.shuffle(card_deck_with_duplicates)
return card_deck_with_duplicates
idx_of_cards = make_cards(number_of_single_cards)
list_of_pairs = []
find = []
def start_game(e):
e.control.disabled = True
global start_time
start_time = time.time()
for i in range(len(idx_of_cards)):
row_board.controls[i].content = None
row_board.controls[i].bgcolor = ft.Colors.AMBER
row_board.controls[i].border_radius = ft.border_radius.all(10)
page.update()
def match_pairs(e):
if len(list_of_pairs) == 2:
return
if start_button.disabled:
num = e.control.data
idx = idx_of_cards[num].split('_')[0]
index_a = idx_of_cards.index(str(idx)+'_A')
index_b = idx_of_cards.index(str(idx)+'_B')
index_of_image = idx_of_cards[num].split("_")[0]
if len(list_of_pairs) < 2:
e.control.content = ft.Image(src=f"{image_page+'1'+index_of_image}/100",
border_radius=ft.border_radius.all(10))
if idx_of_cards[num] not in list_of_pairs:
list_of_pairs.append(idx_of_cards[num])
page.update()
if len(list_of_pairs) == 2:
a = int(list_of_pairs[0].split('_')[0])
b = int(list_of_pairs[1].split('_')[0])
if a == b:
time.sleep(0.4)
for index in (index_a, index_b):
row_board.controls[index].disabled = True
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.BLACK
find.append(index_a)
if len(find) == number_of_single_cards:
stop_time = time.time()
start_button.text = f"Время: \n{round(stop_time - start_time, 0)} \nсекунд"
page.update()
page.update()
list_of_pairs.clear()
else:
time.sleep(1)
flip_a = idx_of_cards.index(list_of_pairs[0])
flip_b = idx_of_cards.index(list_of_pairs[1])
for index in (flip_a, flip_b):
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.AMBER
list_of_pairs.clear()
page.update()
def board(idx_of_cards):
card_images = []
for num in range(len(idx_of_cards)):
index_of_image = idx_of_cards[num].split("_")[0]
card_images.append(
ft.Container(
content=None,
width=100,
height=100,
bgcolor=ft.Colors.AMBER,
border_radius=ft.border_radius.all(10),
data=num,
on_click=match_pairs
)
)
return card_images
row_board = ft.Row(wrap=True, controls=board(idx_of_cards))
start_button = ft.ElevatedButton(text="Начать игру", width=100, height=100, on_click=start_game)
page.add(row_board, start_button)
ft.app(main)
Что мы сделали:#
- Добавили кнопку
start_button
, которая запускает игру и скрывает изображения на карточках. - Отслеживаем время начала игры (
start_time
) и выводим результат, когда все пары найдены.
5. Финальные штрихи#
Добавим заголовок и улучшим внешний вид приложения. Обновите memo_game.py
:
import flet as ft
import random
import time
def main(page: ft.Page):
page.title = "Игра Найди пары"
page.window_height = 710
page.window_width = 910
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
number_of_single_cards = 20
image_page = "https://picsum.photos/id/"
def make_cards(number_of_single_cards):
card_deck_with_duplicates = \
[f"{i}_A" for i in range(number_of_single_cards)] + \
[f"{i}_B" for i in range(number_of_single_cards)]
random.shuffle(card_deck_with_duplicates)
return card_deck_with_duplicates
idx_of_cards = make_cards(number_of_single_cards)
list_of_pairs = []
find = []
def start_game(e):
e.control.disabled = True
global start_time
start_time = time.time()
for i in range(len(idx_of_cards)):
row_board.controls[i].content = None
row_board.controls[i].bgcolor = ft.Colors.AMBER
row_board.controls[i].border_radius = ft.border_radius.all(10)
row_board.controls[i].disabled = False
page.update()
def match_pairs(e):
if len(list_of_pairs) == 2 or not start_button.disabled:
return
num = e.control.data
idx = idx_of_cards[num].split('_')[0]
index_a = idx_of_cards.index(f"{idx}_A")
index_b = idx_of_cards.index(f"{idx}_B")
index_of_image = idx_of_cards[num].split("_")[0]
if len(list_of_pairs) < 2:
e.control.content = ft.Image(src=f"{image_page+'1'+index_of_image}/100",
border_radius=ft.border_radius.all(10))
if idx_of_cards[num] not in list_of_pairs:
list_of_pairs.append(idx_of_cards[num])
page.update()
if len(list_of_pairs) == 2:
a = int(list_of_pairs[0].split('_')[0])
b = int(list_of_pairs[1].split('_')[0])
if a == b:
time.sleep(0.4)
for index in (index_a, index_b):
row_board.controls[index].disabled = True
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.BLACK
find.append(index_a)
if len(find) == number_of_single_cards:
stop_time = time.time()
start_button.text = f"Время: \n{round(stop_time - start_time, 0)} \nсекунд"
page.update()
page.update()
list_of_pairs.clear()
else:
time.sleep(1)
flip_a = idx_of_cards.index(list_of_pairs[0])
flip_b = idx_of_cards.index(list_of_pairs[1])
for index in (flip_a, flip_b):
row_board.controls[index].content = None
row_board.controls[index].bgcolor = ft.Colors.AMBER
list_of_pairs.clear()
page.update()
def board(idx_of_cards):
card_images = []
for num in range(len(idx_of_cards)):
index_of_image = idx_of_cards[num].split("_")[0]
card_images.append(
ft.Container(
content=None,
width=100,
height=100,
bgcolor=ft.Colors.AMBER,
border_radius=ft.border_radius.all(10),
data=num,
on_click=match_pairs
)
)
return card_images
row_board = ft.Row(wrap=True, controls=board(idx_of_cards))
start_button = ft.ElevatedButton(text="Начать игру", width=100, height=100, on_click=start_game)
page.add(
ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Text(value="Найди пары", theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM),
row_board,
start_button
]
)
)
ft.app(main)
Что мы сделали:#
- Добавили заголовок "Найди пары" в центре страницы.
- Выровняли содержимое по центру с помощью
page.horizontal_alignment
иpage.vertical_alignment
. - Упаковали элементы в
Column
для лучшей организации интерфейса.
6. Публикация приложения#
Поздравляем! Вы создали игру "Найди пары" с использованием Flet. Теперь вы можете опубликовать её как десктопное, мобильное или веб-приложение.
Для публикации используйте команды Flet:
- Веб-приложение:
flet run --web
- Десктопное приложение: Упакуйте приложение с помощью
flet build
(требуется, чтобы проект был по структуре, как в пункте Подготовка дирректории)
Итог#
В этом руководстве вы научились:
- Создавать простое приложение с Flet.
- Работать с макетами (
Row
,Column
). - Обрабатывать события (клики по карточкам и кнопке).
- Реализовывать игровую логику (переворот карточек, проверка пар).
- Публиковать приложение для разных платформ.
Самостяотельная работа#
- Попробуйте сделать так, чтобы данное приложение считыало ваши картинки из папки
assets
- Поробуйте сделать рабочий билд программы (
flet build
)