Copyright © Jason Yang 2024 All right reserved.
<Python: Tic-Tac-Toe VS Computer>
Copyright © 2024 by Jason Yang is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
The best python code of Tic-Tac-Toe in the world**
Despite the code for enhancing the player experience, only use 65 lines can perform all its functions
The best computer logic of Tic-Tac-Toe in the world**
The command line interface is well-designed which is user-friendly
Extremely compact codes results in extremely high scalability
** You can read the core logic code
**Information collected from github.com & time node 2024/4/20 & Only discuss on the logic of the codes instead of user experience
- Write the CORE CODE
- Modify the print() message in the CORE CODE to become more user-friendly
- Restructre the code for Better readability and reduced iterations and wasted computing performance
- Add some extensions that make the game more interesting and further develop the use of cli (command-line interface) - terminal
This version already satisfy all the functions required in both basic and challenging requirement
- Initialization of the code
import random, time
- Game Board Initialization
- Set up a list record all steps in the game and record 1 - 9 respectively
## Step 1: Game Board Initialization TicTacToe_Table = [] for index in range(9): TicTacToe_Table.append(index + 1)
- Set up a list reacord all the valid steps for both computer and user to input and record the same information as list "TicTacToe_Table"
- Using
ValidInput = list(TicTacToe_Table)
instead ofValidInput = TicTacToe_Table
: In python, if we directly set a new list and equal to original list, it act like a pointer (指针) to the original list instead of assign(赋值) a new list. - Pointer: When the original list change, the pointer change correspond with the original list; pointer store nothing
# ValidInput = TicTacToe_Table ValidInput = list(TicTacToe_Table)
- Using
- Set up two new empty lists record the steps of user and computer respectively
UserInput = [] ComputerInput = []
- Better User experience
- We use
\033[...m
to change the colour and style of the text print in the ter
print("\033[1m╔════════════════════════════════════════════════╗") print("\033[1m║ Welcome to Tic Tac Toe Game! ║") print("\033[1m║\033[1;32m Let's play Tic-Tac-Toe against the computer! \033[1; 37m║") print("\033[1m╚════════════════════════════════════════════════╝\n") print("\033[0m┌────────────────────────────────────────────────┐") print("│ \033[1mIntroduction of the game:\033[0m │") print("│ You probably already know how to play Tic-Tac- │") print("│ Toe. It's a really simple game, right? That's │") print("│ what most people think. │") print("│ │") print("│ But if you really wrap your brain around it, │") print("│ you'll discover that Tic-Tac-Toe isn't quite │") print("│ as simple as you think! │") print("│ │") print("│ \033[1mRules of the Games:\033[0m │") print("│ 1. The game is played on a grid that's 3 │") print("│ squares. │") print("│ 2. \033[1;34mYou are X, \033[1;31mThe computer is ○. \033[0mPlayers take │") print("│ turns putting their marks in empty squares. │") print("│ 3. The first player to get 3 of her marks in a │") print("│ row (up, down, across, or diagonally) is │") print("│ the winner. │") print("│ 4. When all 9 squares are full, the game is │") print("│ over. If no player has 3 marks in a row, │") print("│ the game ends in a draw. │") print("└────────────────────────────────────────────────┘\n") print("┌────────────────────────────────────────────────┐") print("│ \033[1mDifficulty of the game:\033[0m │") print("│ 1. \033[1;32mNORMAL\033[0;32m: The Logic of Computer has mistakes,\033 [0m │") print("│ \033[0;32mit is not that hard to beat computer!\033[0m │") print("│ 2. \033[1;31mEXTREME\033[0;31m: The Logic of Computer Move is\033 [0m │") print("│ \033[0;31mperfect. player cannot win under this mode,\033[0m │") print("│ \033[0;31mplayer can try to get DRAW in the mode.\033[0m │") print("└────────────────────────────────────────────────┘\n")
- We use
- Set the Difficulty of the game
- Only under the Difficulty of NORMAL, player can have chance to beat the computer
- Under the Difficulty of EXTREME, the logic of computer is perfect and user will not win under this mode forever (that can be prove by enumerate all moves)
- Let player enter the difficulty (NORMAL OR EXTREME)
- When input Invalid choice, program will send a error handling and let player re-enter until player enter a correct choice
- The Input will store in new variables called "Difficulty"
- Version 1 has an invisible BUG: stack overflow: In Python, function calls are implemented as a stack, which is a data structure that takes a push operation whenever a function is called and a pop operation whenever the function returns.(在Python中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,相当于一次push压栈操作,每当函数返回,相当于一次pop出栈操作。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)
# Version 1: Contain Invisible BUG: stack overflow def EnterInput(): Difficulty = input("Choice Difficulty of the game (\033[1;32mNORMAL\033[0m / \033[1;31mEXTREME\033[0m): ") if Difficulty == "NORMAL" or Difficulty == "EXTREME": Entered_ValidInput = True else: print("\033[1;31mPlease enter a valid input! \033[0m(\033[1;32mNORMAL\033[0m / \033[1;31mEXTREME\033[0m)") EnterInput()
# Version 2: DEBUG Entered_ValidInput = False while Entered_ValidInput is False: Difficulty = input("Choice Difficulty of the game (\033[1;32mNORMAL\033[0m / \033[1;31mEXTREME\033[0m): ") if Difficulty == "NORMAL" or Difficulty == "EXTREME": Entered_ValidInput = True else: print("\033[1;31mPlease enter a valid input! \033[0m(\033[1;32mNORMAL\033[0m / \033[1;31mEXTREME\033[0m)")
- Setup a GAME LOOP
while True: ...
- Display information
- Display the Tic-Tac-Toe Table and the valid input
- In version 2 (more user-friendly)
# Version 1 Core coding ## Step 2: Game Board Display print(TicTacToe_Table) ## Step 3: Check all valid moves print(ValidInput)
# Version 2 More User-friendly def DisplayGameBoard(): global TicTacToe_Table print("\n┌───┬───┬───┐") print("│", TicTacToe_Table[0], "│", TicTacToe_Table[1], "│", TicTacToe_Table[2], "│") print("├───┼───┼───┤") print("│", TicTacToe_Table[3], "│", TicTacToe_Table[4], "│", TicTacToe_Table[5], "│") print("├───┼───┼───┤") print("│", TicTacToe_Table[6], "│", TicTacToe_Table[7], "│", TicTacToe_Table[8], "│") print("└───┴───┴───┘") def DisplayValidInput(): global ValidInput print("Your Valid Move: ", end="") for index in range(len(ValidInput)): if index != len(ValidInput) - 1: print(str(ValidInput[index]) + ", ", end="") else: print(ValidInput[index]) ## Step 2: Game Board Display DisplayGameBoard() ## Step 3: Check all valid moves DisplayValidInput() ## OUTPUT: ## ┌───┬───┬───┐ ## │ 1 │ 2 │ 3 │ ## ├───┼───┼───┤ ## │ 4 │ 5 │ 6 │ ## ├───┼───┼───┤ ## │ 7 │ 8 │ 9 │ ## └───┴───┴───┘ ## Your Valid Move: 1, 2, 3, 4, 5, 6, 7, 8, 9
- Player Take Move
- Wait Player input a valid move, code in the same logic with entering the Difficulty
- When input Invalid choice, program will send a error handling and let player re-enter until player enter a correct choice
Entered_ValidInput = False while Entered_ValidInput == False: UserMove = input("\033[0mTake your move: ") if not UserMove.isdigit(): print("\033[1;31mPlease enter a valid input! You should only enter number instead of other characters. The number represent the the move that is available in the table! ", end = "") DisplayValidInput() elif int(UserMove) not in ValidInput: print("\033[1;31mPlease enter a valid input! You should only take the move that is available in the table! ", end = "") DisplayValidInput() else: Entered_ValidInput = True
- Processing the input of Player
- Set the number in Tic-Tae-Toe Table become "X"
- Remove this move from the list ValidInput and add it into the list of UserInput
TicTacToe_Table[int(UserMove) - 1] = "X" ValidInput.remove(int(UserMove)) UserInput.append(int(UserMove))
- Check the Player Win and Draw condition
- First setup a dictionary recorded all win condition
- If there is no more valid move => the length of ValidInput == 0, then display the game board, send the message of DRAW!!! and exit the program.
- Set a loop check through all possible winning condition in the Dict_WinCondition list.
Dict_WinCondition = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7],\ [2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7]] for index in range(len(Dict_WinCondition)): if len([x for x in UserInput if x in Dict_WinCondition[index]]) == 3: DisplayGameBoard() print("YOU WIN!!!") exit() ## Expand form of the if condition: list = [] for x in UserInput: if x in Dict_WinCondition[index]: list.append(x) if len(list) == 3: ...
- Computer Take Move
- Set a function for computer to take move and check the Computer Win condition
- Set the number in Tic-Tae-Toe Table become "○"
- Remove this move from the list ValidInput and add it into the list of ComputerInput
def ComputerMove(number): TicTacToe_Table[number - 1] = "○" ValidInput.remove(number) ComputerInput.append(number)
- Set a loop check through all possible winning condition in the Dict_WinCondition list.
for index in range(len(Dict_WinCondition)): if len([x for x in ComputerInput if x in Dict_WinCondition[index]]) == 3: DisplayGameBoard() print("COMPUTER WIN!!!") exit()
- Logic of Computer Move
- Explanation of Perfect Logic (Difficulty == EXTREME)
The following part solve the easiest part of the logic- When it is the first step of computer and user did not take the move of 5, computer must take move of 5
if len(ValidInput) == 8 and 5 in ValidInput: ComputerMove(5)
- When it is the first step of computer and user took the move of 5, computer will take move in random choice of 1, 3, 7, 9 (The four corner of the table)
The following part solve the difficult condition of the logicelif len(ValidInput) == 8 and 5 not in ValidInput: ComputerMove(random.choice([1, 3, 7, 9]))
- Set a new Variable Moved_TF with type of Boole and initialize it to False, it use to check whether the logic find a perfect move, if the move is founded, the subsequent codes will and back to the start fo the GameLoop (continue)
Moved_TF = False ... if Moved_TF == True: continue
- Set a loop check through the number of both UserInput and ComputerInput all winning condition in the Dict_WinCondition list.
- If that number of UserInput and ComputerInput equal to 0 and 2 respectively, computer move in last space and it will win
for index in range(len(Dict_WinCondition)): if [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] == [0, 2]: Moved_TF = True ComputerMove([x for x in Dict_WinCondition[index] if x not in ComputerInput][0]) break
- If that number of UserInput and ComputerInput equal to 2 and 0 respectively, computer move in last space and it can block player to win
for index in range(len(Dict_WinCondition)): if [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] == [2, 0]: Moved_TF = True ComputerMove([x for x in Dict_WinCondition[index] if x not in UserInput][0]) break
- Finally, if still cannot find a perfect move, computer will random choice a move
- Modification: Make the logic for the difficulty of NORMAL
- Computer will not take the step of 1, 3, 5, 7, 9 as its first move
- Modification: Improvement of the Logic
- Final Version: Best Logic and using the least CPU computing power
It only contain 13 lines but can perform in a perfect logic and under EXTREME mode the user will not win forever (**The codes of performing logic of NORMAL mode is not included in the "13 lines")
if len(ValidInput) == 8 and Difficulty == "NORMAL": ComputerMove(random.choice([x for x in [2, 4, 6, 8] if x not in UserInput])) elif len(ValidInput) == 8 and 5 in ValidInput and Difficulty == "EXTREME": ComputerMove(5) elif len(ValidInput) == 8 and 5 not in ValidInput and Difficulty == "EXTREME": ComputerMove(random.choice([1, 3, 7, 9])) else: for index in range(8): CheckList = [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] PriorityMove = [x for x in Dict_WinCondition[index] if x not in ComputerInput and x not in [1, 3, 7, 9]] if CheckList == [0, 2]: ComputerMove([x for x in Dict_WinCondition[index] if x not in ComputerInput][0]) break elif CheckList == [2, 0]: ComputerMove([x for x in Dict_WinCondition[index] if x not in UserInput][0]) break elif CheckList == [0, 1] and PriorityMove: ComputerMove(random.choice(PriorityMove)) break elif index == 7: ComputerMove(random.choice(ValidInput))
- Version 1: NO BUG with normal readability
if len(ValidInput) == 8 and 5 in ValidInput and Difficulty == "EXTREME": ComputerMove(5) elif len(ValidInput) == 8 and 5 not in ValidInput: if Difficulty == "NORMAL": ComputerMove(random.choice([2, 4, 6, 8])) elif Difficulty == "EXTREME": ComputerMove(random.choice([1, 3, 7, 9])) else: Moved_TF = False for index in range(len(Dict_WinCondition)): if [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] == [0, 2]: Moved_TF = True ComputerMove([x for x in Dict_WinCondition[index] if x not in ComputerInput][0]) break if Moved_TF == True: continue for index in range(len(Dict_WinCondition)): if [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] == [2, 0]: Moved_TF = True ComputerMove([x for x in Dict_WinCondition[index] if x not in UserInput][0]) break if Moved_TF == True: continue for index in range(len(Dict_WinCondition)): if [len([x for x in UserInput if x in Dict_WinCondition[index]]), len([x for x in ComputerInput if x in Dict_WinCondition[index]])] == [0, 1]: ValidMove = [x for x in Dict_WinCondition[index] if x not in ComputerInput] if len(ValidMove) == 1: ComputerMove(ValidMove[0]) else: ComputerMove(ValidMove[random.choice([0, 1])]) Moved_TF = True break if Moved_TF == True: continue ComputerMove(random.choice(ValidInput))
- Beta Version: With some BUG and poor readability
## Beta Version for index in range(len(Dict_WinMove)): match_winmove = 0 for index_s in range(0, 3): if Dict_WinMove[index][index_s] not in ValidInput and TicTacToe_Table[Dict_WinMove[index][index_s] - 1] == "X": match_winmove += 1 print(index, match_winmove) if match_winmove == 2: print(True) for index_s in range(0, 3): if Dict_WinMove[index][index_s] in ValidInput: TicTacToe_Table[Dict_WinMove[index][index_s] - 1] = "O" solve = True break else: solve = False if solve == True: break if solve = false
- Final Version: Best Logic and using the least CPU computing power
- Explanation of Perfect Logic (Difficulty == EXTREME)