Pygame, как обрабатывать состояния

avatar
Thaddaeus Markle
27 сентября 2020 в 03:09
484
1
1

Недавно я закончил игру типа "connect-four" на python с помощью pygame. После того, как любой игрок выиграл, он показывает меню с вопросом, хотите ли вы играть снова или выйти, с кнопкой для каждого варианта. Программа знает, запускать ли игру или показывать меню на основе переменной в классе Game, которая содержит состояние игры. Проблема в том, что мне нужно иметь предложение if - elif в нескольких точках программы, чтобы увидеть, что делать. Например, в методе Game.draw он либо рисует игровое поле и фигуры, либо меню, метод Game.on_click отправляет событие либо на игровое поле, либо на кнопки в меню и т. д. Итак, мой вопрос: есть ли способ отслеживать состояние игры без использования предложений if - elif, разбросанных по всей программе?

game.py:

import pygame

from board import Board
from player import Player
from button import Button
from constants import (
    BLACK,
    RED,
    YELLOW,
    GREEN,
    WHITE,
    SQUARE_SIZE,
    ROWS,
    WIDTH,
    HEIGHT
)


class Game:
    PLAYING = 0
    HAS_WON = 1

    def __init__(self, win):
        self.win = win
        # self.running = True
        self.state = self.PLAYING
        self.running = True
        self.board = Board()

        self.players = [Player(RED, self.board), Player(YELLOW, self.board)]
        self.player_turn_counter = 0

        pygame.font.init()
        self.font = pygame.font.SysFont("Arial", 36)
        self.text = ""

        play_again_xpos = WIDTH // 5
        play_again_ypos = (HEIGHT // 5) * 4
        play_again_button = Button("Play Again",
                                   GREEN,
                                   self.play_again,
                                   xpos=play_again_xpos,
                                   ypos=play_again_ypos,
                                   show_border=True,
                                   border_color=(255, 255, 255))

        quit_xpos = (WIDTH // 5) * 4
        quit_ypos = (HEIGHT // 5) * 4
        quit_button = Button("Quit",
                             RED,
                             self.quit,
                             xpos=quit_xpos,
                             ypos=quit_ypos,
                             show_border=True,
                             border_color=(255, 255, 255))

        self.buttons = [play_again_button, quit_button]

    def play_again(self):
        self.__init__(self.win)
        self.run()

    def quit(self):
        self.running = False

    def draw(self):
        self.win.fill(BLACK)
        self.board.draw(self.win)
        if self.state == self.HAS_WON:
            # self.win.fill(WHITE)
            text = self.font.render(self.text, True, WHITE)

            text_rect = text.get_rect()
            text_rect.centerx = WIDTH // 2
            text_rect.centery = ((SQUARE_SIZE * ROWS) // 3) - SQUARE_SIZE // 2

            self.win.blit(text, text_rect)

            for button in self.buttons:
                button.draw(self.win)
            # self.play_again_button.draw(self.win)
            # self.quit_button.draw(self.win)

        pygame.display.update()

    def is_full(self, column):
        board = self.board.get_board()
        # pieces = []

        # for row in board:
        #     pieces.append(row[column])

        # return all(pieces)
        return all([row[column] for row in board])

    def on_win(self, color):
        self.state = self.HAS_WON
        colors = {(255, 255, 0): "Yellow", (255, 0, 0): "Red"}
        self.text = f"{colors[color]} has won!"
        # print(f"{colors[color]} has won!")

    def on_click(self):
        if self.state == self.PLAYING:
            column = self.board.get_column()
            if not self.is_full(column):
                self.player_turn_counter += 1
                self.player_turn_counter = self.player_turn_counter % 2
            self.players[self.player_turn_counter].on_click()
            has_won, color = self.board.check_for_win()
            if has_won:
                self.on_win(color)
        elif self.state == self.HAS_WON:
            for button in self.buttons:
                button.on_click()

    def run(self):
        clock = pygame.time.Clock()

        while self.running:
            clock.tick(60)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False

                if event.type == pygame.MOUSEBUTTONDOWN:
                    self.on_click()

            self.draw()

Вот репозиторий github для всей программы: https://github.com/pianocomposer321/ConnectFour.git

Источник
Mike67
27 сентября 2020 в 03:36
0

Я бы разделил игру на 2 основные функции - меню и игра. Каждая функция имеет цикл отрисовки (while running:...). В функции меню цикл заканчивается, когда пользователь нажимает «Играть», а функция игры завершается, когда игрок выигрывает\проигрывает. Поместите основные функции в цикл, чтобы меню возвращалось после окончания игры. Программа завершается, когда пользователь выбирает «Выход» в меню.

Rabbid76
1 февраля 2021 в 06:49
0

Проблема решена?

Thaddaeus Markle
1 февраля 2021 в 22:33
0

Да, в итоге я пошел другим путем, чем ваш ответ, если я правильно помню, но я продолжу и приму ваш ответ для дальнейшего использования, если кто-то еще наткнется на этот вопрос.

Ответы (1)

avatar
Rabbid76
21 декабря 2020 в 08:53
1

Создайте два класса с одинаковым интерфейсом, по одному для каждого состояния. Классы содержат реализацию, относящуюся к состоянию игры:

class Playing:
    def __init__(self, game)
        self.game = game

    def draw(self):
        # [...]

    def on_click(self):
        # [...]
class HasWon:
    def __init__(self, game):
        self.game = game

    def draw(self):
        # [...]

    def on_click(self):
        # [...]

Класс Game использует классы, реализующие состояния игры. Дополнительный атрибут (self.current) относится к текущему классу state. Дополнительный метод (set_state) переключает переменную состояния и устанавливает self.current в зависимости от состояния. Это единственный момент в программе, где вам нужен пункт if - elif. В остальных методах вы можете делегировать метод реализации состояния игры (например, self.current.draw(), self.current.on_click()):

class Game:
    PLAYING = 0
    HAS_WON = 1

    def __init__(self, win):
        # [...]

        self.playing = Playing(self)
        self.haswon = HasWon(self)
        self.set_state(self.PLAYING)
        
    def set_state(new_state):
        self.state = self.PLAYING
        if self.state == self.PLAYING:
            self.current = self.playing
        elif self.state == self.HAS_WON:
            self.current = self.haswon

    def draw(self):
        self.win.fill(BLACK)
        self.board.draw(self.win)
        
        self.current.draw()

        pygame.display.update()

    def on_win(self, color):
        self.set_state(self.HAS_WON)
        # [...]
        

    def on_click(self):
        self.current.on_click()