Search
Write a publication
Pull to refresh

От консоли к GUI: Как написать игру «Сапёр» на Python с нуля версия консоли (часть первая)

Level of difficultyEasy
Reading time5 min
Views1.3K

Всем привет! Если вы когда-нибудь задумывались, как написать свою игру "Сапёр" с нуля, то эта статья именно для вас. Мы разберёмся в том, как создать простую текстовую версию этой классической игры на языке Python. Здесь не потребуется никаких особых знаний — просто следуйте пошаговым инструкциям, и вы самостоятельно создадите свою уникальную версию «Сапёра».

Что такое «Сапёр» и почему именно эта игра?

Пример игры "Сапёр"
Пример игры "Сапёр"

Игра "Сапёр" — это популярная логическая игра, в которой игрок открывает клетки на поле, пытаясь избежать мин и угадывая, где они расположены. Считаю, что это идеальный проект для изучения Python:

  • Простые правила

  • Отличный способ освоить рекурсию и работу с GUI (ps в будущем)

  • Наглядный результат, который можно улучшать бесконечно

Подготовка к разработке

Перед тем как приступить к кодированию, убедитесь, что у вас установлен Python (версия 3.6 или выше). Вы можете скачать её с официального сайта python.org.

Шаг 1: Генерация карты


Первым делом нам нужно создать карту, заполненную пустыми клетками и минами. Для этого мы напишем функцию Generator_Map, которая будет генерировать игровое поле заданного размера и случайным образом размещать на нём мины. (буду стараться подробно описать комментариями в коде, если не понятно, то напишите в коммментариях, чтобы обновил статью и сделал её более подробной)

import random # Импорт модуля для работы со случайными числами



def Generator_Map(mines):
    """Метод создания карты"""
    # Задаем размер игрового поля (классический сапёр - 9x9)
    size = 9
    # Создаем пустое игровое поле, заполненное пробелами
    # Пробел означает пустую клетку без мин и чисел
    map = [[' ' for _ in range(size)] for _ in range(size)]
    mines_count = mines

    mines = set()  # убрать дубликаты
    # Генерация случайных мин
    while len(mines) < mines_count:
        # Генерируем случайные координаты в пределах поля
        x, y = random.randint(0, size - 1), random.randint(0, size - 1)  # возвращает случайное число N, где a ≤ N ≤ b
        mines.add((x, y))
        # Помечаем клетку с миной
        map[x][y] = 'M'

    # Заполняем числами (количество мин вокруг) - список смещений для проверки 8 соседних клеток:
    directions = [(-1, -1), (-1, 0), (-1, 1),# Верхние соседи
                  (0, -1), (0, 1),           # Боковые соседи
                  (1, -1), (1, 0), (1, 1)]   # Нижние соседи
    # Проходим по всем клеткам поля для заполнения чисел
    for x in range(size):
        for y in range(size):
            if map[x][y] != 'M':
                count = 0 # Счетчик мин вокруг
                for dx, dy in directions:
                    nx, ny = x + dx, y + dy
                    if 0 <= nx < size and 0 <= ny < size:
                        if map[nx][ny] == 'M':
                            count += 1
                if count > 0:
                    map[x][y] = str(count)

    map_mines = [[' '] + list(range(1, size + 1))]
    for i in range(size):
        map_mines.append([i + 1] + map[i])
    # Возвращаем готовое игровое поле
    return map_mines

Шаг 2: Отображение карты

Теперь, когда у нас есть карта, необходимо научиться её отображать. Для этого мы напишем функцию print_map, которая будет выводить текущее состояние игры.

def print_map(map, flags=None):
    """Метод прорисовки игрового поля в консоли"""
    if flags is None:
        flags = set()
    # Перебираем строки игрового поля с их индексами
    for i, row in enumerate(map):
        printed_row = []
        for j, cell in enumerate(row):
           # Проверяем, стоит ли флаг на текущей позиции (с учетом смещения)
            if (i - 1, j - 1) in flags and cell == 'M':
                printed_row.append('F')
                # Если флаг стоит на мине - показываем 'F' (правильная пометка)
            elif (i - 1, j - 1) in flags:
               # Если флаг стоит не на мине - показываем '?' (ошибка игрока)
                printed_row.append('?')
            else:
              # Для всех остальных случаев выводим содержимое ячейки как есть
                printed_row.append(str(cell))
        # Собираем строку из элементов, разделяя их пробелами и выводим
        print(' '.join(printed_row))

Шаг 3: Обработка пользовательского ввода

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

def get_bomb_count():
    """Метод запроса у пользователя количества мин"""
    while True:
        count_of_bomb = int(input("Введите одно число - количество мин от 1-80 шт.\n"))
        if count_of_bomb < 1 or count_of_bomb > 80:
            continue
        return count_of_bomb
        break

Шаг 4: Логика открытия клеток

Сердце игры — функция reveal_cells(), которая реализует рекурсивное открытие пустых областей. Вот как это работает:

