小彩球游戏

1.实验题目:彩球游戏实现过程

1.1问题描述(游戏规则):

(1)游戏区域为77-99(具体可由键盘输入确定),共有七种颜色的彩球随机出现,其中状态为5个,以后每次出现3个。
(2)用鼠标选中某个彩球,在选择一个空白区域作为目标位置,如果从源到目标位置有通路可走,将彩球移动到目标位置;如果没有通路可走,则不移动并给出提示。
(3)当同色彩球在横向,纵向,斜向达到5个及以上时,可以消除,同时得到相应的分数。
(4)当棋盘中没有空位时,游戏结束。

1.2题目要求:
1.2.1整体要求:

(1)所有小题放在一个程序中,以菜单的形式进行选择
(2)右侧得分,预告彩球以及统计信息。
(3)用伪图形界面进行游戏设计。

1.2.2 显示要求:

(1)被选中的彩球要有不同效果
(2)彩球移动是,要有动画效果沿着通路进行移动
(3)消除时要有相应的动画效果
(4)打印球时以不同的颜色输出
(5)伪图形界面中,鼠标在棋盘上移动实时显示当前所在的行,列位置。

1.2.3游戏规则具体要求:

(1)连续5个及以上则消除,得分规则自定义(具体实现中,我的规则是消除数量为n,则得分为(n-1)*(n-2),其中交叉点要重复计数)。
(2)若本次移动得分,则不产生新球,否则随机产生三个新球。
(3)鼠标左键选择,右键退出。
(4)小彩球只允许在空白位置进行移动,且只可横或竖走,不允许对角线走。

1.2.4 代码要求

(1)尽量使各菜单项中的程序公用函数,用参数解决细微差异。
(2)各函数代码长度尽量不超过50行
(3)不允许使用全局变量,全局指针以及全局数组,允许使用宏定义。
(4)只允许使用目前为止讲过的内容(包括课后补充知识。)

2.整体设计思路

写这个程序首先要考虑的是它的本质,也就是数据在机器内的存储形式。考虑多方面因素,最合适的应该是二维数组,形象的可以看成是10*10的行列矩阵(二维数组事实上还是连续排列的数据存储,这里为了方便理解)。其中数组的元素的值代表不同颜色的小球,0代表空位。程序设计过程中,可以通过数组相应位置(棋盘相应位置)的数值进行是否有球,是否同色等的判断,同时当球移动时,也可以通过移动路径中相应位置的值的变化来实现,同样包括当判断五个相同颜色的小球同线是消去的实现。
整体的过程如下图:

整体流程

3.主要功能的实现

3.1寻路
3.1.1 利用递归算法找到通路
  • 自定义函数找到鼠标左键选择的起点和终点后,将起点和终点的坐标(即在二维数组内部存储的i,j值)存入两个大小为2 的数组begin[2]和final[2].
  • 确定好起点和终点后,就可以开始寻路了。将整一个过程拆分成多个小过程,每个过程由两步组成----起点走一步到起点2;起点2走到终点。不需要考虑每一个小过程到底发生了什么。

函数说明:函数返回类型设置为int型,0代表无返回,也就是没找到路;1代表找到路。找到之后原路返回,一路return 同时存储路径上的点 的坐标。算法完成之后,路径上所有的点逆序存放在rout数组之中。

例如下图:

递归流程

具体函数代码如下

