目录
游戏说明
写一个贪吃蛇游戏,对这段时间的学习的复习,代码涉及到知识点
1.二维数组
2.函数
3.循环
4.全局变量
5.判断
超出知识点的只有光标移动,键盘的操作,颜色的设置,这部分内容可以直接照抄,先不需要理解原理。
实现的功能有游戏背景地图和小蛇展示,蛇的移动,通过键盘控制蛇转向,食物出现消失,游戏结束,计分,蛇吃食物变长,暂停。
现在让我们开始吧。
一.定义地图
1.首先定义一个贪吃蛇背景图,蛇只能在该地图中爬呀爬。
#define H 26
#define W 105
定义地图的长宽,使用的宏定义,方便后期维护。
2.接下来定义地图,地图障碍物为1,非障碍物为0,可以使用一个二维数组来存储,
a[i][j]=1就是障碍,a[i][j]=0就是非障碍物
void init()//程序开始时的初始化操作
{
int i, j;
for (i = 0; i < H; i++)
{
a[i][0] = 1;//让第一列为1
a[i][W - 1] = 1;//让最后一列为1
}
for (j = 0; j < W; j++)
{
a[0][j]=1;//让第一行为1
a[H-1][j]=1;//让最后一行为1
}
}
3.地图定义好了,接下来把地图输出出来,只需要循环嵌套打印即可,
a[i][j]=0是空格,=1是障碍物,用了#表示
void drawMap()//输出地图
{
int i,j;
for (i = 0; i < H; i++)
{
for (j = 0; j < W; j++)//两重for循环遍历数组
{
if(a[i][j]==0)//为0输出空格
printf(" ");
else//为1输出+
printf("#");
}
printf("\n");//别忘了换行
}
}
打印出来的地图大小刚好合适,如果不喜欢可以修改宏定义的H/W,方便快捷^_^
二.设计蛇
1.蛇是由两个坐标i,j组成的,可以用二维数组把这些点的坐标存起来。
int s[H*W][2];
s[0][0]和s[0][1]就是蛇头的坐标,s[i][0]和s[j][0]组成蛇的身子坐标。
定义一个int,初始化蛇一开始的长度
int sLength = 3;
s[0][0] = H / 2;//蛇头在屏幕中间位置
s[0][1] = W / 2;
for (i = 1;i < sLength;i++) {//初始蛇是竖着的
s[i][0] = s[0][0] + i;
s[i][1] = s[0][1] ;
}
2.画出蛇
这里需要一个移动光标的函数
void gotoxy(int i, int j)//移动光标
{
COORD position = { j,i };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
把光标移动到蛇的位置
void drawSnake()//画蛇
{
int i;
for (i = 0; i < sLength; i++) {
gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
if(i==0)
printf("0");//蛇头
else
printf("*");//蛇身子
}
}
看看小蛇的样子,还是蛮可爱的罒ω罒
这里蛇尾巴有个光标,如果不好看可以隐藏
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标
现在小蛇和背景框都出来了
三.让蛇动起来
小蛇的移动,蛇头往前移动一格,然后后面的身体移动到前一节身体原来的位置上。,比如长度3格的蛇,s[2][0]/s[2][1]会朝s[1][0]/s[1][1]移动一格
void move()
{
int i;
for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
{
s[i][0] = s[i - 1][0];
s[i][1] = s[i - 1][1];
}
}
蛇身子可以移动了,蛇头要单独处理下,定义一个变量存储上下左右
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
int direction;//方向
void move()
{
int i;
for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
{
s[i][0] = s[i - 1][0];
s[i][1] = s[i - 1][1];
}
switch (direction) {//蛇头根据方向来加减,向上和左是减少,下和右是加
case UP:
s[i][0]--;
break;
case DOWN:
s[i][0]++;
break;
case LEFT:
s[i][1]--;
break;
case RIGHT:
s[i][1]++;
break;
}
}
来看看可以移动的蛇
小蛇可以移动了,但是会留下移动痕迹,所以需要擦除掉移动的痕迹
gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
printf(" ");
四.通过键盘控制蛇移动
新函数key,用来处理键盘输入。
判断是否有键盘输入用kbhit函数。
C语言中kbhit()函数(conio.h):检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。
输入不能在屏幕上有痕迹,并且不能输入一个按键就回车一下。。。所以用getch函数。
getch() 是 C 语言中的一个函数,用于在不回显到控制台的情况下从键盘读取单个字符。它读取用户按下的字符,但不将其显示在屏幕上。
getch() 的工作原理
- 键盘缓冲区:当用户按下键盘上的键时,该键的 ASCII 码会存储在键盘缓冲区中。
- getch() 读取缓冲区:getch() 函数读取键盘缓冲区中的第一个字符。
- 字符不被回显:与 scanf() 等其他输入函数不同,getch() 不会将字符回显到控制台上,从而隐藏用户的输入。
void key()
{
if (kbhit() != 0)//判断是否有键盘输入
{
char in;
while (!kbhit() == 0)
in = getch();//键盘输入的字符用in存储起来
switch (in)//判断输入的字符是上下左右哪一个
{
case'w':
case 'W':
if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
direction = UP;
break;
case'a':
case 'A':
if (direction != RIGHT)
direction = LEFT;
break;
case's':
case 'S':
if (direction != UP)
direction = DOWN;
break;
case'd':
case 'D':
if (direction != LEFT)
direction = RIGHT;
break;
}
}
}
现在通过wasd这四个按键控制蛇到处走了。
五.生成食物
食物在整个背景生成,所以用的是a[i][j]地图数组,生成之前要判断一下,不能生成到墙和蛇身上,
可以生产返回1,不能生成返回0,
int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
return 0;
int i;
for (i = 0; i < sLength; i++)//判断是不是和蛇重合
{
if (ii == s[i][0] && jj == s[i][1])
return 0;
}
if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
return 0;
return 1;//都不是可以生产了
}
判断坐标可以生成食物了,现在开始画出食物
void food()
{
int i, j;
do
{
i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
}
while (check(i, j) == 0);//找到一个点直到满足条件
a[i][j] = -1; //标记为食物
gotoxy(i, j);
printf("*");//画出食物
}
六.给墙、蛇、食物加颜色
默认颜色不好看,可以用
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //设置颜色
//颜色设置
void color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
现在来看看实现的效果
注意选择方向的时候要切换到英文输入
目前为止的代码
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include<time.h>
#define H 25
#define W 105
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define WAIT_TIME 200
#define true 1
#define false 0
void color(int c);
int a[H][W];//地图数组
int s[H * W][2];//蛇的定义长度
int sLength=3; //蛇身子
int direction;//方向
void init()//程序开始时的初始化操作
{
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标
int i, j;
for (i = 0; i < H; i++)
{
a[i][0] = 1;//让第一列为1
a[i][W - 1] = 1;//让最后一列为1
}
for (j = 0; j < W; j++)
{
a[0][j]=1;//让第一行为1
a[H-1][j]=1;//让最后一行为1
}
s[0][0] = H / 2;
s[0][1] = W / 2;
for (i = 1;i < sLength;i++) {//初始蛇是竖着的
s[i][0] = s[0][0] + i;
s[i][1] = s[0][1] ;
}
direction = UP;
}
void gotoxy(int i, int j)//移动光标
{
COORD position = { j,i };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
void drawMap()//输出地图
{
color(6);
gotoxy(0, 0);
int i,j;
for (i = 0; i < H; i++)
{
for (j = 0; j < W; j++)//两重for循环遍历数组
{
if(a[i][j]==0)//为0输出空格
printf(" ");
else//为1输出+
printf("+");
}
printf("\n");//别忘了换行
}
}
void drawSnake()//画蛇
{
int i;
for (i = 0; i < sLength; i++) {
gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
if(i==0)
{
color(12);
printf("0");//蛇头
}
else
{
color(10);
printf("*");//蛇身子
}
}
}
void move()
{
int i;
gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
printf(" ");
for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
{
s[i][0] = s[i - 1][0];
s[i][1] = s[i - 1][1];
}
switch (direction) {//蛇头根据方向来加减
case UP:
s[i][0]--;
break;
case DOWN:
s[i][0]++;
break;
case LEFT:
s[i][1]--;
break;
case RIGHT:
s[i][1]++;
break;
}
}
void key()
{
if (kbhit() != 0)//判断是否有键盘输入
{
char in;
while (!kbhit() == 0)
in = getch();//键盘输入的字符用in存储起来
switch (in)//判断输入的字符是上下左右哪一个
{
case'w':
case 'W':
if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
direction = UP;
break;
case'a':
case 'A':
if (direction != RIGHT)
direction = LEFT;
break;
case's':
case 'S':
if (direction != UP)
direction = DOWN;
break;
case'd':
case 'D':
if (direction != LEFT)
direction = RIGHT;
break;
}
}
}
int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
return 0;
int i;
for (i = 0; i < sLength; i++)//判断是不是和蛇重合
{
if (ii == s[i][0] && jj == s[i][1])
return 0;
}
if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
return 0;
return 1;//都不是可以生产了
}
void food()
{
int i, j;
do
{
i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
}
while (check(i, j) == 0);//生成点直到满足条件
a[i][j] = -1; //标记为食物
gotoxy(i, j);
printf("*");//画出食物
}
//颜色设置
void color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
int main()
{
init();//程序开始时的初始化操作
drawMap();//画地图
while (1) {
drawSnake();//画蛇
food();//画食物
Sleep(WAIT_TIME);//等待一段时间
key();
move();//移动蛇(主要是修改蛇身数组的数据)
}
getchar();
return 0;
}
七.吃食物变长
给个变量记录蛇吃到食物的状态
bool eated = false;//蛇吃到食物没,没迟到false,吃到true
main函数里面判断,如果吃到食物了,eated=true,然后重新投放食物,
判断蛇吃到没有,用蛇头的坐标和食物坐标判断,如果等于-1(投放食物的时候给食物赋值-1,1是墙,0是空),就是蛇头和食物在同一个坐标内
if (a[s[0][0]][s[0][1]] == -1)//蛇头碰到食物,继续投放食物,把吃到的食物变为0
{
eated = true;//吃到食物 了
food();
a[s[0][0]][s[0][1]] = 0;//食物消失
}
Sleep(WAIT_TIME);//等待一段时间
key();
move();//移动蛇(主要是修改蛇身数组的数据)
然后move函数判断,如果吃到食物,蛇身子+1,同时蛇又变成没吃到食物状态,比如蛇会一直变长
if (eated)
{
sLength++;//蛇变长一格
eated = false;//然后蛇又变成没吃到食物状态
}
现在看看效果,现在蛇还可以从自己身上和墙传过
八.暂停功能
如果突然有事要暂停游戏,需要输入一个字符来暂停游戏。
在key函数里面加入一个case,用来判断是否暂停,
case '1'://输入1暂停游戏
gotoxy(H, 0);//光标移动到下面
system("pause");
gotoxy(H, 0);
printf(" ");//按任意键继续
break;
目前的游戏不会失败和记录积分
九.判断游戏会不会失败
gameover函数
bool gameOver()
{
bool isGameOver = false;
int sX = s[0][0], sY = s[0][1];//蛇头的坐标
if (sX == 0 || sX == H - 1 || sY == 0 || sY == W - 1)//判断蛇头碰到墙壁没有
{
isGameOver = true;
}
for (int i = 1; i < sLength; i++)
{
if (s[i][0] == sX && s[i][1] == sY)//判断蛇头碰到自己身体没有
{
isGameOver = true;//判断了状态变为true 游戏结束
break;//如果蛇头坐标和身子坐标一致,就跳出循环
}
}
return isGameOver;
}
主函数里面加入如果游戏结束了,打印结束语
if(gameOver())
{
system("cls");//清屏
printf("Game Over!!!\n");
system("pause");
break;
}
十.计分
声明一个变量来记录分数
int score = 0;//得分
写一个printScore函数来记录分数
void printfScore()
{
gotoxy(0, W + 2);
printf(" 贪吃蛇的小游戏\n");
gotoxy(1, W + 2);
printf(" by yumibaobao\n");
gotoxy(4, W + 2);
printf(" 得分:%d", score);
}
最终展示效果
所有代码展示
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include<time.h>
#define H 25
#define W 65
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define WAIT_TIME 100
#define true 1
#define false 0
void color(int c);
typedef int bool;
int a[H][W];//地图数组
int s[H * W][2];//蛇的定义长度
int sLength=6; //蛇身子
int direction;//方向
bool eated = false;//蛇吃到食物没
int score = 0;//得分
void init()//程序开始时的初始化操作
{
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标
int i, j;
for (i = 0; i < H; i++)
{
a[i][0] = 1;//让第一列为1
a[i][W - 1] = 1;//让最后一列为1
}
for (j = 0; j < W; j++)
{
a[0][j]=1;//让第一行为1
a[H-1][j]=1;//让最后一行为1
}
s[0][0] = H / 2;
s[0][1] = W / 2;
for (i = 1;i < sLength;i++) {//初始蛇是竖着的
s[i][0] = s[0][0] + i;
s[i][1] = s[0][1] ;
}
direction = UP;
}
void gotoxy(int i, int j)//移动光标
{
COORD position = { j,i };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
void drawMap()//输出地图
{
color(6);
gotoxy(0, 0);
int i,j;
for (i = 0; i < H; i++)
{
for (j = 0; j < W; j++)//两重for循环遍历数组
{
if(a[i][j]==0)//为0输出空格
printf(" ");
else//为1输出+
printf("+");
}
printf("\n");//别忘了换行
}
}
void drawSnake()//画蛇
{
int i;
for (i = 0; i < sLength; i++) {
gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
if(i==0)
{
color(12);
printf("0");//蛇头
}
else
{
color(10);
printf("*");//蛇身子
}
}
}
void move()
{
int i;
gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
printf(" ");
for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
{
s[i][0] = s[i - 1][0];
s[i][1] = s[i - 1][1];
}
switch (direction) //蛇头根据方向来加减
{
case UP:
s[0][0]--;
break;
case DOWN:
s[0][0]++;
break;
case LEFT:
s[0][1]--;
break;
case RIGHT:
s[0][1]++;
break;
}
if (eated)
{
sLength++;//蛇变长一格
eated = false;//然后蛇又变成没吃到食物状态
}
}
void key()
{
if (kbhit() != 0)//判断是否有键盘输入
{
char in;
while (!kbhit() == 0)
in = getch();//键盘输入的字符用in存储起来
switch (in)//判断输入的字符是上下左右哪一个
{
case'w':
case 'W':
if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
direction = UP;
break;
case'a':
case 'A':
if (direction != RIGHT)
direction = LEFT;
break;
case's':
case 'S':
if (direction != UP)
direction = DOWN;
break;
case'd':
case 'D':
if (direction != LEFT)
direction = RIGHT;
break;
case '1':
gotoxy(H, 0);//光标移动到下面
system("pause");
gotoxy(H, 0);
printf(" ");//按任意键继续
break;
}
}
}
int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
return 0;
int i;
for (i = 0; i < sLength; i++)//判断是不是和蛇重合
{
if (ii == s[i][0] && jj == s[i][1])
return 0;
}
if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
return 0;
return 1;//都不是可以生产了
}
void food()
{
int i, j;
do
{
i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
}
while (check(i, j) == 0);//生成点直到满足条件
a[i][j] = -1; //标记为食物
gotoxy(i, j);
printf("*");//画出食物
}
bool gameOver()
{
bool isGameOver = false;
int sX = s[0][0], sY = s[0][1];//蛇头的坐标
if (sX == 0 || sX == H - 1 || sY == 0 || sY == W - 1)//判断蛇头碰到墙壁没有
{
isGameOver = true;
}
for (int i = 1; i < sLength; i++)
{
if (s[i][0] == sX && s[i][1] == sY)//判断蛇头碰到自己身体没有
{
isGameOver = true;//判断了状态变为true 游戏结束
break;
}
}
return isGameOver;
}
void printfScore()
{
gotoxy(0, W + 2);
printf(" 贪吃蛇的小游戏\n");
gotoxy(1, W + 2);
printf(" by yumibaobao\n");
gotoxy(4, W + 2);
printf(" 得分:%d", score);
}
//颜色设置
void color(int c)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
int main()
{
init();//程序开始时的初始化操作
drawMap();//画地图
food();
while (1) {
drawSnake();//画蛇
Sleep(WAIT_TIME);//等待一段时间
key();
move();//移动蛇(主要是修改蛇身数组的数据)
if(gameOver())
{
system("cls");//清屏
printf("Game Over!!!\n");
system("pause");
break;
}
if (a[s[0][0]][s[0][1]] == -1)//蛇头碰到食物,继续投放食物,把吃到的食物变为0
{
eated = true;//吃到食物了
food();
score+= 10;
a[s[0][0]][s[0][1]] = 0;//食物消失
}
printfScore();
}
return 0;
}
整个贪吃蛇游戏,涉及到二维数组,循环,判断,函数,宏定义,全局变量等基础知识,
通过这个游戏,又复习了这些基础知识的使用,把一个复杂的问题拆分成一个个小的问题去解决,希望初学C的同学都可以用它来练练手。