Создание игры «Сапер» на Python с использованием Pygame.
Игры занимают важное место в мире программирования, позволяя сочетать творческий подход и технические навыки. Одна из классических и популярных игр — «Сапер» — отлично подходит для освоения основ разработки игр. В этой статье мы подробно рассмотрим, как создать игру «Сапер» на языке Python с использованием библиотеки Pygame.
Pygame — удобная библиотека для создания 2D-игр, обеспечивающая инструменты для работы с графикой, звуком и событиями. «Сапер» же — логическая игра, в которой игроку нужно открыть все клетки на поле, избегая мин. В процессе реализации мы разберём основные части кода, логику игры, создание интерфейса и взаимодействие с пользователем.
Подготовка к работе: установка и настройка Pygame
Прежде чем приступать к написанию игры, необходимо убедиться, что у вас установлен Python и пакет Pygame. Если Pygame отсутствует, его легко установить через менеджер пакетов pip с помощью команды в терминале:
pip install pygame
После установки библиотеки важно проверить, что Pygame корректно подключается и работает в вашей среде разработки. Для этого можно написать небольшую программу, создающую пустое окно. Это позволит убедиться в готовности к следующему этапу — разработке игрового процесса.
Структура проекта
Для удобства работы рекомендуется организовать проект в отдельную папку, в которой будет основной файл с кодом (например, minesweeper.py) и при необходимости папка с ресурсами — изображениями, звуками и т. д. В нашем случае мы попробуем использовать базовые цветовые решения, не прибегая к внешним картинкам, чтобы сделать проект максимально простым для понимания и кастомизации.
Основные элементы игры «Сапер»
Чтобы правильно подготовить программу, нужно чётко понимать, из каких компонентов состоит игра. В «Сапере» присутствуют следующие элементы:
- Игровое поле — двумерная сетка клеток, часть из которых содержит мины.
 - Мины — опасные клетки, при открытии которых игра заканчивается поражением.
 - Отметки флагами — пользователь может помечать клетки как подозрительные.
 - Числа на клетках — показывают количество мин на соседних клетках.
 - Счётчик оставшихся мин и таймер игры.
 
На уровне программирования нам нужно реализовать данные элементы с помощью соответствующих структур данных и функций обработки событий, таких как нажатие мыши и обновление графики.
Моделирование игрового поля
Игровое поле можно представить в виде двумерного списка (матрицы), где каждый элемент — это объект или словарь с информацией о клетке. Основные атрибуты клетки могут быть следующими:
| Атрибут | Описание | 
|---|---|
| is_mine | Булево значение, указывающее наличие мины. | 
| is_open | Статус, открыта ли клетка. | 
| is_flagged | Помечена ли клетка флагом. | 
| neighbor_mines | Количество мин вокруг текущей клетки. | 
Такое представление упрощает процесс обновления состояния игры и отрисовку интерфейса.
Создание игрового окна и отрисовка элементов
Для начала необходимо инициализировать Pygame и создать окно с определённым размером, зависящим от размера поля и размера клетки. Например, если поле 10×10, а клетка 40×40 пикселей, то окно будет примерно 400×400 пикселей.
Далее нужно продумать, как рисовать клетки. Обычно клетки показываются в трёх состояниях:
- Закрытая — оттенок серого цвета, либо текстура.
 - Открытая — светлая клетка с либо пустым пространством, либо цифрой количества соседних мин.
 - Помеченная — с изображением флага (можно нарисовать простым треугольником или другим символом).
 
Хорошей практикой является создание отдельной функции, которая получает параметры клетки и рисует её на заданных координатах. Это упрощает основной цикл отрисовки и улучшает читаемость кода.
Код инициализации и базовой отрисовки
Пример инициализации игрового окна и отрисовки базовой сетки:
import pygame
pygame.init()
CELL_SIZE = 40
GRID_SIZE = 10
screen = pygame.display.set_mode((CELL_SIZE * GRID_SIZE, CELL_SIZE * GRID_SIZE))
pygame.display.set_caption('Сапер')
def draw_cell(x, y, color):
    rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
    pygame.draw.rect(screen, color, rect)
    pygame.draw.rect(screen, (0, 0, 0), rect, 1)  # рамка
