【C++】运算符重载与模板——五子棋人机对弈的实现

一、问题提出

实现双人对弈的五子棋小游戏,当有某一方的棋在横向或者纵向或者斜向连成五个时,游戏结束,提示获胜。同时,也可以在此基础上做出一些拓展功能。

二、解决思路

①利用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数组,用于存储棋盘上已下棋子的序号数,读取棋盘后,需要判断此时是第几颗棋子,下一步应该是哪个序号数,进而继续游戏,同时需要避免保存的棋盘已经是获胜状态,则不能再进行下棋。

  对于这次实验,只实现了简单的人人对弈模式,还有相应的人机对弈模式没有实现,人机对弈会更难,因为要让电脑自动下棋,模拟人的思路,在想办法连成五连的同时还要堵住对方的棋子,这是比较困难的,在后续的学习中我会继续进行探索,争取完成更多功能。

  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值