C++、easyx组合的界面版五子棋(适合新手)

C++、easyx组合的五子棋界面版(适合新手)

点击进入五子棋控制台版本


前言

贴主大概什么水平呢?目前大二,C++学完了STL(会用函数)、数据结构、C++ primer看了一半。大概就这样的水平2333。相信大部分人跟我一样,对于C++很迷茫,我们不像Java,调个自带GUI就可以实现界面了,我们需要调用第三方库。C++图形库有很多,这里以easyx为例,讲解一下如何实现五子棋界面版。


效果图

在这里插入图片描述

一、游戏规则

哈哈哈哈很简单呀,五个字相连,就可以判定谁赢谁输了√
1.黑棋先
2.五子相连,游戏结束。
3.相较于控制台版本 新增了悔棋功能。

二、实现逻辑

接下来就跟着我的思路一起来学习一下如何写这样一个程序叭!

1.绘制棋盘

控制台版本我们是严格计算过之后,通过cout竖线和横线来完成棋盘绘制的。
那么在图形版本里面,我们easyx提供了相应的绘图函数。
通过翻阅easyx官网的帮助文档:绘图方法
我们可以用一下代码实现:

void draw_chessboard()				
{								
	setlinecolor(BLACK);

	for (int i = 0; i < MAX; i++)
	{
		line(1 + i * unit, 1, 1 + i * unit, unit * (MAX - 1));
		line(1, 1 + i * unit, unit * (MAX - 1), 1 + i * unit);
	}

}

先将线设定为黑色,随后调用line函数绘制桌面。其中MAX是线条数,unit是单元格的宽度。

2.落子

2.1 鼠标坐标的获取

控制台版本是通过手动输入坐标来实现落子的,那么界面版则有点复杂了。
我们需要调用easyx里面的鼠标方法。详见帮助文档:鼠标方法
这里稍微讲一下,通过调用easyx里面的鼠标类,我们就可以获取到鼠标当前位置的横纵坐标:
我们来看官方文档的示例程序:

// 编译环境:Visual C++ 6.0,EasyX 20190314(beta)
// http://www.easyx.cn
//
#include <graphics.h>
#include <conio.h>

int main()
{
	// 初始化图形窗口
	initgraph(640, 480);

	MOUSEMSG m;		// 定义鼠标消息

	while(true)
	{
		// 获取一条鼠标消息
		m = GetMouseMsg();

		switch(m.uMsg)
		{
			case WM_MOUSEMOVE:
				// 鼠标移动的时候画红色的小点
				putpixel(m.x, m.y, RED);
				break;

			case WM_LBUTTONDOWN:
				// 如果点左键的同时按下了 Ctrl 键
				if (m.mkCtrl)
					// 画一个大方块
					rectangle(m.x-10, m.y-10, m.x+10, m.y+10);
				else
					// 画一个小方块
					rectangle(m.x-5, m.y-5, m.x+5, m.y+5);
				break;

			case WM_RBUTTONUP:
				return 0;	// 按鼠标右键退出程序
		}
	}

	// 关闭图形窗口
	closegraph();
}

我们清晰的知道:m.x和m.y就是鼠标当前位置的横纵坐标
switch负责判断鼠标当前状态,是按下,还是按下弹起等等。
知道原理后,我们就可以这样写:

MOUSEMSG m;						// 鼠标类 从easyx.h里面调用
m = GetMouseMsg();
			switch (m.uMsg)
			{
			case WM_LBUTTONDOWN:
				cout<<m.x << " " << endl;
				//随便举个例子    
				break;
			}

2.2 绘制棋子

