Pygameを使用した三目並べゲーム

Aug 22 2020

pygameを使用して三目並べゲームをプログラムしました。それは私が作る最初のゲームです。コーディングスタイルや改善点についてのコメントをいただければ幸いです。

import pygame
import graphics # External module graphics.py
import logic    # External module logic.py

# Setting up window
HEIGHT = 600
WIDTH = 480
FPS = 30
TITLE = "Tic-tac-toe"

# Defining colors
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)

# Initializing pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
clock = pygame.time.Clock()

# Game settings
TOP = 100
SIZE = 450
CELL = SIZE // 3
CPU_SPEED = FPS // 2


class TicTacToe():
    """Main tic-tac-toe game object.

    Attributes
    ----------
    board : graphics.Board
        pygame.Sprite object. Consists mainly of the lines that make
        up the grid.
    board_logic : logic.Board
        Object that defines the game logic and AI.
    cells : 3 x 3 nested list.
        List containing individual graphics.Cell objects.
        Each `Cell` object is a cell sprite.
    player_score : graphics.Text
        Sprite object that shows the player score.
    cpu_score : graphics.Text
        Sprite object that shows the cpu score.
    end_message : graphics.Text
        Sprite object that shows a message when the game ends:
        win/lose/tie.
    player_play : bool
        Determine when it's the player's turn to play.
    check : bool
        Determine when the check the end of the game.
        Set to `True` after each player or cpu move.
    reset : bool
        Determines when to reset the board after the end of game.
    timer : int
        Counter to limit cpu moves speed.
    timer2 : int
        Counter to limit end game reactions speed.

    Methods
    -------
    add_to_group(sprites_group)
        Add all of the sprite objects of the `TicTacToe` object
        to `sprites_group`.
    create_cells(CELL)
        Create 9 `graphics.Cell` objects of size `SIZE` x `SIZE`
        and append them to `self.cells`.
    update()
        Check if the game has ended or a player has won. Calls
        methods accordingly. Verify when to check with `self.check`
        and limit frequency using `self.timer2`.
        Verifies it's the computer's turn to play by checking
        `self.player_turn`. Calls `computer_turn` method when
        corresponds. Limits computer's play speed using
        `self.timer`.
    computer_turn()
        Use `graphics.board_logic.computer_turn()` method to determine
        next case to fill.
        Fill cell accordingly and update to check board end game
        conditions and player's turn.
    reset_game()
        Reset `graphics.Board` to delete extra lines on the board.
        Reset all `graphics.Cell` sprites to a blank surface.
        Reset `self.end_message` to show an empty string.
    update_score()
        Add one point to the corresponding current player score.
        Either `self.cpu_score` or `self.player_score`.
    win_message()
        Modify `self.end_message` to show a win or lose message.
    tie_message()
        Modify `self.end_message` to show a tied game message.
    get_cells():
        Return `self.cells` containing all the `graphics.Cell`
        sprite objects.
    is_player_turn():
        Return `True` when player's turn conditions are met.
        `False` otherwise.
    to_reset():
        Return `self.reset` attribute. Use to determine when
        to reset the game board.

    """

    def __init__(self):
        self.board = graphics.Board()
        self.board_logic = logic.Board()
        self.cells = self.create_cells(CELL)
        self.player_score = graphics.Text('P1 : 0',
                                          (WIDTH * 4 // 5, TOP * 1 // 3))
        self.cpu_score = graphics.Text('CPU : 0',
                                       (WIDTH * 4 // 5, TOP * 2 // 3))
        self.end_message = graphics.Text('',
                                         (WIDTH * 2 // 5, TOP // 2))
        self.player_play = False
        self.check = False
        self.reset = False
        self.timer = 0
        self.timer2 = 0

    def add_to_group(self, sprites_group):
        sprites_group.add(self.player_score)
        sprites_group.add(self.cpu_score)
        sprites_group.add(self.end_message)
        for cell in self.cells:
            sprites_group.add(cell)
        sprites_group.add(self.board)

    def create_cells(self, CELL=CELL):
        cells = []
        for i in range(3):
            row = []
            for j in range(3):
                pos = (self.board.rect.left + j * CELL,
                       self.board.rect.top + i * CELL)
                row.append(graphics.Cell(pos))
            cells.append(row)
        return cells

    def update(self):
        if self.check:
            if self.timer2 > CPU_SPEED // 2:
                self.timer2 = 0
                self.check = False
                if self.board_logic.check_winner():
                    self.board.draw_triple(self.board_logic.win_line_pos())
                    self.win_message()
                    self.update_score()
                    self.reset = True
                elif self.board_logic.endgame():
                    self.tie_message()
                    self.reset = True
            else:
                self.timer2 += 1

        if self.timer < CPU_SPEED:
            self.timer += 1
        else:
            self.timer = 0
            if not self.is_player_turn() and not self.to_reset():
                self.computer_turn()

    def computer_turn(self):
        i, j = self.board_logic.computer_turn()
        # print(self.board_logic)
        self.cells[i][j].computer_fill()
        self.check = True
        self.player_play = True

    def player_turn(self, ij):
        self.timer = 0
        self.board_logic.user_turn(ij)
        i, j = ij
        self.cells[i][j].player_fill()
        self.player_play = False
        self.check = True

    def reset_game(self):
        self.board.new_board()
        self.board_logic.reset()
        self.end_message.write('')
        for i in range(3):
            for j in range(3):
                self.cells[i][j].reset()
        self.reset = False

    def update_score(self):
        if self.board_logic.current_player() == 'o':
            self.player_score.add1()
        else:
            self.cpu_score.add1()

    def win_message(self):
        if self.board_logic.current_player() == 'o':
            self.end_message.write('You win!')
        else:
            self.end_message.write('You lose!')

    def tie_message(self):
        self.end_message.write('     Tie!')

    def get_cells(self):
        return self.cells

    def is_player_turn(self):
        return self.player_play and not self.board_logic.check_winner()

    def to_reset(self):
        return self.reset


all_sprites = pygame.sprite.Group()
game = TicTacToe()
game.add_to_group(all_sprites)

# Game loop
running = True
while running:
    clock.tick(FPS)

    # Process input
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.MOUSEBUTTONUP:
            if game.to_reset():
                game.reset_game()
            elif game.is_player_turn():
                pos = pygame.mouse.get_pos()
                cells = game.get_cells()
                for i in range(3):
                    for j in range(3):
                        if cells[i][j].hits(pos):
                            game.player_turn((i, j))

    game.update()

    # Draw / render
    screen.fill(BLACK)
    all_sprites.draw(screen)
    pygame.display.flip()

pygame.quit()

コードを異なるモジュールに分けました。このgraphicsモジュールには、ゲームで使用されるさまざまなスプライト(ゲームボード、ボードセル、テキストメッセージ)を作成するためのクラスが含まれています。このlogicモジュールはコンピューターAIに使用されます。

コードのこの「ゲーム部分」のみを共有しているのは、ここでコメントをいただければ幸いです。他のモジュールをチェックしたり、ゲームをプレイするためにそれらを入手したい場合は、ここで行うことができます。

どうもありがとうございました。

回答

2 MartinThoma Aug 23 2020 at 22:31

静的コード分析

私のお気に入りの静的コード分析ツールのいくつかを紹介します。

  • 型注釈:何も使用しません。それらは素晴らしく、誰もがPython3.6以降でそれらを使用する必要があります。3.6より前のバージョンのPythonを使用する必要はないため、すべての人が使用する必要があります^^
  • 黒とisort:一般的なフォーマットには、オートフォーマッターの黒を使用するのが好きです。これに関しては、あなたのコードはすでに良いです/ PEP8
  • Flake8:スタイルチェックが可能です。たとえば、パラメータtocreate_cellsは小文字(cellではなくCELL)である必要があります。
  • マッケイブ:わかりにくい部分があるかどうかを確認するために、を使用していますpython -m mccabe --min 5 yourscript.py。ゲームループについてのみ文句を言います。

他のコメント

メイン機能

私は通常、すべてを関数に入れるのが好きです。ゲームループをmain関数に入れて、次を追加することができます。

if __name__ == "__main__":
    main()

これには、実行せずにこのファイルの一部をインポートできるという利点があります。これにより、テストがはるかに簡単になる場合があります。

プロパティとYAGNI

Pythonにはプロパティデコレータがあります。ただし、あなたの場合は、削除get_cellsして.cells直接アクセスします。コードが読みやすくなり、必要に応じて、後でプロパティを導入できます。

YAGNIがあります-あなたはそれを必要としないでしょう。必要がない場合は、抽象化を作成しないでください。この方法to_resetは良い例のようです。

ドックストリングスタイル

docstringスタイルはnumpydocと非常によく似ています。多分あなたはそれと一致するようにそれを変更することができます。これは特に、クラスdocstring内のすべてのメソッドを文書化する必要がないことを意味します。代わりに、メソッド内でメソッドを文書化します。多くの編集者は、これを使用してメソッドに関するヘルプを表示することもできます。

ネーミング

良い名前を見つけるのは難しいです。コンテキストで明確な名前が必要ですが、Javaスタイルの過度に長い名前も必要ありません。

これが私が理解するのが難しいと思ういくつかの例です:

  • timer2:通常、数字を追加する必要がある場合、それは適切な名前ではないことを示しています
  • player_play:呼び出す必要がありますis_players_turnが、それはすでにメソッドです
  • is_player_turn():何self.board_logic.check_winnerが行われているのかわからないので、良い名前が何であるかわかりません。しかし、このメソッドは一度しか呼び出されないので、メソッド自体が本当に必要かどうか疑問に思います。

列挙型

self.board_logic.current_player() == "o"が行われているのかわかりませんが、メソッドが列挙型を返し、列挙型と比較できる可能性がありますか?文字列の比較はタイプミスを起こしやすい