(Developer: Vilayat Kleer)
- Project Goals
- User Experience
- User Stories
- Technical Design
- Technologies Used
- Features
- Testing
- Bugs
- Deployment
- Credits
- Acknowledgements
- Play a simple and fun game for two players
- Be able to create and log in to an account that keeps my score
- Creating a fun game with clear instructions and simple mechanics
- Provide clear feedback to the players so they never get stuck
- People who like Tic-Tac-Toe
- People who want to play a simple and fun game
- Casual gamers
- A simple, fun game that anyone can play
- Clear options for each menu or interaction
- Clear feedback provided when necessary
- Providing a personal touch through unique usernames
Click here for the user manual
When users start the program, they land on the 'Log in' menu. Above the menu, and on every other 'page' of the program except for the actual game, an ASCII logo of the game is shown. Below the logo the user is presented with three options. Operation: Input a numeric value and press the enter key.
- Log in
- Create new account
- Quit game
If the user at any point in the program or game enters a value that does not correspond to the options available, they will receive feedback of what they entered and are reminded of the available options. They will then be able to provide input again until a valid option has been chosen.
If option 1 is selected from the 'Log in' menu, the users will be asked to enter the email addresses they used to create an account. It will first ask player 1 to enter their email address and the player 2. It doesn't matter who logs in first as a random player will get the first turn each game.
Each email address is validated, first by checking if it follows the format of '[email protected]' and second by looking up the email address in the database (Google Sheets file). If the email address doesn't follow the correct format, users will be given feedback, e.g. 'It must have exactly one @-sign'. If they enter the correct format but the email address is not registered, they will be presented with two options. Operation: Input a numeric value and press the enter key.
- Try different email address
- Create new account
If option 1 is selected, the log in process is repeated. If option 2 is chosen, users will be able to create a new account.
If the log in was successful, the users will be taken to the 'Main' menu.
If option 2 is selected in either the 'Log in' menu or after a failed log in, users will be able to create a new account.
The first user, player 1, will be asked to provide a username for their account, which is validated: The username has to be between 2 to 20 characters long can only contain letters and digits - no special characters are allowed. After the validation, the username will be compared to the existing usernames in the database (Google Sheets file) and if a match is found, the player will be informed and has to provide a new username.
Then, player 1 will be asked to provide an email address for their account, which is also validated: the emaill address has to follow the format of '[email protected]'. After the validation, the email address will be compared to the existing email addresses in the database (Google Sheets file) and if a match is found, the player will be informed and has to provide a new email address.
After player 1 has created their account, the second user, player 2, will be presented with two options. Operation: Input a numeric value and press the enter key.
- Create new account
- Already have an account, go to log in menu
If option 1 is selected, player 2 will go through the same process as player 1 did, except that the player will be taken to the 'Log in' menu after their account is created. If option 2 is selected, they will be taken to the log in menu.
After player 1 and player 2 have logged in they land on the 'Main' menu. Here they will be presented with four options. Operation: Input a numeric value and press the enter key.
- Start the game
- Read game instructions
- View win count
- Log out
If option 2 is selected, the game instructions will be displayed. The players will be shown the Tic-Tac-Toe grid and explained how to place a mark down on the grid. After the instructions have been displayed, they will be taken back to the 'Main' menu. Operation: Input any key and press the enter key.
If option 3 is selected from the 'Main' menu, the win count of both players will be displayed. The players will then return to the 'Main' menu. Operation: Input any key and press the enter key.
If option 1 is selected from the 'Main' or 'Play again' menu, the players will be taken to the game. As mentioned before, a random player will be picked to have the first turn at the start of each game.
The player will be asked to select a row number, ranging from 1 to 5. Once a row number is selected, the player will be asked to select a column letter, ranging from A to E. The selected position will then be validated to see if the position is empty or if another mark is already in place. If there is another mark in place, the player will be informed about this and prompted to select a new row number and column letter. If the position is empty, the mark will be placed and the players' turn ends. Now the other player will be able to place a mark on the grid.
if the player provides invalid input, like entering a string as the row number, the player will receive feedback of what they've entered and informed what they did wrong. They will then be able to provide input again until a valid option has been chosen.
The game ends when one of the players has placed 4 consecutive, uninterrupted marks in a row, column or diagonal. Since the game has a 5x5 grid, there are multiple ways to achieve this. A message will be displayed to inform the players who won, displaying the winners' updated total amount of wins. The game then ends.
If neither players are able to secure a win before the grid is full, a message will be displayed to inform the players that the game is a tie. The game then ends.
After the game has ended, the players will be brought to the 'Play again' menu. Operation: Input any key and press the enter key.
After a game has ended, no matter the outcome, players will land on the 'Play again' menu. Here they will be presented with three options. Operation: Input a numeric value and press the enter key.
- Play again
- Main menu
- Log out
- Quit game
If option 4 is selected on the 'Main' menu or option 3 on the 'Play again' menu, the players will be logged out and taken to the 'Log in' menu.
If option 3 is selected on the 'log in' menu or option 4 on the 'Play again' menu, a thank you message will be printed and the program will exit.
- As a user, I want to be able to read the game instructions
- As a user, I want to be able to create an account
- As a user, I want to be able to log in with my account
- As a user, I want to be able to log out when I'm done
- As a user, I want to be able to quit the game
- As a user, I want to know how many games I've won
- As a user, I want to receive feedback during and after the game
- As a user, I want to be informed if I provide wrong input in the game
- As the website owner, I want the game to be fair and have a random player get the first turn on each game
- As the website owner, I want to provide feedback to users when they provide invalid input
- As the website owner, I want usernames and email addresses to be saved to a Google Sheets file
- As the website owner, I want users to be able to decide if they want to create one or two accounts during the registration process
A flowchart has been created to display the structure and logic of the program.
- Git was used for version control
- GitHub was used as a remote repository to store the all project files
- Gitpod was used as the IDE to write the project code
- Google Cloud was used to manage access to the Google Sheets and Google Drive API's using a service account key
- Google Sheets was used to store the players' account details
- Heroku was used to deploy the project
- PEP8 was used to validate my Python code
- LucidChart was used to create the flowchart
- os is used to clear the terminal
- sys is used to to exit the program
- random is used to change from player 1 to player 2 and vice versa
- unittest is used to create automated tests for player_validation.py
- patch from unittest.mock is used to to test my register_players and log_in functions from player_validation.py, which both require multiple inputs in a row to complete the test
- gpread is used to get, set and delete data in my Google Sheets file. The Google Sheets file contains the usernames, email addresses and win counts for each player that has registered.
- Credentials from google.oauth2.service_account is used for the authentification that's required to connect to my Google service account to the Google Drive and Google Sheets API. A service account key has been created in JSON with the name creds.json. The connection is set up in player_validation.py. To keep the creds.json file safe, it is put in the .gitignore file to prevent it from being pushed to GitHub. As the project is deployed on Heroku, the contents of the creds.json file have been stored in a Config Var inside the Heroku application settings to make the connection work.
- email_validator is used to validate the players' email address when entered on registration and log in. It must follow the format of '[email protected]'.
The website has a total of 9 main features:
- Logo created from ASCII characters
- Is present everywhere throughout the program except during the game to make room for the 5x5 grid
- Presents users with the option to log in, create an account or quit the game
- Validates the input, providing feedback if input was invalid. User will be prompted to try again if a non-existing option was selected.
- Covers user story 2, 3, 5 and 10
- Asks for a registered email address from player 1 and player 2, which must follow the format of '[email protected]'
- Validates the email address, providing feedback if input was invalid.
- If the input follows the correct format, compare the entered email address to the list of email addresses in the database (Google Sheets file)
- If the email address is not registered, present the player with the option to try a different email address or create a new account
- If the input follows the correct format, compare the entered email address to the list of email addresses in the database (Google Sheets file)
- After successfully logging in, each player is greeted by their username
- Covers user story 3 and 10
- Allows users to create a new account for the game
- Asks for a username, which has to be unique and can only contain letters or digits
- Asks for an email address, which also has to be unique and must follow the format of '[email protected]'
- Validates the username and email address, providing feedback if input was invalid. User will be prompted to enter a new username of email address if invalid
- After validation, the user will be asked to confirm their username and email address respectively. This is done to minimize user errors.
- Once player 1 has completed their registration, ask if player 2 wants to create a new account or go back to the log in menu
- Covers user story 2, 10, 11 and 12
- Presents users with the option to start the game, read the game instructions, view each players' win count or log out.
- Validates the input, providing feedback if input was invalid. User will be prompted to try again if a non-existing option was selected.
- Covers user story 1, 4, 6 and 10
- Displays the game instructions in a clear and easy to follow format
- Takes users back to the main menu after they've gone through the game instructions
- Covers user story 1
- Displays each players total amount of wins
- Takes players back to the main menu after entering any key and pressing the enter key
- Covers user story 6
- A random player is picked to have the first turn at the start of each game
- The players' name will be displayed if it's their turn
- The game grid is printed when the game starts and is updated after each turn
- Players are asked to select a row number and column letter to place a mark on the grid
- Validates the row number and column letter, providing feedback if input was invalid. User will be prompted to enter a new row number or column letter if invalid
- After a mark is placed, it will then be the other players' turn
- Provides feedback when a game is won or a tie.
- If a game is won, the player will receive a congratulatory message with their name and their new win count
- If the game is a tie, a message will be displayed to inform the players and take them back to the main menu
- Covers user story 7, 8, 9 and 10
- Presents users with the option to play again, to to main menu, log out or quit the game.
- Validates the input, providing feedback if input was invalid. User will be prompted to try again if a non-existing option was selected.
- Covers user story 4, 5 and 10
The Python code has been validated using Pep8 Validation Service - no errors or warnings were found.
- As a user, I want to be able to read the game instructions
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Game instructions | Go to 'Game instructions' using the main menu | See the game instructions | Works as expected |
- As a user, I want to be able to create an account
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Create new account | Go to 'Create new account' using the log in menu | Be able to create an account | Works as expected |
- As a user, I want to be able to log in with my account
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Log in | Go to 'Log in' using the log in menu | Be able to log in | Works as expected |
- As a user, I want to be able to log out when I'm done
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Main menu | Select option 4 on the main menu | Be logged out of our accounts and brought back to the log in menu | Works as expected |
Play again menu | Select option 3 on the play again menu | Be logged out of our accounts and brought back to the log in menu | Works as expected |
- As a user, I want to be able to quit the game
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Log in menu | Select option 3 on the log in menu | Exit the program | Works as expected |
Play again menu | Select option 4 on the play again menu | Exit the program | Works as expected |
- As a user, I want to know how many games I've won
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
View win count | Select option 3 on the log in menu | See each players' win count | Works as expected |
- As a user, I want to receive feedback during and after the game
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Game | Select option 1 on the main menu to start the game, then play until the a player wins | See each player name during and after the game | Works as expected |
- As a user, I want to be informed if I provide wrong input in the game
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Anywhere in the program | Enter invalid input | Receive feedback on the invalid input | Works as expected |
- As the website owner, I want the game to be fair and have a random player get the first turn on each game
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Game | Action | Expected result | Works as expected |
- As the website owner, I want to provide feedback to users when they provide invalid input
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Log in menu | Enter invalid input | Receive feedback on the invalid input | Works as expected |
Log in | Enter invalid input | Receive feedback on the invalid input | Works as expected |
Create new account | Enter invalid input | Receive feedback on the invalid input | Works as expected |
Main menu | Enter invalid input | Receive feedback on the invalid input | Works as expected |
Game | Enter invalid input | Receive feedback on the invalid input | Works as expected |
Play again menu | Enter invalid input | Receive feedback on the invalid input | Works as expected |
- As the website owner, I want usernames and email addresses to be saved to a Google Sheets file
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Create new account | Action | Expected result | Works as expected |
- As the website owner, I want users to be able to decide if they want to create one or two accounts during the registration process
Feature | Action | Expected Result | Actual Result |
---|---|---|---|
Create new account | Go to 'Create new account' using the log in menu and create one account to see the options | Be able to create a second account or go back to log in menu | Works as expected |
To challenge myself, I decided to incorporate automated testing into my project. I used the built-in unittest and unittest.mock libraries to achieve this. I tested multiple seperate functions.
The validate_player_username and validate_player_email functions inside my player_validation.py file do exactly what their names suggest: they take a username or email address as an argument and return True if the format is valid. To test these I wrote a couple of simple unit tests by providing both valid and invalid input.
If the test input is supposed to be valid, assertTrue is used, returning True in the statement - if the input was supposed to be invalid, assertEqual was used, returning None in the statement
The log_in function and register_players function inside my player_validation.py file required a more advanced method to be tested, since they both require multiple user inputs. This took me a while to get right, but in the end I solved it by importing patch from the unittest.mock library.
A patch decorator is used to access the built-in input function. The unit test is then declared, taking two parameters: self and mocked_input. The mocked_input is a Mock object - the side_effect function is called on this object, taking several string arguments. These arguments are iterated through when the unit test is called, providing input until the function has completed. Both functions use the same technique.
Since both functions don't return a value on completion, assertEqual is used, returning None in the statement.
The register_players function asks player 1 for a username, then a username confirmation, then an email address, then an email address confirmation. It will then ask player 2 if they want to create an account as well or if they want to go to the login page. In the unit test I decided to create two test accounts.
The log_in function asks for two email addresses to log in the players. After the test accounts have successfully logged in, the accounts are deleted using the delete_test_data function in my player_validation.py file.
Bug | Fix |
---|---|
Column input not validated correctly after ord function is called on it | Subtract 97 from the integer after the ord function is called instead of 96, to get an integer between 0 and 4 instead of 1 and 5. Indexing starts at 0, so this is important - link to commit |
TypeError is raised when entering a 0 into column input | Create an if statement inside a while loop to check if the input is equal to 0. If not, break out of the while loop - link to commit |
Wrong player is congratulated after winning game and an extra turn is given to this player | Create player_won function to check which player has won, calling it right after a mark is placed, thus congratulating the right player and ending the game - link to commit |
The game_instructions function doesn't work due to undefined object called grid | Create the undefined object before accessing its methods - link to commit |
The game_instructions function doesn't work due to undefined object called grid | Create the undefined object before accessing its methods - link to commit |
The update_score function does not update the score correctly | Define player_1_wins and player_2_wins variables as global variables - link to commit |
Players are sent to main menu after registering, crashing the game since they're not logged in | Set the correct argument for the menu function parameter - link to commit |
Players are asked to log in twice in a row | Remove log_in function call inside register_players function as it's already being called from another function - link to commit |
Username validation error does not show | Remove clear_screen function call after validate_player_username function is being called - link to commit |
Player 2 is forwarded to account creation when providing invalid input | Add while loop to check if invalid input is entered - link to commit |
Game crashes when entering a string longer than 1 character into column input | Add try-except statement to catch TypeError - link to commit |
Game doesn't display message after it ends in a tie | Add input function call to pause the program until input is provided, giving players time to read message - link to commit |
Game crashes due to IndexError after setting column input | Change if statement that checks if column input number is smaller than 0 or higher than 5 to check if number is smaller than 0 or higher than 4 - link to commit |
Able to select row number 0, which does not exist | Change if statement that checks if row input number is smaller than 0 or higher than 5 to check if number is smaller than 1 or higher than 5 - link to commit |
This website was deployed using Github Pages with the following steps:
- Go to your Github Repository
- Navigate to the 'Settings' page
- On the left hand menu under 'Code and automationo', click on 'Pages'
- Under 'Source', click on the 'Branch' dropdown element and set it to your main branch (in my case, this branch is called 'main')
- Click on 'Save'
- Refresh the page and you will be provided with a link to your deployed Github Page.
If you want to fork this repository, follow these steps:
- Go to the Github repository (https://github.com/vkleer/CI_PP1_TD)
- Click on the 'Fork' button in the top right corner under the navigation bar
If you want to clone this repository, follow these steps:
- Go to the Github repository (https://github.com/vkleer/CI_PP1_TD)
- Click on the 'Code' button above the list of files
- Select your preferred way of cloning, I recommend using the 'GitHub CLI' option
- Under 'GitHub CLI', click on the copy button to copy the clone command
- In you IDE, open Git Bash
- Navigate to the working directory where you want to clone this directory
- Paste in the clone command you copied and press the 'enter' key to create the clone
This application has been deployed using Heroku with the following steps:
- Login to Heroku
- Go to your Heroku dashboard
- In the top-right corner, click on the 'New' button, followed by the 'Create a new app' button
- Enter an app name (it has to be unique) and choose your region under the 'Choose a region' dropdown menu.
- Click on the 'Create app' button
- On the next page, click on the 'Settings' tab
- Under 'Config Vars', click on 'Reveal Config Vars' to add a new Config var - this is where you can store sensitive data, like your Google service account key
- Under 'Buildpacks' click on the 'Add buildpack' button to install additional dependencies. For this project the 'python' and 'nodejs' buildpacks were added, in that specific order
- Click on the 'Deploy' tab
- Under 'Deployment method', click on 'Github'. You can then search for your repository under 'Search for a repository to connect to'
- Click on the 'Connect' button to connect your repository
- On the next page, under 'Choose a branch to deploy' you can choose the branch you want to deploy your app from
- Either click on the 'Enable Automatic Deploys' button under 'Automatic deploys' to have the app deploy automatically on each push you make to the branch, or click on the 'Deploy Branch' button under 'Manual deploy'
- Wait for the app to build and be deployed. Once the app is ready, a message will be displayed saying 'App was successfully deployed' along with a button which takes you to your newly deployed app
- The git template IDE from Code Institute
- The connection to my Google Sheets file was implemented using the 'Love Sandwiches Walkthrough Project' by Code institute
- Not exactly code, but the game logo in ASCII characters was created using the Text to ASCII Art Generator by Patrick Gillespie
- The clear_screen function was implemented using a solution on Stackoverflow solution by user Vasiliy Rusin. It's not complicated, but I liked how simple the solution was
- The Python Reference on the ord() function by W3Schools helped me understand how to convert column letters into integers
- To convert the integers from the ord() function into integers from 0 to 4, this ASCII table helped me to calculate the difference
- I learned how to rotate a list of lists by 90 degrees by following a solution on Stackoverflow by user Padraic Cunningham, which saved me a couple of lines of code in my check_for_win method in my Grid class, found in my run.py file
- I learned how to stop Python from running the main function after importing a module using a solution on Stackoverflow by anonymous user user166390
- The unittest and unittest.mock library documentation from the official Python documentation helped me understand how to use the libraries for my automated testing
I would like to thank:
- My mentor Mo Shami for providing me with advice and guidance for this project
- My partner Lauren Baker for helping me with testing and finding multiple bugs in the program