Learn Python With Dhawal - 19 - Creating a CLI Based Tic Tac Toe Multiplayer Game


In the previous chapter, we learnt about writing python programs ending with a '.py' extension. Later, executing the python file on a terminal. Here we will learn making a CLI based Game and hence forth all the chapters will be on the same format. We won't be using the Jupyter Notebook ahead.

For application development, there are several IDEs which I had mentioned in the first chapter. You can download your favourite option of the IDE from the Internet. Some of those are PyCharm, IntellIJ, Sypder, etc. There are some glorified text editors or modular IDEs as well like Visual Studio Code, Notepad++, etc. You can use any method to create your python script. You can also follow the method shown in the previous chapter aka texting using notepad or IDLE ( if you have installed the normal python distribution ) and then running the code in the terminal using the python script run command.

I'd be using Visual Studio Code for this purpose as it's quite light weight and works perfectly fine with multiple things needed for development. You can install Visual Studio Code by clicking here. When you are done with installing, open the vscode and then create a new file and save it with extension '.py '.  If your using anaconda, I'd suggest you launch vscode from your anaconda navigator menu after installing. In that way you won't have to worry about python access issues ( errors like path not found, python not found, etc).

Once you're done with things in the setup, we can begin the chapter here on. I'd advise you to understand the IDE that you'd be using regarding writing code, making changes as well as running it on the terminal. If you feel uneasy you can just write this code inside a notepad like previous exercise and then run it on your daily terminal.


Understanding The Game - Problem Statement

Tic-tac-toe is a game played either with two players or mostly player against the computer. Here we are trying to make a two player game. Players can choose the symbol of their own choice. Once it's done, turn by turn let each player play the game. The player board will be of size 3x3.

Now, here comes the tricky part. Ensure that none of the player overrides other player's mark on the board. Check the condition for win or lose after every player input. Also, check the condition for the tie as well as a condition where the board completely fills up where none of the players are able to win or lose anymore.

We can make a function for the win condition where the function would check all the possible win conditions and return a confirmation. There are also some basic things to take care of like changing players after each input as well as ensuring that after some errand input there are no bugs.

Import Statements

First there are some things needed to be imported that will help us improve the UI experience for the CLI based game.
import os
from os import system,name
from time import sleep
We are opting to use the 'system' and the 'name' library modules from 'os' to find out the type of system this program would be running on aka windows, mac or linux. Another library that we are importing is the 'time' library 'module' sleep. Both of these imports don't have any significant effect on our game building logic. These are imported for a cleaner game play visualisation.

Functions

First we will create all the functions that we'd need to make our game logic work. Before coding, try to write down the things which you might need to do for this program and try making each action as a separate function.

def clearScreenOutput():
    #sleep(5)
    if name=='nt':
        os.system('cls')
    else:
        os.system('clear')

This method clearScreenOutput takes no parameters. There are a few steps which are commented as I thought of them as the options that you can write in your code or just skip them. The sleep() is a method which is used to make the execution 'sleep' or wait in a sense for some time. The time needed to sleep or wait is passed as an argument inside the parentheses.The sleep(5) statement would make the code execution pause for 5 seconds. The use for this you'd understand as we write the code ahead.

Next, there's an if condition which checks whether the system is windows or mac/linux. If the name is 'nt' then it'll be a windows machine else it'll be a mac or linux machine. We need to check the machine type as we needed to know what type of clear screen command to use. In windows terminal to clear the screen we use the command 'cls' while on the mac/linux the command is 'clear'. Hence, we execute this command using a method os.system() and inside the parentheses we pass the command 'cls' or 'clear' as the argument.

This function will wait, if the for a mentioned time period before cleaning the screen if the sleep command is used or else just directly clear the screen output when called upon.

def checkBoardFill():
    fill = 0
    for i in range(3):
        for j in range(3):
            if board[i][j]==player1 or board[i][j]==player2:
                    fill=fill+1
    
    if fill==9:
        return True
    else:
        return False
Next function is checkBoardFill, this function will check if the board is filled and will return the status. First we initialise the fill variable as 0. It will be acting as a counter variable for us. Next, we start traversing the matrix of tic tac toe board that we have created. This is a double for loop setup where the first for loop will run for all the rows and second for loop will be running for all the columns. 

In this way, we would be checking each and every place inside the check board. The if condition inside the nested for loop checks if any of the place has players' symbol. If present then the counter fill is increased by 1 else nothing is done. At the end, we check if the number of filled places are equal to 9, meaning all the places inside the tic tac toe board.

def showBoard(board):
    for i in range(3):
        print("----------\n|",end="")
        for j in range(3):
            print(board[i][j], end=" |")
        print("")
    print("----------\n")
