7
\$\begingroup\$

I am working on Jupyter Notebook and I am new to Python. I have created this tic tac toe game.

from IPython.display import clear_output
import time


def display_board(board):


    print'   |   | '
    print'',board[1],'|',board[2],'|',board[3]
    print'___|___|___'
    print'   |   | '
    print'',board[4],'|',board[5],'|',board[6]
    print'___|___|___'
    print'   |   | '
    print'',board[7],'|',board[8],'|',board[9]
    print'   |   |'

from random import randint

def player_input(board):
    global turn_r
    global first_time   
    if(first_time==0):
        turn_r = randint(0,1)
        if(turn_r == 0):
            print"Player 1 's Move\n"
        if(turn_r == 1):
            print"Player 2's Move\n"
        first_time = 1

    if(turn_r == 0):        
            p1marker=0
            while not int(p1marker) in board:
                p1marker = input("Where Should I Place Your X : ")
            board[p1marker] = 'X'
            turn_r=1

    else:
            p2marker=0
            while not int(p2marker) in board:
                p2marker = input("Where Should I Place Your O : ")
            board[p2marker] = 'O'
            turn_r=0

def result_check(board):
    if((board[7] == 'X' and board[8] == 'X' and board[9] == 'X') or # across the top
    (board[4] == 'X' and board[5] == 'X' and board[6] == 'X') or # across the middle
    (board[1] == 'X' and board[2] == 'X' and board[3] == 'X') or # across the bottom
    (board[7] == 'X' and board[4] == 'X' and board[1] == 'X') or # down the middle
    (board[8] == 'X' and board[5] == 'X' and board[2] == 'X') or # down the middle
    (board[9] == 'X' and board[6] == 'X' and board[3] == 'X') or # down the right side
    (board[7] == 'X' and board[5] == 'X' and board[3] == 'X') or # diagonal
    (board[9] == 'X' and board[5] == 'X' and board[1] == 'X')):
        return 'Player 1'

    elif((board[7] == 'O' and board[8] == 'O' and board[9] == 'O') or # across the top
    (board[4] == 'O' and board[5] == 'O' and board[6] == 'O') or # across the middle
    (board[1] == 'O' and board[2] == 'O' and board[3] == 'O') or # across the bottom
    (board[7] == 'O' and board[4] == 'O' and board[1] == 'O') or # down the middle
    (board[8] == 'O' and board[5] == 'O' and board[2] == 'O') or # down the middle
    (board[9] == 'O' and board[6] == 'O' and board[3] == 'O') or # down the right side
    (board[7] == 'O' and board[5] == 'O' and board[3] == 'O') or # diagonal
    (board[9] == 'O' and board[5] == 'O' and board[1] == 'O')) :
        return 'Player 2'

    elif((board[1] == 'X' or board[1] == 'O') and
         (board[2] == 'X' or board[2] == 'O') and
         (board[3] == 'X' or board[3] == 'O') and
         (board[4] == 'X' or board[4] == 'O') and
         (board[5] == 'X' or board[5] == 'O') and
         (board[6] == 'X' or board[6] == 'O') and
         (board[7] == 'X' or board[7] == 'O') and
         (board[8] == 'X' or board[8] == 'O') and
         (board[9] == 'X' or board[9] == 'O')):
        return 'Draw'

    else:
        return 'None'


print "Welcome to Tic Tac Toe"

while True:
    board = [0,1,2,3,4,5,6,7,8,9]

    global turn_r
    global first_time

    turn_r = 0
    first_time = 0
    clear_output()
    print'Player 1 will be "X"\nPlayer 2 will be "O"\nBe Ready ! ;)'
    time.sleep(3)
    game_on = True

    while game_on:
        clear_output()
        display_board(board)
        player_input(board)
        z = result_check(board)
        if(z == 'Player 1'):
            clear_output()
            display_board(board)
            print 'Yipee ! Player 1 Has Won ! ! ! :) '
            game_on = False
        if(z == 'Player 2'):
            clear_output()
            display_board(board)
            print 'Yipee ! Player 2 Has Won ! ! ! :) '
            game_on = False
        if(z == 'Draw'):
            clear_output()
            display_board(board)
            print 'Wow ! It is a Draw ! ! ! :|'
            game_on = False
        if(z == 'None'):
            continue

    quit = raw_input('Do You Want to Continue (y/n) :')
    if(quit == 'n'):
        break

I want help to make it better. Any code optimization with explanation is welcome.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

This is a great way to learn a programming language. With that being said, let's jump into the review!

Formatting

Entry Point

Python code should have an entry point (if it is the main program). I know you mentioned that you are using Jupyter Notebook (which is a different story), but if you were writing a single Python program, make sure you have a main method like:

if __name__ == '__main__':
    # do stuff here

This just tells the Python interpreter to start your program here if executed as the main program (as opposed as executing your program as a Python library).

Parentheses

Python actually does not require parentheses for conditional statements. That means that:

if (condition):
    # do something 

is the same as:

if condition:
    # do something

It is standard Python convention not to use parentheses in conditional statements as it adds "noise" when reading.

Coding Style

Global Variables

Please try to avoid global variables as much as possible! It makes the code difficult to read.

Compartmentalization

When thinking of a problem to solve, it is much better to start with a top-down approach. This means that your logic will be very easy to understand. For example, it would be nice if your main loop would contain only the high level functionality and the low level functionality be implemented elsewhere. Here's an example (pseudocode):

start a game

while game is not over:
    display game status
    get position from player
    process the turn based on the given position

