
This project aims to implement the Heuristic Alpha-Beta Tree Search Algorithm in Tic-Tac-Toe. The algorithm combines heuristic evaluation and alpha-beta pruning to create an intelligent AI opponent for the game. The goal is to showcase the algorithm’s effectiveness, analyze its performance, and evaluate its strategic decision-making capabilities.
Algorithm Description: The Heuristic Alpha-Beta Tree Search Algorithm combines minimax search and pruning techniques. It evaluates game states using heuristics and optimizes the search process by pruning unnecessary branches.
Program Design: The program simulates a Tic-Tac-Toe game environment with functions for player moves, AI moves, game state evaluation, and winner determination. It is designed for code reusability, readability, and maintainability.
Code Explanations: Key functions include handling moves, evaluating game states, and implementing the algorithm through minimax with alpha-beta pruning.
Experimental Results: Experimental results assess the AI opponent’s performance based on win rate, average moves to win, and game completion time. The algorithm can be tested against different opponents or difficulty levels.
Analysis: The analysis interprets results, discusses strengths and weaknesses, compares with other algorithms, and evaluates computational complexity.
Recommendations and Future Development: Recommendations include fine-tuning heuristics, exploring enhancements to alpha-beta pruning, and extending the algorithm to other games or environments.
Overall, this project demonstrates the potential of the Heuristic Alpha-Beta Tree Search Algorithm in Tic-Tac-Toe and contributes to the understanding of heuristic algorithms in game theory and AI.
This project implements the Heuristic Alpha-Beta Tree Search Algorithm for Tic-Tac-Toe. The algorithm combines heuristic evaluation and alpha-beta pruning to create an intelligent AI opponent. It utilizes minimax search and pruning techniques to evaluate game states efficiently. The program includes functions for player and AI moves, game state evaluation, and determining the winner. Experimental results evaluate AI performance, and the analysis discusses strengths, weaknesses, and potential improvements. The project contributes to game theory and AI understanding.
Alpha-Beta pruning is not actually a new algorithm, but rather an optimization technique for the minimax algorithm. It reduces the computation time by a huge factor. This allows us to search much faster and even go into deeper levels in the game tree. It cuts off branches in the game tree which need not be searched because there already exists a better move available. It is called Alpha-Beta pruning because it passes 2 extra parameters in the minimax function, namely alpha and beta.
Let’s define the parameters alpha and beta.
Alpha is the best value that the maximizer currently can guarantee at that level or above. Beta is the best value that the minimizer currently can guarantee at that level or below.

So far this is how our game tree looks. The 9 is crossed out because it was never computed.

This is how our final game tree looks like. As you can see G has been crossed out as it was never computed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Initial values of Alpha and Beta
MAX, MIN = 1000, -1000
# Returns optimal value for current player
#(Initially called for root and maximizer)
def minimax(depth, nodeIndex, maximizingPlayer,
values, alpha, beta):
# Terminating condition. i.e
# leaf node is reached
if depth == 3:
return values[nodeIndex]
if maximizingPlayer:
best = MIN
# Recur for left and right children
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i,
False, values, alpha, beta)
best = max(best, val)
alpha = max(alpha, best)
# Alpha Beta Pruning
if beta <= alpha:
break
return best
else:
best = MAX
# Recur for left and
# right children
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i,
True, values, alpha, beta)
best = min(best, val)
beta = min(beta, best)
# Alpha Beta Pruning
if beta <= alpha:
break
return best
if __name__ == "__main__":
values = [3, 5, 6, 9, 1, 2, 0, -1]
print("The optimal value is :", minimax(0, 0, True, values, MIN, MAX))
Output
1
The optimal value is : 5

