Skip to content

Instantly share code, notes, and snippets.

@timbledum
Created April 17, 2019 09:04
Show Gist options
  • Save timbledum/fd4763dcd703b0c0c51319e6dbe7ebeb to your computer and use it in GitHub Desktop.
Save timbledum/fd4763dcd703b0c0c51319e6dbe7ebeb to your computer and use it in GitHub Desktop.
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