5
\$\begingroup\$

I have this code ready and it works just fine. But what are the ways by which I can improve this code? I have no CS background and this is my first coding project. I could not paste my entire code so I'm posting my github project link. Any recommendations on that would also be appreciated.

import pygame
pygame.init()
window = pygame.display.set_mode((600,600))
pygame.display.set_caption("Tic Tac Toe")
white = (255,255,255)
Circle = pygame.image.load('Cross.png')
Cross = pygame.image.load('Circle.png')
clicks = []
load1 = False
load2 = False
load3 = False
load4 = False
load5 = False
load6 = False
load7 = False
load8 = False
load9 = False
load10 = False
load11 = False
load12 = False
load13 = False
load14 = False
load15 = False
load16 = False
load17 = False
load18 = False
def Background():
 window.fill(white)
 pygame.draw.rect(window, (0,0,0),( 198,0,4,600))
 pygame.draw.rect(window, (0,0,0),( 398,0,4,600))
 pygame.draw.rect(window, (0,0,0),( 0,198,600,4))
 pygame.draw.rect(window, (0,0,0),( 0,398,600,4))
pygame.display.flip()
run = True
while run:
 pygame.time.delay(100)
 for event in pygame.event.get():
 if event.type == pygame.QUIT:
 run = False
 mouse = pygame.mouse.get_pressed()[0]
 mouse_x,mouse_y = pygame.mouse.get_pos()
 if len(clicks)%2 != 0:
 if mouse:
 if mouse_x > 0 and mouse_x < 198 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load1 = True
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load2 = True
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load3 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load4 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load5 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load6 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load7 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load8 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load9 = True
 elif len(clicks)%2 == 0:
 if mouse:
 if mouse_x > 0 and mouse_x < 198 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load10 = True
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load11 = True
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load12 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load13 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load14 = True
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load15 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 0 and mouse_y < 198:
 clicks.append(mouse)
 load16 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 202 and mouse_y < 398:
 clicks.append(mouse)
 load17 = True
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 402 and mouse_y < 600:
 clicks.append(mouse)
 load18 = True
 if load1:
 window.blit(Circle, (0,0))
 if load2:
 window.blit(Circle, (0,201))
 if load3:
 window.blit(Circle, (0,401))
 if load4:
 window.blit(Circle, (201,0))
 if load5:
 window.blit(Circle, (201,201))
 if load6:
 window.blit(Circle, (201,401))
 if load7:
 window.blit(Circle, (401,0))
 if load8:
 window.blit(Circle, (401,201))
 if load9:
 window.blit(Circle, (401,401))
 if load10:
 window.blit(Cross, (0,0))
 if load11:
 window.blit(Cross, (0,201))
 if load12:
 window.blit(Cross, (0,401))
 if load13:
 window.blit(Cross, (201,0))
 if load14:
 window.blit(Cross, (201,201))
 if load15:
 window.blit(Cross, (201,401))
 if load16:
 window.blit(Cross, (401,0))
 if load17:
 window.blit(Cross, (401,201))
 if load18:
 window.blit(Cross, (401,401))
 if ((load1 and load2 and load3) or (load2 and load5 and load8) or (load3 and load6 and load9) or (load1 and load4 and load7) or (load4 and load5 and load6) or
 (load7 and load8 and load9) or (load1 and load5 and load9) or (load3 and load5 and load7)):
 print("Circle Wins")
 elif ((load10 and load11 and load12) or (load13 and load14 and load15) or (load16 and load17 and load18) or (load10 and load13 and load16) or
 (load11 and load14 and load17) or(load12 and load15 and load18) or (load10 and load14 and load18) or (load13 and load14 and load16)):
 print("Cross Wins")
 pygame.display.update()
 Background()
pygame.quit()
AlexV
7,3532 gold badges24 silver badges47 bronze badges
asked Jul 3, 2019 at 4:22
\$\endgroup\$
0

1 Answer 1

