├── .gitattributes ├── .gitignore ├── AISnake ├── SnakeAI.sln └── SnakeAI │ ├── FindPathBFS.cpp │ ├── FindPathBFS.h │ ├── GameMain.cpp │ ├── SnakeAI.vcxproj │ └── SnakeAI.vcxproj.filters └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /AISnake/SnakeAI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SnakeAI", "SnakeAI\SnakeAI.vcxproj", "{58BA182E-1423-4DE4-9821-4478CCEF3588}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Debug|x64.ActiveCfg = Debug|x64 17 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Debug|x64.Build.0 = Debug|x64 18 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Debug|x86.ActiveCfg = Debug|Win32 19 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Debug|x86.Build.0 = Debug|Win32 20 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Release|x64.ActiveCfg = Release|x64 21 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Release|x64.Build.0 = Release|x64 22 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Release|x86.ActiveCfg = Release|Win32 23 | {58BA182E-1423-4DE4-9821-4478CCEF3588}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8C17551D-664A-44C7-BFB3-D819909E1858} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /AISnake/SnakeAI/FindPathBFS.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengfaheng/AISnakeGame/ceef2e8ff424c23496ac0763ff1c537ebe1e17d8/AISnake/SnakeAI/FindPathBFS.cpp -------------------------------------------------------------------------------- /AISnake/SnakeAI/FindPathBFS.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengfaheng/AISnakeGame/ceef2e8ff424c23496ac0763ff1c537ebe1e17d8/AISnake/SnakeAI/FindPathBFS.h -------------------------------------------------------------------------------- /AISnake/SnakeAI/GameMain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "FindPathBFS.h" 10 | using namespace std; 11 | 12 | int dir[4][2] = { { 0,1 },{ 0,-1 },{ 1,0 },{ -1,0 } }; 13 | 14 | #define UP 1 15 | #define DOWN 2 16 | #define LEFT 3 17 | #define RIGHT 4 18 | #define HEAD 0 19 | 20 | int speed = 0; 21 | //将光标移动到x,y位置 22 | void gotoxy(int x, int y) 23 | { 24 | COORD c; 25 | c.X = x; c.Y = y; 26 | SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); 27 | } 28 | //设置颜色 用到两个Windows API 不做详细介绍 29 | void setColor(unsigned short ForeColor = 7, unsigned short BackGroundColor = 0) 30 | { 31 | HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取当前窗口句柄 32 | SetConsoleTextAttribute(handle, ForeColor + BackGroundColor * 0x10);//设置颜色 33 | } 34 | //游戏设置相关模块,把函数都放到一个类里面了。函数定义为static静态成员,不生成实体也可以直接调用 35 | class GameSetting 36 | { 37 | public: 38 | //游戏窗口的长宽 39 | static const int window_height = 40; 40 | static const int window_width = 80; 41 | public: 42 | static void GameInit() 43 | { 44 | //设置游戏窗口大小 45 | char buffer[32]; 46 | sprintf_s(buffer, "mode con cols=%d lines=%d",window_width, window_height); 47 | system(buffer); 48 | 49 | //隐藏光标 50 | HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); 51 | CONSOLE_CURSOR_INFO CursorInfo; 52 | GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 53 | CursorInfo.bVisible = false; //隐藏控制台光标 54 | SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态 55 | //初始化随机数种子 56 | srand((unsigned int)time(0)); 57 | } 58 | }; 59 | //打印信息类,打印相关信息:欢迎,分数,说明,结束等等 60 | class PrintInfo 61 | { 62 | public: 63 | //选择模式:手动? AI? 64 | static void DrawChoiceInfo() 65 | { 66 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 - 5); 67 | cout << "请选择游戏模式:" << endl; 68 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 - 3); 69 | cout << "1. 手动操作模式" << endl; 70 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 - 1); 71 | cout << "2. AI智能模式" << endl; 72 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 1); 73 | cout << "请输入您的选择-> "; 74 | } 75 | //画地图边界 76 | static void DrawMap() 77 | { 78 | system("cls"); 79 | int i, j; 80 | for (i = 0; i < GameSetting::window_width; i++) 81 | cout << "#"; 82 | cout << endl; 83 | for (i = 0; i < GameSetting::window_height-2; i++) 84 | { 85 | for (j = 0; j < GameSetting::window_width; j++) 86 | { 87 | if (i == 13 && j >= GameSetting::window_width - 29) 88 | { 89 | cout << "#"; 90 | continue; 91 | } 92 | 93 | if (j == 0 || j == GameSetting::window_width - 29 || j == GameSetting::window_width-1) 94 | { 95 | cout << "#"; 96 | } 97 | else 98 | cout << " "; 99 | 100 | } 101 | cout << endl; 102 | } 103 | for (i = 0; i < GameSetting::window_width; i++) 104 | cout << "#"; 105 | 106 | } 107 | //游戏结束 108 | static void GameOver(int score) 109 | { 110 | setColor(12, 0); 111 | gotoxy(GameSetting::window_width / 2 - 20, GameSetting::window_height / 2 - 5); 112 | cout << "游戏结束,您输了!" << endl;; 113 | gotoxy(GameSetting::window_width / 2 - 20, GameSetting::window_height / 2 - 3); 114 | cout << "您的得分为:" << score << endl; 115 | } 116 | //画分数 117 | static void DrawScore(int score) 118 | { 119 | gotoxy(GameSetting::window_width - 22+14, 6); 120 | cout << " "; 121 | gotoxy(GameSetting::window_width - 22+14, 4); 122 | cout << " "; 123 | 124 | gotoxy(GameSetting::window_width - 22, 6); 125 | cout << "当前玩家分数: " << score << endl; 126 | gotoxy(GameSetting::window_width - 22, 4); 127 | cout << "当前游戏速度: " << 10 - speed / 25 << endl; 128 | 129 | } 130 | //画游戏操作说明等 131 | static void DrawGameInfo(bool model) 132 | { 133 | gotoxy(GameSetting::window_width - 22, 8); 134 | if (model) 135 | { 136 | cout << "当前游戏模式: " << "人机" << endl; 137 | } 138 | else 139 | { 140 | cout << "当前游戏模式: " << "AI" << endl; 141 | } 142 | gotoxy(GameSetting::window_width - 22, 10); 143 | cout << "历史最优分数: " << 100 << endl; 144 | 145 | gotoxy(GameSetting::window_width - 22, 18); 146 | cout << "游戏操作说明:" << endl; 147 | gotoxy(GameSetting::window_width - 22, 20); 148 | cout << "W: 上 S: 下" << endl; 149 | gotoxy(GameSetting::window_width - 22, 22); 150 | cout << "A: 左 D: 右" << endl; 151 | 152 | gotoxy(GameSetting::window_width - 22, 24); 153 | cout << "调节游戏速度:" << endl; 154 | gotoxy(GameSetting::window_width - 22, 26); 155 | cout << "小键盘 + : 加快" << endl; 156 | gotoxy(GameSetting::window_width - 22, 28); 157 | cout << "小键盘 - : 减慢" << endl; 158 | 159 | gotoxy(GameSetting::window_width - 22, 32); 160 | cout << "作者: infinitor" << endl; 161 | gotoxy(GameSetting::window_width - 22, 34); 162 | cout << "版本: 1.0" << endl; 163 | } 164 | 165 | }; 166 | //食物类,定义食物的生成等相关操作 167 | class Food 168 | { 169 | private: 170 | //食物坐标 171 | COORDINATE m_coordinate; 172 | public: 173 | //坐标范围: 174 | //x: 1 to GameSetting::window_width - 30 闭区间 175 | //y: 1 to GameSetting::window_height - 2 闭区间 176 | void RandomXY(vector & coord) 177 | { 178 | m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1; 179 | m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1; 180 | unsigned int i; 181 | //原则上不允许食物出现在蛇的位置上,如果有,重新生成 182 | for (i = 0; i < coord.size(); i++) 183 | { 184 | //食物出现在蛇身的位置上。重新生成 185 | if (coord[i].x == m_coordinate.x && coord[i].y == m_coordinate.y) 186 | { 187 | m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1; 188 | m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1; 189 | i = 0; 190 | } 191 | } 192 | } 193 | //默认构造函数 194 | Food() {} 195 | //构造函数,传入参数为蛇身坐标 196 | Food(vector & coord) 197 | { 198 | RandomXY(coord); 199 | } 200 | //画出食物的位置 201 | void DrawFood() 202 | { 203 | setColor(12, 0); 204 | gotoxy(m_coordinate.x, m_coordinate.y); 205 | cout << "@"; 206 | setColor(7, 0); 207 | } 208 | //接口,获取食物位置 209 | COORDINATE GetFoodCoordinate() 210 | { 211 | return m_coordinate; 212 | } 213 | 214 | }; 215 | //贪吃蛇类,定义贪吃蛇的移动,打印,吃食物等等 216 | //地图范围width:2 to width-2 height: 2 to height-2 217 | class Snake 218 | { 219 | private: 220 | bool m_model; //true人机 false AI 221 | int m_direction; 222 | bool m_is_alive; 223 | private: //AI功能相关 224 | bool m_chess[GameSetting::window_width - 29 + 1][GameSetting::window_height]; //AI功能用 225 | FindPathBFS m_AISnake; 226 | COORDINATE map_size; 227 | public://蛇身坐标 228 | vector m_coordinate; 229 | 230 | public://默认构造函数 231 | Snake(bool model = false) : m_model(model) //默认人机模式 232 | { 233 | map_size.x = GameSetting::window_width - 29 + 1; 234 | map_size.y = GameSetting::window_height; 235 | //移动方向向上 236 | m_direction = 1; 237 | m_is_alive = true; 238 | COORDINATE snake_head; 239 | snake_head.x = GameSetting::window_width / 2 - 15; 240 | snake_head.y = GameSetting::window_height / 2; 241 | 242 | this->m_coordinate.push_back(snake_head); 243 | snake_head.y++; 244 | this->m_coordinate.push_back(snake_head); 245 | snake_head.y++; 246 | this->m_coordinate.push_back(snake_head); //初始蛇身长度三节 247 | 248 | //围墙是障碍 249 | for (int i = 0; i < GameSetting::window_width - 29 + 1; i++) 250 | { 251 | m_chess[i][0] = true; 252 | m_chess[i][GameSetting::window_height - 1] = true; 253 | } 254 | 255 | for (int j = 0; j < GameSetting::window_height - 1; j++) 256 | { 257 | m_chess[0][j] = true; 258 | m_chess[GameSetting::window_width - 29][j] = true; 259 | } 260 | 261 | } 262 | //设置游戏模式 263 | void set_model(bool m) { m_model = m; } 264 | //监听键盘 265 | void listen_key_borad() 266 | { 267 | char ch; 268 | 269 | if (_kbhit()) //kbhit 非阻塞函数 270 | { 271 | ch = _getch(); //使用 getch 函数获取键盘输入 272 | switch (ch) 273 | { 274 | case 'w': 275 | case 'W': 276 | if (this->m_direction == DOWN) 277 | break; 278 | this->m_direction = UP; 279 | break; 280 | case 's': 281 | case 'S': 282 | if (this->m_direction == UP) 283 | break; 284 | this->m_direction = DOWN; 285 | break; 286 | case 'a': 287 | case 'A': 288 | if (this->m_direction == RIGHT) 289 | break; 290 | this->m_direction = LEFT; 291 | break; 292 | case 'd': 293 | case 'D': 294 | if (this->m_direction == LEFT) 295 | break; 296 | this->m_direction = RIGHT; 297 | break; 298 | case '+': 299 | if (speed >= 25) 300 | { 301 | speed -= 25; 302 | } 303 | break; 304 | case '-': 305 | if (speed < 250) 306 | { 307 | speed += 25; 308 | } 309 | break; 310 | } 311 | } 312 | } 313 | //AI功能 314 | void AI_speed() 315 | { 316 | char ch; 317 | 318 | if (_kbhit()) //kbhit 非阻塞函数 319 | { 320 | ch = _getch(); //使用 getch 函数获取键盘输入 321 | switch (ch) 322 | { 323 | case '+': 324 | if (speed >= 25) 325 | { 326 | speed -= 25; 327 | } 328 | break; 329 | case '-': 330 | if (speed < 250) 331 | { 332 | speed += 25; 333 | } 334 | break; 335 | } 336 | } 337 | } 338 | //AI功能 339 | void AI_find_path(Food &f) 340 | { 341 | static int not_found = 1; 342 | COORDINATE fpoint = f.GetFoodCoordinate(); 343 | 344 | for (unsigned int i = 0; i < m_coordinate.size(); i++) 345 | { 346 | m_chess[m_coordinate[i].x][m_coordinate[i].y] = true; //蛇的身体也是障碍 347 | } 348 | COORDINATE begin_point, end_point; 349 | 350 | begin_point = m_coordinate[HEAD]; 351 | end_point = fpoint; 352 | 353 | m_AISnake.InitMap((bool**)m_chess); 354 | m_AISnake.GetPath(begin_point, end_point); 355 | 356 | } 357 | //检测是否碰到自己 358 | bool self_collision(COORDINATE head) 359 | { 360 | for (unsigned int i = 1; i < m_coordinate.size(); i++) 361 | { 362 | if (head.x == m_coordinate[i].x && head.y == m_coordinate[i].y) 363 | { 364 | return true; 365 | } 366 | } 367 | return false; 368 | } 369 | //AI功能 370 | void AI_move_snake() 371 | { 372 | static int cot = 0; 373 | AI_speed(); 374 | COORDINATE head, temp; 375 | if (!m_AISnake.m_paths_queue.empty()) 376 | { 377 | head = m_AISnake.m_paths_queue.front(); 378 | m_AISnake.m_paths_queue.pop(); 379 | } 380 | else 381 | { 382 | // 下 上 左 右 383 | //int direction[4][2] = { { 0,1 },{ 0,-1 },{ 1,0 },{ -1,0 } }; 384 | //随机走一步,但是不能碰墙或者碰到自己 385 | for (int i = 0; i < 4; i++) 386 | { 387 | int break_num = rand() % 4; 388 | temp = m_coordinate[HEAD]; 389 | temp.x = temp.x + dir[i][0]; 390 | temp.y = temp.y + dir[i][1]; 391 | 392 | if (temp.x <= 0 || 393 | (temp.x >= (map_size.x - 1)) || 394 | temp.y <= 0 || 395 | (temp.y >= (map_size.y - 1)) || 396 | self_collision(temp) 397 | ) //路径不通不可走 398 | { 399 | continue; 400 | } 401 | head = temp; 402 | if(break_num == i) 403 | break; 404 | } 405 | } 406 | 407 | m_coordinate.insert(m_coordinate.begin(), head); 408 | } 409 | //移动贪吃蛇 410 | void move_snake() 411 | { 412 | //监听键盘 413 | listen_key_borad(); 414 | //蛇头 415 | COORDINATE head = m_coordinate[0]; 416 | //direction方向:1 上 2 下 3 左 4 右 417 | switch (this->m_direction) 418 | { 419 | case UP: 420 | head.y--; 421 | break; 422 | case DOWN: 423 | head.y++; 424 | break; 425 | case LEFT: 426 | head.x--; 427 | break; 428 | case RIGHT: 429 | head.x++; 430 | break; 431 | } 432 | //插入移动后新的蛇头 433 | m_coordinate.insert(m_coordinate.begin(), head); 434 | } 435 | //判断是否吃到食物 436 | bool is_eat_food(Food & f) 437 | { 438 | //获取食物坐标 439 | COORDINATE food_coordinate = f.GetFoodCoordinate(); 440 | //吃到食物,食物重新生成,不删除蛇尾 441 | if (m_coordinate[HEAD].x == food_coordinate.x && m_coordinate[HEAD].y == food_coordinate.y) 442 | { 443 | f.RandomXY(m_coordinate); 444 | return true; 445 | } 446 | else 447 | { 448 | //没有吃到食物,删除蛇尾 449 | m_coordinate.erase(m_coordinate.end() - 1); 450 | return false; 451 | } 452 | } 453 | //判断贪吃蛇死了没 454 | bool snake_is_alive() 455 | { 456 | if (m_coordinate[HEAD].x <= 0 || 457 | m_coordinate[HEAD].x >= GameSetting::window_width - 29 || 458 | m_coordinate[HEAD].y <= 0 || 459 | m_coordinate[HEAD].y >= GameSetting::window_height - 1) 460 | { 461 | //超出边界 462 | m_is_alive = false; 463 | return m_is_alive; 464 | } 465 | //和自己碰到一起 466 | for (unsigned int i = 1; i < m_coordinate.size(); i++) 467 | { 468 | if (m_coordinate[i].x == m_coordinate[HEAD].x && m_coordinate[i].y == m_coordinate[HEAD].y) 469 | { 470 | m_is_alive = false; 471 | return m_is_alive; 472 | } 473 | } 474 | m_is_alive = true; 475 | 476 | return m_is_alive; 477 | } 478 | //画出贪吃蛇 479 | void draw_snake() 480 | { 481 | //设置颜色为浅绿色 482 | setColor(10, 0); 483 | for (unsigned int i = 0; i < this->m_coordinate.size(); i++) 484 | { 485 | gotoxy(m_coordinate[i].x, m_coordinate[i].y); 486 | cout << "*"; 487 | } 488 | //恢复原来的颜色 489 | setColor(7, 0); 490 | } 491 | //清除屏幕上的贪吃蛇 492 | void ClearSnake() 493 | { 494 | for (unsigned int i = 0; i < m_coordinate.size(); i++) 495 | { 496 | m_chess[m_coordinate[i].x][m_coordinate[i].y] = false; 497 | } 498 | gotoxy(m_coordinate[this->m_coordinate.size()-1].x, m_coordinate[this->m_coordinate.size() - 1].y); 499 | cout << " "; 500 | 501 | } 502 | //获取贪吃蛇的长度 503 | int GetSnakeSize() 504 | { 505 | return m_coordinate.size(); 506 | } 507 | //获取当前游戏模式 508 | bool GetModel() 509 | { 510 | return m_model; 511 | } 512 | }; 513 | 514 | //主函数,组合各种类和资源,进行游戏。 515 | int main() 516 | { 517 | GameSetting setting; 518 | PrintInfo print_info; 519 | Snake snake; 520 | //初始化游戏 521 | setting.GameInit(); 522 | //游戏模式选择 523 | print_info.DrawChoiceInfo(); 524 | 525 | char ch = _getch(); 526 | switch (ch) 527 | { 528 | case '1': 529 | snake.set_model(true); 530 | speed = 50; 531 | break; 532 | case '2': 533 | snake.set_model(false); 534 | break; 535 | default: 536 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3); 537 | cout << "输入错误,Bye!" << endl; 538 | cin.get(); 539 | cin.get(); 540 | return 0; 541 | } 542 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3); 543 | system("pause"); 544 | //画地图 545 | print_info.DrawMap(); 546 | print_info.DrawGameInfo(snake.GetModel()); 547 | //生成食物 548 | Food food(snake.m_coordinate); 549 | //游戏死循环 550 | while (true) 551 | { 552 | //打印成绩 553 | print_info.DrawScore(snake.GetSnakeSize()); 554 | //画出食物 555 | food.DrawFood(); 556 | //清理蛇尾,每次画蛇前必做 557 | snake.ClearSnake(); 558 | //判断是否吃到食物 559 | snake.is_eat_food(food); 560 | //根据用户模式选择不同的调度方式 561 | if (snake.GetModel() == true) 562 | { 563 | snake.move_snake(); 564 | } 565 | else 566 | { 567 | snake.AI_find_path(food); 568 | snake.AI_move_snake(); 569 | } 570 | //画蛇 571 | snake.draw_snake(); 572 | //判断蛇是否还活着 573 | if (!snake.snake_is_alive()) 574 | { 575 | print_info.GameOver(snake.GetSnakeSize()); 576 | break; 577 | } 578 | //控制游戏速度 579 | Sleep(speed); 580 | } 581 | 582 | cin.get(); 583 | cin.get(); 584 | 585 | return 0; 586 | } -------------------------------------------------------------------------------- /AISnake/SnakeAI/SnakeAI.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {58BA182E-1423-4DE4-9821-4478CCEF3588} 24 | SnakeAI 25 | 10.0.17134.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | Level3 84 | Disabled 85 | true 86 | true 87 | 88 | 89 | 90 | 91 | Level3 92 | MaxSpeed 93 | true 94 | true 95 | true 96 | true 97 | 98 | 99 | true 100 | true 101 | 102 | 103 | 104 | 105 | Level3 106 | MaxSpeed 107 | true 108 | true 109 | true 110 | true 111 | 112 | 113 | true 114 | true 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /AISnake/SnakeAI/SnakeAI.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 源文件 20 | 21 | 22 | 源文件 23 | 24 | 25 | 26 | 27 | 头文件 28 | 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++编写的一个AI贪吃蛇 2 | 3 | ## 总体概况 4 | - 开发环境:VIsual Studio 2017 5 | - 开发语言:C++ 和 少许Windows API 6 | - 运行环境:Windows 10 7 | 8 | - 效果如下: 9 | 10 | ![](http://upload-images.jianshu.io/upload_images/10386940-1e76db60f4bf831a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | ![](http://upload-images.jianshu.io/upload_images/10386940-f65452c70ec0af2b.jpg?imageMogr2/auto-orient/strip) 12 | 13 | ## 已知问题 14 | 15 | 目前这货还不能算成品。基本是蛇每移动一次,就进行一次BFS找路。但是不能保证每一次都能找到一条路,找不到路的时候怎么办呢?我们让蛇随机走几步,走几步以后就有可能找到路了。但是这样做带来的后果就是:可能随机走着走着就走进死胡同了,结果只能GG。 16 | 17 | 改进的话:我想到一个点就是,BFS找不到路的时候,不随机走。而是有规律跟着尾巴走,比如做S型运动等。想法是这样,代码待写。 18 | 19 | 20 | ## 下载使用 21 | 22 | 大体情况就是这样,代码是VS2017编译的。大家可以下载后,VS2017的直接打开,其他编译器的,新建一个工程,把.h和.cpp文件拖进去编译即可。 23 | 24 | 具体的代码详解,会在后续发表相关文章的。也会更新在这里。 25 | 26 | ## 完善 27 | 最后就是想不断完善这个小程序,毕竟麻雀虽小五脏俱全。想把各种算法都实现一遍。 28 | 29 | 盲目式搜索: 30 | - BFS 31 | - DFS 32 | 33 | 启发式搜索: 34 | - AStar 35 | - 有序搜索 36 | 37 | 慢慢完成吧。 38 | 39 | 40 | # C++编写贪吃蛇小游戏快速入门 41 | 刚学完C++。一时兴起,就花几天时间手动做了个贪吃蛇,后来觉得不过瘾,于是又加入了AI功能。希望大家Enjoy It. 42 | 43 | # 效果图示 44 | 45 | AI模式演示 46 | 47 | ![](http://upload-images.jianshu.io/upload_images/10386940-1e76db60f4bf831a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 48 | ![](http://upload-images.jianshu.io/upload_images/10386940-f65452c70ec0af2b.jpg?imageMogr2/auto-orient/strip) 49 | 50 | # 整体规划+原理 51 | 52 | ![](http://upload-images.jianshu.io/upload_images/10386940-f8ed50c8f39668c9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 53 | 54 | 大体上可以分为图上所示的几个类。不过……怎么看都有点强行面向对象的味道在里面。。[哭笑][哭笑][哭笑]。不管了……代码写得可能有点凌乱,下面我会为大家一一讲解。 55 | 56 | 整个程序设计的原理就是:主函数死循环,不断刷新打印贪吃蛇和食物。这样每循环一次,就类似电影里面的一帧,最终显示的效果就是蛇会动起来。 57 | 58 | # 01 初始化工作-游戏设置 59 | 游戏设置和相关初始化放在了一个类里面,并进行了静态声明。主要设置了游戏窗口的长和款。并在GameInit()函数里面设置了窗口大小,隐藏光标,初始化随机数种子等。代码如下: 60 | ```C++ 61 | //游戏设置相关模块,把函数都放到一个类里面了。函数定义为static静态成员,不生成实体也可以直接调用 62 | class GameSetting 63 | { 64 | public: 65 | //游戏窗口的长宽 66 | static const int window_height = 40; 67 | static const int window_width = 80; 68 | public: 69 | static void GameInit() 70 | { 71 | //设置游戏窗口大小 72 | char buffer[32]; 73 | sprintf_s(buffer, "mode con cols=%d lines=%d",window_width, window_height); 74 | system(buffer); 75 | 76 | //隐藏光标 77 | HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); 78 | CONSOLE_CURSOR_INFO CursorInfo; 79 | GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 80 | CursorInfo.bVisible = false; //隐藏控制台光标 81 | SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态 82 | //初始化随机数种子 83 | srand((unsigned int)time(0)); 84 | } 85 | }; 86 | ``` 87 | 用到了几个相关的Windows API,本文不做过多介绍,大家百度即可。 88 | 89 | # 02 打印信息类 90 | 该类主要是用来打印一些游戏相关信息的。该类大体如下: 91 | ![image](http://upload-images.jianshu.io/upload_images/10386940-31ef77c3d6f2da28.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 92 | 93 | 下面挑几个重点的来讲: 94 | ## 2.1 画地图边界 95 | 这个函数主要是根据上面所给的游戏窗口长宽来打印地图边界的。其中还划分了几个区域,主要用来放不同的信息的。 96 | ```C++ 97 | //画地图边界 98 | static void DrawMap() 99 | { 100 | system("cls"); 101 | int i, j; 102 | for (i = 0; i < GameSetting::window_width; i++) 103 | cout << "#"; 104 | cout << endl; 105 | for (i = 0; i < GameSetting::window_height-2; i++) 106 | { 107 | for (j = 0; j < GameSetting::window_width; j++) 108 | { 109 | if (i == 13 && j >= GameSetting::window_width - 29) 110 | { 111 | cout << "#"; 112 | continue; 113 | } 114 | 115 | if (j == 0 || j == GameSetting::window_width - 29 || j == GameSetting::window_width-1) 116 | { 117 | cout << "#"; 118 | } 119 | else 120 | cout << " "; 121 | 122 | } 123 | cout << endl; 124 | } 125 | for (i = 0; i < GameSetting::window_width; i++) 126 | cout << "#"; 127 | 128 | } 129 | ``` 130 | 划分区域如下图,#就是边框了: 131 | ![image](http://upload-images.jianshu.io/upload_images/10386940-4992ee3e118c196f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 132 | 133 | ## 2.2 画出分数和模式 134 | 135 | 该函数主要是在右上角画出成绩和游戏模式的,在绘制之前会进行刷新处理。先清除,再重新打印。用到了一个gotoxy()函数。这个函数主要是移动光标到(x, y)坐标处的。关于(x, y)的位置,根据实际情况调整即可。 136 | 137 | ```C++ 138 | //画分数 139 | static void DrawScore(int score) 140 | { 141 | gotoxy(GameSetting::window_width - 22+14, 6); 142 | cout << " "; 143 | gotoxy(GameSetting::window_width - 22+14, 4); 144 | cout << " "; 145 | 146 | gotoxy(GameSetting::window_width - 22, 6); 147 | cout << "当前玩家分数: " << score << endl; 148 | gotoxy(GameSetting::window_width - 22, 4); 149 | cout << "当前游戏速度: " << 10 - speed / 25 << endl; 150 | 151 | } 152 | ``` 153 | 154 | # 03 食物类 155 | 食物类定义了食物的坐标,随机生成规则,和画出食物等一系列操作。其中食物坐标我们用了一个结构体: 156 | ```C++ 157 | typedef struct 158 | { 159 | int x; 160 | int y; 161 | }COORDINATE; 162 | ``` 163 | 该结构体两个成员,分别保存坐标的(x, y)。蛇身的坐标也会用到这个结构体。 164 | 有关食物类的大体如下: 165 | ![image](http://upload-images.jianshu.io/upload_images/10386940-79e8cd6597585790.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 166 | 167 | 下面我们还是挑几个重点来讲。 168 | ## 3.1 随机生成食物 169 | 随机生成食物,**原则上不允许食物出现在蛇身的位置上**,如果有。我们重新生成。注意地图的范围,就是区域左边一块。实际情况根据自身的地图范围来调整食物坐标的范围,注意不要越界。用rand()函数获得随机坐标。代码如下: 170 | ```C++ 171 | void RandomXY(vector & coord) 172 | { 173 | m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1; 174 | m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1; 175 | unsigned int i; 176 | //原则上不允许食物出现在蛇的位置上,如果有,重新生成 177 | for (i = 0; i < coord.size(); i++) 178 | { 179 | //食物出现在蛇身的位置上。重新生成 180 | if (coord[i].x == m_coordinate.x && coord[i].y == m_coordinate.y) 181 | { 182 | m_coordinate.x = rand() % (GameSetting::window_width - 30) + 1; 183 | m_coordinate.y = rand() % (GameSetting::window_height - 2) + 1; 184 | i = 0; 185 | } 186 | } 187 | } 188 | ``` 189 | 然后,在构造函数里面传入蛇身的坐标。即可生成食物。 190 | 191 | ## 3.2 画出食物 192 | 画出食物比较简单了,gotoxy到随机生成的坐标之后,cout就行。我们在这还设置了一个食物颜色为红色。代码如下: 193 | ```C++ 194 | void DrawFood() 195 | { 196 | setColor(12, 0); 197 | gotoxy(m_coordinate.x, m_coordinate.y); 198 | cout << "@"; 199 | setColor(7, 0); 200 | } 201 | ``` 202 | # 04 贪吃蛇类 203 | 定义贪吃蛇的移动,打印,吃食物等等。这节课我们暂时不讨论AI功能,先把手动操作的贪吃蛇做了跑起来,下节课再做AI功能的介绍。该类大体如下: 204 | ![image](http://upload-images.jianshu.io/upload_images/10386940-1624cf64843452f7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 205 | ## 4.1 成员变量 206 | 成员变量m_direction记录每次移动的方向。m_is_alive记录贪吃蛇是否还活着。m_coordinate则是贪吃蛇身体坐标的记录。贪吃蛇是一节一节的,整条蛇必然是由许多节组成的。因此用了一个vector来存储蛇身,每节类型是COORDINATE结构体的。 207 | 208 | ## 4.2 默认构造函数 209 | 默认构造函数Snake()里面主要是做了初始贪吃蛇的生成,以及移动方向的定义等。初始的蛇为3节。在中间位置,向上移动。代码如下: 210 | ```C++ 211 | //移动方向向上 212 | m_direction = 1; 213 | m_is_alive = true; 214 | COORDINATE snake_head; 215 | //蛇头生成位置 216 | snake_head.x = GameSetting::window_width / 2 - 15; 217 | snake_head.y = GameSetting::window_height / 2; 218 | 219 | this->m_coordinate.push_back(snake_head); 220 | snake_head.y++; 221 | this->m_coordinate.push_back(snake_head); 222 | snake_head.y++; 223 | this->m_coordinate.push_back(snake_head); //初始蛇身长度三节 224 | ``` 225 | ## 4.3 监听键盘 226 | 监听键盘用了C里面的一个库函数。_kbhit()非阻塞函数,可以不断监听键盘的情况从而不产生阻塞。有键盘按下的时候,就获取按下的键盘是哪个。然后做出相应的变化,其实是方向的调整。需要注意的是,当我们的蛇往上走的时候,按下方向的键,我们是不做处理的。其它方向一样。还有一个调整游戏速度的,speed是休眠时间,speed越小,速度越快。反之速度越慢。 227 | ```C++ 228 | //监听键盘 229 | void listen_key_borad() 230 | { 231 | char ch; 232 | 233 | if (_kbhit()) //kbhit 非阻塞函数 234 | { 235 | ch = _getch(); //使用 getch 函数获取键盘输入 236 | switch (ch) 237 | { 238 | case 'w': 239 | case 'W': 240 | if (this->m_direction == DOWN) 241 | break; 242 | this->m_direction = UP; 243 | break; 244 | case 's': 245 | case 'S': 246 | if (this->m_direction == UP) 247 | break; 248 | this->m_direction = DOWN; 249 | break; 250 | case 'a': 251 | case 'A': 252 | if (this->m_direction == RIGHT) 253 | break; 254 | this->m_direction = LEFT; 255 | break; 256 | case 'd': 257 | case 'D': 258 | if (this->m_direction == LEFT) 259 | break; 260 | this->m_direction = RIGHT; 261 | break; 262 | case '+': 263 | if (speed >= 25) 264 | { 265 | speed -= 25; 266 | } 267 | break; 268 | case '-': 269 | if (speed < 250) 270 | { 271 | speed += 25; 272 | } 273 | break; 274 | } 275 | } 276 | } 277 | ``` 278 | ## 4.4 移动贪吃蛇 279 | 移动贪吃蛇,我们用了一个方向变量,在监听键盘的时候获取移动的方向,然后在根据方向移动贪吃蛇的蛇头。这里的移动我们是这样处理的,首先,贪吃蛇每移动一次,需要改变的只有蛇头和蛇尾两节。我们只需要把新的蛇头插进去,最后再画出来就可以了。至于蛇尾,如果我们不删除蛇尾的话,蛇会不断变长的。因此我们的做法是:吃到食物的时候插入蛇头而不删除蛇尾,没有吃到食物的时候插入蛇头同时删除蛇尾。这样就完美搞定了。 280 | ```C++ 281 | //移动贪吃蛇 282 | void move_snake() 283 | { 284 | //监听键盘 285 | listen_key_borad(); 286 | //蛇头 287 | COORDINATE head = m_coordinate[0]; 288 | //direction方向:1 上 2 下 3 左 4 右 289 | switch (this->m_direction) 290 | { 291 | case UP: 292 | head.y--; 293 | break; 294 | case DOWN: 295 | head.y++; 296 | break; 297 | case LEFT: 298 | head.x--; 299 | break; 300 | case RIGHT: 301 | head.x++; 302 | break; 303 | } 304 | //插入移动后新的蛇头。是否删除蛇尾,在后续吃到食物判断那里做 305 | m_coordinate.insert(m_coordinate.begin(), head); 306 | } 307 | ``` 308 | ## 4.5 是否吃到食物 309 | 判断是否吃到食物,就是看看蛇头的坐标等不等于食物的坐标。如果等于,就重新生成食物,不删除蛇尾,蛇变长一节。不等于,就删除蛇尾,蛇长不变。 310 | ```C++ 311 | bool is_eat_food(Food & f) 312 | { 313 | //获取食物坐标 314 | COORDINATE food_coordinate = f.GetFoodCoordinate(); 315 | //吃到食物,食物重新生成,不删除蛇尾 316 | if (m_coordinate[HEAD].x == food_coordinate.x && m_coordinate[HEAD].y == food_coordinate.y) 317 | { 318 | f.RandomXY(m_coordinate); 319 | return true; 320 | } 321 | else 322 | { 323 | //没有吃到食物,删除蛇尾 324 | m_coordinate.erase(m_coordinate.end() - 1); 325 | return false; 326 | } 327 | } 328 | ``` 329 | ## 4.6判断蛇是否还存活 330 | 判断蛇是否GG,主要是看是否超出边界,是否碰到自己身体其他部分。 331 | ```C++ 332 | //判断贪吃蛇死了没 333 | bool snake_is_alive() 334 | { 335 | if (m_coordinate[HEAD].x <= 0 || 336 | m_coordinate[HEAD].x >= GameSetting::window_width - 29 || 337 | m_coordinate[HEAD].y <= 0 || 338 | m_coordinate[HEAD].y >= GameSetting::window_height - 1) 339 | { 340 | //超出边界 341 | m_is_alive = false; 342 | return m_is_alive; 343 | } 344 | //和自己碰到一起 345 | for (unsigned int i = 1; i < m_coordinate.size(); i++) 346 | { 347 | if (m_coordinate[i].x == m_coordinate[HEAD].x && m_coordinate[i].y == m_coordinate[HEAD].y) 348 | { 349 | m_is_alive = false; 350 | return m_is_alive; 351 | } 352 | } 353 | m_is_alive = true; 354 | 355 | return m_is_alive; 356 | } 357 | ``` 358 | ## 4.7 画出贪吃蛇 359 | 画出贪吃蛇比较简单,gotoxy到身体的每一节,然后cout就行。在此之前设置了颜色为浅绿色。 360 | ```C++ 361 | //画出贪吃蛇 362 | void draw_snake() 363 | { 364 | //设置颜色为浅绿色 365 | setColor(10, 0); 366 | for (unsigned int i = 0; i < this->m_coordinate.size(); i++) 367 | { 368 | gotoxy(m_coordinate[i].x, m_coordinate[i].y); 369 | cout << "*"; 370 | } 371 | //恢复原来的颜色 372 | setColor(7, 0); 373 | } 374 | ``` 375 | ## 4.8 清除屏幕上的贪吃蛇 376 | 我们是死循环不断刷新打印贪吃蛇的,因此每移动一次,必然会在屏幕上留下上一次贪吃蛇的痕迹。因此我们每次在画蛇之前,不是添足,而是清理一下上次遗留的蛇身。我们知道,蛇每次移动,变的只有蛇头和蛇尾,因此该函数我们只需要清理蛇尾就行。gotoxy到蛇尾的坐标,cout<<" ";就行。 377 | ```C++ 378 | gotoxy(m_coordinate[this->m_coordinate.size()-1].x, m_coordinate[this->m_coordinate.size() - 1].y); 379 | cout << " "; 380 | ``` 381 | 382 | # 05 主函数,组装我们的游戏 383 | 我们的游戏在主函数里面进行组装。然后开始运行。 384 | 首先我们做游戏相关的初始化和模式选择。 385 | ```C++ 386 | GameSetting setting; 387 | PrintInfo print_info; 388 | Snake snake; 389 | //初始化游戏 390 | setting.GameInit(); 391 | //游戏模式选择 392 | print_info.DrawChoiceInfo(); 393 | 394 | char ch = _getch(); 395 | switch (ch) 396 | { 397 | case '1': 398 | snake.set_model(true); 399 | speed = 50; 400 | break; 401 | case '2': 402 | snake.set_model(false); 403 | break; 404 | default: 405 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3); 406 | cout << "输入错误,Bye!" << endl; 407 | cin.get(); 408 | cin.get(); 409 | return 0; 410 | } 411 | gotoxy(GameSetting::window_width / 2 - 10, GameSetting::window_height / 2 + 3); 412 | system("pause"); 413 | ``` 414 | 然后就是画地图边框,打印游戏相关信息和说明。生成食物了。 415 | ```C++ 416 | //画地图 417 | print_info.DrawMap(); 418 | print_info.DrawGameInfo(snake.GetModel()); 419 | //生成食物 420 | Food food(snake.m_coordinate); 421 | ``` 422 | 最后就是游戏死循环,在死循环里面,我们需要不断移动蛇,画蛇,判断蛇的状态,判断食物的状态,是否吃到食物等等。具体代码: 423 | ```C++ 424 | //游戏死循环 425 | while (true) 426 | { 427 | //打印成绩 428 | print_info.DrawScore(snake.GetSnakeSize()); 429 | //画出食物 430 | food.DrawFood(); 431 | //清理蛇尾,每次画蛇前必做 432 | snake.ClearSnake(); 433 | //判断是否吃到食物 434 | snake.is_eat_food(food); 435 | //根据用户模式选择不同的调度方式 436 | if (snake.GetModel() == true) 437 | { 438 | snake.move_snake(); 439 | } 440 | else 441 | { 442 | snake.AI_find_path(food); 443 | snake.AI_move_snake(); 444 | } 445 | //画蛇 446 | snake.draw_snake(); 447 | //判断蛇是否还活着 448 | if (!snake.snake_is_alive()) 449 | { 450 | print_info.GameOver(snake.GetSnakeSize()); 451 | break; 452 | } 453 | //控制游戏速度 454 | Sleep(speed); 455 | } 456 | ``` 457 | 最终,我们的代码就可以Run起来了。具体效果放在开头了。界面算不上好看,但是整个程序向大家展示了最基本最核心的功能和代码,大家可以在这个基础上开发自己喜欢的各种美丽的界面哦。 458 | 459 | # 06 AI部分和完善 460 | 代码是画了几天间间断断写出来的,水平不算很高,代码也写得乱七八糟的。不过代码会在后期不断优化,尽量做到精简优美。至于AI功能,等下一篇博文写吧。 461 | 462 | # 代码获取 463 | 欲获取代码,请关注我们的微信公众号【程序猿声】,在后台回复:**aisnake**。即可下载。 464 | 465 | ![微信公众号](http://upload-images.jianshu.io/upload_images/10386940-546ac15b9d7add56.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 466 | 467 | 推荐文章:[10分钟教你用Python做个打飞机小游戏超详细教程](https://www.cnblogs.com/infroad/p/9260954.html) 468 | 469 | 推荐文章:[10分钟教你用python下载和拼接微信好友头像图片](https://www.cnblogs.com/infroad/p/9269158.html) 470 | 471 | 推荐文章:[10分钟教你用python一行代码搞点大新闻](https://www.cnblogs.com/infroad/p/9275903.html) 472 | 473 | 推荐文章:[10分钟教你用python打造贪吃蛇超详细教程 474 | ](https://www.cnblogs.com/infroad/p/9241267.html) --------------------------------------------------------------------------------