首页 > 解决方案 > C++ 中的井字游戏极小极大算法

问题描述

我一直在学习 C++,我决定制作一个使用 MiniMax 算法的 tictactoe 项目。我已经制作了所有的播放功能,包括 2 播放器。当我选择玩 AI 时,为什么我的 minimax 算法不起作用?它总是移动到 0 或 8,我不知道为什么。任何帮助将不胜感激

井字游戏.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

井字游戏.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;
}

主文件

#include "TicTacToeGame.h"


int main() {

    TicTacToeGame game;
    game.play(game);


    return 0;
}

标签: c++

解决方案


这一行:

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

best.index打印此行时未初始化。也就是说,你永远不会设置best.index一个值。

此外,您通过值将 TicTacBoard 的实例作为参数传递给同一类上的另一个方法实例。您的一些代码对board参数进行操作。其他部分隐含在this. 您可能希望消除board按值传递。

更改您的代码,以便在 main.xml 中像这样调用它。

TicTacToeGame game;
game.play();

board并删除您作为参数传递的所有地方。


推荐阅读