int search(int(*p)[10], int(*rout)[2], int x1, int y1, int x2, int y2, int rows, int cols, int *num_pace)
{
	if (x1 >= 0 && x1 < cols&&y1 >= 0 && y1 < rows)
	{
		if (x1 == x2&&y1 == y2)
		{
			rout[*num_pace][0] = y1;
			rout[*num_pace][1] = x1;
			(*num_pace)++;
			return 1;
		}
		else
			p[y1][x1] = -1;

		if (p[y1][x1 + 1] == 0)
		{
			search(p, rout, x1 + 1, y1, x2, y2, rows, cols, num_pace);
			if (*num_pace)
			{
				rout[*num_pace][0] = y1;
				rout[*num_pace][1] = x1;
				(*num_pace)++;
				return 1;
			}
		}
		if (p[y1 - 1][x1] == 0)
		{
			search(p, rout, x1, y1 - 1, x2, y2, rows, cols, num_pace);
			if (*num_pace)
			{
				rout[*num_pace][0] = y1;
				rout[*num_pace][1] = x1;
				(*num_pace)++;
				return 1;
			}
		}
		if (p[y1][x1 - 1] == 0)
		{
			search(p, rout, x1 - 1, y1, x2, y2, rows, cols, num_pace);
			if (*num_pace)
			{
				rout[*num_pace][0] = y1;
				rout[*num_pace][1] = x1;
				(*num_pace)++;
				return 1;
			}
		}
		if (p[y1 + 1][x1] == 0)
		{
			search(p, rout, x1, y1 + 1, x2, y2, rows, cols, num_pace);
			if (*num_pace)
			{
				rout[*num_pace][0] = y1;
				rout[*num_pace][1] = x1;
				(*num_pace)++;
				return 1;
			}
		}
		return 0;
	}
	else
		return 0;
}
3.1.2找通路时内部存储变化

每次查找过程中,首先判断是否为结束条件,如果不是,则将内部数组该位置上的数值设置为-1,避免重复判断查找,保证不走同一个位置。
而在判断能不能走的条件时,首先,要走的点对应的数值需要为0,同时不超出棋盘界限,所以当search完之后,需要将所有值为-1 的恢复成为原先的值,也就是0.除此之外,起点和终点的值要进行互换。
不是所有值为-1的都是路径上的点,还有可能是探了一下但没有成功的点! 真正路径上的点都存储在相应的数组中。通过路径数组中存储的位置值,也就是对应二维数组中的i,j值对输出形式,位置进行选择。

3.1.3伪图形显示移动路径

通过路径数组中存储的位置值,也就是对应二维数组中的i,j值确定路径!由于存储时是先存终点后存起点,所以要逆方向读取。

  • 判断路径上有几个点,用简单的循环和条件就可以判断
  • 从倒数第二个开始,对应伪图形界面中的坐标,打印相应颜色的字符串,同时在倒数第一个位置打印空字符串

以此类推,一直到路径数组中的点全都对应完毕。

3.2判断得分

在新的一颗子落下之前,没有达到可以消除的条件;这颗子落下后,消除条件形成,发生消除动作。所以,每次寻路成功之后,以此次寻路中的终点作为第一颗子进行展开,判断是否满足消除条件。

判断得分

具体代码如下

