Created
April 17, 2019 09:04
-
-
Save timbledum/fd4763dcd703b0c0c51319e6dbe7ebeb to your computer and use it in GitHub Desktop.
Tic tac toe refactoring for the fam here: https://www.reddit.com/r/learnpython/comments/be0x63/just_finished_a_game_of_tic_tac_toeany_ways_to/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import sys | |
# Window | |
WIDTH = 300 | |
HEIGHT = 325 | |
LINETHICKNESS = 5 # The thickness of the line used to represent 3 in a row | |
RED = (250, 0, 0) | |
WHITE = (250, 250, 250) | |
BLACK = (10, 10, 10) | |
CELL_DIMENSIONS = 100 | |
class Game: | |
""" A class to contain the game state, and useful methods for modifying it.""" | |
def __init__(self): | |
# Game Settings | |
# Makes X start first by default, will get changed to decide whos turn it is | |
self.PlayerTurn = "X" | |
# Creates our board layout to put values to. by default, the board is blank. | |
self.grid = [[None, None, None], [None, None, None], [None, None, None]] | |
self.winner = None # Adds a winner variable for later | |
def game_message(self): | |
# Determine what to write | |
if self.winner == None and self.checkforFull() == False: | |
# If there isn't a winner and the board isn't full | |
message = "{}'s turn".format(self.PlayerTurn) | |
# make message equal to whoevers turn it is | |
elif self.winner != None: | |
message = "{} Wins!".format(self.winner) | |
# Otherwise, display whoever wins instead | |
elif self.winner == None and self.checkforFull() == True: | |
# If there isn't a winner and the board is full | |
message = "A tie!" # Call for a tie | |
return message | |
def checkforFull(self): | |
for row in self.grid: | |
# For every row in grid (keeping in mind grid is just made up of 3 lists) | |
if None in row: # If there is None in any of the lists in grid | |
finished = False # The game hasn't finished yet | |
break | |
else: # If there isn't... | |
finished = True # The game has finished | |
return finished | |
def gameFinished(self): | |
"""Job: To check for either a player win or a tie | |
board is the game board surface.""" | |
# For extra info: | |
# To draw a line,the following variables are needed (in order) | |
# Surface, color, start position, end position, width | |
grid = self.grid | |
# Check for rows in which someone has won | |
for row in range(0, 3): # In any of the rows (i.e row 1, row 2, or row 3) | |
# If the 1st, 2nd and 3rd values are the same and are not empty... | |
if (grid[row][0] == grid[row][1] == grid[row][2]) and ( | |
grid[row][0] is not None | |
): | |
self.winner = grid[row][0] | |
# The winner is the symbol on the first value of the row (i.e column 1) | |
# Draws a line through the row | |
return ("row", row) # Break out of the for loop | |
# Check for columns in which someone has won | |
for col in range( | |
0, 3 | |
): # IN any of the columns (i.e column 1, column 2 or column 3 | |
# If the 1st, 2nd and 3rd values are the same and are not empty... | |
if (grid[0][col] == grid[1][col] == grid[2][col]) and ( | |
grid[0][col] is not None | |
): | |
self.winner = grid[0][col] | |
# The winner is the symbol on the first value of the column (i.e row 1) | |
# Draws a line through the column | |
return ("column", col) # Break out of the for loop | |
# Check for diagonal wins | |
# If the diagonal values starting from the top left are all the same and are not empty | |
if (grid[0][0] == grid[1][1] == grid[2][2]) and (grid[0][0] is not None): | |
self.winner = grid[0][0] # The winner is the symbol in the top left | |
return ("diag1", None) # Draw a line through the diagonal | |
# If the diagonal values starting from the top right are all the same and are not empty | |
if (grid[0][2] == grid[1][1] == grid[2][0]) and (grid[0][2] is not None): | |
self.winner = grid[0][2] # The winner is the symbol in the top right | |
return ("diag2", None) # Draw a line through the diagonal | |
# Check for ties | |
# If the board is full and winner is not equal to X or O | |
# It is a tie. Change the status | |
return (None, None) | |
def toggle_turn(self): | |
"""Turn the player turn to the opposite player.""" | |
if self.PlayerTurn == "X": | |
self.PlayerTurn = "O" | |
elif self.PlayerTurn == "O": | |
self.PlayerTurn = "X" | |
def is_populated(self, row, col): | |
cell = self.grid[row][col] | |
return cell != None | |
class App: | |
""" A class to initialise the game, take input, and display the board.""" | |
def __init__(self): | |
pygame.init() | |
self.ttt = pygame.display.set_mode((WIDTH, HEIGHT)) | |
# Creates the surface with the given width and height | |
pygame.display.set_caption("Tic-Tac-Toe") # Sets the caption for the game | |
self.board = self.initialiseBoard() # Initalises the board | |
self.game = Game() | |
self.play_game() | |
def initialiseBoard(self): | |
"""Job: Initialise the board and return the board as a variable.""" | |
# Setup background surface | |
background = pygame.Surface(self.ttt.get_size()) | |
# Creates a surface object with the window size | |
background = background.convert() # Converts toe background to pixel format | |
background.fill(WHITE) # Converts the backgorund to a WHITE color (I think) | |
# Draw Grid Lines | |
# Vertical lines | |
pygame.draw.line(background, (0, 0, 0), (100, 0), (100, 300), 2) | |
pygame.draw.line(background, (0, 0, 0), (200, 0), (200, 300), 2) | |
# horizontal lines... | |
pygame.draw.line(background, (0, 0, 0), (0, 100), (300, 100), 2) | |
pygame.draw.line(background, (0, 0, 0), (0, 200), (300, 200), 2) | |
# Note about lines: Draws the lines with the Surface, Color, Start pos, End pos, and width | |
return background # returns the fully realised background | |
def play_game(self): | |
""" The main game loop.""" | |
running = True # On/Off switch | |
while running: # Main event loop | |
for event in pygame.event.get(): | |
# Finds the events that are currently queued up | |
if event.type == pygame.QUIT: # If the window close button is pressed | |
running = False # The game stops running | |
pygame.quit() | |
sys.exit() | |
elif event.type == pygame.MOUSEBUTTONDOWN: | |
# If the mouse button is pressed | |
# Find out where the user clicked and if the space isn't occupied, have a symbol drawn there | |
self.clickBoard() | |
self.display_winning() # Check to see if the game finished | |
self.showBoard() # Update the display | |
self.drawStatus() | |
def drawStatus(self): | |
"""Job: Shows status at the bottom of the board (i.e player turn, num of moves, etc) | |
In this case, board is the initialised game board where the status will be drawn onto.""" | |
message = self.game.game_message() | |
# Setup (render) the message | |
font = pygame.font.Font(None, 24) | |
# Creates a default pygame font with the size of the font in pixels | |
text = font.render(message, True, (BLACK)) | |
# Creates a new surface with the specified message on it. | |
# It doesn't allow you to add to an existing surface, so we'll have to blit this surface onto our one. | |
# After that, we set antialiasing to true. It then sets the color of the text to BLACK. | |
# Send the finished status onto the board | |
self.board.fill(WHITE, (0, 300, 300, 25)) | |
self.board.blit(text, (10, 300)) | |
# Pastes the status onto the board at the coordinates 10, 300 | |
def showBoard(self): | |
"""Job: Redraw the board onto the display (basically updates the screen) | |
In this case, ttt is the initialized pyGame display and board is the game board surface.""" | |
# Draw/redraws the status info at the bottom of the board | |
self.ttt.blit(self.board, (0, 0)) | |
# Places the board onto the display at the very base coordinates (top left corner) | |
pygame.display.flip() # Updates the entire display | |
def drawMove(self, boardRow, boardCol): | |
"""Job: Draw either an X or an O (Symbol) on the board in a specific boardRow and boardCol | |
Board is the game board surface we are drawing on. boardRow and boardCol are the row and column in which | |
we will draw the symbol in. Symbol is either an X or an O, based on who's turn it is. | |
Note: Lists are 0 based in terms of index. This means that if you call for the 0th item (i.e list[0]), you will | |
get the first, as the index "starts" from 0.""" | |
Symbol = self.game.PlayerTurn | |
# Finds the center of the box we will draw in | |
centerX = ((boardCol) * CELL_DIMENSIONS) + (CELL_DIMENSIONS // 2) | |
centerY = ((boardRow) * CELL_DIMENSIONS) + (CELL_DIMENSIONS // 2) | |
# Draws the appropriate symbol | |
if Symbol == "O": # If the Symbol is O, draw a circle in the specified square | |
pygame.draw.circle(self.board, (0, 0, 0), (centerX, centerY), 33, 2) | |
elif Symbol == "X": | |
# If the Symbol is X, draw two lines (i.e an X) in the specified square | |
pygame.draw.line( | |
self.board, | |
(0, 0, 0), | |
(centerX - 22, centerY - 22), | |
(centerX + 22, centerY + 22), | |
2, | |
) | |
pygame.draw.line( | |
self.board, | |
(0, 0, 0), | |
(centerX + 22, centerY - 22), | |
(centerX - 22, centerY + 22), | |
2, | |
) | |
# Mark the square as occupied | |
self.game.grid[boardRow][boardCol] = Symbol | |
@staticmethod | |
def boardPosition(mouseX, mouseY): | |
"""Job: Find out which board space (i.e row, column) the user clicked in based on their mouse coordinates | |
Here, mouseX is the X coordinate the user clicked and mouseY is the Y coordinate the user clicked | |
Note: Lists are 0 based in terms of index. This means that if you call for the 0th item (i.e list[0]), you will | |
get the first, as the index "starts" from 0.""" | |
point = (position // CELL_DIMENSIONS for position in (mouseY, mouseX)) | |
return point | |
# Returns the tuple containing the row and column that the user clicked in | |
def clickBoard(self): | |
"""Job: Find out where the user clicked and if the space isn't occupied, have a symbol drawn there | |
board is the game board surface.""" | |
mouseX, mouseY = pygame.mouse.get_pos() | |
# Makes mouseX and mouseY equal to the coordinates of the mouse | |
row, col = self.boardPosition(mouseX, mouseY) | |
# As a note, boardPosition, returns the row and column that the user clicked in | |
# Check to see if the space is empty | |
if self.game.is_populated(row, col): | |
# If an X or an O is present in the box that was clicked | |
return | |
# Draw an X or an O, based on whos turn it is | |
self.drawMove(row, col) | |
# Toggle PlayerTurn to make it the other persons turn | |
self.game.toggle_turn() | |
def display_winning(self): | |
win_state, value = self.game.gameFinished() | |
if win_state == "row": | |
row = value | |
pygame.draw.line( | |
self.board, | |
(RED), | |
(0, (row + 1) * 100 - 50), | |
(300, (row + 1) * 100 - 50), | |
LINETHICKNESS, | |
) | |
elif win_state == "column": | |
col = value | |
pygame.draw.line( | |
self.board, | |
(RED), | |
((col + 1) * 100 - 50, 0), | |
((col + 1) * 100 - 50, 300), | |
LINETHICKNESS, | |
) | |
elif win_state == "diag1": | |
pygame.draw.line(self.board, (RED), (50, 50), (250, 250), LINETHICKNESS) | |
elif win_state == "diag2": | |
pygame.draw.line( | |
self.board, (250, 0, 0), (250, 50), (50, 250), LINETHICKNESS | |
) | |
if __name__ == "__main__": | |
App() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment