最近和别人聊天,聊到了控制台绘图,曾经学了一些windows的API编程,却没想到直接在控制台窗口上进行绘图
今天有时间,便把代码写了出来
#include <Windows.h>
#include <stdio.h>
/************************************************************************/
/* 函数 DrawCircle
/* 参数 HDC hdc 绘图的句柄
/* int x
/* int y 圆心的x,y坐标
/* int r 圆的半径
/* COLORREF color 圆的填充颜色
/* 功能 在(x, y)处画一个半径为r的圆,用color的颜色填充
/************************************************************************/
void DrawCircle(HDC hdc, int x, int y, int r, COLORREF color)
{
HBRUSH brush = (HBRUSH)CreateSolidBrush (color); //新建一个画刷
SelectObject(hdc, (HGDIOBJ)brush); //选择画刷
Ellipse(hdc, x-r, y-r, x+r, y+r); //画一个圆
DeleteObject(brush); //删除画刷
}
int main()
{
HWND console = GetConsoleWindow(); //获取控制台窗口句柄
HDC console_hdc = GetDC(console); //获取绘图dc
RECT rect; //保存绘图区域大小的结构体
int x,
y, //小球的xy坐标
dx = 3, //x的增量
dy = 3; //y的增量
int r = 0, g = 0, b = 0; //小球颜色
int speed; //小球速度
COORD pos={0,4}; //光标位置
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetClientRect(console, &rect); //首先获取绘图区域的大小
x = 10 + rand() % (rect.right);
y = 10 + rand() % (rect.bottom);
printf("当前窗口宽度 %d\n当前窗口高度 %d\n请输入小球颜色(r g b):", rect.right, rect.bottom);
scanf("%d %d %d", &r, &g, &b);
printf("请输入小球运动速度(1~10)");
scanf("%d", &speed);
//rgb 的范围分别是0~255
//255 255 255为白色
if (speed <1 || speed > 10 || r >255 || r < 0 || g >255 || g < 0 || b >255 || g <0)
{
printf("无效的输入");
return 0;
}
while (1)
{
SetConsoleCursorPosition(hOut,pos); //设置光标位置
printf("当前小球位置 x= %d \ty=%d ", x, y);
DrawCircle(console_hdc, x, y, 10, RGB(r, g, b)); //画一个白色的圆
Sleep(200/speed);
DrawCircle(console_hdc, x, y, 10, RGB(0, 0, 0)); //画一个黑色的圆(擦除)
if (x > rect.right - 10 || x < 10)
dx = -dx;
if (y > rect.bottom - 10 || y < 10)
dy = -dy;
x += dx;
y += dy;
}
ReleaseDC(console, console_hdc);
return 0;
}
要实现绘图就需要先获取控制台窗口的句柄
句柄说的高大上,其实它就是一个数字而已,它标记了一种资源,根据这个数字,我们就可以找到这个窗口。不然在绘图函数绘图的时候,就可能写到别的窗口上了
获取了控制台句柄之后,我们需要获取HDC,HDC是设备上下文,名字变得更加玄乎了,其实它就是用来绘图的。
GetStdHandle这个API又是获取句柄的,不过这一次不是窗口了,而是标准输出,因为我们程序中需要直接定位光标的位置,因此需要使用到标准输出的句柄
GetClientRect能获取绘图的范围,知道了绘图的范围后,我们能让画出来的圆“碰到”边框后自动的“弹回”
因为printf会在每次画圆之后输出圆的坐标,因此我们需要在每次输出前将光标放到指定的位置
SetConsoleCursorPosition便发挥了这个作用,它将光标定位到第三行第一个字符的位置
DrawCircle是我们自己封装的一个函数,它的具体功能注释中已经明确的写了出来
DrawCircle中我们先新建了一个画刷,因为我们需要用指定的颜色去画圆
SelectObject选择画刷,这样下面的作图就会变成我们所指定的颜色
Ellipse是一个画椭圆的函数,它的后四个参数分别是椭圆的外界矩形的坐标,如果我们使矩形的边长相等,那么它画出来的自然就是圆形了
最后别忘了把画刷用DeleteObject删除,因为画刷也是需要占用资源的
下面就是实现小球的运动过程了,其实实现起来很简单。由于“视觉暂留”效应,我们只需要先画一个圆,再删除,接着立即在他的旁边画一个圆,人眼便会形成一种错觉,小球在运动!
那么怎么擦除呢,很简单啦,在我们画出的圆上再画一个圆,它的颜色和背景颜色相同,这样不就把刚才的圆给覆盖了吗?
小球的运动速度由每次画圆到擦除之前的延时时间决定,根据你的输入,每次延时的值将在200ms – 20ms之间变化
这样,我们便实现了控制台版本的运动的小球程序了


