大概是5月份人工智能导论的作业。(在这贴一下代码和总结报告)
一、问题:实现井字棋游戏。即玩家先手或后手与电脑进行井字棋游戏,使得电脑方总是获胜或是平局。井字棋游戏:在一个空白的3*3棋盘内,两名玩家轮流落子。若有一方的棋子中有3个棋子可连为一条线(横线、竖线或对角线),则游戏结束,该玩家胜利。若棋盘上已没有地方可以落子,则游戏结束,双方平局。
二、原理:
最大最小值法。对于棋盘有一个估值函数。对于一个局面,其估值越大,对一方(记作A)越有利;其估值越小,对另一方(记作B)越有利。当A方行动时,必定希望他落子后局面的估值最大;当B方行动时,必定希望他落子后局面的估值最小。假定双方足够聪明,他们就会将接下来的棋局情况模拟一遍,选出那个在双方都不发生失误情况下对自己最有利的一步。
在这个程序中,设定了这样的估值函数,如果横线、竖线或对角线中,有1个玩家方的棋子和2个空白格子那么估值+1;有2个玩家方的棋子和1个空白格子那么估值+5;有1个电脑方的棋子和2个空白格子那么估值-1;有2个电脑方的棋子和1个空白格子那么估值-5;有3个玩家方的棋子那么估值+1000;有3个电脑方的棋子那么估值-1000。(最后两种情况估值的设定是为了判断输赢)。
三、实现
在程序中玩家每走完一步,电脑就会模拟自己将棋子下在当前的某一个空白格上后,玩家与电脑都选择最优策略所能达到的最后局面的估值,在这些落子方案中选择一个最后估值分数最小的作为自己的落子方案。如果存在多个最后估值分数最小的落子方案,就在他们中随机一个作为最终的落子方案。
四、代码
#include<cstdio> #include<cstring> #include<algorithm> #include<time.h> #include<stdlib.h> using namespace std; int tim,f[15]; void print()//输出当前棋盘 { for (int i=0;i<9;i++) { if (f[i]==0) printf("_ "); else if (f[i]==1) printf("O "); else if (f[i]==2) printf("X "); if ((i+1)%3==0) printf(" "); } printf(" "); return; } int re(int x,int y,int z)//行、列、对角线分别估分 { int i,a[5],cnt1=0,cnt2=0; a[0]=x;a[1]=y;a[2]=z; for (i=0;i<3;i++) if (a[i]!=0) { if (a[i]==1) cnt1++; else if (a[i]==2) cnt2++; } if (cnt2==0) { if (cnt1==1) return 1;//一个玩家棋子和两个空格的得分 if (cnt1==2) return 5;// 两个玩家棋子和一个空格的得分 if (cnt1==3) return 1000;//玩家获胜 } else if (cnt1==0) { if (cnt2==1) return -1;//同理,电脑棋子的情况 if (cnt2==2) return -5; if (cnt2==3) return -1000; } return 0; } int jud()//估值函数,分数越低对电脑越有利,越高对玩家越有利 { int i,cnt=0,sc=0; for (i=0;i<3;i++) { sc+=re(f[i],f[i+3],f[i+6]); sc+=re(f[i*3],f[i*3+1],f[i*3+2]); } sc+=re(f[0],f[4],f[8]); sc+=re(f[2],f[4],f[6]);//3行,3列,2条对角线,一共8种 return sc;//返回当前局面得分 } int dfs(int player)//电脑模拟接下来的棋局,找到在接下来玩家没有失误的情况下对玩家最不利的下法 { //player:0玩家,1电脑 int now=jud();//当前局势评分 if (now>900 || now<-900) return now;//玩家已赢或已输 int i,re,flag=1,ma=-999999,mi=999999; for (i=0;i<9;i++)//枚举每种落子情况 if (!f[i]) { flag=0; f[i]=player+1; re=dfs(player^1);//递归 f[i]=0; ma=max(re,ma); mi=min(re,mi);//接下来的最高/低评分 } if (flag) return now;//flag==1代表棋盘已满 if (player==0) return ma;//如果此时是玩家的回合,就选择评分最高的局面 return mi;//如果此时是电脑的回合,就选择评分最低的局面 } void player_() { int r_flag=1,x,y,k; printf("轮到你了,请输入棋子坐标x和y(0<=x,y<=2) "); while (r_flag) { scanf("%d%d",&x,&y); k=x*3+y; if (f[k] || x>2 || y>2 || x<0 || y<0) printf("无效输入,请重新输入 "); else r_flag=0; } f[k]=1;tim++; print(); return; } void computer_() { int mi=999999,note[15],tot=0,i,now; for (i=0;i<9;i++)//枚举电脑的落子 if (!f[i]) { f[i]=2; if (jud()<-900) { note[++tot]=i;break; } now=dfs(0); if (now<mi) tot=0,note[++tot]=i,mi=now; else if (now==mi) note[++tot]=i; f[i]=0; } now=note[rand()%tot+1]; f[now]=2; tim++; printf("轮到电脑,棋子坐标%d,%d ",now/3,now%3); print(); return; } int play() { while (tim<9) { player_(); if (jud()>900) return 1;//玩家获胜 if (tim==9) break; computer_(); if (jud()<-900) return 0;//电脑获胜 } return 2;//平局 } int main() { int i,op,fin; srand((unsigned)time(NULL)); while (1) { for (i=0;i<9;i++) f[i]=0;//每次游戏前清空棋盘 tim=0;//时间重置为零 printf("你想要先手(1)或后手(2)? "); scanf("%d",&op); while (op!=1 && op!=2) { printf("无效输入,请输入先手(1)或后手(2) "); scanf("%d",&op); } if (op==2) computer_(); else print(); fin=play(); if (fin==1) printf("你赢了 "); else if (fin==0) printf("你输了 "); else if (fin==2) printf("平局 "); printf("再来一局吗?是(1) or 否(0) "); scanf("%d",&op); if (op==0) return 0; else if (op!=1) printf("无效输入,那么我假设你想要再来一局 "); } return 0; }
五、运行时截图
一种玩家先手,电脑获胜的情况,如下图所示。
一种玩家先手平局的情况。
一种玩家后手,电脑获胜的情况。
一种玩家后手的平局情况