6
\$\begingroup\$

These points are in order of how I went through your program. They are not ordered in matter of importance.

Your code is suspicious from the start:

Circle = pygame.image.load('Cross.png')
Cross = pygame.image.load('Circle.png')

Why? What prompted you to define a a circle as a cross and vice versa? Either way, the variable names start with a Capital while all other variables do not. Considering Python is a case-sensitive language, please keep all variable names lowercase to prevent confusion.

Your code does not specify anywhere what the expected sizes of Cross.png and Circle.png are. Which means my first game looked like this:

Tic Tac Toe with crosses and circles too large

The pictures aren't correctly centred in their square, so the second game (with modified cross/circle files) looked like this:

Tic Tac Toe with cross misaligned

The cross in the middle square is misaligned, too far on the top-left of the corner. With the earlier game, it didn't do this.

If you ship/share code without shipping/sharing the pictures, like you did when posting your question, make sure you either:

  • leave a note somewhere either in the documentation or the code itself which states the required sizes.
  • enforce the program won't start by checking the dimensions of the pictures.
  • scale the images in your program so they'll always be made to fit.

Your code only counts the clicks made. Not whether any of those clicks were correct. If I tap an already selected square again, I can make this game happen:

Tic Tac Toe just circles

Or this:

Tic Tac Toe cheats, circle always wins

Because you're keeping track of each square twice, once for circle and once for cross, it sometimes happens that I can overwrite a square with a circle. Or that the logic of the program no longer corresponds with the visual representation, leading to a very confusing win:

Tic Tac Toe not won, but program thinks so anyway

Long story short, your program is very easy to break. 5+ games in I got more rounds that didn't go according to expectations than games that did.

This is how the program currently keeps track of the squares:

load1 = False
load2 = False
load3 = False
load4 = False
load5 = False
load6 = False
load7 = False
load8 = False
load9 = False
load10 = False
load11 = False
load12 = False
load13 = False
load14 = False
load15 = False
load16 = False
load17 = False
load18 = False

load is an incredibly bad name for a variable. My first guess would be that it loads something externally, not that it's the state of an internal part. state or square would have been better. And while we're at it, might as well turn it into a list. While we could start with an empty list and append a False 18x times, we can also do it the Python way and use a list comprehension:

states = [False for x in range(18)]

Which can be called like states[3] (0-indexed, so 0..17 are valid).

However, we don't want 18 values. There are only 9 squares and each square should only be fillable once. You're currently tracking each square twice, which is a good way to get bugs. Instead of 2 bool per square, we can give each square a State using Enum.

The following example:

from enum import Enum
class State(Enum):
 FREE = 1
 CIRCLE = 2
 CROSS = 3
states = [State.FREE for x in range(9)]
states[4] = State.CIRCLE
states[6] = State.CROSS
for i in states:
 print(i, type(i))

Produces the following output:

State.FREE <enum 'State'>
State.FREE <enum 'State'>
State.FREE <enum 'State'>
State.FREE <enum 'State'>
State.CIRCLE <enum 'State'>
State.FREE <enum 'State'>
State.CROSS <enum 'State'>
State.FREE <enum 'State'>
State.FREE <enum 'State'>

Now a square can no longer be a circle and a cross at the same time, not even by accident. Before overwriting, the square can be checked whether it's free. If it's not free, it can't be written to. Only after a successful write, should the game change players. This means we'll have to do away with the clicks list, stop appending the mouse location to anywhere and just handle the input. If it was right, act. If it was wrong, don't act.

The easiest (not necessarily the best) way to keep track of a player, is using a player variable. We could use a bool, but why not make it an Enum instead? After all, we can toggle (invert the value) of an Enum just as easily as we can with a bool, if we pick the values right.

class Player(Enum):
 CIRCLE = 1
 CROSS = -1

Now, if we want to switch players, all we have to do is:

player = Player(player.value * -1)

Don't mind my poor naming, but functionally we're making progress.

