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 

  • 相关阅读:
    [javaSE] 数组(获取最值)
    [javascript] Promise简单学习使用
    [javaSE] 基本类型(String相关)
    [android] 手机卫士黑名单功能(ListView结合SQLite增删改)
    [PHP] 重回基础(IO流)
    [PHP] 重回基础(Array相关函数)
    [PHP] 重回基础(date函数和strtotime函数)
    [HTML5] Canvas绘制简单图片
    [javaSE] 集合框架(TreeSet)
    [android] 手机卫士黑名单功能(ListView优化)
  • 原文地址:https://www.cnblogs.com/sylvia1111/p/15612862.html
Copyright © 2011-2022 走看看