Exploring Alternatives: Using Self Balancing Trees for Tic Tac Toe Beyond the Minimax Algorithm
In the realm of classic games, Tic Tac Toe stands as a timeless challenge. Tic Tac Toe has been a staple of algorithmic learning for decades. Traditionally, the Minimax algorithm has been the go-to method for implementing AI in this game. However, as we explore new approaches, I’ve tried a different method using a self balancing binary tree to store player's moves, which promises more efficient performance, especially in real-time game scenario with a 3X3 board. In this post, I've tried to compare this new approach with the standard Minimax algorithm.
Classic Minimax Algorithm Overview
The classic Minimax algorithm is a decision-making strategy used in two-player, zero-sum games to determine the optimal move. It operates by recursively evaluating all possible game states and choosing the move that maximizes the player's minimum possible loss, assuming the opponent also plays optimally.
Game Tree Construction:
Tree Nodes
Evaluation
Complexity:
Pseudo-Code
function minimax(node, depth, isMaximizingPlayer):
if depth == 0 or node is a terminal node:
return the heuristic value of node
if isMaximizingPlayer:
bestValue = -∞
for each child of node:
value = minimax(child, depth - 1, false)
bestValue = max(bestValue, value)
return bestValue
else:
bestValue = ∞
for each child of node:
value = minimax(child, depth - 1, true)
bestValue = min(bestValue, value)
return bestValue
The Minimax algorithm is widely used in classic games like Tic Tac Toe, Chess, and Checkers, where it ensures optimal play by evaluating all possible outcomes and making the best decision based on the assumption of perfect play from both players. While effective, the classic Minimax algorithm can be computationally expensive, especially for games with a high branching factor or depth, with complexity generally being O(b^d), where b is the branching factor and d is the depth.
Self balancing Binary Trees approach
The approach to solving Tic Tac Toe presented here utilizes a binary tree to efficiently track player moves and determine game outcomes. This method leverages tree balancing and equidistant path checks to ensure optimal performance, particularly for a 3x3 board. The approach contrasts with the classic Minimax algorithm by focusing on specific game conditions and tree operations rather than exploring all possible game states.
Construction of the Tree:
Recommended by LinkedIn
Inserting Moves:
Equidistant Path Checking:
Performance Characteristics:
Insertion and Win-Checking code:
class Node
{
internal int Value { get; set; }
internal Node Left { get; set; }
internal Node Right { get; set; }
internal Node(int value)
{
Value = value;
Left = null;
Right = null;
}
internal bool HasEquidistantChildren
{
get
{
if (Left == null || Right == null)
return false;
return Value - Left?.Value == Right?.Value - Value;
}
}
}
class PlayerPath
{
internal Node _root;
internal PlayerPath()
{
_root = null;
}
internal void Insert(int value)
{
_root = InsertRecursively(_root, value);
BalanceTree();
}
private Node InsertRecursively(Node root, int value)
{
if (root == null)
{
root = new Node(value);
return root;
}
if (value < root.Value)
{
root.Left = InsertRecursively(root.Left, value);
}
else if (value > root.Value)
{
root.Right = InsertRecursively(root.Right, value);
}
return root;
}
internal void BalanceTree()
{
_root = Balance(_root);
}
private Node Balance(Node root)
{
if (root == null) return root;
root.Left = Balance(root.Left);
root.Right = Balance(root.Right);
if (root.Left != null && root.Right == null)
{
root = RightRotate(root);
}
else if (root.Right != null && root.Left == null)
{
root = LeftRotate(root);
}
else if (root.Left != null && root.Right != null)
{
if (root.Left.Value > root.Value)
{
root = RightRotate(root);
}
if (root.Right.Value < root.Value)
{
root = LeftRotate(root);
}
}
return root;
}
private Node LeftRotate(Node node)
{
Node newRoot = node.Right;
node.Right = newRoot.Left;
newRoot.Left = node;
return newRoot;
}
private Node RightRotate(Node node)
{
Node newRoot = node.Left;
node.Left = newRoot.Right;
newRoot.Right = node;
return newRoot;
}
internal bool HasWon()
{
return CheckEquidistantChildren(_root);
}
private bool CheckEquidistantChildren(Node node)
{
if (node == null)
{
return false;
}
if (node.HasEquidistantChildren)
{
return true;
}
return CheckEquidistantChildren(node.Left) || CheckEquidistantChildren(node.Right);
}
}
Example moves
Let's visualize the tree after each move!
You can get the complete console client code here
Considerations
Minimax approach:
Self balancing tree approach:
To enhance scalability and performance for more complex scenarios, further research into advanced data structures and algorithms may be needed. Adapting the approach to incorporate heuristic evaluations or parallel processing could also be considered.
Note: In exploring alternative strategies for solving Tic Tac Toe, I examined a self balancing binary tree approach that diverges from the well-known Minimax algorithm. While this method offers a novel perspective, it’s important to acknowledge that similar concepts might exist in academic or niche contexts. This approach, focusing on dynamic balancing and efficient equidistant checks, presents a fresh take but may not be universally established or documented. Therefore, while it provides an innovative alternative, further research and comparison with existing scholarly work are necessary to confirm its uniqueness and effectiveness. This exploration is intended to stimulate discussion and consideration of new strategies rather than claim groundbreaking originality.