一、变量作用域和框架
在程序中变量起作用的范围,称为变量的作用域。根据作用域的不同C语言中的变量可以分为局部变量和全局变量。
局部变量
在函数内部定义的变量称为局部变量,其作用域从变量定义开始,到函数结束时“}”处结束。
#include <conio.h>
#include <stdio.h>
void fun()
{
int a; // 局部变量,仅能在fun函数内部使用
a = 10;
}
int main()
{
int x; // 局部变量,仅能在main函数内部使用
x = 1;
_getch();
return 0;
}
全局变量
全局变量是在所有函数定义之外的变量,其作用域从变量定义处开始,到整个程序最后结束。
#include <conio.h>
#include <stdio.h>
int m = 1; // 全局变量,整个程序都可以访问
void fun()
{
m = 3;
}
int main()
{
printf("%d\n",m);
m = 2;
printf("%d\n",m);
fun();
printf("%d\n",m);
_getch();
return 0;
}
ps.如果全局变量与局部变量同名,则在局部变量作用域内取局部变量访问
利用函数和变量作用域的知识,可以设计出一个简单的游戏框架
#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;
首先在所有函数外定义全局变量,然后通过四个函数实现具体功能:
程序从主函数开始,先运行一次startup(),进行游戏初始化;再进入show()函数进行绘制;updateWithoutInput()执行重复和输入无关的更新,updateWithInput()执行和输入有关的更新
二、基于二维数组的游戏地图
为了实现游戏地图的网状效果,这一节介绍二维数组的概念:
#include <conio.h>
#include <stdio.h>
int main()
{
int a[3][5];
int i,j;
for (i=0;i<3;i++)
for (j=0;j<5;j++)
a[i][j] = i*10 + j;
for (i=0;i<3;i++)
{
for (j=0;j<5;j++)
printf("%3d",a[i][j]);
printf("\n");
}
_getch();
return 0;
}
上述代码中,int a[3][5];定义了一个二维数组a,有三行五列15个元素
二维数组定义
对于二维数组定义时初始化,也可以采用如下方式,比如定义a[2][4] 时:
1、int a[2][4]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}
{1,2,3,4,5}赋值给第0行, {6,7,8,9,10}赋值给第1行 ,{11,12,13,14,15}赋值给第2行
2、int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
把所有数据写在一个花括号内,依次对其赋值
3、int a[3][4]={1,2,3,4}
如果只对部分元素赋值,其余元素自动为0
接下来,利用二维数组来存储地图信息:
首先定义格子数目
#define BLOCK_SIZE 20 // 每个小格子的长宽大小
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 // 宽度上一共40个小格子
然后用数组来存储格子的信息
int Blocks[HEIGHT][WIDTH] = { 0 }; // 二维数组,用于记录所有的游戏数据
再用双重for循环给二维数组赋值
for (i = 0;i < HEIGHT;i++) // 对二维数组所有元素值初始化
for (j = 0;j < WIDTH;j++)
Blocks[i][j] = rand() % 30; // 赋为随机数
开始作图,新开画面
initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); // 新开画面
cleardevice();
根据随机的二维数组数值设定,宽与高重复循环填充颜色,在对应位置绘制小方格
for (i = 0;i < HEIGHT;i++) // 对二维数组所有元素遍历
{
for (j = 0;j < WIDTH;j++)
{
setlinecolor(RGB(200, 200, 200));
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); // 根据元素值设定填充颜色
// 在对应位置处,以对应颜色绘制小方格
fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
}
}
完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 // 宽度上一共40个小格子
int main() // 主函数
{
initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); // 新开画面
cleardevice();
int Blocks[HEIGHT][WIDTH] = { 0 }; // 二维数组,用于记录所有的游戏数据
int i, j;
for (i = 0;i < HEIGHT;i++) // 对二维数组所有元素值初始化
for (j = 0;j < WIDTH;j++)
Blocks[i][j] = rand() % 30; // 赋为随机数
for (i = 0;i < HEIGHT;i++) // 对二维数组所有元素遍历
{
for (j = 0;j < WIDTH;j++)
{
setlinecolor(RGB(200, 200, 200));
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); // 根据元素值设定填充颜色
// 在对应位置处,以对应颜色绘制小方格
fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
}
}
_getch();
closegraph();
return 0;
}
代码输出结果如图:
接下来,让“蛇”的身体部分有颜色,其余变为灰色。在数组中,令蛇身以外的部分数值为0,蛇头蛇身部分用1,2,3···来记录 ,设置颜色和绘制方块如下
if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
else
setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
// 在对应位置处,以对应颜色绘制小方格
fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
fillrectangle()是绘制矩形的函数,其中四个参数对应长方形四个角的坐标,在之前小球躲避方块有提及。
完整代码如下:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#define BLOCK_SIZE 20 // 每个小格子的长宽大小
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 // 宽度上一共40个小格子
int main() // 主函数
{
initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); // 新开画面
setlinecolor(RGB(200, 200, 200));
cleardevice();
int Blocks[HEIGHT][WIDTH] = { 0 }; // 二维数组,用于记录所有的游戏数据
int i, j;
Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
for (i = 1;i <= 4;i++) // 向左依次4个蛇身,数值依次为2,3,4,5
Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
for (i = 0;i < HEIGHT;i++) // 对二维数组所有元素遍历
{
for (j = 0;j < WIDTH;j++)
{
if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
else
setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
// 在对应位置处,以对应颜色绘制小方格
fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE,
(j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
}
}
_getch();
closegraph();
return 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}; // 二维数组,用于记录所有的游戏数据
void startup() // 初始化函数
{
int i;
Blocks[HEIGHT/2][WIDTH/2] = 1; // 画面中间画蛇头,数字为1
for (i=1;i<=4;i++) // 向左依次4个蛇身,数值依次为2,3,4,5
Blocks[HEIGHT/2][WIDTH/2-i] = i+1;
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) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
setfillcolor(HSVtoRGB(Blocks[i][j]*10,0.9,1));
else
setfillcolor(RGB(150,150,150)); // 元素为0表示为空,颜色为灰色
// 在对应位置处,以对应颜色绘制小方格
fillrectangle(j*BLOCK_SIZE,i*BLOCK_SIZE,
(j+1)*BLOCK_SIZE,(i+1)*BLOCK_SIZE);
}
}
FlushBatchDraw(); // 批量绘制
}
void updateWithoutInput() // 与输入无关的更新函数
{
}
void updateWithInput() // 和输入有关的更新函数
{
}
int main() // 主函数
{
startup(); // 初始化函数,仅执行一次
while (1) // 一直循环
{
show(); // 进行绘制
updateWithoutInput(); // 和输入无关的更新
updateWithInput(); // 和输入有关的更新
}
return 0;
}
三、小蛇移动
3.1小蛇向右移动
将原本数组中5,4,3,2,1的位置向右移动,不改变长度,其中一种思路是,先把所有元素+1,再把不该存在的6去掉,令原本的蛇头1增加到2的右边。
定义小蛇的移动代码如下:
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]++;//让其+1
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(初始为0)
{
max = Blocks[i][j];//不断更新找到最大的值
oldTail_i = i;
oldTail_j = j;//这个最大值就是旧蛇尾
}
if (Blocks[i][j] == 2)//找到+1后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);
}
四、小蛇向四个方向移动
变量oldHead_i,oldHead_j储存移动前的蛇头位置(对应元素在二维数组中的行号、列号),newHead_i、newHead_j存储移动后蛇头的位置。
如果小蛇向上移动,只需把新蛇头的坐标设为旧蛇头的上方即可。即:
newHead_i= oldHead_i+1;
4.1 if-else选择语句
else不能单独出现,必须和if搭配使用,若else后有多条语句,也需要用{}将多条语句包含起来
样例如下:
#include<conio.h>
#include<stdio.h>
int main()
{
int x;
scanf("%d",&x);
if(x>=90)
printf("优秀");
else if(x>=80)
printf("良好");
else if(x>=70)
printf("中等");
else if(x>=60)
printf("及格");
else if(x<60)
printf("不及格");
_getch();
return 0;
}
利用else语句,可以减少不必要的重复if判断
char moveDirection;
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;
利用 update WithInput函数获得按键输入
获得输入后更新moveDirection变量,执行moveSnake()函数让小蛇向对应方向移动;
void updateWithInput() // 和输入有关的更新函数
{
if(kbhit())
{
char input = getch();
if (input == 'a' || input == 's' || input == 'd' || input == 'w')
{
moveDirection = input;
moveSnake();
}
}
}
在此时需要定义小蛇初始的运动方向,即在未接收输入是小蛇的运动方向,在startup()函数中加入
moveDirection='d';
并且需要控制小蛇的运动方向不能为当前运动方向的反向
所以最终检测输入的代码如下:
char input = _getch();
// 检查输入方向是否与当前方向相反
if ((input == 'a' && moveDirection == 'd') ||
(input == 'd' && moveDirection == 'a') ||
(input == 'w' && moveDirection == 's') ||
(input == 's' && moveDirection == 'w'))
{
// 输入为反方向,忽略操作
return;
}
// 输入合法,更新方向
if (input == 'a' || input == 's' || input == 'd' || input == 'w')
{
moveDirection = input;
moveSnake(); // 调用小蛇移动函数
}
五、控制时间的改进
updateWithoutInput()函数中调用Sleep()函数来降低小蛇的移动速度。然而Sleep()运行时整个程序暂停,包括用户输入模块。
5.1静态变量与动态变量
#include<conio.h>
#include<stdio.h>
void fun()
{
int m=0;
m++;
printf("%d\n",m);
}
int main()
{
fun();
fun();
fun();
_getch();
return 0;
}
函数中定义的变量默认为动态变量,程序从变量的作用域开始,为其动态分配内存空间;到变量作用域结束时,收回变量的内存空间。
主函数第一次调用fun()时,动态分配内存空间,m初始化为0,然后加1,输出1,fun()结束;第二次调用fun(),重新分配内存空间,m初始化为0,加1,输出1,收回;第三次同样。
将动态分配该为静态分布
static int m=0;
主函数第一次调用fun()时,分配内存空间,m初始化为0,然后加1,输出1,fun()结束,不收回空间;第二次调用fun(),m直接加1,输出2,收回;第三次调用,输出3。
利用静态变量,将updateWithoutInput()修改如下:
void updateWithoutInput() // 与输入无关的更新函数
{
static int waitIndex = 1;//静态局部变量,初始化时为1
waitIndex++;//每一帧加1
if (waitIndex = 10)//等于10时执行,10帧小蛇移动一次
{
moveSnake();//调用小蛇移动
waitIndex = 1;
}
}
ps.动态变量如果不赋处置,其处置为随机函数;静态变量如果不赋初值,其处置为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);
当新蛇头碰到食物时,保留原蛇尾,不必将最大值变为0,即可让蛇的长度加1
bool ateFood = (newHead_i == food_i && newHead_j == food_j);
// 更新新蛇头的位置
Blocks[newHead_i][newHead_j] = 1;
// 如果吃到食物,不移除尾部;否则移除尾部
if (!ateFood)
{
Blocks[oldTail_i][oldTail_j] = 0;
}
else
{
// 食物被吃掉,生成新食物
food_i = rand() % (HEIGHT - 5) + 2;
food_j = rand() % (WIDTH - 5) + 2;
}
七、失败判断与显示
定义全局变量isFailure表示游戏是否失败,初始化为0:
int isFailure=0;
小蛇的失败条件判断如下:
//如果蛇头超出边界或者碰到蛇身,游戏失败
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() 给出条件让游戏失败时直接跳出后续的绘制
if (isFailure)
return;//如果游戏失败,函数返回
对于updateWithInput() 也严格控制当不失败时才允许输入
if(_kbhit()&&isFailure==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 }; // 二维数组,用于记录所有的游戏数据
int food_i, food_j;
char moveDirection;
int isFailure = 0;
void moveSnake()
{
int i, j;
// 遍历地图,让蛇的每一节身体的值增加 1
for (i = 0; i < HEIGHT; i++)
for (j = 0; j < WIDTH; j++)
if (Blocks[i][j] > 0)
Blocks[i][j]++;
int oldTail_i = -1, oldTail_j = -1, oldHead_i = -1, oldHead_j = -1; // 定义变量存储旧蛇尾、蛇头坐标
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) // 找到蛇头,值为 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;
}
// 判断是否吃到食物
bool ateFood = (newHead_i == food_i && newHead_j == food_j);
// 更新新蛇头的位置
Blocks[newHead_i][newHead_j] = 1;
// 如果吃到食物,不移除尾部;否则移除尾部
if (!ateFood)
{
Blocks[oldTail_i][oldTail_j] = 0;
}
else
{
// 食物被吃掉,生成新食物
food_i = rand() % (HEIGHT - 5) + 2;
food_j = rand() % (WIDTH - 5) + 2;
}
}
void startup() // 初始化函数
{
int i;
food_i = rand()%(HEIGHT - 5) + 2;
food_j= rand() % (WIDTH - 5) + 2;//初始化食物位置
Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
for (i = 1;i <= 4;i++) // 向左依次4个蛇身,数值依次为2,3,4,5
Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
moveDirection='d';
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) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
else
setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
// 在对应位置处,以对应颜色绘制小方格
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 updateWithoutInput() // 与输入无关的更新函数
{
if (isFailure)
return;//如果游戏失败,函数返回
static int waitIndex = 1;//静态局部变量,初始化时为1
waitIndex++;//每一帧加1
if (waitIndex == 10)//等于10时执行,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) // 一直循环
{
updateWithoutInput(); // 和输入无关的更新
updateWithInput(); // 和输入有关的更新
show(); // 进行绘制
}
return 0;
}
八、优化与改进
对于以上简单的贪吃蛇,依旧存在一些可以改进的地方,比如,按键实现游戏的重新开始,得分的显示,拾取食物后小蛇加速的实现
8.1给出重置游戏函数
void resetGame() {
memset(Blocks, 0, sizeof(Blocks)); // 重置地图
food_i = rand() % (HEIGHT - 5) + 2;
food_j = rand() % (WIDTH - 5) + 2;
Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 重置蛇头
for (int i = 1; i <= 4; i++) // 重置蛇身
Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
moveDirection = 'd';
score = 0; // 重置得分
speed = initialSpeed; // 重置速度
isFailure = 0; // 重置失败标志
}
在游戏失败时,按键输入使游戏重新开始
void updateWithInput() // 和输入有关的更新函数
{
if(_kbhit())//有按键输入
{
char input = _getch();
if (isFailure && input == 'r') {
resetGame();
}
if ( isFailure == 0 &&input == 'a' || input == 's' || input == 'd' || input == 'w')
{
moveDirection = input;
moveSnake();
}
}
}
8.1得分显示
对于得分,我们设置一个变量存储
int score = 0; // 当前得分
在moveSnake()中设置吃到食物得分增加
score += 10;
得分的显示:
TCHAR scoreText[50];//显示分数
swprintf_s(scoreText, _T("Score: %d"), score);
settextcolor(RGB(255, 255, 255));
settextstyle(20, 0, _T("宋体"));
outtextxy(10, 10, scoreText);
8.3 拾取食物后小蛇的加速
设置变量控制速度,也即更新频率的帧数间隔
int speed = 10; // 初始速度(帧数间隔) int initialSpeed = 10;
给出与输入无关的帧数更新代码
void updateWithoutInput() // 与输入无关的更新函数
{
if (isFailure)
return;//如果游戏失败,函数返回
static int waitIndex = 1;//静态局部变量,初始化时为1
waitIndex++;//每一帧加1
if (waitIndex == speed)//等于速度时执行,每速度帧小蛇移动一次
{
moveSnake();//调用小蛇移动
waitIndex = 1;
}
}
最后游戏完整效果如下:
最后优化后的完整代码如下:
#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 }; // 二维数组,用于记录所有的游戏数据
int food_i, food_j;
char moveDirection;
int isFailure = 0;
int score = 0; // 当前得分
int speed = 10; // 初始速度(帧数间隔)
int initialSpeed = 10; // 初始速度,用于重置
void moveSnake()
{
int i, j;
// 遍历地图,让蛇的每一节身体的值增加 1
for (i = 0; i < HEIGHT; i++)
for (j = 0; j < WIDTH; j++)
if (Blocks[i][j] > 0)
Blocks[i][j]++;
int oldTail_i = -1, oldTail_j = -1, oldHead_i = -1, oldHead_j = -1; // 定义变量存储旧蛇尾、蛇头坐标
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) // 找到蛇头,值为 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;
}
// 判断是否吃到食物
bool ateFood = (newHead_i == food_i && newHead_j == food_j);
// 更新新蛇头的位置
Blocks[newHead_i][newHead_j] = 1;
// 如果吃到食物,不移除尾部;否则移除尾部
if (!ateFood)
{
Blocks[oldTail_i][oldTail_j] = 0;
}
else
{
// 食物被吃掉,生成新食物
food_i = rand() % (HEIGHT - 5) + 2;
food_j = rand() % (WIDTH - 5) + 2;
score += 10;
speed = speed > 2 ? speed - 1 : speed; // 提升速度,避免过快限制最小值
}
}
void resetGame() {
memset(Blocks, 0, sizeof(Blocks)); // 重置地图
food_i = rand() % (HEIGHT - 5) + 2;
food_j = rand() % (WIDTH - 5) + 2;
Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 重置蛇头
for (int i = 1; i <= 4; i++) // 重置蛇身
Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
moveDirection = 'd';
score = 0; // 重置得分
speed = initialSpeed; // 重置速度
isFailure = 0; // 重置失败标志
}
void startup() // 初始化函数
{
int i;
food_i = rand()%(HEIGHT - 5) + 2;
food_j= rand() % (WIDTH - 5) + 2;//初始化食物位置
Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1
for (i = 1;i <= 4;i++) // 向左依次4个蛇身,数值依次为2,3,4,5
Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1;
moveDirection='d';
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) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变
setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1));
else
setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色
// 在对应位置处,以对应颜色绘制小方格
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);
TCHAR scoreText[50];//显示分数
swprintf_s(scoreText, _T("Score: %d"), score);
settextcolor(RGB(255, 255, 255));
settextstyle(20, 0, _T("宋体"));
outtextxy(10, 10, scoreText);
if (isFailure)//如果游戏失败
{
setbkmode(TRANSPARENT);//文字字体透明
settextcolor(RGB(255, 0, 0));//设定文字颜色
settextstyle(80, 0, _T("宋体"));//设定文字大小样式
outtextxy(240, 220, _T("游戏失败"));//输出文字内容
settextstyle(30, 0, _T("宋体"));
outtextxy(240, 320, _T("按 R 键重新开始"));
}
FlushBatchDraw(); // 批量绘制
}
void updateWithoutInput() // 与输入无关的更新函数
{
if (isFailure)
return;//如果游戏失败,函数返回
static int waitIndex = 1;//静态局部变量,初始化时为1
waitIndex++;//每一帧加1
if (waitIndex == speed)//等于速度时执行,每速度帧小蛇移动一次
{
moveSnake();//调用小蛇移动
waitIndex = 1;
}
}
void updateWithInput() // 和输入有关的更新函数
{
if(_kbhit())//有按键输入
{
char input = _getch();
if (isFailure && input == 'r')
{
resetGame();
}
;
// 检查输入方向是否与当前方向相反
if ((input == 'a' && moveDirection == 'd') ||
(input == 'd' && moveDirection == 'a') ||
(input == 'w' && moveDirection == 's') ||
(input == 's' && moveDirection == 'w'))
{
// 输入为反方向,忽略操作
return;
}
// 输入合法,更新方向
if (input == 'a' || input == 's' || input == 'd' || input == 'w')
{
moveDirection = input;
moveSnake(); // 调用小蛇移动函数
}
}
}
int main() // 主函数
{
startup(); // 初始化函数,仅执行一次
while (1) // 一直循环
{
updateWithoutInput(); // 和输入无关的更新
updateWithInput(); // 和输入有关的更新
show(); // 进行绘制
}
return 0;
}