def reveal_cells(map, player_map, display_map, x, y, size):
    """Рекурсивно открывает все соседние пустые клетки"""
    # Проверяем, что координаты в пределах поля и клетка еще не открыта
    if not (0 <= x < size and 0 <= y < size) or player_map[x][y] != '-':
        return

    # Открываем текущую клетку
    player_map[x][y] = map[x + 1][y + 1]  # +1 из-за заголовков в map
    display_map[x + 1][y + 1] = player_map[x][y]  # Обновляем отображаемую карту

    # Если клетка пустая (не число и не мина), открываем соседей
    if player_map[x][y] == ' ':
        directions = [(-1, -1), (-1, 0), (-1, 1),
                      (0, -1), (0, 1),
                      (1, -1), (1, 0), (1, 1)]

        for dx, dy in directions:
            reveal_cells(map, player_map, display_map, x + dx, y + dy, size)

Шаг 5: Обработка флагов

Игрок может помечать предполагаемые мины через функцию get_action():

def get_action():
    """Метод запроса действия: открыть или пометить"""
    while True:
        action = input("Выберите действие (d - открыть, f - пометить флагом): ").lower()
        if action in ('d', 'f'):
            return action
        print("Используйте 'd' или 'f'!")

Шаг 6: Проверка ввода

Проверка правильности формата и контроль диапазона чисел (1-9)

def get_coordinates():
    """Получение координат с проверкой"""
    while True:
        try:
          # Запрашиваем ввод и разбиваем по разделителю '-'
            coords = input("Введите координаты (формат Y-X): ").split('-')
            if len(coords) != 2:
                raise ValueError
            x, y = map(int, coords)# Преобразуем в числа
            # Проверяем диапазон координат (1-9)
            if abs(x - 5.5) > 4.5 or abs(y - 5.5) > 4.5:
                print("Координаты должны быть от 1 до 9!")
                continue
            return x - 1, y - 1  # Переводим в индексы массива
        except ValueError:
            print("Ошибка! Используйте формат 'Y-X' (например, 3-5)")

Шаг 7: Запуск игры и последняя часть кода

Главный цикл в функции main() управляет игровым процессом:

def main():
    # Инициализация структур данных
    dictionary_for_x_y = {'y': [], 'x': []}  # История ходов
    bomb_count = get_bomb_count()             # Запрос количества мин
    hidden_map = Generator_Map(bomb_count)    # Генерация поля с минами
    player_map = [['-' for _ in range(9)] for _ in range(9)]  # Видимое поле
    flags = set()               # Множество флагов
    correct_flags = set()       # Правильно поставленные флаги

    # Создание отображаемой карты с номерами строк/столбцов
    display_map = []
    display_map.append([' '] + list(range(1, 10)))
    for i in range(9):
        display_map.append([i + 1] + player_map[i])

    # Главный игровой цикл
    while True:
        print("\nТекущая карта:")
        print_map(display_map, flags)
        
        # Получение действия и координат
        action = get_action()
        x, y = get_coordinates()

        # Проверка на уже открытую клетку
        if player_map[x][y] != '-':
            print("Эта клетка уже открыта!")
            continue

        # Логика установки/снятия флага
        if action == 'f':
            if (x, y) in flags:
                flags.remove((x, y))
                if hidden_map[x + 1][y + 1] == 'M':
                    correct_flags.remove((x, y))
            else:
                flags.add((x, y))
                if hidden_map[x + 1][y + 1] == 'M':
                    correct_flags.add((x, y))

            # Проверка победы по флагам
            if len(correct_flags) == bomb_count and len(flags) == bomb_count:
                print("\nПоздравляем! Вы правильно отметили все мины!")
                print("Игровое поле:")
                print_map(hidden_map)
                break
            continue

        # Логика открытия клетки
        if (x, y) in flags:
            print("Сначала уберите флаг с этой клетки!")
            continue

        if hidden_map[x + 1][y + 1] == 'M':
            print("\nBOOM! Вы наступили на мину!")
            print("Игровое поле:")
            print_map(hidden_map)
            break

        # Рекурсивное открытие клеток
        reveal_cells(hidden_map, player_map, display_map, x, y, 9)

        # Обновление истории ходов
        dictionary_for_x_y['y'].append(x + 1)
        dictionary_for_x_y['x'].append(y + 1)
        print(x + 1, y + 1, dictionary_for_x_y)

        # Проверка победы по открытым клеткам
        if all(player_map[i][j] != '-' or hidden_map[i + 1][j + 1] == 'M'
               for i in range(9) for j in range(9)):
            print("\nПоздравляем! Вы открыли все безопасные клетки!")
            print("Игровое поле:")
            print_map(hidden_map)
            break

main() # запуск главной функции          

Итог: Что мы получили?

Пример консольного вывода
Пример консольного вывода
  1. Полноценная консольная версия "Сапёра"

  2. Чистый код с разделением логики и отображения

  3. Гибкую систему, которую можно расширять:

    • Добавить таймер

    • Реализовать уровни сложности

    • Перевести на GUI (Tkinter/PyQt)

Полный код доступен в репозитории GitHub.

Пишите идеи в комментарии! Ваш вариант кода может попасть в обновлённую версию статьи.

P.S. Если найдёте баги — сообщите, исправлю

Tags:
Hubs:
-2
Comments10

Articles