Isaac Muriuki
Isaac Muriuki

Reputation: 1

Tic Tac Toe minimax algorithm in C++

Ive been learning C++ and I decided to make a tictactoe project that utilizes a MiniMax algorithm. I have made all the functionality for playing, including 2 player. Why doesn't my minimax algorithm when I choose to play with the AI work ? It always moves to 0 or 8 and I can't figure out why. Any help will be appreciated

TicTacToe.h

#ifndef TICTACTOE_TICTACTOEGAME_H
#define TICTACTOE_TICTACTOEGAME_H

#include <string>
#include <iostream>
#include <vector>


class TicTacToeGame {
    struct move{
        int score;
        int index;
    };

public:
    const int PLAYER1_INDEX = 1;
    const int PLAYER2_INDEX = 2;
    const int AI_INDEX = 3;

    TicTacToeGame();
    void play(TicTacToeGame);
    bool hasWon() const;
    bool haveTied() const;
    char getMarker(int, int) const;
    void setMarker(int, int);
    void resetMarker(int);
    int getCurrentPlayer() const;
    std::vector<int> possibleMoves();

private:
    char board[3][3];
    std::string player1_name;
    std::string player2_name;
    int currentPlayer;
    bool isMultiplayer;
    int moveCount = 0;

    void init();
    void printBoard();
    void setPlayer1Name(std::string);
    void setPlayer2Name(std::string);
    std::string getPlayer1Name() const;
    std::string getPlayer2Name() const;
    void switchCurrentPlayer();
    std::string winMessage();
    int getRow(int) const;
    int getColumn(int) const;
    bool checkMarker(int, int);

    void makeAIMove(TicTacToeGame);
    move miniMax(TicTacToeGame, int);
};


#endif //TICTACTOE_TICTACTOEGAME_H

TicTacToe.cpp

#include "TicTacToeGame.h"

// Player is MINIMIZER, AI is MAXIMIZER
TicTacToeGame::move TicTacToeGame::miniMax(TicTacToeGame board, int player) {
    move current, best;
    int otherPlayer = (player == 1) ? 3 : 1;

    if (board.hasWon()){
        if(player == PLAYER1_INDEX){
            best.score = 10;
            return best;
        } else if(player == AI_INDEX){
            best.score = -10;
            return best;
        }
    } else if(board.haveTied()){
        best.score = 0;
        return best;
    }

    if(player == AI_INDEX){
        best.score = -9999999;
    } else best.score = 9999999;

    std::vector<int> possibleMoves = board.possibleMoves();

    for (int i = 0; i < possibleMoves.size(); i++) {
        board.setMarker(possibleMoves[i], player);
        current = miniMax(board, otherPlayer);
        current.index = possibleMoves[i];

        if(player == AI_INDEX){
            if(current.score > best.score) {
                best = current;
            }
        } else
            if(current.score < best.score){
                best = current;
            }
        board.resetMarker(possibleMoves[i]);

    }
    std::cout << "best index: " << best.index << " best score: " << best.score << std::endl;
    return best;

}

void TicTacToeGame::makeAIMove(TicTacToeGame board) {
    int move = miniMax(board, getCurrentPlayer()).index;
    setMarker(move, getCurrentPlayer());
    std::cout << "\nThe AI has moved to " << move << std::endl;
}

std::vector<int> TicTacToeGame::possibleMoves() {
    std::vector<int> possibleMoves;
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            if(board[i][j] != 'X' && board[i][j] != 'O'){
                possibleMoves.push_back(3 * i +j);
            }
        }
    }
    return possibleMoves;
}

TicTacToeGame::TicTacToeGame() {
    init();
}

// Create the board with indexes
void TicTacToeGame::init() {
    currentPlayer = 2;
    int k = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++, k++) {
            board[i][j] = '0' + k;
        }
    }
}

// Prints the board int he format of a TicTacToe game
void TicTacToeGame::printBoard() {
    std::cout << "\n";
    for (int i = 0; i < 3; i++) {
        if (i != 0) std::cout << "\n   --------------------\n";
        for (int j = 0; j < 3; j++) {
            std::cout << "\t" << board[i][j];
            if (j != 2) std::cout << "\t|";
        }
    }
}