running = True
while running:
    screen.fill((192, 192, 192))  # фон
    for y in range(GRID_SIZE):
        for x in range(GRID_SIZE):
            draw_cell(x, y, (160, 160, 160))  # закрытые клетки
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    pygame.display.flip()
pygame.quit()
Этот код создаёт окно и рисует серые квадраты как закрытые клетки. Дальше будем добавлять логику и отображение обновлённого состояния.
Реализация логики игры
Следующий важный этап — создание логики игры, включающей:
- Расстановку мин случайным образом.
 - Вычисление количества мин вокруг каждой клетки.
 - Обработку кликов игроком.
 - Открытие клеток и автоматическое открытие соседних пустых клеток.
 - Управление флагами.
 
Распределение мин
Распределять мины можно с помощью модуля random. Для этого создаём список всех координат клеток, перемешиваем и выбираем первые n, где n — количество мин. Затем отмечаем эти клетки как содержащие мины.
Подсчёт соседних мин
Для каждой клетки считаем количество мин в восьми соседних позициях (если они в пределах поля). Эта информация нужна для отображения цифр после открытия клетки.
Обработка кликов
В Pygame мышиные события можно обрабатывать через pygame.MOUSEBUTTONDOWN. Левой кнопкой обычно открываем клетку, правой — ставим или снимаем флаг. Важно корректно преобразовывать координаты клика в индексы клеток.
Открытие клеток и рекурсивное раскрытие пустых зон
Если игрок открывает клетку без мин и с нулём соседних мин, нужно автоматически открыть все соседние клетки, чтобы упростить игровой процесс. Для этого используется рекурсивный (или с использованием стека) алгоритм обхода соседей.
Работа с пользовательским интерфейсом
В дополнение к игровому полю стоит реализовать простой интерфейс, включающий:
- Отображение количества оставшихся мин.
 - Таймер, показывающий время с момента запуска игры.
 - Кнопку перезапуска.
 
Для вывода текста используется модуль шрифтов Pygame — pygame.font. Таймер можно реализовать с помощью вычисления разницы времени между началом игры и текущим моментом.
Пример вывода текста
font = pygame.font.SysFont('Arial', 24)
text_surface = font.render('Мины: 10', True, (0, 0, 0))
screen.blit(text_surface, (10, 10))
Данная технология позволяет встраивать в основное окно различные вспомогательные элементы без дополнительной сложной графики.
Полный пример кода: упрощённый «Сапер»
Ниже приведён упрощённый отрывок кода, демонстрирующий основные части игры. Это не полный код, но основа для понимания работы.
import pygame
import random
import sys
from pygame.locals import *
pygame.init()
CELL_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 10
MINES_COUNT = 15
WIDTH = CELL_SIZE * GRID_WIDTH
HEIGHT = CELL_SIZE * GRID_HEIGHT + 40  # для панели статистики
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Сапер')
font = pygame.font.SysFont('Arial', 20)
class Cell:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.is_mine = False
        self.is_open = False
        self.is_flagged = False
        self.neighbor_mines = 0
def init_field():
    field = [[Cell(x, y) for x in range(GRID_WIDTH)] for y in range(GRID_HEIGHT)]
    # Расставляем мины
    coords = [(x, y) for x in range(GRID_WIDTH) for y in range(GRID_HEIGHT)]
    random.shuffle(coords)
    for i in range(MINES_COUNT):
        x, y = coords[i]
        field[y][x].is_mine = True
    # Считаем соседей
    for y in range(GRID_HEIGHT):
        for x in range(GRID_WIDTH):
            if not field[y][x].is_mine:
                count = 0
                for ny in range(max(0, y-1), min(GRID_HEIGHT, y+2)):
                    for nx in range(max(0, x-1), min(GRID_WIDTH, x+2)):
                        if field[ny][nx].is_mine:
                            count +=1
                field[y][x].neighbor_mines = count
    return field