display the result of the game

From there, you can implement those functions in a similar manner. This means that helper functions are your friends!

Human Usability

If you gave your program to a random person and asked them to play, would that person understand how to play? From a quick glance, it appears your program is pretty understandable, but just in case there is some person who has never played Tic-Tac-Tac before (seriously), you should make the program somewhat flexible.

Error Handling

Does your program "gracefully" handle bad input? It is common in Python to handle errors as such:

try:
    # attempt to do something
except Exception as e:
    # capture the error in variable "e" and gracefully handle the error

More Formatting

Comments

It makes the code easier to read if you have comments that explain high level abstractions.

Literal Formatting

Try to be consistent with how you format your code. For instance, in your code, sometimes you tab/indent with 4 spaces and sometimes with 8 spaces. Pick one (preferably 4 spaces or 2 spaces) and stick with it. Also, try to only use white space when it improves readability. For example:

def display_board(board):


    print'   |   | '
    # other stuff

should be:

def display_board(board):
    print '   |   | '

or could even be:

def display_board(board):
    """Displays a human-readable version of the given board"""
    print '   |   | '

The last example includes a docstring.

Code "Optimizations"

It would be much more natural to represent the tic-tac-toe board as a 2D grid rather than a 1D list.

Checking for a winner could be implemented with loops rather than hard-coding all of the possibilities.

Also, I personally think that it would have been easier to just build a TicTacToe class to deal with the game logic.

Other Remarks

I think you are well on your way to learning Python! Below is the code I came up with (didn't really test it much):

class TicTacToe:
  SIZE = 3
  PLAYERS = 2
  EMPTY = ' '
  horizontal_lines = '___|___|___'
  vertical_lines = '   |   | '
  bar = ' | '

  def __init__(self, size=SIZE):
    self._grid = [[TicTacToe.EMPTY for col in xrange(size)]
                  for row in xrange(size)]
    self._current_player = 0
    self._moves = 0

  @property
  def is_over(self):
    """Returns True if the game is over"""
    return self._moves == TicTacToe.SIZE * TicTacToe.SIZE or self.check_winner(
    ) is not None

  @property
  def current_player(self):
    """Returns the current player number"""
    return self._current_player

  def check_winner(self):
    """Returns the player number if there is a winner, otherwise returns None"""
    # check rows
    for row in self._grid:
      if len(set(row)) == 1 and row[0] != TicTacToe.EMPTY:
        return self._marker_to_player(row[0])

    # check columns
    for col_index in xrange(TicTacToe.SIZE):
      col = [
          self._grid[row_index][col_index]
          for row_index in xrange(TicTacToe.SIZE)
      ]
      if len(set(col)) == 1 and col[0] != TicTacToe.EMPTY:
        return self._marker_to_player(col[0])

    # check diagonals
    pos_diagonal = [
        self._grid[~index][index] for index in xrange(TicTacToe.SIZE)
    ]
    if len(set(pos_diagonal)) == 1 and pos_diagonal[0] != TicTacToe.EMPTY:
      return self._marker_to_player(self._grid[-TicTacToe.SIZE][0])

    neg_diagonal = [
        self._grid[index][index] for index in xrange(TicTacToe.SIZE)
    ]
    if len(set(neg_diagonal)) == 1 and neg_diagonal[0] != TicTacToe.EMPTY:
      return self._marker_to_player(self._grid[0][0])

    # return None if no winner

  def process_turn(self, position):
    """Processes turn and returns whether processing was successful"""
    row, col = position

    # check bounds
    if not (0 <= row < TicTacToe.SIZE and 0 <= col < TicTacToe.SIZE):
      return False

    # check if valid placement
    if self._grid[row][col] != TicTacToe.EMPTY:
      return False

    self._grid[row][col] = self._player_to_marker(self._current_player)

    self._current_player = (self._current_player + 1) % TicTacToe.PLAYERS
    self._moves += 1
    return True

  def _player_to_marker(self, player_number):
    """Converts player number into the string representation of the player"""
    if player_number == 0:
      return 'X'
    else:
      return 'O'

  def _marker_to_player(self, marker_string):
    """Converts marker string into the player number"""
    if marker_string == 'X':
      return 0
    else:
      return 1

  def __str__(self):
    lines = []

    # automate drawing
    for i, row in enumerate(self._grid):
      lines.append(TicTacToe.EMPTY + (TicTacToe.bar).join(row))
      if i < TicTacToe.SIZE - 1:
        lines.append(TicTacToe.horizontal_lines)
    lines.append(TicTacToe.vertical_lines)

    return '\n'.join(lines)


from time import sleep
SLEEP_TIME = 3

if __name__ == '__main__':
  game = TicTacToe()
  print 'Player 1 will be "X"\nPlayer 2 will be "O"\nBe Ready! ;)'
  sleep(SLEEP_TIME)

  while not game.is_over:
    print str(game)

    # continue until position is properly parsed
    correct_format = False
    while not correct_format:
      position = raw_input(
          'Player {}: Please provide the desired row and column (space '
          'separated)\n'.format(game.current_player + 1))
      try:
        correct_format = game.process_turn(tuple(map(int, position.split())))
      except:
        correct_format = False
      if not correct_format:
        print "Invalid query: '{}'".format(position)

  print str(game)

  # display winner
  winner = game.check_winner()
  if winner is None:
    print 'Wow! It is a Draw! ! ! :|'
  else:
    print 'Yipee! Player {} Has Won! ! ! :)'.format(winner + 1)
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.