五子棋2.0来临:.゚ヽ(。◕‿◕。)ノ゚.:
功能概述:
- 可以实现人机对战
- 可以实现人人对战
- 可以投降,可以统计对局时间
相比之前的五子棋,核心的算法并没有变,依旧是通过判断每个棋子的当前状态(四个方向上)是否存在五子一线,若存在五子一线则可直接判断当前对局情况。
好了,话不多说,直接上代码咯(´・ω・`)
首先是函数声明和宏定义:
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#define hang 18 //定义的棋盘是18行,18列
#define lie 18
void menu(void);
void Init(void);
void Drawmap(void);
int Judge(int x,int y);
void Computer_game(void);
void Player_game(void);
char map[hang][lie]; //用一个二维数组来表示棋盘
接下来是初始化函数以及打印地图函数:
void Init(void)
{
int i,j;
for(i=4;i<hang-4;i++)
{
for(j=4;j<lie-4;j++)
{
map[i][j]=' '; //初始化里,设定每个棋盘当中每个棋子的落点是空
}
}
}
void Drawmap(void)
{
int i,j;
printf("0 ");
for(i=4;i<lie-8;i++)
{
printf("%d ",i);
}
for(i=10;i<lie-4;i++)
{
printf("%d ",i);
}
printf("\n");
printf("\n");
printf("\n");
printf("\n");
for(i=4;i<hang-8;i++)
{
printf("%d ",i);
for(j=4;j<lie-4;j++)
{
printf("%c ",map[i][j]);
}
printf("\n");
}
for(i=10;i<hang-4;i++)
{
printf("%d ",i);
for(j=4;j<lie-4;j++)
{
printf("%c ",map[i][j]);
}
printf("\n");
}
}
这里注意不要忘记添加序号,包括横向的序号以及纵向的序号。在这里存在着一个关于数组越界的问题。
因为要判断每个棋子在四个方向上的当前状态,那么就会涉及到底部问题。 假如设定的二维数组正好可以储存当前所有的棋子,那么进行判断的时候就会出现数组越界,即当棋子处在最上端或者最下端时,所判断的区域不包含在数组内部。
比如当棋子位于(0,0)时,那么要判断横向的四个棋子,明显就会出现判断(0,-1)以及(0,-2)的情况,可是根本没有-2列,所以这就会是一个大问题,程序会有潜在的崩溃可能。
那么这时候就会有人说:我数组越界不是照样运行得好好的?
那么我觉得这么解释比较形象一些:
坐火车,你买的是一张站票/(ㄒoㄒ)/~~
但是你运气很好,你发现有一个座位正好是空着的o( ̄▽ ̄)ブ
于是你抱着试一试的心理坐在了那个位置上,想着坐一会儿就行,可是没想到你运气特别好一直做到了终点站还顺利下车了
也就是说你数组越界的那一部分的地址并没有其他程序在使用,所以是正常运行起来了,这一般是小程序,大程序的话很容易发生崩溃或出现BUG。
了解了数组越界之后,我们就很容易理解了这种设定更大范围数组的做法。那么我们继续:
判断函数(核心函数):
int Judge(int x,int y)
{
if(map[x][y]==map[x+1][y+1]&&map[x][y]==map[x+2][y+2]&&map[x][y]==map[x+3][y+3]&&map[x][y]==map[x+4][y+4])
{
return 1;
}
if(map[x][y]==map[x-1][y-1]&&map[x][y]==map[x+1][y+1]&&map[x][y]==map[x+2][y+2]&&map[x][y]==map[x+3][y+3])
{
return 1;
}
if(map[x][y]==map[x-1][x-1]&&map[x][y]==map[x-2][y-2]&&map[x][y]==map[x+1][y+1]&&map[x][y]==map[x+2][y+2])
{
return 1;
}
if(map[x][y]==map[x-1][y-1]&&map[x][y]==map[x-2][y-2]&&map[x][y]==map[x-3][y-3]&&map[x][y]==map[x+1][y+1])
{
return 1;
}
if(map[x][y]==map[x-1][y-1]&&map[x][y]==map[x-2][y-2]&&map[x][y]==map[x-3][y-3]&&map[x][y]==map[x-4][y-4])
{
return 1;
}
if(map[x][y]==map[x+1][y-1]&&map[x][y]==map[x+2][y-2]&&map[x][y]==map[x+3][y-3]&&map[x][y]==map[x+4][y-4])
{
return 1;
}
if(map[x][y]==map[x-1][y+1]&&map[x][y]==map[x+1][y-1]&&map[x][y]==map[x+2][y-2]&&map[x][y]==map[x+3][y-3])
{
return 1;
}
if(map[x][y]==map[x-1][y+1]&&map[x][y]==map[x-2][y+2]&&map[x][y]==map[x+1][y-1]&&map[x][y]==map[x+2][y-2])
{
return 1;
}
if(map[x][y]==map[x+1][y-1]&&map[x][y]==map[x-1][y+1]&&map[x][y]==map[x-2][y+2]&&map[x][y]==map[x-3][y+3])
{
return 1;
}
if(map[x][y]==map[x-1][y+1]&&map[x][y]==map[x-2][y+2]&&map[x][y]==map[x-3][y+3]&&map[x][y]==map[x-4][y+4])
{
return 1;
}
if(map[x][y]==map[x][y+1]&&map[x][y]==map[x][y+2]&&map[x][y]==map[x][y+3]&&map[x][y]==map[x][y+4])
{
return 1;
}
if(map[x][y]==map[x][y-1]&&map[x][y]==map[x][y+1]&&map[x][y]==map[x][y+2]&&map[x][y]==map[x][y+3])
{
return 1;
}
if(map[x][y]==map[x][y-2]&&map[x][y]==map[x][y-1]&&map[x][y]==map[x][y+1]&&map[x][y]==map[x][y+2])
{
return 1;
}
if(map[x][y]==map[x][y-3]&&map[x][y]==map[x][y-2]&&map[x][y]==map[x][y-1]&&map[x][y]==map[x][y+1])
{
return 1;
}
if(map[x][y]==map[x][y-4]&&map[x][y]==map[x][y-3]&&map[x][y]==map[x][y-2]&&map[x][y]==map[x][y-1])
{
return 1;
}
if(map[x][y]==map[x+1][y]&&map[x][y]==map[x+2][y]&&map[x][y]==map[x+3][y]&&map[x][y]==map[x+4][y])
{
return 1;
}
if(map[x][y]==map[x-1][y]&&map[x][y]==map[x+1][y]&&map[x][y]==map[x+2][y]&&map[x][y]==map[x+3][y])
{
return 1;
}
if(map[x][y]==map[x-2][y]&&map[x][y]==map[x-1][y]&&map[x][y]==map[x+1][y]&&map[x][y]==map[x+2][y])
{
return 1;
}
if(map[x][y]==map[x-3][y]&&map[x][y]==map[x-2][y]&&map[x][y]==map[x-1][y]&&map[x][y]==map[x+1][y])
{
return 1;
}
if(map[x][y]==map[x-4][y]&&map[x][y]==map[x-3][y]&&map[x][y]==map[x-2][y]&&map[x][y]==map[x-1][y])
{
return 1;
}
return 0;
}
判断下的棋子是否和其余几个方向的其他棋子构成五子一线,这种可能性是20种。不要忘了最中心的这个点的位置,它不算是重合的,而且来自四个方向上的判断。如果五子一线,返回1,如果没有达到五子一线,那么返回0,这里还是很容易理解的。
人机对战:
void Computer_game(void)
{
int start,end;
int x,y,x0,y0,n=0;
Init();
start=clock();
while(1)
{
system("cls");
Drawmap();
printf("请输入你的坐标(行,列):\n");
scanf("%d%d",&x,&y);
if(x<4||x>13||y<4||y>13||map[x][y]!=' ')
{
printf("你的坐标不合法,请尝试重新输入...\n");
system("pause");
continue;
}
else
{
map[x][y]='X';
n++;
}
while(1)
{
srand(time(NULL));
x0=rand()%10+4;
y0=rand()%10+4;
if(map[x0][y0]!=' ')
{
continue;
}
else
{
map[x0][y0]='O';
n++;
break;
}
}
if(Judge(x,y)==1)
{
system("cls");
Drawmap();
printf("玩家获胜!\n");
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
if(Judge(x0,y0)==1)
{
system("cls");
Drawmap();
printf("电脑获胜!\n");
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
if(n==100)
{
system("cls");
Drawmap();
printf("平局!\n");
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
}
}
人机对战的核心依然是srand函数和rand函数,缺点显而易见,由于时间作为种子,导致的结果就是电脑笨得和一头猪一样,乱下,优点就是方便省事。当然可以向计算机插入算法,使计算机具有多种路径可以选择并取最优路径,不过这种方法比较复杂,优点也很明显,使计算机具有思维模式,让游戏变得具有挑战性。
rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。
rand函数需要有srand()函数用来设置rand()产生随机数时的随机数种子。
参数seed是整数,通常可以利用time(0)或getpid(0)的返回值作为seed。
人人对战:
void Player_game(void)
{
char name1[20];
char name2[20];
int x1,y1,x2,y2,n=0;
int start,end;
printf("请输入玩家1的昵称:\n");
scanf("%s",name1);
printf("请输入玩家2的昵称:\n");
scanf("%s",name2);
Init();
start=clock();
while(1)
{
system("cls");
Drawmap();
printf("请 %s 输入坐标(行,列): 输入坐标(0,0)投降\n",name1);
scanf("%d%d",&x1,&y1);
if(x1==0&&y1==0)
{
goto w2;
}
if(x1<4||x1>13||y1<4||y1>13||map[x1][y1]!=' ')
{
printf("你输入的坐标不合法,请尝试重新输入...\n");
system("pause");
continue;
}
else
{
map[x1][y1]='X';
n++;
}
system("cls");
Drawmap();
if(Judge(x1,y1)==1)
{
w1:
system("cls");
Drawmap();
printf("%s获胜!\n",name1);
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
printf("请 %s 输入坐标(行,列): 输入坐标(0,0)投降\n",name2);
scanf("%d%d",&x2,&y2);
if(x2==0&&y2==0)
{
goto w1;
}
if(x2<4||x2>13||y2<4||y2>13||map[x2][y2]!=' ')
{
printf("你输入的坐标不合法,请尝试重新输入...\n");
system("pause");
continue;
}
else
{
map[x2][y2]='O';
n++;
}
if(Judge(x2,y2)==1)
{
w2:
system("cls");
Drawmap();
printf("%s获胜!\n",name2);
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
if(n==100)
{
system("cls");
Drawmap();
printf("平局!\n");
end=clock();
printf("本局用时为%d秒...\n",(end-start)/1000);
system("pause");
break;
}
}
}
这里采用了输入昵称的方法来确定玩家。同时设定根据(0,0)即可判断当前玩家决定投降以此来结束游戏。
其他方面就是合理第利用judge函数,这里强调一下判断函数的位置很重要,不可以两个判断函数放在一起,要在一位玩家完成落子之后立刻进行判定,不然就会出现有一方五子连线了而另一方仍旧可以继续进行下棋的情况。同时,start和end变量用于计时。
菜单函数:
void menu(void)
{
printf("============================================\n");
printf("============================================\n");
printf("===============五子棋Gobang=================\n");
printf("============================================\n");
printf("================1.人机对战==================\n");
printf("============================================\n");
printf("================2.玩家对战==================\n");
printf("============================================\n");
printf("================3.退出游戏==================\n");
printf("============================================\n");
printf("============================================\n");
}
main函数:
int main(void)
{
char choice,c;
system("color 2");
while(1)
{
p:
system("cls");
menu();
printf("请输入你的选择:\n");
scanf(" %c",&choice);
if(choice=='1')
{
Computer_game();
break;
}
else if(choice=='2')
{
Player_game();
break;
}
else if(choice=='3')
{
printf("正在退出游戏......\n");
Sleep(1200);
return 0;
}
else
{
printf("输入错误,请尝试重新输入...\n");
continue;
break;
}
}
printf("是否继续游戏?(Y/N)\n");
scanf(" %c",&c);
if(c=='y'||c=='Y')
{
goto p;
}
return 0;
}
main函数起到的就是一个调配函数的过程。
因为是五子棋,所以我没有进行存档只是计时,存档也很简单,通过fopen函数就可以进行,在这里就不进行赘述了。
运行无警告无错误,目前没发现任何BUG。
到这里就算是彻底结束了,各个板块也算是分工明确了,欢迎大家给出意见,帮我指正,谢谢大家啦 (づ ̄ 3 ̄)づ