Next comes the showBoard function. This will simply print the board on the terminal. It takes a board nested list of 3x3  or matrix you can say, as an input. This is not needed but we are using it to just make the game look fancy. It's your usual print statement inside the nested for loop running for all rows and columns across the board size. Here we are using end attribute of the print method. This attribute specifies what should a printing statement end with. Usually, it ends with the '\n' or next line instruction but we can specify it to use something else like blank space or ' |' as shown in above. We used end="" as to specify to end with nothing.

To understand this way of printing, you can copy paste and try running a seperate code to know how this thing functions. You can try various other print with other things as well. Next, we  will create the main function which will serve has the heart of our game.

def playOnBoard(boardIndexHelp,player):
    notPlayed = True
    while (notPlayed):
        
        print("Reference Board for Index Position : ")
        showBoard(board_help)
        pos=int(input("Enter the position where you'd like to enter the symbol : "))
        clearScreenOutput()
        a=boardIndexHelp[pos]
        try:
            if((board[a[0]][a[1]]!=player1 and board[a[0]][a[1]]==' ') or (board[a[0]][a[1]]!=player2 and board[a[0]][a[1]]==' ')):
                board[a[0]][a[1]]=player
                notPlayed = False
                print("The Current Board Status is : ")
                showBoard(board)
                print("\n--------------------------------------------------\n")
            else :
                print("That position is already occupied,Enter a valid position")
                notPlayed=True
        except Exception as e :
            print("Enter a valid position \n The Error is : "+e)
            notPlayed=True
The playOnBoard  method will take two parameters to function. First one is the boardIndexHelp which will be a dictionary to aid our user in giving input to the game. Second parameter is the player which will hold the identity of the player who will be playing on the board. When passed both the arguments while calling the function, the first step would be initialising a variable notPlayed which will hold the player's playing status.

Next we print out the reference board and ask the user's input. As you can see after taking the input there's a function call for cleaning the screen. Hence now when the next time a user gives input or some output is shown the screen will be clear of all hindrances and garbage output of previous played moves.

Once the user input is taken followed by clearing the output on the terminal, find out the index of the position the user has entered. The boardIndexHelp contains all the positions and it's values inside our play matrix. So for example, when a user enters '1' as input, a = boardIndexHelp[1] will return the value [0][0]. All such indices are predefined in the dictionary which we will be creating later in the code. 

Next all of the statements are placed inside the try and except blocks. This is done to ensure the code gives a proper output even when the user gives an erratic input. This will let the user play incase of any wrong input as well without losing a chance or game coming to an abrupt halt.

Next, inside the try block there is a pair of if and else. Here the if condition checks that the user's input is not overriding any other input and there's blank position before entering the user's input. If everything works fine, then the playing status is changed and the function ends. If the user entered a position where there's already his or the other player's mark then the control of the code execution switches to the else block.

Inside the else block,  we print the error and ask user to play again. This time we ensure that the player's status is not played. As we are running a while loop which will go on until the entering condition becomes false. It means the main while loop will run until the player has played successfully.

Following the try block, except  block  just takes the exception and prints it and keeps the user's status as not played as the user was unable to play due to erratic input. Hence, even if any exception occurs, the user will be allowed to play once again to give a proper input.

def lineCheck():
    ## This will check if the winning conditions are true.
    if (board[0][0]==board[0][1] and board[0][1]==board[0][2]and board[0][0]==board[0][2] and board[0][2]!=' '):
        return False
    elif (board[1][0]==board[1][1] and board[1][1]==board[1][2] and board[1][0]==board[1][2] and board[1][2]!=' '):
        return False
    elif (board[2][0]==board[2][1] and board[2][1]==board[2][2] and board[2][0]==board[2][2] and board[2][2]!=' '):
        return False
    elif (board[0][0]==board[1][0] and board[1][0]==board[2][0] and board[0][0]==board[2][0] and board[2][0]!=' '):
        return False
    elif (board[0][1]==board[1][1] and board[1][1]==board[2][1] and board[0][1]==board[2][1] and board[2][1]!=' '):
        return False
    elif (board[0][2]==board[1][2] and board[1][2]==board[2][2] and board[0][2]==board[2][2] and board[2][2]!=' '):
        return False
    elif (board[0][0]==board[1][1] and board[1][1]==board[2][2] and board[0][0]==board[2][2] and board[2][2]!=' '):
        return False
    elif (board[2][0]==board[1][1] and board[1][1]==board[0][2] and board[2][0]==board[0][2] and board[0][2]!=' '):
        return False
    else:
        return True
This is a lineCheck function which just checks the winning conditions. This function is like the brain of our game's code. We aren't passing any parameters here. If you see the function checks the conditions using an if...else nesting. If any of the winning conditions are true, then it'll return 'false' otherwise 'true'. This is a boolean flag, the reason for which you'd know further in the code when we write the main code. If there's a difficulty in understanding the winning conditions just imagine a 3x3 matrix and try writing indices of a line occuring in the matrix.

