目标 :实现随机生成不相交的圆,并且尽量填满画布,按键可以切换不同的绘制模式。
知识点:函数定义
目录
一、绘制多个圆
1.1 把多个圆一口气摆上画布
首先,为了尽量填满画布,我们需要生成很多个圆,这点很简单,循环进行画圆操作,并且每次给圆随机的坐标和半径即可
int xArray[100]; // 数组存储所有圆心的x坐标
int yArray[100]; // 数组存储所有圆心的y坐标
int rArray[100]; // 数组存储所有圆的半径
设定圆的个数circleNum = 100,在for循环中随机生成圆心坐标、半径,并存储在数组中:
for (i = 0; i < circleNum; i++) // 生成circleNum个圆,把数据保存在数组中
{
xArray[i] = rand() % width; // 圆心的x坐标
yArray[i] = rand() % height; // 圆心的y坐标
rArray[i] = rand() % (rmax - rmin + 1) + rmin; // 圆的半径
}
然后对数组的所有元素进行遍历,即可绘制出所有圆的图案:
for (i = 0; i < circleNum; i++) // 绘制出所有的圆
{
fillcircle(xArray[i], yArray[i], rArray[i]); // 用数组存储的数据绘制圆
}
该部分完整代码如下:
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
int main()
{
//创建窗口绘制背景
int width = 600, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
cleardevice();
srand(time(0));//随机种子函数
int xArray[100]; // 数组存储所有圆心的x坐标
int yArray[100]; // 数组存储所有圆心的y坐标
int rArray[100]; // 数组存储所有圆的半径
int rmin = 8;
int rmax = 50;
int circleNum = 100;//定义随机圆的最小最大半径以及个数
int i;
for (i = 0; i < circleNum; i++) // 生成circleNum个圆,把数据保存在数组中
{
xArray[i] = rand() % width; // 圆心的x坐标
yArray[i] = rand() % height; // 圆心的y坐标
rArray[i] = rand() % (rmax - rmin + 1) + rmin; // 圆的半径
}
for (i = 0; i < circleNum; i++) // 绘制出所有的圆
{
setlinecolor(BLACK);
setfillcolor(BLUE);
fillcircle(xArray[i], yArray[i], rArray[i]); // 用数组存储的数据绘制圆
}
_getch();
closegraph();
return 0;
}
输出结果如下:
1.2依次显示圆
为展示圆生成的过程,每次生成后暂停100毫秒
Sleep(100);
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
int main()
{
//创建窗口绘制背景
int width = 600, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
cleardevice();
srand(time(0));//随机种子函数
int xArray[100]; // 数组存储所有圆心的x坐标
int yArray[100]; // 数组存储所有圆心的y坐标
int rArray[100]; // 数组存储所有圆的半径
int rmin = 8;
int rmax = 50;
int circleNum = 100;//定义随机圆的最小最大半径以及个数
int i;
for (i = 0; i < circleNum; i++) // 生成circleNum个圆,把数据保存在数组中
{
xArray[i] = rand() % width; // 圆心的x坐标
yArray[i] = rand() % height; // 圆心的y坐标
rArray[i] = rand() % (rmax - rmin + 1) + rmin; // 圆的半径
}
for (i = 0; i < circleNum; i++) // 绘制出所有的圆
{
setlinecolor(BLACK);
setfillcolor(BLUE);
fillcircle(xArray[i], yArray[i], rArray[i]);// 用数组存储的数据绘制圆
Sleep(100);
}
_getch();
closegraph();
return 0;
}
输出结果如下:
同样,我们可以采用for循环以外的循环方式来实现相同效果;
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
int main()
{
//创建窗口绘制背景
int width = 600, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
cleardevice();
srand(time(0));//随机种子函数
int xArray[100]; // 数组存储所有圆心的x坐标
int yArray[100]; // 数组存储所有圆心的y坐标
int rArray[100]; // 数组存储所有圆的半径
int rmin = 8;
int rmax = 50;
int circleNum = 0;//定义随机圆的最小最大半径以及个数
int x, y, r;
while (circleNum < 100)
{ // 当圆的个数小于100时,循环运行
x = rand() % width; // 新圆的圆心x坐标
y = rand() % height; // 新圆的圆心y坐标
r = rand() % (rmax - rmin + 1) + rmin; // 新圆的半径
xArray[circleNum] = x; // 把新圆的圆心x坐标添加到数组
yArray[circleNum] = y; // 把新圆的圆心y坐标添加到数组
rArray[circleNum] = r; // 把新圆的半径添加到数组中
circleNum++; // 圆的数量+1
// 假设以下函数用于设置绘图属性并绘制圆
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
Sleep(100); // 暂停100毫秒
}
_getch();
closegraph();
return 0;
}
对于while()函数, 其中while (circleNum < 100) 表示当circleNum < 100为真时,while中语句循环运行
前者的for循环采用了如下策略——第一个for循环一口气存完了所有圆的半径、坐标,再用第二给for循环依次把数据读取出来,再进行绘制
后者的while循环采用了如下策略——先记录一个圆的数据进入数组,数组位置后移,利用刚刚的数据画圆,画完后更新数据,再次录入数组,一个个进行存储绘制
1.3新圆与老圆不相交
一个很简单的数学问题,两个圆心之间的距离平方要大于等于它们两个半径之和的平方。
即
(r1+r2)(r1+r2)<=(x1-x2)(x1-x2)+(y1-y2)(y1-y2)
关键代码如下:
while (circleNum < 1000)
{ // 当圆的个数小于1000时,循环运行
isNewCircleOK = 0; // 假设开始不OK
while (isNewCircleOK == 0)
{ // 当新生成的圆不ok时,重复生成新圆进行比较
x = rand() % width; // 新圆的圆心x坐标
y = rand() % height; // 新圆的圆心y坐标
r = rand() % (rmax - rmin + 1) + rmin; // 新圆的半径
for ( i = 0; i < circleNum; i++)
{ // 对已有圆遍历
float dist2 = (xArray[i] - x) * (xArray[i] - x) + (yArray[i] - y) * (yArray[i] - y);
float r2 = (rArray[i] + r) * (rArray[i] + r);
if (dist2 < r2)
{ // 如果已有圆和新圆相交
break; // 跳出循环
}
}
if (i == circleNum)
{ // 如果上面for语句都不跳出,说明i等于circleNum-1
isNewCircleOK = 1; // 这个新生成的圆和已有圆都不相交
}
}
xArray[circleNum] = x;
yArray[circleNum] = y;
rArray[circleNum] = r;
circleNum++; // 圆的数量+1
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
Sleep(10);
}
根本思路如下——从任意圆开始,
如果不相交——>因为与所有存在圆比较过,当前比较的计数i与已经录入circleNum数量相等——>将isNewCircleOK置为1——>跳出isNewCircleOK==0的whlie循环——>计入数组,while (circleNum < 1000)继续把ok置零,比较下一个圆
如果相交——>跳出比较的for循环——>此时isNewCircleOK == 0——>继续while(isNewCircleOK==0)的循环,找下一个随机数据
完整代码如下:
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
int main()
{
//创建窗口绘制背景
int width = 600, height = 600;
initgraph(width, height);
setbkcolor(WHITE);
cleardevice();
srand(time(0));//随机种子函数
int xArray[1000]; // 数组存储所有圆心的x坐标
int yArray[1000]; // 数组存储所有圆心的y坐标
int rArray[1000]; // 数组存储所有圆的半径
int rmin = 8;
int rmax = 50;
int circleNum = 0;//定义随机圆的最小最大半径以及个数
int x, y, r;
int isNewCircleOK;
int i;
while (circleNum < 1000)
{ // 当圆的个数小于1000时,循环运行
isNewCircleOK = 0; // 假设开始不OK
while (isNewCircleOK == 0)
{ // 当新生成的圆不ok时,重复生成新圆进行比较
x = rand() % width; // 新圆的圆心x坐标
y = rand() % height; // 新圆的圆心y坐标
r = rand() % (rmax - rmin + 1) + rmin; // 新圆的半径
for ( i = 0; i < circleNum; i++)
{ // 对已有圆遍历
float dist2 = (xArray[i] - x) * (xArray[i] - x) + (yArray[i] - y) * (yArray[i] - y);
float r2 = (rArray[i] + r) * (rArray[i] + r);
if (dist2 < r2)
{ // 如果已有圆和新圆相交
break; // 跳出循环
}
}
if (i == circleNum)
{ // 如果上面for语句都不跳出,说明i等于circleNum-1
isNewCircleOK = 1; // 这个新生成的圆和已有圆都不相交
}
}
xArray[circleNum] = x;
yArray[circleNum] = y;
rArray[circleNum] = r;
circleNum++; // 圆的数量+1
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
Sleep(10);
}
_getch();
closegraph();
return 0;
}
输出结果如下:
二、多个不同颜色的同心圆
2.1函数的定义与应用
在重复绘制多个圆时,我们要重复判断两圆距离,重复放入同心圆,给同心圆不同颜色.....对于如此复杂的工作,我们可以采用函数来独立完成不同的功能设计。
C程序是由函数组成的,每个函数包含着如下结构
int main()
{
return 0;
}
main函数又称为主函数,一根C程序有且只有一个main函数。所有程序均从main函数开始执行。
同时,我们也可以根据我们的需要定义自己的函数。
比如如下代码:
这段代码首先定义了函数void printStars(),其中——
printStars为函数名,后跟圆括号;
void表示该函数没有返回值;
之后的花括号内为函数主题;
直接在main函数里调用函数,即可获得对应结果——一次打印五个 “ * ”
对于不同的函数定义可以实现不同的对应效果,例如:
void printStars(int num)——表示函数接收整型变量为num参数
int printnum(int x,int y)——表示函数接收整型变量为x,y作为参数,返回型为int
所以,我们在解决复杂问题时可以把问题分块,用独立函数来实现每一个独立的内容,易于维护和功能扩充。
2.2新圆半径最大化
上述的圆在生成时,只是随机从最小到最大值当中选择半径,无法确保所有圆都尽可能的大,所有我们采用初始化圆的半径为最小
int r = rmin
不断循环增加圆的半径,直到找到与现有圆相切的最大圆,否则就直接跑到rmax,输出设定中最大的圆
isNewCircleOK = 0; // 继续设为不OK,下面要让这个新圆的半径最大
while (isNewCircleOK == 0 && r < rmax) // 当不ok,并且新圆的半径小于最大半径时
{
r++; // 让半径+1
for (j = 0;j < circleNum;j++) // 对所有旧圆遍历
{
if (isTwoCirclesIntersect(xArray[j], yArray[j], rArray[j], x, y, r))
{
isNewCircleOK = 1; // 一旦和一个旧圆相交,这时新圆Ok
break; // 因为新圆半径已经达到最大的情况,这时跳出循环
}
}
}
2.3函数封装绘制效果
综上,我们可以逐个设计绘制功能:
(1)首先,我们对于一个坐标为(x,y),半径r的圆,我们有多种绘制方案,可以绘制不同颜色组成的同心圆,也可以绘制多个随机的纯色圆
画出随机颜色的圆方案:
void DrawCircles2(float x, float y, float r)
{
float h = rand() % 360;
COLORREF color = (HSVtoRGB(h, 0.6, 0.8));
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
}
绘制同心圆放方案:
void DrawCircles3(float x, float y, float r)
{
while (r > 0)
{
float h = rand() % 360;
COLORREF color = (HSVtoRGB(h, 0.6, 0.8));
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
r = r - 5;
}
}
(2)计算两个元之间的距离,并判断是否相交
计算两点距离:
// 求解两个点之间的距离
float Dist2Points(float x1,float y1,float x2,float y2)
{
float result;
result = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
return result;
}
判断是否相交:
// 判断两个圆是否相交
int isTwoCirclesIntersect(float x1,float y1,float r1,float x2,float y2,float r2)
{
if (Dist2Points(x1,y1,x2,y2)<r1+r2)
return 1;
return 0;
}
(3)最后随机从所给模式编号【最少几号,最大几号】中抽选一个模式的数,便于之后选择绘制模式
// 生成[min,max]之间的随机整数
int randBetweenMinMax(int min,int max)
{
int r = rand() % (max-min+1) + min;
return r;
}
最后整合之后,完整代码如下:
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
#include<math.h>
// 求解两个点之间的距离
float Dist2Points(float x1, float y1, float x2, float y2)
{
float result;
result = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
return result;
}
// 判断两个圆是否相交
int isTwoCirclesIntersect(float x1, float y1, float r1, float x2, float y2, float r2)
{
if (Dist2Points(x1, y1, x2, y2) < r1 + r2)
return 1;
return 0;
}
// 生成[min,max]之间的随机整数
int randBetweenMinMax(int min, int max)
{
int r = rand() % (max - min + 1) + min;
return r;
}
void DrawCircles1(float x, float y, float r)
{
setlinecolor(RGB(0, 0, 0)); // 设置线条颜色为黑色
setfillcolor(RGB(255, 255, 0)); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
}
void DrawCircles2(float x, float y, float r)
{
float h = rand() % 360;
COLORREF color = (HSVtoRGB(h, 0.6, 0.8));
setfillcolor(color); // 设置填充颜色为随机的color
fillcircle(x, y, r); // 绘制新圆
}
void DrawCircles3(float x, float y, float r)
{
while (r > 0)
{
float h = rand() % 360;
COLORREF color = (HSVtoRGB(h, 0.6, 0.8));
setlinecolor(RGB(255,255,255)); // 设置线条颜色为黑色
setfillcolor(color); // 设置填充颜色为黄色
fillcircle(x, y, r); // 绘制新圆
r = r - 5;
}
}
int main() // 主函数
{
int width = 600; // 窗口宽度
int height = 600; // 窗口高度
initgraph(width, height); // 新开一个窗口
setbkcolor(RGB(255, 255, 255)); // 背景颜色为白色
cleardevice(); // 以背景颜色清空背景
srand(time(0)); // 随机种子函数
int xArray[1000]; // 数组存储所有圆心的x坐标
int yArray[1000]; // 数组存储所有圆心的y坐标
int rArray[1000]; // 数组存储所有圆的半径
int rmin = 8; // 圆的最小半径
int rmax = 50; // 圆的最大半径
int circleNum = 0; // 生成的圆的个数
float x, y, r; // 新增圆的圆心坐标、半径
int isNewCircleOK; // 用于判断新生成的圆是否可以了
int i, j;
int drawMode = randBetweenMinMax(1, 3); // 随机生成一种绘制模式
while (circleNum < 1000) // 当圆的个数小于100时,循环运行
{
isNewCircleOK = 0; // 假设开始不OK
while (isNewCircleOK == 0) // 当新生成的圆不Ok时,重复生成新圆进行比较
{
x = rand() % width; // 新圆的圆心x坐标
y = rand() % height; // 新圆的圆心y坐标
r = rmin; // 新圆的半径开始设为最小半径
for (i = 0;i < circleNum;i++) // 对已有圆遍历
if (isTwoCirclesIntersect(xArray[i], yArray[i], rArray[i], x, y, r))
break; // 如果已有圆和新圆相交,跳出循环,此时i<circleNum
if (i == circleNum) // 如果上面for语句都不跳出,说明i等于circleNum
isNewCircleOK = 1; // 这个新生成的圆和已有圆都不相交
}
isNewCircleOK = 0; // 继续设为不OK,下面要让这个新圆的半径最大
while (isNewCircleOK == 0 && r < rmax) // 当不ok,并且新圆的半径小于最大半径时
{
r++; // 让半径+1
for (j = 0;j < circleNum;j++) // 对所有旧圆遍历
{
if (isTwoCirclesIntersect(xArray[j], yArray[j], rArray[j], x, y, r))
{
isNewCircleOK = 1; // 一旦和一个旧圆相交,这时新圆Ok
break; // 因为新圆半径已经达到最大的情况,这时跳出循环
}
}
}
xArray[circleNum] = x; // 把新圆的圆心坐标添加到数组中
yArray[circleNum] = y; //
rArray[circleNum] = r; // 把新圆的半径添加到数组中
circleNum++; // 圆的个数+1
// 根据不同绘图模式进行绘制
if (drawMode == 1)
DrawCircles1(x, y, r);
if (drawMode == 2)
DrawCircles2(x, y, r);
if (drawMode == 3)
DrawCircles3(x, y, r);
Sleep(10); // 暂停若干毫秒
}
_getch(); // 等待按键输入
closegraph(); // 关闭窗口
return 0;
}
模式三绘制效果如图:
三、按键交互
要求最后在按下空格键的时候,屏幕清空,在绘制时可以采用数字1.2.3.4来混合切换绘制模式。
完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
// 求解两个点之间的距离
float Dist2Points(float x1, float y1, float x2, float y2)
{
float result;
result = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
return result;
}
// 判断两个圆是否相交
int isTwoCirclesIntersect(float x1, float y1, float r1, float x2, float y2, float r2)
{
if (Dist2Points(x1, y1, x2, y2) < r1 + r2)
return 1;
return 0;
}
int randBetweenMinMax(int min, int max)
{
int r = rand() % (max - min + 1) + min;
return r;
}
// 填充黄色圆绘制
void DrawCircles1(float x, float y, float r)
{
setlinecolor(RGB(0, 0, 0));
setfillcolor(RGB(255, 255, 0));
fillcircle(x, y, r);
}
// 填充随机颜色圆绘制
void DrawCircles2(float x, float y, float r)
{
float h = rand() % 360;
COLORREF color = HSVtoRGB(h, 0.6, 0.8);
setlinecolor(RGB(255, 255, 255));
setfillcolor(color);
fillcircle(x, y, r);
}
// 填充随机颜色同心圆绘制
void DrawCircles3(float x, float y, float r)
{
while (r > 0)
{
float h = rand() % 360;
COLORREF color = HSVtoRGB(h, 0.6, 0.8);
setlinecolor(RGB(255, 255, 255));
setfillcolor(color);
fillcircle(x, y, r);
r = r - 5;
}
}
// 随机颜色同心圆线条绘制
void DrawCircles4(float x, float y, float r)
{
while (r > 0)
{
float h = rand() % 360;
COLORREF color = HSVtoRGB(h, 0.9, 0.8);
setlinecolor(color);
circle(x, y, r);
r = r - 5;
}
}
int main() // 主函数
{
int width = 600; // 窗口宽度
int height = 600; // 窗口高度
initgraph(width, height); // 新开一个窗口
setbkcolor(RGB(255, 255, 255)); // 背景颜色为白色
cleardevice(); // 以背景颜色清空背景
srand(time(0)); // 随机种子函数
int xArray[1000]; // 数组存储所有圆心的x坐标
int yArray[1000]; // 数组存储所有圆心的y坐标
int rArray[1000]; // 数组存储所有圆的半径
int rmin = 8; // 圆的最小半径
int rmax = 50; // 圆的最大半径
int circleNum = 0; // 生成的圆的个数
float x, y, r; // 新增圆的圆心坐标、半径
int isNewCircleOK; // 用于判断新生成的圆是否可以了
int i, j;
int drawMode = randBetweenMinMax(1, 4); // 用于设定4种不同的绘制模式,开始设为3
while (circleNum < 1000) // 当圆的个数小于100时,循环运行
{
isNewCircleOK = 0; // 假设开始不OK
while (isNewCircleOK == 0) // 当新生成的圆不Ok时,重复生成新圆进行比较
{
if (_kbhit())
{
char input = _getch();
if (input == ' ') {
circleNum = 0;
cleardevice();
while (1)
{
if (_kbhit())
{
input = _getch();
if (input >= '1' && input <= '4')
{
drawMode = input - '0';
printf("Changed to draw mode %d\n", drawMode);
break; // Exit the inner while loop
}
else {
TCHAR s[20];//得分的字符处理
_stprintf_s(s, _T("错误输入"));
settextstyle(40, 0, _T("黑体"));
settextcolor(RED);
outtextxy(260, 300, s);
}
}
}
}
else if (input == '1') {
drawMode = 1;
}
else if (input == '2') {
drawMode = 2;
}
else if (input == '3') {
drawMode = 3;
}
else if (input == '4') {
drawMode = 4;
}
}
x = rand() % width; // 新圆的圆心x坐标
y = rand() % height; // 新圆的圆心y坐标
r = rmin; // 新圆的半径开始设为最小半径
for (i = 0;i < circleNum;i++) // 对已有圆遍历
if (isTwoCirclesIntersect(xArray[i], yArray[i], rArray[i], x, y, r))
break; // 如果已有圆和新圆相交,跳出循环,此时i<circleNum
if (i == circleNum) // 如果上面for语句都不跳出,说明i等于circleNum
isNewCircleOK = 1; // 这个新生成的圆和已有圆都不相交
}
isNewCircleOK = 0; // 继续设为不OK,下面要让这个新圆的半径最大
while (isNewCircleOK == 0 && r < rmax) // 当不ok,并且新圆的半径小于最大半径时
{
r++; // 让半径+1
for (j = 0;j < circleNum;j++) // 对所有旧圆遍历
{
if (isTwoCirclesIntersect(xArray[j], yArray[j], rArray[j], x, y, r))
{
isNewCircleOK = 1; // 一旦和一个旧圆相交,这时新圆Ok
break; // 因为新圆半径已经达到最大的情况,这时跳出循环
}
}
}
xArray[circleNum] = x; // 把新圆的圆心坐标添加到数组中
yArray[circleNum] = y; //
rArray[circleNum] = r; // 把新圆的半径添加到数组中
circleNum++; // 圆的个数+1
// 根据不同绘图模式进行绘制
if (drawMode == 1)
DrawCircles1(x, y, r);
if (drawMode == 2)
DrawCircles2(x, y, r);
if (drawMode == 3)
DrawCircles3(x, y, r);
if (drawMode == 4)
DrawCircles4(x, y, r);
Sleep(10); // 暂停若干毫秒
}
_getch(); // 等待按键输入
closegraph(); // 关闭窗口
return 0;
}
在按键处理中,采用在输入空格后,进入一个等待输入新按键的循环(如果没有这个循环,采用_getch()会直接停止后按照按键进入下一轮输出,没有空白界面),在这个等待新按键循环的过程中,如果有符合条件的数字输入会直接跳出这个循环,进入接收绘制模式,然后正式开始绘制。
完整结果输出如下: