• Day16:C++之STL应用篇(推箱子cxk限定)


    成果展示

    一、部分设计思想

    1.用一个资源类去管理资源,采用单例设计模式:

    构造函数私有化

    ②提供一个静态接口(通过类名直接访问资源)

    (资源包括:图片\音乐)

    2.公共需要包含的头文件 common.h

    1. #pragma once
    2. #include<iostream>
    3. #include<map>
    4. #include<array>
    5. #include<stack>
    6. #include<conio.h>
    7. #include<graphics.h>
    8. #include<mmsystem.h>
    9. #pragma comment(lib,"winmm.lib")

    3.静态数据成员要在类外进行初始化。

    4.数据和绘图分离

    5.整个游戏的逻辑由game完成

    /改相似代码小技巧:alt+左键选中需要修改的那一部分,ctrl+h进行替换即可。/

    6.利用栈,(按空格键)实现悔棋(回退)功能

                原理:利用栈,每次修改地图的放置之前,都将原地图存一份到栈中。当按下空格键,优先检测empty()的值(防止越界),然后若栈中的值不为空,则用top()将地图赋给getmap()然后pop删除。

    二、代码部分:

    common.h

            主要包含:需要用到的一些容器的头文件,以及音乐播放、图形库等

    1. #pragma once
    2. #include<iostream>
    3. #include<map>
    4. #include<array>
    5. #include<stack>
    6. #include<string>
    7. #include<conio.h>
    8. #include<graphics.h>
    9. #include<mmsystem.h>
    10. #pragma comment(lib,"winmm.lib")
    11. using namespace std;

     res.h

            主要包含私有构造函数、静态的构造函数的接口、静态映射map容器来管理资源

    1. #pragma once
    2. #include"common.h"
    3. class Res
    4. {
    5. public:
    6. static map<string, IMAGE*> img;
    7. static map<string, string>music;
    8. static void drawIMG(int x, int y, string str);
    9. static Res* getInstance();
    10. ~Res();
    11. private:
    12. Res();
    13. };

     res.cpp

            主要内容:通过构造函数实现对资源之间的映射关系(绑定)

            细节:静态数据成员要在类外进行初始化。

    1. #include "res.h"
    2. /*静态数据成员要在类外初始化*/
    3. map<string, IMAGE*>Res::img;
    4. map<string, string>Res::music;
    5. void Res::drawIMG(int x, int y, string str)
    6. {
    7. putimage(x, y, getInstance()->img[str]);
    8. }
    9. Res* Res::getInstance()
    10. {
    11. static Res* res = new Res;
    12. return res;/*所有的资源都用这一个对象即可*/
    13. }
    14. Res::~Res()
    15. {
    16. delete img["墙"];
    17. delete img["路"];
    18. delete img["球"];
    19. delete img["框"];
    20. delete img["人"];
    21. delete img["鸡"];
    22. }
    23. Res::Res()
    24. {
    25. img["墙"] = new IMAGE;//0
    26. img["路"] = new IMAGE;//0
    27. img["球"] = new IMAGE;//4
    28. img["框"] = new IMAGE;//3
    29. img["人"] = new IMAGE;//5 8
    30. img["鸡"] = new IMAGE;//7
    31. loadimage(img["墙"], "..//res//img//1.bmp");
    32. loadimage(img["路"], "..//res//img//0.bmp");
    33. loadimage(img["球"], "..//res//img//4.bmp");
    34. loadimage(img["框"], "..//res//img//3.bmp");
    35. loadimage(img["人"], "..//res//img//5.bmp");
    36. loadimage(img["鸡"], "..//res//img//7.bmp");
    37. music["背景"] = "res//music//back.mp3";
    38. music["移动"] = "res//music//Boxmove.WAV";
    39. }

     data.h

            主要内容:①数据成员map表示绘制的地图(0\1\3\5分别对应不同的绘制)

                           ②提供修改地图的接口setValue()、getMap()

                           ③searchPos()用来返回角色所在的坐标({行,列})

                           ④三个判断函数isBox()、isMove()、noBall()分别用来判断当前位置是否是box(篮球)、是否空地、以及是否还有球未进框(用来判断游戏是否需要结束)

    1. #pragma once
    2. #include"common.h"
    3. class Data
    4. {
    5. public:
    6. void setValue(int i, int j, int value); //设置地图中的值
    7. pair<int, int>searchPos(); //找任务的坐标
    8. bool isBox(int i, int j);
    9. bool isMove(int i, int j);
    10. bool noBall();
    11. array<array<int, 8>, 8>& getMap();
    12. protected:
    13. array<array<int, 8>, 8> map /*8*8的地图*/
    14. =
    15. {
    16. 1,1,1,1,1,1,1,1,
    17. 1,3,4,0,0,4,3,1,
    18. 1,0,1,1,0,1,1,1,
    19. 1,0,0,0,5,0,0,1,
    20. 1,0,1,1,0,0,1,1,
    21. 1,0,1,0,0,4,0,1,
    22. 1,3,4,0,0,3,0,1,
    23. 1,1,1,0,0,0,1,1
    24. };
    25. };

     data.cpp

            主要内容:相关成员函数的实现过程。

                   细节:①setValue的时候采用+=的算法,就是为了好让人从篮筐中走开时,还原篮筐

                           ②pair键值对的返回类型的写法{ i, j }就已经表示一个pair<int,int>类型

                           ③isbox()在进行逻辑判断的时候,4和7都要算,因为当为7的时候(即球在篮筐上的时候也要保证是可以推的)

                           ④isMove()保证0和3都算,因为篮筐在此我们认为属于空地的范畴                      

    1. #include "data.h"
    2. void Data::setValue(int i, int j, int value)
    3. {
    4. map[i][j] += value;/*注意:这里为何是+=而非=,因为人从篮筐走开,是需要还原篮筐的。*/
    5. }
    6. pair<int, int> Data::searchPos()
    7. {
    8. /*5和8->显示人 ques:为何8也是人,因为人可能是站在篮筐上,此时map中对应的值对应8*/
    9. for (int i = 0; i < 8; i++)
    10. {
    11. for (int j = 0; j < 8; j++)
    12. {
    13. if (map[i][j] == 5 || map[i][j] == 8)
    14. return { i,j };/*这样的写法,是因为容器是可以用列表的方式进行初始化的。*/
    15. }/*小技巧:列表数据可以直接初始化容器*/
    16. }
    17. return {-1,-1};/*没找到就返回-1,-1代表没找到人的位置*/
    18. }
    19. bool Data::isBox(int i, int j)
    20. {/*球=箱子*/
    21. /*当前位置是箱子or箱子在篮筐上*/
    22. if (map[i][j] == 4 || map[i][j] == 7)
    23. return true;
    24. return false;
    25. }
    26. bool Data::isMove(int i, int j)
    27. {
    28. /*判断当前位置是不是空地*/
    29. if (map[i][j] == 0 || map[i][j] == 3) /*0是空地,3是篮筐,可以走,等效于空地*/
    30. return true;
    31. return false;
    32. }
    33. bool Data::noBall()
    34. {
    35. for(int i=0;i<8;i++)
    36. for (int j = 0; j < 8; j++)
    37. {
    38. if (map[i][j] == 4)
    39. return true;/*代表地图中还存在球,游戏还不能结束*/
    40. }
    41. return false;
    42. }
    43. array<array<int, 8>, 8>& Data::getMap()
    44. {
    45. return map;//通过接口访问map
    46. }

     game.h

            主要内容:构造函数、绘制函数、按键捕捉函数、判断游戏结束+

                                    Data类型的指针、一个8*8的栈

                   细节:在头文件中用到的自定义类型,尽量使用前向声明的方式(防止交叉包含)

    1. #pragma once
    2. #include"common.h"
    3. class Data;/*小技巧:头文件中用到类型,尽量用前向声明的方式*/
    4. class Game
    5. {
    6. public:
    7. Game();
    8. void DrawGame();
    9. void KeyDown();
    10. bool GameOver();
    11. protected:
    12. Data* pData;
    13. stack<array<array<int, 8>, 8>>mstack;
    14. };

    game.cpp

            主要内容:①构造函数game()中创建出一个窗口,并播放音乐

                            ②DrawGame()中根据pData中的数据成员中map的值分别对应到绘制相应的图形

                            ③keydown()中主要实现空地移动的逻辑+推球移动的逻辑

                   细节:①构造函数的参数列表的pData直接new出来

    1. #include "game.h"
    2. #include"data.h"/*不要在头文件中互相包含(用前向声明)!!!,尽量在.cpp中互相包含*/
    3. #include"res.h"
    4. Game::Game():pData(new Data)
    5. {
    6. initgraph(64 * 8, 64 * 8);
    7. /*先打开音乐,再播放音乐*/
    8. string open = "open " + Res::getInstance()->music["背景"];
    9. mciSendString(open.c_str(), 0, 0, 0);
    10. string play = "play " + Res::getInstance()->music["背景"];
    11. mciSendString(play.c_str(), 0, 0, 0);
    12. }
    13. void Game::DrawGame()
    14. {
    15. for (int i = 0; i < 8; i++)
    16. {
    17. for (int j = 0; j < 8; j++)
    18. {
    19. int x = 64 * j;
    20. int y = 64 * i;
    21. switch (pData->getMap()[i][j])
    22. {
    23. case 0: /*路*/
    24. putimage(x, y, Res::getInstance()->img["路"]);
    25. break;
    26. case 1: /*墙*/
    27. putimage(x, y, Res::getInstance()->img["墙"]);
    28. break;
    29. case 3: /*框*/
    30. putimage(x, y, Res::getInstance()->img["框"]);
    31. break;
    32. case 4: /*球*/
    33. putimage(x, y, Res::getInstance()->img["球"]);
    34. break;
    35. case 5: /*人*/
    36. case 8:
    37. putimage(x, y, Res::getInstance()->img["人"]);
    38. break;
    39. case 7: /*3+4=7->鸡*/
    40. putimage(x, y, Res::getInstance()->img["鸡"]);
    41. break;
    42. }
    43. }
    44. }
    45. }
    46. void Game::KeyDown()
    47. {
    48. pair<int, int>role = pData->searchPos();/*得到人物的坐标*/
    49. char userkey = _getch();
    50. string close = "close" + Res::getInstance()->music["移动"];
    51. mciSendString(close.c_str(), 0, 0, 0);
    52. /*先打开音乐,再播放音乐,再关闭*/
    53. string open = "open" + Res::getInstance()->music["移动"];
    54. mciSendString(open.c_str(), 0, 0, 0);
    55. string play = "play" + Res::getInstance()->music["移动"]+"wait";
    56. mciSendString(play.c_str(), 0, 0, 0);
    57. /*注:此处是不用while(1)持续画的,只有当按键后,才需要改变*/
    58. switch (userkey)
    59. {
    60. case 'w':
    61. case 'W':
    62. case 72:
    63. if (pData->isMove(role.first - 1, role.second)) //检测行-1的位置(即向上走)是否为空地
    64. {
    65. mstack.push(pData->getMap());
    66. pData->setValue(role.first, role.second ,- 5); //原来的位置要-5,显示空地or篮筐
    67. pData->setValue(role.first - 1, role.second, 5);//w向上移动,+5显示人物
    68. }
    69. if (pData->isBox(role.first - 1, role.second)) /*若行进方向有箱子*/
    70. {
    71. if (pData->isMove(role.first - 2, role.second))
    72. {
    73. mstack.push(pData->getMap());
    74. pData->setValue(role.first, role.second, -5);
    75. pData->setValue(role.first-1, role.second, 1);/*为啥是1:球走-4 人来+5 =1*/
    76. pData->setValue(role.first - 2, role.second, 4);
    77. }
    78. }
    79. break;
    80. case 'S':
    81. case 's':
    82. case 80:
    83. if (pData->isMove(role.first + 1, role.second))
    84. {
    85. mstack.push(pData->getMap());
    86. pData->setValue(role.first, role.second, -5); //原来的位置要-5,显示空地or篮筐
    87. pData->setValue(role.first +1, role.second, 5);//S向down移动,+5显示人物
    88. }
    89. if (pData->isBox(role.first + 1, role.second))
    90. {
    91. if (pData->isMove(role.first + 2, role.second))
    92. {
    93. mstack.push(pData->getMap());
    94. pData->setValue(role.first, role.second, -5);
    95. pData->setValue(role.first + 1, role.second, 1);/*为啥是1,球走-4 人来+5 =1*/
    96. pData->setValue(role.first + 2, role.second, 4);
    97. }
    98. } /*改相似代码小技巧:alt+左键选中需要修改的那一部分,ctrl+h进行替换即可。*/
    99. break;
    100. case 'a':
    101. case 'A':
    102. case 75:
    103. if (pData->isMove(role.first , role.second-1))
    104. {
    105. mstack.push(pData->getMap());
    106. pData->setValue(role.first, role.second, -5); //原来的位置要-5,显示空地or篮筐
    107. pData->setValue(role.first , role.second-1, 5);//a向left移动,+5显示人物
    108. }
    109. if (pData->isBox(role.first, role.second-1))
    110. {
    111. if (pData->isMove(role.first , role.second-2))
    112. {
    113. mstack.push(pData->getMap());
    114. pData->setValue(role.first, role.second, -5);
    115. pData->setValue(role.first , role.second-1, 1);/*为啥是1,球走-4 人来+5 =1*/
    116. pData->setValue(role.first , role.second-2, 4);
    117. }
    118. }
    119. break;
    120. case 'd':
    121. case 'D':
    122. case 77:
    123. if (pData->isMove(role.first , role.second+1))
    124. {
    125. mstack.push(pData->getMap());
    126. pData->setValue(role.first, role.second, -5); //原来的位置要-5,显示空地or篮筐
    127. pData->setValue(role.first , role.second+1, 5);//d向right移动,+5显示人物
    128. }
    129. if (pData->isBox(role.first , role.second+1))
    130. {
    131. if (pData->isMove(role.first , role.second+2))
    132. {
    133. mstack.push(pData->getMap());
    134. pData->setValue(role.first, role.second, -5);
    135. pData->setValue(role.first , role.second+1, 1);/*为啥是1,球走-4 人来+5 =1*/
    136. pData->setValue(role.first , role.second+2, 4);
    137. }
    138. }
    139. break;
    140. case ' ': /*当空格键被按下*/
    141. if (!mstack.empty())
    142. {
    143. pData->getMap() = mstack.top();
    144. mstack.pop();
    145. }
    146. }
    147. }
    148. bool Game::GameOver()
    149. {
    150. return pData->noBall();
    151. }

     box.cpp(main)

    1. #include"game.h"
    2. int main()
    3. {
    4. Game game;
    5. game.DrawGame();
    6. while (1)
    7. {
    8. game.DrawGame();
    9. if (!game.GameOver())
    10. break;
    11. game.KeyDown();
    12. }
    13. cout << "恭喜你,通关了!" << endl;
    14. return 0;
    15. }

     三、写在最后

            巩固了stl容器部分的知识.

                    完整素材见个人主页的资源部分自行下载。

  • 相关阅读:
    为什么我们需要企业架构?
    websocket多实例推送解决方案-数据实时展示
    0动态规划中等 LeetCode1014. 最佳观光组合
    springboot监控
    巨细胞病毒CMV感染检测,试剂盒怎么选?
    初识 PyIntObject 对象
    智慧教育:数字化时代的未来教育模式
    DataTable 几个扩展小功能
    ssm+mysql实现进销存系统
    【Jetpack】Navigation 导航组件 ① ( Navigation 引入 | Navigation 特点 | Navigation 重要组件 | Navigation 使用流程 )
  • 原文地址:https://blog.csdn.net/zjjaibc/article/details/125452894