人机对战五子棋游戏(纯字符版)
主要算法思想:
1.用一个二维数组记录棋盘上15*15个位置的棋形。
2.电脑决策:遍历所有空位,对每个空位估值评分(总分=对于电脑而言的价值+对于玩家而言的价值),取分值最大的点作为电脑落子点。
3.估值评分:通过对某个空位各个方向上的各种棋形进行分析及打分总结出价值数组,价值数组决定AI的高低。
试玩棋局:
游戏代码清单:
/*WuZiQi.V3.0.2011.04.21 By:Estrong. All Rights Reserved*/
/*更新日志:
2011.04.18~04.21完成基本功能。
2011.05.01 改进棋盘,优化视图。
2011.06.01 添加悔棋功能,优化算法,改进价值数组,提高AI智商:
W[3][5]={{2,10,100,1000,5000},{-50,1,-10,-10,5000},{1,6,30,300,5000}}。
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#define hei_qi -1
#define bai_qi 1
/***************************************************************************/
int PrintMap(int x,int y,int map[][16]) /*棋盘更新及输出*/
{
int i,j;
char line[32];
printf(" WuZiQi.V3.0.2011.04.21\nBy:Estrong. All Rights Reserved\n");
line[0]='\40';
for(i=1;i<=29;i++)
line[i]='_';
line[i]='\0' ;
puts(line);
for(i=1;i<=15;i++)
{
line[0]='|';
for(j=1;j<=15;j++)
{
/*line[4*j-3]='_'; 棋子左边_*/
if(map[i][j]==0) line[2*j-1]='_';/*用0表示空位*/
else if(map[i][j]==hei_qi) line[2*j-1]='x'; /*黑棋用'x'对应的字符图案输出*/
else line[2*j-1]='o'; /*白棋用'o'对应的字符图案输出*/
/* line[4*j-1]='_'; 棋子右边_*/
line[2*j]='|';
if(i==x) line[y]='*';
}
line[31]='\0';
puts(line);
}
printf("\n[8]Up [2]Down [4]Left [6]Right\n[5]Put [r]Regret [e]Exit.\n");
return 0;
}
/***************************************************************************/
int Around(int color,int x,int y,int dir,int SibAir,int map[][16])/* 返回八个方向中某一个方向上与此空位紧挨的同色棋子数或者之后的紧接空位数n */
{ /* SibAir=1返回紧挨同色棋子数 */
int dx,dy,n=0; /* SibAir=0返回紧接空位数 */
switch(dir)
{
case 1:dx=-1;dy=0;break; /*Up*/
case 2:dx=-1;dy=1;break;/*UpRight*/
case 3:dx=0;dy=1;break; /*Right*/
case 4:dx=1;dy=1;break; /*RightDown*/
case 5:dx=1;dy=0;break; /*Down*/
case 6:dx=1;dy=-1;break;/*DownLeft*/
case 7:dx=0;dy=-1;break; /*Left*/
case 8:dx=-1;dy=-1;break; /*LeftUp*/
default:break;
}
x=x+dx;y=y+dy;
if(map[x][y]==color)/*有同色棋子的情况下*/
while(x!=0&&y!=0&&x!=16&&y!=16&&map[x][y]==color) /*紧挨的同色棋子数*/
{ n++;
x=x+dx;y=y+dy;
}
if(SibAir==0)/*紧接的空位数*/
{
n=0;
while(x!=0&&y!=0&&x!=16&&y!=16&&map[x][y]==0)
{
n++;
x=x+dx;y=y+dy;
}
}
return n;
}
/***************************************************************************/
int Weight(int color,int x,int y,int dir,int map[][16]) /*返回四个方向中某一个方向的价值*/
{
int W[3][5]={{2,10,100,1000,5000},{-50,1,-10,-10,5000},{1,6,30,300,5000}};/*根据一个方向上各种棋形总结出的估值数组*/
/*两边都无界*/ /*两边都有界*/ /*一边有界一边无界*/
int sib,air,sib1,air1,sib2,air2,weight=0;
sib1=Around(color,x,y,dir,1,map) ;
sib2=Around(color,x,y,dir+4,1,map);
sib=sib1+sib2;
air1=Around(color,x,y,dir,0,map);
air2=Around(color,x,y,dir+4,0,map);
/*air=air1+air2;*/
if(sib==4) return 8000;
if(sib>0)
{
if(air1>0&&air2>0) /*两边都无界(用价值数组第一个下标为0表示)*/
weight=W[0][sib];
else if((air1==0)&&(air2==0)) /*两边都有界(用价值数组第一个下标为1表示)*/
weight=W[1][sib];
else /*一边有界一边无界(用价值数组第一个下标为2表示)*/
weight=W[2][sib];
}
return weight;
}
/***************************************************************************/
int Score(int color,int x,int y,int map[][16]) /*返回该空位八个方向上 黑棋总价值+白棋总价值*/
{
int Wnow=0,Wnext=0,Score,i;
for(i=1;i<=4;i++)
{
Wnow+=Weight(color,x,y,i,map); /*对于当前要下子的一方(color)如果下此处的价值*/
Wnext+=Weight(-1*color,x,y,i,map); /*当前要下子的一方(color)如果没下此处,被对手下的话相对于对手的价值*/
}
Score=Wnow+Wnext;
return Score;
}
/***************************************************************************/
int IsWin(int color,int x,int y,int map[][16]) /* 若已分出胜负提示并返回1,否则返回0 */
{
int i;
for(i=1;i<=4;i++)
if((Around(color,x,y,i,1,map)+Around(color,x,y,i+4,1,map))>=4) return 1;
return 0;
}
/***************************************************************************/
int ComputerDecide(int color,int map[][16],int regret[],int *top) /*遍历所有空位点,取价值最大的点作为电脑落子点 并 返回是否已分出胜负*/
{ /*regret[510]堆栈存储双方下子横纵坐标*/
int i,j,sx,sy,t,max=0;
for(i=1;i<=15;i++)
for(j=1;j<=15;j++)
if(map[i][j]==0)
{
t=Score(color,i,j,map);
if(t>max) {max=t;sx=i;sy=j;}
}
map[sx][sy]=color;
regret[++(*top)]=sy;regret[++(*top)]=sx; /*入栈*/
return(IsWin(color,sx,sy,map));
}
/***************************************************************************/
int GetRand(int n,int m)/* 获得n到m之间的随机数,n<m */
{ int res;
srand((unsigned)time(NULL));
res=rand()%m+1;
if(res<n) res=(n+m)/2;
return res;
}
/***************************************************************************/
int main()
{
int i,j,x=8,y=15,ren_color,map[16][16],win1=0,win2=0,sure,regret[510]={0},top=-1,temp1,temp2;
char choose='t';
system("cls");
for(i=0;i<=15;i++) /* 棋盘初始化 */
for(j=0;j<=15;j++)
map[i][j]=0;
printf("Please choose your color:\n");
printf("[1]Black x [2]White o\n");
while(choose!='1'&&choose!='2')
{
choose=getch();
if(choose=='1') ren_color=hei_qi;
else
{
ren_color=bai_qi;
map[GetRand(3,12)][GetRand(3,12)]=-1; /* 玩家选择白棋的话让电脑黑棋先下第一子,位置随机*/
}
}
system("cls");PrintMap(x,y,map);
while((!win1)&&(!win2))
{ sure=0;
choose='0';
while(!sure) /* 玩家未落子时,光标移动 */
{
choose=getch();
switch(choose)
{
case '8':x=x-1;if(x==0) x=15;break; /*上移*/
case '2':x=x+1;if(x==16) x=1;break; /*下移*/
case '4':y=y-2;if(y<0) y=29;break; /*左移*/
case '6':y=y+2;if(y>30) y=1;break; /*右移*/
case '5':if(map[x][(y+1)/2]==0) /* 玩家落子 */
{
map[x][(y+1)/2]=ren_color;
regret[++top]=(y+1)/2; /*入栈*/
regret[++top]=x;sure=1;
} break;
case 'r':if(top>=3) /*悔棋*/
{
temp1=regret[top--]; /*出栈*/
temp2=regret[top--];
map[temp1][temp2]=0;
temp1=regret[top--];
temp2=regret[top--];
map[temp1][temp2]=0;
sure=0;
}break;
case 'e':exit(0);
default:break;
}
system("cls");/* 棋盘更新 */
PrintMap(x,y,map);
}
win1=IsWin(ren_color,x,(y+1)/2,map);/* 玩家赢了没 */
if(!win1)
{
win2=ComputerDecide(-1*ren_color,map,regret,&top);/* 电脑落子,电脑赢了没 */
system("cls");/* 棋盘更新 */
PrintMap(x,y,map);
}
}
printf("*******************************");
if(win1) printf("\n* You Win! *\n");
else printf("\n* You lost! *\n");
printf("*******************************");
printf("\n [r]Replay [e]Exit");
choose='t';
while(choose!='r'&&choose!='e')
{
choose=getch();
if(choose=='r') main();
if(choose=='e') exit(0);
}
return 0;
}
BUG1:
BUG2:
改进与完善:
本程序对于棋形的分析尚有改进的空间,价值数组也还可以进一步斟酌。对于前面提到的bug1、bug2情形,可以增加一个价值数组把对于电脑自己和对于玩家而言的价值分开打分。此外,也可不遍历所有空位,只计算一定范围内的空位价值以提升搜索效率。