C++入门指南之贪吃蛇游戏的实现
参考
- 《C和C++游戏趣味编程》
贪吃蛇游戏
键盘控制小蛇上、下、左、右移动,迟到食物后长度加1;蛇头碰到自身或窗口边缘,游戏失败
程序框架
#include <graphics.h> #include <conio.h> #include <stdio.h> // 全局变量定义 void startup() // 初始化函数 { } void show() // 绘制函数 { } void updateWithoutInput() // 与输入无关的更新 { } void updateWithInput() // 和输入有关的更新 { } int main() { startup(); // 初始化函数,仅执行一次 while (1) { show(); // 进行绘制 updateWithoutInput(); // 和输入无关的更新 updateWithInput(); // 和输入有关的更新 } return 0; }
绘制游戏地图和蛇
绘制网格状的游戏地图,使用二维数组Blocks存储每个网格的信息。二维数组Blocks中也可以记录蛇的信息。设定元素值为0表示空,画出灰色的方格;元素值为1表示蛇头,蛇头后的蛇身依次为2、3、4、5等正整数,画出彩色的方格
int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (Blocks[i][j] > 0) { setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); } else { setfillcolor(RGB(150, 150, 150)); } fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); } }
小蛇向右移动
假设小蛇初始元素值为54321,其中1位蛇头,5432位蛇身。首先将二维数组中所有大于0的元素加1,得到65432;然后将最大值6变成0,即去除了原来的蛇尾;最后将2右边的元素由0变成1,即实现了小蛇向右移动
void moveSnake() { int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (Blocks[i][j] > 0) { Blocks[i][j]++; } } } int oldTail_i, oldTail_j, oldHead_i, oldHead_j; int max = 0; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (max < Blocks[i][j]) { max = Blocks[i][j]; oldTail_i = i; oldTail_j = j; } if (Blocks[i][j] == 2) { oldHead_i = i; oldHead_j = j; } } } int newHead_i = oldHead_i; int newHead_j = oldHead_j; newHead_j = oldHead_j + 1; Blocks[newHead_i][newHead_j] = 1; Blocks[oldTail_i][oldTail_j] = 0; } void updateWithoutInput() // 与输入无关的更新 { moveSnake(); Sleep(100); }
控制小蛇4个方向移动
变量oldHead_i、oldHead_j存储移动前的蛇头位置,newHead_i、newHead_j存储移动后的蛇头位置。小蛇向上移动,只需把新蛇头的坐标设为旧蛇头的上方即可
newHead_i = oldHead_i - 1;
让玩家用A、S、D、W键控制游戏角色移动,定义字符变量moveDirection表示小蛇运动方向,在moveSnake函数中对其值进行判断,取A向左运动、D向右运动、W向上运动、S向下运动:
if (moveDirection == 'A') { newHead_j = oldHead_j - 1; } else if (moveDirection == 'D') { newHead_j = oldHead_j + 1; } else if (moveDirection == 'W') { newHead_i = oldHead_i - 1; } else if (moveDirection == 'S') { newHead_i = oldHead_i + 1; }
在updateWithInput()函数中获得用户按键输入,如果是A、S、D、W键之一,就更新moveDirection变量,执行moveSnake()函数让小蛇向对应方向移动:
void updateWithInput() // 和输入有关的更新 { if (_kbhit()) { char input = _getch(); if (input == 'A' || input == 'S' || input == 'D' || input == 'W') { moveDirection = input; moveSnake(); } } }
时间控制的改进
在Sleep()函数运行时,整个程序都会暂停,包括用户输入模块。用户会感觉到卡顿
利用静态变量,将updateWithoutInput()修改如下:
void updateWithoutInput() // 与输入无关的更新 { static int waitIndex = 1; waitIndex++; // 每一帧加1 if (waitIndex == 10) { moveSnake(); waitIndex = 1; } }
其中,updateWithoutInput()每次运行时,waitIndex加1,每隔10帧,才执行一次移动函数moveSnake()。这样可在不影响用户按键输入的情况下,降低小蛇的移动速度
失败判断与显示
定义全局变量isFailure表示游戏是否失败,初始化为0:
int isFailure = 0;
当小蛇碰到画面边界时,则认为游戏失败;当蛇头与蛇身发生碰撞时,游戏也失败。由于每次只有蛇头是新生成的位置,所以在moveSnake()函数中只需判断蛇头是否越过边界和碰撞:
if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j] > 0) { isFailure = 1; return; } 在show()函数中添加游戏失败后的显示信息: if (isFailure) // 游戏失败 { setbkmode(TRANSPARENT); // 文字字体透明 settextcolor(RGB(255, 0, 0)); settextstyle(80, 0, _T("宋体")); outtextxy(240, 220, _T("游戏失败")); }
在updateWithoutInput()中添加代码,当isFailure为1时,直接返回:
void updateWithoutInput() // 与输入无关的更新 { if (isFailure) { return; } //... }
在updateWithInput()中,只有当按下键盘且isFailure为0时,才进行相应的处理:
void updateWithInput() // 和输入有关的更新 { if (_kbhit() && isFailure == 0) { // ... } }
添加食物
添加全局变量记录食物的位置:
int food_i, food_j; 在startup()函数中初始化食物的位置: void startup() // 初始化函数 { food_i = rand() % (HEIGHT - 5) + 2; food_j = rand() % (WIDTH - 5) + 2; }
在show()函数中在食物位置处绘制一个绿色小方块:
setfillcolor(RGB(0, 255, 0)); fillrectangle(food_j * BLOCK_SIZE, food_i * BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE);
当新蛇头碰到食物时,只需保留原蛇尾,即可让蛇的长度加1。当吃到食物时,食物位置重新随机出现,蛇长度加1;当没有迟到食物时,旧蛇尾变成空白,蛇长度保持不变:
Blocks[newHead_i][newHead_j] = 1;// 新蛇头位置数值为1 if (newHead_i == food_i && newHead_j == food_j) // 如果新蛇头碰到食物 { food_i = rand() % (HEIGHT - 5) + 2; // 食物重新随机位置 food_j = rand() % (WIDTH - 5) + 2; } else { Blocks[oldTail_i][oldTail_j] = 0; // 旧蛇尾变成空白 }
完整代码
#include <graphics.h> #include <conio.h> #include <stdio.h> #define BLOCK_SIZE 20 // 每个小格子的长宽 #define HEIGHT 30 // 高度上一共30个小格子 #define WIDTH 40 // 宽度上一共40个小格子 // 全局变量定义 int Blocks[HEIGHT][WIDTH] = { 0 }; char moveDirection; int isFailure = 0; int food_i, food_j; // 食物的位置 void startup() // 初始化函数 { int i; Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头 for (i = 1; i <= 4; i++) // 向左依次4个蛇身 { Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1; } moveDirection = 'D'; food_i = rand() % (HEIGHT - 5) + 2; food_j = rand() % (WIDTH - 5) + 2; initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); setlinecolor(RGB(200, 200, 200)); BeginBatchDraw(); // 开始批量绘制 } void show() // 绘制函数 { cleardevice(); int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (Blocks[i][j] > 0) { setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); } else { setfillcolor(RGB(150, 150, 150)); } fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); } } setfillcolor(RGB(0, 255, 0)); // 食物颜色为绿色 fillrectangle(food_j * BLOCK_SIZE, food_i * BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE); if (isFailure) // 游戏失败 { setbkmode(TRANSPARENT); // 文字字体透明 settextcolor(RGB(255, 0, 0)); settextstyle(80, 0, _T("宋体")); outtextxy(240, 220, _T("游戏失败")); } FlushBatchDraw(); // 批量绘制 } void moveSnake() { int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (Blocks[i][j] > 0) // 大于0的为小蛇元素 { Blocks[i][j]++; } } } int oldTail_i, oldTail_j, oldHead_i, oldHead_j; // 存储旧蛇 int max = 0; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (max < Blocks[i][j]) { max = Blocks[i][j]; oldTail_i = i; oldTail_j = j; } if (Blocks[i][j] == 2) // 旧蛇头 { oldHead_i = i; oldHead_j = j; } } } int newHead_i = oldHead_i; // 设定变量存储新蛇头 int newHead_j = oldHead_j; if (moveDirection == 'A') // 根据用户按键,设定新蛇头的位置 { newHead_j = oldHead_j - 1; } else if (moveDirection == 'D') { newHead_j = oldHead_j + 1; } else if (moveDirection == 'W') { newHead_i = oldHead_i - 1; } else if (moveDirection == 'S') { newHead_i = oldHead_i + 1; } if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j] > 0) // 失败条件 { isFailure = 1; return; } Blocks[newHead_i][newHead_j] = 1; // 新蛇头位置数值为1 if (newHead_i == food_i && newHead_j == food_j) // 如果新蛇头碰到食物 { food_i = rand() % (HEIGHT - 5) + 2; // 食物重新随机位置 food_j = rand() % (WIDTH - 5) + 2; } else { Blocks[oldTail_i][oldTail_j] = 0; // 旧蛇尾变成空白 } } void updateWithoutInput() // 与输入无关的更新 { if (isFailure) { return; } static int waitIndex = 1; waitIndex++; // 每一帧加1 if (waitIndex == 10) { moveSnake(); waitIndex = 1; } } void updateWithInput() // 和输入有关的更新 { if (_kbhit() && isFailure == 0) { char input = _getch(); if (input == 'A' || input == 'S' || input == 'D' || input == 'W') { moveDirection = input; moveSnake(); } } } int main() { startup(); // 初始化函数,仅执行一次 while (1) { show(); // 进行绘制 updateWithoutInput(); // 和输入无关的更新 updateWithInput(); // 和输入有关的更新 } return 0; }
总结
到此这篇关于C++入门指南之贪吃蛇游戏实现的文章就介绍到这了,更多相关C++实现贪吃蛇游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
如何通过UltraEdit解析BMP文件内部结构(BMP位图基础)
我们先打开画图随便画一幅图并采用24位bmp图像格式保存,就得到了一张24位真彩色的位图,下面我们来详细分析bmp位图的各个组成部分,感兴趣的朋友跟随小编一起看看吧2021-08-08
最新评论