贪吃蛇C/C++面向过程源码分析(双缓冲区 | 248行代码)
贪吃蛇会是C/C++选手学完做的一个小DIY,考察了游戏的设计逻辑和C/C++基础编程能力。由于贪吃蛇的设计过程相对简单,采用面向过程(函数式)编程来实现。
一、游戏逻辑设计
贪吃蛇的游戏逻辑可以归纳如下:
-
蛇在一个区域中向上下左右四个方向移动
-
吃到食物之后会变长(增长 1 格),在蛇尾巴添加一格
-
蛇头的运动决定蛇的运动方向,只要蛇头不碰墙或者不撞到自己,游戏就能继续
二、蛇活动区域设计:
蛇的活动区域就是一个方格,本质就是一个字符二维矩阵。在这个矩阵中,有四种方格:墙(’#’)、蛇(’*’)、食物(‘O’)和空白(’ ')。蛇的运动会动态改变这些格子的状态
// 一开始蛇在中间,头朝右运动
// H = 20, W = 26
char mp[H][W] = {
"#########################",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# ** #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"#########################"
};
三、蛇的表示设计:
一条蛇实际上就是“一条链”,在每个“节骨眼”(格子)都会有自己的状态,这些状态分别是上(UP),下(DOWN),左(LEFT),右(RIGHT),而这些状态就决定了蛇的运动方向,间接影响了区域中方格的状态。这四个状态可以使用枚举类型表示:
enum direction {
UP,
DOWN,
LEFT,
RIGHT,
null
} ;
//紧接着定义 enum direction status[H][W],表示方格的状态(上下左右,仅对蛇身生效,若非蛇格,则为null),记得main刚开始要初始化:
void init() {
for (int i = 1; i <= 18; ++i) {
for (int j = 1; j <= 23; ++j) {
status[i][j] = null;
}
}
status[9][12] = RIGHT;
status[9][11] = RIGHT;
while (true) { // 同时随机生成食物,注意食物不能在蛇身上和墙(外)
int rx = 1 + rand()%23;
int ry = 1 + rand()%18;
if (mp[rx][ry] == ' ') {
mp[rx][ry] = 'O';
return ;
}
}
}
四、蛇的运动设计
蛇的运动,根源来自于蛇头。所以可以从蛇头开始,循着status[][]的信息,进行DFS,一直更新到蛇尾状态结束。而如果蛇头刚好吃到食物,则在DFS开始之前,在蛇尾巴再加上一格。
// (hx,hy)是蛇头,从此DFS,线性更新蛇,模拟蛇的运动
void dfs(int hx,int hy) {
if (status[hx][hy] == UP) {
mp[hx][hy] = mp[hx+1][hy];
if (mp[hx][hy] == ' ') {
tail = {hx-1,hy}; // tail是尾巴标记,方便吃到食物增长
status[hx][hy] = null;
return ;
}
dfs(hx+1,hy);
}
else if (status[hx][hy] == DOWN) {
mp[hx][hy] = mp[hx-1][hy];
if (mp[hx][hy] == ' ') {
tail = {hx+1,hy};
status[hx][hy] = null;
return ;
}
dfs(hx-1,hy);
}
else if (status[hx][hy] == LEFT) {
mp[hx][hy] = mp[hx][hy+1];
if (mp[hx][hy] == ' ') {
tail = {hx,hy-1};
status[hx][hy] = null;
return ;
}
dfs(hx,hy+1);
}
else if (status[hx][hy] == RIGHT) {
mp[hx][hy] = mp[hx][hy-1];
if (mp[hx][hy] == ' ') {
tail = {hx,hy+1};
status[hx][hy] = null;
return ;
}
dfs(hx,hy-1);
}
}
贪吃蛇还要相应键盘输入,应该添加键盘相应事件,并更新蛇头状态,并调用DFS模块:
void operation() {
while (true) {
int hx = head.first; // 蛇头x坐标
int hy = head.second; // 蛇头y坐标
// 键盘相应事件
if (kbhit()) {
char ch = getch();
if (ch == 'w' && status[hx][hy] != DOWN) { // 往上
--hx;
status[hx][hy] = UP;
}
else if (ch == 's' && status[hx][hy] != UP) { // 往下
++hx;
status[hx][hy] = DOWN;
}
else if (ch == 'a' && status[hx][hy] != RIGHT) { // 往左
--hy;
status[hx][hy] = LEFT;
}
else if (ch == 'd' && status[hx][hy] != LEFT) { // 往右
++hy;
status[hx][hy] = RIGHT;
}
else {
goto T1;
}
} else {
T1:
if (status[hx][hy] == UP) {
--hx;
status[hx][hy] = UP;
}
if (status[hx][hy] == DOWN) {
++hx;
status[hx][hy] = DOWN;
}
if (status[hx][hy] == LEFT) {
--hy;
status[hx][hy] = LEFT;
}
if (status[hx][hy] == RIGHT) {
++hy;
status[hx][hy] = RIGHT;
}
}
if (mp[hx][hy] == '#' || mp[hx][hy] == '*') {
printf("\a");
printf("You lose! Get %d points",score);
exit(0);
}
// ----------------
if (mp[hx][hy] == 'O') {
int tx = tail.first;
int ty = tail.second;
if (status[tx][ty] == UP) {
++tx;
status[tx][ty] = UP;
mp[tx][ty] = '*';
}
if (status[tx][ty] == DOWN) {
--tx;
status[tx][ty] = DOWN;
mp[tx][ty] = '*';
}
if (status[tx][ty] == LEFT) {
++ty;
status[tx][ty] = LEFT;
mp[tx][ty] = '*';
}
if (status[tx][ty] == RIGHT) {
--ty;
status[tx][ty] = RIGHT;
mp[tx][ty] = '*';
}
tail = {tx,ty};
while (true) {
int rx = 1 + rand()%23;
int ry = 1 + rand()%18;
if (mp[rx][ry] == ' ') {
mp[rx][ry] = 'O';
break;
}
}
delay -= 10; // 运动加速,但不得少于100
delay = delay > 100 ? delay : 100;
score += 10; // 得分增加
}
dfs(hx,hy); // 新蛇头,进行蛇蠕动
head = {hx,hy};
display();
}
}
五、刷新控制台:
贪吃蛇游戏需要不断地刷新控制台,用System(“cls”)当然可以解决这个问题,但当蛇地活动区域较大时,由于输出缓冲的问题,看到的屏幕是闪烁的。良好的设计应该解决这个问题,这里我们可以使用双缓冲区解决这个问题。
HANDLE hOutput, hOutBuf;//控制台屏幕缓冲区句柄
HANDLE *houtpoint;//显示指针
COORD coord = { 0,0 };
//双缓冲处理显示
DWORD bytes = 0;
bool showcircle = false;
在main模块中引入下列定义:
//创建新的控制台缓冲区
hOutBuf = CreateConsoleScreenBuffer(
GENERIC_WRITE,//定义进程可以往缓冲区写数据
FILE_SHARE_WRITE,//定义缓冲区可共享写权限
NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL
);
hOutput = CreateConsoleScreenBuffer(
GENERIC_WRITE,//定义进程可以往缓冲区写数据
FILE_SHARE_WRITE,//定义缓冲区可共享写权限
NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL
);
//隐藏两个缓冲区的光标
CONSOLE_CURSOR_INFO cci;
cci.bVisible = 0;
cci.dwSize = 1;
SetConsoleCursorInfo(hOutput, &cci);
SetConsoleCursorInfo(hOutBuf, &cci);
展示活动区域的时候使用双缓冲区输出:
void display() {
showcircle = !showcircle;
if (showcircle) {
houtpoint = &hOutput;
}
else {
houtpoint = &hOutBuf;
}
for (int i = 0; i < H; ++i) {
coord.X = 0;
coord.Y = i;
WriteConsoleOutputCharacterA(*houtpoint, (char*)mp[i], W, coord, &bytes);
}
//设置新的缓冲区为活动显示缓冲
SetConsoleActiveScreenBuffer(*houtpoint);
Sleep(delay);
}
欢迎同步关注:ACMFans_Club微信公众号
本文地址:https://blog.csdn.net/weixin_44026604/article/details/107623402
下一篇: Qt学习总结——飞机大战小游戏制作