五子棋人机交互 程序分两部分:1。核心程序 2。windows图形界面程序
此处只介绍核心程序部分。下面,我们边看边侃。
//========定义文件部分=============================
#define MAX_LENGTH 19 //棋盘格数
#define COMPUTER 1 //计算机棋子
#define PLAYER 2 //玩家棋子
int qp[MAX_LENGTH][MAX_LENGTH]; //定义棋盘
int iswin; //输赢标记,1=计算机胜,2=玩家胜,0=未分胜负;
#define RandInt(n) (rand()%n) //随机数宏
struct LIST
{
int id;
struct LIST *next;
}*PList=NULL,*CList=NULL; //定义链表,Plist为玩家棋局链表指针,用于阻击,Clist为计算机棋局链表指针,用于攻击
//=========核心程序代码部分=========================
//本函数将数据add添加到链表中,并将数据按从大到小排序
//add低位为棋盘位置,高位代表该位置在当前棋局中的优先级
static int AddList(struct LIST **List,int add)
{
struct LIST *tmp=*List;
struct LIST **temp=List;
int atemp=add>>16;
int id;
struct LIST *newlist=malloc(sizeof(*newlist));
if(!newlist)
return 1;
while(tmp)
{
id=(tmp->id)>>16;
if(id<=atemp)
break;
if(id>atemp)
{
temp=&(tmp->next);
tmp=tmp->next;
}
}
newlist->id=add;
newlist->next=tmp;
*temp=newlist;
return 0;
}
//函数获得指定链中最大优先级的值
static int GetMax(struct LIST *List)
{
if(List)
return (List->id>>16);
return 0;
}
//函数获得指定链中的链首数据
static int GetLast(struct LIST **List)
{
if(*List)
{
int ret;
struct LIST *temp;
ret=((*List)->id&0xffff);//取低字节棋盘位置数据
temp=*List;
*List=(*List)->next;
free(temp);
return ret;
}
return 0;
}
#define DestroyList(l) while(GetLast(&l)) //宏——销毁链表
//函数探测参数tmp指定的棋盘位置的优先级并加入相应的链表中
//凡进入该函数扫描的点,皆为棋盘上的空位置。我们将在此处分析该位置在棋局中的地位。
//tmp=y*MAX_LENGTH+x
static int AddTo_List(int tmp)
{
int temp;
int py,px;
py=tmp/MAX_LENGTH;
px=tmp%MAX_LENGTH;
//探测计算机在此位置的优先级
qp[py][px]=COMPUTER;
temp=scan(px,py,0);//最后一个参数必须为0,否则将进入死循环。
AddList(&CList,(temp<<16)+tmp);
//探测玩家在此位置的优先级
qp[py][px]=PLAYER;
temp=scan(px,py,0);//同上。
AddList(&PList,(temp<<16)+tmp);
qp[py][px]=0;//恢复空子状态。
return 0;
}
//函数对指定的棋盘位置进行格式化全方位扫描,并返回该位置在棋局中的优先级
//进行格式化扫描,可以减少扫描的冗余度,提高扫描效率。这对于大棋盘,如本例的19*19
尤为适用。
//mode控制扫描方式,0=试验性扫描,不将扫描结果加入链表。1=实战性扫描,将扫描结
果加入链表。程序可利用链表中的数据,进行下一步棋的决策。
//px,py在循环中反复使用,我们为其加上const标记,防止不必要的麻烦。
static int scan(const int px,const int py,int mode)
{
register int i;
int play=qp[py][px];//获得该位置棋子
int ret=0;rtemp=0;//返回值
int temp[4];
int base;
base=RandInt(8);//生成随机数,使用不同的起点,使棋局具有一定的随机性
//对该棋子八个方向进行扫描
for(i=0;i<8;i++)
{
int x=px;
int y=py;
int side=0;//边界标记
register j;
switch((base+i)%8)
{
case 0://x--
{
//格式化,首先定位到第一个不与play相同的位置。
//这样,在此方向上,我们便可以只考虑四格棋盘位置,大大减少了不必要的开销。
do
{
x--;
}while(x>=0&&qp[y][x]==play);
//该方向前面没有充足的位置,置标记位为1。这样,我们可以略去无效探测。
if(x+5>MAX_LENGTH)
{
side=1;
}
else
{
x+=2;//沿该方向向前,跳过第一个与play相等的位置,对前方四个位置进行纪录
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);//将棋盘位置合成整型数据保存
x++;
}
}
break;
}
case 1://x--;y--;
{
do
{
x--;
y--;
}while(x>=0&&y>=0&&qp[y][x]==play);
if(x+5>MAX_LENGTH||y+5>MAX_LENGTH)
{
side=1;
}
else
{
x+=2;
y+=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
x++;
y++;
}
}
break;
}
case 2://y--;
{
do
{
y--;
}while(y>=0&&qp[y][x]==play);
if(y+5>MAX_LENGTH)
{
side=1;
}
else
{
y+=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
y++;
}
}
break;
}
case 3://x--;y++;
{
do
{
x--;
y++;
}while(x>=0&&y<MAX_LENGTH&&qp[y][x]==play);
if(x+5>MAX_LENGTH||y-5<0)
{
side=1;
}
else
{
x+=2;
y-=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
x++;
y--;
}
}
break;
}
case 4://x++;
{
do
{
x++;
}while(x<MAX_LENGTH&&qp[y][x]==play);
if(x-5<0)
{
side=1;
}
else
{
x-=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
x--;
}
}
break;
}
case 5://x++;y--;
{
do
{
x++;
y--;
}while(x<MAX_LENGTH&&y>=0&&qp[y][x]==play);
if(x-5<0||y+5>MAX_LENGTH)
{
side=1;
}
else
{
x-=2;
y+=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
x--;
y++;
}
}
break;
}
case 6://x++;y++;
{
do
{
x++;
y++;
}while(x<MAX_LENGTH&&y<MAX_LENGTH&&qp[y][x]==play);
if(x-5<0||y-5<0)
{
side=1;
}
else
{
x-=2;
y-=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
x--;
y--;
}
}
break;
}
case 7://y++;
{
do
{
y++;
}while(y<MAX_LENGTH&&qp[y][x]==play);
if(y-5<0)
{
side=1;
}
else
{
y-=2;
for(j=0;j<4;j++)
{
temp[j]=(MAX_LENGTH*y+x);
y--;
}
}
break;
}
}
if(!side)
{
//该部分是决定优先级的关键部分,本例只使用了简单的优先级决定方案,可以通过修改该部分使程序具有更高的智能。
int t=0;
int k[4]={8,4,2,1};
int kt[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
//对纪录的棋盘位置进行检测
for(j=0;j<4;j++)
{
int jx=temp[j]%MAX_LENGTH;
int jy=temp[j]/MAX_LENGTH;
int p=qp[jy][jx];
if(p==play)
t+=k[j];
else if(p==0)//可利用位置
{
if(mode)
AddTo_List(temp[j]);//对可利用位置进行探测纪录
}
else//存在阻隔
{
t--;//此处有可能使t=-1,因为t用于数组下标,所以我们必须将其排除。
if(t<0)
t=0;
break;
}
}
if(t==0xf&&mode)//若五子连成一线,返回零
return 0;
if(ret<kt[t])
{
ret=kt[t];
if(ret>rtemp)
{
int t=ret;
ret=rtemp;
rtemp=t;
}
}
}
}
return (ret+rtemp);//当前局势
}
//初始化棋盘
void initqp(void)
{
register int i;
register int j;
for(i=0;i<MAX_LENGTH;i++)
for(j=0;j<MAX_LENGTH;j++)
qp[i][j]=0;
iswin=0;
srand(time(NULL));//初始化随机生成器
}
//主函数,检测玩家所走位置,返回计算机所走位置
//px,py代表玩家棋子位置。
int five(int px,int py)
{
struct LIST **list;
int tmp;
if(qp[py][px]!=0)//该位置已存在棋子
{
return -1;
}
//对玩家所走棋子进行扫描
qp[py][px]=PLAYER;
if(!scan(px,py,1))
{
//玩家胜
DestroyList(PList);
DestroyList(CList);
iswin=PLAYER;
return 0;
}
if(GetMax(PList)>GetMax(CList))//确定攻击性
list=&PList;//防御
else
list=&CList;//攻击
while(tmp=GetLast(list))//获取最大优先级的棋盘位置
{
px=tmp%MAX_LENGTH;
py=tmp/MAX_LENGTH;
if(qp[py][px]!=0)//该位置不为空则继续循环
{
if(GetMax(PList)>GetMax(CList))//重新确定攻击性
list=&PList;
else
list=&CList;
continue;
}
break;
}
if(!tmp) //在链表中未找到数据则生成随机数据
{
do
{
px=RandInt(MAX_LENGTH);
py=RandInt(MAX_LENGTH);
}while(qp[py][px]!=0);
}
//计算机走子
qp[py][px]=COMPUTER;
if(!scan(px,py,1))
{
//计算机胜
DestroyList(PList);
DestroyList(CList);
iswin=COMPUTER;
}
return ((py*MAX_LENGTH)+px);//返回走子位置
}
核心程序部分就这么多,图形界面部分只需调用函数initqp()初始化棋盘,然后不断调用函数five(int px,int py)即可。