《C语言课程设计与游戏开发实践课程》67章总结

 目录

一、知识点总结

       第六章:指针、字符串、结构体、文件在游戏中的应用

二、代码实践

       6.2字符雨

  6.3互动粒子仿真

  6.4飞机大战(升级版)

       7.1可视化汉诺塔

       7.2基于链表的祖玛游戏

一、知识点总结

       第六章:指针、字符串、结构体、文件的应用

  1 6.1指针
  2 用指针在函数间传值可以避免过多的全局变量。对于指针和字符串的知识点会专门写博客。这主要讲应用
  3 动态二维数组的使用
  4 使用指针,定义二维数组,可以动态调整数组的大小,然后把指针指向新生成数组,销毁旧数组
  5 // 分配动态二维数组的内存空间
  6 int **canvas=(int**)malloc(high*sizeof(int*)); 
  7 for(i=0;i<high;i++)
  8         canvas[i]=(int*)malloc(width*sizeof(int));
  9 
 10 // 使用完后清除动态数组的内存空间
 11     for(i=0; i<high; i++)
 12         free(canvas[i]);
 13     free(canvas);
 14 
 15 6.2字符串
 16 字符串常见函数
 17 #include<string.h>
 18 strlen(s);//计算s的长度
 19 strcpy(s1,s2);//将S2复制到S1
 20 strcat(s1,s2);//将字符串s2添加到字符串s1的末端,但必须保证字符串s1足够大;
 21 strcmp(s1,s2);//比较S1和S2,返回第一个不等的字符的大小比较结果,
 22 如果 s1>s2 则返回 1,等于则返回 0 ,小于则返回 -1 
 23 
 24 字符串在游戏的应用,对于读取文件,用字符串存文件名,然后封装成函数,只需传文件名即可。
 25 
 26 6.2字符雨动画
 27 整个的实现逻辑是:首先随机生成每一列字符个数和字符(A-Z)并记录在数组里,然后每列字符下移,最上面新增字符,如果已满,改颜色。如果已改颜色,重新初始化这列。具体代码看代码实践。
 28 
 29 srand((unsigned) time(NULL)); // 设置随机函数种子
 30 
 31 seed相当于一个种子,srand函数根据这个种子seed,设置一个随机起点,而rand函数根据这个随机起点,返回一个随机数【seed   ,RAND_MAX】
其中RAND_MAX是0x7ffff,但是是一个伪的随机数(多次编译产生的随机数是一样的,除非给一个变化的种子)
32 33 值得注意的是: 34 rand函数每一次被调用的时候,它都会查看之前是否调用了srand函数 35 1. 如果调用了,则会调用srand(seed)来初始化它的随机值 36 2. 如果没有调用,则会默认的调用srand(1)来初始化它的随机值 37 38 srand这段的原文链接:https://blog.csdn.net/msdnwolaile/article/details/50707481 39 40 6.3互动粒子仿真 41 !!!本章的重头戏,主要难点在于力学的应用,达到更好的交互体验。 42 分析了粒子间的三个力的相互作用,包括自带的速度和加速度,鼠标的吸引力,鼠标的击打斥力、鼠标的扰动力。听到这是不是感觉受力分析很复杂了。 43 在电脑中的实现,只是对速度和加速度方向做更改就行,不同力影响不同,分开讨论。 44 这里运用的结构体,小球定义为结构体,方便对小球做统一操作。 45 // 定义小球结构 46 struct Mover 47 { 48 COLORREF color; // 颜色 49 float x, y; // 坐标 50 float vX, vY; // 速度 51 float radius; // 半径 52 }; 53 54 实现过程: 55 1.初始化多个粒子,任意分布在画布上,数值在范围内任意。 56 // 设置随机种子 57 srand((unsigned int)time(NULL)); 58 59 // 初始化小球数组 60 for (int i = 0; i < NUM_MOVERS; i++) 61 { 62 movers[i].color = RGB(rand() % 256, rand() % 256, rand() % 256); 63 movers[i].x = rand()%WIDTH; 64 movers[i].y = rand()%HEIGHT; 65 movers[i].vX = float(cos(float(i))) * (rand() % 34); 66 movers[i].vY = float(sin(float(i))) * (rand() % 34); 67 movers[i].radius = (rand() % 34)/15.0; 68 } 69 70 2.小球碰撞与反弹,同时加一点摩擦力。 71 摩擦力好解决,给速度乘摩擦系数,然后越来越慢,小球快停再给它一个速度。 72 #define FRICTION 0.96f // 摩擦力/阻尼系数 73 // 小球运动有一个阻尼(摩擦力),速度逐渐减少 74 vX = vX * FRICTION; 75 vY = vY * FRICTION; 76 // 速度的绝对值 77 float avgVX = fabs(vX); 78 float avgVY = fabs(vY); 79 // 两个方向速度的平均 80 float avgV = (avgVX + avgVY) * 0.5f; 81 // 因为有上面阻尼的作用,如果速度过小的话,乘以一个[0,3]的随机数,会以比较大的概率让速度变大 82 if (avgVX < 0.1) 83 vX = vX * float(rand()) / RAND_MAX * 3; 84 if (avgVY < 0.1) 85 vY = vY * float(rand()) / RAND_MAX * 3; 86 87 3.加入鼠标的吸引力,鼠标的击打斥力、鼠标的扰动力 88 我打算把这几个力的计算搞出来: 89 思路:(1)首先定义力的影响范围toDist、blowDist、stirDist 90 (2) 对于每个球for一遍,利用两点距离公式,计算与鼠标的距离,然后dX,dY表示力的方向,四个力都影响一下。更新小球的速度和坐标 91 (3)对吸引力,判断在吸引距离内,直接在dX,dY上加一个toAcc和距离的吸引加速度。 92 toAcc = (1 - (d / toDist)) * WIDTH * 0.0014f; 93 vX = vX - dX * toAcc; 94 vY = vY - dY * toAcc; 954)对打击力,打击距离内,有一个打击力,然后有个小扰动(具体也不知道为什么,可能是为了效果更加真实,因为我们没有计算小球间作用力) 96 blowAcc = (1 - (d / blowDist)) * 10 97 vX = vX + dX * blowAcc + 0.5f - float(rand()) / RAND_MAX; 98 vY = vY + dY * blowAcc + 0.5f - float(rand()) / RAND_MAX; 995)扰动力,和吸引力一样,改一下加速度就行 100 float mAcc = (1 - (d / stirDist)) * WIDTH * 0.00026f; 101 vX = vX + mouseVX * mAcc; 102 vY = vY + mouseVY * mAcc; 103 本段代码实现:
  1 void updateWithoutInput()
  2 {
  3     float toDist   = WIDTH * 0.86;  // 吸引距离,小球距离鼠标在此范围内,会受到向内的吸力
  4     float blowDist = WIDTH * 0.5;   // 打击距离,小球距离鼠标在此范围内,会受到向外的斥力
  5     float stirDist = WIDTH * 0.125; // 扰动距离,小球距离鼠标在此范围内,会受到鼠标的扰动
  6 
  7     // 前后两次运行间鼠标移动的距离,即为鼠标的速度
  8     mouseVX    = mouseX - prevMouseX;
  9     mouseVY    = mouseY - prevMouseY;
 10     
 11     // 更新上次鼠标坐标变量,为记录这次鼠标的坐标
 12     prevMouseX = mouseX;
 13     prevMouseY = mouseY;
 14 
 15     for(int i = 0; i < NUM_MOVERS; i++)  // 对所有小球遍历
 16     {
 17         float x  = movers[i].x;  // 当前小球坐标
 18         float y  = movers[i].y;
 19         float vX = movers[i].vX;  // 当前小球速度
 20         float vY = movers[i].vY;
 21 
 22         float dX = x - mouseX;    // 计算当前小球位置和鼠标位置的差
 23         float dY = y - mouseY; 
 24         float d  = sqrt(dX * dX + dY * dY);    // 当前小球和鼠标位置的距离
 25         
 26         // 下面将dX、dY归一化,仅反映方向,和距离长度无关。
 27         if (d!=0)
 28         {
 29             dX = dX/d;
 30             dY = dY/d;
 31         }
 32         else
 33         {
 34             dX = 0;
 35             dY = 0;
 36         }
 37 
 38         // 小球距离鼠标 < toDist,在此范围内小球会受到鼠标的吸引
 39         if (d < toDist)
 40         {
 41             // 吸引力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。但吸引力的值明显比上面斥力的值要小很多
 42             float toAcc = (1 - (d / toDist)) * WIDTH * 0.0014f;
 43             // 由dX、dY归一化方向信息,加速度幅度值toAcc,得到新的小球速度
 44             vX = vX - dX * toAcc;
 45             vY = vY - dY * toAcc;            
 46         }
 47 
 48         // 当鼠标左键按下,并且小球距离鼠标 < blowDist(在打击范围内,会受到向外的力)
 49         if (isMouseDown && d < blowDist)
 50         {
 51             // 打击力引起的加速度幅度(Acceleration),这个公式表示小球距离鼠标越近,打击斥力引起的加速度越大
 52             float blowAcc = (1 - (d / blowDist)) * 10;
 53             // 由上面得到的dX、dY归一化方向信息,上面的加速度幅度值blowAcc,得到新的小球速度
 54             //  float(rand()) / RAND_MAX 产生[0,1]之间的随机数
 55             // 0.5f - float(rand()) / RAND_MAX 产生[-0.5,0.5]之间的随机数,加入一些扰动
 56             vX = vX + dX * blowAcc + 0.5f - float(rand()) / RAND_MAX;
 57             vY = vY + dY * blowAcc + 0.5f - float(rand()) / RAND_MAX;
 58         }
 59 
 60         // 小球距离鼠标 < stirDist,在此范围内小球会受到鼠标的扰动
 61         if (d < stirDist)
 62         {
 63             // 扰动力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。扰动力的值更小
 64             float mAcc = (1 - (d / stirDist)) * WIDTH * 0.00026f;
 65             // 鼠标速度越快,引起的扰动力越大
 66             vX = vX + mouseVX * mAcc;
 67             vY = vY + mouseVY * mAcc;            
 68         }
 69 
 70         // 小球运动有一个阻尼(摩擦力),速度逐渐减少
 71         vX = vX * FRICTION;
 72         vY = vY * FRICTION;
 73         
 74         // 速度的绝对值
 75         float avgVX = abs(vX);
 76         float avgVY = abs(vY);
 77         // 两个方向速度的平均
 78         float avgV  = (avgVX + avgVY) * 0.5f;
 79         
 80         // 因为有上面阻尼的作用,如果速度过小的话,乘以一个[0,3]的随机数,会以比较大的概率让速度变大
 81         if (avgVX < 0.1) 
 82             vX = vX * float(rand()) / RAND_MAX * 3;
 83         if (avgVY < 0.1) 
 84             vY = vY * float(rand()) / RAND_MAX * 3;
 85         
 86         // 小球的半径在[0.4,3.5]之间,速度越大,半径越大
 87         float sc = avgV * 0.45f;
 88         sc = max(min(sc, 3.5f), 0.4f);
 89         movers[i].radius = sc;
 90         
 91         // 根据位置+速度,更新小球的坐标
 92         float nextX = x + vX;
 93         float nextY = y + vY;
 94         
 95         // 小球如果超过上下左右四个边界的话,位置设为边界处,速度反向
 96         if    (nextX > WIDTH)    
 97         { 
 98             nextX = WIDTH;    
 99             vX = -1*vX; 
100         }
101         else if (nextX < 0)    
102         {
103             nextX = 0;        
104             vX = -1*vX; 
105         }
106         if    (nextY > HEIGHT)
107         { 
108             nextY = HEIGHT;    
109             vY = -1*vY; 
110         }
111         else if (nextY < 0)    
112         { 
113             nextY = 0;        
114             vY = -1*vY; 
115         }
116         
117         // 更新小球位置、速度的结构体数组
118         movers[i].vX = vX;
119         movers[i].vY = vY;
120         movers[i].x  = nextX;
121         movers[i].y  = nextY;
122     }    
123 }
四个作用力影响的实现
106 
107 
108 4.绝对延迟
109 保证不同机器上同样运行效果
110 void delay(DWORD ms)
111 {
112     static DWORD oldtime = GetTickCount();    
113     while(GetTickCount() - oldtime < ms)
114         Sleep(1);    
115     oldtime = GetTickCount();
116 }
117  
118 6.4文件实现存读档功能
119 给飞机大战加了一个进入界面,实现多画面显示。给游戏加状态,不同状态和不同输入调用不同函数,显示不同画面。
120 
121 应用文件用于存游戏档案(记录画面所有飞机、子弹的位置和得分)。
122 void readRecordFile()  //读取游戏数据文件存档
123 {
124     FILE *fp;
125     fp = fopen(".\\gameRecord.dat","r");
126     fscanf(fp,"%f %f %f %f %f %f %d %d",&position_x,&position_y,&bullet_x,&bullet_y,&enemy_x,&enemy_y,&isExpolde,&score);
127     fclose(fp);
128 }
129 
130 void writeRecordFile()  //存储游戏数据文件存档
131 {
132     FILE *fp;
133     fp = fopen(".\\gameRecord.dat","w");
134     fprintf(fp,"%f %f %f %f %f %f %d %d",position_x,position_y,bullet_x,bullet_y,enemy_x,enemy_y,isExpolde,score);
135     fclose(fp);
136 }
137             