// Handles the playing
void TicTacToeGame::play(TicTacToeGame game) {
    std::string welcome = "\t\t*********************************************\n\t\t*\t\tWelcome to the TicTacToe game !\t\t*\n\t\t*********************************************\n\n\n";
    int position = -1;
    char multiplayer;
    char playAgain;

    std::cout << welcome;

    while (true){
        std::cout << "Do you want to play multiplayer ? (y/n): ";

        std::cin >> multiplayer;
        multiplayer = tolower(multiplayer);

        // Checks if input is valid, asks for another input if not
        while (std::cin.fail() || (multiplayer != 'y' && multiplayer != 'n')) {
            std::cout << "Invalid entry ! Enter either y OR n: ";
            std::cin.clear();
            std::cin.ignore(256, '\n');
            std::cin >> multiplayer;
            multiplayer = tolower(multiplayer);
        }

        if (multiplayer == 'y') isMultiplayer = true;
        else if (multiplayer == 'n') isMultiplayer = false;
        break;
    }

    // Initializing players
        std::cout << "Player 1, please type in your name: ";
        std::cin >> player1_name;

        // Checks if input is valid, asks for another input if not
        while (std::cin.fail()) {
            std::cout << "Invalid entry ! Re-enter your name: ";
            std::cin.clear();
            std::cin.ignore(256, '\n');
            std::cin >> player1_name;
        }

        setPlayer1Name(player1_name);
        std::cout << getPlayer1Name() << " will be X." << std::endl;

        // Only initializes player 2 if it's a multiplayer game
        if (isMultiplayer){
            std::cout << "\nPlayer 2, please type in your name: ";
            std::cin >> player2_name;

            // Checks if input is valid, asks for another input if not
            while (std::cin.fail()) {
                std::cout << "Invalid entry ! Re-enter your name: ";
                std::cin.clear();
                std::cin.ignore(256, '\n');
                std::cin >> player2_name;
            }

            setPlayer2Name(player2_name);
            std::cout << getPlayer2Name() << " will be O.\n" << std::endl;
        }


    std::cout << "Starting game: \nINSTRUCTIONS: Select the number of the position where you want to play" << std::endl;

    // Play loop
    while (!hasWon()) {
        printBoard();

        // Alternate between players after evey move
        switchCurrentPlayer();
        std::cout << "\ncurrent player is " << currentPlayer << std::endl;

        // Player's move
        if (currentPlayer == PLAYER1_INDEX || currentPlayer == PLAYER2_INDEX){
            while (true) {
                std::cin >> position;

                // Checks if input is an integer, asks for another input if not
                while (std::cin.fail() || position < 0 || position > 8) {
                    std::cout << "Invalid entry ! Enter a number between 0 and 8 inclusive: ";
                    std::cin.clear();
                    std::cin.ignore(256, '\n');
                    std::cin >> position;
                }

                // Check if there's an X or O in that position already, if yes - ask for another position to if so. If no - writes X/O
                if (checkMarker(getRow(position), getColumn(position))) {
                    setMarker(position, getCurrentPlayer());
                    break;
                } else {
                    std::cout
                            << "There's already an X/O in that position. Try place it in an empty spot.\nEnter a new position: ";
                }
            }
        }
        // AI's move
        else if (currentPlayer == AI_INDEX){
            makeAIMove(game);
        }

        moveCount++;

        // Checks for a winner or a tie after every move
        if (hasWon() || haveTied()) {
            if (hasWon()) std::cout << winMessage() << std::endl;
            else if(haveTied()) std::cout << "The game is a tie !" << std::endl;

            std::cout << "\n\nDo you want to play again ? Enter y OR n: ";
            std::cin >> playAgain;
            playAgain = tolower(playAgain); // converts to lowercase

            // If there's a winner ask if user wants to play another game, stops application if not
            while (true) {
                if (playAgain != 'y' && playAgain != 'n') {
                    std::cout << playAgain << std::endl;
                    std::cout << "Invalid expression ! Enter either y OR n: ";
                    std::cin >> playAgain;
                    playAgain = tolower(playAgain); // converts to lowercase
                } else if (playAgain == 'y' || playAgain == 'n') break;
            }

            if (playAgain == 'y') {
                std::cout << "Generating a new board " << std::endl;
                init();
                moveCount = 0;
            } else if (playAgain == 'n') {
                std::cout << "\nThank you for playing the TicTacToe game !\nExiting the game.";
                break;
            }
        }
    }
}

