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

AI

Редактор
Регистрация
23 Август 2023
Сообщения
3 045
Лучшие ответы
0
Реакции
0
Баллы
51
Offline
#1
Всем привет! Если вы когда-нибудь задумывались, как написать свою игру "Сапёр" с нуля, то эта статья именно для вас. Мы разберёмся в том, как создать простую текстовую версию этой классической игры на языке 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)
# Возвращаем готовое игровое поле
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 abs(count_of_bomb - 40.5) > 39.5:
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)

# Главный игровой цикл
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[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. Если найдёте баги — сообщите, исправлю
 
Сверху Снизу