Comments 17
А в следующей части добавим лутбоксы, колесо фортуны, коллекционируемые предметы, секреты, скины на мины, скины на флажки, скины на цифры, скины на игровую доску, дерево прокачки, способности, квесты, записки с сюжетом, интервью с разработчиками, дейлики, награды за непрерывный вход, премиум подписки и баттлпасы.
Создание игр, кстати, хороший путь к изучению языка. Сам так же всегда делал. Ободряю.
Не умаляю Ваш труд, просто отметил для себя, что нейронки наверняка уже могут делать такие игры. Попробовал deepseek, и он создала такую игру в 1 промт без ошибок , хотя немного неаккуратный интерфейс. Хотя объективно было 2 промта, первым я попросил Алису написать тз. Вот и думаю теперь, как дальше жить с этим знанием. )
Скрытый текст
import tkinter as tk
from tkinter import messagebox, Menu
import random
import time
import json
import os
class Cell(tk.Button):
"""Класс клетки игрового поля, наследник tk.Button"""
COLORS = {
1: 'blue',
2: 'green',
3: 'red',
4: 'darkblue',
5: 'maroon',
6: 'cyan',
7: 'black',
8: 'gray'
}
def __init__(self, master, row, col, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.row = row
self.col = col
self.is_mine = False
self.is_open = False
self.is_flagged = False
self.adjacent_mines = 0
self.configure(
font=('Arial', 12, 'bold'),
width=2, height=1,
relief='raised',
bg='#c0c0c0'
)
class GameLogic:
"""Класс игровой логики"""
DIFFICULTIES = {
'Новичок': {'rows': 9, 'cols': 9, 'mines': 10},
'Средний': {'rows': 16, 'cols': 16, 'mines': 40},
'Эксперт': {'rows': 16, 'cols': 30, 'mines': 99}
}
def __init__(self, rows, cols, mines):
self.rows = rows
self.cols = cols
self.total_mines = mines
self.remaining_mines = mines
self.first_click = True
self.game_over = False
self.start_time = None
self.elapsed_time = 0
self.board = None
def init_board(self):
"""Инициализация игрового поля"""
self.board = [[None for _ in range(self.cols)] for _ in range(self.rows)]
def place_mines(self, safe_row, safe_col):
"""Размещение мин на поле, избегая безопасной области"""
mines_placed = 0
while mines_placed < self.total_mines:
row = random.randint(0, self.rows - 1)
col = random.randint(0, self.cols - 1)
# Гарантируем, что первая клетка и ее соседи безопасны
if abs(row - safe_row) <= 1 and abs(col - safe_col) <= 1:
continue
if not self.board[row][col].is_mine:
self.board[row][col].is_mine = True
mines_placed += 1
# Обновляем счетчики соседних клеток
for r in range(max(0, row - 1), min(self.rows, row + 2)):
for c in range(max(0, col - 1), min(self.cols, col + 2)):
if not (r == row and c == col):
self.board[r][c].adjacent_mines += 1
def reveal(self, row, col):
"""Рекурсивное открытие клеток"""
cell = self.board[row][col]
if cell.is_open or cell.is_flagged or self.game_over:
return
cell.is_open = True
if cell.is_mine:
cell.configure(text='💣', bg='red')
return True # Игрок наступил на мину
if cell.adjacent_mines > 0:
cell.configure(
text=str(cell.adjacent_mines),
fg=Cell.COLORS[cell.adjacent_mines],
bg='white',
relief='sunken'
)
else:
cell.configure(bg='white', relief='sunken')
# Рекурсивно открываем соседей
for r in range(max(0, row - 1), min(self.rows, row + 2)):
for c in range(max(0, col - 1), min(self.cols, col + 2)):
if not (r == row and c == col):
self.reveal(r, c)
return False
def toggle_flag(self, row, col):
"""Установка/снятие флажка"""
cell = self.board[row][col]
if cell.is_open or self.game_over:
return
if not cell.is_flagged:
cell.is_flagged = True
self.remaining_mines -= 1
cell.configure(text='🚩')
return self.remaining_mines
else:
cell.is_flagged = False
self.remaining_mines += 1
cell.configure(text='')
return self.remaining_mines
def check_win(self):
"""Проверка условий победы"""
for row in range(self.rows):
for col in range(self.cols):
cell = self.board[row][col]
if not cell.is_mine and not cell.is_open:
return False
return True
class MinesweeperGUI:
"""Класс графического интерфейса игры"""
def __init__(self, root):
self.root = root
self.root.title('Сапёр')
# Настройки по умолчанию
self.difficulty = 'Новичок'
self.settings = {
'colors': True,
'sound': False,
'font_size': 12
}
# Загрузка рекордов
self.best_scores = self.load_scores()
# Инициализация интерфейса
self.setup_menu()
self.setup_game()
def setup_menu(self):
"""Создание меню"""
menubar = Menu(self.root)
# Меню игры
game_menu = Menu(menubar, tearoff=0)
game_menu.add_command(label="Новая игра", command=self.reset_game)
game_menu.add_separator()
game_menu.add_command(label="Выход", command=self.root.quit)
menubar.add_cascade(label="Игра", menu=game_menu)
# Меню сложности
diff_menu = Menu(menubar, tearoff=0)
for level in GameLogic.DIFFICULTIES:
diff_menu.add_command(
label=level,
command=lambda lvl=level: self.change_difficulty(lvl)
)
menubar.add_cascade(label="Сложность", menu=diff_menu)
# Меню настроек
settings_menu = Menu(menubar, tearoff=0)
settings_menu.add_checkbutton(
label="Цветные цифры",
variable=tk.BooleanVar(value=self.settings['colors']),
command=self.toggle_colors
)
menubar.add_cascade(label="Настройки", menu=settings_menu)
# Меню статистики
stats_menu = Menu(menubar, tearoff=0)
stats_menu.add_command(label="Лучшие результаты", command=self.show_best_scores)
menubar.add_cascade(label="Статистика", menu=stats_menu)
self.root.config(menu=menubar)
def setup_game(self):
"""Инициализация игровых элементов"""
# Верхняя панель
top_frame = tk.Frame(self.root)
top_frame.pack(pady=10)
self.mines_label = tk.Label(
top_frame,
text=f'💣 {GameLogic.DIFFICULTIES[self.difficulty]["mines"]}',
font=('Arial', 20),
fg='red'
)
self.mines_label.pack(side=tk.LEFT, padx=20)
self.reset_button = tk.Button(
top_frame,
text='😀',
font=('Arial', 20),
command=self.reset_game
)
self.reset_button.pack(side=tk.LEFT, padx=20)
self.timer_label = tk.Label(
top_frame,
text='00:00',
font=('Arial', 20)
)
self.timer_label.pack(side=tk.LEFT, padx=20)
# Игровое поле
self.game_frame = tk.Frame(self.root)
self.game_frame.pack()
self.reset_game()
def reset_game(self):
"""Сброс игры"""
# Очистка предыдущего поля
for widget in self.game_frame.winfo_children():
widget.destroy()
# Создание новой игры
config = GameLogic.DIFFICULTIES[self.difficulty]
self.game = GameLogic(config['rows'], config['cols'], config['mines'])
self.game.init_board()
# Создание клеток
for row in range(self.game.rows):
for col in range(self.game.cols):
cell = Cell(self.game_frame, row, col)
cell.grid(row=row, column=col, sticky='nsew')
cell.bind('<Button-1>', lambda e, r=row, c=col: self.left_click(r, c))
cell.bind('<Button-3>', lambda e, r=row, c=col: self.right_click(r, c))
self.game.board[row][col] = cell
# Настройка сетки
for i in range(self.game.rows):
self.game_frame.rowconfigure(i, weight=1)
for i in range(self.game.cols):
self.game_frame.columnconfigure(i, weight=1)
# Сброс состояния
self.game.first_click = True
self.game.game_over = False
self.game.remaining_mines = self.game.total_mines
self.mines_label.config(text=f'💣 {self.game.remaining_mines}')
self.reset_button.config(text='😀')
self.elapsed_time = 0
self.timer_running = False
self.timer_label.config(text='00:00')
def change_difficulty(self, difficulty):
"""Изменение уровня сложности"""
self.difficulty = difficulty
self.reset_game()
def start_timer(self):
"""Запуск таймера"""
if not self.timer_running:
self.timer_running = True
self.game.start_time = time.time()
self.update_timer()
def update_timer(self):
"""Обновление таймера"""
if self.timer_running:
self.elapsed_time = int(time.time() - self.game.start_time)
minutes = self.elapsed_time // 60
seconds = self.elapsed_time % 60
self.timer_label.config(text=f'{minutes:02d}:{seconds:02d}')
self.root.after(1000, self.update_timer)
def left_click(self, row, col):
"""Обработка левого клика мыши"""
if self.game.game_over:
return
if not self.timer_running:
self.start_timer()
if self.game.first_click:
self.game.place_mines(row, col)
self.game.first_click = False
mine_hit = self.game.reveal(row, col)
if mine_hit:
self.game_over(False)
elif self.game.check_win():
self.game_over(True)
def right_click(self, row, col):
"""Обработка правого клика мыши"""
if self.game.game_over or self.game.first_click:
return
remaining = self.game.toggle_flag(row, col)
self.mines_label.config(text=f'💣 {remaining}')
def game_over(self, won):
"""Завершение игры"""
self.game.game_over = True
self.timer_running = False
# Открываем все мины при проигрыше
if not won:
self.reset_button.config(text='💀')
for row in range(self.game.rows):
for col in range(self.game.cols):
cell = self.game.board[row][col]
if cell.is_mine and not cell.is_flagged:
cell.configure(text='💣', bg='red')
messagebox.showerror("Игра окончена", "Вы наступили на мину!")
else:
self.reset_button.config(text='😎')
# Сохраняем рекорд
self.save_score(self.difficulty, self.elapsed_time)
messagebox.showinfo("Победа!", f"Вы выиграли за {self.elapsed_time} секунд!")
def toggle_colors(self):
"""Переключение цветных цифр"""
self.settings['colors'] = not self.settings['colors']
# Применяем изменения к уже открытым клеткам
if hasattr(self, 'game') and self.game.board:
for row in range(self.game.rows):
for col in range(self.game.cols):
cell = self.game.board[row][col]
if cell.is_open and cell.adjacent_mines > 0:
if self.settings['colors']:
cell.configure(fg=Cell.COLORS[cell.adjacent_mines])
else:
cell.configure(fg='black')
def load_scores(self):
"""Загрузка рекордов из файла"""
try:
if os.path.exists('scores.json'):
with open('scores.json', 'r') as f:
return json.load(f)
except:
pass
return {'Новичок': None, 'Средний': None, 'Эксперт': None}
def save_score(self, difficulty, time):
"""Сохранение нового рекорда"""
if self.best_scores[difficulty] is None or time < self.best_scores[difficulty]:
self.best_scores[difficulty] = time
with open('scores.json', 'w') as f:
json.dump(self.best_scores, f)
def show_best_scores(self):
"""Отображение лучших результатов"""
scores = "\n".join(
f"{diff}: {self.best_scores[diff] or '--'} сек"
for diff in self.best_scores
)
messagebox.showinfo("Лучшие результаты", scores)
if __name__ == "__main__":
root = tk.Tk()
root.resizable(False, False)
game = MinesweeperGUI(root)
root.mainloop()
Согласен, нейросети действительно могут сгенерировать подобную игру, что печально, но одновременно с этим удивительно.
Моя цель была создать простой и понятный код, который сможет повторить даже новичок, а при возникновении вопросов - обсудить их в комментариях. Да, ИИ может сделать это лучше, но я хотел начать с доступной темы, которую могу объяснить наверняка.
В следующей (и, вероятно, последней) части планирую добавить что-то необычное с использованием нестандартных библиотек - то, что ИИ пока не делает сходу (ну или хотя бы не с двух промтов). Я считаю, что важно не просто получить готовый результат, а разобрать процесс и предложить что-то новое и доступное.
Цвета кодируются цифрами, уровни сложности - сразу строками.
Я не смотрел код, не было цели. Умные люди говорят, что со временем привычка вычитывать, или даже заглядывать в код нейронок исчезнет, смысла не будет. Зачем людям смотреть на код, который не будут изменять люди напрямую? Сейчас ведь мало кто лезет изучать, что там нагенерил транслятор/кодогенератор/компилятор/оптимизатор. Что-то тестами покроют (тоже сгенеренными), а остальному просто будут верить "на слово". И мне кажется, что это время в отношении нейронок для многих уже наступило. И я тоже пока не знаю, как к этому относиться. )
Ну я посмотрел как обрабатываются уровни мастерства. Ну, обрабатываются как-то. Ок. Возможно, этот код даже работает. Возможно, даже правильно. А если неправильно? Что с ним делать дальше?
Код работает, я проверял, выиграл одн у игру. Конкретно с этим кодом можно ничего не делать, можно руками доработать, можно довайбкодить по желанию, можно обучаться языку в диалоге с ИИ (например с просьбой пояснить непонятные участки). Все зависит о цели, которую вы преследуете. Если вопрос общего характера, про то, как исправлять и отлаживать код через ИИ, то в сети много инфы на эту тему. Разумеется, надо отдавать себе отчет, что так, как это делают сейчас, уже не будут делать "завтра". Но, имхо, потыкаться стоит, хотя бы послушать умных людей на ютубах, чтобы держать руку на пульсе, но это не точно. )
Код работает, я проверял
Тестирование наше всё, да. А если это игра уровня Ведьмака? Армия тестировщика гоняет ее неделю, собирает 100500 багов. Что делать дальше? Читать этот код невозможно. Сгенерировать новый, который тоже тестировать неделю, и в нем баги окажутся в совершенно других местах?
Я не адвокат вайбкодинга и надежных пайплайнов у меня нет. Люди и сейчас спорят о подходах и используют различные парадигмы и при классической(без ИИ) разработке. Но, думаю, трудно найти того, кто-бы не пробовал на практике или прикинуть в мыслях, как использовать нейронки в разработке. Так что в какой-то мере уже сейчас, и с большей вероятностью в ближайшем будущем, опытным путем появятся новые или будут скорректированы старые подходы. Типа сейчас время сильных перемен и возможностей.
писать тот код, который работает и читабелен одновременно?
если не будешь заглядывать в нагенеренное компиляторами, с отладкой могут быть проблемы
Ну, читабельность кода, который сейчас генерят модели да еще с подробными комментариями зачастую лучше, чем у начинающего разработчика. И найденные ошибки как правило нейронки исправляют во вполне локализованных участках кода, а не выдавая совершенно новый код в новом стиле, с другими именами классов, функций, переменных и т.д. Поэтому на данном этапе развития заглядывать глазами никто не запрещает.
В тоже время, что принципиально мешает нейронкам сейчас или "завтра" отлаживать любой сложный код без участия человека? А что мешает нейронкам писать сразу в машинных кодах или на каком-нибудь мене "сладком" для человека CIL ? Ответов на эти вопросы я не знаю, но склоняюсь к версии, что принципиально ничего не мешает, нужно лишь, чтобы это было экономический оправдано, ожиданий фундаментальных научных прорывов уже не нужно, все есть.
В оригинальном сапере, кстати, минное поле генерируется при запуске приложения, просто первый ход проверяется на наличие мины и она переставляется на соседнее незаминированное поле:
Не нашел в тексте def init_game, хотя на гитхабе всё есть
От консоли к GUI: Как написать игру «Сапёр» на Python с нуля версия GUI (часть вторая)