All streams
Search
Write a publication
Pull to refresh

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, хотя на гитхабе всё есть

Спасибо за вашу внимательность, скорее всего не заметил при переносе кода (уже исправил), но больше удивляет, что за месяц никто не заметил )

Sign up to leave a comment.

Articles