def draw_cell(cell):
    rect = pygame.Rect(cell.x * CELL_SIZE, cell.y * CELL_SIZE + 40, CELL_SIZE, CELL_SIZE)
    if cell.is_open:
        pygame.draw.rect(screen, (200, 200, 200), rect)
        if cell.is_mine:
            pygame.draw.circle(screen, (255, 0, 0), rect.center, CELL_SIZE//4)
        elif cell.neighbor_mines > 0:
            text = font.render(str(cell.neighbor_mines), True, (0, 0, 255))
            text_rect = text.get_rect(center=rect.center)
            screen.blit(text, text_rect)
    else:
        pygame.draw.rect(screen, (100, 100, 100), rect)
        if cell.is_flagged:
            pygame.draw.polygon(screen, (255, 0, 0), [
                (rect.left + CELL_SIZE * 0.3, rect.top + CELL_SIZE * 0.2),
                (rect.left + CELL_SIZE * 0.7, rect.top + CELL_SIZE * 0.5),
                (rect.left + CELL_SIZE * 0.3, rect.top + CELL_SIZE * 0.8)
            ])
    pygame.draw.rect(screen, (0, 0, 0), rect, 1)
def open_cell(field, x, y):
    cell = field[y][x]
    if cell.is_open or cell.is_flagged:
        return
    cell.is_open = True
    if cell.neighbor_mines == 0 and not cell.is_mine:
        for ny in range(max(0, y-1), min(GRID_HEIGHT, y+2)):
            for nx in range(max(0, x-1), min(GRID_WIDTH, x+2)):
                if not field[ny][nx].is_open:
                    open_cell(field, nx, ny)
def check_victory(field):
    for row in field:
        for cell in row:
            if not cell.is_mine and not cell.is_open:
                return False
    return True
def main():
    field = init_field()
    clock = pygame.time.Clock()
    start_ticks = pygame.time.get_ticks()
    game_over = False
    victory = False
    while True:
        screen.fill((220, 220, 220))
        
        # Отрисовка панели статистики
        elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000
        mines_left = MINES_COUNT - sum(cell.is_flagged for row in field for cell in row)
        info_surf = font.render(f'Мины: {mines_left}   Время: {elapsed_seconds}s', True, (0,0,0))
        screen.blit(info_surf, (10, 10))
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if not game_over and not victory:
                if event.type == MOUSEBUTTONDOWN:
                    mx, my = event.pos
                    if my >= 40:
                        gx, gy = mx // CELL_SIZE, (my - 40) // CELL_SIZE
                        if event.button == 1:  # левая кнопка
                            if not field[gy][gx].is_flagged:
                                open_cell(field, gx, gy)
                                if field[gy][gx].is_mine:
                                    game_over = True
                        elif event.button == 3:  # правая кнопка
                            cell = field[gy][gx]
                            if not cell.is_open:
                                cell.is_flagged = not cell.is_flagged
        
        for row in field:
            for cell in row:
                draw_cell(cell)
        if not game_over and check_victory(field):
            victory = True
        if game_over:
            lose_text = font.render('Вы проиграли! Нажмите R для рестарта.', True, (255, 0, 0))
            screen.blit(lose_text, (10, HEIGHT // 2))
        if victory:
            win_text = font.render('Поздравляем! Вы выиграли! Нажмите R для рестарта.', True, (0, 128, 0))
            screen.blit(win_text, (10, HEIGHT // 2))
        keys = pygame.key.get_pressed()
        if keys[K_r]:
            field = init_field()
            game_over = False
            victory = False
            start_ticks = pygame.time.get_ticks()
        pygame.display.flip()
        clock.tick(30)
if __name__ == '__main__':
    main()
Данный код показывает базовую работу с игровым полем, отрисовку клеток, открытие, постановку флагов и проверку победы/поражения.
Заключение
Создание игры «Сапер» на Python с использованием Pygame является отличным упражнением для начинающих и продвинутых разработчиков, позволяющим практиковаться в работе с графикой, событиями и алгоритмами. Мы рассмотрели основные этапы разработки: подготовку среды, построение структуры данных, логику игры, отрисовку и взаимодействие с пользователем.
Реализованный проект можно расширять, добавляя более сложные функции — разные уровни сложности, сохранение результатов, улучшенную графику и анимации. Также можно оптимизировать производительность и интерфейс для более комфортной игры.
Освоив эти базовые навыки, вы сможете создавать собственные интересные проекты и углублять знания в программировании игр.
«`html
«`