std::string TicTacToeGame::winMessage() {
    if (getCurrentPlayer() == 1) { return "\n\n" + getPlayer1Name() + " has won !"; }
    else if (getCurrentPlayer() == 2) return "\n\n" + getPlayer2Name() + "has won !";
    else return "\n\nThe AI has won !";
}

// Checks for winning conditions
bool TicTacToeGame::hasWon() const {
    for (int i = 0; i < 3; ++i) {
        // Checking horizontals
        if (getMarker(0, i) == getMarker(1, i) && getMarker(1, i) == getMarker(2, i)) {
            //cout << "horizontal alignment";
            return true;
        }

        // Chekcing verticals
        if (getMarker(i, 0) == getMarker(i, 1) && getMarker(i, 1) == getMarker(i, 2)) {
            //cout << "vertical alignment";
            return true;
        }
    }

    // Checking diagonals
    if (getMarker(0, 0) == getMarker(1, 1) && getMarker(1, 1) == getMarker(2, 2)) return true;
    if (getMarker(0, 2) == getMarker(1, 1) && getMarker(1, 1) == getMarker(2, 0)) return true;

    // If no alignment
    return false;
}

void TicTacToeGame::setPlayer1Name(std::string name) {
    player1_name = name;
}

void TicTacToeGame::setPlayer2Name(std::string name) {
    player2_name = name;
}

std::string TicTacToeGame::getPlayer1Name() const {
    return player1_name;
}

std::string TicTacToeGame::getPlayer2Name() const {
    return player2_name;
}

int TicTacToeGame::getCurrentPlayer() const {
    return currentPlayer;
}

int TicTacToeGame::getRow(int row) const{
    return row / 3;
}

int TicTacToeGame::getColumn(int column) const {
    return column % 3;
}

bool TicTacToeGame::checkMarker(int row, int column) {
    if (getMarker(row, column) == 'X' || getMarker(row, column) == 'O') return false;
    return true;
}

char TicTacToeGame::getMarker(int row, int column) const {
    return board[row][column];
}

void TicTacToeGame::setMarker(int position, int player) {
    board[getRow(position)][getColumn(position)] = player == 1 ? 'X' : 'O';
}

/* Switches the play either from:
 *  - PLAYER1 to PLAYER2
 *  - PLAYER 2 to PLAYER1
 *  - AI to PLAYER1
 *  - PLAYER1 to AI
 */
void TicTacToeGame::switchCurrentPlayer() {
    if (currentPlayer == PLAYER1_INDEX && !isMultiplayer){
        currentPlayer = AI_INDEX;
        std::cout << "\n\nIt's the AI's turn now" << std::endl;
    } else if (currentPlayer == PLAYER1_INDEX) {
        currentPlayer = PLAYER2_INDEX;
        std::cout << "\n\nIt's " << getPlayer2Name() << "'s turn, select the position you want to move to:";
    } else if (currentPlayer == PLAYER2_INDEX || currentPlayer == AI_INDEX) {
        currentPlayer = PLAYER1_INDEX;
        std::cout << "\n\nIt's " << getPlayer1Name() << "'s turn, select the position you want to move to:";
    }
}

bool TicTacToeGame::haveTied() const {
    if (moveCount == 9) return true;
    return false;
}

void TicTacToeGame::resetMarker(int position) {
    board[getRow(position)][getColumn(position)] = (char) position;
}

main.cpp

#include "TicTacToeGame.h"


int main() {

    TicTacToeGame game;
    game.play(game);


    return 0;
}

Upvotes: 0

Views: 1467

Answers (1)

selbie
selbie

Reputation: 104524

This line:

std::cout << "best index: " << best.index << " best score: " << best.score << std::endl;

best.index is uninitialized when this line is printed. That is, you never set best.index to a value.

Further, you are passing an instance of TicTacBoard by value as a parameter to another instance of methods on the same class. Some of your code operates on the board parameter. Other parts work implicitly on this. You probably want to eliminate the passing around of board by value.

Change your code such that it's invoked like this in main.

TicTacToeGame game;
game.play();

And remove all the places where you pass board as a parameter.

Upvotes: 1

Related Questions