void judge(int(*p)[10], char final[2], const int rows, const int cols, int *score, int *eve_score)
{
	int new_chess = p[final[0] - 'A'][final[1] - '1'];
	int x = final[1] - '1', y = final[0] - 'A';
	int i1 = 1, i2 = 1, i3 = 1, i4 = 1, j1 = 1, j2 = 1, j3 = 1, j4 = 1, sum = 0;

	if (x >= 1 && new_chess == p[y][x - 1] || x<cols - 1 && new_chess == p[y][x + 1])
	{
		while (x - i1 >= 0 && p[y][x - i1] == new_chess)
			i1++;
		while (x + j1<cols&&p[y][x + j1] == new_chess)
			j1++;
	}
	if (y >= 1 && p[y - 1][x] == new_chess || y < rows - 1 && p[y + 1][x] == new_chess)
	{
		while (p[y - i2][x] == new_chess && y - i2 >= 0)
			i2++;
		while (p[y + j2][x] == new_chess && y + j2<rows)
			j2++;
	}
	if (x >= 1 && y >= 1 && p[y - 1][x - 1] == new_chess || x < cols - 1 && y < rows - 1 && new_chess == p[y + 1][x + 1])
	{
		while (p[y + i3][x + i3] == new_chess && y + i3<rows && x + i3<cols)
			i3++;
		while (p[y - j3][x - j3] == new_chess &&y - j3 >= 0 && x - j3 >= 0)
			j3++;
	}
	if (x<cols - 1 && y >= 1 && p[y - 1][x + 1] == new_chess || x >= 1 && y < rows - 1 && new_chess == p[y + 1][x - 1])
	{
		while (p[y - i4][x + i4] == new_chess && y - i4 >= 0 && x + i4<cols)
			i4++;
		while (p[y + j4][x - j4] == new_chess && x - j4 >= 0 && y + j4<rows)
			j4++;
	}
	if (i1 + j1 >= 6)
	{
		sum += (i1 + j1 - 1);
		for (int i = x + 1 - i1; i < x + j1; i++)
			p[y][i] = 0;
	}
	if (i2 + j2 >= 6)
	{
		sum += (i2 + j2 - 1);
		for (int i = y - i2 + 1; i < y + j2; i++)
			p[i][x] = 0;
	}
	if (i3 + j3 >= 6)
	{
		sum += (i3 + j3 - 1);
		for (int i = 1 - j3; i < i3; i++)
			p[y + i][x + i] = 0;
	}
	if (i4 + j4 >= 6)
	{
		sum += (i4 + j4 - 1);
		for (int i = 1 - i4; i<j4; i++)
			p[y + i][x - i] = 0;
	}

	if (sum)
	{
		*eve_score = (sum - 1)*(sum - 2);
		*score += (sum - 1)*(sum - 2);
	}
	else
		*eve_score = 0;
}

(如图只是为解释,该情况在实际中是不可能出现的)
判断时,从黑色小球出发,以上下为例,分两个方向进行搜索,分别定义i=1,j=1,循环判断p[y-i][x]?=p[y][x],不等于循环结束,记录i值;等于则i++,继续搜索。。。j同理,向黑色小球的下方进行搜索,最终纵向的小球个数为i+j-1;其他方向都是同理。
由于交叉点需要重复计数,所以选择先判断完所有方向,记录所有可以消除的球(包括交叉点的多次计数)后再对内部数组进行改变,防止交叉小球满足一个条件后就被消除,对潜在的其他方向的消除条件判断造成影响。
记录完之后计算此次得分,以此次得分是否为零,用以后续判断是否进行了消除。

代码较长,故仅贴出部分重要的函数
一下是在实现伪图形的游戏界面时的主要代码,其中包括比较多的没有贴出来的函数,我会尽量做出注释。

