1. 程式人生 > 實用技巧 >簡單的井字棋 AI DEMO | MaxMin演算法

簡單的井字棋 AI DEMO | MaxMin演算法

在“類與物件”實訓課上,有一道附加題讓我們用 OOP 做一個的井字棋模擬程式,要求中電腦是隨機落子的,這樣顯然不是很優雅。回憶起以前學的對抗搜尋(這裡叫 MaxMin 演算法),我繼續給遊戲中的電腦一方寫了個 AI。由於井字棋遊戲運算規模很小,大部分的剪枝手段變得比較雞肋,但以此為引搜尋了一些資料,瞭解一些有趣的計算機博弈論知識,有機會再繼續探究一下。

待改進的點:

  • 判定勝利的方法比較蠢,可以用迴圈代替

  • 沒有對搜尋樹的預計深度進行評估,使 AI 做出最 “快” 的策略

  • 程式碼可讀性不強,不符合工程程式碼規範

#include <bits/stdc++.h>
using namespace std;
class MyChess {
	private:
		char A[3][3];
  		int step;	
	public:
		MyChess();
		void DispChessboard();
		void  PlayerMove();
		void  ComputerMove();
		bool isFull();
		int MaxSearch();
		int MinSearch();
		int checkWin();              
};
MyChess::MyChess() {
	memset(A,0,sizeof(A));
	step=0;
}
void MyChess::DispChessboard() {
	cout<<"-------------------\n";
	for (int i=0;i<3;i++) {
		cout<<"|  "<<A[i][0]<<"  |  "<<A[i][1]<<"  |  "<<A[i][2]<<"  |\n";
		cout<<"-------------------\n";
	}
}
void MyChess::PlayerMove() {
	int x,y;
	cout<<"Step "<<++step<<" piece(x,y): ";
	cin>>x>>y;
	while (A[x][y] || x>2 || x<0 || y>2 || y<0) {
		cout<<"ERROR!! piece(x,y): ";
		cin>>x>>y;
	}
	A[x][y]='O';
}
void MyChess::ComputerMove() { // 模擬 AI 的選擇過程 
	int x,y,score=1;
	for (int i=0;i<3;i++) {
		for (int j=0;j<3;j++) {
			if (!A[i][j]) {
				A[i][j]='X';
				int temp=MaxSearch();
				if (score>temp) x=i,y=j,score=temp;
				A[i][j]=0;
			}
		}
	}
	A[x][y]='X';
}
int MyChess::MaxSearch() { // 人類執子時,希望找到權值最大的子節點 
	int ret=-1,sta=checkWin();
	if (sta) return sta;
	if (isFull()) return 0;
	for (int i=0;i<3;i++) for (int j=0;j<3;j++)
		if (!A[i][j]) A[i][j]='O',ret=max(ret,MinSearch()),A[i][j]=0;
	return ret;
}
int MyChess::MinSearch() { // 電腦執子時,希望找找到權值最小的子節點 
	int ret=1,sta=checkWin();
	if (sta) return sta;
	if (isFull()) return 0;
	for (int i=0;i<3;i++) for (int j=0;j<3;j++)
		if (!A[i][j]) A[i][j]='X',ret=min(ret,MaxSearch()),A[i][j]=0;
	return ret;	
}
bool MyChess::isFull() { // 平局判定 
	for (int i=0;i<3;i++)
		for (int j=0;j<3;j++)
			if (!A[i][j]) return false;
	return true;
}
int MyChess::checkWin() {
	for (int i=0;i<3;i++) if (A[i][0]=='O' && A[i][1]=='O' && A[i][2]=='O') return 1;	
	for (int i=0;i<3;i++) if (A[0][i]=='O' && A[1][i]=='O' && A[2][i]=='O') return 1;		
	for (int i=0;i<3;i++)  if (A[i][0]=='X' && A[i][1]=='X' && A[i][2]=='X') return -1;
	for (int i=0;i<3;i++) if (A[0][i]=='X' && A[1][i]=='X' && A[2][i]=='X') return -1;
	if (A[0][0]=='O' && A[1][1]=='O' && A[2][2]=='O') return 1;
	if (A[2][0]=='O' && A[1][1]=='O' && A[0][2]=='O') return 1;
	if (A[0][0]=='X' && A[1][1]=='X' && A[2][2]=='X') return -1;
	if (A[2][0]=='X' && A[1][1]=='X' && A[0][2]=='X') return -1;	
	return 0;
}
int main() {
	MyChess Ch; int now=1;
	Ch.DispChessboard();
	while (!Ch.isFull()) {
		if (now&1) {
			Ch.PlayerMove();
			if (Ch.checkWin()==1) { Ch.DispChessboard(),cout<<"You win!\n"; break; }
			else if (Ch.isFull()) { Ch.DispChessboard(),cout<<"Gme tie!\n"; break; }	
		}
		else {
			Ch.ComputerMove();
			Ch.DispChessboard();
			if (Ch.checkWin()==-1) { cout<<"Computer win!\n"; break; }
		}
		now^=1;	
	}
	
	return 0;
}