深圳大学电信C语言期末大作业 弹球模拟
实验16-17:综合实验 4-在二维封闭房间中的弹球模拟程序
实验任务:
(1) 进一步掌握数组的定义与使用;进一步掌握函数的定义和函数调用方法; (2) 学习和掌握结构体的定义和使用方法。
(3) 进一步掌握C 语言的编程方法;学习动画程序的基本设计思想和方法。
(4) 编译并运行你的程序。调试正确后将原程序工程文件目录压缩后提交到Blackboard。其中压缩文件名称的前两个字母为你的姓与名的拼音的首字母。
(5) 提交正式的实验报告
一、 对实验报告的要求:
1) 实验报告文档的书写格式与 综合实验1 的要求一致。2) 实验报告内容要求有:
实验目的
实验要求
实验程序实现方法的流程图
对流程图的简要说明
实验结果
对本课程的意见和建议
二、 程序思想的一些说明
本实验编写一个在控制台窗口中,在不考虑重力的条件下,模拟一组弹球在一个二维封闭房间中运
动的程序。本实验中,涉及到计算机动画的生成原理。
在计算机中如何生成动画?
所谓动画,实际上是按照一定的时间间隔显示的图像,在这些图像的每一帧之间都有一些不同。
在计算机中,每一帧图像是以内存中的一个二维数组的形式存储的。数组中的每一个元素的值代表图像中的一个像素值。由于在 VC6 的集成开发环境的控制台窗口中可以显示 25 行 80 列的字符,因此该窗口一帧图像的大小最大为25*80 个像素。在本次试验中,动画中图像的大小规定为24 行79 列。因此,可以定义一个 24*80 的二维数组,该数组的最后 1 列存储字符串结束标志”\0”,以便可以使用字符串函数的形式显示二维数组中的每一行字符。
要想使一个图像序列在连续显示时看起来像动画,每一帧图像在屏幕上的停留时间要基本与人眼的视觉暂留时间相适应。因此在显示每一帧图像以后,还要继续适当延时,然后再进行下一帧图像的显示。因此你的模拟程序中需要有一个延时函数,以控制每一帧图像的显示时间。
要想让动画连续不断地进行,还需要设计一个不限定循环次数的循环。
如何在一个二维数组中绘制一幅图像?
首先,需要对数组元素进行初始化。初始化的实质是将背景图像重新写入到二维数组中。
然后,将要绘制的图形以像素点的形式写入对应的二维数组元素中,二维数组中的每个元素对应于一个像素点。
如何显示二维数组中的图像?
图像显示的实质,就是将二维字符数组中存储的每个字符输出到屏幕上。在本次实验的程序中,实际就是输出到控制台窗口中。由于图像以字符阵列的形式存储在二维数组中,因此,可以用一个字符串输出的循环实现。
在本次实验的程序中,为了加快字符数组的显示过程,在二维数组的每一行的最后一个元素中,可以写入字符串结束标志:”\n”,然后用字符串输出函数显示二维数组的每一行字符。
如何让一个弹球运动?
1) 定义描述一个弹球的结构体BALL,一种可能的形式如下:
struct BALL{
char body[2]; //两个不同的字符,分别代表两个不同颜色的球int sel; //当前球的颜色。0表示第一种颜色,1表示第二种颜色int wX; //在二维数组中,球在x方向的实际显示位置(整数)int wY; //在二维数组中,球在y方向的实际显示位置(整数)double X; //球在x方向的精确位置(实数) double Y; //球在y方向的精确位置(实数) double dX; //球在x方向的速度(实数) double dY; //球在y方向的速度(实数)
};
其中,结构体中的每一个成员的说明如上所示。
2) 对弹球BALL 结构体的每一个元素进行初始化
为了使模拟程序看起来更自然,我们可以用随机数对其进行初始化:
随机生成0、1最为当前弹球的颜色值 sel;
随机生成 1-22 之间的随机数,最为当前弹球的行坐标位置 wX,X;
随机生成 1-77 之间的随机数,最为当前弹球的列坐标位置 wY,Y;
每个弹球的速度大小都是1,但速度的方向θ是一个0-359之间的随机数,表示角度。这样它的X、Y方向的速度分量分别为:
dX = cos(πθ/180);dY = sin(πθ/180);
3) 弹球根据自己的速度,移动一步
弹球运动的实质是改变弹球当前的位置。由于弹球在X、Y方向的速度分量dX、dY都为 < 1 的值,因此弹球一步运动后的精确位置是两个实数分量:
X = X + dX;Y = Y + dY;
但是,弹球在二维数组图像中的显示位置是二维数组的行、列两个下标,只能是整数值。因此,需要对弹球当前的精确实数位置进行四舍五入取整,得到实际显示的数组行、列位置wX、wY。可以用下面的方法实现四舍五入取整:
wX = (int)( X + 0.5);wY = (int)( Y + 0.5);
如何检测弹球撞到了墙壁?如何弹回来?
假设,弹球当前的位置是(X,Y),弹球运动一步以后的位置是:
X = X + dX;
Y = Y + dY;
假设表示图像的二维字符数组有24行,则若 X<0,则说明弹球撞到了上面的墙壁;X>23,则说明弹球撞到了下面的墙壁。
检测到弹球撞墙壁后,弹球应该被弹回。也就是说弹球的速度分量需要改变方向,并且被弹回到上次的位置。具体可用下面数学模型实现:
dX = - dX;
X = X + dX;
对弹球在左、右方向(即 Y方向)的撞墙检测,以及被弹回的原理同上。
如何检测两个弹球相撞?
首先,根据两个弹球的当前位置(X1,Y1)、(X2,Y2),计算它们之间的距离:
dist = sqrt((X1-X2)^2 + (Y1 – Y2)^2);
然后,若 dist < 1,则可判定两个弹球相撞。
如何让弹球的速度方向改变90 度?
若弹球当前的速度矢量为(dX1,dY1),则方向改变90度后的速度矢量(dX2,dY2)为:
dX2 = dY1
dY2 = dX1
三、 对编程的具体要求:
1) 每个弹球用一个结构体BALL 变量描述(可参考前面的程序说明),一组弹球用结构体数组定义;
2) 除main 主程序外,你的程序至少还要编写4 个以上的子函数。
3) 主程序在一个单独的原文件中;其余子程序放在另外一个源文件中;必要的声明与定义,包括结构体的定义,以及子程序的声明等放在自定义的一个头文件中。
4) 本模拟程序可以模拟1 到NUM 个弹球的运动,NUM 是由预处理语句定义的一个整型常量,表示模拟中可能的最多的弹球数。
5) 模拟程序在每次运行时,弹球的实际个数由用户输入。若用户输入的弹球个数小于1,则设置弹球数量为 1;若用户输入的弹球个数大于 NUM 时,则设置弹球个数为 NUM;若用户输入的弹球个数在1 到NUM 之间,则弹球个数为用户输入的个数。
6) 弹球在画面中的初始位置、运动方向(运动速度为 1.0)、显示的颜色(不同颜色可以不同的字符
表示)等,在程序运行开始时随机设置。
7) 弹球运动时,如果碰到墙壁直接弹回;如果碰到地面(下墙壁),则在弹回的同时,要求发出碰撞声,同时统计弹球撞到地面的次数。发出碰撞声可以使用下面语句实现:putchar('\7');
8) 弹球运动过程中,如果两个弹球相撞,两个弹球的速度不变,但运动速度的方向都改变90 度,同时两个弹球的颜色都发生改变。
9) 附件程序bounceNBalls.exe 是一个可运行的模拟参考程序。
四、 拓展编程(选作):
1) 假设你模拟的是在封闭盒子中游走的一组贪食蛇,如何设计你的程序,谈谈你的设计思想。
2) 假设你模拟的是在封闭盒子中的贪食蛇吃老鼠,如何设计你的程序,谈谈你的设计思想。
五、 本实验编程的一些提示:
主程序 main 功能:可包括随机数发生器初始化,确定接受用户输入的弹球数,初始化弹球参数,控制模拟程序的循环运行,输出弹球碰撞地面的总次数,等。
你的程序中可能的一些子函数:
void initBall(struct BALL ball[], int num)
功能:对num个弹球的数据进行初始化。
int moveBall(struct BALL ball[], int num)
功能:将num个弹球根据各自的速度移动一次。返回本次运动中弹球碰撞地面的次数。若弹球撞墙,则被弹回;特别,若弹球撞到地面(下面的墙),则被弹回的同时,发出弹回的声音。若两个弹球相撞,则两个弹球都改变自身的颜色,改变速度的方向。
void redrawCWin(struct BALL ball[], int num)
功能:根据num个弹球的最新位置,在屏幕上绘制出当前弹球位置的图像。
void delay(int nTime)
功能:延时一段时间。其中,参数 nTime 调节延时的长度。
int randX(void)
功能:生成一个随机数,作为弹球的初始行位置。
int randY(void)
功能:生成一个随机数,作为弹球的初始列位置。
int randA(void)
功能:生成0-359之间的一个随机数,作为弹球的运动方向。
double distBetweenBalls(struct BALL ball[], int k, int i)
功能:计算弹球 k 和弹球 i 之间的距离。
void initCharPicture(char cWin[24][80])
功能:将图像数组初始化为背景图模式。
//头文件
#include "head.h"
void initBall(struct BALL ball[], int num)
{
int i;
for (i = 0; i < num; i++)
{
ball[i].body[0]='*';
ball[i].body[1]='0';
ball[i].sel = randsel();
ball[i].X = randX();
ball[i].Y = randY();
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
ball[i].dX = cos((PI* randA() )/180);
ball[i].dY = sin((PI* randA() )/180);
}
}
int randsel(void)
{
int s;
s = rand() % 2;
return s;
}
int randX(void)//功能:生成一个随机数,作为弹球的初始行位置。
{
return (1+rand()%22);
}
int randY(void)//功能:生成一个随机数,作为弹球的初始列位置。
{
return (1+rand()%77);
}
int randA(void)//功能:生成0-359之间的一个随机数,作为弹球的运动方向。
{
return (rand()%359);
}
double distBetweenBalls(struct BALL ball[], int k, int i)//功能:计算弹球 k 和弹球 i 之间的距离。
{
double dist;
dist = sqrt(pow((ball[k].X - ball[i].X),2)+pow((ball[k].Y-ball[i].Y),2));
return dist;
}
void initCharPicture(char cWin[24][80])//功能:将图像数组初始化为背景图模式。
{
int i,j;
for(i=0;i<24;i++)
{
for(j=0;j<79;j++)
{
cWin[i][j]=' ';
}
}
for(i=0;i<24;i++)
cWin[i][79]='\0';
}
void delay(int nTime)//功能:延时一段时间。其中,参数 nTime 调节延时的长度。
{
int i,j;
for(i=0;i<nTime;i++)
{
for(j=0;j<1100000;j++);
}
}
int moveBall(struct BALL ball[], int num, int p)//功能:将num个弹球根据各自的速度移动一次。返回本次运动中弹球碰撞地面的次数。若弹球撞墙,则被弹回;特别,若弹球撞到地面(下面的墙),则被弹回的同时,发出弹回的声音。若两个弹球相撞,则两个弹球都改变自身的颜色,改变速度的方向。
{
int i;
p=0;
for(i=0;i<num;i++)
{
ball[i].X += ball[i].dX ;
ball[i].Y += ball[i].dY ;
}
for (i = 0; i < num; i++)
{
int j;
if(ball[i].X>23)
{
putchar('\7');
p++;
}
if(ball[i].X<0||ball[i].X>23)
{
ball[i].dX = -ball[i].dX;
ball[i].X += ball[i].dX ;
}
else if(ball[i].Y<0||ball[i].Y>79)
{
ball[i].dY = -ball[i].dY;
ball[i].Y += ball[i].dY;
}
else
{
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
}
for(j=i+1;j<num;j++)
{
double dist=distBetweenBalls(ball, i, j);
if(dist<1)
{
double t;
t=ball[i].dX;
ball[i].dX=ball[i].dY;
ball[i].dY=t;
if(ball[i].sel==0)
ball[i].sel=1;
else
ball[i].sel=0;
t=ball[j].dX;
ball[j].dX=ball[j].dY;
ball[j].dY=t;
if(ball[i].sel==0)
ball[j].sel=1;
else
ball[j].sel=0;
}
}
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
}
return p;
}
void redrawCWin(struct BALL ball[], int num)//功能:根据num个弹球的最新位置,在屏幕上绘制出当前弹球位置的图像。
{
int i;
char cWin[24][80];
initCharPicture(cWin);
for (i=0;i<num;i++)
{
cWin[ball[i].wX][ball[i].wY]= ball[i].body[ball[i].sel];
}
for (i=0;i<24;i++)
{
printf("%s",cWin[i]);
printf("\n");
}
}
//函数
#include "head.h"
void initBall(struct BALL ball[], int num)
{
int i;
for (i = 0; i < num; i++)
{
ball[i].body[0]='*';
ball[i].body[1]='0';
ball[i].sel = randsel();
ball[i].X = randX();
ball[i].Y = randY();
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
ball[i].dX = cos((PI* randA() )/180);
ball[i].dY = sin((PI* randA() )/180);
}
}
int randsel(void)
{
int s;
s = rand() % 2;
return s;
}
int randX(void)//功能:生成一个随机数,作为弹球的初始行位置。
{
return (1+rand()%22);
}
int randY(void)//功能:生成一个随机数,作为弹球的初始列位置。
{
return (1+rand()%77);
}
int randA(void)//功能:生成0-359之间的一个随机数,作为弹球的运动方向。
{
return (rand()%359);
}
double distBetweenBalls(struct BALL ball[], int k, int i)//功能:计算弹球 k 和弹球 i 之间的距离。
{
double dist;
dist = sqrt(pow((ball[k].X - ball[i].X),2)+pow((ball[k].Y-ball[i].Y),2));
return dist;
}
void initCharPicture(char cWin[24][80])//功能:将图像数组初始化为背景图模式。
{
int i,j;
for(i=0;i<24;i++)
{
for(j=0;j<79;j++)
{
cWin[i][j]=' ';
}
}
for(i=0;i<24;i++)
cWin[i][79]='\0';
}
void delay(int nTime)//功能:延时一段时间。其中,参数 nTime 调节延时的长度。
{
int i,j;
for(i=0;i<nTime;i++)
{
for(j=0;j<1100000;j++);
}
}
int moveBall(struct BALL ball[], int num, int p)//功能:将num个弹球根据各自的速度移动一次。返回本次运动中弹球碰撞地面的次数。若弹球撞墙,则被弹回;特别,若弹球撞到地面(下面的墙),则被弹回的同时,发出弹回的声音。若两个弹球相撞,则两个弹球都改变自身的颜色,改变速度的方向。
{
int i;
p=0;
for(i=0;i<num;i++)
{
ball[i].X += ball[i].dX ;
ball[i].Y += ball[i].dY ;
}
for (i = 0; i < num; i++)
{
int j;
if(ball[i].X>23)
{
putchar('\7');
p++;
}
if(ball[i].X<0||ball[i].X>23)
{
ball[i].dX = -ball[i].dX;
ball[i].X += ball[i].dX ;
}
else if(ball[i].Y<0||ball[i].Y>79)
{
ball[i].dY = -ball[i].dY;
ball[i].Y += ball[i].dY;
}
else
{
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
}
for(j=i+1;j<num;j++)
{
double dist=distBetweenBalls(ball, i, j);
if(dist<1)
{
double t;
t=ball[i].dX;
ball[i].dX=ball[i].dY;
ball[i].dY=t;
if(ball[i].sel==0)
ball[i].sel=1;
else
ball[i].sel=0;
t=ball[j].dX;
ball[j].dX=ball[j].dY;
ball[j].dY=t;
if(ball[i].sel==0)
ball[j].sel=1;
else
ball[j].sel=0;
}
}
ball[i].wX = (int)(ball[i].X + 0.5);
ball[i].wY = (int)(ball[i].Y + 0.5);
}
return p;
}
void redrawCWin(struct BALL ball[], int num)//功能:根据num个弹球的最新位置,在屏幕上绘制出当前弹球位置的图像。
{
int i;
char cWin[24][80];
initCharPicture(cWin);
for (i=0;i<num;i++)
{
cWin[ball[i].wX][ball[i].wY]= ball[i].body[ball[i].sel];
}
for (i=0;i<24;i++)
{
printf("%s",cWin[i]);
printf("\n");
}
}
//主函数
#include "head.h"
void main()
{
int n,p=0;
printf("请输入弹球的个数:");
scanf("%d", &n);
if (n < 1)
n = 1;
else if (n > NUM)
n = NUM;
struct BALL b[NUM];
srand(time(NULL));
initBall(b, n);
redrawCWin(b, n);
while(1)
{
system("CLS");
p+=moveBall(b, n,p);
printf ("落地次数:%d",p);
redrawCWin(b, n);
delay(50);
}
}