void seven(int(*p)[10])
{
	HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
	const HANDLE hin = GetStdHandle(STD_INPUT_HANDLE);
	int rows, cols, X, Y;//X,Y用于存放打印完棋盘后的光标坐标,方便打印结束语
	char begin[2], final[2];//存放鼠标输入的起点坐标和终点坐标
	int next[3], rout[81][2];//next用于存放下一轮将会出现的球的信息;rout用于存放寻到的路上的点坐标
	init(&rows, &cols, p, 5);//初始化内部数组+随机产生5个球
	const int *row_ptr = &rows;//防止不小心对cols rows篡改
	const int *col_ptr = &cols;
	int score = 0, eve_score = 0;//总得分,每次得分
	int leap = 0;
	print_console2(rows, cols, p);//打印棋盘
	getxy(hout, X, Y);
	print_console3(rows, cols, next, 0);//打印得分,预示等
	for (int i = 0; i < 3; i++)
		next[i] = rand() % 7;
	print_console3(rows, cols, next, 4);
	while (1)
	{
		int num_pace = 0;
		bgn_fnl_mouse(begin, final, rows, cols, Y, p);//输入起点和终点的坐标
		int begin_value = p[begin[0] - 'A'][begin[1] - '1'];//将起点值先存起来,寻完路后再赋给终点,防止寻路过程中值被“破坏
		if (search(p, rout, begin[1] - '1', begin[0] - 'A', final[1] - '1', final[0] - 'A', *row_ptr, *col_ptr, &num_pace))
		{//search返回1代表寻到路,否则没寻到路!但不论是否寻到路,这个函数中的操作已经执行,也就是路径上的点的值已被改变
			reset_array(p, col_ptr, row_ptr);//search是吧探索过的店全都变成了-1要变回来,包括起始点
			p[final[0] - 'A'][final[1] - '1'] = begin_value;
			show_rout(rout, begin_value);//把存在rout里的路径上的点坐标按顺序(加延时)显示,即移动路径
			for (int i = 0; i < 81; i++)
				for (int j = 0; j < 2; j++)
					rout[i][j] = -1;//清空存储的路径记录
			judge(p, final, *row_ptr, *col_ptr, &score, &eve_score);//判断能否得分,若得分,设置为0,更新内部数组
			if (eve_score == 0)
			{
				update_array(p, next, col_ptr, row_ptr, 1);//将next中存的值随机赋给"棋盘"中空白的位置,也就是给内部数组p
				for (int i = 0; i < 3; i++)
					next[i] = rand() % 7+1;
				print_console3(rows, cols, next, 4);//4用于控制打印位置
			}
			else
			{/*遍历棋盘,把刚消去球的位置打印空格*/
				for (int i = 0; i < rows; i++)
					for (int j = 0; j < cols; j++)
						if (p[i][j] == 0)
							showstr(hout, 2 + j * 4, (i + 1) * 2, "  ", COLOR_HWHITE, COLOR_HWHITE);
				setcolor(hout, COLOR_HWHITE, COLOR_BLACK);
				gotoxy(hout, 4 * cols + 12, 2);
				cout << score;
			}
		}
		else//如果没有找到路
		{
			p[begin[0] - 'A'][begin[1] - '1']=begin_value;
			showstr(hout, 2 +(begin[1] - '1') * 4, (begin[0] - 'A' + 1) * 2, "○", begin_value + 1, COLOR_HWHITE);
			reset_array(p, col_ptr, row_ptr);
			continue;
		}
		if (game_over(p, row_ptr, col_ptr,Y-1))
			break;
	}
	end();
}

github链接
小彩球源码