知道如何去获取鼠标坐标后,我们就可以手动添加其他响应事件了
我们首先要做的就是:鼠标按下之后,我们要在当前位置绘制一个棋子,对吧。
上代码:

			switch (m.uMsg)
			{
			case WM_LBUTTONDOWN:
				x_sim = (int)((double)m.x / unit + 0.5);
				y_sim = (int)((double)m.y / unit + 0.5);
				//cout << x_sim << " " << y_sim << endl;			// 用于看坐标用
				if (chess_pos[y_sim][x_sim] == ' ' && y_sim >= 0 && y_sim < MAX && x_sim >= 0 && x_sim < MAX)
				{
					chess_pos[y_sim][x_sim] = 'b';
					fillcircle(x_sim * unit, y_sim * unit, chess_r);
					step++;
				}
				break;
			}

这里就有人有疑问了:我明明已经从鼠标类里面获取到横纵坐标的位置了,为什么还要搞个x_sim呢?
首先,我们落子,是不是必须要落到棋盘的十字点上?对吧,我们不能哪里都下棋,那不就乱套了。
所以这里通过小公式,计算出了当前坐标下,最近一个十字点的位置,并且提供了一个误差范围。
计算好落点后,我们还得判断一下。
1.当前点有没有下过棋子?也就是当前点对应的数组内是不是“ ”(空格)?
2.当前点是否在棋盘内
满足这两个条件后,我们就可以落子了。
将数组内的值替换成对应棋子的颜色“b”“w,然后绘制棋子。
这里依旧是easyx的绘制方法,详见文档。

三、输赢判定算法

到这里,基本上一个图形界面就成型了。
我们接下来要考虑游戏规则了。
如何判定胜利?
网上有很多好方法,甚至还有KMP的。
这里我用一个炒鸡炒鸡通俗易懂的方法来写如何判定输赢。
首先:赢的条件是,当前落点,其横纵坐标,以及两个斜方向上有包含它并且与它连续相同的五个棋子。
上代码:

int Judge(int x, int y)			// 五连子判断
{	
	//横向
	cnt = 0;
	for (int i = 0; i < MAX; i++)
	{	
		if (chess_pos[x][i] == chess_pos[x][y])
		{
			for (int j = 1; j <= 4; j++)
			{
				if (chess_pos[x][i + j] == chess_pos[x][y])
					cnt++;
				else
					cnt = 0;
			}
		}
		if (cnt >= 4)
			return 1;
	}
	//纵向
	cnt = 0;
	for (int i = 0; i < MAX; i++)
	{	
		if (chess_pos[i][y] == chess_pos[x][y])
		{
			for (int j = 1; j <= 4; j++)
			{
				if (chess_pos[i+j][y] == chess_pos[x][y])
					cnt++;
				else
					cnt = 0;
			}
		}
		if (cnt >= 4)
			return 1;
	}
	//左斜

	cnt = 0;
	next_x = x + 1;
	next_y = y - 1;
	while (next_x<MAX && next_y>-1 && chess_pos[next_x][next_y] == chess_pos[x][y])
	{
		next_x++;
		next_y--;
		cnt++; 
	}

	next_x = x - 1;
	next_y = y + 1;
	while (next_x > -1 && next_y < MAX && chess_pos[next_x][next_y] == chess_pos[x][y])
	{
		next_x--;
		next_y++;
		cnt++; 
	}

	if (cnt >= 4)
		return 1; 

	//右斜
	cnt = 0;
	next_x = x - 1;
	next_y = y - 1;
	while (next_x > -1 && next_y > -1 && chess_pos[next_x][next_y] == chess_pos[x][y])
	{
		next_x--;
		next_y--;
		cnt++; 
	}
	next_x = x + 1;
	next_y = y + 1;
	while (next_x < MAX && next_y < MAX && chess_pos[next_x][next_y] == chess_pos[x][y])
	{
		next_x++;
		next_y++;
		cnt++; 
	}

	if (cnt >= 4)
		return 1; 
	
	return 0;
}

