5
\$\begingroup\$

This is my first python/pygame program. Instead of using images I tried drawing with pygame.draw.line() and pygame.draw.arc() with time.sleep() for animation, but this adds delay.

I have commented that code before the while loop in main method. Problem is, it still takes time to render as I have added pygame.time.Clock().tick(16.67) to maintain 60fps.(Maybe this is not needed at all)

I want animation (by using drawGridLinesAnim() and adding delay to drawX() and drawO() methods) and also want to execute event handling in its own thread. I tried threading.Thread(target=fucn, args=(tuple)).start() but that keeps adding latency every move.

How can I go about this? Also, this is the first time I am posting to code review. So any tips/suggestions regarding, code style, code design, etc. will be appreciated.

""" Tic Tac Toe game in python using pygame """
import sys, pygame, time, threading, numpy as np
pygame.init()
size = width, height = 800, 600
screen = pygame.display.set_mode(size)
#resources
surface_tile_bg = pygame.image.load("tile_bg.png").convert_alpha()
surface_tile_x = pygame.image.load("tile_x1.png").convert_alpha()
surface_tile_o = pygame.image.load("tile_o1.png").convert_alpha()
surface_board = pygame.Surface((300, 300))
color_grid_bg = pygame.color.Color("#14bdac")
color_grid_line = pygame.color.Color("#0da192")
color_symbol_x = pygame.color.Color("#545454")
color_symbol_o = pygame.color.Color("#F2EBD3")
color_white = [255,255,255]
#checks whether all items in a list are exactly same
def all_same(items):
 return all(x == items[0] for x in items)
#draws grid background using surface_tile_bg
def drawBackgroundUsingImage():
 for i in range (0,3):
 for j in range (0,3):
 surface_board.blit(surface_tile_bg, ((i*100, j*100), (100, 100)))
 pygame.display.update()
#draws grid lines with animation 
def drawGridLinesAnim():
 for i in range(0, 301, 10):
 pygame.draw.line(surface_board, color_grid_line, (0, 100), (i,100), 10)
 pygame.draw.line(surface_board, color_grid_line, (0, 200), (i,200), 10)
 pygame.draw.line(surface_board, color_grid_line, (100, 0), (100,i), 10)
 pygame.draw.line(surface_board, color_grid_line, (200, 0), (200,i), 10)
 screen.blit(surface_board, (100, 100))
 pygame.display.update()
 time.sleep(0.005)
#draws grid background using color
def drawBackground():
 surface_board.fill(color_grid_bg)
#draws grid lines using color
def drawGridLines():
 pygame.draw.line(surface_board, color_grid_line, (0, 100), (300,100), 10)
 pygame.draw.line(surface_board, color_grid_line, (0, 200), (300,200), 10)
 pygame.draw.line(surface_board, color_grid_line, (100, 0), (100,300), 10)
 pygame.draw.line(surface_board, color_grid_line, (200, 0), (200,300), 10)
#draws X at given offset on surface 
def drawX(x, y):
 for i in range(0, 70, 10):
 pygame.draw.line(surface_board, color_symbol_x, (x + 20, y + 20), (x + 20 + i, y + 20 + i), 20)
 screen.blit(surface_board, (100, 100))
 for i in range(0, 70, 10):
 pygame.draw.line(surface_board, color_symbol_x, (x + 75, y + 20), (x + 75 - i, y + 20 + i), 20)
 screen.blit(surface_board, (100, 100))
#draws O at given offset on surface
def drawO(x, y):
 for i in range(0, 360, 5):
 rect = ((x+15, y+15), (70,70))
 pygame.draw.arc(surface_board, color_symbol_o, rect, 0, i, 10)
 screen.blit(surface_board, (100, 100))
#draws gameboard depending on contents of TicTacToe object's _board data
def drawBoard(TTTGameObject):
 rows = len(TTTGameObject.board)
 cols = len(TTTGameObject.board[0])
 for i in range (0,rows):
 for j in range (0, cols):
 if TTTGameObject.isPositionX(i,j):
 drawX(j*100, i*100)
 elif TTTGameObject.isPositionO(i,j):
 drawO(j*100, i*100)
