zoukankan      html  css  js  c++  java
  • 《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 

  • 相关阅读:
    桟错误分析方法
    gstreamer调试命令
    sqlite的事务和锁,很透彻的讲解 【转】
    严重: Exception starting filter struts2 java.lang.NullPointerException (转载)
    eclipse 快捷键
    POJ 1099 Square Ice
    HDU 1013 Digital Roots
    HDU 1087 Super Jumping! Jumping! Jumping!(动态规划)
    HDU 1159 Common Subsequence
    HDU 1069 Monkey and Banana(动态规划)
  • 原文地址:https://www.cnblogs.com/sylvia1111/p/15612862.html
Copyright © 2011-2022 走看看