目录
一、绘制圆和针
首先是圆和针的绘制代码如下:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
int main()
{
float PI = 3.1415926;
int width = 800;
int height = 600;
initgraph(width, height); // 初始化图形模式,设置窗口的宽度和高度
setbkcolor(RGB(255, 255, 255)); // 设置背景颜色为白色
cleardevice(); // 清空背景
setlinestyle(PS_SOLID, 3); // 设置线型为实线,线宽为3
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
line( width/2, height / 2, width / 2+160, height / 2); // 绘制一条水平直线
setlinecolor(HSVtoRGB(0, 0.9, 0.8)); // 设置颜色为红色
circle(width / 2, height / 2, 60); // 在中心绘制一个半径为60的圆
_getch(); // 等待用户按键
closegraph(); // 关闭图形模式
return 0;
}
二、旋转后的针
2.1 针的旋转坐标
因为我们的针需要在每一个瞬间不断变化以此来达到旋转的效果,所以对于针的绘制,我们需要改变其末端坐标,但是由于末端坐标不能用简单的 (X末端+常数C,Y末端+常数C)来求解其不断变化规律,我们需要引入新的相关变量。
针的起点(xCenter,yCenter)和针长lineLength,与旋转角度angle已知的情况下,可以采用三角函数公式来求解末端坐标(xEnd,yEnd)的值
xEnd = xCenter + lineLength * cos(angle);
yEnd = yCenter + lineLength * sin(angle);
#include <graphics.h>
#include <conio.h>
#include <math.h>
int main() {
int width = 800, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
cleardevice();
float PI = 3.1415926f;
float lineLength = 160.0f; // 针的长度
float angle = PI / 3; // 针的角度
setlinestyle(PS_SOLID, 3);
setlinecolor(BLACK);
float xCenter = width / 2.0f;
float yCenter = height / 2.0f;
float xEnd = xCenter + lineLength * cos(-angle);
float yEnd = yCenter + lineLength * sin(-angle); // 注意y轴方向
line(xCenter, yCenter, xEnd, yEnd);
// 绘制中心圆盘
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
circle(xCenter, yCenter, 60); // 注意使用solidcircle而不是circle,除非你想要绘制空心圆
_getch();
closegraph();
return 0;
}
需要注意的是如下几点:
1、关于头文件——这次为了使用三角函数增加了新的头文件<math.h>,以下是部分头文件对应的功能
<math.h>——包含了三角函数等数学功能
<graphics.h>——提供了交互绘图的功能
<conio.h>——提供了 _getch()、rand()等函数
<stdio.h>——提供了printf()、scanf()等函数
在实际应用中要根据程序需求将必要头文件包含进来
2、计算针末端的位置—— Easy绘图坐标系y轴方向和一般数学坐标系相反, 所以这里取 -angle 而不是 angle
xEnd = xCenter + lineLength * cos(-angle);
yEnd = yCenter + lineLength * sin(-angle); // 注意y轴方向
最后输出的结果如下图所示:
2.2 针的旋转动画效果
和之前的代码一样,采用循环进行画一帧擦除一帧的处理方式来实现小球的旋转动画效果,至于他旋转的快慢由角度变化来决定
angle=angle+rotateSpeed
具体代码如下:
#include <graphics.h>
#include <conio.h>
#include <math.h>
int main() {
int width = 800, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
float PI = 3.1415926f;
float lineLength = 160.0f; // 针的长度
float xCenter = width / 2.0f;
float yCenter = height / 2.0f;
float angle = 0; // 针的初识角度
float rotateSpeed = PI/360;//针的旋转速度
setlinestyle(PS_SOLID, 3);
while (1)
{//注意针的绘制和屏幕清空都要在循环中完成,清空完再绘制线和圆
cleardevice();
angle = angle + rotateSpeed;
if(angle>2*PI)
angle = angle - 2*PI;//防止角度无限增加
float xEnd = xCenter + lineLength * cos(-angle);
float yEnd = yCenter + lineLength * sin(-angle);
setlinecolor(BLACK);
line(xCenter, yCenter, xEnd, yEnd);
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
circle(xCenter, yCenter, 60);
Sleep(10);
}
closegraph();
return 0;
}
ps:因为PI的变量是我们全程不希望意外改变的,为了防止PI的变量被修改,可以再前面加上const,或者采用宏定义的方式——即在程序开头写下
#define PI 3.1415926
最后结果如下,我们得到了一根针旋转的动画:
2.3采用数组实现多根针插入
为了在图中同时显示多根针,我们需要存储多个针的位置数据,由此我们加入数组来对数据进行存储。
以下是一个关于数组的代码:
#include<conio.h>
#include<stdio.h>
int main()
{
int i;
int a[5]={1,3,5,7,9};
for(i=0;i<5;i++)
printf("%d",a[i]) ;
printf("\n");
int b[5]={2,4,6};
for(i=0;i<5;i++)
printf("%d",b[i]) ;
printf("\n");
_getch();
return 0;
}
关于数组
int A[ n]={a1,a2,a3,a4,a5...an}
A作为数组名,n为其中元素个数,其中数组中有n个元素——A[0],A[1]......A[n-1](注意,数组下标是从零开始编号的)
可以如同上述代码中一样采用花括号给数组的元素依次初始化,对于未初始化的元素会自动赋值为零。
上述代码输出结果如下:
接下来,采用数组来存储针的角度,假设有目前有20根针需要存储。
需要添加的关键代码如下 :
int lineNum=20; //有20根针,循环中用于计数
float Angles[20]; //用数组来存储20根针的不同角度
int i;
for(i=0;i<lineNum;i++)//依次存储20根针的角度
Angle[i]=i*2*PI/lineNum;//i逐次递增,针的角度也均匀增加
完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <math.h>
int main() {
int width = 800, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
float PI = 3.1415926f;
float lineLength = 160.0f; // 针的长度
float xCenter = width / 2.0f;
float yCenter = height / 2.0f;
float angle = 0; // 针的初识角度
float rotateSpeed = PI/360;//针的旋转速度
setlinestyle(PS_SOLID, 3);
int lineNum = 20;
float Angles[20];
int i;
for (i = 0;i < lineNum;i++)
Angles[i] = i * 2 * PI / lineNum;//先依次给Angles[x]赋值,之后再变化
while (1)
{//注意针的绘制和屏幕清空都要在循环中完成,清空完再绘制线和圆
cleardevice();
for (i = 0;i < lineNum;i++)
{
Angles[i] = Angles[i] + rotateSpeed;//每个均分下来的针都以这个速度运动
if (Angles[i] > 2 * PI)
Angles[i] = Angles[i] - 2 * PI;//防止角度无限增加
float xEnd = xCenter + lineLength * cos(-Angles[i]);
float yEnd = yCenter + lineLength * sin(-Angles[i]);
setlinecolor(BLACK);
line(xCenter, yCenter, xEnd, yEnd);
}
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
circle(xCenter, yCenter, 60);
Sleep(10);
}
closegraph();
return 0;
}
代码结果如图:
批量绘制函数
在绘制元素较多时,会出现明显的画面闪烁,只是可以使用批量绘制函数。
BeginBatchDraw()用于开始批量绘图,执行后任何绘图操作都将暂时不显示到屏幕上,直到执行FlushBatchDraw()或EndBatchDraw()才将之前绘图输出;
FlushBatchDraw()用于执行未完成的绘制任务,执行批量绘制;
EndBatchDraw()用于结束批量绘制;
BeginBatchDraw()//开始批量绘制
while(1)//重复循环
{
// 之间代码包括多条绘制函数
FlushBatchDraw();用于执行未完成的绘制任务,执行批量绘制;
Sleep(10);//暂停十毫秒
}
三、针的发射与新增
给出待发射的针的位置——屏幕左边
line(0,height/2,lineLength,height/2);
并且初始状态下针不旋转。
int lineNum=0;
在针的绘制中,for (i = 0;i < lineNum;i++),由于int lineNum=0,所以直接跳出绘制的循环,直接不绘制,即——初始只有最左的针,没有旋转。
然后是按键交互如果按键,绘制新的针(针插入)
if (_kbhit())
{
char input = _getch();//获取用户按键输入
if (input == ' ')
{
lineNum++;
Angles[lineNum - 1] = PI;//新加入针的角度
xEnd= xCenter + lineLength * cos(-Angles[lineNum - 1]);
yEnd = yCenter + lineLength * sin(-Angles[lineNum - 1]);//全新指针的末端坐标
line(xCenter, yCenter, xEnd, yEnd);//绘制一根针
}
}
综上所述,完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <stdio.h>
int main() {
int width = 800, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
float PI = 3.1415926f;
float lineLength = 160.0f; // 针的长度
float xCenter = width / 2.0f;
float yCenter = height / 2.0f;
float angle = 0; // 针的初识角度
float rotateSpeed = PI/360;//针的旋转速度
float xEnd, yEnd;
setlinestyle(PS_SOLID, 3);
int lineNum = 0;
float Angles[100];
int i;
for (i = 0;i < lineNum;i++)
Angles[i] = i * 2 * PI / lineNum;//先依次给Angles[x]赋值,之后再变化
BeginBatchDraw();
while (1)
{//注意针的绘制和屏幕清空都要在循环中完成,清空完再绘制线和圆
cleardevice();
setlinecolor(BLACK);
line(0, yCenter, lineLength, yCenter);
for (i = 0;i < lineNum;i++)
{
Angles[i] = Angles[i] + rotateSpeed;//每个均分下来的针都以这个速度运动
if (Angles[i] > 2 * PI)
Angles[i] = Angles[i] - 2 * PI;//防止角度无限增加
xEnd = xCenter + lineLength * cos(-Angles[i]);
yEnd = yCenter + lineLength * sin(-Angles[i]);
setlinecolor(BLACK);
line(xCenter, yCenter, xEnd, yEnd);
}
if (_kbhit())
{
char input = _getch();//获取用户按键输入
if (input == ' ')
{
lineNum++;
Angles[lineNum - 1] = PI;//新加入针的角度
xEnd= xCenter + lineLength * cos(-Angles[lineNum - 1]);
yEnd = yCenter + lineLength * sin(-Angles[lineNum - 1]);//全新指针的末端坐标
line(xCenter, yCenter, xEnd, yEnd);//绘制一根针
}
}
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
circle(xCenter, yCenter, 60);
FlushBatchDraw();
Sleep(10);
}
closegraph();
return 0;
}
需要注意的是:
关于 kbhit()
函数,它实际上是 _kbhit()
的一个变种或简化形式。在某些编译器或环境中,为了简化代码或提高可读性,可能会省略下划线前缀,直接使用 kbhit()
。但这并不是普遍适用的,具体能否使用取决于你的编译器和环境是否提供了这样的函数实现。
其次,批量绘制函数的开头与结尾位置,确实可能会导致输出结果无法达到预期。
最后完整结果如下,按下空格键时会增加新的针:
四、游戏失败判断
当新增加的针与已经存在的针发生碰撞时,游戏失败。设定当两根针旋转的角度差值绝对值小于PI/60时,认为两根针足够接近。
for(i=0;i<lineNum-1;i++)
{
if(abs(Angles[lineNum-1]-Angles[i])<PI/60)
{
rotateSpeed=0;//速度置为0
break;//不用比较直接跳出循环
}
}
其中
abs()是求取绝对值的函数
break; 被称为里流程跳转语句,在while或者for循环语句中执行break;,表示跳出当前循环,直接输入循环之后的代码。
与之对应的还有个continue;语句,表示跳过当此循环,循环语句继续运行。
continue与break的辨析
如图所示,continue;只是跳过其中一项,而break;是直接跳出整个循环
五、得分与显示效果改进
参考之前的记分方法,给出记分部分的代码如下,提前
int score=0;
TCHAR s[20];//得分的字符处理
_stprintf_s(s, _T("得分:%d"), score);
settextstyle(40, 0, _T("黑体"));
settextcolor(BLACK);
outtextxy(65, 200, s);
按空格且没失败,得分+1,失败得分归零;
if (_kbhit() && rotateSpeed!=0)
{
char input = _getch();//获取用户按键输入
if (input == ' ')
{
lineNum++;
Angles[lineNum - 1] = PI;//新加入针的角度
xEnd= xCenter + lineLength * cos(-Angles[lineNum - 1]);
yEnd = yCenter + lineLength * sin(-Angles[lineNum - 1]);//全新指针的末端坐标
line(xCenter, yCenter, xEnd, yEnd);//绘制一根针
for (i = 0;i < lineNum - 1;i++)//新的针与老针比较判断碰撞与否
{
if (fabs(Angles[lineNum - 1] - Angles[i]) < PI / 60)
{
rotateSpeed = 0;//速度置为0
score=-1;//跳出去后要+1,所以这样才能把分数置零
break;//不用比较直接跳出循环
}
}
score = score + 1;//不满足if条件直接加分
}
}
把正在旋转的指针设为i蓝色,最新发送的指针设为红色
setlinecolor(BLUE);//已经插入的旋转中针为蓝色
if(i== lineNum-1)//最新发射的针设定为红色
setlinecolor(RED);
line(xCenter, yCenter, xEnd, yEnd);
最后,实现插入的针越多,圆盘颜色越深
setfillcolor(HSVtoRGB(0, lineNum / 60.0, 0.8));
//针越多中间的圆盘越红
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
fillcircle(xCenter, yCenter, 60);
综上所述,最后完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <stdio.h>
int main() {
int width = 800, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
float PI = 3.1415926f;
float lineLength = 160.0f; // 针的长度
float xCenter = width / 2.0f;
float yCenter = height / 2.0f;
float angle = 0; // 针的初识角度
float rotateSpeed = PI/360;//针的旋转速度
float xEnd, yEnd;
setlinestyle(PS_SOLID, 3);
int lineNum = 0;
float Angles[100];
int score = 0;
int i;
for (i = 0;i < lineNum;i++)
Angles[i] = i * 2 * PI / lineNum;//先依次给Angles[x]赋值,之后再变化
BeginBatchDraw();
while (1)
{//注意针的绘制和屏幕清空都要在循环中完成,清空完再绘制线和圆
cleardevice();
setlinecolor(BLACK);
line(0, yCenter, lineLength, yCenter);
for (i = 0;i < lineNum;i++)
{
Angles[i] = Angles[i] + rotateSpeed;//每个均分下来的针都以这个速度运动
if (Angles[i] > 2 * PI)
Angles[i] = Angles[i] - 2 * PI;//防止角度无限增加
xEnd = xCenter + lineLength * cos(-Angles[i]);
yEnd = yCenter + lineLength * sin(-Angles[i]);
setlinecolor(BLUE);//已经插入的旋转中针为蓝色
if(i== lineNum-1)//最新发射的针设定为红色
setlinecolor(RED);
line(xCenter, yCenter, xEnd, yEnd);
}
if (_kbhit() )
{
char input = _getch();//获取用户按键输入
if (input == ' ')
{
lineNum++;
Angles[lineNum - 1] = PI;//新加入针的角度
xEnd= xCenter + lineLength * cos(-Angles[lineNum - 1]);
yEnd = yCenter + lineLength * sin(-Angles[lineNum - 1]);//全新指针的末端坐标
line(xCenter, yCenter, xEnd, yEnd);//绘制一根针
for (i = 0;i < lineNum - 1;i++)//新的针与老针比较判断碰撞与否
{
if (fabs(Angles[lineNum - 1] - Angles[i]) < PI / 60)
{
rotateSpeed = 0;//速度置为0
score = -1;
break;//不用比较直接跳出循环
}
}
score = score + 1;
}
}
setfillcolor(HSVtoRGB(0, lineNum / 60.0, 0.8));
//针越多中间的圆盘越红
setlinecolor(RED);// 假设RED已定义或你可以使用RGB宏
fillcircle(xCenter, yCenter, 60);
TCHAR s[20];//得分的字符处理
_stprintf_s(s, _T("得分:%d"), score);
settextstyle(40, 0, _T("黑体"));
settextcolor(BLACK);
outtextxy(65, 200, s);
FlushBatchDraw();
Sleep(10);
}
closegraph();
return 0;
}
最后实现的效果如下图:
ps.原本的代码中有一句
if (_kbhit() && rotateSpeed!=0)
目的是为了在速度为零的时候,按键不会进入循环,但实际操作中,速度为零之后,按键再怎么按都是在会再插入碰撞的部分,分数置零,速度保持为零,不会有实际效果的影响