Main Method or the Main Game Play.

board=[[' ',' ',' '],[' ',' ',' '],[' ',' ',' ']]
Here, we initialise an empty matrix as our playing board.

board_help=[['1','2','3'],['4','5','6'],['7','8','9']]

Next, we create a helper board with all the index numbers. This board will help the user to enter the input position at which the user wants to place their symbol.

player1=input("Enter the Symbol you want to use for player1 : ") 
player2=input("Enter the Symbol you want to use for player2 : ")

Here we take input Symbols of both our Players. This is again to make things fancier, if you want to just use the plain standard X and O as the symbols then you can skip these two steps and instead write these two steps mentioned below.

player1='X'
player2='O'
Next, we print the choices of the Symbols that both the players have opted for using this statement.

print("Player 1 will use : "+player1+" and Player 2 will use : "+player2)
clearScreenOutput()
Following which we clear the screen as well calling out clearScreenOutput function once again.

boardIndexHelp={1:[0,0],2:[0,1],3:[0,2],
      4:[1,0],5:[1,1],6:[1,2],
      7:[2,0],8:[2,1],9:[2,2]}
print("The Game Board is initialised")
showBoard(board)
In the further steps, we initialise the dictionary boardIndexHelp with all the positions and the indices in the main matrix they will correspond to, as when the user gives the input it can be translated into the matrix position using this dictionary. After all the initialisation steps are over, we print confirmation and then show the initial empty board by calling out showBoard function and passing out main board as the argument for the parameter.

line = True
previousPlayer=player2

These two steps are the most important steps for our main game play code. Here we initialised a boolean variable that we are going to use as a flag to run our code. Here the line  is initialised to 'True' and the previousPlayer variable holds the identity of player 2 since we will begin playing with the player 1.

while(line):
    
    if previousPlayer==player2:
        print("\nPLAY : PLAYER "+player1)
        playOnBoard(boardIndexHelp,player1)
        previousPlayer=player1
        line = lineCheck()
        if line==False:
            print("The Winner is : "+player1)
    else:
        print("\nPLAY : PLAYER "+player2)
        playOnBoard(boardIndexHelp,player2)
        line = lineCheck()
        previousPlayer=player2
        if line==False:
            print("The Winner is : "+player2)
    tie = checkBoardFill()
    if (tie == True and line == True):
        print("Game Over it's a tie")
        line=False
This is the Main while loop. This loop will run until and unless a line is found inside the matrix, which means someone win's the game or else there's a tie. Here there's a block of if...else which decides who plays next. If our previous player is player 2, then player 1 will play and otherwise it'll be vice versa. Hence for this reason we had initialised the previousPlayer variable. Both the if and else block are identical in terms of code with just change of player identities.

If a player is chosen, first we print a statement asking that particular player to play. Then we call the playOnBoard method by passing the player identity and boardIndexHelp as arguments. In the next step we change the value of the previousPlayer to current one as the current player has already played his turn. Following which we call the lineCheck function and store the result which it'll return inside the line  variable which we had previously initialised. If the line  returns 'False' which means one of the winning conditions has passed we will print the Winning Statement.

As mentioned earlier the else block is identical to the if block just only difference is the player who'd be playing next. Now, coming on to the last portion which is the tie condition. We have initialised a variable tie inside the loop. This variable will store the result of the checkBoardFill method after we have called it. If the result returned is 'True' which will mean that the board is now full and there are no more spaces to be filled. it'll be a tie. Hence the line variable will be made 'False' and tie statement is printed.

But if you notice the if condition that is checking the tie condition. You'd notice that we have mentioned two conditions for declaring a tie. Tie condition will be checked only when there's no winner from the if...else playing block that we were running with the loop.

I hope you enjoyed this little exercise and I'd love to see if you can find some bugs in it or optimise the code further. Debugging the code will only increase your understanding of how each statement in the code works as well as it will help you find errors in your own code.



<< PREVIOUS CHAPTER || NEXT CHAPTER >> --INDEX--


~*~*~*~

This series is totally authored by me (Dhawal Joshi). Any similarities found on the text, or codes or anything is purely accidental. All the sources of reference will be mentioned, linked and will be given the proper credits. If I miss anything or there's anything wrong, feel free to comment or send me an email and I'll try to edit it out. I am not a Python expert, I am sharing whatever I have learnt on my own and with a few sources around to refer from which will be mentioned. Also feel free to share this series with others so most can benefit out of it.

~*~*~*~

Do comment and share your thoughts about it! I'd love to know what do you think. Also, I'd keep updating it quite often so do follow the Website to get all the updates by clicking here.

Also, a minor headsup.... Obsessed is free to read on Kindle Unlimited! Do check it out. I'd be glad to read your reviews!
Share :

Are you getting regular fresh content updates? If not click on the button below.

0 comments:

Post a Comment

Do comment and share your thoughts.