对于横纵方向,我用for循环+计数来判定是否是五连子,代码很好懂。
对于斜方向,由于每个点的下一个点,其横坐标纵坐标都会变,所以套for就有点力不从心,于是,这里我用了while循环。对于确定次数的循环,我们用for,对于不确定的,我们用while。
需要说明的是,Judge函数的两个形参,分别是鼠标的m.y(x_sim)和m.x(y_sim)。
为什么会反过来?
因为我们数组的下标是xx行xx列,先行后列。
而easyx的绘图,是先列后行,是反过来的。
所以传进去参数的时候,需要更改一下顺序。

四、其他小细节

1.音效

C++放音乐有很多方法,这里用一种比较简单的playsound()方法。
头文件:

// 以下三个为PlaySound()函数的头文件 顺序不能错
#include <Windows.h>	
#include <mmsystem.h>
#pragma comment(lib,"WINMM.LIB")

如何使用:

void music()					// 下每一步棋的音效
{
	PlaySound(TEXT("chess.wav"), NULL, SND_FILENAME | SND_ASYNC);
}

如何使用详见其他博主的playsound详解。

2.悔棋

悔棋思路:
将上一步坐标对应的数组重新置为“ ”。
调用easyx的clearcircle()函数清除当前绘制的棋子。
代码:

		if (_kbhit()) {				// 当键盘按下R键 执行悔棋
			char input = _getch();
			if (input == 'r')
			{	
				step--;
				regret(x_sim,y_sim);
			}
		}
void regret(int x_sim,int y_sim)			// 悔棋函数
{											
	chess_pos[y_sim][x_sim] = ' ';						// 如果悔棋 先将棋子坐标重置
	clearcircle(x_sim * unit, y_sim * unit, chess_r);	//再清除原来绘制的圆
}

值得一提的是,由于easyx的问题,清除绘图后,会出现这个情况:
在这里插入图片描述
大家可能会是其他颜色。
这是由于清除的时候直接把背景图案也给清除了,这个暂时想不到什么解决办法,有好方法的评论区可以留言。

源代码以及素材

代码和素材我放Github里面了,大家点击下载即可。

``

  • 7
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
题目:五子棋对弈 对弈规则如下: 主要功能是实现两人之间的对弈,在画好的棋盘上,两个玩家轮流选择自己的落子坐标,然后由五子棋系统自动识别判断游戏的进展,知道一方的五子连成一条线或者棋盘已经无法落子时游戏结束。 选定五子棋的棋盘大小为19*19,玩家可以在这个棋盘上选择落子坐标位置,通过在棋盘上显示不同的符号来代替不同玩家所下的棋子,“o”代表A玩家,“*”代表B玩家。玩家每次落子之后游戏系统都会对落子位置进行检查,如果落子坐标输入有错应提示错误,并要求玩家继续输入。 当出现同一玩家五子连成一线时,无论是行、列或是对角线的五子连线,都表示玩家游戏胜利,退出游戏 任务:编程实现以下功能 1. 欢迎主界面 提示玩家选择游戏开始,结束,设置悔棋次数等。 2. 绘制棋盘 该模块要求的功能是实现棋盘的显示及棋子的显示,,“o”代表A玩家,“*”代表B玩家。在每次下棋后要对棋盘进行刷新,将棋盘的状态变化为当前最新状态,然后等待另一个玩家下棋。 3. 玩家交替下棋 玩家能在棋盘上下棋,玩家每次选择好下棋的行和列坐标,并在该位置落子。 要求:a.提示当前玩家输入落子坐标 b.能判断用户输入的坐标是否正确(坐标超出范围或该处已有棋子) 4. 悔棋功能 玩家选择悔棋后刷新棋盘,删除前一次的落子,悔棋次数有限制。 5. 输赢判断 判断输赢模块的作用是每次玩家落子后判断是否已分出胜负,如果是,则返回胜利者相关信息。 6. 设计丰富的用户界面,方便用户操作 设计要求: ① 根据以上功能需求,自己定义合适的数据结构,并说明原因; ② 每个功能能提供友好的用户界面,方便用户操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值