tangjikede 2020-05-06
今天突发奇想想用C++实现一个贪吃蛇小游戏,无奈C++没有自带的GUI框架,蒟蒻博主也不会用C++做GUI,于是只能在黑乎乎的命令行中完成这个游戏了(qwq)。
贪吃蛇游戏还是比较简单的,就用C++的基础知识和一点个Windows的api就可以开发完成了,这里就稍微讲一下思路吧。
总共是GameInit.cpp,Snake.cpp,Food.cpp,Display.cpp,GameStart.cpp 这5个文件。
GameInit是游戏的初始设置类,负责设置游戏窗口大小,设置光标,初始化随机种子等等初始化工作。
#include"pch.h" #include "GmaeInit.h" #include<Windows.h> #include<random> #include<ctime> #include <conio.h> using namespace std; GameInit::GameInit(int s) : speed(s) { //设置游戏窗口大小 char buffer[32]; sprintf_s(buffer, "mode con cols=%d lines=%d", windowWidth, windowHeight); system(buffer); //隐藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态 //初始化随机数种子 srand((unsigned int)time(0)); }
Snake是控制蛇的类,包括监听鼠标对蛇的方向做出改变,以及在地图上移动蛇两个功能。当然在移动中非常重要的是,判断蛇是否撞到边界,是否撞到自己等等。
这里讲一下移动应该怎么做,首先蛇的身体是用list来存储,移动其实很简单分两种情况:①没吃到食物,那么就是蛇头朝目标方向移动一个作为新的蛇头加入身体list,然后把蛇的尾部删除。②那么如果吃到了食物该怎么办呢?想一想,欸没错就是不把蛇尾删除就是了(相当于增上了一格)。
#include"pch.h" #include"Snake.h" #include"GmaeInit.h" #include<iostream> #include<unordered_set> #include<Windows.h> #include <conio.h> using namespace std; Snake::Snake() { oldDirection = 1; newDirection = -1; body.push_back(point{ 40, 15 }); body.push_back(point{ 39,15 }); body.push_back(point{ 38,15 }); body.push_back(point{ 37, 15 }); body.push_back(point{ 36, 15 }); } //监听键盘 void Snake::listen_key_borad() { char ch; if (_kbhit()) //kbhit 非阻塞函数 { ch = _getch(); //使用 getch 函数获取键盘输入 switch (ch) { case ‘w‘: case ‘W‘: if (oldDirection == 2) break; newDirection = 0; break; case ‘s‘: case ‘S‘: if (oldDirection == 0) break; newDirection = 2; break; case ‘a‘: case ‘A‘: if (oldDirection == 1) break; newDirection = 3; break; case ‘d‘: case ‘D‘: if (oldDirection == 3) break; newDirection = 1; break; } } } bool Snake::movePoint(point& pt,int dir) { point newpt = pt; newpt.x += dx[dir]; newpt.y += dy[dir]; if (newpt.x<=1 || newpt.x>=GameInit::windowWidth || newpt.y<=1 || newpt.y>=GameInit::windowHeight) return 0; unordered_set<int> snakebody; for (auto pt : body) snakebody.insert(pt.x*1000+pt.y); if (snakebody.count(newpt.x*1000+newpt.y)) return 0; pt = newpt; return 1; } bool Snake::moveHead() { if (newDirection == -1) newDirection = oldDirection; point newpt = body.front(); if (movePoint(newpt,newDirection) == 0) return 0; body.push_front(newpt); oldDirection = newDirection; newDirection = -1; return 1; } bool Snake::moveHeadAndTail() { if (newDirection == -1) newDirection = oldDirection; body.pop_back(); point newpt = body.front(); if (movePoint(newpt,newDirection) == 0) return 0; body.push_front(newpt); oldDirection = newDirection; newDirection = -1; return 1; }
Food是食物类,只有生成食物只一个主要功能(当然生成还要避免生成在蛇身上)。
#include"pch.h" #include<iostream> #include<random> #include<cstdlib> #include"Food.h" #include"GmaeInit.h" #include"Snake.h" using namespace std; bool Food::EatFood(const list<point>& body) { if (x == body.front().x && y == body.front().y) return 1; else return 0; } bool Food::FoodOnBody(int tmpx,int tmpy,const list<point>& body) { for (auto pt : body) if (pt.x == tmpx && pt.y == tmpy) return 1; return 0; } void Food::CreateFood(const list<point>& body) { int tmpx,tmpy; do { tmpx = rand() % GameInit::windowWidth + 1; tmpy = rand() % GameInit::windowHeight + 1; } while (FoodOnBody(tmpx, tmpy, body)); this->x = tmpx; this->y = tmpy; }
Display是一个比较关键的类,它负责地图上各个资源的展示,包括蛇身体,食物,地图边界,当然也包括各个资源的销毁。
这个函数要用到一个比较有趣的Window API:SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); 这个函数能够移动命令行光标位置,然后我们就能在那个位置输出符号!!!
#include"pch.h" #include"Display.h" #include"GmaeInit.h" #include"Snake.h" #include<iostream> #include<Windows.h> #include <conio.h> using namespace std; void Display::printBorder() { system("cls"); int n = GameInit::windowHeight; int m = GameInit::windowWidth; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (i == 1 || i==n || j == 1 || j == m) cout << "#"; else cout << " "; } cout << endl; } } //将光标移动到x,y位置 void Display::gotoxy(int x, int y) { COORD c; c.X = x; c.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); } void Display::printSnake(const list<point> &body) { for (auto pt : body) { gotoxy(pt.x, pt.y); cout << "*"; } } void Display::destroySnake(const list<point> &body) { for (auto pt : body) { gotoxy(pt.x, pt.y); cout << " "; } } void Display::printFood(int x, int y) { gotoxy(x, y); cout << "@"; } void Display::destroyFood(int x, int y) { gotoxy(x, y); cout << " "; } void Display::clean() { for (int i = 32; i <= 50; i++) { gotoxy(i, 16); cout << " "; } } void Display::gameover() { string over = "游戏结束"; for (int i = 32; i <= 40; i++) { gotoxy(i, 16); cout << over[i-32]; } }
这只是个心血来潮做的小demo,读者很容易能看懂,也很容易为它扩展各种功能:例如计分板,根据玩家选难度加快蛇移动速度,加入经典的无边界地图玩法等等。
游戏项目Github地址:https://github.com/Clno1/Blockade 。改一改玩一玩很是很欢乐的,哈哈哈。