一、问题提出
实现双人对弈的五子棋小游戏,当有某一方的棋在横向或者纵向或者斜向连成五个时,游戏结束,提示获胜。同时,也可以在此基础上做出一些拓展功能。
二、解决思路
①利用easyx创建棋盘的图形界面,将整个棋盘划分为15*15的小格,棋盘上的棋子用一个二维数组来储存,没有棋子用0表示,黑棋用1表示,白棋用2表示。
②建立鼠标点击事件,当鼠标点击到方格交界处时,表示下了一个棋子,判断此时是第几颗棋子,即轮到哪一方下棋,在该点绘制圆形代表棋子。
③设计获胜的判断方法,遍历整个棋盘二维数组,对每个棋子进行判断,考虑它作为边界棋子的情况即可,判断它右方、右下方、下方、左下方是否连成了同样颜色的五颗棋子,若有这样的情况,代表某方棋子获胜,游戏结束。
④实现五子棋的其他功能,包括:重置棋盘、保存棋盘、读入棋盘、悔棋操作等,这些功能的实现通过绘制按钮,创建鼠标点击事件,完成相应的算法。重置棋盘:重新绘制棋盘,将二维数组重新全部赋值为0;保存棋盘:利用输出流对象,将此时的棋盘二维数组以二进制文件的方式保存下来;读入棋盘:利用输入流对象,将二进制文件存储的棋盘读入进来,绘制棋盘;悔棋操作:找到最后一步下的棋,将其在二维数组中置为0即可。
三、代码实现
引入头文件、定义宏和全局变量
#include <graphics.h>
#include<bits/stdc++.h>
#include <conio.h>
#include <cstring>
using namespace std;//里面存在x0,y0
#define for_(i,a,b) for(int i=a;i<=b;i++)
#define for2(i,j,a,b) for(int i=a;i<=b;i++)for(int j=0;j<=b;j++)
#define chess(i,j) chess[i][j]
#define nums(i,j) nums[i][j]
int chess(15,15); //定义为数组
int nums(15,15); //序号
//typedef vector<int> ivec;
//typedef vector<ivec> imat;
//全局变量
int num=1;//棋子数
int X0=100;
int Y0=50;
int step=43;//间隔
int r0=20;
bool gameover=false;
判断五连函数
bool isfive(int clr){
for2(i,j,0,14){
if(chess(i,j)==clr&& j+4<=14 && //横向五连
chess(i,j+1)==clr &&
chess(i,j+2)==clr &&
chess(i,j+3)==clr &&
chess(i,j+4)==clr ){
return true;
}
}
for2(i,j,0,14){ //右下
if(chess(i,j)==clr&& j+4<=14 && i+4<=14 &&
chess(i+1,j+1)==clr &&
chess(i+2,j+2)==clr &&
chess(i+3,j+3)==clr &&
chess(i+4,j+4)==clr ){
return true;
}
}
for2(i,j,0,14){ //下
if(chess(i,j)==clr&& j+4<=14 &&
chess(i+1,j)==clr &&
chess(i+2,j)==clr &&
chess(i+3,j)==clr &&
chess(i+4,j)==clr ){
return true;
}
}
for2(i,j,0,14){ //左下
if(chess(i,j)==clr&& j-4>=0 && i+4<=14 &&
chess(i+1,j-1)==clr &&
chess(i+2,j-2)==clr &&
chess(i+3,j-3)==clr &&
chess(i+4,j-4)==clr ){
return true;
}
}
return false;
}
绘制棋盘函数:
void drawboard(){ //画棋盘
clearrectangle(0,0,900,800); //棋盘清空 棋子还在
COLORREF bg=RGB(255*.9,255*.8,255*.5); //背景色
setfillcolor(bg);
setlinecolor(bg);
fillrectangle(0,0,900,800);
setlinecolor(BLACK);
setlinestyle(PS_SOLID,2);
rectangle(750,50,850,100);//重置按钮
settextcolor(BLACK);
setbkmode(TRANSPARENT);
settextstyle(30,0,_T("Consolas"));
outtextxy(770,60,"reset");
rectangle(750,150,850,200);
outtextxy(770,160,"save");//保存
rectangle(750,250,850,300);
outtextxy(770,260,"read");//读取
rectangle(750,350,850,400);
outtextxy(770,360,"back");//悔棋
for_(i,0,14){ //画棋盘
line(X0,Y0+step*i,X0+step*14,Y0+step*i);
line(X0+step*i,Y0,X0+step*i,Y0+step*14);
}
}
绘制棋子函数:
void drawchess(){
num=1;
for2(i,j,0,14){
int x=X0+j*step;
int y=Y0+i*step;
if(chess(i,j)==1){//黑棋
setfillcolor(BLACK);
settextcolor(WHITE);
fillcircle(x,y,r0);
char s[20];
sprintf(s, "%d", nums(i,j)); //将num输入s中
settextstyle(30,0,_T("Consolas"));
if(nums(i,j)<10) //个位数
outtextxy(x - 7, y - 15, s);
else
outtextxy(x - 15, y - 15, s);
num++;
}
else if(chess(i,j)==2){//白棋
setfillcolor(WHITE);
settextcolor(BLACK);
fillcircle(x,y,r0);
char s[20];
sprintf(s, "%d", nums(i,j)); //将num输入s中
settextstyle(30,0,_T("Consolas"));
if(nums(i,j)<10) //个位数
outtextxy(x - 7, y - 15, s);
else
outtextxy(x - 15, y - 15, s);
num++;
}
}
}
主函数:
int main(){
initgraph(900,800);
for2(i,j,0,14){
chess(i,j)=0;//初始化棋盘
}
drawboard();
ExMessage m;
while(true){
m=getmessage(EX_MOUSE|EX_KEY); //鼠标相应事件
switch (m.message) {
case WM_MOUSEMOVE:
break;
case WM_LBUTTONDOWN:{
//reset
if(750<=m.x and m.x<=850 and 50<=m.y and m.y<=100)
{
drawboard();
for2(i,j,0,14){
chess(i,j)=0; //清空棋子
}
num=1;
gameover=false;//重置棋盘
}
//save
if(750<=m.x and m.x<=850 and 150<=m.y and m.y<=200){
ofstream out("chess.dat",ios::trunc|ios::binary);//文件输出流
out.write((char*)chess,15*15*4);//存入棋子,以二进制字节形式
out.write((char*)nums,15*15*4); //存入序号
}
//read
if(750<=m.x and m.x<=850 and 250<=m.y and m.y<=300){
ifstream in("chess.dat",ios::in|ios::binary);//文件输入流
in.read((char*)chess,15*15*4);//读入棋子
in.read((char*)nums,15*15*4);//读入序号
drawchess();
settextcolor(BLACK);
if(isfive(1)){
outtextxy(5,5,"black wins");
gameover=true;
}
else if(isfive(2))
{
outtextxy(5,5,"white wins");
gameover=true;
}
}
if(750<=m.x and m.x<=850 and 350<=m.y and m.y<=400){ //悔棋
//找到序号最大的棋改为0
int mx=0,my=0;
for2(i,j,0,14){
if(nums(i,j)>nums(mx,my)){
mx=i;my=j;
}
}
chess(mx,my)=0;
nums(mx,my)=0;
num--;
drawboard();
drawchess();
}
if(gameover)
break;//游戏结束
if(num%2==1){ //判断奇偶
setfillcolor(BLACK); //奇数是黑棋
settextcolor(WHITE);
}
else
{ setfillcolor(WHITE);//偶数是白棋
settextcolor(BLACK);
}
int x=m.x;//获取鼠标坐标
int y=m.y;
int dx=round((x-X0)*1./step);//行号
int dy=round((y-Y0)*1./step);//列好
x = X0 + dx * step;//格子坐标
y = Y0 + dy * step;
int c=x/step-2;//(0,0)坐标位置是(100,50),100/43-2=0
int r=y/step-1;//50/43-1=0
if (x<X0 or x>X0+14*step or y<Y0 or y>Y0+14*step) //限制在棋盘内
break;
char s[20];
sprintf(s, "%d", num); //将num输入s中
if(chess(r,c)!=0){ //该点已有棋子
break;
}
chess(r,c)=num%2==0?2:1; //奇数:1 黑 偶数:2 白
nums(r,c)=num;
fillcircle(x,y,r0);//画圆
settextstyle(30,0,_T("Consolas")); //高度 宽度(自适应) 字体
setbkmode(TRANSPARENT);//使文本背景与控件背景一样,文字背景变透明
if(num<10) //个位数
outtextxy(x-7, y -15,s); //将此时的数字打印在棋子上
else
outtextxy(x-15,y-15,s);
settextcolor(BLACK);
if(isfive(1)){ //黑棋赢
outtextxy(5,5,"black wins");
gameover=true;
}
else if(isfive(2)) //白棋赢
{
outtextxy(5,5,"white wins");
gameover=true;
}
num++; //棋子数++
}
break;
case WM_KEYDOWN:
if (m.vkcode == VK_ESCAPE)
return 0; // 按 ESC 键退出程序
}
}
getchar();
}
四、结果分析
棋盘界面:
下棋中:
白棋获胜:
重置棋盘reset
继续下棋:
保存棋盘save:
保存为.dat文件
用记事本打开
再次重置棋盘reset
加载棋盘read:
成功读取到保存的棋盘
可以继续下棋
悔棋操作按下back
可退回上一步
如图所示,黑棋11被清除了.
五、实验总结
本次实验完成了五子棋对弈小游戏的设计,主要运用easyx完成界面设计,结合相应的算法补充了一些功能,实现了较为完善的五子棋设计。
实验中开始是用向量来存储棋子,但为了后续引用方便还是改成了数组,用二维数组来存放棋子,根据下的步数,判断是偶数还是奇数,即判断是白棋还是黑棋,白棋需要白色填充色和黑色文字,黑棋需要黑色填充色和白色文字。这样就实现了黑白棋子交替下棋的对弈情况。棋子应该下在网格线的交界处,而不能任意下,所以还需要对鼠标点击的位置做处理,判断该点与哪个交界点接近,应该下在几行几列,才能将棋子画上。
五子棋的获胜规则比较简单,判断是否有连续五颗同样颜色的棋子即可。这个函数设计的也比较巧妙,只用考虑它作为边界棋子的情况,而且只用考虑4个方向(右、右下、下、左下),其它4个方向在其他棋子里会有涉及,而且也不用考虑它作为中间棋子的情况,这样简化了代码和算法思路,也让五连规则变得明确。调用isfive()函数,判断是否有白棋或者黑棋获胜,若返回true,代表获胜,则游戏结束,不能再向棋盘内下棋,可以通过reset重置棋盘来重新开始一局。
实验中给五子棋增加的功能主要是:重置棋盘、保存棋盘、加载棋盘和悔棋操作。保存和加载运用到了输入输出流对象,使用起来也比较简单方便。在加载棋盘时,要继续该棋盘上的序号下棋,所以还运用到了nums数组,用于存储棋盘上已下棋子的序号数,读取棋盘后,需要判断此时是第几颗棋子,下一步应该是哪个序号数,进而继续游戏,同时需要避免保存的棋盘已经是获胜状态,则不能再进行下棋。
对于这次实验,只实现了简单的人人对弈模式,还有相应的人机对弈模式没有实现,人机对弈会更难,因为要让电脑自动下棋,模拟人的思路,在想办法连成五连的同时还要堵住对方的棋子,这是比较困难的,在后续的学习中我会继续进行探索,争取完成更多功能。