博弈演算法實現三子棋
阿新 • • 發佈:2018-12-30
用博弈樹演算法實現井字棋遊戲。 井字棋遊戲是一種簡單的棋類遊戲,在3*3的棋盤上,兩人輪流下子,誰的棋子先連成3顆一條直線,誰就贏了,可以橫著、豎著、斜著。博弈樹演算法是用搜索來解決這類問題的演算法,井字棋遊戲步數較少,很容易用博弈樹演算法實現
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
int num = 0;
int p, q;
int tmpQP[3][3]; //表示棋盤資料的臨時陣列,其中的元素0表示該格為空,
int cur[3][3];
const int depth = 3; //搜尋樹的最大深度
void Init()
{
for (int i = 0; i<3; i++)
for (int j = 0; j<3; j++)
{
cur[i][j] = 0;
}
}
void PrintQP() //列印當棋盤格局的函式
{
for (int i = 0; i<3; i++)
{
for (int j = 0; j<3; j++)
cout << cur[i][j] << '\t';
cout << endl;//換行
}
}
int CheckWin() //有人贏了嗎?返回0表示沒有人贏,返回-1表示人贏了,返回1表示計算機贏了
{
for (int i = 0; i<3; i++)
{
if (cur[i][0] == 1 && cur[i][1] == 1 && cur[i][2] == 1)
return 1;
if (cur[i][0] == -1 && cur[i][1] == -1 && cur[i][2] == -1)
return -1;
}
for (int i = 0; i<3; i++)
{
if (cur[0][i] == 1 && cur[1][i] == 1 && cur[2][i] == 1)
return 1;
if (cur[0][i] == -1 && cur[1][i] == -1 && cur[2][i] == -1)
return -1;
}
if ((cur[0][0] == 1 && cur[1][1] == 1 && cur[2][2] == 1) || (cur[2][0] == 1 && cur[1][1] == 1 && cur[0][2] == 1))
return 1;
if ((cur[0][0] == -1 && cur[1][1] == -1 && cur[2][2] == -1) || (cur[2][0] == -1 && cur[1][1] == -1 && cur[0][2] == -1))
return -1;
return 0;
}
int value()//評估函式
{
p = 0;
q = 0;
//將棋盤中的空格填滿自己的棋子,既將棋盤陣列中的0變為1
for (int i = 0; i<3; i++)
for (int j = 0; j<3; j++)
if (cur[i][j] == 0)
tmpQP[i][j] = 1;
else
tmpQP[i][j] = cur[i][j];
//電腦一方
//計算每一行中有多少行的棋子連成3個的
for (int i = 0; i<3; i++)
p += (tmpQP[i][0] + tmpQP[i][1] + tmpQP[i][2]) / 3;
//計算每一列中有多少列的棋子連成3個的
for (int i = 0; i<3; i++)
p += (tmpQP[0][i] + tmpQP[1][i] + tmpQP[2][i]) / 3;
//斜行有沒有連成3個的?
p += (tmpQP[0][0] + tmpQP[1][1] + tmpQP[2][2]) / 3;
p += (tmpQP[2][0] + tmpQP[1][1] + tmpQP[0][2]) / 3;
//將棋盤中的空格填滿對方的棋子,既將棋盤陣列中的0變為-1
for (int i = 0; i<3; i++)
for (int j = 0; j<3; j++)
if (cur[i][j] == 0)tmpQP[i][j] = -1;
else tmpQP[i][j] = cur[i][j];
//對方
//計算每一行中有多少行的棋子連成3個的
for (int i = 0; i<3; i++)
q += (tmpQP[i][0] + tmpQP[i][1] + tmpQP[i][2]) / 3;
//計算每一列中有多少列的棋子連成3個的
for (int i = 0; i<3; i++)
q += (tmpQP[0][i] + tmpQP[1][i] + tmpQP[2][i]) / 3;
//斜行有沒有連成3個的?
q += (tmpQP[0][0] + tmpQP[1][1] + tmpQP[2][2]) / 3;
q += (tmpQP[2][0] + tmpQP[1][1] + tmpQP[0][2]) / 3;
return p + q;
}
//極大結點下界為A,極小結點上界為B
int cut(int &val, int dep, bool max) //主演算法部分,實現A-B剪枝的演算法,val為上一層的評價值,dep為搜尋深度,max記錄上一層是否為極大層
{
if (dep == depth || dep + num == 9) //如果搜尋深度達到最大深度,或者深度加上當前棋子數已經達到9,就直接呼叫評價函式
{
return value();
}
int i, j, flag, temp;
bool out = false; //out記錄是否剪枝,初始為false
if (CheckWin() == 1) //如果使用者玩家輸了,就置上一層的評價值為無窮(用很大的值代表無窮)
{
val = 10000;
return 0;
}
if (max) //如果上一層是極大層,本層則需要是極小層,記錄flag為無窮大;反之,則為記錄為負無窮大
flag = 10000; //flag記錄本層節點的極值
else
flag = -10000;
for (i = 0; i<3 && !out; i++) //兩重迴圈,遍歷棋盤所有位置
{
for (j = 0; j<3 && !out; j++)
{
if (cur[i][j] == 0) //如果該位置上沒有棋子
{
if (max) //並且為上一層為極大層,即本層為極小層,輪到使用者玩家走了。
{
cur[i][j] = -1; //該位置填上使用者玩家棋子
if (CheckWin() == -1) //如果使用者玩家贏了
temp = -10000; //置棋盤評價值為負無窮
else
temp = cut(flag, dep + 1, !max); //否則繼續呼叫ab剪枝函式
if (temp<flag) //如果下一步棋盤的評價值小於本層節點的極值,則置本層極值為更小者,即後輩結點極小值<=祖先節點極大值
flag = temp;
if (flag <= val) //如果本層的極值已經小於上一層的評價值,則不需要搜尋下去,剪枝 A剪枝
out = true;
}
else //如果上一層為極小層,演算法與上面剛好相反
{
cur[i][j] = 1;//該位置填上電腦玩家棋子
if (CheckWin() == 1)//如果電腦玩家贏了,置棋盤評價值為正無窮
temp = 10000;
else
temp = cut(flag, dep + 1, !max); // 否則繼續呼叫ab剪枝函式
if (temp>flag) //如果下一步棋盤的評價值大於本層節點的極值,則置本層極值為更小者,即後輩結點極大值>=祖先節點極小值
flag = temp;
if (flag >= val) //如果本層的極值已經大於上一層的評價值,則不需要搜尋下去,剪枝 B剪枝
out = true;
}
cur[i][j] = 0; //把模擬下的一步棋還原,回溯
}
}
}
if (max) //根據上一層是否為極大層,用本層的極值修改上一層的評價值
{
if (flag>val)
val = flag;
}
else
{
if (flag<val)
val = flag;
}
return flag; //函式返回的是本層的極值
}
//使用者通過此函式來輸入落子的位置,
//比如,使用者輸入31,則表示使用者在第3行第1列落子。
void UserInput()
{
int pos, x, y;
L1: cout << "Please input your qizi (xy):\n ";
cin >> pos;
x = pos / 10, y = pos % 10;
if (x>0 && x<4 && y>0 && y<4 && cur[x - 1][y - 1] == 0)
{
cur[x - 1][y - 1] = -1;
}
else
{
cout << "Input Error!\n";
goto L1;
}
}
//主程式
int main()
{
int m = -10000, val = -10000, dep = 1; //m 用來存放最大的val
int x_pos, y_pos; //記錄最佳走步的座標
Init();
cout << "Qipan: " << endl;
PrintQP();
char IsFirst;
cout << "Do you want do first?(y/n)";
cin >> IsFirst;
while (IsFirst != 'y'&&IsFirst != 'n')
{
cout << "ERROR!" << "Do you want do first?(y/n)";
cin >> IsFirst;
}
if (IsFirst == 'y')
{
L4: // 人先走
UserInput();
PrintQP();
cout << endl;
num++;
value();
if (q == 0)
{
cout << "DOWN GAME!" << endl;
system("pause");
return 0;
}
if (CheckWin() == -1)
{
cout << "You Win! GAME OVER." << endl;
system("pause");
return 0;
}
for (int x = 0; x<3; x++)
for (int y = 0; y<3; y++){
if (cur[x][y] == 0){
cur[x][y] = 1;
cut(val, dep, 1);
if (CheckWin() == 1) {
cout << "The computer put the qizi at:" << x + 1 << y + 1 << endl;
PrintQP();
cout << "The computer WIN! GAME OVER." << endl;
system("pause");
return 0;
}
if (val>m) {
m = val;
x_pos = x; y_pos = y;
}
val = -10000;
cur[x][y] = 0;
}
}
cur[x_pos][y_pos] = 1;
val = -10000; m = -10000; dep = 1;
cout << "The computer put the qizi at:" << x_pos + 1 << y_pos + 1 << endl;
PrintQP();
cout << endl;
num++;
value();
if (q == 0)
{
cout << "DOWN GAME!" << endl;
system("pause");
return 0;
}
goto L4;
}
else
{ // 計算機先走
L5:
for (int x = 0; x<3; x++)
for (int y = 0; y<3; y++){
if (cur[x][y] == 0){
cur[x][y] = 1;
cut(val, dep, 1);
if (CheckWin() == 1) {
cout << "The computer put the qizi at:" << x + 1 << y + 1 << endl;
PrintQP();
cout << "The computer WIN! GAME OVER." << endl;
system("pause");
return 0;
}
if (val>m) {
m = val;
x_pos = x; y_pos = y;
}
val = -10000;
cur[x][y] = 0;
}
}
cur[x_pos][y_pos] = 1;
val = -10000; m = -10000; dep = 1;
cout << "The computer put the qizi at:" << x_pos + 1 << y_pos + 1 << endl;
PrintQP();
cout << endl;
num++;
value();
if (p == 0)
{
cout << "DOWN GAME!" << endl;
system("pause");
return 0;
}
UserInput();
PrintQP();
cout << endl;
num++;
value();
if (p == 0)
{
cout << "DOWN GAME!" << endl;
system("pause");
return 0;
}
if (CheckWin() == -1)
{
cout << "Conguatulations! You Win! GAME OVER." << endl;
system("pause");
return 0;
}
goto L5;
}
system("pause");
return 0;
}