I've created a Noughts and Crosses game in Python using pygame, and I'm quite pleased with the result that I've produced. I've managed to reduce the code to what it is now, as it used to be much longer, and I'd like to submit it here for review. I know that it's common practice to not have more than 79 characters on any one line, but I'm not too bothered about this. I don't know what sort of things could possibly be done to improve the code further, or to correct any other things that I've done that go against the PEP 8 style guide.
import os, sys, pygame, classes.sprite #Imports the necessary modules as well a custom sprite class
from pygame.locals import *
pygame.init() #Initialises the pygame module
path, fname = os.path.split(__file__)
path = os.path.join(path, 'data') #Defines the path that stores all of the data (Images)
#Define some constants
WIDTH = 800
HEIGHT = 600
SCREEN = pygame.display.set_mode((WIDTH,HEIGHT))
CLOCK = pygame.time.Clock()
FPS = 30
FONT = 'LovelyK'
ICON = pygame.transform.scale(pygame.image.load(os.path.join(path, 'icon.png')).convert_alpha(), (32, 32))
tick = 0
#Initialise the custom sprite module
classes.sprite.init(SCREEN, FPS)
#Sets the game's icon and caption
pygame.display.set_icon(ICON)
pygame.display.set_caption('Noughts and Crosses')
#Some variables needed for the game
turn = 0
wins = [0, 0]
grid = [0, 0, 0,
0, 0, 0,
0, 0, 0]
winning_lines = [[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]]
p1_colour, p2_colour = (237, 28, 36), ( 62, 72, 204)
def print_to_screen(text, position = (0, 0), centered = False, size = 30, colour = (255, 255, 255), font = 'Courier New'):
'''Prints text to the screen'''
FONT = pygame.font.SysFont(font, size)
label = FONT.render(str(text), 1, colour)
if centered == True:
position = label.get_rect(center = position)
SCREEN.blit(label, position)
def Quit():
'''Shuts down the game if it's closed'''
for event in pygame.event.get():
if event.type == QUIT:
print('Quit the game...')
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
print('Quit the game...')
pygame.quit()
sys.exit()
def wait(seconds):
'''Pauses the program for the specified amout of time'''
for i in range(int(seconds * FPS)):
Quit()
pygame.display.update()
CLOCK.tick(FPS)
def reset_grid():
'''Resets the playing grid'''
global grid
grid = [0, 0, 0,
0, 0, 0,
0, 0, 0]
def test_for_win():
'''Tests to see if one of the players has won'''
global grid
for i in winning_lines:
if grid[i[0]] == grid[i[1]] == grid[i[2]]:
if grid[i[0]] == 0: continue
elif grid[i[0]] == 'X':
print_to_screen('Winner!', ( 15, 450), False, 80, p1_colour, FONT)
wins[0] += 1
elif grid[i[0]] == 'O':
print_to_screen('Winner!', (570, 450), False, 80, p2_colour, FONT)
wins[1] += 1
wait(2)
reset_grid()
def test_for_draw():
'''Tests to see if the game results in a tie'''
global grid
finish = 0
for i in grid:
if i == 0: finish += 1
if finish == 0:
print_to_screen('It\'s a draw!', (400, 550), True, 100, OLIVE, FONT)
wait(2)
reset_grid()
def main():
global turn, grid, wins
clicked = False #Used to stop holding the mouse down from affecting the program
#Create the game board using the custom sprite class
boards = [classes.sprite.Sprite(x = x, y = y) for x, y in [[250, 200], [350, 200], [450, 200],
[250, 300], [350, 300], [450, 300],
[250, 400], [350, 400], [450, 400]]]
#Set the image to be shown for each tile (image size: 100px, 100px)
for board in boards: board.set_image('board.png')
#Custom sprite group class, allow all the sprites to be rendered in one method
board_group = classes.sprite.SpriteGroup(boards)
#Creates instance of the sprite class to show the logo
logo = classes.sprite.Sprite(100, 20)
logo.set_image('logo.png')
while True: #Main loop
SCREEN.fill(MAROON)
#Closes the game if the 'X' is clicked or is ESC is pressed
Quit()
#Changes the colour of the lettering depending on the turn
if turn == 0:
p1_colour = (237, 28, 36)
p2_colour = ( 36, 44, 134)
else:
p1_colour = (157, 13, 20)
p2_colour = ( 62, 72, 204)
#Updates the image of a tile if the mouse hovers over it
i = 0
for board in boards:
if board.mouse_hover() and grid[i] == 0:
if turn == 0: board.set_image('cross.png')
elif turn == 1: board.set_image('nought.png')
else:
if grid[i] == 0: board.set_image('board.png')
i += 1
#Updates grid with player's turn when tile is clicked
if pygame.mouse.get_pressed()[0]:
if clicked == False:
clicked = True
i = 0
for board in boards:
if board.mouse_click() and grid[i] == 0:
if turn == 0:
board.set_image('cross.png')
grid[i] = 'X'
turn = 1
elif turn == 1:
board.set_image('nought.png')
grid[i] = 'O'
turn = 0
i += 1
else: clicked = False
#Prints whose turn it is and scores to the screen
print_to_screen('P1', ( 40, 200), False, 250, p1_colour, FONT)
print_to_screen('P2', (570, 200), False, 250, p2_colour, FONT)
print_to_screen(wins[0], (110, 550), True, 100, p1_colour, FONT)
print_to_screen(wins[1], (670, 550), True, 100, p2_colour, FONT)
#Renders the board tiles and the logo
board_group.draw()
logo.render()
test_for_win()
test_for_draw()
#Update the display
pygame.display.update()
CLOCK.tick(FPS)
if __name__ == '__main__':
main()
Here's an image of what the game looks like when played: