C++|C++入门——实现贪吃蛇游戏
参考
- 《C和C++游戏趣味编程》
程序框架
#include
#include
#include // 全局变量定义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;
}
【C++|C++入门——实现贪吃蛇游戏】在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
#include
#include
#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;
}
推荐阅读
- 急于表达——往往欲速则不达
- 慢慢的美丽
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 2019-02-13——今天谈梦想()
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- Ⅴ爱阅读,亲子互动——打卡第178天
- 低头思故乡——只是因为睡不着
- 取名——兰
- 每日一话(49)——一位清华教授在朋友圈给大学生的9条建议
- 广角叙述|广角叙述 展众生群像——试析鲁迅《示众》的展示艺术