1
2
from random import choice
from math import inf
The first piece of code imports the “choice” function from the “random” library and the “inf” constant from the “math” library.
1
2
3
4
# The Tic-Tac-Toe board
board = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
The second piece of code defines the initial state of the Tic-Tac-Toe board. It is represented as a 3x3 nested list where each element represents a cell on the board. The value 0 indicates an empty cell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Function to display the game board
def Gameboard(board):
chars = {1: 'X', -1: 'O', 0: ' '}
for x in board:
for y in x:
ch = chars[y]
print(f'| {ch} ', end='')
print('|\n' + '_____________')
print("")
print('===============')
print("")
# Function to clear the game board
def Clearboard(board):
for x, row in enumerate(board):
for y, col in enumerate(row):
board[x][y] = 0
The third piece of code includes two functions related to the game board.
ِA. Gameboard(board):
B. Clearboard(board):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Function to check if a player has won
def winningPlayer(board, player):
conditions = [[board[0][0], board[0][1], board[0][2]],
[board[1][0], board[1][1], board[1][2]],
[board[2][0], board[2][1], board[2][2]],
[board[0][0], board[1][0], board[2][0]],
[board[0][1], board[1][1], board[2][1]],
[board[0][2], board[1][2], board[2][2]],
[board[0][0], board[1][1], board[2][2]],
[board[0][2], board[1][1], board[2][0]]]
if [player, player, player] in conditions:
return True
return False
# Function to check if the game has been won
def gameWon(board):
return winningPlayer(board, 1) or winningPlayer(board, -1)
# Function to print the game result
def printResult(board):
if winningPlayer(board, 1):
print('Player has won! ' + '\n')
elif winningPlayer(board, -1):
print('AI has won! ' + '\n')
else:
print('The game is over with the result of Draw' + '\n')
The fourth piece of code includes functions related to checking the game’s winning condition and printing the game result.
A. winningPlayer(board, player):
B. gameWon(board):
C. printResult(board):
This function takes the game board as input. It uses the winningPlayer() function to determine the game’s result. If the player has won, it prints “Player has won!” If the AI has won, it prints “AI has won!” If the game is a draw, it prints “The game is over with the result of Draw.” These functions are responsible for checking the game’s outcome and printing the corresponding result on the console.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Function to get the list of blank positions on the board
def blanks(board):
blank = []
for x, row in enumerate(board):
for y, col in enumerate(row):
if board[x][y] == 0:
blank.append([x, y])
return blank
# Function to check if the board is full
def boardFull(board):
if len(blanks(board)) == 0:
return True
return False
The fifth piece of code includes functions related to checking the board’s status and available moves.
A. blanks(board):
B. boardFull(board):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Function to set a move on the board
def setMove(board, x, y, player):
board[x][y] = player
# Function to evaluate the game state
def evaluate(board):
if winningPlayer(board, 1):
return 1
elif winningPlayer(board, -1):
return -1
else:
return 0
# Heuristic function for the AI
def heuristic(board):
return evaluate(board)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
from random import choice
from math import inf
board = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
def Gameboard(board):
chars = {1: 'X', -1: 'O', 0: ' '}
for x in board:
for y in x:
ch = chars[y]
print(f'| {ch} |', end='')
print('\n' + '---------------')
print('===============')
def Clearboard(board):
for x, row in enumerate(board):
for y, col in enumerate(row):
board[x][y] = 0
def winningPlayer(board, player):
conditions = [[board[0][0], board[0][1], board[0][2]],
[board[1][0], board[1][1], board[1][2]],
[board[2][0], board[2][1], board[2][2]],
[board[0][0], board[1][0], board[2][0]],
[board[0][1], board[1][1], board[2][1]],
[board[0][2], board[1][2], board[2][2]],
[board[0][0], board[1][1], board[2][2]],
[board[0][2], board[1][1], board[2][0]]]
if [player, player, player] in conditions:
return True
return False
def gameWon(board):
return winningPlayer(board, 1) or winningPlayer(board, -1)
def printResult(board):
if winningPlayer(board, 1):
print('X has won! ' + '\n')
elif winningPlayer(board, -1):
print('O\'s have won! ' + '\n')
else:
print('Draw' + '\n')
def blanks(board):
blank = []
for x, row in enumerate(board):
for y, col in enumerate(row):
if board[x][y] == 0:
blank.append([x, y])
return blank
def boardFull(board):
if len(blanks(board)) == 0:
return True
return False
def setMove(board, x, y, player):
board[x][y] = player
def playerMove(board):
e = True
moves = {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]}
while e:
try:
move = int(input('Enter a number between 1-9: '))
if move < 1 or move > 9:
print('Invalid Move! Try again!')
elif not (moves[move] in blanks(board)):
print('Invalid Move! Try again!')
else:
setMove(board, moves[move][0], moves[move][1], 1)
Gameboard(board)
e = False
except(KeyError, ValueError):
print('Enter a number!')
def getScore(board):
if winningPlayer(board, 1):
return 10
elif winningPlayer(board, -1):
return -10
else:
return 0
def abminimax(board, depth, alpha, beta, player):
row = -1
col = -1
if depth == 0 or gameWon(board):
return [row, col, getScore(board)]
else:
for cell in blanks(board):
setMove(board, cell[0], cell[1], player)
score = abminimax(board, depth - 1, alpha, beta, -player)
if player == 1:
# X is always the max player
if score[2] > alpha:
alpha = score[2]
row = cell[0]
col = cell[1]
else:
if score[2] < beta:
beta = score[2]
row = cell[0]
col = cell[1]
setMove(board, cell[0], cell[1], 0)
if alpha >= beta:
break
if player == 1:
return [row, col, alpha]
else:
return [row, col, beta]
def o_comp(board):
if len(blanks(board)) == 9:
x = choice([0, 1, 2])
y = choice([0, 1, 2])
setMove(board, x, y, -1)
Gameboard(board)
else:
result = abminimax(board, len(blanks(board)), -inf, inf, -1)
setMove(board, result[0], result[1], -1)
Gameboard(board)
def x_comp(board):
if len(blanks(board)) == 9:
x = choice([0, 1, 2])
y = choice([0, 1, 2])
setMove(board, x, y, 1)
Gameboard(board)
else:
result = abminimax(board, len(blanks(board)), -inf, inf, 1)
setMove(board, result[0], result[1], 1)
Gameboard(board)
def makeMove(board, player, mode):
if mode == 1:
if player == 1:
playerMove(board)
else:
o_comp(board)
else:
if player == 1:
o_comp(board)
else:
x_comp(board)
def pvc():
while True:
try:
order = int(input('Enter to play 1st or 2nd: '))
if not (order == 1 or order == 2):
print('Please pick 1 or 2')
else:
break
except(KeyError, ValueError):
print('Enter a number')
Clearboard(board)
if order == 2:
currentPlayer = -1
else:
currentPlayer = 1
while not (boardFull(board) or gameWon(board)):
makeMove(board, currentPlayer, 1)
currentPlayer *= -1
printResult(board)
# Driver Code
print("=================================================")
print("TIC-TAC-TOE using MINIMAX with ALPHA-BETA Pruning")
print("=================================================")
pvc()