Object Oriented Tic Tac Toe Python












0















I've been learning to code in my spare time for a couple of years. I have covered object oriented stuff before and can usually understand other people's object oriented code. This is the first time I've written an object oriented program from scratch.



It has all the functionality that I want and (hopefully) no mistakes - I have stress tested it.



However, I still feel the code is a bit dirty so I'd like some suggested improvements. Some areas that I feel I've gone wrong:



1, I have put too much functionality into the board class and not enough into the player class. Is this an issue?



2, It was relatively streamlined to begin with but then I started adding stuff to "make it look cool" such as a coin toss to decide who goes first and drawing the winning line on the board/displaying a victory message. This is all great but my code seems to have gotten less clean as the game loop function is a bit messy - any recommendations for how to repackage it?



3, Any other comments? StackExchange can be a very brutal place; please be gentle in your critique......



4, Any recommendations for a next project that would be slightly harder but still manageable for someone of my level.



My code:



import random
import pygame
import itertools

# Initailize PyGame object
pygame.init()
DISPLAY_WIDTH = 600
DISPLAY_HEIGHT = 600
BLOCK_SIZE = 200

# Defining colors (rgb values)
BACKGROUND_COLOR = (0, 0, 0)
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
pink = (255, 20, 147)
cyan = (0, 255, 255)
gray = (128, 128, 128)

# Draw black rectangle to cover square numbers once played
def draw_rect(x, y, color = black, size=30, width = 0):
pygame.draw.polygon(gameDisplay, color, [(x-size, y-size), (x-size, y+size), (x+size, y+size), (x+size, y-size)], width)
def draw_wide_rect(x, y, color = black, size=30, width = 0):
pygame.draw.polygon(gameDisplay, color, [(x-3*size, y-2*size), (x-3*size, y+2*size), (x+3*size, y+2*size), (x+3*size, y-2*size)], width)
# How to draw naughts and crosses
def draw_naught(x, y, color, radius = 40, thickness = 20):
pygame.draw.circle(gameDisplay, color, (x,y), radius, thickness)

def draw_cross(x, y, color, closed = False, thickness = 20):
pygame.draw.lines(gameDisplay, color, closed, [(x-30, y-30), (x+30, y+30)], thickness)
pygame.draw.lines(gameDisplay, color, closed , [(x+30, y-30), (x-30, y+30)], thickness)

largefont = pygame.font.SysFont(None, 80)
player_font = pygame.font.SysFont('Comic Sans MS', 40)
def display_text(text, x, y, color):
text = largefont.render("{}".format(str(text)), True, color)
gameDisplay.blit(text, [x, y])

# Set up the display
gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT + 100))
pygame.display.set_caption("Naughts and Crosses")
clock = pygame.time.Clock()

# Draw the background grid
def drawGrid(w, rows, surface):
sizeBtwn = w // rows

x = 0
y = 0
pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)
for l in range(rows):
x = x + sizeBtwn
y = y + sizeBtwn

pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)




class Board:
def __init__(self, current_player, next_player, naught_list = , cross_list = ):
self.naught_list = naught_list
self.cross_list = cross_list
self.empty_list = [1,2,3,4,5,6,7,8,9]
self.current_player = current_player
self.next_player = next_player
self.naught_trebles =
self.naught_treble_sums =
self.cross_trebles =
self.cross_treble_sums =
self.magic_dic = {1: 6,
2: 1,
3: 8,
4: 7,
5: 5,
6: 3,
7: 2,
8: 9,
9: 4}


def create_board(self):
gameDisplay.fill(BACKGROUND_COLOR)
drawGrid(DISPLAY_WIDTH, int(DISPLAY_WIDTH / BLOCK_SIZE), gameDisplay)

for i in range(9):
display_text(str(i+1), ((i)%3)*BLOCK_SIZE + BLOCK_SIZE/2.5, (i//3)*BLOCK_SIZE + BLOCK_SIZE/2.5, white)


def cover_text(self, square_num):
x = int((square_num%3)*BLOCK_SIZE + BLOCK_SIZE/2)
y = int((square_num//3)*BLOCK_SIZE + BLOCK_SIZE/2)


def play_move(self, marker, square_num, player):
if player.marker == 'naughts':
if int(square_num) in self.empty_list:
idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
draw_rect(x, y)
draw_naught(x, y, player.color)

self.empty_list.remove(square_num)
self.naught_list.append(square_num)
tmp = player
self.current_player = self.next_player
self.next_player = tmp
else:
print("Invalid move - square already taken. Pick again please:")
return 0
elif player.marker == 'crosses':
if int(square_num) in self.empty_list:
idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
draw_rect(x, y)
draw_cross(x, y, player.color)

self.empty_list.remove(square_num)
self.cross_list.append(square_num)
tmp = player
self.current_player = self.next_player
self.next_player = tmp
else:
print("Invalid move - square already taken. Pick again please:")
return 0
else:
print("Invalid marker for this player.")



def victory_line(self, winning_list, color, closed = False, thickness = 20):
# get the square numbers of the winning line. These will be in terms of magic square from magic_dic
magic_list_0 = winning_list[0]
magic_list_1 = winning_list[1]
magic_list_2 = winning_list[2]

# convert them back to the square numbers we see on the screen
for key in self.magic_dic:
if magic_list_0 == self.magic_dic[key]:
list_0 = key

for key in self.magic_dic:
if magic_list_1 == self.magic_dic[key]:
list_1 = key

for key in self.magic_dic:
if magic_list_2 == self.magic_dic[key]:
list_2 = key

non_magic_list = [list_0, list_1, list_2]

# subtract 1 as pygame numbers squares from 0 and we number from 1
left_idx = min(non_magic_list) - 1
right_idx = max(non_magic_list) -1

# get coords
left_x = int((left_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
left_y = int((left_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
right_x = int((right_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
right_y = int((right_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)

# draw a line to show winning combination
pygame.draw.lines(gameDisplay, color, closed , [(left_x, left_y), (right_x, right_y)], thickness)

def magic_square_winner_check(self, player):
# magic square has every row, column and diagonal sum to 15 meaning we can just check if any triple of used squares sums to 15
# this won't be computationally challenging due to small board size. at most 5C3 = 10 combinations (no player can occupy more than 5 squares)
# current numbering is not magic so we must first use a dictionary to magic-ify the board


if player.marker == 'naughts':
updated_naught_list = [self.magic_dic[x] for x in self.naught_list]
self.naught_trebles = list(itertools.combinations(updated_naught_list, 3))
self.naught_treble_sums = [sum(x) for x in self.naught_trebles]
if 15 in self.naught_treble_sums:
print("Victory")
return 1

elif player.marker == 'crosses':
updated_cross_list = [self.magic_dic[x] for x in self.cross_list]
self.cross_trebles = list(itertools.combinations(updated_cross_list, 3))
self.cross_treble_sums = [sum(x) for x in self.cross_trebles]
if 15 in self.cross_treble_sums:
print("Victory")
return 1
else:
return 0

def victory_message(self, msg, x = DISPLAY_WIDTH//5, y=DISPLAY_HEIGHT+20, color = cyan):
text = largefont.render("{} wins!".format(str(msg)), True, color)
gameDisplay.blit(text, [x, y])

def draw_message(self, x = DISPLAY_WIDTH//6, y=DISPLAY_HEIGHT+20, color = cyan):
text = largefont.render("A boring draw...", True, color)
gameDisplay.blit(text, [x, y])

def player1_text(self, color, x = 10, y=DISPLAY_HEIGHT+30):
text = player_font.render("Player 1", True, color)
gameDisplay.blit(text, [x, y])

def player2_text(self, color, x = DISPLAY_WIDTH - 160, y= DISPLAY_HEIGHT+30):
text = player_font.render("Player 2", True, color)
gameDisplay.blit(text, [x, y])

class Player:
def __init__(self, marker, coin_toss_victor, color, name):
self.marker = marker # naughts or crosses
self.coin_toss_victor = coin_toss_victor #boolean indication of who starts
self.color = color
self.name = name
pass

def request_move(self, board):
move = input("Choose a square: ")
tmp = move
while move.isnumeric() == False:
move = input("{} is not an int! Choose again: ".format(tmp))
move = int(move)
# check valid
while move not in range(1,10):
tmp = move
move = int(input("{} is not a valid square. Please choose again: ".format(tmp)))
while move not in board.empty_list:
#board.square_taken_message()
tmp = move
move = int(input("Square {} is already taken. Please choose again: ".format(tmp)))

return move


"""<<<GAME LOOP>>>"""

def game_loop():
# display screen asking for coin fkip guess
gameDisplay.fill(BACKGROUND_COLOR)
myfont = pygame.font.SysFont('Comic Sans MS', 50)
textsurface = myfont.render("Heads or Tails?", True, white)
gameDisplay.blit(textsurface, [60, 10])
pygame.display.update()

# receive coin guess
coin = ["H", "T"]
coin_toss = input("We will decide who goes first with a coin flip. Player 1 pease enter H or T: ")
if coin_toss not in coin:
coin_toss = input("Not a valid input. Please enter H or T: ")

# dictionary for translating input to display words
coin_dic = {"H" : "Heads", "T": "Tails"}

# random toss. display the guess now. display the outcome after images
random_toss = random.choice(coin)
myfont = pygame.font.SysFont('Comic Sans MS', 50)
textsurface2 = myfont.render("Your choice: {}".format(coin_dic[coin_toss]), True, white)
gameDisplay.blit(textsurface2, [60, DISPLAY_HEIGHT - 140])


# load two images of coin
coin_a = pygame.image.load('coin_a.png')
coin_b = pygame.image.load('coin_b.png')
tosser = 0

textsurface3 = myfont.render("Result: {}".format(coin_dic[random_toss]), True, white)
if coin_toss == random_toss:
textsurface4 = myfont.render("Win! You go first!", True, white)
else:
textsurface4 = myfont.render("Lose! You go second!", True, white)
# change image 20 times for nice effect. display outcome of coin flip after 10 times.
while tosser < 21:
print(20 - tosser)
gameDisplay.blit(coin_a, (60,DISPLAY_HEIGHT//3))
pygame.display.update()
pygame.time.wait(100)
gameDisplay.blit(coin_b, (60, DISPLAY_HEIGHT//3))
pygame.display.update()
pygame.time.wait(100)
tosser += 1

if tosser > 10:
gameDisplay.blit(textsurface3, [60, DISPLAY_HEIGHT - 70])
gameDisplay.blit(textsurface4, [60, DISPLAY_HEIGHT])
pygame.display.update()

# short pause for user to read result before switching screen to tic-tac-toe
pygame.time.wait(500)

# instantiate the player objects according to the result of the coin toss
while coin_toss not in coin:
coin_toss = input("You didn't enter a valid input! Try again please: ")
if coin_toss == random_toss:
print("Coin toss result was {}".format(random_toss))
print("Player 1 to go first!")
p1 = Player('naughts', True, pink, 'Player 1')
p2 = Player('crosses', False, green, 'Player 2')
current_player = p1
next_player = p2
else:
print("Coin toss result was {}".format(random_toss))
print("Player 2 to go first!")
p1 = Player('naughts', False, pink, 'Player 1')
p2 = Player('crosses', True, green, 'Player 2')
current_player = p2
next_player = p1

# instantiate the board and display it
b = Board(current_player, next_player)
b.create_board()

# display current player
if current_player == p1:
b.player1_text(white)
b.player2_text(gray)
elif current_player == p2:
b.player1_text(gray)
b.player2_text(white)
pygame.display.update()

# begin while loop - initialise all variables here so they're reset if we restart the game
play_again = True
end = False
b.empty_list = [1,2,3,4,5,6,7,8,9]
b.naught_list =
b.naught_trebles =
b.naught_treble_sums =
b.cross_list =
b.cross_trebles =
b.cross_treble_sums =
while play_again:

# display current player
if current_player == p1:
b.player1_text(white)
b.player2_text(gray)
pygame.display.update()
elif current_player == p2:
b.player1_text(gray)
b.player2_text(white)
pygame.display.update()

# allow window to be quit
for event in pygame.event.get():
if event.type == pygame.QUIT:
play_again = False

# print useful stuff to terminal
print("-----Empty Squares-----")
print(b.empty_list)
print("-----Naughts-----")
print(b.naught_list)
print(b.naught_trebles)
print(b.naught_treble_sums)
print("-----Crosses-----")
print(b.cross_list)
print(b.cross_trebles)
print(b.cross_treble_sums)

# request player move and display it
move = current_player.request_move(b)
b.play_move(current_player.marker, move, current_player)

# display current player
if current_player == p1:
b.player1_text(white)
b.player2_text(gray)
pygame.display.update()
elif current_player == p2:
b.player1_text(gray)
b.player2_text(white)
pygame.display.update()

# check if current player has won and then switch player
if current_player == p1 and end == False:
if b.magic_square_winner_check(p1) == 1:
draw_wide_rect(70, DISPLAY_HEIGHT+70)
draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
b.victory_message(current_player.name)
winning_idx = b.naught_treble_sums.index(15)
winning_tuple = b.naught_trebles[winning_idx]
winning_list = [x for x in winning_tuple]
print(winning_list)
b.victory_line(winning_list, p1.color)
pygame.display.update()
end = True
else:
current_player = p2

elif current_player == p2 and end == False:
if b.magic_square_winner_check(p2) == 1:
draw_wide_rect(70, DISPLAY_HEIGHT+70)
draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
b.victory_message(current_player.name)
winning_idx = b.cross_treble_sums.index(15)
winning_tuple = b.cross_trebles[winning_idx]
winning_list = [x for x in winning_tuple]
b.victory_line(winning_list, p2.color)
pygame.display.update()
end = True
else:
current_player = p1

# check if game is a draw
if len(b.empty_list) == 0 and end == False:
b.draw_message()
pygame.display.update()
end = True

# if game reaches a terminal state, return 1 (used as code in main loop to ask about restart)
if end:
return 1
pygame.display.update()


def main():
end_msg = game_loop()
while end_msg == True:
play_again = int(input("Play again? 1 = Yes, 0 = No : "))
if play_again == 1:
game_loop()
if play_again == 0:
break

if __name__ == '__main__':
main()








share







New contributor




user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    0















    I've been learning to code in my spare time for a couple of years. I have covered object oriented stuff before and can usually understand other people's object oriented code. This is the first time I've written an object oriented program from scratch.



    It has all the functionality that I want and (hopefully) no mistakes - I have stress tested it.



    However, I still feel the code is a bit dirty so I'd like some suggested improvements. Some areas that I feel I've gone wrong:



    1, I have put too much functionality into the board class and not enough into the player class. Is this an issue?



    2, It was relatively streamlined to begin with but then I started adding stuff to "make it look cool" such as a coin toss to decide who goes first and drawing the winning line on the board/displaying a victory message. This is all great but my code seems to have gotten less clean as the game loop function is a bit messy - any recommendations for how to repackage it?



    3, Any other comments? StackExchange can be a very brutal place; please be gentle in your critique......



    4, Any recommendations for a next project that would be slightly harder but still manageable for someone of my level.



    My code:



    import random
    import pygame
    import itertools

    # Initailize PyGame object
    pygame.init()
    DISPLAY_WIDTH = 600
    DISPLAY_HEIGHT = 600
    BLOCK_SIZE = 200

    # Defining colors (rgb values)
    BACKGROUND_COLOR = (0, 0, 0)
    white = (255, 255, 255)
    black = (0, 0, 0)
    red = (255, 0, 0)
    blue = (0, 0, 255)
    green = (0, 255, 0)
    pink = (255, 20, 147)
    cyan = (0, 255, 255)
    gray = (128, 128, 128)

    # Draw black rectangle to cover square numbers once played
    def draw_rect(x, y, color = black, size=30, width = 0):
    pygame.draw.polygon(gameDisplay, color, [(x-size, y-size), (x-size, y+size), (x+size, y+size), (x+size, y-size)], width)
    def draw_wide_rect(x, y, color = black, size=30, width = 0):
    pygame.draw.polygon(gameDisplay, color, [(x-3*size, y-2*size), (x-3*size, y+2*size), (x+3*size, y+2*size), (x+3*size, y-2*size)], width)
    # How to draw naughts and crosses
    def draw_naught(x, y, color, radius = 40, thickness = 20):
    pygame.draw.circle(gameDisplay, color, (x,y), radius, thickness)

    def draw_cross(x, y, color, closed = False, thickness = 20):
    pygame.draw.lines(gameDisplay, color, closed, [(x-30, y-30), (x+30, y+30)], thickness)
    pygame.draw.lines(gameDisplay, color, closed , [(x+30, y-30), (x-30, y+30)], thickness)

    largefont = pygame.font.SysFont(None, 80)
    player_font = pygame.font.SysFont('Comic Sans MS', 40)
    def display_text(text, x, y, color):
    text = largefont.render("{}".format(str(text)), True, color)
    gameDisplay.blit(text, [x, y])

    # Set up the display
    gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT + 100))
    pygame.display.set_caption("Naughts and Crosses")
    clock = pygame.time.Clock()

    # Draw the background grid
    def drawGrid(w, rows, surface):
    sizeBtwn = w // rows

    x = 0
    y = 0
    pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
    pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)
    for l in range(rows):
    x = x + sizeBtwn
    y = y + sizeBtwn

    pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
    pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)




    class Board:
    def __init__(self, current_player, next_player, naught_list = , cross_list = ):
    self.naught_list = naught_list
    self.cross_list = cross_list
    self.empty_list = [1,2,3,4,5,6,7,8,9]
    self.current_player = current_player
    self.next_player = next_player
    self.naught_trebles =
    self.naught_treble_sums =
    self.cross_trebles =
    self.cross_treble_sums =
    self.magic_dic = {1: 6,
    2: 1,
    3: 8,
    4: 7,
    5: 5,
    6: 3,
    7: 2,
    8: 9,
    9: 4}


    def create_board(self):
    gameDisplay.fill(BACKGROUND_COLOR)
    drawGrid(DISPLAY_WIDTH, int(DISPLAY_WIDTH / BLOCK_SIZE), gameDisplay)

    for i in range(9):
    display_text(str(i+1), ((i)%3)*BLOCK_SIZE + BLOCK_SIZE/2.5, (i//3)*BLOCK_SIZE + BLOCK_SIZE/2.5, white)


    def cover_text(self, square_num):
    x = int((square_num%3)*BLOCK_SIZE + BLOCK_SIZE/2)
    y = int((square_num//3)*BLOCK_SIZE + BLOCK_SIZE/2)


    def play_move(self, marker, square_num, player):
    if player.marker == 'naughts':
    if int(square_num) in self.empty_list:
    idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
    x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
    y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
    draw_rect(x, y)
    draw_naught(x, y, player.color)

    self.empty_list.remove(square_num)
    self.naught_list.append(square_num)
    tmp = player
    self.current_player = self.next_player
    self.next_player = tmp
    else:
    print("Invalid move - square already taken. Pick again please:")
    return 0
    elif player.marker == 'crosses':
    if int(square_num) in self.empty_list:
    idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
    x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
    y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
    draw_rect(x, y)
    draw_cross(x, y, player.color)

    self.empty_list.remove(square_num)
    self.cross_list.append(square_num)
    tmp = player
    self.current_player = self.next_player
    self.next_player = tmp
    else:
    print("Invalid move - square already taken. Pick again please:")
    return 0
    else:
    print("Invalid marker for this player.")



    def victory_line(self, winning_list, color, closed = False, thickness = 20):
    # get the square numbers of the winning line. These will be in terms of magic square from magic_dic
    magic_list_0 = winning_list[0]
    magic_list_1 = winning_list[1]
    magic_list_2 = winning_list[2]

    # convert them back to the square numbers we see on the screen
    for key in self.magic_dic:
    if magic_list_0 == self.magic_dic[key]:
    list_0 = key

    for key in self.magic_dic:
    if magic_list_1 == self.magic_dic[key]:
    list_1 = key

    for key in self.magic_dic:
    if magic_list_2 == self.magic_dic[key]:
    list_2 = key

    non_magic_list = [list_0, list_1, list_2]

    # subtract 1 as pygame numbers squares from 0 and we number from 1
    left_idx = min(non_magic_list) - 1
    right_idx = max(non_magic_list) -1

    # get coords
    left_x = int((left_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
    left_y = int((left_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
    right_x = int((right_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
    right_y = int((right_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)

    # draw a line to show winning combination
    pygame.draw.lines(gameDisplay, color, closed , [(left_x, left_y), (right_x, right_y)], thickness)

    def magic_square_winner_check(self, player):
    # magic square has every row, column and diagonal sum to 15 meaning we can just check if any triple of used squares sums to 15
    # this won't be computationally challenging due to small board size. at most 5C3 = 10 combinations (no player can occupy more than 5 squares)
    # current numbering is not magic so we must first use a dictionary to magic-ify the board


    if player.marker == 'naughts':
    updated_naught_list = [self.magic_dic[x] for x in self.naught_list]
    self.naught_trebles = list(itertools.combinations(updated_naught_list, 3))
    self.naught_treble_sums = [sum(x) for x in self.naught_trebles]
    if 15 in self.naught_treble_sums:
    print("Victory")
    return 1

    elif player.marker == 'crosses':
    updated_cross_list = [self.magic_dic[x] for x in self.cross_list]
    self.cross_trebles = list(itertools.combinations(updated_cross_list, 3))
    self.cross_treble_sums = [sum(x) for x in self.cross_trebles]
    if 15 in self.cross_treble_sums:
    print("Victory")
    return 1
    else:
    return 0

    def victory_message(self, msg, x = DISPLAY_WIDTH//5, y=DISPLAY_HEIGHT+20, color = cyan):
    text = largefont.render("{} wins!".format(str(msg)), True, color)
    gameDisplay.blit(text, [x, y])

    def draw_message(self, x = DISPLAY_WIDTH//6, y=DISPLAY_HEIGHT+20, color = cyan):
    text = largefont.render("A boring draw...", True, color)
    gameDisplay.blit(text, [x, y])

    def player1_text(self, color, x = 10, y=DISPLAY_HEIGHT+30):
    text = player_font.render("Player 1", True, color)
    gameDisplay.blit(text, [x, y])

    def player2_text(self, color, x = DISPLAY_WIDTH - 160, y= DISPLAY_HEIGHT+30):
    text = player_font.render("Player 2", True, color)
    gameDisplay.blit(text, [x, y])

    class Player:
    def __init__(self, marker, coin_toss_victor, color, name):
    self.marker = marker # naughts or crosses
    self.coin_toss_victor = coin_toss_victor #boolean indication of who starts
    self.color = color
    self.name = name
    pass

    def request_move(self, board):
    move = input("Choose a square: ")
    tmp = move
    while move.isnumeric() == False:
    move = input("{} is not an int! Choose again: ".format(tmp))
    move = int(move)
    # check valid
    while move not in range(1,10):
    tmp = move
    move = int(input("{} is not a valid square. Please choose again: ".format(tmp)))
    while move not in board.empty_list:
    #board.square_taken_message()
    tmp = move
    move = int(input("Square {} is already taken. Please choose again: ".format(tmp)))

    return move


    """<<<GAME LOOP>>>"""

    def game_loop():
    # display screen asking for coin fkip guess
    gameDisplay.fill(BACKGROUND_COLOR)
    myfont = pygame.font.SysFont('Comic Sans MS', 50)
    textsurface = myfont.render("Heads or Tails?", True, white)
    gameDisplay.blit(textsurface, [60, 10])
    pygame.display.update()

    # receive coin guess
    coin = ["H", "T"]
    coin_toss = input("We will decide who goes first with a coin flip. Player 1 pease enter H or T: ")
    if coin_toss not in coin:
    coin_toss = input("Not a valid input. Please enter H or T: ")

    # dictionary for translating input to display words
    coin_dic = {"H" : "Heads", "T": "Tails"}

    # random toss. display the guess now. display the outcome after images
    random_toss = random.choice(coin)
    myfont = pygame.font.SysFont('Comic Sans MS', 50)
    textsurface2 = myfont.render("Your choice: {}".format(coin_dic[coin_toss]), True, white)
    gameDisplay.blit(textsurface2, [60, DISPLAY_HEIGHT - 140])


    # load two images of coin
    coin_a = pygame.image.load('coin_a.png')
    coin_b = pygame.image.load('coin_b.png')
    tosser = 0

    textsurface3 = myfont.render("Result: {}".format(coin_dic[random_toss]), True, white)
    if coin_toss == random_toss:
    textsurface4 = myfont.render("Win! You go first!", True, white)
    else:
    textsurface4 = myfont.render("Lose! You go second!", True, white)
    # change image 20 times for nice effect. display outcome of coin flip after 10 times.
    while tosser < 21:
    print(20 - tosser)
    gameDisplay.blit(coin_a, (60,DISPLAY_HEIGHT//3))
    pygame.display.update()
    pygame.time.wait(100)
    gameDisplay.blit(coin_b, (60, DISPLAY_HEIGHT//3))
    pygame.display.update()
    pygame.time.wait(100)
    tosser += 1

    if tosser > 10:
    gameDisplay.blit(textsurface3, [60, DISPLAY_HEIGHT - 70])
    gameDisplay.blit(textsurface4, [60, DISPLAY_HEIGHT])
    pygame.display.update()

    # short pause for user to read result before switching screen to tic-tac-toe
    pygame.time.wait(500)

    # instantiate the player objects according to the result of the coin toss
    while coin_toss not in coin:
    coin_toss = input("You didn't enter a valid input! Try again please: ")
    if coin_toss == random_toss:
    print("Coin toss result was {}".format(random_toss))
    print("Player 1 to go first!")
    p1 = Player('naughts', True, pink, 'Player 1')
    p2 = Player('crosses', False, green, 'Player 2')
    current_player = p1
    next_player = p2
    else:
    print("Coin toss result was {}".format(random_toss))
    print("Player 2 to go first!")
    p1 = Player('naughts', False, pink, 'Player 1')
    p2 = Player('crosses', True, green, 'Player 2')
    current_player = p2
    next_player = p1

    # instantiate the board and display it
    b = Board(current_player, next_player)
    b.create_board()

    # display current player
    if current_player == p1:
    b.player1_text(white)
    b.player2_text(gray)
    elif current_player == p2:
    b.player1_text(gray)
    b.player2_text(white)
    pygame.display.update()

    # begin while loop - initialise all variables here so they're reset if we restart the game
    play_again = True
    end = False
    b.empty_list = [1,2,3,4,5,6,7,8,9]
    b.naught_list =
    b.naught_trebles =
    b.naught_treble_sums =
    b.cross_list =
    b.cross_trebles =
    b.cross_treble_sums =
    while play_again:

    # display current player
    if current_player == p1:
    b.player1_text(white)
    b.player2_text(gray)
    pygame.display.update()
    elif current_player == p2:
    b.player1_text(gray)
    b.player2_text(white)
    pygame.display.update()

    # allow window to be quit
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    play_again = False

    # print useful stuff to terminal
    print("-----Empty Squares-----")
    print(b.empty_list)
    print("-----Naughts-----")
    print(b.naught_list)
    print(b.naught_trebles)
    print(b.naught_treble_sums)
    print("-----Crosses-----")
    print(b.cross_list)
    print(b.cross_trebles)
    print(b.cross_treble_sums)

    # request player move and display it
    move = current_player.request_move(b)
    b.play_move(current_player.marker, move, current_player)

    # display current player
    if current_player == p1:
    b.player1_text(white)
    b.player2_text(gray)
    pygame.display.update()
    elif current_player == p2:
    b.player1_text(gray)
    b.player2_text(white)
    pygame.display.update()

    # check if current player has won and then switch player
    if current_player == p1 and end == False:
    if b.magic_square_winner_check(p1) == 1:
    draw_wide_rect(70, DISPLAY_HEIGHT+70)
    draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
    b.victory_message(current_player.name)
    winning_idx = b.naught_treble_sums.index(15)
    winning_tuple = b.naught_trebles[winning_idx]
    winning_list = [x for x in winning_tuple]
    print(winning_list)
    b.victory_line(winning_list, p1.color)
    pygame.display.update()
    end = True
    else:
    current_player = p2

    elif current_player == p2 and end == False:
    if b.magic_square_winner_check(p2) == 1:
    draw_wide_rect(70, DISPLAY_HEIGHT+70)
    draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
    b.victory_message(current_player.name)
    winning_idx = b.cross_treble_sums.index(15)
    winning_tuple = b.cross_trebles[winning_idx]
    winning_list = [x for x in winning_tuple]
    b.victory_line(winning_list, p2.color)
    pygame.display.update()
    end = True
    else:
    current_player = p1

    # check if game is a draw
    if len(b.empty_list) == 0 and end == False:
    b.draw_message()
    pygame.display.update()
    end = True

    # if game reaches a terminal state, return 1 (used as code in main loop to ask about restart)
    if end:
    return 1
    pygame.display.update()


    def main():
    end_msg = game_loop()
    while end_msg == True:
    play_again = int(input("Play again? 1 = Yes, 0 = No : "))
    if play_again == 1:
    game_loop()
    if play_again == 0:
    break

    if __name__ == '__main__':
    main()








    share







    New contributor




    user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      0












      0








      0








      I've been learning to code in my spare time for a couple of years. I have covered object oriented stuff before and can usually understand other people's object oriented code. This is the first time I've written an object oriented program from scratch.



      It has all the functionality that I want and (hopefully) no mistakes - I have stress tested it.



      However, I still feel the code is a bit dirty so I'd like some suggested improvements. Some areas that I feel I've gone wrong:



      1, I have put too much functionality into the board class and not enough into the player class. Is this an issue?



      2, It was relatively streamlined to begin with but then I started adding stuff to "make it look cool" such as a coin toss to decide who goes first and drawing the winning line on the board/displaying a victory message. This is all great but my code seems to have gotten less clean as the game loop function is a bit messy - any recommendations for how to repackage it?



      3, Any other comments? StackExchange can be a very brutal place; please be gentle in your critique......



      4, Any recommendations for a next project that would be slightly harder but still manageable for someone of my level.



      My code:



      import random
      import pygame
      import itertools

      # Initailize PyGame object
      pygame.init()
      DISPLAY_WIDTH = 600
      DISPLAY_HEIGHT = 600
      BLOCK_SIZE = 200

      # Defining colors (rgb values)
      BACKGROUND_COLOR = (0, 0, 0)
      white = (255, 255, 255)
      black = (0, 0, 0)
      red = (255, 0, 0)
      blue = (0, 0, 255)
      green = (0, 255, 0)
      pink = (255, 20, 147)
      cyan = (0, 255, 255)
      gray = (128, 128, 128)

      # Draw black rectangle to cover square numbers once played
      def draw_rect(x, y, color = black, size=30, width = 0):
      pygame.draw.polygon(gameDisplay, color, [(x-size, y-size), (x-size, y+size), (x+size, y+size), (x+size, y-size)], width)
      def draw_wide_rect(x, y, color = black, size=30, width = 0):
      pygame.draw.polygon(gameDisplay, color, [(x-3*size, y-2*size), (x-3*size, y+2*size), (x+3*size, y+2*size), (x+3*size, y-2*size)], width)
      # How to draw naughts and crosses
      def draw_naught(x, y, color, radius = 40, thickness = 20):
      pygame.draw.circle(gameDisplay, color, (x,y), radius, thickness)

      def draw_cross(x, y, color, closed = False, thickness = 20):
      pygame.draw.lines(gameDisplay, color, closed, [(x-30, y-30), (x+30, y+30)], thickness)
      pygame.draw.lines(gameDisplay, color, closed , [(x+30, y-30), (x-30, y+30)], thickness)

      largefont = pygame.font.SysFont(None, 80)
      player_font = pygame.font.SysFont('Comic Sans MS', 40)
      def display_text(text, x, y, color):
      text = largefont.render("{}".format(str(text)), True, color)
      gameDisplay.blit(text, [x, y])

      # Set up the display
      gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT + 100))
      pygame.display.set_caption("Naughts and Crosses")
      clock = pygame.time.Clock()

      # Draw the background grid
      def drawGrid(w, rows, surface):
      sizeBtwn = w // rows

      x = 0
      y = 0
      pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
      pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)
      for l in range(rows):
      x = x + sizeBtwn
      y = y + sizeBtwn

      pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
      pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)




      class Board:
      def __init__(self, current_player, next_player, naught_list = , cross_list = ):
      self.naught_list = naught_list
      self.cross_list = cross_list
      self.empty_list = [1,2,3,4,5,6,7,8,9]
      self.current_player = current_player
      self.next_player = next_player
      self.naught_trebles =
      self.naught_treble_sums =
      self.cross_trebles =
      self.cross_treble_sums =
      self.magic_dic = {1: 6,
      2: 1,
      3: 8,
      4: 7,
      5: 5,
      6: 3,
      7: 2,
      8: 9,
      9: 4}


      def create_board(self):
      gameDisplay.fill(BACKGROUND_COLOR)
      drawGrid(DISPLAY_WIDTH, int(DISPLAY_WIDTH / BLOCK_SIZE), gameDisplay)

      for i in range(9):
      display_text(str(i+1), ((i)%3)*BLOCK_SIZE + BLOCK_SIZE/2.5, (i//3)*BLOCK_SIZE + BLOCK_SIZE/2.5, white)


      def cover_text(self, square_num):
      x = int((square_num%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((square_num//3)*BLOCK_SIZE + BLOCK_SIZE/2)


      def play_move(self, marker, square_num, player):
      if player.marker == 'naughts':
      if int(square_num) in self.empty_list:
      idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
      x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      draw_rect(x, y)
      draw_naught(x, y, player.color)

      self.empty_list.remove(square_num)
      self.naught_list.append(square_num)
      tmp = player
      self.current_player = self.next_player
      self.next_player = tmp
      else:
      print("Invalid move - square already taken. Pick again please:")
      return 0
      elif player.marker == 'crosses':
      if int(square_num) in self.empty_list:
      idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
      x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      draw_rect(x, y)
      draw_cross(x, y, player.color)

      self.empty_list.remove(square_num)
      self.cross_list.append(square_num)
      tmp = player
      self.current_player = self.next_player
      self.next_player = tmp
      else:
      print("Invalid move - square already taken. Pick again please:")
      return 0
      else:
      print("Invalid marker for this player.")



      def victory_line(self, winning_list, color, closed = False, thickness = 20):
      # get the square numbers of the winning line. These will be in terms of magic square from magic_dic
      magic_list_0 = winning_list[0]
      magic_list_1 = winning_list[1]
      magic_list_2 = winning_list[2]

      # convert them back to the square numbers we see on the screen
      for key in self.magic_dic:
      if magic_list_0 == self.magic_dic[key]:
      list_0 = key

      for key in self.magic_dic:
      if magic_list_1 == self.magic_dic[key]:
      list_1 = key

      for key in self.magic_dic:
      if magic_list_2 == self.magic_dic[key]:
      list_2 = key

      non_magic_list = [list_0, list_1, list_2]

      # subtract 1 as pygame numbers squares from 0 and we number from 1
      left_idx = min(non_magic_list) - 1
      right_idx = max(non_magic_list) -1

      # get coords
      left_x = int((left_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      left_y = int((left_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      right_x = int((right_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      right_y = int((right_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)

      # draw a line to show winning combination
      pygame.draw.lines(gameDisplay, color, closed , [(left_x, left_y), (right_x, right_y)], thickness)

      def magic_square_winner_check(self, player):
      # magic square has every row, column and diagonal sum to 15 meaning we can just check if any triple of used squares sums to 15
      # this won't be computationally challenging due to small board size. at most 5C3 = 10 combinations (no player can occupy more than 5 squares)
      # current numbering is not magic so we must first use a dictionary to magic-ify the board


      if player.marker == 'naughts':
      updated_naught_list = [self.magic_dic[x] for x in self.naught_list]
      self.naught_trebles = list(itertools.combinations(updated_naught_list, 3))
      self.naught_treble_sums = [sum(x) for x in self.naught_trebles]
      if 15 in self.naught_treble_sums:
      print("Victory")
      return 1

      elif player.marker == 'crosses':
      updated_cross_list = [self.magic_dic[x] for x in self.cross_list]
      self.cross_trebles = list(itertools.combinations(updated_cross_list, 3))
      self.cross_treble_sums = [sum(x) for x in self.cross_trebles]
      if 15 in self.cross_treble_sums:
      print("Victory")
      return 1
      else:
      return 0

      def victory_message(self, msg, x = DISPLAY_WIDTH//5, y=DISPLAY_HEIGHT+20, color = cyan):
      text = largefont.render("{} wins!".format(str(msg)), True, color)
      gameDisplay.blit(text, [x, y])

      def draw_message(self, x = DISPLAY_WIDTH//6, y=DISPLAY_HEIGHT+20, color = cyan):
      text = largefont.render("A boring draw...", True, color)
      gameDisplay.blit(text, [x, y])

      def player1_text(self, color, x = 10, y=DISPLAY_HEIGHT+30):
      text = player_font.render("Player 1", True, color)
      gameDisplay.blit(text, [x, y])

      def player2_text(self, color, x = DISPLAY_WIDTH - 160, y= DISPLAY_HEIGHT+30):
      text = player_font.render("Player 2", True, color)
      gameDisplay.blit(text, [x, y])

      class Player:
      def __init__(self, marker, coin_toss_victor, color, name):
      self.marker = marker # naughts or crosses
      self.coin_toss_victor = coin_toss_victor #boolean indication of who starts
      self.color = color
      self.name = name
      pass

      def request_move(self, board):
      move = input("Choose a square: ")
      tmp = move
      while move.isnumeric() == False:
      move = input("{} is not an int! Choose again: ".format(tmp))
      move = int(move)
      # check valid
      while move not in range(1,10):
      tmp = move
      move = int(input("{} is not a valid square. Please choose again: ".format(tmp)))
      while move not in board.empty_list:
      #board.square_taken_message()
      tmp = move
      move = int(input("Square {} is already taken. Please choose again: ".format(tmp)))

      return move


      """<<<GAME LOOP>>>"""

      def game_loop():
      # display screen asking for coin fkip guess
      gameDisplay.fill(BACKGROUND_COLOR)
      myfont = pygame.font.SysFont('Comic Sans MS', 50)
      textsurface = myfont.render("Heads or Tails?", True, white)
      gameDisplay.blit(textsurface, [60, 10])
      pygame.display.update()

      # receive coin guess
      coin = ["H", "T"]
      coin_toss = input("We will decide who goes first with a coin flip. Player 1 pease enter H or T: ")
      if coin_toss not in coin:
      coin_toss = input("Not a valid input. Please enter H or T: ")

      # dictionary for translating input to display words
      coin_dic = {"H" : "Heads", "T": "Tails"}

      # random toss. display the guess now. display the outcome after images
      random_toss = random.choice(coin)
      myfont = pygame.font.SysFont('Comic Sans MS', 50)
      textsurface2 = myfont.render("Your choice: {}".format(coin_dic[coin_toss]), True, white)
      gameDisplay.blit(textsurface2, [60, DISPLAY_HEIGHT - 140])


      # load two images of coin
      coin_a = pygame.image.load('coin_a.png')
      coin_b = pygame.image.load('coin_b.png')
      tosser = 0

      textsurface3 = myfont.render("Result: {}".format(coin_dic[random_toss]), True, white)
      if coin_toss == random_toss:
      textsurface4 = myfont.render("Win! You go first!", True, white)
      else:
      textsurface4 = myfont.render("Lose! You go second!", True, white)
      # change image 20 times for nice effect. display outcome of coin flip after 10 times.
      while tosser < 21:
      print(20 - tosser)
      gameDisplay.blit(coin_a, (60,DISPLAY_HEIGHT//3))
      pygame.display.update()
      pygame.time.wait(100)
      gameDisplay.blit(coin_b, (60, DISPLAY_HEIGHT//3))
      pygame.display.update()
      pygame.time.wait(100)
      tosser += 1

      if tosser > 10:
      gameDisplay.blit(textsurface3, [60, DISPLAY_HEIGHT - 70])
      gameDisplay.blit(textsurface4, [60, DISPLAY_HEIGHT])
      pygame.display.update()

      # short pause for user to read result before switching screen to tic-tac-toe
      pygame.time.wait(500)

      # instantiate the player objects according to the result of the coin toss
      while coin_toss not in coin:
      coin_toss = input("You didn't enter a valid input! Try again please: ")
      if coin_toss == random_toss:
      print("Coin toss result was {}".format(random_toss))
      print("Player 1 to go first!")
      p1 = Player('naughts', True, pink, 'Player 1')
      p2 = Player('crosses', False, green, 'Player 2')
      current_player = p1
      next_player = p2
      else:
      print("Coin toss result was {}".format(random_toss))
      print("Player 2 to go first!")
      p1 = Player('naughts', False, pink, 'Player 1')
      p2 = Player('crosses', True, green, 'Player 2')
      current_player = p2
      next_player = p1

      # instantiate the board and display it
      b = Board(current_player, next_player)
      b.create_board()

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # begin while loop - initialise all variables here so they're reset if we restart the game
      play_again = True
      end = False
      b.empty_list = [1,2,3,4,5,6,7,8,9]
      b.naught_list =
      b.naught_trebles =
      b.naught_treble_sums =
      b.cross_list =
      b.cross_trebles =
      b.cross_treble_sums =
      while play_again:

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      pygame.display.update()
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # allow window to be quit
      for event in pygame.event.get():
      if event.type == pygame.QUIT:
      play_again = False

      # print useful stuff to terminal
      print("-----Empty Squares-----")
      print(b.empty_list)
      print("-----Naughts-----")
      print(b.naught_list)
      print(b.naught_trebles)
      print(b.naught_treble_sums)
      print("-----Crosses-----")
      print(b.cross_list)
      print(b.cross_trebles)
      print(b.cross_treble_sums)

      # request player move and display it
      move = current_player.request_move(b)
      b.play_move(current_player.marker, move, current_player)

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      pygame.display.update()
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # check if current player has won and then switch player
      if current_player == p1 and end == False:
      if b.magic_square_winner_check(p1) == 1:
      draw_wide_rect(70, DISPLAY_HEIGHT+70)
      draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
      b.victory_message(current_player.name)
      winning_idx = b.naught_treble_sums.index(15)
      winning_tuple = b.naught_trebles[winning_idx]
      winning_list = [x for x in winning_tuple]
      print(winning_list)
      b.victory_line(winning_list, p1.color)
      pygame.display.update()
      end = True
      else:
      current_player = p2

      elif current_player == p2 and end == False:
      if b.magic_square_winner_check(p2) == 1:
      draw_wide_rect(70, DISPLAY_HEIGHT+70)
      draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
      b.victory_message(current_player.name)
      winning_idx = b.cross_treble_sums.index(15)
      winning_tuple = b.cross_trebles[winning_idx]
      winning_list = [x for x in winning_tuple]
      b.victory_line(winning_list, p2.color)
      pygame.display.update()
      end = True
      else:
      current_player = p1

      # check if game is a draw
      if len(b.empty_list) == 0 and end == False:
      b.draw_message()
      pygame.display.update()
      end = True

      # if game reaches a terminal state, return 1 (used as code in main loop to ask about restart)
      if end:
      return 1
      pygame.display.update()


      def main():
      end_msg = game_loop()
      while end_msg == True:
      play_again = int(input("Play again? 1 = Yes, 0 = No : "))
      if play_again == 1:
      game_loop()
      if play_again == 0:
      break

      if __name__ == '__main__':
      main()








      share







      New contributor




      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.












      I've been learning to code in my spare time for a couple of years. I have covered object oriented stuff before and can usually understand other people's object oriented code. This is the first time I've written an object oriented program from scratch.



      It has all the functionality that I want and (hopefully) no mistakes - I have stress tested it.



      However, I still feel the code is a bit dirty so I'd like some suggested improvements. Some areas that I feel I've gone wrong:



      1, I have put too much functionality into the board class and not enough into the player class. Is this an issue?



      2, It was relatively streamlined to begin with but then I started adding stuff to "make it look cool" such as a coin toss to decide who goes first and drawing the winning line on the board/displaying a victory message. This is all great but my code seems to have gotten less clean as the game loop function is a bit messy - any recommendations for how to repackage it?



      3, Any other comments? StackExchange can be a very brutal place; please be gentle in your critique......



      4, Any recommendations for a next project that would be slightly harder but still manageable for someone of my level.



      My code:



      import random
      import pygame
      import itertools

      # Initailize PyGame object
      pygame.init()
      DISPLAY_WIDTH = 600
      DISPLAY_HEIGHT = 600
      BLOCK_SIZE = 200

      # Defining colors (rgb values)
      BACKGROUND_COLOR = (0, 0, 0)
      white = (255, 255, 255)
      black = (0, 0, 0)
      red = (255, 0, 0)
      blue = (0, 0, 255)
      green = (0, 255, 0)
      pink = (255, 20, 147)
      cyan = (0, 255, 255)
      gray = (128, 128, 128)

      # Draw black rectangle to cover square numbers once played
      def draw_rect(x, y, color = black, size=30, width = 0):
      pygame.draw.polygon(gameDisplay, color, [(x-size, y-size), (x-size, y+size), (x+size, y+size), (x+size, y-size)], width)
      def draw_wide_rect(x, y, color = black, size=30, width = 0):
      pygame.draw.polygon(gameDisplay, color, [(x-3*size, y-2*size), (x-3*size, y+2*size), (x+3*size, y+2*size), (x+3*size, y-2*size)], width)
      # How to draw naughts and crosses
      def draw_naught(x, y, color, radius = 40, thickness = 20):
      pygame.draw.circle(gameDisplay, color, (x,y), radius, thickness)

      def draw_cross(x, y, color, closed = False, thickness = 20):
      pygame.draw.lines(gameDisplay, color, closed, [(x-30, y-30), (x+30, y+30)], thickness)
      pygame.draw.lines(gameDisplay, color, closed , [(x+30, y-30), (x-30, y+30)], thickness)

      largefont = pygame.font.SysFont(None, 80)
      player_font = pygame.font.SysFont('Comic Sans MS', 40)
      def display_text(text, x, y, color):
      text = largefont.render("{}".format(str(text)), True, color)
      gameDisplay.blit(text, [x, y])

      # Set up the display
      gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT + 100))
      pygame.display.set_caption("Naughts and Crosses")
      clock = pygame.time.Clock()

      # Draw the background grid
      def drawGrid(w, rows, surface):
      sizeBtwn = w // rows

      x = 0
      y = 0
      pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
      pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)
      for l in range(rows):
      x = x + sizeBtwn
      y = y + sizeBtwn

      pygame.draw.line(gameDisplay, white, (x,0),(x,w), 10)
      pygame.draw.line(gameDisplay, white, (0,y),(w,y), 10)




      class Board:
      def __init__(self, current_player, next_player, naught_list = , cross_list = ):
      self.naught_list = naught_list
      self.cross_list = cross_list
      self.empty_list = [1,2,3,4,5,6,7,8,9]
      self.current_player = current_player
      self.next_player = next_player
      self.naught_trebles =
      self.naught_treble_sums =
      self.cross_trebles =
      self.cross_treble_sums =
      self.magic_dic = {1: 6,
      2: 1,
      3: 8,
      4: 7,
      5: 5,
      6: 3,
      7: 2,
      8: 9,
      9: 4}


      def create_board(self):
      gameDisplay.fill(BACKGROUND_COLOR)
      drawGrid(DISPLAY_WIDTH, int(DISPLAY_WIDTH / BLOCK_SIZE), gameDisplay)

      for i in range(9):
      display_text(str(i+1), ((i)%3)*BLOCK_SIZE + BLOCK_SIZE/2.5, (i//3)*BLOCK_SIZE + BLOCK_SIZE/2.5, white)


      def cover_text(self, square_num):
      x = int((square_num%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((square_num//3)*BLOCK_SIZE + BLOCK_SIZE/2)


      def play_move(self, marker, square_num, player):
      if player.marker == 'naughts':
      if int(square_num) in self.empty_list:
      idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
      x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      draw_rect(x, y)
      draw_naught(x, y, player.color)

      self.empty_list.remove(square_num)
      self.naught_list.append(square_num)
      tmp = player
      self.current_player = self.next_player
      self.next_player = tmp
      else:
      print("Invalid move - square already taken. Pick again please:")
      return 0
      elif player.marker == 'crosses':
      if int(square_num) in self.empty_list:
      idx = square_num - 1 # user numbering starts at 1 but Python starts at 0
      x = int((idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      y = int((idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      draw_rect(x, y)
      draw_cross(x, y, player.color)

      self.empty_list.remove(square_num)
      self.cross_list.append(square_num)
      tmp = player
      self.current_player = self.next_player
      self.next_player = tmp
      else:
      print("Invalid move - square already taken. Pick again please:")
      return 0
      else:
      print("Invalid marker for this player.")



      def victory_line(self, winning_list, color, closed = False, thickness = 20):
      # get the square numbers of the winning line. These will be in terms of magic square from magic_dic
      magic_list_0 = winning_list[0]
      magic_list_1 = winning_list[1]
      magic_list_2 = winning_list[2]

      # convert them back to the square numbers we see on the screen
      for key in self.magic_dic:
      if magic_list_0 == self.magic_dic[key]:
      list_0 = key

      for key in self.magic_dic:
      if magic_list_1 == self.magic_dic[key]:
      list_1 = key

      for key in self.magic_dic:
      if magic_list_2 == self.magic_dic[key]:
      list_2 = key

      non_magic_list = [list_0, list_1, list_2]

      # subtract 1 as pygame numbers squares from 0 and we number from 1
      left_idx = min(non_magic_list) - 1
      right_idx = max(non_magic_list) -1

      # get coords
      left_x = int((left_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      left_y = int((left_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)
      right_x = int((right_idx%3)*BLOCK_SIZE + BLOCK_SIZE/2)
      right_y = int((right_idx//3)*BLOCK_SIZE + BLOCK_SIZE/2)

      # draw a line to show winning combination
      pygame.draw.lines(gameDisplay, color, closed , [(left_x, left_y), (right_x, right_y)], thickness)

      def magic_square_winner_check(self, player):
      # magic square has every row, column and diagonal sum to 15 meaning we can just check if any triple of used squares sums to 15
      # this won't be computationally challenging due to small board size. at most 5C3 = 10 combinations (no player can occupy more than 5 squares)
      # current numbering is not magic so we must first use a dictionary to magic-ify the board


      if player.marker == 'naughts':
      updated_naught_list = [self.magic_dic[x] for x in self.naught_list]
      self.naught_trebles = list(itertools.combinations(updated_naught_list, 3))
      self.naught_treble_sums = [sum(x) for x in self.naught_trebles]
      if 15 in self.naught_treble_sums:
      print("Victory")
      return 1

      elif player.marker == 'crosses':
      updated_cross_list = [self.magic_dic[x] for x in self.cross_list]
      self.cross_trebles = list(itertools.combinations(updated_cross_list, 3))
      self.cross_treble_sums = [sum(x) for x in self.cross_trebles]
      if 15 in self.cross_treble_sums:
      print("Victory")
      return 1
      else:
      return 0

      def victory_message(self, msg, x = DISPLAY_WIDTH//5, y=DISPLAY_HEIGHT+20, color = cyan):
      text = largefont.render("{} wins!".format(str(msg)), True, color)
      gameDisplay.blit(text, [x, y])

      def draw_message(self, x = DISPLAY_WIDTH//6, y=DISPLAY_HEIGHT+20, color = cyan):
      text = largefont.render("A boring draw...", True, color)
      gameDisplay.blit(text, [x, y])

      def player1_text(self, color, x = 10, y=DISPLAY_HEIGHT+30):
      text = player_font.render("Player 1", True, color)
      gameDisplay.blit(text, [x, y])

      def player2_text(self, color, x = DISPLAY_WIDTH - 160, y= DISPLAY_HEIGHT+30):
      text = player_font.render("Player 2", True, color)
      gameDisplay.blit(text, [x, y])

      class Player:
      def __init__(self, marker, coin_toss_victor, color, name):
      self.marker = marker # naughts or crosses
      self.coin_toss_victor = coin_toss_victor #boolean indication of who starts
      self.color = color
      self.name = name
      pass

      def request_move(self, board):
      move = input("Choose a square: ")
      tmp = move
      while move.isnumeric() == False:
      move = input("{} is not an int! Choose again: ".format(tmp))
      move = int(move)
      # check valid
      while move not in range(1,10):
      tmp = move
      move = int(input("{} is not a valid square. Please choose again: ".format(tmp)))
      while move not in board.empty_list:
      #board.square_taken_message()
      tmp = move
      move = int(input("Square {} is already taken. Please choose again: ".format(tmp)))

      return move


      """<<<GAME LOOP>>>"""

      def game_loop():
      # display screen asking for coin fkip guess
      gameDisplay.fill(BACKGROUND_COLOR)
      myfont = pygame.font.SysFont('Comic Sans MS', 50)
      textsurface = myfont.render("Heads or Tails?", True, white)
      gameDisplay.blit(textsurface, [60, 10])
      pygame.display.update()

      # receive coin guess
      coin = ["H", "T"]
      coin_toss = input("We will decide who goes first with a coin flip. Player 1 pease enter H or T: ")
      if coin_toss not in coin:
      coin_toss = input("Not a valid input. Please enter H or T: ")

      # dictionary for translating input to display words
      coin_dic = {"H" : "Heads", "T": "Tails"}

      # random toss. display the guess now. display the outcome after images
      random_toss = random.choice(coin)
      myfont = pygame.font.SysFont('Comic Sans MS', 50)
      textsurface2 = myfont.render("Your choice: {}".format(coin_dic[coin_toss]), True, white)
      gameDisplay.blit(textsurface2, [60, DISPLAY_HEIGHT - 140])


      # load two images of coin
      coin_a = pygame.image.load('coin_a.png')
      coin_b = pygame.image.load('coin_b.png')
      tosser = 0

      textsurface3 = myfont.render("Result: {}".format(coin_dic[random_toss]), True, white)
      if coin_toss == random_toss:
      textsurface4 = myfont.render("Win! You go first!", True, white)
      else:
      textsurface4 = myfont.render("Lose! You go second!", True, white)
      # change image 20 times for nice effect. display outcome of coin flip after 10 times.
      while tosser < 21:
      print(20 - tosser)
      gameDisplay.blit(coin_a, (60,DISPLAY_HEIGHT//3))
      pygame.display.update()
      pygame.time.wait(100)
      gameDisplay.blit(coin_b, (60, DISPLAY_HEIGHT//3))
      pygame.display.update()
      pygame.time.wait(100)
      tosser += 1

      if tosser > 10:
      gameDisplay.blit(textsurface3, [60, DISPLAY_HEIGHT - 70])
      gameDisplay.blit(textsurface4, [60, DISPLAY_HEIGHT])
      pygame.display.update()

      # short pause for user to read result before switching screen to tic-tac-toe
      pygame.time.wait(500)

      # instantiate the player objects according to the result of the coin toss
      while coin_toss not in coin:
      coin_toss = input("You didn't enter a valid input! Try again please: ")
      if coin_toss == random_toss:
      print("Coin toss result was {}".format(random_toss))
      print("Player 1 to go first!")
      p1 = Player('naughts', True, pink, 'Player 1')
      p2 = Player('crosses', False, green, 'Player 2')
      current_player = p1
      next_player = p2
      else:
      print("Coin toss result was {}".format(random_toss))
      print("Player 2 to go first!")
      p1 = Player('naughts', False, pink, 'Player 1')
      p2 = Player('crosses', True, green, 'Player 2')
      current_player = p2
      next_player = p1

      # instantiate the board and display it
      b = Board(current_player, next_player)
      b.create_board()

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # begin while loop - initialise all variables here so they're reset if we restart the game
      play_again = True
      end = False
      b.empty_list = [1,2,3,4,5,6,7,8,9]
      b.naught_list =
      b.naught_trebles =
      b.naught_treble_sums =
      b.cross_list =
      b.cross_trebles =
      b.cross_treble_sums =
      while play_again:

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      pygame.display.update()
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # allow window to be quit
      for event in pygame.event.get():
      if event.type == pygame.QUIT:
      play_again = False

      # print useful stuff to terminal
      print("-----Empty Squares-----")
      print(b.empty_list)
      print("-----Naughts-----")
      print(b.naught_list)
      print(b.naught_trebles)
      print(b.naught_treble_sums)
      print("-----Crosses-----")
      print(b.cross_list)
      print(b.cross_trebles)
      print(b.cross_treble_sums)

      # request player move and display it
      move = current_player.request_move(b)
      b.play_move(current_player.marker, move, current_player)

      # display current player
      if current_player == p1:
      b.player1_text(white)
      b.player2_text(gray)
      pygame.display.update()
      elif current_player == p2:
      b.player1_text(gray)
      b.player2_text(white)
      pygame.display.update()

      # check if current player has won and then switch player
      if current_player == p1 and end == False:
      if b.magic_square_winner_check(p1) == 1:
      draw_wide_rect(70, DISPLAY_HEIGHT+70)
      draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
      b.victory_message(current_player.name)
      winning_idx = b.naught_treble_sums.index(15)
      winning_tuple = b.naught_trebles[winning_idx]
      winning_list = [x for x in winning_tuple]
      print(winning_list)
      b.victory_line(winning_list, p1.color)
      pygame.display.update()
      end = True
      else:
      current_player = p2

      elif current_player == p2 and end == False:
      if b.magic_square_winner_check(p2) == 1:
      draw_wide_rect(70, DISPLAY_HEIGHT+70)
      draw_wide_rect(DISPLAY_WIDTH - 80, DISPLAY_HEIGHT+70)
      b.victory_message(current_player.name)
      winning_idx = b.cross_treble_sums.index(15)
      winning_tuple = b.cross_trebles[winning_idx]
      winning_list = [x for x in winning_tuple]
      b.victory_line(winning_list, p2.color)
      pygame.display.update()
      end = True
      else:
      current_player = p1

      # check if game is a draw
      if len(b.empty_list) == 0 and end == False:
      b.draw_message()
      pygame.display.update()
      end = True

      # if game reaches a terminal state, return 1 (used as code in main loop to ask about restart)
      if end:
      return 1
      pygame.display.update()


      def main():
      end_msg = game_loop()
      while end_msg == True:
      play_again = int(input("Play again? 1 = Yes, 0 = No : "))
      if play_again == 1:
      game_loop()
      if play_again == 0:
      break

      if __name__ == '__main__':
      main()






      python object-oriented tic-tac-toe





      share







      New contributor




      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.










      share







      New contributor




      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.








      share



      share






      New contributor




      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 5 mins ago









      user11128user11128

      1011




      1011




      New contributor




      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      user11128 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          user11128 is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211413%2fobject-oriented-tic-tac-toe-python%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          user11128 is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          user11128 is a new contributor. Be nice, and check out our Code of Conduct.













          user11128 is a new contributor. Be nice, and check out our Code of Conduct.












          user11128 is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211413%2fobject-oriented-tic-tac-toe-python%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          TypeError: fit_transform() missing 1 required positional argument: 'X'