----------------------- 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\n", 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", n[i]);

// 找出最大的

int max = -1;

for (i=0; i<10; i++)

{

if (n[i] > max)

max = n[i];

}

printf("最大的数字是:%d\n", 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:\test.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. 至于作业,其实从前几讲开始就没必要弄什么作业了,能坚持看下来的,

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

EASY-X的更多相关文章

  1. 【转】Windows下使用libsvm中的grid.py和easy.py进行参数调优

    libsvm中有进行参数调优的工具grid.py和easy.py可以使用,这些工具可以帮助我们选择更好的参数,减少自己参数选优带来的烦扰. 所需工具:libsvm.gnuplot 本机环境:Windo ...

  2. Struts2 easy UI插件

    一.easy UI是类似于jQuery UI的插件库,它提供了丰富的各种常用插件:tree.datagrid... tree插件: 语法:$(selector).tree([settings]); 常 ...

  3. Easy UI常用插件使用

    一.easy UI是类似于jQuery UI的插件库,它提供了丰富的各种常用插件:tree.datagrid... tree插件: 语法:$(selector).tree([settings]); 常 ...

  4. UVA-11991 Easy Problem from Rujia Liu?

    Problem E Easy Problem from Rujia Liu? Though Rujia Liu usually sets hard problems for contests (for ...

  5. CodeForces462 A. Appleman and Easy Task

    A. Appleman and Easy Task time limit per test 1 second memory limit per test 256 megabytes input sta ...

  6. easy ui插件

    简介: easy UI是类似于jQuery UI的插件库 注意:多脚本同时使用时,注意脚本冲突问题. 常用插件: 1.tree插件(tree插件实现动态树形菜单) 2.datagrid插件(datag ...

  7. 用TPP开启TDD的easy模式

    Test-Drived Development 测试驱动开发三步曲:写一个失败的测试用例->编写生产代码通过这个测试用例(transformation)->重构(refactor).重构是 ...

  8. Easy Sysprep更新日志-skyfree大神

    Easy Sysprep更新日志: Skyfree 发表于 2016-1-22 13:55:55 https://www.itsk.com/forum.php?mod=viewthread&t ...

  9. [官方软件] Easy Sysprep v4.3.29.602 【系统封装部署利器】(2016.01.22)--skyfree大神

    [官方软件] Easy Sysprep v4.3.29.602 [系统封装部署利器](2016.01.22) Skyfree 发表于 2016-1-22 13:55:55 https://www.it ...

  10. [原创] Easy SysLite V1.2 (2016.5.29更新,新增加WIN10支持,一个程序适配所有系统减肥)

    [原创] Easy SysLite V1.2 (2016.5.29更新,新增加WIN10支持,一个程序适配所有系统减肥) nohacks 发表于 2016-5-29 17:12:51 https:// ...

随机推荐

  1. openstack第一章:keystone

    第一篇keystone— 身份认证服务 一.Keystone介绍:       keystone 是OpenStack的组件之一,用于为OpenStack家族中的其它组件成员提供统一的认证服务,包括身 ...

  2. PHP加密解密函数(带有效期,过了有效期也解不了)

    转的,原来应该是discuz中弄的 <?php //加解密函数 //此函数的厉害之处在于可以在指定时间内加密还原字符串,超时无法还原. //这样我们就可以拿此函数来做很多用途了,比如:单点登录的 ...

  3. [转帖]Zoom

    Zoom美国上市:华裔创始人为大股东 创业想法来自“异地恋” https://baijiahao.baidu.com/s?id=1631166070308020680&wfr=spider&a ...

  4. this.$router.push、replace、go的区别

    1.this.$router.push() 描述:跳转到不同的url,但这个方法会向history栈添加一个记录,点击后退会返回到上一个页面. 用法: 2.this.$router.replace() ...

  5. JS学习笔记:(二)回流和重绘

    在搞清楚回流和重绘的概念之前,我们要清除浏览器的渲染过程. 解析生成DOM Tree(此时包含所有节点,包括display:none); 根据CSS Object Module(CCSSOM)计算节点 ...

  6. MyBatis 3源码解析(二)

    二.获取SqlSession对象 1.首先调用DefaultSqlSessionFactory 的 openSession 方法,代码如下: @Override public SqlSession o ...

  7. python 元组用法

    tup1 = ('physics', 'chemistry', 1997, 2000) 元组中的元素值是不允许修改的 序号 方法及描述 1 cmp(tuple1, tuple2)比较两个元组元素. 2 ...

  8. hdu-4635(tarjan缩点)

    题意:先给你一个n个点,m条边的有向图,问你最多能够增加多少条边,使得这个图不是一个强连通图 解题思路:考虑最多要添加的边数,所以如果能把初始图划分成两个部分,每个部分都是完全图,这两个部分分别用单向 ...

  9. Django_rest framework 框架介绍

    restful介绍  restful协议 一切皆是资源,操作只是请求方式 URL 设计 原先的URL设计方式 在url 中体现出操作行为 /books/ books /books/add/ addbo ...

  10. Docker 介绍及基础命令

    Docker 简介 Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目.它基于 Google 公司推出的 Go 语言实现. 项目后来加入了 Linu ...