Створюємо 'Сапер' на Python з нуля: покрокове керівництво для початківців


(https://habr.com/ru/articles/953416/)

Хочете створити свою першу гру, але не знаєте з чого почати? "Сапер" - ідеальний проект для цього! У ньому є проста, але цікава логіка, робота з введенням користувача і графіка, що робить його відмінною стартовою точкою для будь-якого початківця розробника.

У цьому покроковому посібнику ми разом напишемо повністю робочу гру «Сапер» за допомогою мови Python та популярної бібліотеки для створення ігор Pygame. Вам не потрібний досвід у розробці ігор – лише базові знання Python. Ми пройдемо весь шлях від порожнього файлу до фінального результату, і я докладно поясню кожен крок.

Ось наш план:

  1. Налаштуємо робоче оточення: створимо окремий простір для нашого проекту.
  2. Створимо «мозок» гри: напишемо код, який відповідатиме за міни та цифри на полі.
  3. Намалюємо ігрове поле: виведемо нашу гру на екран.
  4. Навчимо гру реагувати: додамо обробку кліків миші.
  5. Додамо умови перемоги та поразки: щоб у гру було цікаво грати!

Насамкінець у вас буде готовий проект, яким можна поділитися з друзями, і чітке розуміння, як влаштовані прості 2D-ігри.

Ну, що, готові? Почнемо

Крок 0. Підготовка робочого місця (найважливіший крок!)

Перш ніж написати хоча б один рядок коду, нам потрібно підготувати для нашого проекту чистий та організований робочий простір. Ми зробимо це за допомогою віртуального оточення .

Що таке простими словами? Уявіть, що для кожного проекту ви створюєте окрему, ізольовану "коробку". У цю коробку ви складаєте тільки інструменти (бібліотеки), які потрібні саме для цього проекту. Це захищає ваші проекти від конфліктів і вважається золотим стандартом у Python-розробці.

Давайте створимо таку "коробку" для нашого "Сапера".

1. Створюємо папку проекту

Спочатку нам потрібна папка, де лежатимуть усі файли нашої гри. Для цього відкрийте термінал (або командний рядок у Windows). Це програма, де ми можемо давати комп'ютер команди текстом.

Введіть наступні команди по черзі, натискаючи Enter після кожної:
  # Створюємо папку з іменем minesweeper
  mkdir minesweeper
  # Заходим в середину цієї папки
  cd minesweeper  

Тепер усі подальші дії ми будемо виконувати всередині папки minesweeper.

2. Створюємо віртуальне оточення

Перебуваючи в папці minesweeper, введіть у термінал наступну команду:

python -m venv venv

Ця команда створить усередині minesweeperнову папку з ім'ям venv. У ній зберігатиметься "чиста" копія Python і всі бібліотеки, які ми встановимо для нашої гри.

3. Активуємо оточення

Ми створили "коробку", а тепер її потрібно "відкрити" чи активувати. Команди для цього дещо відрізняються залежно від вашої операційної системи.

Якщо у вас Windows:

venv\Scripts\activate

Якщо у вас macOS або Linux:

source venv/bin/activate

Якщо все пройшло успішно, ви побачите, що на початку терміналу з'явилося (venv). Це наш сигнал – віртуальне оточення активно!

# приклад того, як має виглядати:

(venv) C:\Users\YourName\minesweeper

4. Встановлюємо Pygame

Тепер, коли наша "коробка" відкрита, ми можемо покласти до неї наш головний інструмент - бібліотеку Pygame. pip— це стандартний менеджер пакетів Python, який завантажить та встановить її для нас.

Виконайте у терміналі одну просту команду:

pip install pygame

pip встановить Pygame саме у наше віртуальне оточення, не торкаючись основної системи.

Крок 1. "Мозок" гри - створюємо логіку поля

Перш ніж ми почнемо щось малювати, нам потрібно створити правила та внутрішній пристрій нашої гри. Цю частину часто називають логікою чи моделлю . Уявіть, що ми створюємо «Сапера», в якого можна було б грати із заплющеними очима, просто називаючи координати.

Наш план для цього кроку:

  1. Створити "цеглинку" - об'єкт для одного осередку поля.
  2. Зібрати з цих "цеглинок" ціле ігрове поле.
  3. Навчити поле розставляти міни.
  4. Навчити поле рахувати цифри навколо мін.

Почнемо! Створіть у папці minesweeperновий файл з ім'ям main.pyта пишіть весь код із цього кроку туди.

1.1. "Цегла" для нашого поля: клас Cell

Кожен квадратик на полі «Сапера» повинен щось про себе "знати": чи є в ньому міна, чи він відкритий і так далі. Щоб зручно зберігати цю інформацію, ми створимо для осередку власний клас-шаблон.

Додати цей код у ваш файл main.py:

class Cell:

    def __init__(self):

        self.is_mine = False

        self.is_open = False

        self.is_flagged = False

        self.adjacent_mines = 0

Цей простий клас – наш "будівельний блок". Щоразу, коли нам знадобиться новий осередок на полі, ми створюватимемо його за цим шаблоном.

1.2. Збираємо поле в одне ціле: клас GameBoard

Тепер, коли у нас є "цеглинка", давайте побудуємо з них стіну - наше ігрове поле. Для цього створимо ще один клас, GameBoardякий буде керувати всіма осередками.

Додати цей код в main.py під класом Cell:

import random

# Класс Cell, который мы написали выше, должен быть здесь...

class GameBoard:

    def __init__(self, width, height, mines_count):

        self.width = width

        self.height = height

        self.mines_count = mines_count

        # Создаем сетку (список списков) и заполняем её нашими "кирпичиками"

        self.board = [[Cell() for _ in range(width)] for _ in range(height)]

Тут ми створюємо сітку (двовимірний перелік), заповнену об'єктами Cell. Тепер у нас є структура поля, але воно поки що порожнє. Давайте це виправимо.

1.3. Розставляємо міни

Нам потрібно випадково розмістити на полі задану кількість мін. Для цього додамо новий метод (функцію) _place_minesусередину нашого класу GameBoard.

def _place_mines(self):

   # Создаем список всех возможных координат(строка, столбец)

   all_coords = [(r, c) for r in range(self.height) for c in range(self.width)]

    # Выбираем из списка случайные уникальные координаты для мин

    mine_coords = random.sample(all_coords, self.mines_count)

    # В ячейках по этим координатам ставим мины

    for r, c in mine_coords:

        self.board[r][c].is_mine = True

Ми використали random.sample– це зручний спосіб вибрати кілька випадкових елементів зі списку, при цьому він гарантує, що всі вони будуть унікальними. Так ми уникнемо ситуації, коли дві міни потрапили в один і той самий осередок.

1.4. Вважаємо цифри навколо мін

Це найважливіша частина логіки. Нам потрібно пройти по кожному осередку поля. Якщо в ній немає міни, ми повинні порахувати, скільки мін знаходиться у 8 сусідніх осередках.

Додайте наступний метод _calculate_adjacent_minesдо класу GameBoard:

def _calculate_adjacent_mines(self):

    # Проходим по каждой ячейке поля

    for r in range(self.height):

        for c in range(self.width):

        # Если в ячейке уже есть мина, считать ничего не нужно        

            if self.board[r][c].is_mine:

                continue

        mine_count = 0

        # Проверяем всех 8 соседей    

        for dr in [-1, 0,1]:  # Смещение по строке    

            for dc in [-1, 0, 1]: # Смещение по столбцу

            # Пропускаем саму текущую ячейку  

            if dr == 0 and dc == 0:

                continue

            # Вычисляем координаты соседа

            nr, nc = r + dr, c + dc

            # ВАЖНО: Проверяем, что сосед не вышел за границы поля

            if 0 <= nr < self.height and 0 <= nc < self.width:

                if self.board[nr][nc].is_mine:

                    mine_count += 1

        # Записываем результат в ячейку

        self.board[r][c].adjacent_mines = mine_count

І останнє – давайте зробимо так, щоб міни та цифри розставлялися автоматично одразу при створенні поля. Для цього просто викличемо наші нові методи в __init__.

Обновіть метод __init__у класі GameBoard, додавши в кінець два нові рядки:

def __init__(self, width, height, mines_count):

    self.width = width

    self.height = height

    self.mines_count = mines_count

    self.board = [[Cell() for _ in range(width)] for _ in range(height)]

    # Добавляем эти две строки:

    self._place_mines()

    self._calculate_adjacent_mines()

Крок 2. Перше вікно – малюємо наше ігрове поле

Отже, "мозок" нашої гри готовий, але поки що він працює невидимо. Час дати йому "тіло" - графічне відображення! У цьому кроці ми створимо вікно гри та намалюємо у ньому сітку, яка представляє наше ігрове поле.

Наш план:

  1. Написати базовий код для запуску вікна Pygame.
  2. Встановити основні параметри: розміри осередків, кольори.
  3. Створити функцію, яка буде малювати сітку на основі даних нашого GameBoard.
  4. Зібрати все разом у головний ігровий цикл.
  5. Продовжуємо працювати у нашому файлі main.py.

2.1. Константи та базовий запуск Pygame

Хороша практика в програмуванні - виносити значення, які можуть часто змінюватися (на зразок кольорів або розмірів), в окремі змінні на початку файлу. Їх називають константами (і часто пишуть ВЕЛИКИМИ_ЛІТами).

Додайте цей код в самий низ вашого файлу main.py після всіх класів.

import pygame

# --- Константы ---

# Размеры поля в ячейках

BOARD_WIDTH = 20

BOARD_HEIGHT = 15

MINES_COUNT = 30

# Размер одной ячейки в пикселях

CELL_SIZE = 30

# Рассчитываем размер окна в пикселях

SCREEN_WIDTH = BOARD_WIDTH * CELL_SIZE

SCREEN_HEIGHT = BOARD_HEIGHT * CELL_SIZE

# Цвета (в формате RGB)

BG_COLOR = (192, 192, 192) # Серый

LINE_COLOR = (128, 128, 128) # Темно-серый

# --- Инициализация Pygame и создание окна ---

pygame.init()

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

pygame.display.set_caption("Сапёр")

# --- Создание игрового поля ---

game_board = GameBoard(BOARD_WIDTH, BOARD_HEIGHT, MINES_COUNT)

Що ми тут зробили:

  • Імпортували pygame.
  • Встановили всі основні налаштування нашої гри. Тепер, якщо ви захочете поле більше або менше, достатньо буде змінити цифри тут.
  • Ініціалізували Pygame та створили вікно потрібного розміру.
  • Створили один екземпляр нашого "мозку" - game_board. Саме його ми й малюватимемо.

2.2. Функція для відображення поля

Давайте створимо окрему функцію, яка відповідатиме лише малювання. Це робить код більш чистим та організованим.

Додайте цю функцію main.py після блоку з константами:

def draw_board(board_obj):

    # Заливаем весь экран фоновым цветом

    screen.fill(BG_COLOR)

    # Проходим по каждой ячейке

    for r in range(board_obj.height):

        for c in range(board_obj.width):

        # Рассчитываем координаты ячейки на экране (в пикселях)            x = c * CELL_SIZE

            y = r * CELL_SIZE

            # Создаем прямоугольник для ячейки    

            rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)

            # Рисуем контур ячейки

            pygame.draw.rect(screen, LINE_COLOR, rect, 1)

