Table Of Contents
The Tic-Tac-Toe AI Game Project is an interactive application of the classic game, enhanced with different types of Artificial intelligence (AI) opponents using different methods of strategic algorithms and design. This project showcases the fusion of game development and algorithm techniques to create a challenging and strategic gaming experience.
A computer access to the Internet
Welcome to the documentation of my latest creation, Tic-Tac-Toe AI. This documentation provides insights into the structure of my game, game algorithms, and encountered challenges. Some key ideas include game state representation, heuristic evaluation, and user interface development. The code implementation includes modules for board representation, AI logic, and user interface. Some challenges include algorithm complexity, UI design, and algorithmic optimization.
One of the central features of this Tic-Tac-Toe AI is the implementation of difficulty levels, ranging from easy to hard. Each difficulty level comes with its algorithmic implementation to provide some challenge to the player. These algorithms determine the AI's decision-making process and strategy during the game. The easy level focuses on random moves using a randomization algorithm. In contrast, the medium and hard levels would operate more sophisticated algorithms, such as the minimax algorithm with alpha-beta pruning and heuristics to provide increasingly competitive gameplay. Incorporating these difficulty levels adds depth to the AI's abilities and improves the general user experience.
Directory Structure
index.html: This is the main file or the structure of the webpage. This holds all the webpage elements and decides where the gameboard is placed using a div.
style.css: This is the decorations of the webpage, using background color manipulation, and transitions. There also lies some code for responsive page design for all device mediums (Phones, Tablets, and Laptops).
main.js: This is where most of the code is located (board, algorithmic design, game states, check wins, etc).
There are also folders named with their designated file name and type. Also, folders contain images (img) and sound effects (sfx) to enhance the player experience.
When creating the structure of the webpage, we need several key details and an idea of how to organize and structure this game.
For the webpage, creating the game board is essential when playing tic-tac-toe, where are you going to play without a game board, right? The webpage contains a text box where it states a player's turn, and a title to recognize the game. A settings menu, containing several controls, such as selecting different game modes and resetting games. Finally, this is optional but adding an instruction text bar and a menu button. The instruction text is to give someone who has no clue or never played tic-tac-toe before. This gives them a hint on what to do, although I kept the instructions extremely short since the game is very simple. The menu button is to pop-up the menu.
To add the structure for the gameboard, we just add a <div> that's all! The <div> element with the id board serves as the container for the Tic-Tac-Toe game board. Inside this container, individual <div> elements are dynamically generated to represent each cell of the Tic-Tac-Toe grid. These cells will be filled with 'X', 'O', or left empty based on the game state.
Having a separate container for the game board allows for easier manipulation of the game board elements through JavaScript. It provides a structured way to organize and manage the Tic-Tac-Toe grid within the HTML document. Additionally, it makes styling and positioning of the game board elements more manageable through CSS.
The HTML Settings (The Impossible AI isn't added anymore UPDATED)
This code structure represents the game settings section of the Tic-Tac-Toe game. Within the `<div>` element with the id `settings` and the class `settings-container`, users can interact with the game to choose various settings and options.
The `<label>` element with the `for` attribute points to the `<select>` element with the id `gameMode`, creating a label for the game mode selection dropdown. This dropdown allows users to choose different game modes, including options such as "Human vs. Easy AI," "Human vs. Medium AI," "Human vs. Hard AI," and "Two Players." Each option has a corresponding value attribute, which will be used to determine the game mode selected by the user.
Adjacent to the game mode selection dropdown is the "Reset Game" button, represented by the `<button>` element with the id `resetButton`. This button enables users to reset the game board and start a new game.
Below the game settings section are two `<div>` elements with the ids `stateTurn` and `stateResult`. These elements are used to display information about the current game state to the user. Specifically, `stateTurn` displays whose turn it is (e.g., "Player X's turn"), while `stateResult` is used to show the result of the game (e.g., "X wins!" or "It's a draw!").
The code in the `main.js` file generates the game board using a combination of JavaScript DOM manipulation techniques. It employs a `for` loop to iterate through each cell of the game board. In this loop, a new `<div>` element is created for each cell using the `document.createElement()` method. Each cell `<div>` is given the class name 'cell' using the `classList.add()` method. This class is used for styling purposes and to distinguish the cells of the game board from other elements on the webpage.
Additionally, the `dataset.index` attribute is utilized to store the index of each cell within the game board. This allows for easy reference to the specific cell when making moves during the game. An event listener is attached to each cell using the `addEventListener()` method. This event listener listens for click events on the cell, triggering the `makeMove()` function when a cell is clicked. The `makeMove()` function handles the logic of placing a player's symbol (X or O) on the clicked cell.
Finally, each cell `<div>` is appended as a child element to the `board` container, which represents the overall game board. This process repeats for each cell, resulting in the creation of a 3x3 grid that forms the Tic-Tac-Toe game board.
The code in the `main.js` file is responsible for handling player moves and triggering the next player's move is the makeMove() function.
In this function, the parameter `index` represents the index of the cell where the player has clicked to make a move. It first checks if the game is still active and if the clicked cell is empty (`gameBoard[index] === ''`). If both conditions are met, it updates the game board array ('gameBoard') with the current player's symbol ('X' or 'O') placed in the clicked cell.
After updating the game board, the function then switches to the next player's turn. This is achieved by toggling the `currentPlayer` variable between 'X' and 'O' using a ternary operator (`currentPlayer = currentPlayer === 'X' ? 'O' : 'X'`). This ensures that each player takes turns making moves in the game.
In this section, the code checks the current game mode (gameMode) to determine which AI move function to call. If the game mode is set to 'easy', 'medium', 'hard', or 'impossible', it calls the corresponding AI move function (bestEasyAIMove(), bestMediumAIMove(), or bestHardAIMove()).
After determining the best move for the AI, the function makeMove() is called with the bestMove as its argument. This triggers the AI's move on the game board.
Let's explain each AI move function:
bestEasyAIMove(): This function selects a random empty cell on the game board. It is the easiest level of AI and does not use complex strategies.
bestMediumAIMove(): This function evaluates the game board to find the move that maximizes its score. It considers the current player's positions and attempts to block potential wins or create winning opportunities.
bestHardAIMove(): This function utilizes the minimax algorithm with alpha-beta pruning to determine the best move. It explores possible future moves and chooses the one that leads to the highest likelihood of winning.
In the provided code, the difficulty levels ('easy', 'medium', and 'hard') are used to adjust the behavior of the AI opponent, providing different levels of challenge for the player. Let's examine how each difficulty level works and the algorithms used:
Easy Difficulty:
Algorithm: Randomization
Explanation: In the bestEasyAIMove() function, the AI selects its move randomly from the available empty cells on the game board. This simplistic approach doesn't involve any complex decision-making or look-ahead. Instead, it relies on chance to determine the AI's move.
Medium Difficulty:
Algorithm: Evaluation Function
Explanation: The bestMediumAIMove() function evaluates each potential move based on a heuristic score calculated in the evaluateMediumAI() function. This score considers factors such as the number of the AI's marks versus the player's marks in rows, columns, and diagonals. The AI prioritizes moves that contribute to its own winning conditions while also blocking the opponent.
Hard Difficulty:
Algorithm: Minimax with Alpha-Beta Pruning
Explanation: The bestHardAIMove() function implements the minimax algorithm with alpha-beta pruning. This advanced technique explores possible future game states recursively to determine the optimal move. Alpha-beta pruning enhances efficiency by eliminating unnecessary branches of the game tree, ensuring that the AI makes optimal moves while considering the opponent's best responses.
Have you ever wondered how a computer can make optimal decisions in games like Tic-Tac-Toe? Unlike choosing random cells in the game board, the AI uses an algorithm called "Minimax."
In a Tic-Tac-Toe game, the Minimax algorithm recursively evaluates all possible moves to determine the current player's best move. It simulates every possible game outcome by assuming both players play optimally, assigning scores to each outcome based on the final game state. The algorithm then chooses the move that maximizes the score for the current player while minimizing the opponent's score, thus leading to an optimal strategy. This process continues until a terminal game state is reached, such as a win, loss, or draw, at which point the algorithm backtracks and returns the best move found.
The function recursively evaluates the game state by simulating all possible moves and their outcomes.
It assigns scores to terminal states (win, lose, or draw).
For each possible move, it alternates between maximizing and minimizing players.
The function returns the score of the best move for the current player.
Alpha represents the minimum score that the maximizing player is assured of.
Beta represents the maximum score that the minimizing player is assured of.
If a branch's score is outside the range defined by alpha and beta, the branch can be pruned (skipped).
This pruning helps discard unproductive branches, significantly reducing the number of nodes explored and improving the algorithm's efficiency.
To check for wins and draws in the game, the code provides a systematic approach that examines the current state of the game board. This process involves iterating over predefined win patterns, which encompass various combinations of rows, columns, and diagonals on the game board.
The checkWinner(board) function meticulously looks through these win patterns, examining each combination to determine if any player has achieved a winning combination. Upon finding a winning combination, the function promptly declares the corresponding player ('X' or 'O') as the winner. If no winning combination is detected but the board is fully occupied, signifying a draw, the function returns a 'draw'. Otherwise, it returns null, indicating that the game is continuing.