極大極小算法是典型的博弈算法,它是模擬兩個人下棋,雙方都下對自己最有利的位置,進行指數級模擬得出最優走步。負極大算法是極大極小的簡潔版本,但是卻坑了我很久,今天我們實現一個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;
}
值得一提的是,網上很多的博客寫了極大極小和負極大的搜索算法和原理,但是有一點很多博客博主卻一帶而過的東西就是:關於極大極小和負極大算法的評估函數是不一樣的
原理我就不多說話了。直接看上面代碼裏面重載的兩個評估函數就行了。