二、代码实践

       6.2字符雨

 1 #pragma warning(disable:4996);
 2 #include <graphics.h>
 3 #include <time.h>
 4 #include <conio.h>
 5 #define High 800  // 游戏画面尺寸
 6 #define Width 1000
 7 #define CharSize 25 // 每个字符显示的大小
 8 void main()
 9 {
10     int highNum = High / CharSize;
11     int widthNum = Width / CharSize;
12     // 存储对应字符矩阵中需要输出字符的ASCII码
13     int CharRain[Width / CharSize][High / CharSize];
14     int CNum[Width / CharSize]; // 每一列的有效字符个数
15     int ColorG[Width / CharSize]; // 每一列字符的颜色
16     int i, j, x, y;
17     srand((unsigned)time(NULL)); // 设置随机函数种子
18     for (i = 0;i < widthNum;i++) // 初始化字符矩阵
19     {
20         CNum[i] = (rand() % (highNum * 9 / 10)) + highNum / 10; // 这一列的有效字符个数
21         ColorG[i] = 255;
22         for (j = 0;j < CNum[i];j++)
23             CharRain[j][i] = (rand() % 26) + 65;  // 产生A-Z的随机ASCII码
24     }
25     initgraph(Width, High);
26     BeginBatchDraw();
27     setfont(25, 10, "Courier");    // 设置字体
28     // 下面每一帧,让字符向下移动,然后最上面产生新的字符
29     while (1)
30     {
31         for (i = 0;i < widthNum;i++)
32         {
33             if (CNum[i] < highNum - 1)  // 当这一列字符没有填满时
34             {
35                 for (j = CNum[i] - 1;j >= 0;j--)  // 向下移动一格
36                     CharRain[j + 1][i] = CharRain[j][i];
37                 CharRain[0][i] = (rand() % 26) + 65;  // 最上面的产生A-Z的随机ASCII码
38                 CNum[i] = CNum[i] + 1; // 这一列的有效字符的个数+1
39             }
40             else // 这一列字符已经填满
41             {
42                 if (ColorG[i] > 40)
43                     ColorG[i] = ColorG[i] - 20; // 让满的这一列逐渐变暗
44                 else
45                 {
46                     CNum[i] = (rand() % (highNum / 3)) + highNum / 10; // 这一列字符的个数
47                     ColorG[i] = (rand() % 75) + 180;  // 这一列字符的颜色
48                     for (j = 0;j < CNum[i];j++)  // 重新初始化这一列字符
49                         CharRain[j][i] = (rand() % 26) + 65;  // 产生A-Z的随机ASCII码
50                 }
51             }
52         }
53         // 输出整个字符矩阵
54         for (i = 0;i < widthNum;i++)
55         {
56             x = i * CharSize; // 当前字符的x坐标
57             for (j = 0;j < CNum[i];j++)
58             {
59                 y = j * CharSize;  // 当前字符的y坐标
60                 setcolor(RGB(0, ColorG[i], 0));
61                 outtextxy(x, y, CharRain[j][i]); // 输出当前字符
62             }
63         }
64         FlushBatchDraw();
65         Sleep(100);
66         clearrectangle(0, 0, Width - 1, High - 1);    // 清空画面全部矩形区域
67     }
68     EndBatchDraw();
69     getch();
70     closegraph();
71 }
6.2字符雨

  6.3互动粒子仿真

 真好看,按头安利大家实践一下!

  1 #pragma warning(disable:4996);
  2 #include <graphics.h>
  3 #include <math.h>
  4 #include <time.h>
  5 
  6 #define WIDTH        1024        // 屏幕宽
  7 #define HEIGHT        768            // 屏幕高
  8 #define NUM_MOVERS    800            // 小球数量
  9 #define    FRICTION    0.96f        // 摩擦力/阻尼系数
 10 
 11 // 定义小球结构
 12 struct Mover
 13 {
 14     COLORREF    color;            // 颜色
 15     float        x, y;            // 坐标
 16     float        vX, vY;            // 速度
 17     float       radius;         // 半径
 18 };
 19 
 20 // 定义全局变量
 21 Mover    movers[NUM_MOVERS];        // 小球数组
 22 int        mouseX, mouseY;            // 当前鼠标坐标
 23 int        prevMouseX, prevMouseY;        // 上次鼠标坐标
 24 int        mouseVX, mouseVY;        // 鼠标速度
 25 int        isMouseDown;                // 鼠标左键是否按下
 26 
 27 // 绝对延时
 28 void delay(DWORD ms)
 29 {
 30     static DWORD oldtime = GetTickCount();
 31     while (GetTickCount() - oldtime < ms)
 32         Sleep(1);
 33     oldtime = GetTickCount();
 34 }
 35 
 36 void startup()
 37 {
 38     // 设置随机种子
 39     srand((unsigned int)time(NULL));
 40 
 41     // 初始化小球数组
 42     for (int i = 0; i < NUM_MOVERS; i++)
 43     {
 44         movers[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
 45         movers[i].x = rand() % WIDTH;
 46         movers[i].y = rand() % HEIGHT;
 47         movers[i].vX = float(cos(float(i))) * (rand() % 34);
 48         movers[i].vY = float(sin(float(i))) * (rand() % 34);
 49         movers[i].radius = (rand() % 34) / 15.0;
 50     }
 51 
 52     // 初始化鼠标变量,当前鼠标坐标、上次鼠标坐标都在画布中心
 53     mouseX = prevMouseX = WIDTH / 2;
 54     mouseY = prevMouseY = HEIGHT / 2;
 55 
 56     isMouseDown = 0; // 初始鼠标未按下
 57 
 58     initgraph(WIDTH, HEIGHT);
 59     BeginBatchDraw();
 60 }
 61 
 62 void show()
 63 {
 64     clearrectangle(0, 0, WIDTH - 1, HEIGHT - 1);    // 清空画面全部矩形区域
 65 
 66     for (int i = 0; i < NUM_MOVERS; i++)
 67     {
 68         // 画小球
 69         setcolor(movers[i].color);
 70         setfillstyle(movers[i].color);
 71         fillcircle(int(movers[i].x + 0.5), int(movers[i].y + 0.5), int(movers[i].radius + 0.5));
 72     }
 73 
 74     FlushBatchDraw();
 75     delay(5);
 76 }
 77 
 78 void updateWithoutInput()
 79 {
 80     float toDist = WIDTH * 0.86;  // 吸引距离,小球距离鼠标在此范围内,会受到向内的吸力
 81     float blowDist = WIDTH * 0.5;   // 打击距离,小球距离鼠标在此范围内,会受到向外的斥力
 82     float stirDist = WIDTH * 0.125; // 扰动距离,小球距离鼠标在此范围内,会受到鼠标的扰动
 83 
 84     // 前后两次运行间鼠标移动的距离,即为鼠标的速度
 85     mouseVX = mouseX - prevMouseX;
 86     mouseVY = mouseY - prevMouseY;
 87 
 88     // 更新上次鼠标坐标变量,为记录这次鼠标的坐标
 89     prevMouseX = mouseX;
 90     prevMouseY = mouseY;
 91 
 92     for (int i = 0; i < NUM_MOVERS; i++)  // 对所有小球遍历
 93     {
 94         float x = movers[i].x;  // 当前小球坐标
 95         float y = movers[i].y;
 96         float vX = movers[i].vX;  // 当前小球速度
 97         float vY = movers[i].vY;
 98 
 99         float dX = x - mouseX;    // 计算当前小球位置和鼠标位置的差
100         float dY = y - mouseY;
101         float d = sqrt(dX * dX + dY * dY);    // 当前小球和鼠标位置的距离
102 
103         // 下面将dX、dY归一化,仅反映方向,和距离长度无关。
104         if (d != 0)
105         {
106             dX = dX / d;
107             dY = dY / d;
108         }
109         else
110         {
111             dX = 0;
112             dY = 0;
113         }
114 
115         // 小球距离鼠标 < toDist,在此范围内小球会受到鼠标的吸引
116         if (d < toDist)
117         {
118             // 吸引力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。但吸引力的值明显比上面斥力的值要小很多
119             float toAcc = (1 - (d / toDist)) * WIDTH * 0.0014f;
120             // 由dX、dY归一化方向信息,加速度幅度值toAcc,得到新的小球速度
121             vX = vX - dX * toAcc;
122             vY = vY - dY * toAcc;
123         }
124 
125         // 当鼠标左键按下,并且小球距离鼠标 < blowDist(在打击范围内,会受到向外的力)
126         if (isMouseDown && d < blowDist)
127         {
128             // 打击力引起的加速度幅度(Acceleration),这个公式表示小球距离鼠标越近,打击斥力引起的加速度越大
129             float blowAcc = (1 - (d / blowDist)) * 10;
130             // 由上面得到的dX、dY归一化方向信息,上面的加速度幅度值blowAcc,得到新的小球速度
131             //  float(rand()) / RAND_MAX 产生[0,1]之间的随机数
132             // 0.5f - float(rand()) / RAND_MAX 产生[-0.5,0.5]之间的随机数,加入一些扰动
133             vX = vX + dX * blowAcc + 0.5f - float(rand()) / RAND_MAX;
134             vY = vY + dY * blowAcc + 0.5f - float(rand()) / RAND_MAX;
135         }
136 
137         // 小球距离鼠标 < stirDist,在此范围内小球会受到鼠标的扰动
138         if (d < stirDist)
139         {
140             // 扰动力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。扰动力的值更小
141             float mAcc = (1 - (d / stirDist)) * WIDTH * 0.00026f;
142             // 鼠标速度越快,引起的扰动力越大
143             vX = vX + mouseVX * mAcc;
144             vY = vY + mouseVY * mAcc;
145         }
146 
147         // 小球运动有一个阻尼(摩擦力),速度逐渐减少
148         vX = vX * FRICTION;
149         vY = vY * FRICTION;
150 
151         // 速度的绝对值
152         float avgVX = fabs(vX);
153         float avgVY = fabs(vY);
154         // 两个方向速度的平均
155         float avgV = (avgVX + avgVY) * 0.5f;
156 
157         // 因为有上面阻尼的作用,如果速度过小的话,乘以一个[0,3]的随机数,会以比较大的概率让速度变大
158         if (avgVX < 0.1)
159             vX = vX * float(rand()) / RAND_MAX * 3;
160         if (avgVY < 0.1)
161             vY = vY * float(rand()) / RAND_MAX * 3;
162 
163         // 小球的半径在[0.4,3.5]之间,速度越大,半径越大
164         float sc = avgV * 0.45f;
165         sc = max(min(sc, 3.5f), 0.4f);
166         movers[i].radius = sc;
167 
168         // 根据位置+速度,更新小球的坐标
169         float nextX = x + vX;
170         float nextY = y + vY;
171 
172         // 小球如果超过上下左右四个边界的话,位置设为边界处,速度反向
173         if (nextX > WIDTH)
174         {
175             nextX = WIDTH;
176             vX = -1 * vX;
177         }
178         else if (nextX < 0)
179         {
180             nextX = 0;
181             vX = -1 * vX;
182         }
183         if (nextY > HEIGHT)
184         {
185             nextY = HEIGHT;
186             vY = -1 * vY;
187         }
188         else if (nextY < 0)
189         {
190             nextY = 0;
191             vY = -1 * vY;
192         }
193 
194         // 更新小球位置、速度的结构体数组
195         movers[i].vX = vX;
196         movers[i].vY = vY;
197         movers[i].x = nextX;
198         movers[i].y = nextY;
199     }
200 }
201 
202 void updateWithInput()
203 {
204     MOUSEMSG m;        // 定义鼠标消息
205     while (MouseHit())  //检测当前是否有鼠标消息
206     {
207         m = GetMouseMsg();
208         if (m.uMsg == WM_MOUSEMOVE) // 鼠标移动的话,更新当前鼠标坐标变量
209         {
210             mouseX = m.x;
211             mouseY = m.y;
212         }
213         else if (m.uMsg == WM_LBUTTONDOWN)  // 鼠标左键按下
214             isMouseDown = 1;
215         else if (m.uMsg == WM_LBUTTONUP)    // 鼠标左键抬起
216             isMouseDown = 0;
217     }
218 }
219 
220 void gameover()
221 {
222     EndBatchDraw();
223     closegraph();
224 }
225 
226 int main()
227 {
228     startup();  // 数据初始化    
229     while (1)  //  游戏循环执行
230     {
231         show();  // 显示画面
232         updateWithInput();     // 与用户输入有关的更新
233         updateWithoutInput();  // 与用户输入无关的更新
234     }
235     gameover();     // 游戏结束、后续处理
236     return 0;
237 }
6.3互动粒子仿真

  6.4飞机大战(升级版)

  1 #pragma warning(disable:4996);
  2 #include <graphics.h>
  3 #include <conio.h>
  4 #include <math.h>
  5 #include <stdio.h>
  6 
  7 // 引用 Windows Multimedia API
  8 #pragma comment(lib,"Winmm.lib")
  9 
 10 #define High 800  // 游戏画面尺寸
 11 #define Width 590
 12 
 13 IMAGE img_bk; // 背景图片
 14 float position_x, position_y; // 飞机位置
 15 float bullet_x, bullet_y; // 子弹位置
 16 float enemy_x, enemy_y; // 敌机位置
 17 IMAGE img_planeNormal1, img_planeNormal2; // 正常飞机图片
 18 IMAGE img_planeExplode1, img_planeExplode2; // 爆炸飞机图片
 19 IMAGE img_bullet1, img_bullet2; // 子弹图片
 20 IMAGE img_enemyPlane1, img_enemyPlane2; // 敌机图片
 21 int isExpolde = 0; // 飞机是否爆炸
 22 int score = 0; // 得分
 23 
 24 int gameStatus = 0; // 游戏状态,0为初始菜单界面,1为正常游戏,2为结束游戏状态,3为游戏暂停
 25 
 26 void startMenu(); // 初始菜单界面
 27 void pauseMenu(); // 游戏暂停后菜单界面,一般按ESC键后启动该界面
 28 void startup();  // 数据初始化    
 29 void show();  // 显示画面
 30 void updateWithoutInput();  // 与用户输入无关的更新
 31 void updateWithInput();     // 与用户输入有关的更新
 32 void gameover();     // 游戏结束、后续处理
 33 void readRecordFile();  //读取游戏数据文件存档
 34 void writeRecordFile();  //存储游戏数据文件存档
 35 
 36 void startMenu() // 初始菜单界面
 37 {
 38     putimage(0, 0, &img_bk);    // 显示背景
 39     setbkmode(TRANSPARENT);
 40     settextcolor(BLACK);
 41     settextstyle(50, 0, _T("黑体"));
 42     outtextxy(Width * 0.3, High * 0.2, "1 新游戏");
 43     outtextxy(Width * 0.3, High * 0.3, "2 读取游戏存档");
 44     outtextxy(Width * 0.3, High * 0.4, "3 退出");
 45 
 46     settextcolor(BLUE);
 47     settextstyle(30, 0, _T("黑体"));
 48     outtextxy(Width * 0.25, High * 0.6, "鼠标移动控制飞机移动");
 49     outtextxy(Width * 0.25, High * 0.65, "鼠标左键发射子弹");
 50     outtextxy(Width * 0.25, High * 0.7, "ESC键暂停游戏");
 51     outtextxy(Width * 0.25, High * 0.75, "撞击后按任意键重新开始");
 52     FlushBatchDraw();
 53     Sleep(2);
 54 
 55     char input;
 56     if (kbhit())  // 判断是否有输入
 57     {
 58         input = getch();  // 根据用户的不同输入来移动,不必输入回车
 59         if (input == '1')
 60             gameStatus = 1;
 61         else if (input == '2')
 62         {
 63             readRecordFile();
 64             gameStatus = 1;
 65         }
 66         else if (input == '3')
 67         {
 68             gameStatus = 2;
 69             exit(0);
 70         }
 71     }
 72 }
 73 
 74 void pauseMenu() // 游戏暂停后菜单界面,一般按ESC键后启动该界面
 75 {
 76     putimage(0, 0, &img_bk);    // 显示背景
 77     setbkmode(TRANSPARENT);
 78     settextcolor(BLACK);
 79     settextstyle(50, 0, _T("黑体"));
 80     outtextxy(Width * 0.3, High * 0.2, "1 继续游戏");
 81     outtextxy(Width * 0.3, High * 0.3, "2 保存档案");
 82     outtextxy(Width * 0.3, High * 0.4, "3 退出");
 83 
 84     settextcolor(BLUE);
 85     settextstyle(30, 0, _T("黑体"));
 86     outtextxy(Width * 0.25, High * 0.6, "鼠标移动控制飞机移动");
 87     outtextxy(Width * 0.25, High * 0.65, "鼠标左键发射子弹");
 88     outtextxy(Width * 0.25, High * 0.7, "ESC键暂停游戏");
 89     outtextxy(Width * 0.25, High * 0.75, "撞击后按任意键重新开始");
 90     FlushBatchDraw();
 91     Sleep(2);
 92 
 93     char input;
 94     if (kbhit())  // 判断是否有输入
 95     {
 96         input = getch();  // 根据用户的不同输入来移动,不必输入回车
 97         if (input == '1')
 98             gameStatus = 1;
 99         else if (input == '2')
100         {
101             writeRecordFile();
102             gameStatus = 1;
103         }
104         else if (input == '3')
105         {
106             gameStatus = 2;
107             exit(0);
108         }
109     }
110 }
111 
112 void readRecordFile()  //读取游戏数据文件存档
113 {
114     FILE* fp;
115     fp = fopen(".\\gameRecord.dat", "r");
116     fscanf(fp, "%f %f %f %f %f %f %d %d", &position_x, &position_y, &bullet_x, &bullet_y, &enemy_x, &enemy_y, &isExpolde, &score);
117     fclose(fp);
118 }
119 
120 void writeRecordFile()  //存储游戏数据文件存档
121 {
122     FILE* fp;
123     fp = fopen(".\\gameRecord.dat", "w");
124     fprintf(fp, "%f %f %f %f %f %f %d %d", position_x, position_y, bullet_x, bullet_y, enemy_x, enemy_y, isExpolde, score);
125     fclose(fp);
126 }
127 
128 void startup()
129 {
130     mciSendString("open .\\game_music.mp3 alias bkmusic", NULL, 0, NULL);//打开背景音乐
131     mciSendString("play bkmusic repeat", NULL, 0, NULL);  // 循环播放
132 
133     initgraph(Width, High);
134     // 获取窗口句柄
135     HWND hwnd = GetHWnd();
136     // 设置窗口标题文字
137     SetWindowText(hwnd, "飞机大战 v1.0");
138 
139     loadimage(&img_bk, ".\\background.jpg");
140     loadimage(&img_planeNormal1, ".\\planeNormal_1.jpg");
141     loadimage(&img_planeNormal2, ".\\planeNormal_2.jpg");
142     loadimage(&img_bullet1, ".\\bullet1.jpg");
143     loadimage(&img_bullet2, ".\\bullet2.jpg");
144     loadimage(&img_enemyPlane1, ".\\enemyPlane1.jpg");
145     loadimage(&img_enemyPlane2, ".\\enemyPlane2.jpg");
146     loadimage(&img_planeExplode1, ".\\planeExplode_1.jpg");
147     loadimage(&img_planeExplode2, ".\\planeExplode_2.jpg");
148 
149     position_x = Width * 0.5;
150     position_y = High * 0.7;
151     bullet_x = position_x;
152     bullet_y = -85;
153     enemy_x = Width * 0.5;
154     enemy_y = 10;
155 
156     BeginBatchDraw();
157 
158     while (gameStatus == 0)
159         startMenu(); // 初始菜单界面
160 }
161 
162 void show()
163 {
164     while (gameStatus == 3)
165         pauseMenu(); // 游戏暂停后菜单界面,一般按ESC键后启动该界面
166 
167     putimage(0, 0, &img_bk);    // 显示背景    
168     if (isExpolde == 0)
169     {
170         putimage(position_x - 50, position_y - 60, &img_planeNormal1, NOTSRCERASE); // 显示正常飞机    
171         putimage(position_x - 50, position_y - 60, &img_planeNormal2, SRCINVERT);
172 
173         putimage(bullet_x - 7, bullet_y, &img_bullet1, NOTSRCERASE); // 显示子弹    
174         putimage(bullet_x - 7, bullet_y, &img_bullet2, SRCINVERT);
175         putimage(enemy_x, enemy_y, &img_enemyPlane1, NOTSRCERASE); // 显示敌机    
176         putimage(enemy_x, enemy_y, &img_enemyPlane2, SRCINVERT);
177     }
178     else
179     {
180         putimage(position_x - 50, position_y - 60, &img_planeExplode1, NOTSRCERASE); // 显示爆炸飞机    
181         putimage(position_x - 50, position_y - 60, &img_planeExplode2, SRCINVERT);
182     }
183 
184     settextcolor(RED);
185     settextstyle(20, 0, _T("黑体"));
186     outtextxy(Width * 0.48, High * 0.95, "得分:");
187     char s[5];
188     sprintf(s, "%d", score);
189     outtextxy(Width * 0.55, High * 0.95, s);
190 
191     FlushBatchDraw();
192     Sleep(2);
193 }
194 
195 void updateWithoutInput()
196 {
197     if (isExpolde == 0)
198     {
199         if (bullet_y > -25)
200             bullet_y = bullet_y - 2;
201 
202         if (enemy_y < High - 25)
203             enemy_y = enemy_y + 0.5;
204         else
205             enemy_y = 10;
206 
207         if (fabs(bullet_x - enemy_x) + fabs(bullet_y - enemy_y) < 80)  // 子弹击中敌机
208         {
209             enemy_x = rand() % Width;
210             enemy_y = -40;
211             bullet_y = -85;
212             mciSendString("stop gemusic", NULL, 0, NULL);   // 先把前面一次的音乐停止
213             mciSendString("close gemusic", NULL, 0, NULL); // 先把前面一次的音乐关闭
214             mciSendString("open .\\gotEnemy.mp3 alias gemusic", NULL, 0, NULL); // 打开跳动音乐
215             mciSendString("play gemusic", NULL, 0, NULL); // 仅播放一次
216             score++;
217 
218             if (score > 0 && score % 5 == 0 && score % 2 != 0)
219             {
220                 mciSendString("stop 5music", NULL, 0, NULL);   // 先把前面一次的音乐停止
221                 mciSendString("close 5music", NULL, 0, NULL); // 先把前面一次的音乐关闭
222                 mciSendString("open .\\5.mp3 alias 5music", NULL, 0, NULL); // 打开跳动音乐
223                 mciSendString("play 5music", NULL, 0, NULL); // 仅播放一次
224             }
225             if (score % 10 == 0)
226             {
227                 mciSendString("stop 10music", NULL, 0, NULL);   // 先把前面一次的音乐停止
228                 mciSendString("close 10music", NULL, 0, NULL); // 先把前面一次的音乐关闭
229                 mciSendString("open .\\10.mp3 alias 10music", NULL, 0, NULL); // 打开跳动音乐
230                 mciSendString("play 10music", NULL, 0, NULL); // 仅播放一次
231             }
232         }
233 
234         if (fabs(position_x - enemy_x) + fabs(position_y - enemy_y) < 150)  // 敌机击中我们
235         {
236             isExpolde = 1;
237             mciSendString("stop exmusic", NULL, 0, NULL);   // 先把前面一次的音乐停止
238             mciSendString("close exmusic", NULL, 0, NULL); // 先把前面一次的音乐关闭
239             mciSendString("open .\\explode.mp3 alias exmusic", NULL, 0, NULL); // 打开跳动音乐
240             mciSendString("play exmusic", NULL, 0, NULL); // 仅播放一次        
241         }
242     }
243 }
244 
245 void updateWithInput()
246 {
247     if (isExpolde == 0)
248     {
249         MOUSEMSG m;        // 定义鼠标消息
250         while (MouseHit())  //这个函数用于检测当前是否有鼠标消息
251         {
252             m = GetMouseMsg();
253             if (m.uMsg == WM_MOUSEMOVE)
254             {
255                 // 飞机的位置等于鼠标所在的位置
256                 position_x = m.x;
257                 position_y = m.y;
258             }
259             else if (m.uMsg == WM_LBUTTONDOWN)
260             {
261                 // 按下鼠标左键,发射子弹
262                 bullet_x = position_x;
263                 bullet_y = position_y - 85;
264                 mciSendString("stop fgmusic", NULL, 0, NULL);   // 先把前面一次的音乐停止
265                 mciSendString("close fgmusic", NULL, 0, NULL); // 先把前面一次的音乐关闭
266                 mciSendString("open .\\f_gun.mp3 alias fgmusic", NULL, 0, NULL); // 打开跳动音乐
267                 mciSendString("play fgmusic", NULL, 0, NULL); // 仅播放一次
268             }
269         }
270     }
271 
272     char input;
273     if (kbhit())  // 判断是否有输入
274     {
275         input = getch();  // 根据用户的不同输入来移动,不必输入回车
276         if (input == 27) // ESC键的ACSII码为27
277         {
278             gameStatus = 3;
279         }
280     }
281 }
282 
283 void gameover()
284 {
285     EndBatchDraw();
286     getch();
287     closegraph();
288 }
289 
290 int main()
291 {
292     startup();  // 数据初始化    
293     while (1)  //  游戏循环执行
294     {
295         show();  // 显示画面
296         updateWithoutInput();  // 与用户输入无关的更新
297         updateWithInput();     // 与用户输入有关的更新
298     }
299     gameover();     // 游戏结束、后续处理
300     return 0;
301 }
6.4飞机大战(省级版)

       7.1可视化汉诺塔

  1 #pragma warning(disable:4996);
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include <ctime>
  5 #include <windows.h>
  6 void move(char x, char y, int n, int** p);
  7 void hanoi(int n, char one, char two, char three, int** p);
  8 void changeshuzu(char x, char y, int n, int** p);
  9 void changehigh(char x, char y); // 改变塔高
 10 void print(int** p); // 输出起始塔
 11 void printstar(int** p); // 输出*
 12 void gotoxy(int x, int y);  // 光标移动到(x,y)位置
 13 
 14 static int higha, highb, highc, r, c;
 15 int main()
 16 {
 17     int i;
 18     int** p;
 19     printf("input a number:");
 20     scanf("%d", &r);
 21     c = r * 10;
 22     p = new int* [r]; // 动态分配二维数组
 23     p[0] = new int[r * c];
 24     for (i = 1; i < r; i++) // 动态分配二维数组
 25         p[i] = p[i - 1] + c;
 26     higha = r;
 27     highb = 0;
 28     highc = 0;
 29 
 30     printf("the step to move %d diskes:\n\n", r);
 31     printstar(p);
 32     gotoxy(0, 1);
 33     getchar();
 34     hanoi(r, 'A', 'B', 'C', p);
 35     return 0;
 36 }
 37 
 38 void hanoi(int n, char one, char two, char three, int** p)
 39 {
 40     if (n == 1)
 41         move(one, three, n, p);
 42     else
 43     {
 44         hanoi(n - 1, one, three, two, p);
 45         move(one, three, n, p);
 46         hanoi(n - 1, two, one, three, p);
 47     }
 48 }
 49 
 50 void move(char x, char y, int n, int** p) // move x:被移柱子 y:得到盘的柱子 n:盘的大小
 51 {
 52     getchar();
 53     printf("  %c->%c\n", x, y);
 54     changeshuzu(x, y, n, p); // 改变数组
 55     print(p);
 56     changehigh(x, y); // 变高
 57     gotoxy(0, 1);
 58 }
 59 
 60 void print(int** p)
 61 {
 62     int i, j;
 63     for (i = 0;i < r;i++)
 64     {
 65         for (j = 0;j < c;j++)
 66         {
 67             if (p[i][j] == 1)
 68                 printf("*");
 69             else printf(" ");
 70         }
 71         printf("\n");
 72     }
 73 }
 74 void changehigh(char x, char y)
 75 {
 76     switch (x)
 77     {
 78     case 'A':higha--;break;
 79     case 'B':highb--;break;
 80     case 'C':highc--;break;
 81     }
 82     switch (y)
 83     {
 84     case 'A':higha++;break;
 85     case 'B':highb++;break;
 86     case 'C':highc++;break;
 87     }
 88 }
 89 
 90 void changeshuzu(char x, char y, int n, int** p)
 91 {
 92     int i, j;
 93 
 94     // 移去 m-high为要去掉的行数
 95     if (x == 'A')
 96     {
 97         for (i = 0;i < r;i++)
 98             for (j = 0;j < c;j++)
 99             {
100                 if (i == r - higha && j >= r - n && j <= r + n - 2)
101                     p[i][j] = 0;
102             }
103     }
104     else if (x == 'B')
105     {
106         for (i = 0;i < r;i++)
107             for (j = 0;j < c;j++)
108             {
109                 if (i == r - highb && j >= 3 * r - n && j <= 3 * r + n - 2)
110                     p[i][j] = 0;
111             }
112     }
113     else if (x == 'C')
114     {
115         for (i = 0;i < r;i++)
116             for (j = 0;j < c;j++)
117             {
118                 if (i == r - highc && j >= 5 * r - n && j <= 5 * r + n - 2)
119                     p[i][j] = 0;
120             }
121     }
122 
123     // 添加 m-high-1为要去掉的行数
124     if (y == 'A')
125     {
126         for (i = 0;i < r;i++)
127             for (j = 0;j < c;j++)
128             {
129                 if (i == r - higha - 1 && j >= r - n && j <= r + n - 2)
130                     p[i][j] = 1;
131             }
132     }
133     else if (y == 'B')
134     {
135         for (i = 0;i < r;i++)
136             for (j = 0;j < c;j++)
137             {
138                 if (i == r - highb - 1 && j >= 3 * r - n && j <= 3 * r + n - 2)
139                     p[i][j] = 1;
140             }
141     }
142     else if (y == 'C')
143     {
144         for (i = 0;i < r;i++)
145             for (j = 0;j < c;j++)
146             {
147                 if (i == r - highc - 1 && j >= 5 * r - n && j <= 5 * r + n - 2)
148                     p[i][j] = 1;
149             }
150     }
151 }
152 
153 void printstar(int** p)
154 {
155     int i, j;
156     for (i = 0;i < r;i++)
157     {
158         for (j = 0;j < c;j++)
159         {
160             if (j >= r - i - 1 && j <= r + i - 1)
161                 p[i][j] = 1;
162         }
163     }
164     for (i = 0;i < r;i++)
165     {
166         for (j = 0;j < c;j++)
167         {
168             if (p[i][j] == 1)
169                 printf("*");
170             else printf(" ");
171         }
172         printf("\n");
173     }
174 }
175 
176 void gotoxy(int x, int y)    // 光标移动到(x,y)位置
177 {
178     CONSOLE_SCREEN_BUFFER_INFO    csbiInfo;
179     HANDLE    hConsoleOut;
180     hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
181     GetConsoleScreenBufferInfo(hConsoleOut, &csbiInfo);
182     csbiInfo.dwCursorPosition.X = x;
183     csbiInfo.dwCursorPosition.Y = y;
184     SetConsoleCursorPosition(hConsoleOut, csbiInfo.dwCursorPosition);
185 }
7.1可视化汉诺塔

  我发现这边书作者还写了另外一本相似的书,里面有用链表实现祖玛游戏的方法,也是对这本书课后题的回答

  1 #include <graphics.h>  
  2 #include <conio.h>
  3 #include <time.h>
  4 #include <vector>
  5 #include <algorithm>
  6 #pragma comment(lib,"Winmm.lib")
  7 using namespace std;
  8 #define  WIDTH 1000 // 窗口宽度
  9 #define  HEIGHT 700 // 窗口高度
 10 #define  Radius 25 //  小球半径
 11 #define  ColorNum 5 //  小球颜色种类数目
 12 COLORREF  colors[ColorNum] = {RED,BLUE,GREEN,YELLOW,MAGENTA}; // 定义数组保存所有的颜色
 13 
 14 // 求两点之间的距离函数
 15 float Distance(float x1,float y1,float x2,float y2)
 16 {
 17     float xd = x1 - x2;
 18     float yd = y1 - y2;
 19     float length = sqrt(xd*xd+yd*yd); 
 20     return length;
 21 }
 22 
 23 void sleep(DWORD ms)  // 精确延时函数
 24 {
 25     static DWORD oldtime = GetTickCount();
 26     while(GetTickCount() - oldtime < ms)
 27         Sleep(1);
 28     oldtime = GetTickCount();
 29 }
 30 
 31 void PlayMusicOnce(TCHAR fileName[80]) // 播放一次音乐函数
 32 {
 33     TCHAR cmdString1[50];
 34     _stprintf(cmdString1, _T("open %s alias tmpmusic"), fileName); // 生成命令字符串
 35     mciSendString(_T("close tmpmusic"), NULL, 0, NULL); // 先把前面一次的音乐关闭
 36     mciSendString(cmdString1, NULL, 0, NULL); // 打开音乐
 37     mciSendString(_T("play tmpmusic"), NULL, 0, NULL); // 仅播放一次
 38 }
 39 
 40 class Point // 定义顶点类
 41 {
 42 public:
 43     float x,y; // 记录(x,y)坐标
 44     Point() // 无参数的构造函数
 45     {
 46     }
 47     Point (float ix,float iy) // 有参数的构造函数
 48     {
 49         x = ix;
 50         y = iy;
 51     }
 52 };
 53 
 54 class Path  // 定义轨迹类
 55 {
 56 public:
 57     vector<Point> keyPoints; //  记录轨迹上的一些关键点,关键点之间以直线相连
 58     float sampleInterval; // 对特征点连成的轨迹线,进行均匀采样的间隔
 59     vector<Point> allPoints; //  所有以采样间隔得到的采样点
 60 
 61     void getAllPoints() // 以采样间隔进行采样,得到所有的采样点
 62     {
 63         int i;
 64         // 对关键点依次连接形成的多条线段进行遍历
 65         for (i=0;i<keyPoints.size()-1;i++)
 66         {
 67             float xd = keyPoints[i+1].x - keyPoints[i].x;
 68             float yd = keyPoints[i+1].y - keyPoints[i].y;
 69             float length = sqrt(xd*xd+yd*yd); // 这一段直线段的长度
 70 
 71             int num = length/sampleInterval; // 这一段直线段要被采样的个数
 72             for (int j=0;j<num;j++)
 73             {
 74                 float x_sample = keyPoints[i].x + j*xd/num;
 75                 float y_sample = keyPoints[i].y + j*yd/num;
 76                 allPoints.push_back(Point(x_sample,y_sample)); // 添加进去所有的采样点
 77             }
 78         }
 79         // 还有最后一个关键点
 80         allPoints.push_back(Point(keyPoints[i].x,keyPoints[i].y));
 81     }
 82 
 83     void draw() // 画出轨迹
 84     {
 85         setlinecolor(RGB(0,0,0)); // 设置线条颜色
 86         setfillcolor(RGB(0,0,0)); // 设置填充颜色
 87         // 画出关键点依次连接形成的多条线段
 88         for (int i=0;i<keyPoints.size()-1;i++)
 89             line(keyPoints[i].x,keyPoints[i].y,keyPoints[i+1].x,keyPoints[i+1].y); 
 90         // 所有采样点处,分别画一个小圆点
 91         for (int i=0;i<allPoints.size();i++)
 92             fillcircle(allPoints[i].x,allPoints[i].y,2); 
 93     }
 94 
 95     ~Path()  // 析构函数
 96     {
 97         keyPoints.clear(); // 清除vector的内存空间
 98         allPoints.clear();
 99     }
100 };
101 
102 class Ball // 定义小球类
103 {
104 public:    
105     Point center; // 圆心坐标
106     float radius; // 半径
107     int colorId;  // 小球的颜色序号,具体颜色在colors数组中取
108     int indexInPath; // 小球位置在Path的allPoints中的对应序号
109     int direction; // 小球移动方向,1向终点,-1向起点,0暂停
110 
111     void draw() // 画出小球
112     {
113         setlinecolor(colors[colorId]);
114         setfillcolor(colors[colorId]);
115         fillcircle(center.x,center.y,radius); 
116     }
117 
118     void movetoIndexInPath(Path path)
119     {
120         // 让小球移动到 Path的allPoints中的indexInPath序号位置
121         center = path.allPoints[indexInPath];
122     }
123 
124     void initiate(Path path) // 初始化小球到path最开始的位置上
125     {
126         radius = Radius; //  半径
127         indexInPath = 0; // 初始化序号
128         direction = 0; // 初始静止
129         movetoIndexInPath(path); // 移动到Path上面的对应序号采样点位置        
130         colorId = rand() % ColorNum; // 随机颜色序号
131     }
132 
133     // 让小球沿着轨迹Path移动,注意不要越界
134     // direction为0暂时不动,direction为1向着终点移动,direction为-1向着起点移动
135     void changeIndexbyDirection(Path path) 
136     {
137         if (direction==1 && indexInPath+1<path.allPoints.size())
138             indexInPath++;
139         else if (direction==-1 && indexInPath-1>=0)
140             indexInPath--;
141     }
142 };
143 
144 class Cannon // 炮台类,包括角色图片,还有一个小球
145 {
146 public:
147     IMAGE im; // 角色图片
148     IMAGE im_rotate; // 角色旋转后的图片
149     float x,y; // 中心坐标
150     Ball ball;  // 一个可以绕着中心旋转,变颜色的小球
151     float angle; // 旋转角度
152 
153     void draw() // 一些绘制函数
154     {
155         rotateimage(&im_rotate,&im,angle,RGB(160,211,255),false,false);//旋转角色图片
156         putimage(x-im.getwidth()/2,y-im.getheight()/2,&im_rotate); // 显示旋转后角色图片
157         ball.draw(); // 绘制这个待发射的小球
158     }
159 
160     void setBallPosition() // 生成炮台小球的坐标
161     {
162         ball.center.x = x + 100 * cos(angle);
163         ball.center.y = y + 100 * sin(angle);
164     }    
165 
166     void updateWithMouseMOVE(int mx,int my) // 根据鼠标的移动位置来更新
167     {
168         // 求出炮台到鼠标的角度
169         float xs = mx - x;
170         float ys = my - y;
171         float length = sqrt(xs*xs+ys*ys);
172         if (length>4) // 鼠标距离中心位置过近,不处理
173         {
174             angle = atan2(-ys,xs); // 求出炮台旋转角度
175 
176             // 也顺便求出炮台附带的球的位置
177             ball.center.x = x + 100 * xs/length;
178             ball.center.y = y + 100 * ys/length;
179         }
180     }    
181 
182     void updateWithRButtonDown() // 当鼠标右键点击时,改变小球的颜色
183     {
184         // 更改炮台要发射的小球的颜色
185         ball.colorId +=1;
186         if (ball.colorId==ColorNum)
187             ball.colorId =0;
188     }
189 };
190 
191 // 在Balls中序号i位置球,寻找其前后有没有和他颜色一样,且多余个连续靠近的球
192 // 如果有的话,就删除掉,返回的结果是删除掉的小球的个数
193 // 如果一个没有删除,就返回0
194 int eraseSameColorBalls(int i,Ball fireball,Path &path,vector <Ball> &balls)
195 {
196     // 记录下前后和插入的小球颜色一样的序号,后面去重复,得到对应的要删除的序号
197     vector<int> sameColorIndexes;  
198     int forward = i; 
199     int backward = i; 
200     sameColorIndexes.push_back(i); // 首先把i添加到vector中
201 
202     // 向Path终点方向寻找,也就是向最开始加入的球方向寻找
203     while(forward>0 &&  balls[forward].colorId==fireball.colorId)
204     {
205         sameColorIndexes.push_back(forward);
206         if (balls[forward-1].indexInPath - balls[forward].indexInPath>2*Radius/path.sampleInterval)
207             break; // 前面一个球和这个球间距过大,跳出循环
208         forward--;
209     }
210     if (forward==0 && balls[0].colorId==fireball.colorId) // 处理特殊情况,最接近终点的那个球
211         sameColorIndexes.push_back(forward);
212 
213     // 向Path起点方向寻找,也就是最后加入的球的方向寻找
214     while (backward<balls.size()-1 && balls[backward].colorId==fireball.colorId) // 还没有找到最后一个加入的球
215     {
216         sameColorIndexes.push_back(backward);
217         if (balls[backward].indexInPath - balls[backward+1].indexInPath>2*Radius/path.sampleInterval)
218             break; // 前面一个球和这个球间距过大,跳出循环
219         backward++;
220     }
221     if (backward==balls.size()-1 && balls[balls.size()-1].colorId==fireball.colorId) // 处理特殊情况,最接近起点的那个球
222         sameColorIndexes.push_back(backward);
223 
224     // 去除同样颜色小球中重复的序号
225     sort(sameColorIndexes.begin(), sameColorIndexes.end());
226     vector<int>::iterator ite = unique(sameColorIndexes.begin(), sameColorIndexes.end());
227     sameColorIndexes.erase(ite, sameColorIndexes.end());
228 
229     int NumSameColors = sameColorIndexes.size();
230     if (NumSameColors>=3) // 相同颜色的球达到3个或以上
231     {
232         int minIndex = sameColorIndexes[0];
233         int maxIndex = sameColorIndexes[NumSameColors-1];
234         // 把这些球给删掉                        
235         balls.erase(balls.begin()+minIndex,balls.begin()+maxIndex+1);
236         return NumSameColors; // 消除了,返回消除小球数目
237     }
238     return 0; // 没有消除,返回0
239 }
240 
241 // 以下定义一些全局变量
242 Path path; // 定义轨迹对象
243 vector <Ball> balls; // 记录多个小球
244 IMAGE im_role,im_house,im_bk; // 一些图片
245 Cannon cannon;  // 定义炮台对象
246 int gameStatus = 0;  // 游戏状态,-1失败,0正常,1胜利
247 
248 void startup()  // 初始化函数
249 {    
250     mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);//打开背景音乐
251     mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);  // 循环播放
252     srand(time(0)); // 随机初始化种子
253     initgraph(WIDTH,HEIGHT); // 新开一个画面
254     cleardevice(); // 清屏
255     loadimage(&im_bk, _T("bk.jpg")); // 导入背景图片
256     loadimage(&im_role, _T("role.jpg")); // 导入角色图片
257     loadimage(&im_house, _T("house.jpg")); // 导入家图片
258 
259     // 为轨迹类添加一些关键点
260     path.keyPoints.push_back(Point(50, 300));
261     path.keyPoints.push_back(Point(50, 600));
262     path.keyPoints.push_back(Point(100, 650));
263     path.keyPoints.push_back(Point(700, 650));
264     path.keyPoints.push_back(Point(700, 550));
265     path.keyPoints.push_back(Point(250, 550));
266     path.keyPoints.push_back(Point(200, 500));
267     path.keyPoints.push_back(Point(200, 200));
268     path.keyPoints.push_back(Point(250, 150));
269     path.keyPoints.push_back(Point(800, 150));
270     path.keyPoints.push_back(Point(850, 200));
271     path.keyPoints.push_back(Point(850, 650));
272     path.keyPoints.push_back(Point(950, 650));
273     path.keyPoints.push_back(Point(950, 100));
274     path.keyPoints.push_back(Point(900, 50));
275     path.keyPoints.push_back(Point(150, 50));
276 
277     path.sampleInterval = Radius/5; // 设置轨迹线的采样间隔,需被Radius整除以便处理
278     path.getAllPoints();    // 获得轨迹上的所有采样点
279 
280     // 炮台做一些初始化
281     cannon.im = im_role; // 炮台角色图片
282     cannon.angle = 0; // 初始角度
283     cannon.x = 500;  // 中心坐标
284     cannon.y = 350;
285     cannon.ball.radius = Radius; // 炮台带的小球的半径
286     cannon.ball.colorId = rand()%ColorNum; // 炮台小球颜色
287     cannon.setBallPosition(); // 设置炮台小球的坐标
288 
289     // 先添加一些小球
290     for (int i=0;i<15;i++)
291     {
292         Ball ball;  // 定义一个小球对象
293         ball.initiate(path); // 初始化小球到path最开始的位置上    
294         balls.push_back(ball); // 把小球ball添加到balls中
295     }
296 
297     BeginBatchDraw(); // 开始批量绘制
298 }
299 
300 void show()  // 绘制函数
301 {
302     putimage(0,0,&im_bk); // 显示背景图片
303     putimage(30,10,&im_house); // 显示房子图片
304     //path.draw();  // 画出轨迹
305     cannon.draw(); // 画出炮台    
306     for (int i=0;i<balls.size();i++)
307         balls[i].draw();  // 画出所有小球
308 
309     // 设置字体显示属性
310     setbkmode(TRANSPARENT);
311     settextcolor(RGB(255,0,0)); 
312     settextstyle(60, 0, _T("宋体"));
313     if (gameStatus==1) // 输出游戏胜利
314         outtextxy(WIDTH*0.35, HEIGHT*0.35, _T("游戏胜利 :)"));
315     else if (gameStatus==-1) // 输出游戏失败
316         outtextxy(WIDTH*0.35, HEIGHT*0.35, _T("游戏失败 :("));
317 
318     FlushBatchDraw(); // 批量绘制
319 }
320 
321 void updateWithoutInput() // 和输入无关的更新
322 {    
323     static clock_t start = clock(); // 记录第一次运行时刻
324     clock_t now = clock(); // 获得当前时刻
325     // 计算程序目前一共运行了多少秒
326     int nowSecond =( int(now - start) / CLOCKS_PER_SEC);
327     // 100秒内,时间每过10秒钟,新增一批小球
328     if (nowSecond%10==0 && nowSecond<=100 && gameStatus==0) 
329     {
330         Ball ball;  // 定义一个小球对象
331         ball.initiate(path); // 初始化小球到path最开始的位置上
332         balls.push_back(ball); // 把小球ball添加到balls中
333     }
334     if (balls.size()==0) // 小球清空完了
335     {
336         if (nowSecond>100) // 时间到了,游戏胜利
337             gameStatus = 1; // 游戏胜利
338         return; // 没有到截止时间,小球清空了,等到到时间后产生新的小球
339     }
340     // 第一个球跑到终点了,游戏失败
341     if (balls[0].indexInPath >= path.allPoints.size()-1)
342     {
343         gameStatus = -1; // 游戏失败
344         return;
345     }
346 
347     int i;    
348     for (i=0;i<balls.size();i++)
349         balls[i].direction = 0; // 先让所有小球的速度设为0
350 
351     //balls向前移动的源动力来自最后面一个小球,最后一个小球direction=1
352     //如果终点方向前面一个小球和这个小球正好相切,则其direction也为1,否则direction为0
353     i = balls.size() - 1; // 最后一个小球
354     balls[i].direction = 1; // 最后一个小球向前运动
355 
356     while (i>0)  // 一直向前判断,还没有遍历到最前面一个小球
357     {
358         // 如果前后两个小球正好相切
359         if (balls[i-1].indexInPath-balls[i].indexInPath <= 2*Radius/path.sampleInterval)
360         {
361             balls[i-1].direction = 1; // 前一个小球的方向也是向前
362             // 对前一个小球的indexInPath进行规则化,确保正好相切
363             balls[i-1].indexInPath = balls[i].indexInPath+2*Radius/path.sampleInterval; 
364             i--;
365         }
366         else // 有一个小球不直接接触,就停止向前速度的传递
367             break; // 跳出循环
368     }
369 
370     for (int i=0;i<balls.size();i++)  // 每一个小球根据其direction更新他的位置
371     {
372         balls[i].movetoIndexInPath(path);
373         balls[i].changeIndexbyDirection(path);
374     }
375 
376     sleep(30); // 暂停若干毫秒
377 }
378 
379 void updateWithInput()  // 和输入相关的更新
380 {
381     if (gameStatus!=0) // 游戏胜利或失败,不需要用户再输入,函数直接返回
382         return;
383 
384     int i,j;
385     MOUSEMSG m;        // 定义鼠标消息
386     while (MouseHit())  // 检测当前是否有鼠标消息
387     {
388         m = GetMouseMsg();
389         if(m.uMsg == WM_MOUSEMOVE)  // 鼠标移动时            
390         {
391             cannon.updateWithMouseMOVE(m.x,m.y); // 炮台旋转,小球也移动到对应位置上
392         }
393         else if(m.uMsg == WM_RBUTTONDOWN)  // 鼠标右键点击时,更改炮台要发射的小球的颜色        
394         {
395             cannon.updateWithRButtonDown();
396         }
397         else if(m.uMsg == WM_LBUTTONDOWN)  // 鼠标左键点击时            
398         {    
399             cannon.updateWithMouseMOVE(m.x,m.y); //  先更新下炮台旋转角度、炮台小球的坐标
400             float vx = (cannon.ball.center.x - cannon.x)/5; // 炮台小球移动速度
401             float vy = (cannon.ball.center.y - cannon.y)/5;            
402             int isCollider = 0; // 假设balls中没有小球和炮台小球碰撞
403             // 沿着发射的方向炮台小球逐步移动,判断有balls有没有小球和炮台小球碰撞
404             while (isCollider==0 && cannon.ball.center.y>0 && cannon.ball.center.y < HEIGHT
405                 && cannon.ball.center.x>0  && cannon.ball.center.x < WIDTH ) // 炮台小球超出边界就不用处理了
406             {
407                 cannon.ball.center.x += vx; // 更新发射小球的位置
408                 cannon.ball.center.y += vy;
409                 show(); // 显示下炮台小球的运动轨迹
410 
411                 // balls中所有小球和炮台小球坐标判断,看看是否有相交的
412                 for (i=0;i<balls.size();i++)
413                 {
414                     float distance = Distance(balls[i].center.x, balls[i].center.y,cannon.ball.center.x,cannon.ball.center.y); 
415                     if (distance<Radius) // 找到和炮台小球碰撞的小球
416                     {            
417                         isCollider = 1; // 设为找到碰撞小球了                        
418                         cannon.updateWithMouseMOVE(m.x,m.y); // 把炮台小球的位置移动回去
419 
420                         // 下面复制一份小球,插入到这个地方
421                         Ball fireball = balls[i];
422                         fireball.colorId = cannon.ball.colorId; // 将插入小球变成炮台小球的颜色
423                         balls.insert(balls.begin()+i,fireball); // 复制一个小球,插入到vector中
424 
425                         // 在Balls中序号i位置球,寻找其前后有没有和他颜色一样,且多余3个连续靠近的球
426                         // 如果是的话,就删除掉,返回的结果是删除掉的小球的个数
427                         // 如果一个没有删除,就返回0
428                         int count = eraseSameColorBalls(i,fireball,path,balls);
429                         if (count>=3)
430                             PlayMusicOnce(_T("coin.mp3"));  // 播放一次金币音效
431                         if (count==0)// 如果没有消除的话
432                         {
433                             for (j=i;j>=0;j--) // 移动前面的小球,留出空间放下新插入的小球
434                             {
435                                 if (balls[j].indexInPath - balls[j+1].indexInPath <=0)
436                                     balls[j].indexInPath = balls[j+1].indexInPath + 2*Radius/path.sampleInterval;
437                                 else
438                                     break; // 前面小球间有空隙,不用再处理了
439                             }
440                         }
441                         return; // 找到一个和炮台碰撞的小球了,后面的不用再找了
442                     }
443                 }  // for (i=0;i<balls.size();i++)
444             } // 炮台小球逐步移动,和balls数组中所有小球进行判断
445         } // 鼠标左键点击时    
446     } 
447 }
448 
449 void gameover() // 游戏结束时的处理
450 {
451     balls.clear(); // 清除vector的内存空间
452 }
453 
454 int main() // 主函数
455 {
456     startup();  // 初始化    
457     while (1)  // 循环
458     {
459         show(); // 显示
460         updateWithoutInput();  // 和输入无关的更新
461         updateWithInput();  // 和输入相关的更新
462     }
463     gameover(); // 游戏结束时的处理
464     return 0;
465 }
链表实现祖玛游戏

地址为:https://zhuanlan.zhihu.com/p/264970806 

原文地址:https://www.cnblogs.com/sylvia1111/p/15612862.html