井字棋展示經典博弈算法——極大極小+負極大

極大極小算法是典型的博弈算法,它是模擬兩個人下棋,雙方都下對自己最有利的位置,進行指數級模擬得出最優走步。負極大算法是極大極小的簡潔版本,但是卻坑了我很久,今天我們實現一個c++的人機對戰井字棋,來理解下這兩種算法。


井字棋實現

井字棋肯定離不開棋盤,我們實現一個Board類來表示棋盤

board.h

#pragma once
class Board {
public:
    Board();
    ~Board();
    /* 
    我們使用一個大小爲9的數組來表示棋盤.
    數值爲1表示先手,數值爲-1後手,數值爲0表示還沒下
    */
    // 獲取col列的3個數字的和
    int Board::getCol(int col);

    // 獲取row行的3個數字的和

    // 獲取主對角線3個數字的和
    int Board::getMainLine();

    // 獲取副對角線3個數字的和

    // 重載<<運算符,便於使用cout輸出Board
    friend std::ostream& operator<<(std::ostream &os, Board board);
};
std::ostream& operator<<(std::ostream &os, Board board) {
    for (int i = 0; i < 9; i++) {
        if (i % 3 == 0 && i != 0) {
            os << std::endl;
        }
        if (board.board[i] == -1) {
            os << "  X";
        }
        else if (board.board[i] == 1) {
            os << "  O";
        }
        else {
            os << "  _";
        }
    }
    return os;
}
Board::Board() {
    for (int i = 0; i < 9; i++) {
        this->board[i] = 0;
    }
}
Board::~Board() {}
int Board::getCol(int col) {
    int sum = board[col] + board[col + 3] + board[col + 6];
    return sum;
}
int Board::getRow(int row) {
    int sum = board[row * 3] + board[row * 3 + 1] + board[row * 3 + 2];
    return sum;
}
int Board::getMainLine() {
    return this->board[0] + this->board[4] + this->board[8];
}
int Board::getCountLine() {
    return this->board[2] + this->board[4] + this->board[6];
}

我們設計好了一個井字棋的類,下面我們來完成整個遊戲的主體。

遊戲的主體代碼

#include<iostream>
#include<windows.h>
#include<vector>
#include"board.h"

using namespace std;

void gameBody();
void placeChess(int pos);
int winner();
bool isDraw();
// 負極大算法專用vip評估函數
int evaluate(int playerNow);
// 極大極小專用vip評估函數
int evaluate();
// 模擬走步
void GenerateMoves(vector<int> &steps);
int negaMax(int depth, int playerNow);
int maxMin(int depth, int playerNow);

namespace {
    Board board;
    int playerNow = 1;//默認玩家先手
    int bestMove;
    int depth = 9;//默認初始搜索深度
}
void gameBody() {
    // 只要不是平局和由玩家獲勝,就一直進行遊戲
    while (winner()==0 && !isDraw()) {
        // 玩家落子
        if (playerNow > 0) {
            cout << board;
            cout << std::endl << "--------請輸入您要落子的位置(0-8)"
            << std::endl;
            int pos;
            cin >> pos;
            // 放置棋子
            placeChess(pos);
            depth--;
        }
        else {
            //電腦開始搜索啦
            // 採用極大極小搜索
            maxMin(depth, playerNow);
            // 採用負極大搜索
            //negaMax(depth, playerNow);
            placeChess(bestMove);
            depth--;
        }
    }
    cout << board;
}
void placeChess(int pos) {
    board.board[pos]= playerNow;
    playerNow = -playerNow;
}
int winner(){
    for (int i = 0; i < 3; i++) {
        if (board.getCol(i) == -3 || board.getRow(i) == -3||board.getMainLine()==-3||board.getCountLine()==-3) {
            return -1;
        }
        if (board.getCol(i) == 3 || board.getRow(i) == 3 || board.getMainLine() == 3 || board.getCountLine() == 3) {
            return 1;
        }
    }
    return 0;
}
bool isDraw() {
    for (int i = 0; i < 9; i++) {
        if (board.board[i] == 0) {
            return false;
        }
    }
    return (winner() == 0)&& true;
}

void GenerateMoves(vector<int> &steps) {
    for (int i = 0; i < 9; i++) {
        if (board.board[i] == 0) {
            steps.push_back(i);
        }
    }
}

int evaluate(int playerNow) {
    if (winner() ==1) {
        return 10000* playerNow;
    }
    else if (winner() == -1) {
        return -10000* playerNow;
    }
    else {
        return 0;
    }
}
int evaluate() {
    if (winner() == 1) {
        return 10000 ;
    }
    else if (winner() == -1) {
        return -10000;
    }
    else {
        return 0;
    }
}
int maxMin(int depth, int playerNow) {
    if (winner()!=0 || depth == 0) {
        return evaluate();
    }

    int bestValue = 0;
    if (playerNow == -1) {
        bestValue = 10000;
    }
    else {
        bestValue = -10000;
    }
    vector<int> steps;
    vector<int>& stepVector = steps;
    GenerateMoves(stepVector);// 獲得走步
    while(stepVector.size()>0) {
        int mv = stepVector[stepVector.size() - 1];
        stepVector.pop_back();

        board.board[mv] = playerNow;// makeMove
        int value = maxMin(depth - 1, -playerNow);
        board.board[mv] = 0;// unMakeMove

        if (playerNow == 1) {
            if (value > bestValue) {
                bestValue = value;
                if (depth == ::depth) {
                    bestMove = mv;
                }
            }
        }
        else {
            if (value < bestValue) {
                bestValue = value;
                if (depth == ::depth) {
                    bestMove = mv;
                }
            }
        }
    }

    return bestValue;
}


int negaMax(int depth,int playerNow) {
    int best = -100000;
    if (depth == 0 || winner() != 0) {
        return evaluate(playerNow);
    }
    else {
        vector<int> steps;
        vector<int>& stepVector = steps;
        GenerateMoves(stepVector);// 獲得走步

        while (stepVector.size()>0) {
            int mv = stepVector[stepVector.size()-1];
            stepVector.pop_back();

            board.board[mv] = playerNow;// makeMove
            int value = -negaMax(depth - 1,-playerNow);
            board.board[mv] = 0;// unMakeMove
            if (value >= best) {
                best = value;

                if (depth == ::depth) {
                    bestMove = mv;
                }
            }
        }
    }
    return best;
}

int main() {
    gameBody();
    system("pause");
    return 0;
}

值得一提的是,網上很多的博客寫了極大極小和負極大的搜索算法和原理,但是有一點很多博客博主卻一帶而過的東西就是:關於極大極小和負極大算法的評估函數是不一樣的
原理我就不多說話了。直接看上面代碼裏面重載的兩個評估函數就行了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章