#mark position if empty with player's symbol
def handleInput(TTTGameObject, player):
 did_user_quit = False
 symbol_val = player.symbol_val
 events = pygame.event.get()
 for event in events:
 if event.type == pygame.QUIT:
 did_user_quit = True
 elif event.type == pygame.KEYDOWN:
 if event.key == pygame.K_KP1:
 if TTTGameObject.isPositionEmpty(2,0):
 TTTGameObject.markPosition(2,0,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP2:
 if TTTGameObject.isPositionEmpty(2,1):
 TTTGameObject.markPosition(2,1,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP3:
 if TTTGameObject.isPositionEmpty(2,2):
 TTTGameObject.markPosition(2,2,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP4:
 if TTTGameObject.isPositionEmpty(1,0):
 TTTGameObject.markPosition(1,0,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP5:
 if TTTGameObject.isPositionEmpty(1,1):
 TTTGameObject.markPosition(1,1,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP6:
 if TTTGameObject.isPositionEmpty(1,2):
 TTTGameObject.markPosition(1,2,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP7:
 if TTTGameObject.isPositionEmpty(0,0):
 TTTGameObject.markPosition(0,0,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP8:
 if TTTGameObject.isPositionEmpty(0,1):
 TTTGameObject.markPosition(0,1,symbol_val)
 TTTGameObject.turn *= -1
 elif event.key == pygame.K_KP9:
 if TTTGameObject.isPositionEmpty(0,2):
 TTTGameObject.markPosition(0,2,symbol_val)
 TTTGameObject.turn *= -1
 return did_user_quit
#player class
class Player:
 def __init__(self):
 self._name = ""
 self._symbol_val = 0
 @property
 def name(self):
 return self._name
 @name.setter
 def name(self, pname):
 self._name = pname
 @property
 def symbol_val(self):
 return self._symbol_val
 @symbol_val.setter
 def symbol_val(self, pSymbVal):
 self._symbol_val = pSymbVal
#tictactoe class
class TicTacToe:
 def __init__(self):
 #set-up board
 self._board = np.array([[0,0,0],
 [0,0,0],
 [0,0,0]])
 #1 for player 1; -1 for player 2 
 self._turn = 1
 @property
 def board(self):
 return self._board
 @property
 def turn(self):
 return self._turn
 @turn.setter
 def turn(self, val):
 self._turn = val
 #positions will always be marked by 0,1 and 2 for empty, x and o respectively
 def isPositionEmpty(self, x, y):
 return True if self._board[x][y] == 0 else False
 def isPositionX(self, x, y):
 return True if self._board[x][y] == 1 else False
 def isPositionO(self, x, y):
 return True if self._board[x][y] == 2 else False
 #mark 1(x) or 2(0) at specified board position
 def markPosition(self, x, y, val):
 self._board[x][y] = val
 #game is over if board is full or winning combination found
 def isGameOver(self):
 gameOver = False;
 if self.isBoardFull() or not np.array_equal(self.getWinningCombination(), [0,0,0]):
 gameOver = True
 return gameOver
 #check whether all positions filled
 def isBoardFull(self):
 boardFull = True;
 for i in range (0,3):
 for j in range (0,3):
 if self._board[i][j] == 0:
 boardFull = False
 break
 return boardFull
 #return [0,0,0] if no winning combination present; else return winning combination
 def getWinningCombination(self):
 winningCombination = [0,0,0]
 if all_same(self._board[0]): winningCombination = self._board[0]
 if all_same(self._board[1]): winningCombination = self._board[1]
 if all_same(self._board[2]): winningCombination = self._board[2]
 if all_same(self._board[:,0]): winningCombination = self._board[:,0]
 if all_same(self._board[:,1]): winningCombination = self._board[:,1]
 if all_same(self._board[:,2]): winningCombination = self._board[:,2]
 if all_same(np.diag(self._board)): winningCombination = np.diag(self._board)
 #temp board since fliplr() alters array
 temp_check_board = np.copy(self._board)
 temp_alter_board = np.copy(self._board)
 if all_same(np.diag(np.fliplr(temp_check_board))): winningCombination = np.diag(np.fliplr(temp_alter_board))
 return winningCombination 
def main():
 tttGameObject = TicTacToe() 
 p1 = Player()
 p2 = Player()
 p1.symbol_val = 1
 p2.symbol_val = 2
 turn = 1
 #drawBackgroundUsingImage()
 #drawGridLinesAnim()
 user_quits = False
 while not tttGameObject.isGameOver() and not user_quits:
 drawBackground()
 drawGridLines()
 curr_player = p1 if tttGameObject.turn == 1 else p2
 user_quits = handleInput(tttGameObject, curr_player)
 drawBoard(tttGameObject)
 pygame.display.update()
 pygame.time.Clock().tick(16.67)
 surface_board.fill(color_white)
main()
pygame.quit()
Peilonrayz
44.4k7 gold badges80 silver badges157 bronze badges
asked Jan 31, 2017 at 19:19
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers . \$\endgroup\$ Commented Feb 3, 2017 at 10:25
  • \$\begingroup\$ I will keep that in mind. Thanks for reverting back to original question. \$\endgroup\$ Commented Feb 5, 2017 at 15:33

1 Answer 1

3
\$\begingroup\$

There's quite a lot going on here, beside the style issues (use snake_case instead of camelCase, for example).

  • The first big issue is that you're checking who won on every frame. You only need to check that when there's an input from either player, because the board will not change in any other case.

  • You also don't need numpy for such a simple task, if you have a 3x3 grid, you can easily get the two diagonals.

For example:

grid = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
diag = [grid[i][i] for i in range(0, 3)]
print diag
diag = [grid[2-i][i] for i in range(2, -1, -1)]
print diag

You also don't need the all_same check, what you can do is use 1 for a player and -1 for the other player and check if the sum in any of the rows, columns or diagonals is either 3 or -3.

This also means that you can build an array containing the sums and check if one of the -3, 3 is in there.

For example:

grid = [[-1, 0, 1],[-1, 1, 1],[-1, 0, 0]]
diag19 = [grid[i][i] for i in range(0, 3)]
diag37 = [grid[2-i][i] for i in range(2, -1, -1)]
sums_rows = [sum(row) for row in grid]
sums_cols = [sum(col) for col in zip(*grid)]
sums_diag = [sum(diag) for diag in [diag19, diag37]]
winning_list = [status for status in [sums_rows, sums_cols, sums_diag] if 3 in status or -3 in status]
print winning_list

Of course:

  • Only the player with the current turn can win
  • The board can be full only after nine moves, so no need to check the content, just check if it's the ninth move.

Also, you're making the same check a lot of times when the user presses a key. It's much easier if you move that check into the markPosition function and maybe rename that to mark_position_if_free.

The same goes for changing the turn, you do that in every if, so just put it again in the mark_position_if_free function.

That leaves you with:

# TODO: rename everything to be snake_case
def markPosition(self, x, y, val):
 if TTTGameObject.isPositionEmpty(x, y):
 self._board[x][y] = val
 self.turn *= -1
def handleInput(TTTGameObject, player):
 did_user_quit = False
 symbol_val = player.symbol_val
 events = pygame.event.get()
 for event in events:
 if event.type == pygame.QUIT:
 did_user_quit = True
 elif event.type == pygame.KEYDOWN:
 if event.key == pygame.K_KP1:
 TTTGameObject.markPosition(2,0,symbol_val)
 elif event.key == pygame.K_KP2:
 TTTGameObject.markPosition(2,1,symbol_val)
 elif event.key == pygame.K_KP3:
 TTTGameObject.markPosition(2,2,symbol_val)
 elif event.key == pygame.K_KP4:
 TTTGameObject.markPosition(1,0,symbol_val)
 elif event.key == pygame.K_KP5:
 TTTGameObject.markPosition(1,1,symbol_val)
 elif event.key == pygame.K_KP6:
 TTTGameObject.markPosition(1,2,symbol_val)
 elif event.key == pygame.K_KP7:
 TTTGameObject.markPosition(0,0,symbol_val)
 elif event.key == pygame.K_KP8:
 TTTGameObject.markPosition(0,1,symbol_val)
 elif event.key == pygame.K_KP9:
 TTTGameObject.markPosition(0,2,symbol_val)
 return did_user_quit

But that really looks a lot like a lookup table, basically a dictionary. So maybe you could use something like:

# TODO: rename everything to be snake_case
keys_to_coord = {
 pygame.K_KP1: [2, 0],
 pygame.K_KP2: [2, 1],
 pygame.K_KP3: [2, 2],
 pygame.K_KP4: [1, 0],
 pygame.K_KP5: [1, 1],
 pygame.K_KP6: [1, 2],
 pygame.K_KP7: [0, 0],
 pygame.K_KP8: [0, 1],
 pygame.K_KP9: [0, 2]
}
def handleInput(TTTGameObject, player):
 did_user_quit = False
 symbol_val = player.symbol_val
 events = pygame.event.get()
 for event in events:
 if event.type == pygame.QUIT:
 did_user_quit = True
 elif event.type == pygame.KEYDOWN:
 TTTGameObject.markPosition(keys_to_coord[event.key][0],keys_to_coord[event.key][1],symbol_val)
 return did_user_quit

I don't think this is all, but start by simplifying your code and other things will pop-up (how often are you actually drawing X and O, for example).

answered Feb 1, 2017 at 12:16
\$\endgroup\$
0

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.