Gioco tris con Pygame
Ho programmato un tris usando pygame. È il primo gioco che realizzo. Apprezzerei davvero qualsiasi commento tu abbia sullo stile di codifica e qualsiasi miglioramento che potrebbe essere fatto.
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()
Ho separato il codice in diversi moduli. Il graphics
modulo include le classi per creare i diversi sprite usati dal gioco (il tabellone, le celle del tabellone e i messaggi di testo). Il logic
modulo è utilizzato per il computer AI.
Condivido solo questa "parte del gioco" del codice perché è qui che apprezzerei i tuoi commenti. Se desideri controllare gli altri moduli o ottenerli per poter giocare, puoi farlo qui .
La ringrazio molto per il vostro tempo.
Risposte
Analisi statica del codice
Lascia che ti guidi attraverso alcuni dei miei strumenti preferiti di analisi del codice statico :
- Scrivi annotazioni : non ne usi nessuna. Sono fantastici e tutti dovrebbero usarli per Python 3.6+. Poiché nessuno dovrebbe usare le versioni di Python precedenti alla 3.6, tutti dovrebbero usarle ^^
- black and isort : per la formattazione generale, mi piace usare l'autoformatter nero. Il tuo codice è già buono quando si tratta di questo / PEP8
- Flake8 : controlli di stile di maggio. Ad esempio, il parametro da a
create_cells
dovrebbe essere minuscolo (cell
, nonCELL
). - McCabe : Per verificare se ci sono parti difficili da capire, utilizzo
python -m mccabe --min 5 yourscript.py
. Si lamenta solo del loop di gioco.
Altri commenti
funzione principale
Di solito mi piace mettere tutto in funzioni. Puoi inserire il loop di gioco in una main
funzione e quindi aggiungere:
if __name__ == "__main__":
main()
Questo ha il vantaggio che puoi importare parti di questo file senza eseguirlo. Questo rende i test a volte molto più facili.
Proprietà e YAGNI
Python ha il decoratore di proprietà . Nel tuo caso, tuttavia, rimuoverei get_cells
e accedo .cells
direttamente. Rende il codice più facile da leggere e se ne hai bisogno, puoi comunque introdurre la proprietà in un secondo momento.
C'è YAGNI - non ne avrai bisogno. Non creare astrazioni se non ne hai bisogno. Il metodo to_reset
sembra essere un buon esempio.
Stile Docstring
Il tuo stile docstring è molto simile a numpydoc ; forse puoi cambiarlo per essere coerente con quello. Ciò significa in particolare che non è necessario documentare tutti i metodi all'interno della classe docstring. Invece, si documenta il metodo all'interno del metodo. Molti editor possono quindi utilizzarlo anche per mostrare aiuto sul metodo.
Denominazione
I buoni nomi sono difficili da trovare. Vogliamo nomi chiari nel contesto, ma anche nomi non troppo lunghi in stile Java.
Ecco alcuni esempi che trovo difficile da capire:
- timer2: Di solito quando devi aggiungere numeri, questo è un segno che non è un buon nome
- player_play: dovrebbe essere chiamato
is_players_turn
, ma questo è già un metodo - is_player_turn (): Non sono sicuro di cosa
self.board_logic.check_winner
stia facendo, quindi non sono sicuro di quale sarebbe un buon nome. Ma poiché questo metodo viene chiamato solo una volta, mi chiedo se il metodo stesso sia davvero necessario.
Enumerazioni
Non so cosa self.board_logic.current_player() == "o"
stia facendo, ma forse il metodo potrebbe restituire un enum e potresti confrontare con un enum? I confronti tra stringhe sono soggetti a errori di battitura