• EASY-X


    ----------------------- Page 1-----------------------

    一 创建新项目

    VC 写程序要有项目的概念,一个项目可以有多个 .cpp 文件,多个项目构

    成一个工作区。先记住这两个英文单词吧:

    • Workspace: 工作区

    • Project: 项目

    现在开始创建一个新项目。

    • VC6 创建新项目请参考这个视频:

    http://www.easyx.cn/news/View.aspx?id=65

    • VC2008 创建新项目请参考这个视频:

    http://www.easyx.cn/news/View.aspx?id=85

    • VC2010 与 VC2008 相似。

    看明白后,自己动手建立项目试试,并输入以下代码:

    #include <stdio.h>
    void main()
    {
    printf("Hello World!");
    }

    尤其是之前用 tc 的同学,请务必创建新项目试一试。成功执行后,会看到

    屏幕上有“Hello World!”几个字符。然后,再重新输入以下代码试试(无需理解

    代码含义):

    #include <graphics.h>
    #include <conio.h>
    void main()
    {
    initgraph(640, 480);
    line(200, 240, 440, 240);
    line(320, 120, 320, 360);
    getch();
    closegraph();
    }

    ----------------------- Page 2-----------------------

    执行后应该可以看到屏幕正中央有一个十字。

    看到该十字后,本节课结束。

    ----------------------- Page 3-----------------------

    二 简单绘图,学习单步执行

    学会简单绘图,并学会简单调试。

    先看看上一课的代码,我加上了注释

    #include <graphics.h> // 绘图库头文件,绘图语句需要

    #include <conio.h> // 控制台输入输出头文件,

    getch()语句需要

    void main()
    {

    initgraph(640, 480); // 初始化640x480 的绘图屏幕

    line(200, 240, 440, 240); // 画线(200,240) - (440,240)

    line(320, 120, 320, 360); // 画线(320,120) - (320,360)

    getch(); // 按任意键

    closegraph(); // 关闭绘图屏幕

    }

    解释一下:

    1. 创建的绘图屏幕640x480,表示横向有640 个点,纵向有480 个点。注意:

    左上角是原点(0,0),也就是说,y 轴和数学的y 轴是相反的。

    2. getch 实现按任意键功能,按任意键后,程序继续执行。否则,程序会立

    刻执行closegraph 以至于看不到绘制的内容。

    [作业]

    用线条画出更多的图形,要求不少于10 条直线。

    [学习单步执行]

    完成作业后(务必完成),开始试着单步执行刚才的程序,由于绘图和多线

    程等因素的限制,请务必按照以下步骤尝试(熟练了以后就不用了):

    1. 将VC 取消最大化,并缩小窗口,能看到代码就行。

    2. 按一下F10 (单步执行),会看到屏幕上出现一个黄色的小箭头,指示将

    要执行的代码。

    ----------------------- Page 4-----------------------

    3. 当箭头指向initgraph 语句时,按F10 ,能看到窗口发生了变化。

    4. 将新的绘图窗口和VC 并排放,相互不要有覆盖。这步很重要,否则绘图

    内容将会被VC 窗口覆盖。

    5. F10 执行getch 后,记得激活绘图窗口,并按任意键,否则程序不会继续

    执行。

    6. closegraph 后,直接按F5 执行全部剩余程序,结束。

    单步执行很重要,可以让你知道程序执行到哪里是什么效果,哪条语句执行

    出了问题等等。

    更详细的调试资料,请看这里:http://pan.baidu.com/s/1eR6HT

    该文档写的调试的东西比较多,看一下大概有个了解,以后都会用到(不过以后

    我就不再讲了)

    [作业2]

    仍然是写一个用直线绘制的图形,并熟悉调试过程。

    注:

    1. 许多学校都忽略了调试部分,如果你不曾用过调试,请务必熟练该过程。

    2. win-tc 不带有任何调试功能,即便你不用vc ,也请不要使用win-tc 。调试

    是相当相当重要的。

    ----------------------- Page 5-----------------------

    三 学会更多的绘图语句

    [常用的绘图语句]

    • line(x1, y1, x2, y2); // 画直线 (x1,y1)-(x2,y2),都是整形

    • circle(x, y, r); // 画圆,圆心为(x,y),半径为r

    • putpixel(x, y, c); // 画点(x,y),颜色c

    还有很多,如画椭圆、圆弧、矩形、多边形,等等,请参考绘图帮助文件(第

    一课的绘图库的下载里面有)

    [设置颜色]

    setlinecolor(c);// 设置画线颜色,如setlinecolor(RED)设置画线颜色为红色

    常用的颜色常量可以用:

    • BLACK 黑 DARKGRAY 深灰

    • BLUE 蓝 LIGHTBLUE 亮蓝

    • GREEN 绿 LIGHTGREEN 亮绿

    • CYAN 青 LIGHTCYAN 亮青

    • RED 红 LIGHTRED 亮红

    • MAGENTA 紫 LIGHTMAGENTA 亮紫

    • BROWN 棕 YELLOW 黄

    • LIGHTGRAY 浅灰 WHITE 白

    [配出更多的颜色]

    颜色除了前面写的16 种以外,还可以自由配色。格式:RGB(r, g, b)

    r / g / b 分别表示红色、绿色、蓝色,范围都是0~255 。例如,RGB(255,0,0)

    表示纯红色。

    红色和绿色配成黄色,因此 RGB(255, 255, 0) 表示黄色。

    嫌调色麻烦可以用画笔里面的调色试试,调好了以后直接将数值抄过来就行。

    例如,画两条红色浓度为200 的直线,可以这么写:

    ----------------------- Page 6-----------------------

    setlinecolor(RGB(200, 0, 0));
    line(100, 100, 200, 100);
    line(100, 120, 200, 120);

    [用数字表示颜色]

    除了用RGB(r,g,b)方式外,还可以用16 进制表示颜色,格式:0xbbggrr

    例如,setlinecolor(0x0000ff) 和 setlinecolor(RGB(255, 0, 0)) 是等效的。

    [延时语句]

    这个很简单,Sleep(n) 就可以表示 n 毫秒的延时。例如延时3 秒,可以用

    Sleep(3000);

    [作业]

    1. 简单看一下绘图库的帮助文件,了解更多的绘图语句。

    2. 绘制更丰富的图形内容,不低于20 行。

    3. 将延时语句适当的插入上个作业的代码中,看看执行效果。

    注:绘图语句不需要记住,用的时候翻翻手册就行。

    ----------------------- Page 7-----------------------

    四 结合流程控制语句来绘图

    熟练使用循环、判断语句

    [熟悉for 语句]

    这步需要自学,看看自己手边的书,是怎样讲for 语句的,简单看看就行。

    [范例]

    例如,画10 条直线的代码:

    #include <graphics.h>
    #include <conio.h>
    void main()
    {
    initgraph(640, 480);
    for(int y=100; y<200; y+=10)
    line(100, y, 300, y);
    getch();
    closegraph();
    }

    换一下循环的范围和间隔,看看效果。

    还可以用来画渐变色,例如:

    #include <graphics.h>
    #include <conio.h>
    void main()
    {
    initgraph(640, 480);
    for(int y=0; y<256; y++)
    {
    setcolor(RGB(0,0,y));
    line(100, y, 300, y);
    }
    getch();
    closegraph();
    }

    ----------------------- Page 8-----------------------

    [熟悉if 语句]

    这步需要自学,看看自己手边的书,是怎样讲if 语句的,简单看看就行。

    配合if 语句,实现红色、蓝色交替画线:

    #include <graphics.h>
    #include <conio.h>
    void main()
    {
    initgraph(640, 480);
    for(int y=100; y<200; y+=10)
    {

    if ( y/10 % 2 == 1) // 判断奇数行偶数行

    setcolor(RGB(255,0,0));
    else
    setcolor(RGB(0,0,255));

    line(100, y, 300, y);
    }
    getch();
    closegraph();
    }

    [作业]

    1. 画围棋棋盘。

    2. 画中国象棋的棋盘

    3. 画国际象棋的棋盘,看手册找到颜色填充语句,实现国际象棋棋盘的区块

    填充。

    4. 自学while 语句。

    学到这里,已经可以画出很多东西了。把自己想象中的图案绘制一下吧。

    ----------------------- Page 9-----------------------

    五 数学知识在绘图中的运用

    理解数学的重要性

    1. 最简单的,来个全屏的渐变色吧,是上一课的扩展。就是需要将0~255

    的颜色和0~479 的y 轴对应起来

    c 表示颜色,范围0~255

    y 表示y 轴,范围0~479

    于是:

    c / 255 = y / 479

    c = y / 479 * 255 = y * 255 / 479 (先算乘法再算除法可以提高精度)

    看代码:

    #include <graphics.h>
    #include <conio.h>
    void main()
    {
    initgraph(640, 480);
    int c;
    for(int y=0; y<480; y++)
    {
    c = y * 255 / 479;
    setcolor(RGB(0,0,c));
    line(0, y, 639, y);
    }
    getch();
    closegraph();
    }

    试试效果吧。

    2. 画一个圆形的渐变色

    首先,我们要用到圆形的基本公式:

    x*x + y*y = r*r

    让弧度从0~2*3.14,然后需要根据弧度和半径算出(x,y),

    用pi 表示圆周率

    用r 表示半径

    ----------------------- Page 10-----------------------

    用a 表示弧度(小数)

    用c 表示颜色

    于是:

    x=r*cos(a)
    y=r*sin(a)
    c=a*255/(2*pi)

    看看代码:

    #include <graphics.h>
    #include <conio.h>
    #include <math.h>

    #define PI 3.14

    void main()
    {
    initgraph(640, 480);
    int c;
    double a;
    int x, y, r = 200;
    for(a = 0; a < PI * 2; a += 0.0001)
    {
    x=(int)(r * cos(a) + 320 + 0.5);
    y=(int)(r * sin(a) + 240 + 0.5);
    c=(int)(a * 255 / (2 * PI) + 0.5);
    setcolor(RGB(c, 0, 0));
    line(320, 240, x, y);
    }
    getch();
    closegraph();
    }

    [作业]

    这次没什么作业,只是理解一下数学的重要性而已。如果读者还在念书,请

    重视数学。

    ----------------------- Page 11-----------------------

    六 实现简单动画

    所谓动画,其实是连续显示一系列图形而已。

    结合到程序上,我们需要以下几个步骤:

    1. 绘制图像

    2. 延时

    3. 擦掉图像

    循环以上即可实现动画。

    举一个例子,我们实现一条直线从上往下移动:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);

    for(int y=0; y<480; y++)
    {

    // 绘制绿色直线

    setcolor(GREEN);
    line(0, y, 639, y);


    // 延时

    Sleep(10);


    // 绘制黑色直线(即擦掉之前画的绿线)

    setcolor(BLACK);
    line(0, y, 639, y);
    }

    closegraph();
    }

    再看一个例子,实现一个圆从左往右跳动:

    #include <graphics.h>

    ----------------------- Page 12-----------------------

    #include <conio.h>

    void main()
    {
    initgraph(640, 480);

    for(int x= 100; x<540; x+=20)
    {

    // 绘制黄线、绿色填充的圆

    setcolor(YELLOW);
    setfillcolor(GREEN);
    fillcircle(x, 100, 20);


    // 延时

    Sleep(500);


    // 绘制黑线、黑色填充的圆

    setcolor(BLACK);
    setfillcolor(BLACK);
    fillcircle(x, 100, 20);
    }

    closegraph();
    }

    也就是说,移动的间距小、延时短,动画就会越细腻。但当画面较复杂时,

    会带来画面的闪烁(怎样消除闪烁是以后的话题)。

    [作业]

    绘制一个沿 45 度移动的球,碰到窗口边界后反弹。

    ----------------------- Page 13-----------------------

    七 捕获按键,实现动画的简单控制

    最常用的一个捕获按键的函数:getch()

    前几课,都把这个函数当做“按任意键继续”来用,现在我们用变量保存这个按键:

    char c = getch();

    然后再做判断即可。

    不过程序执行到 getch() 是会阻塞的,直到用户有按键才能继续执行。可游

    戏中总不能因为等待按键而停止游戏执行吧?所以,要有一个函数,判断是否有

    用户按键:kbhit()

    这个函数返回当前是否有用户按键,如果有,再用 getch() 获取即可,这样是不

    会阻塞的。

    即:

    char c;
    if (kbhit())
    c = getch();

    举一个简单的例子,如果有按键,就输出相关按键。否则,输出“.” 。每隔 100

    毫秒输出一次。按 ESC 退出。

    注:ESC 的 ASCII 码是 27 。

    完整代码如下:

    #include <graphics.h>
    #include <stdio.h>
    #include <conio.h>

    void main()
    {
    char c = 0;
    while(c != 27)
    {
    if (kbhit())
    c = getch();
    else
    c = '.';

    ----------------------- Page 14-----------------------

    printf("%c", c);
    Sleep(100);
    }
    }

    结合上一课的简单动画,就可以做出来靠按键移动的图形了吧,看以下代码,

    实现 a s 控制圆的左右移动:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);

    int x = 320;


    // 画初始图形

    setcolor(YELLOW);
    setfillcolor(GREEN);
    fillcircle(x, 240, 20);

    char c;
    while(c != 27)
    {

    // 获取按键

    c = getch();


    // 先擦掉上次显示的旧图形

    setcolor(BLACK);
    setfillcolor(BLACK);
    fillcircle(x, 240, 20);


    // 根据输入,计算新的坐标

    switch(c)
    {
    case 'a': x-=2; break;
    case 'd': x+=2; break;
    case 27: break;
    }

    ----------------------- Page 15-----------------------



    // 绘制新的图形

    setcolor(YELLOW);
    setfillcolor(GREEN);
    fillcircle(x, 240, 20);


    // 延时

    Sleep(10);
    }

    closegraph();
    }

    [作业]

    请继续完成这个程序,实现以下功能:

    1. 上下的控制;

    2. 边界检测;

    3. 结合 kbhit 实现惯性移动(即按一下方向键,圆就会一直向这个方向移动)

    注:上下左右等按键的控制,会返回 2 个字符。由于该系列教程面向初学

    者,因此有兴趣的请查看 MSDN 。

    ----------------------- Page 16-----------------------

    八 用函数简化相同图案的制作

    实际中有许多类似的图案,如果一一单独绘制,太麻烦。于是,我们需要一

    个公用的绘制过程,就是函数。

    例如,我们需要画5 个三角形,位于不同的位置。我们可以将绘制单个三角

    形的过程写成函数,函数内是一个独立的程序段,这个绘制过程很简单。

    然后,在需要绘制的时候,调用这个函数即可。可以通过参数来解决细微差异(图

    案的坐标、颜色等),例如:

    #include <graphics.h>
    #include <conio.h>


    // 在坐标 (x,y) 处,用颜色 c 绘制三角形

    void sanjiaoxing(int x, int y, int c)
    {

    // 设置画线颜色

    setlinecolor(c);


    // 画三角形的三条边

    line(x, y, x+50, y);
    line(x, y, x, y+50);
    line(x+50, y, x, y+50);
    }

    void main()
    {

    initgraph(640, 480); // 初始化图形窗口


    sanjiaoxing(100, 100, RED);
    sanjiaoxing(120, 160, BLUE);
    sanjiaoxing(140, 220, GREEN);
    sanjiaoxing(160, 120, BLUE);
    sanjiaoxing(160, 160, GREEN);
    sanjiaoxing(220, 140, GREEN);


    getch(); // 按任意键继续

    closegraph(); // 关闭图形窗口

    ----------------------- Page 17-----------------------

    }

    再结合循环等控制条件,就能绘制更复杂漂亮的图案了。试试运行下面程序,

    理解一下函数的用处:

    #include <graphics.h>
    #include <conio.h>

    void sanjiaoxing(int x, int y, int color)
    {

    // 设置画线颜色

    setlinecolor(color);


    // 画三角形的三条边

    line(x, y, x+10, y);
    line(x, y, x, y+10);
    line(x+10, y, x, y+10);
    }

    void main()
    {

    initgraph(640, 480); // 初始化图形窗口


    for(int x=0; x<640; x+=10)
    for(int y=0; y<480; y+= 10)
    sanjiaoxing(x, y, RGB(x*255/640, y*255/480, 0));


    getch(); // 按任意键继续

    closegraph(); // 关闭图形窗口

    }

    ----------------------- Page 18-----------------------

    运行效果:



    本节作业:

    1. 绘制 Windows 自带游戏“扫雷” 的初始界面。

    2. 这个作业有点独特,仔细看下面这个数学过程:

    1. 随机生成 3 个点 P[0] 、P[1] 、P[2] ;

    2. 随机生成 1 个点 P ;

    3. 绘制点 P ;

    4. 随机生成 [0, 2] 内的整数 n ;

    5. 令 P = P 与 P[n] 的中点;

    6. 重复执行步骤 (3)~(5) 三万次。

    问题是:以上步骤执行完以后,这三万个点在屏幕上会是个什么情况?有规

    律吗?很难想出来吧,那就写个程序把这个过程模拟一下,看看究竟是什么~~

    ----------------------- Page 19-----------------------

    ----------------------- Page 20-----------------------

    九 绘图中的位运算

    位运算和绘图有什么关系?先举个例子来个感性认识:使用XOR 运算可以

    实现擦除图形后不破坏背景,这在时钟程序中绘制表针是很有用的。稍后我们会

    给出这样的例子。

    一、位运算的运算法则

    位运算主要分4 种:NOT 、AND 、OR、XOR ,位运算的运算对象是二进制

    数(十进制要转换为二进制,计算机会自动转换)。

    运算法则如下:

    1. NOT

    表示“取反”,将二进制位的1 变0、0 变1。

    C 语言用符号 ~ 表示。

    如:

    二进制: ~1101 = 0010

    用十进制表示就是:~13 = 2

    2. AND

    表示“并且”,只有两数的对应二进制位都为1,结果的二进制位才为1;否则,

    结果的二进制位为0 。

    C 语言用符号 & 表示。

    如:

    二进制:1101 & 0110 = 0100

    用十进制表示就是:13 & 6 = 4

    3. OR

    表示“或者”,两数的对应二进制位只要有一个是1,结果的二进制位就是1;否

    则,结果的二进制位为0 。

    C 语言用符号 | 表示。

    如:

    二进制:0101 | 0110 = 0111

    用十进制表示就是:5 | 6 = 7

    ----------------------- Page 21-----------------------

    4. XOR

    表示“异或”,两数的对应二进制位不同,结果的二进制位为1;相同,结果的二

    进制位为0 。

    C 语言用符号 ^ 表示。

    如:

    二进制:0101 ^ 1110 = 1011

    以上只是简单介绍一下,详细的还是请大家看课本上的讲解。

    二、位运算的应用

    位运算的应用很多,例如 AND 和 OR 在获取和设置标志位时经常使用。

    更多的,以后大家会逐渐遇到,暂时先记下有这么回事。

    这里着重说一下 XOR 运算,它有一个重要的特性:(a ^ b) ^ b = a

    也就是说,a ^ b 之后可能是某些其它数字,但是只要再 ^b 一下,就又成

    了 a 。

    一些简单的加密就用的 XOR 的这个特性。

    至于绘图,假如 a 是背景图案,b 是将要绘制的图案,只要用 XOR 方式

    绘图,连续绘两次,那么背景是不变的。

    三、演示

    我们来一个简单的绘图 XOR 运算演示:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {

    initgraph(640, 480); // 初始化 640 x

    480 的绘图窗口

    setlinestyle(PS_SOLID, 10); // 设置线宽为 10,

    这样效果明显

    setlinecolor(GREEN); // 设置画线颜色

    为绿色

    ----------------------- Page 22-----------------------

    rectangle(100, 100, 200, 200); // 画一个矩形,当做背景图





    setwritemode(R2_XORPEN); // 设置 XOR 绘

    图模式

    setcolor(RED); // 设置画

    线颜色为红色



    line(50, 0, 200, 300); // 画线

    getch(); // 等待按

    任意键

    line(50, 0, 200, 300); // 画线(XOR 方

    式重复画线会恢复背景图案)

    getch(); // 等待按

    任意键



    closegraph(); // 关闭绘

    图窗口

    }

    运行一下,看到第一次画线后,矩形与直线相交的部分,颜色变成了青色,

    青色就是白色和红色 XOR 的值。当再次以红色画线时,青色部分消失了,还

    原为完整的白色矩形框。

    四、完整的范例

    来一个相对完整的范例吧,就是钟表程序,三个表针用的都是 XOR 方式

    绘制,请大家运行体会一下 XOR 的作用:

    #include <graphics.h>
    #include <conio.h>
    #include <math.h>

    #define PI 3.14159265359

    void Draw(int hour, int minute, int second)
    {

    ----------------------- Page 23-----------------------

    double a_hour, a_min, a_sec;

    // 时、分、秒针的弧度值

    int x_hour, y_hour, x_min, y_min, x_sec, y_sec; // 时、分、秒针的

    末端位置



    // 计算时、分、秒针的弧度值

    a_sec = second * 2 * PI / 60;
    a_min = minute * 2 * PI / 60 + a_sec / 60;
    a_hour= hour * 2 * PI / 12 + a_min / 12;


    // 计算时、分、秒针的末端位置

    x_sec = 320 + (int)( 120 * sin(a_sec));
    y_sec = 240 - (int)( 120 * cos(a_sec));
    x_min = 320 + (int)( 100 * sin(a_min));
    y_min = 240 - (int)( 100 * cos(a_min));
    x_hour= 320 + (int)(70 * sin(a_hour));
    y_hour= 240 - (int)(70 * cos(a_hour));


    // 画时针

    setlinestyle(PS_SOLID, 10, NULL);
    setlinecolor(WHITE);
    line(320, 240, x_hour, y_hour);


    // 画分针

    setlinestyle(PS_SOLID, 6, NULL);
    setlinecolor(LIGHTGRAY);
    line(320, 240, x_min, y_min);


    // 画秒针

    setlinestyle(PS_SOLID, 2, NULL);
    setlinecolor(RED);
    line(320, 240, x_sec, y_sec);
    }

    void main()
    {

    initgraph(640, 480); // 初始化 640 x 480 的绘图窗口



    // 绘制一个简单的表盘

    ----------------------- Page 24-----------------------

    circle(320, 240, 2);
    circle(320, 240, 60);
    circle(320, 240, 160);
    outtextxy(296, 310, _T("BestAns"));


    // 设置 XOR 绘图模式

    setwritemode(R2_XORPEN); // 设置 XOR 绘图模式



    // 绘制表针

    SYSTEMTIME ti; // 定义变量保存当前时间

    while(!kbhit()) // 按任意键退出钟表程序

    {

    GetLocalTime(&ti); // 获取当前时间

    Draw(ti.wHour, ti.wMinute, ti.wSecond); // 画表针

    Sleep(1000); // 延时 1

    Draw(ti.wHour, ti.wMinute, ti.wSecond); // 擦表针(擦表针

    和画表针的过程是一样的)

    }


    closegraph(); // 关闭绘图窗口

    }

    五、作业

    最后给出的绘制时钟的例子,很不完善,有不少问题。请完善该程序。例如

    样式上,表盘上没有刻度,没有数字,指针靠中心的一端应该长出来一点点,表

    盘太简单。还有就是尝试发现并改进功能实现上的问题。

    ----------------------- Page 25-----------------------

    十 用鼠标控制绘图/游戏程序

    捕获鼠标消息就像捕获按键消息一样简单。

    对于按键,通常我们会先检查是否有按键,然后定义一个变量保存按键,再然后

    根据该按键的值,执行相应的程序。

    对于鼠标,道理是一样的。

    先写个代码对比一下:

    获取按键: 获取鼠标:

    char c; MOUSEMSG m;
    if (kbhit()) if (MouseHit())
    c = getch(); m = GetMouseMsg();



    很简单吧。由于鼠标消息的内容太多,不像按键那么简单,因此需要用一个

    结构体来保存。通过该结构体,我们可以获取鼠标的如下信息:

    struct MOUSEMSG
    {

    UINT uMsg; // 当前鼠标消息

    bool mkCtrl; // Ctrl 键是否按下

    bool mkShift; // Shift 键是否按下

    bool mkLButton; // 鼠标左键是否按下

    bool mkMButton; // 鼠标中键是否按下

    bool mkRButton; // 鼠标右键是否按下

    int x; // 当前鼠标 x 坐标

    int y; // 当前鼠标 y 坐标

    int wheel; // 鼠标滚轮滚动值

    };

    其中,“当前鼠标消息”可能是以下值:

    WM_MOUSEMOVE 鼠标移动消息

    WM_MOUSEWHEEL 鼠标滚轮拨动消息

    WM_LBUTTONDOWN 左键按下消息

    WM_LBUTTONUP 左键弹起消息

    WM_LBUTTONDBLCLK 左键双击消息

    ----------------------- Page 26-----------------------

    WM_MBUTTONDOWN 中键按下消息

    WM_MBUTTONUP 中键弹起消息

    WM_MBUTTONDBLCLK 中键双击消息

    WM_RBUTTONDOWN 右键按下消息

    WM_RBUTTONUP 右键弹起消息

    WM_RBUTTONDBLCLK 右键双击消息

    例如,判断获取的消息是否是鼠标左键按下,可以用:

    if (m.uMsg == WM_LBUTTONDOWN) ...



    下面举一个综合的例子(我偷点懒,直接粘贴的绘图库帮助里面的鼠标范例),

    该程序会用红色的点标出鼠标移动的轨迹,按左键画一个小方块,按Ctrl+左键

    画一个大方块,按右键退出:

    #include <graphics.h> #include <conio.h> void main() { // 初始化图形窗口

    initgraph(640, 480); MOUSEMSG m; // 定义鼠标消息

    while(true) { // 获取一条鼠标消息 m =

    GetMouseMsg(); switch(m.uMsg) { case

    WM_MOUSEMOVE: // 鼠标移动的时候画红色的小点

    putpixel(m.x, m.y, RED); break; case

    WM_LBUTTONDOWN: // 如果点左键的同时按下了 Ctrl

    键 if (m.mkCtrl) // 画一个大方块

    rectangle(m.x-10, m.y-10, m.x+10, m.y+10); else

    // 画一个小方块 rectangle(m.x-5, m.y-5, m.x+5, m.y+5);

    break; case WM_RBUTTONUP: return;

    // 按鼠标右键退出程序 } } // 关闭图形窗口

    closegraph(); }

    [本节作业]

    1. 画一个填充的三角形,要用鼠标点选三角形的三个顶点。提示:可以用 fillpoly

    函数画多边形。

    2. 写一个“格子涂色” 的游戏,要求:屏幕上有16x8 的格子,屏幕底部有类似画

    笔中的选色区(随便放上一些常用的颜色),鼠标点击选择区的颜色后,就作为

    当前颜色,然后再点屏幕上的格子,就可以用刚才的颜色填涂相应格子。

    ----------------------- Page 27-----------------------

    十一 随机函数简介

    游戏中,许多情况都是随即发生的。还有一些图案程序,例如屏保,也是随

    即运动的。这就需要用随机函数。

    随机函数很简单,只有一个:

    rand()

    该函数返回 0~32767 之间的一个整数。(不需要记住 32767 这个数字,大概知

    道这个范围就行了)

    该函数在头文件 <stdlib.h> 中,使用前记得引用。



    [简单测试]

    来写个程序测试一下:

    #include <stdio.h>
    #include <stdlib.h>

    void main()
    {
    int r;
    for(int i=0; i<10; i++)
    {
    r = rand();
    printf("%d ", r);
    }
    }

    执行后,可以看到输出了 10 个随机数字。



    [指定范围的随机函数]

    实际中,我们经常要产生指定范围的随机函数,通常我们用求余数的办法。例如,

    产生 0~9 之间的随机数,只需要将任意产生的随机数除以 10 求余数即可。求

    余数的运算符号是 %,我们可以这样做:

    r = rand() % 10;

    ----------------------- Page 28-----------------------

    修改前面的测试程序执行后可以看到,产生的数字都是小于 10 的。

    如果是 1~6 之间的怎样求呢?

    r = rand() % 6 + 1;

    无论产生什么样范围的随机函数,都是通过各种运算将随机数的范围 [0,

    32767] 修改为自己需要的范围。



    [随机种子]

    做了多次试验,我们会发现一个问题:虽然产生的数字是随机的,但每次产生的

    数字序列都一样。为了解决这个问题,我们需要用“随机种子” 。

    随机函数的产生原理简单来说,就是:前一个随机函数的值,决定下一个随机函

    数的值。

    根据这个原理我们可以知道:只要第一个随机函数的值确定了,那么后面数字序

    列就是确定的。如果我们想的得到不同的数字序列,我们需要确定第一个随机函

    数的值,对于设置第一个随机函数的值,叫做设置“随机种子” 。易知,随机种子

    设置一次即可。

    设置随机种子的函数如下:

    srand(种子);

    通常,我们用当前时间来做随机种子:

    srand( (unsigned)time( NULL ) );

    因为使用 time 函数,所以记得引用 <time.h> 。



    [绘图中的应用]

    来一个简单的程序,在屏幕上任意位置画任意颜色的点(按任意键退出) :

    #include <graphics.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <time.h>

    ----------------------- Page 29-----------------------

    void main()
    {
    srand( (unsigned)time( NULL ) );

    initgraph(640, 480);

    int x, y, c;
    while(!kbhit())
    {
    x = rand() % 640;
    y = rand() % 480;
    c = RGB(rand() % 256, rand() % 256, rand() % 256);
    putpixel(x, y, c);
    }

    closegraph();
    }



    [作业]

    1. 回顾一下第 6 课“实现简单动画” 的作业:绘制一个沿 45 度移动的球,碰到

    窗口边界后反弹。

    将这个球改为任意方向运动,碰到边界后任意反弹。

    ----------------------- Page 30-----------------------

    十二 数组

    [一维数组]

    数组可以实现批量操作。比如,我们产生 10 个随机数,产生后先保存起来,然

    后输出最大的:

    int n[10];
    int i;
    for (i=0; i<10; i++)
    n[i] = rand() % 1000;

    // 按生成的顺序,逆序输出

    for (i=9; i>=0; i--)
    printf("%d ", n[i]);

    // 找出最大的

    int max = -1;
    for (i=0; i<10; i++)
    {
    if (n[i] > max)
    max = n[i];
    }

    printf("最大的数字是:%d ", max);



    看明白这个程序后,我们继续。

    下面,我们绘制一个从屏幕上边任意位置往下落的白色点:

    #include <graphics.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <time.h>

    void main()
    {
    srand( (unsigned)time(NULL) );

    initgraph(640, 480);

    ----------------------- Page 31-----------------------

    int x = rand() % 640; // 点的 x 坐标

    int y = 0; // 点的 y 坐标

    while(!kbhit())
    {

    // 擦掉前一个点

    putpixel(x, y, BLACK);

    // 计算新坐标

    y+=3;
    if (y >= 480) break;

    // 绘制新点

    putpixel(x, y, WHITE);

    Sleep(10);
    }

    closegraph();
    }



    现在利用数组,来产生 100 个随机下落的点。并且每个点落到底部后,就

    回到顶部重新往下落:

    #include <graphics.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <time.h>

    void main()
    {
    srand( (unsigned)time(NULL) );

    initgraph(640, 480);

    // 定义点的坐标数组

    int x[100]; // 点的 x 坐标

    int y[100]; // 点的 y 坐标

    int i;

    ----------------------- Page 32-----------------------

    // 初始化点的初始坐标

    for (i=0; i<100; i++)
    {
    x[i] = rand() % 640;
    y[i] = rand() % 480;
    }

    while(!kbhit())
    {
    for(i=0; i<100; i++)
    {

    // 擦掉前一个点

    putpixel(x[i], y[i], BLACK);

    // 计算新坐标

    y[i]+=3;
    if (y[i] >= 480) y[i] = 0;

    // 绘制新点

    putpixel(x[i], y[i], WHITE);
    }

    Sleep(10);
    }

    closegraph();
    }



    [二维数组]

    理解了一维数组,再看二维数组甚至多维数组,就简单多了,看下面程序理解一

    下二维数组:

    程序要求:屏幕上有 16x8 的方格,按随机顺序在将 1~128 的数字写到每

    个格子上。

    考虑:我们需要记录这些格子,哪些写过数字,哪些没写数字。

    我们用一个二维数组来记录:

    bool cell[16][8];

    写过数字后,我们将相应数组的值设置为 true ,看程序:

    ----------------------- Page 33-----------------------

    #include <graphics.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <stdio.h>
    #include <time.h>

    void main()
    {
    int x, y;
    char num[4];

    srand( (unsigned)time(NULL) );

    initgraph(640, 480);

    // 画格子

    for (x=0; x<=480; x+=30)
    for (y=0; y<=240; y+=30)
    {
    line(x, 0, x, 240);
    line(0, y, 480, y);
    }

    // 定义二维数组

    bool cell[16][8];

    // 初始化二维数组

    for (x=0; x<16; x++)
    for (y=0; y<8; y++)
    cell[x][y] = false;

    // 在每个格子上写数字

    for (int i=1; i<=128; i++)
    {

    // 找到一个没有写数字的随机格子

    do
    {
    x = rand() % 16;
    y = rand() % 8;
    }while(cell[x][y] == true);

    ----------------------- Page 34-----------------------

    // 标记该格子已用

    cell[x][y] = true;

    // 在格子上写数字

    sprintf(num, "%d", i);
    outtextxy(x * 30, y * 30, num);
    }

    getch();
    closegraph();
    }

    以上几个范例,无论从实用上还是美观上都很差,我只是希望大家能举一反

    三,写出更多漂亮的程序。



    [作业]

    1. 回顾一下上一节课的作业,绘制一个任意反弹的球。这次,将程序修改成屏

    幕上有 10 个任意反弹的球。

    2. 如果反弹的不是球,而是点呢?再将某些点之间用线连起来,就可以做一个

    屏保“变幻线” 的程序了。试试做一个。

    3. 写“涂格子(也叫点灯)” 的游戏。详细规则可以试玩网上的各种版本。

    以下作业,有时间就写。因为讲完这 12 节课,可以写出很多游戏了,所以

    可能会感觉作业一下子多了许多。

    4. 写个俄罗斯方块。

    5. 写贪吃蛇、扫雷。这两个稍微复杂一些,如果遇到问题,贴吧里贴出来,大

    家一起讨论。

    后面还会有更精彩的课程,敬请期待。

    ----------------------- Page 35-----------------------

    十三 getimage/putimage/loadimage/

    saveimage

    这一组命令和 IMAGE 对象可以实现图像处理的相关功能,下面逐个介绍。

    (有点类似 tc 中的 imagesize )

    [加载图片]

    实现加载图片主要分三步:

    1. 定义 IMAGE 对象

    2. 读取图片至 IMAGE 对象

    3. 显示 IMAGE 对象到需要的位置

    很简单,我们看一下完整的代码:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);


    IMAGE img; // 定义 IMAGE 对象

    loadimage(&img, "C:\test.jpg"); // 读取图片到 img 对象中

    putimage(0, 0, &img); // 在坐标 (0, 0) 位置显示 IMAGE 对象


    getch();
    closegraph();
    }

    注意要显示的图片是 C: est.jpg ,你可以修改为自己的图片路径。

    如果只需要加载图片到绘图窗体上, 那么请将 loadimage 的第一个参数设

    置为 NULL 即可,这样就不需要定义 IMAGE 对象了。

    [保存屏幕区域]

    ----------------------- Page 36-----------------------

    和加载图片类似,我们可以从屏幕的某个区域加载图像至 IMAGE 对象,

    然后再 putimage 到需要的地方。

    获取屏幕区域的代码格式:

    getimage(IMAGE& img, int x, int y, int w, int h);

    参数说明:

    img: 保存该屏幕区域的 IMAGE 对象

    x, y: 区域的左上角坐标

    w, h: 区域的宽和高(注意:不是右下角坐标)

    看代码:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);


    // 定义 IMAGE 对象

    IMAGE img;


    // 绘制内容

    circle(100, 100, 20);
    line(70, 100, 130, 100);
    line(100, 70, 100, 130);


    // 保存区域至 img 对象

    getimage(&img, 70, 70, 60, 60);


    // 将 img 对对象显示在屏幕的某个位置

    putimage(200, 200, &img);

    getch();
    closegraph();
    }



    ----------------------- Page 37-----------------------

    [移动复杂的图案]

    复杂的图案如果要移动,每次都重新绘制显然效率很低,移动的时候会出现

    严重的屏幕闪烁。

    而 getimage / putimage 的效率十分高,我们可以将复杂的图案用 getimage

    保存下来,然后再逐步 putimage 实现复杂图案的移动。

    这个代码就不举例了,作为作业大家练习吧。



    [更多的功能]

    getimage / putimage 有许多重载,这里就不多介绍了,详细看看帮助中的描述吧。

    读取图片的技巧:将图片内嵌到 exe 文件中,请参见:

    http://hi.baidu.com/bestans/blog/item/0012a915ffd5a80f4b90a733.html



    [作业]

    1. 用线条、圆等各种基础绘图语句画一个“汽车”,然后用 getimage / putimage 实

    现该“汽车” 的平滑移动。

    2. 自己学一下帮助中 BeginBatchDraw / FlushBatchDraw / EndBatchDraw 三个

    函数,可以进一步优化“平滑移动” 的效果。这三个命令挺简单的,一看就懂。

    ----------------------- Page 38-----------------------

    十四 通过位运算实现颜色的分离与处理



    本节课要求熟练掌握位运算,详见:

    http://hi.baidu.com/bestans/blog/item/fb75b439404876e614cecb9f.html



    [颜色基础]

    在 EasyX 库中,颜色是一个 int 类型的数据,转换为 16 进制后的颜色格

    式是 0xbbggrr,其中,bb/gg/rr 分别表示两位十六进制的蓝/绿/红颜色值,每种

    颜色的范围是 0x0~0xff,转换为十进制就是 0~255 。

    举几个颜色标示的例子:

    • 颜色 直接表示 RGB 宏标示

    • 纯绿色 0x00ff00 RGB(0, 255, 0)

    • 青色 0xffff00 RGB(0, 255, 255) 注:青=蓝+绿

    • 中灰色 0x7f7f7f RGB(127, 127, 127)

    • 黄色 0x00ffff RGB(255, 255, 0) 注:黄=红+绿

    例如设置绘图颜色为黄色,可以多种方法,例如:

    • setcolor(YELLOW);
    • setcolor( RGB(255, 255, 0) );
    • setcolor(0x00ffff);

    [获取颜色]

    getpixel 是用来获取屏幕颜色的函数,其返回值为 int 类型的颜色。例如:

    int c = getpixel(100, 100); // 该语句将返回坐标 (100, 100) 位置的颜色。

    [颜色分离与处理]

    有时候我们需要修改颜色某一位的值,这时,可以通过位运算来实现。比如,

    我们想把某一个点的颜色的红色部分去掉,可以这么做:

    ----------------------- Page 39-----------------------

    int c = getpixel(100, 100);
    c &= 0xffff00;
    putpixel(100, 100);

    我们来看一个完整的程序,这个程序,将图片左半部中的红色“去掉” 了,就

    像是显示器“缺色” 的效果:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);


    // 读取图片

    loadimage(NULL, "c:\test.jpg");

    int c;
    for(int x=0; x<320; x++)
    for(int y=0; y<480; y++)
    {
    c = getpixel(x, y);
    c = (0xff0000 - (c & 0xff0000)) | (0x00ff00 - (c & 0x00ff00)) |
    (0x0000ff - (c & 0x0000ff));
    putpixel(x, y, c);
    }

    getch();
    closegraph();
    }

    继续实践,找到这行:

    c &= 0xffff00;

    我们修改为:

    c = (0xff0000 - (c & 0xff0000)) | (0x00ff00 - (c & 0x00ff00)) | (0x0000ff - (c &
    0x0000ff));

    在执行看看效果,就成了照片的底片效果。

    ----------------------- Page 40-----------------------

    注:通过宏 GetRValue / GetGValue / GetBValue 可以直接获取 COLORREF

    中的颜色分量,详见帮助。

    [作业]

    1. 实现提高/ 降低图像亮度的程序。

    2. 自己搜索“灰度算法”,实现彩色图像转换为灰度图像。

    ----------------------- Page 41-----------------------

    十五 窗体句柄—Windows 编程入门

    EasyX 库有一个获取窗口句柄的功能,很是强大,这里介绍一下。

    【窗体句柄】

    窗体句柄是 Windows 下窗口的标识,可以理解为窗口的 ID 。Windows SDK 中

    的许多窗口操作函数都需要指明窗体句柄,也就是说,有了句柄,我们可以通过

    Windows SDK 中的 API 实现许多高级的窗体控制。

    【函数原型】

    窗体句柄为 HWND 类型,通过 GetHWnd() 函数可以返回绘图窗体的句柄。其

    函数原型是:

    HWND GetHWnd();

    【使用句柄】

    举个例子,设置窗体标题文字的 Windows API 为:

    BOOL SetWindowText(HWND hWnd, LPCTSTR lpString);

    参数:

    hWnd: 要设置标题文字的窗口句柄

    lpString: 窗体的标题文字,是一个指向字符串的指针。

    返回值:

    设置成功与否。

    以下是设置窗体标题文字的完整范例:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {
    initgraph(640, 480);

    // 获取窗口句柄

    HWND hwnd = GetHWnd();

    // 设置窗口标题文字

    SetWindowText(hwnd, "Hello World!");

    ----------------------- Page 42-----------------------

    getch();
    closegraph();
    }

    更多的窗体控制函数,请参考 MSDN 。

    ----------------------- Page 43-----------------------

    十六 设备上下文句柄—Windows 编程入门

    注:学习本节前,请自备MSDN ,以便查阅Windows GDI 函数。

    EasyX 的绘图函数最初是模仿的BGI 的函数命名。为了让大家借此学习

    Windows GDI 绘图,EasyX 增加了获取HDC 句柄的功能。

    对于Windows GDI 中的绘图函数,很多都需要一个HDC 句柄。我们用

    GetImageHDC()函数获取该句柄,然后就可以使用Windows GDI 了。先看看例子

    吧:

    #include <graphics.h>
    #include <conio.h>

    void main()
    {

    // 初始化绘图窗口,并获取HDC 句柄

    initgraph(640, 480);
    HDC hdc = GetImageHDC();


    // 以下是标准Windows GDI 操作画一条线(相关语句,请查阅MSDN )

    MoveToEx(hdc, 100, 100, NULL);
    LineTo(hdc, 200, 200);

    // 标准 Windows GDI 操作结束



    // 使之前的 Windows GDI 操作生效

    FlushBatchDraw();


    // 按任意键返回

    getch();
    closegraph();
    }

    还可以针对IMAGE 对象使用GDI 绘图函数,看下面这个例子:

    #include <graphics.h>
    #include <conio.h>

    void main()

    ----------------------- Page 44-----------------------

    {

    // 初始化绘图窗口

    initgraph(640, 480);


    // 创建 300x300 的 IMAGE 对象,并获取其 HDC 句柄

    IMAGE img(300, 300);
    HDC hdc = GetImageHDC(&img);


    // 以下是标准Windows GDI 操作画一条线(相关语句,请查阅MSDN )

    MoveToEx(hdc, 100, 100, NULL);
    LineTo(hdc, 200, 200);

    // 标准 Windows GDI 操作结束



    // 将 img 贴到绘图窗口上:

    putimage(0, 0, &img);


    // 按任意键返回

    getch();
    closegraph();
    }

    注意:

    1. 通过 GetImageHDC() 获取绘图窗口的 HDC 时,绘图后需要执行

    FlushBatchDraw() 使之生效;获取 IMAGE 的 HDC 无需执行

    FlushBatchDraw() 。

    2. 这次的内容虽然少,但是 Windows GDI 的内容相当多,所以,完成本

    节的学习还是很不容易的。

    3. Windows GDI 并没有设置颜色这样的函数,需要创建画笔(画刷)并选入画

    笔(画刷) ,并且在不用的时候记得删除。Windows GDI 相当的丰富,这里就不多

    做介绍了,感兴趣的请参考相关书籍。

    4. 至于作业,其实从前几讲开始就没必要弄什么作业了,能坚持看下来的,

    相信都会自觉的写一些东西。

  • 相关阅读:
    C语言程序设计II—第六周教学
    第一次结对编程情况反馈
    C语言程序设计II—第五周教学
    C语言程序设计II—第四周教学
    放缩
    切线垂直
    指数为对数时取对数
    整体运算
    数列求通项+离散数列单调性判断
    整体运算+求零点
  • 原文地址:https://www.cnblogs.com/alan-W/p/10729961.html
Copyright © 2020-2023  润新知