When checking for a player win, you print who won. However, you don't pause/exit the game and continue to accept input. This is unwanted. All we need to fix this using the already existing while run loop. You do have a pygame.quit() at the end of your program, but by the time the code reaches there the program will be terminated anyway. You don't need it. Just turn run to False.

Then the validation of the win can be simplified:

def is_3_in_row(a, b, c, sign):
 return states[a] == State.CIRCLE and states[b] == State.CIRCLE \
 and states[c] == sign

Your board also doesn't recognize a tie (9 filled squares without winner). If we account for all that, we can wrap most of the program into a main guard. This will prevent the game from accidentally running if you later decide to import the file in a different program.

All remaining global variables that will not be changed after being defined are now UPPER_CASE, see PEP 8. Background is now background. Your comma's (,) also got a bit of breathing room, making the code easier to read.

The score so far:

import pygame
from enum import Enum
class State(Enum):
 CIRCLE = 1
 CROSS = 2
 FREE = 3
class Player(Enum):
 CIRCLE = 1
 CROSS = -1
def background(window):
 window.fill(WHITE)
 pygame.draw.rect(window, (0, 0, 0), (198, 0, 4, 600))
 pygame.draw.rect(window, (0, 0, 0), (398, 0, 4, 600))
 pygame.draw.rect(window, (0, 0, 0), (0, 198, 600, 4))
 pygame.draw.rect(window, (0, 0, 0), (0, 398, 600, 4))
WHITE = (255, 255, 255)
CIRCLE = pygame.image.load('Circle.png')
CROSS = pygame.image.load('Cross.png')
def main():
 pygame.init()
 pygame.display.set_caption("Tic Tac Toe")
 window = pygame.display.set_mode((600, 600))
 pygame.display.flip()
 states = [State.FREE for x in range(9)]
 player = Player.CIRCLE
 run = True
 def is_3_in_row(a, b, c, sign):
 return states[a] == State.CIRCLE and states[b] == State.CIRCLE \
 and states[c] == sign
 while run:
 pygame.time.delay(100)
 for event in pygame.event.get():
 if event.type == pygame.QUIT:
 run = False
 mouse = pygame.mouse.get_pressed()[0]
 mouse_x, mouse_y = pygame.mouse.get_pos()
 if mouse:
 if mouse_x > 0 and mouse_x < 198 and mouse_y > 0 and mouse_y < 198:
 if states[0] is State.FREE:
 if player == player.CIRCLE:
 states[0] = State.CIRCLE
 elif player == player.CROSS:
 states[0] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 202 and mouse_y < 398:
 if states[1] is State.FREE:
 if player == player.CIRCLE:
 states[1] = State.CIRCLE
 elif player == player.CROSS:
 states[1] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 0 and mouse_x < 198 and mouse_y > 402 and mouse_y < 600:
 if states[2] is State.FREE:
 if player == player.CIRCLE:
 states[2] = State.CIRCLE
 elif player == player.CROSS:
 states[2] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 0 and mouse_y < 198:
 if states[3] is State.FREE:
 if player == player.CIRCLE:
 states[3] = State.CIRCLE
 elif player == player.CROSS:
 states[3] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 202 and mouse_y < 398:
 if states[4] is State.FREE:
 if player == player.CIRCLE:
 states[4] = State.CIRCLE
 elif player == player.CROSS:
 states[4] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 202 and mouse_x < 398 and mouse_y > 402 and mouse_y < 600:
 if states[5] is State.FREE:
 if player == player.CIRCLE:
 states[5] = State.CIRCLE
 elif player == player.CROSS:
 states[5] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 0 and mouse_y < 198:
 if states[6] is State.FREE:
 if player == player.CIRCLE:
 states[6] = State.CIRCLE
 elif player == player.CROSS:
 states[6] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 202 and mouse_y < 398:
 if states[7] is State.FREE:
 if player == player.CIRCLE:
 states[7] = State.CIRCLE
 elif player == player.CROSS:
 states[7] = State.CROSS
 player = Player(player.value * -1)
 elif mouse_x > 402 and mouse_x < 600 and mouse_y > 402 and mouse_y < 600:
 if states[8] is State.FREE:
 if player == player.CIRCLE:
 states[8] = State.CIRCLE
 elif player == player.CROSS:
 states[8] = State.CROSS
 player = Player(player.value * -1)
 if states[0] == State.CIRCLE:
 window.blit(CIRCLE, (0, 0))
 elif states[0] == State.CROSS:
 window.blit(CROSS, (0, 0))
 if states[1] == State.CIRCLE:
 window.blit(CIRCLE, (0, 201))
 elif states[1] == State.CROSS:
 window.blit(CROSS, (0, 201))
 if states[2] == State.CIRCLE:
 window.blit(CIRCLE, (0, 401))
 elif states[2] == State.CROSS:
 window.blit(CROSS, (0, 401))
 if states[3] == State.CIRCLE:
 window.blit(CIRCLE, (201, 0))
 elif states[3] == State.CROSS:
 window.blit(CROSS, (201, 0))
 if states[4] == State.CIRCLE:
 window.blit(CIRCLE, (201, 201))
 elif states[4] == State.CROSS:
 window.blit(CROSS, (201, 201))
 if states[5] == State.CIRCLE:
 window.blit(CIRCLE, (201, 401))
 elif states[5] == State.CROSS:
 window.blit(CROSS, (201, 401))
 if states[6] == State.CIRCLE:
 window.blit(CIRCLE, (401, 0))
 elif states[6] == State.CROSS:
 window.blit(CROSS, (401, 0))
 if states[7] == State.CIRCLE:
 window.blit(CIRCLE, (401, 201))
 elif states[7] == State.CROSS:
 window.blit(CROSS, (401, 201))
 if states[8] == State.CIRCLE:
 window.blit(CROSS, (401, 401))
 elif states[8] == State.CROSS:
 window.blit(CROSS, (401, 401))
 if (is_3_in_row(0, 1, 2, State.CIRCLE)
 or is_3_in_row(1, 4, 7, State.CIRCLE)
 or is_3_in_row(2, 5, 8, State.CIRCLE)
 or is_3_in_row(0, 3, 6, State.CIRCLE)
 or is_3_in_row(3, 4, 5, State.CIRCLE)
 or is_3_in_row(6, 7, 8, State.CIRCLE)
 or is_3_in_row(0, 4, 8, State.CIRCLE)
 or is_3_in_row(2, 4, 6, State.CIRCLE)):
 print("Circle wins")
 run = False
 elif (is_3_in_row(0, 1, 2, State.CROSS)
 or is_3_in_row(1, 4, 7, State.CROSS)
 or is_3_in_row(2, 5, 8, State.CROSS)
 or is_3_in_row(0, 3, 6, State.CROSS)
 or is_3_in_row(3, 4, 5, State.CROSS)
 or is_3_in_row(6, 7, 8, State.CROSS)
 or is_3_in_row(0, 4, 8, State.CROSS)
 or is_3_in_row(2, 4, 6, State.CROSS)):
 print("Cross wins")
 run = False
 elif all((state == State.CIRCLE or state == State.CROSS)
 for state in states):
 print("It's a tie")
 run = False
 pygame.display.update()
 background(window)
if __name__ == "__main__":
 main()

Is this as good as it can get? Absolutely not. We can simplify the part setting the new state of the square, by putting the logic behind a function. Including the player = Player(player.value * -1), but not without a long explanation about scope. So I will leave that for another time.

If you work out what the relationships between the various coordinates of a square are, you can make a pre-defined datastructure that works out the coordinates for you instead of having to do all the adjustments in all 9 cells when something has to be adjusted (for example, if you ever increase your window-size to anything bigger than 600x600.

answered Jun 20, 2022 at 19:59
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.