1.基本要求 能够实现如下功能: ‹ 首先用蓝色清屏 ‹ 在屏幕中央显示由字符串“-============#”组成的黄色的小球,#为球, 按下方向键可以控制上述小球球行方式在屏幕上行走 ‹ 在行进过程中,球只能左转、右转或继续前进,不能掉转 180 度 2.鼓励实现完整的彩球滚动游戏,鼓励有新的创意 3.提示 ①通过调用 INT 16H 的 0 号功能可以读取光标控制键的扩展码 光标控制键: ↑ ↓ ← → 扩展码(十进制): 72 80 75 77 ②在指定位置用指定属性显示字符的方法有两种:一是直接写显示缓冲区,二是利用 BIOS 屏显功能调用。解: 功能描述(基本上是全部功能):本程序有以下功能 共分为7关,可以手工选择关卡(带有输入异常处理)。走完一关后,如果后面还有关, 直接跳到下一关。否则,结束游戏。各个关之间的差别是速度不同。 按下方向键,球能够按照题的要求在屏幕上行走。按下 ESC 键,退出游戏,按下其他键, 程序不理会。 长时间不按键,球会自动前进。 能够产生随机数,作为蛋。球的初始大小为 14,当大小为 20 时,此关结束。 如果球运动到了边界,球死亡。游戏结束。 ① 设计思路 程序开始时,由用户指定一个关卡,进入游戏。 每次用清屏加显示字符的方式重新显示球和蛋。 当检测到有键子按下时,判断是什么键子,如果是 esc,退出游戏,如果是方向键,按 正确的方向走(如果方向键与球运动方向相反,不理会按键),如果按下的时其它键, 不理会。 设置一个等待时间,如果超过等待时间仍没有按键,球自动前行一步。否则,重新比较 时间。 每次球运动或有键子被按下时,判断是否撞到了自身和边界。 如果吃到了一个蛋,更新完球的位置后,将原球的位置加入球中。 如果球的长度达到了 20(设置的球的最大大小),判断后面是否还有关,没有了,就结 束程序,还有,就跳到下一关卡。 ② 算法说明 设置两个标记变量,分别记录球的大小 ssize 和球上次的大小 befor。设置标记变量, 分别记录蛋的横纵座标 xlabel 和 ylabel,设置标记变量,记录球的位置 tailx 和 taily。设置 snake 记录球各个部分的位置,设置球的最大大小为 20。设置变量 TIME 为等待按键时间。 关卡的选择:程序开始时,从键盘读入一个数字,当作关卡,根据读入的数据,设置等 待时间,也就实现了对球的速度的控制。 清屏和显示小球和蛋。调用 bios 中断可以实现。每次输出 1 个球,下面说一下如何实现小球的手工移动(有按键输入时)。可以知道,如果把球看成一个 个单元,球每移动一次,它的身体的位置都等于它的前一个身体单元的上一步 的位置,因此,可以从尾部进行循环,把前一节的位置给后一节。这样循环 ssize-1 次 就更新了身体,再根据输入的按键判断如何如移动头部,如果按键是左或者右, 只需将球的列加减 1,如果按键是上或者下,只需将球的行加减 1。至此,完成了 对球的显示位置的更新,之后重新清屏、显示,可以使球移动了。 在判断球是否向相反方向走时,可以采用如下算法:已知按键了(以向上为例),检查 球和身体第一节的行号,如果球行号大,说明此时设在向下运动,按键无效。 判断球撞到边界的算法如下(以向上键为例):判断球此时的行号是否为 0,如果是 0, 又按下了向上键,结束游戏,输出“I AM DEAD!!!”。如果球运动过程中撞到了自己, 也同撞到边界的操作。算法是这样的:取出球的位置,依次和每个身体和尾巴的位置 进行比较(从球开始比较),如果相等,说明撞上了,结束游戏。 如何实现球的自动移动。可以用 INT 10H 的 1 号功能检测是否有按键输入,如果有,转 到手工移动模块,否则,调用 INT 1AH 中的 00 号功能,读取当前时间。与上次读的时 间相比,如果小于设定的时间,重新比较,否则,球自动前移。实现前移的算法与手工 移动相似,也是将身体的某一单元的位置置成塔前一单元上一次的位置,之后判断球 方向。 产生并输出蛋。相当于产生一个随机数,我已经读去了当前时间,可以利用 DX 移位(防 止溢出)以后,对 80 和 25 取余,获得位置的随机数。之后判断球的大小 ssize 和 befor, 先让 ssize 为 14,befor 为 13,每产生一个随机数,befor 加 1,每吃一个蛋,ssize 加 1。比较 ssize 和 befor,如果相等,证明球没有吃蛋,不用产生新的随机数,仍在 原位置输出随机数,否则,产生新的随机数。球的大小的变化。每次吃到一个蛋后,ssize加1,并且将更新前球的位置加入到snake, 这样下次输出就能够多输出一个球,实现了大小的增加。 关卡的切换:每当球吃了一个蛋以后,判断是否达到了球的最大大小20,如果达到了, 继续判断是否的达到了等待时间的最小值(也就是最高的一关),如果没有达到,就更 新等待时间,进入下一关卡之前,还要将球的大小 ssize 和初始大小befor 分别设为 14 和 13。将记录球位置的内存 snake 的前 14 个字更新到屏幕中央。之后,就可以进入下 一关了。如果已经达到了最高关,并且通过了,就结束程序。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值