Skip to content

Instantly share code, notes, and snippets.

@Anonym0usWork1221
Created December 9, 2023 15:25
Show Gist options
  • Save Anonym0usWork1221/a0966f7e73671a59ef148e2766a5c88d to your computer and use it in GitHub Desktop.
Save Anonym0usWork1221/a0966f7e73671a59ef148e2766a5c88d to your computer and use it in GitHub Desktop.
Tic-Tac-Toe game using alpha beata pruning algorithm.
"""
Tic-Tac-Toe is implemented as a game in this Python programme, which was created by
Abdul Moez (Roll No: 0387-BSCS-20-E1) from Section E1.
For AI gameplay, the programme uses the Alpha-Beta-Pruning algorithm. It is
provided to Sir Nazim and made available with an MIT licence.
Features:
The game can accommodate any game board size, as determined by a NxN matrix. It is a console-based
game that offers an engaging playing experience. The code is clean and maintainable since it complies with best
practises. Functions are described to improve the readability and comprehension of the code.
MIT Licence: The MIT licence is a permissive open-source licence that places few limits on users'
freedom to use, alter, and distribute the software.
"""
from random import choice
from time import sleep
from os import system
from math import inf
import msvcrt
# Global Matrix Size
MATRIX = 2
class Game:
"""
represents a game of tic-tac-toe.
Attributes:
The size of the game board is indicated by the integer _matrix (int).
a list that is 2D and serves as the gaming board.
Methods:
__init__(self): Creates the Game object from scratch.
Makes a move on the game board with the parameters self, row, col, and ai=True.
Checks whether the game has a winner by calling check_winner(self).
is_board_full(self) determines whether the game board is full.
display_board(self): Shows the game board's current status.
"""
def __init__(self) -> None:
"""
The Game object at creation.
It creates blank spots on the game board.
Returns: None
"""
self._matrix = MATRIX
self.board = [[' ' for _ in range(self._matrix)] for _ in range(self._matrix)]
def make_move(self, row: int, col: int, AI: bool = True) -> bool:
"""
on the game board, moves.
The programme assigns "O" or "X" based on the player and determines if the designated position is unoccupied.
The game board is updated if the move is legal.
Row (int): The move's row index is one of the arguments.
col (int): The move's column index.
Determines whether an AI made the manoeuvre (bool). As a default, True.
Returns a bool value of True if the relocation was successful and valid, and False otherwise.
"""
if self.board[row][col] == ' ':
if AI:
self.board[row][col] = "O"
else:
self.board[row][col] = "X"
return True
return False
def check_winner(self) -> any([str, None]):
"""
determines if the game has a winner.
To determine if there is a winning sequence, it repeatedly iterates across rows, columns, and diagonals.
Returns: str or None: "O" if player O wins, "X" if player X wins, or None if no winner has been
determined yet.
"""
for i in range(self._matrix):
if all(self.board[i][j] == self.board[i][0] != ' ' for j in range(1, self._matrix)):
return self.board[i][0]
if all(self.board[j][i] == self.board[0][i] != ' ' for j in range(1, self._matrix)):
return self.board[0][i]
if all(self.board[i][i] == self.board[0][0] != ' ' for i in range(1, self._matrix)):
return self.board[0][0]
if all(self.board[i][self._matrix - i - 1] == self.board[0][self._matrix - 1] !=
' ' for i in range(1, self._matrix)):
return self.board[0][self._matrix - 1]
return None
def is_board_full(self) -> bool:
"""
evaluates whether the game board is full.
It checks to see if the game board still has any open spots.
Returns a bool value that is either True or False depending on whether the game board is full.
"""
for row in self.board:
if ' ' in row:
return False
return True
def display_board(self) -> None:
"""
shows the game board's current condition.
The game board is printed together with row and column separators.
Returns: None
"""
print("\n")
for row in range(len(self.board)):
print(" " + " | ".join(self.board[row]))
sleep(.1)
if row < len(self.board) - 1:
print("----" * self._matrix)
print("\n")
sleep(1)
def evaluate_state(game: Game) -> int:
"""
Analyse the game's present situation.
Args: game: A Tic-Tac-Toe game representation in the form of a Game class instance.
returns the evaluation score for the current game state as an int.
One if "O" has triumphed, one if "X" has, and zero if the game is still in progress.
"""
winner = game.check_winner()
if winner == 'O':
return 1
elif winner == 'X':
return -1
else:
return 0
def minmax(game: Game, depth: int, maximizing_player: bool, alpha: float, beta: float) -> int:
"""
Find the best move for the AI player using the Minimax algorithm.
Args: game: A Tic-Tac-Toe game representation in the form of a Game class instance.
depth (int): The Minimax search's current depth.
player_maximization (bool): If the current player is the AI player ('O'), then the answer is true;
otherwise, the answer is false.
The alpha value for alpha-beta pruning is alpha (float).
beta (float): The alpha-beta pruning beta value.
The evaluation score for the player's current best move is returned as an integer.
The evaluate_state function serves as the foundation for the evaluation score.
"""
if depth == 0 or game.check_winner() is not None or game.is_board_full():
return evaluate_state(game)
if maximizing_player:
max_eval = -inf
for i in range(MATRIX):
for j in range(MATRIX):
if game.board[i][j] == ' ':
game.make_move(i, j, AI=False) # as it is max
evaluation = minmax(game, depth - 1, False, alpha, beta)
game.board[i][j] = ' '
max_eval = max(max_eval, evaluation)
alpha = max(alpha, max_eval)
if beta <= alpha:
break
else:
continue
break
return int(max_eval)
else:
min_eval = inf
for i in range(MATRIX):
for j in range(MATRIX):
if game.board[i][j] == ' ':
game.make_move(i, j, AI=True)
evaluation = minmax(game, depth - 1, True, alpha, beta)
game.board[i][j] = ' '
min_eval = min(min_eval, evaluation)
beta = min(beta, min_eval)
if beta <= alpha:
break
else:
continue
break
return int(min_eval)
def find_best_move(game: Game, depth: int) -> tuple[int, int]:
"""
Utilise the Minimax algorithm to determine the AI player's optimal move.
Args: game: A Tic-Tac-Toe game representation in the form of a Game class instance.
depth (int): The Minimax algorithm's maximum search depth.
The row and column indices of the AI player's best move are returned as a tuple.
The results of the Minimax evaluation are used to select the optimum move.
"""
best_eval = -inf
best_moves = []
for i in range(MATRIX):
for j in range(MATRIX):
if game.board[i][j] == ' ':
game.make_move(i, j, AI=False) # check for User wining
if game.check_winner() == 'X':
# Block the user from winning
game.board[i][j] = 'O'
return i, j
game.board[i][j] = ' '
evaluation = minmax(game, depth, False, -inf, inf)
game.board[i][j] = ' '
if evaluation == 1:
return i, j
game.make_move(i, j, AI=False) # set the opponent move to check
opponent_win = evaluate_state(game) == -1
game.board[i][j] = ' '
if opponent_win:
continue
if evaluation > best_eval:
best_eval = evaluation
best_moves = [(i, j)]
elif evaluation == best_eval:
best_moves.append((i, j))
return choice(best_moves)
def begin_game() -> None:
"""
Clear the console screen after printing the intro text.
It should be noted that this function is in charge of showing the game's initial setup.
"""
system("cls")
begin_text = "Let's begin the game"
print("\033[5m")
for char in begin_text:
print(char, end="", flush=True)
sleep(.05)
sleep(1)
for _ in begin_text:
print("\b \b", end="", flush=True)
sleep(.05)
print("\033[0m")
# Do not take any input keys until the program reaches on game portion
while msvcrt.kbhit():
msvcrt.getch()
def validate_input(prompt: str) -> int:
"""
Verify the row and column numbers entered by the user.
Args: prompt (str): The input prompt that will be shown to the user.
Returns: int: The input value that has been verified and falls within the acceptable range.
Note: This function makes sure that the user provides an integer that is valid and falls inside the
allowed range.
"""
while True:
try:
value = int(input(prompt))
if 0 <= value <= MATRIX - 1:
return value
else:
print(f"[Enter Error] Please type a value between 0 and {MATRIX - 1}.")
except ValueError:
print("[Enter Error] Please type a real number.")
def input_handler(game: Game) -> tuple[int, int]:
"""
Row and column numbers should be handled by the user, and the entered values should be verified.
Args: game: A Tic-Tac-Toe game representation in the form of a Game class instance.
returns a tuple containing the user-inputted row and column indices.
Note: The user is prompted to enter row and column numbers, and the function validates the input.
"""
row = validate_input(f'Enter the row (0-{MATRIX - 1}): ')
col = validate_input(f'Enter the column (0-{MATRIX - 1}): ')
while game.board[row][col] != " ":
print("[Space Error] Entered path is already filled try again")
row = validate_input(f'Enter the row (0-{MATRIX - 1}): ')
col = validate_input(f'Enter the column (0-{MATRIX - 1}): ')
return row, col
def play_game(depth: int) -> None:
"""
Play the game of tic-tac-toe to begin.
depth (int) is the greatest depth to which the Minimax algorithm will search.
The game loop is managed by this function, and the game board is shown.
"""
begin_game()
game = Game()
while True:
print(f"YOU=X\tAI=O\tSIZE={MATRIX}x{MATRIX}\n")
game.display_board()
if game.check_winner() is not None or game.is_board_full():
break
row, col = input_handler(game=game)
if game.make_move(row, col, AI=False):
game.display_board()
else:
print('Invalid action! Try once more.')
if game.check_winner() is not None or game.is_board_full():
break
print("AI thinking...")
ai_move = find_best_move(game, depth)
system("cls")
if MATRIX < 5:
sleep(.8)
game.make_move(ai_move[0], ai_move[1])
game.display_board()
winner = game.check_winner()
if winner is not None:
print("It's over. {} wins!".format(winner))
else:
print("It's a draw!")
def get_matrix_size() -> int:
"""
Ask the user to enter the Tic-Tac-Toe game's matrix size.
gives back an integer indicating the size of the game's square matrices.
Note: By using this function, you can be confident that you're using a correct matrix size,
which must be between 2x2 and 6x6.
"""
prompt = "Type Matrix Size (for example, 3x3): "
while True:
try:
value = input(prompt)
if "x" not in value.lower():
print("[Enter Error] Enter a valid matrix, such as 3x3, please.")
continue
matrix_size = value.lower().split("x")
value1 = int(matrix_size[0].strip())
value2 = int(matrix_size[-1].strip())
if 3 <= value1 <= 6 and value1 == value2:
return value1
else:
print("[Enter Error] Please provide a true matrix in the range of 3x3 to 6x6. And the matrix must be "
"square.")
except ValueError:
print("[Enter Error] Enter only valid matrix values here.")
# Play the game.
if __name__ == '__main__':
try:
MATRIX = get_matrix_size()
desired_depth = MATRIX
play_game(desired_depth)
except KeyboardInterrupt:
print("\n[Info] Exiting the game.")
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment