《C语言课程设计与游戏开发实践课程》6-7章总结
目录
一、知识点总结
第六章:指针、字符串、结构体、文件在游戏中的应用
二、代码实践
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; 95 (4)对打击力,打击距离内,有一个打击力,然后有个小扰动(具体也不知道为什么,可能是为了效果更加真实,因为我们没有计算小球间作用力) 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; 99 (5)扰动力,和吸引力一样,改一下加速度就行 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.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.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 }
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 }
我发现这边书作者还写了另外一本相似的书,里面有用链表实现祖玛游戏的方法,也是对这本书课后题的回答
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