Ця функція:

  • Заливає екран сірим кольором, щоб стерти попередній кадр.
  • У подвійному циклі проходить по кожному осередку.
  • Обчислює, де на екрані (у пікселях) потрібно намалювати поточну комірку.
  • Малює для кожної комірки прямокутник із чорним контуром товщиною в 1 піксель.


2.3. Головний ігровий цикл

Будь - яка гра працює всередині нескінченного циклу . На кожному витку цього циклу гра:

  1. Перевіряє дії гравця (чи він натиснув кнопку, чи закрив вікно).
  2. Оновлює логіку гри.
  3. Перемальовує екран.

Цей цикл – серце нашої програми. Додайте його до кінця файлу main.py:

# --- Главный игровой цикл ---

running = True

while running:

    # 1. Обработка событий

    for event in pygame.event.get():

        # Если пользователь нажал на "крестик"

        if event.type == pygame.QUIT:

            running = False

    # 2. Отрисовка

    draw_board(game_board)

    # 3. Обновление экрана

    pygame.display.flip()

# Корректное завершение работы

pygame.quit()

Тепер, якщо ви запустите ваш файл main.pyіз терміналу ( python main.py), ви повинні побачити вікно із сірою сіткою!

(venv) ...\minesweeper> python main.py



Крок 3. Пожвавлюємо гру – вчимо її слухати мишку

Гра без управління – не гра. Зараз наше вікно просто показує статичну картинку. Давайте навчимо нашу програму "чути" кліки миші і розуміти, яким саме осередку клікнув гравець.

Наш план на цей крок:

  • Знайти у нашому ігровому циклі місце, де відловлюються всі дії гравця.
  • Додати код для відстеження кліків миші.
  • Написати просту формулу для перетворення координат кліка (у пікселях) на координати комірки (номер рядка та стовпця).
  • Перевірити, що все працює, виводячи результат у термінал.

Ми будемо вносити зміни до головного ігрового циклу , який знаходиться в самому кінці файлу main.py.

3.1. Ловимо кліки миші

Наш ігровий цикл while running:містить цикл for event in pygame.event.get():. Цей цикл - "вуха" нашої програми. Він ловить всі події: рух миші, натискання кнопок, закриття вікна і, звичайно, кліки.

Давайте додамо перевірку на подію кліку миші.

Знайдіть свій ігровий цикл і додайте в нього блок if, як показано нижче:

ДАЛІ БУДЕ

Коментарі