电脑围棋程序编写思路
荷蒲 wq.hepu.cn
荷蒲围棋软件的编写思路。
在围棋程序的实现中,较为关键的是围棋电子棋盘的
。其他所有功能都是围绕围棋
电子棋盘展开的。电子围棋盘的核心是围棋棋盘的数据描述。
1、首先定义围棋子信息:
#define EDGE 23 //棋盘最大格数
#define MAXMM 500 //最大手数
//color
示棋子颜色,x,y表示在棋盘上的坐标
//num表示下子的顺序。=0表示提前摆放的子。
//zt 表示棋子状态
//qs 表示棋子的气数
//sm 表示有说明信息
typedef struct qizi
{
int color,x,y,num,zt,qs,sm; } qizi;
qizi qipu[MAXMM]; //棋谱信息
qizi qipan[EDGE][EDGE]; //棋盘信息
2、紧接着要考虑的是下棋相关信息。
int nk=0; //显示棋子序号,nk=2显示序号,1=气数
int BoardLines=19; //棋盘线数,默认19
bool ComputerPlaying; //1=该计算机下 0=人下
bool Computerp1=0; //1=计算机下黑 0=人下
bool Computerp2=0; //1=计算机下白 0=人下
int PlayType=0; //2=人-人,1=人-计算机,13=人-网络,0=没有开始,-1=删除棋盘上死子,-2=暂停,3=布黑子,4=布白子,9=演示,11=学习
int PlayType1=0; //2=人-人,1=人-计算机,13=人-网络,0=没有开始,-1=删除棋盘上
-2=暂停,3=布黑子,4=布白子,11=学习 死子,
int MoveCount,MoveCount1; //计步器,记录落子手数,自然顺序
int Playnum=0,Playnum1=0; //要标识的围棋手数,下棋顺序
int CurrentX; //记录热子X坐标,
int CurrentY; //记录热子Y坐标
char CurrentWho; //记录当前棋子颜色,0=黑 1=白 2=空(终局等,待写)
char CurrentWho1; //备份上一次CurrentWho
int timew=0,timeb=0; //计时器设定数据
int sdy1=0,sdy2=0; //学习功能上使用
int gz; //规则0=中国规则,1=日本规则,2=应氏规则
bool plays1=true; //学习持黑
bool plays2=false; //学习持白
3、围棋电子棋盘的数据初始化。
//数据初始化
void wqinit(void)
{
BoardLines=19; //19X19路
围棋盘
MoveCount=0; //一步棋未下,自然顺序
MoveCount1=0; //一步棋未下
ComputerPlaying=1; //默认电脑执黑先行
CurrentWho=0; //默认黑先; 黑方=0;白方=1;空方=2;
CurrentX=0; //当前一步棋的X坐标,水平从左至右为1...19
CurrentY=0; //当前一步棋的Y坐标,垂直从上到下为1...19
timew=0,timeb=0;
Playnum=0; //下棋顺序
Playnum1=0;
//下面是棋盘初始化
for (int i=0;i<=BoardLines;i++)
for (int j=0;j<=BoardLines;j++)
{
qipan[i][j].color=2;
qipan[i][j].x=0;
qipan[i][j].y=0;
qipan[i][j].num=0;
qipan[i][j].zt=0;
}
//清空棋谱记录,全部设为无效点。QiPu[0][x]留作它用
for (int i=0;i<500;i++)
{
qipu[i].color=2;
qipu[i].x=0;
qipu[i].y=0;
qipu[i].num=0;
qipu[i].zt=0;
qipu[i].sm=0;
qpsm1[i].n=0;
qpsm1[i].t=0;
strcpy(qpsm1[i].sm,"/");
}
}
4、根据围棋规则编写的一些相关处理函数模块
围棋棋子的吃子,是根据围棋棋子的气数来计算的。气数为0的棋子应当从棋盘上拿掉。
围棋气数的计算问题,应当说是围棋软件的核心问题。
“气”是指棋子在棋盘上可以连接的交叉点,也是棋子的出路。
围棋的气数计算,要考虑一个围棋子的连通问题。
下面的图形中,交叉点的X代表棋子的气,
[图1]
图1中右上角的黑子,有两个交叉点和它的直线相接,因此它有两口气。左上角的黑子
有三口气,而下边的黑子有四口气。
[图2]
图2中右边的黑子有四口气,中间连接在一起的两个黑子有六口气,而右边连接在一起的三个黑子有八口气。连接在一起的棋子越多,气也越多。
[图3]
图2中同样是四个连接在一起的黑子,左边的四个黑棋有十口气,中间的黑棋只有九口气,而右边的黑棋仅有八口气。
从上面分析,可以得出,计算一个棋子的气,还有分析该棋子周围的情况,因此我们利用递归函数来解决围棋气数的计算。实现
看下面程序断。
int go[EDGE][EDGE];
/*表示棋盘 其中第0路和第20路为沉余数据
在 (X,Y)下黑子
go[x][y]=0;// 0表示黑
在 (X,Y)下白子
go[x][y]=1;// 1表示白子
在 (X,Y)提子
go[x][y]=2; //2表示空子
当前棋步脱先pass则 当前棋步 X坐标=0,y=0
否则 1<=x<=19, 1<=x<=19,
*/
int gokong[EDGE][EDGE]; //0=该空点未曾计算过气,1=已计算,避免重复计算公气 int gozi[EDGE][EDGE]; //0=该子未计算串气,1=已计算,避免重复计算同一个子的气 int goqi; //气数
//以上变量声明为全局变量
void str_qi(int x,int y,int hb) {//本函数计算 x,y 处的hb颜色棋子的气
gozi[x][y]=1; //标记本子已经计算过气
/////////////////////////////////////////////////////右临子
if (x+1<=19)//如果没有超出棋盘边线
{
if ((go[x+1][y]==2)&&(gokong[x+1][y]==0)) //如果右临点为空并且该点未曾计算过气则
{
goqi++; //气数加一
gokong[x+1][y]=1; //标记本空点已经计算过气
}
else if ((go[x+1][y]==hb)&&(gozi[x+1][y]==0))
//否则如果右临点为和本子同色子并且该子未曾计算过气则
str_qi(x+1,y,hb); //递归调用到右临子
}
/////////////////////////////////////////////////////左临子
if (x-1>=1) //果没有超出棋盘边线
{
if ((go[x-1][y]==2)&&(gokong[x-1][y]==0))
//如果左临点为空并且该点未曾计算过气则
{
goqi++; //气数加一
gokong[x-1][y]=1; //标记本空点已经计算过气
}
else if ((go[x-1][y]==hb)&&(gozi[x-1][y]==0))
//否则如果左临点为和本子同色子并且该子未曾计算过气则
str_qi(x-1,y,hb); //递归调用到左临子
}
////////////////////////////////////////////////////下临子
if (y-1>=1)//如果没有超出棋盘边线
{
if ((go[x][y-1]==2)&&(gokong[x][y-1]==0))
//如果下临点为空并且该点未曾计算过气则
{
goqi++; //气数加一
gokong[x][y-1]=1; //标记本空点已经计算过气
}
else if ((go[x][y-1]==hb)&&(gozi[x][y-1]==0))
//否则如果下临子点为和本子同色子并且该子未曾计算过气则
str_qi(x,y-1,hb); //递归调用到下临子
}
////////////////////////////////////////////////////上临点
if (y+1<=19)//如果没有超出棋盘边线
{
if ((go[x][y+1]==2)&&(gokong[x][y+1]==0))
//如果上临点为空并且该点未曾计算过气则
{
goqi++; //气数加一
gokong[x][y+1]=1; //标记本空点已经计算过气
}
else if ((go[x][y+1]==hb)&&(gozi[x][y+1]==0))
//否则如果上临点为和本子同色子并且该子未曾计算过气则 str_qi(x,y+1,hb); //递归调用到上临子
}
}
int str_lib(int x,int y, int hb)
{
int i,j;
for (i = 1; i <= 19; i++) for (j = 1; j <= 19; j++) {
gozi[i][j] = 0; //初始化变量,表示该子未计算串气
gokong[i][j] = 0; //初始化变量,表示该空点未计算串气 }
goqi=0; //串气初值
str_qi(x,y,hb); //调用串气子程序
return(goqi); //全局变量goqi带回串气值
}
void shuanqi(void)
{
int i,j,cc,qq;
for (i = 1; i <=19; i++) for (j = 1; j <= 19; j++) {
go[i][j]=qipan[i][j].color; }
for (i = 1; i <=19; i++) for (j = 1; j <=19; j++) {
if (go[i][j]!=2)
{
cc=go[i][j];
qq=str_lib(i,j,cc); qipan[i][j].qs=qq;
}
}
}
5、围棋的提子(吃子)
提子:就是把没有气的棋子从棋盘上拿掉。
下面函数实现提子功能。
void chizi(void)
{
int i,j,qq,cc;
shuanqi();
for (i = 1; i <=19; i++) for (j = 1; j <= 19; j++) {
qq=qipan[i][j].qs;
cc=qipan[i][j].color; if (qq==0 && cc!=2 && cc!=CurrentWho)
{
qipan[i][j].color=2;
qipan[i][j].x=i;
qipan[i][j].y=j;
qipan[i][j].num=0;
qipan[i][j].zt=0; }
}
}
围棋程序设计的核心,基本完成,下面是输赢的判断问题。
6、围棋胜负判断
围棋盘上共有三百六十一个交叉点,一盘棋的胜负就是由对局双方所占据的交叉点的多少所决定的。更精确地说就是由双方活棋所占据的地域的大小来决定的。一个交叉点为一子,每方以一百八十又二分之一子为归本数,超过此数者为胜,不足此数者为负。
按我国现行的围棋规则规定,由于黑棋先走,有一定的先手威力,应由执黑的一方贴出2(3,4)子。所以黑所占的地域必须超过183(1,4)子(180 (1,2),2(3,4))才能取胜。比如黑棋数出来有185个子,即黑棋1(3,4)子。而白方的地域只要超过177(3,4)子(180(1,2),2(3,4))即可获胜。
[图4]
下面函数实现计算围棋的地域功能。在计算前,应当先去掉围棋中的死子。
// 计算围棋的地域
int sum[3]; //sum[0]=黑子数量,sum[1]=白子数量
int summ(void)
{
int i=0,j=0,c=2,k=2; sum[0]=0;
sum[1]=0;
sum[2]=0;
for (i=1;i<=19;i++) {
k=qipan[i][1].color; for (j=1;j<=19;j++) {
c=qipan[j][i].color; switch (c)
{
case 2:
if (k==2) sum[2]++; else sum[k]++;
break;
case 0:
if (k==0)
{
sum[c]++;
}
else if(k==2)
{
sum[c]=sum[c]+sum[2]+1;
k=c;
sum[2]=0;
}
else if(k==1)
{
sum[c]++;
k=c;
sum[2]=0;
}
break;
case 1:
if (k==1)
{
sum[c]++;
}
else if(k==2) {
sum[c]=sum[c]+sum[2]+1;
k=c;
sum[2]=0;
}
else if(k==0)
{
sum[c]++;
k=c;
sum[2]=0;
}
break;
}
}
}
return sum[0];
}
上面介绍了我国围棋规则,还有日本规则和应氏规则,这两种规则主要是计算胜负的不同,在围棋行棋上没有太大的区别。这方面
,我们以后逐步是完成。
围棋程序的其他方面的设计和编写,主要依赖于计算机的编程环境。例如选择的计算机语言等等。后面我们针对C++Bulider 6的语言特点,专门介绍。