C语言学习第016课——项目实训
贪吃蛇项目
需求:在控制台上,跑一个贪吃蛇,可以按照上下左右方向移动,可以吃食物增加长度,撞墙或者撞到自身结束游戏
参考资料:
一、控制光标的位置和显示
Windows.h 中有一个可以控制光标位置的函数
SetConsoleCursorPosition(HANDLE,COORD);
使用方式如下:
#include<stdio.h>
#include<windows.h>
int main(){
COORD coord; //先初始化一个coord结构体,结构体里面有两个元素,X和Y
//表示光标的坐标
coord.X = 20; //给坐标赋值
coord.Y = 7;
//然后应用到函数中,HANDLE参数为固定写法
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar('h'); //打印一个h,看看是否是在20,7这样的坐标
return 0;
}
运行结果:
可以实现根据坐标实现光标移动打印
有这个函数,贪吃蛇出现在屏幕的任意地方就可以实现了
以上函数使用完,发现总有“按任意键退出程序”这种东西,我们可以在代码最后,简单的使用一个getchar,意思是我这里还没结束,还要等键盘输入呢,你程序结束的信息不要出来。
int main(){
COORD coord;
coord.X = 20;
coord.Y = 7;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar('h');
getchar();
return 0;
}
可以看到,getchar是可以解决问题的,但是h后面总有一个光标,闪来闪去的,很烦,解决一下
可以在main中最前面加入此段代码,意为让光标不可见
CONSOLE_CURSOR_INFO cci; 声明一个控制台光标信息结构体
cci.dwSize = sizeof(cci); 将里面的信息改一下,
cci.bVisible = FALSE;
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci); 再设置回去
可以看到,光标没有了
二、控制键盘输入
贪吃蛇玩法是,按一下方向键,他拐一下弯,如果不按,他就一直走,
使用getchar函数作为接收键盘输入的话,getchar 他是一个阻塞函数,键盘不输入,他就停在那里等着键盘输入了。
解决办法是使用_getch()和_kbhit()函数组合
_kbhit函数的作用是检查看见是否被按下,按下范围非0值,没有按下返回0,该函数是一个非阻塞函数,不管有没有按键按下,都会立即响应 返回
_getch()函数是从控制台中获取输入的字符,获取输入的字符后,并不会在控制台上显示该字符,
举个例子,我需要控制台在我的键盘不动的时候,一直打印“走”,当按“w”“s”“a”“d”的时候,打印“拐弯”
#include<stdio.h>
#include<windows.h>
#include<conio.h>
int main(){
char ch;
while(1){
if(kbhit()){
//键盘有输入
ch = _getch();
if(ch=='w'||ch=='s'||ch=='a'||ch=='d'){
printf("拐弯\n");
}
}else{
//键盘没有输入
printf("走\n");
}
Sleep(1000);
}
return 0;
}
有了以上这些参考资料,实现贪吃蛇游戏就成为可能,我们先分析一下怎么个思路
分析
先界定一个贪吃蛇的活动范围,定义一个宽和高,WIDE和HIGH
定义贪吃蛇结构体:一个字段为它的长度size,另一个字段是坐标数组,这个坐标也可以定义为一个结构体body,所以一个贪吃蛇就是由size个body组成的数组
食物可以随机出现,而且只有一个坐标组成,可以用一个长度为2的int数组表示,
玩游戏的时候,将整个过程放在一个while循环中,每移动一步,计算一次坐标,按照坐标,绘制一次蛇身体,并且判断是否撞墙,是否撞到自己的身体,和是否撞到食物,撞到食物,则size+1
伪代码
有了上面的分析,可以简单的写一个伪代码
定义蛇的身体结构体,蛇结构体(size+身体数组)
确定蛇的活动范围
初始化蛇的size和初始位置坐标
初始化食物的坐标
开始玩游戏
根据坐标在控制台上画蛇
控制方向(不可以向反方向移动)
检查是否撞墙
检查是否撞到自己身体
碰到食物
长度+1
重新初始化食物
更新蛇的位置(根据移动方向,更新蛇头坐标,蛇身体位置的其他坐标也更新)
代码
简单的代码测试一下宽和高,然后宏定义为常量
#define WIDE 78
#define HIGH 24
定义蛇的结构体
struct BODY{
int X;
int Y;
};
struct SNAKE{
int size;
struct BODY body[WIDE*HIGH]; //将蛇的长度定义为最大
}snake;
初始化蛇的长度和初始坐标
void InitSnake(){
snake.size = 3;
snake.body[0].X = WIDE/2;
snake.body[0].Y = HIGH/2;
snake.body[1].X = WIDE/2-1;
snake.body[1].Y = HIGH/2;
snake.body[2].X = WIDE/2-2;
snake.body[2].Y = HIGH/2;
}
初始化食物
void InitFood(){
food[0] = rand()%WIDE;
food[1] = rand()%HIGH;
}
因为该动作每吃掉一个食物会再次调用一次,所以将srand操作放到了main中
初始化墙:
void InitWall(){
for(int i=0;i<=HIGH;i++){
for(int j = 0;j<=WIDE;j++){
if(i==HIGH){
putchar('=');
}else if(j==WIDE){
putchar('=');
}else{
putchar(' ');
}
}
putchar('\n');
}
}
画蛇和画食物的过程,因为每次都需要调用,所以分离出来一个函数
void ShowUI(){
COORD coord;
//处理蛇尾巴
coord.X = lx;
coord.Y = ly;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar(' ');
//显示蛇的位置
for(int i=0;i<snake.size;i++){
coord.X = snake.body[i].X;
coord.Y = snake.body[i].Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
if(i==0){
putchar('@');
}else{
putchar('*');
}
}
//显示食物位置
coord.X = food[0];
coord.Y = food[1];
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar('#');
}
开始玩游戏
void PlayGame(){
char key = 'D'; //初始化一个刚开始移动的方向
while(snake.body[0].X<WIDE && snake.body[0].Y<HIGH
&& snake.body[0].X>=0 && snake.body[0].Y>=0){
//只要在墙内,就可以循环
ShowUI(); //根据坐标画蛇和食物
//方向控制
while(_kbhit()){ //如果有按键
key = _getch(); //获取到按键
switch(lastkey){ //和当前蛇的运行方向相比较,如果相反,则还按之前的方向运行,不理会
case 'D':case 'd':
if(key=='a' || key=='A'){
key = 'D';
}
break;
case 'S':case 's':
if(key=='w' || key=='W'){
key = 'S';
}
break;
case 'A':case 'a':
if(key=='D' || key=='d'){
key = 'A';
}
break;
case 'W':case 'w':
if(key=='s' || key=='S'){
key = 'W';
}
break;
}
}
lastkey = key;
switch(key){ //根据方向按键,计算出蛇头下一步的偏移量
case 'D':case 'd':dx=1;dy=0;break;
case 'S':case 's':dx=0;dy=1;break;
case 'A':case 'a':dx=-1;dy=0;break;
case 'W':case 'w':dx=0;dy=-1;break;
}
//是否和自身碰撞
for(int i = 1;i<snake.size;i++){
if(snake.body[0].X == snake.body[i].X && snake.body[0].Y == snake.body[i].Y){
return;
}
}
//蛇头和食物碰撞
if(snake.body[0].X == food[0] && snake.body[0].Y == food[1]){
InitFood(); //重新初始化食物
snake.size++;
score++;
}
//获取蛇尾的坐标,等下次画蛇的时候直接置为空格
lx = snake.body[snake.size-1].X;
ly = snake.body[snake.size-1].Y;
//蛇身体更新位置
for(int i = snake.size-1;i>0;i--){
snake.body[i].X = snake.body[i-1].X;
snake.body[i].Y = snake.body[i-1].Y;
}
//蛇头更新坐标
snake.body[0].X+=dx;
snake.body[0].Y+=dy;
Sleep(100);
//system("cls");
}
}
以上代码就编写完了,整体文件查看代码
snake.h
#ifndef __SNACK_H__
#define __SNACK_H__
#define WIDE 78
#define HIGH 24
struct BODY{ // 蛇坐标
int X;
int Y;
};
struct SNAKE{
int size;
struct BODY body[WIDE*HIGH];
}snake;
int food[2] = {0}; //食物坐标
int score;
int dx; //蛇头坐标偏移量
int dy;
int lx; //蛇尾坐标
int ly;
char lastkey = 'D';
void InitFood();
void InitSnake();
void ShowUI();
void PlayGame();
void InitWall();
#endif // __SNACK_H__
main.c
#include <stdio.h>
#include <stdlib.h>
#include "snack.h"
#include <time.h>
#include <Windows.h>
#include <conio.h>
int main()
{
//去掉控制台光标
CONSOLE_CURSOR_INFO cci;
cci.bVisible = FALSE;
cci.dwSize = sizeof(cci);
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);
srand((size_t)time(NULL));
InitSnake();
InitFood();
InitWall();
PlayGame();
getchar();
return 0;
}
void InitFood(){
food[0] = rand()%WIDE;
food[1] = rand()%HIGH;
}
void InitSnake(){
snake.size = 3;
snake.body[0].X = WIDE/2;
snake.body[0].Y = HIGH/2;
snake.body[1].X = WIDE/2-1;
snake.body[1].Y = HIGH/2;
snake.body[2].X = WIDE/2-2;
snake.body[2].Y = HIGH/2;
}
void ShowUI(){
COORD coord;
//显示蛇的位置
coord.X = lx;
coord.Y = ly;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar(' ');
for(int i=0;i<snake.size;i++){
coord.X = snake.body[i].X;
coord.Y = snake.body[i].Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
if(i==0){
putchar('@');
}else{
putchar('*');
}
}
//显示食物位置
coord.X = food[0];
coord.Y = food[1];
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
putchar('#');
}
void PlayGame(){
char key = 'D';
while(snake.body[0].X<WIDE && snake.body[0].Y<HIGH
&& snake.body[0].X>=0 && snake.body[0].Y>=0){
ShowUI();
//方向控制
while(_kbhit()){
key = _getch();
switch(lastkey){
case 'D':case 'd':
if(key=='a' || key=='A'){
key = 'D';
}
break;
case 'S':case 's':
if(key=='w' || key=='W'){
key = 'S';
}
break;
case 'A':case 'a':
if(key=='D' || key=='d'){
key = 'A';
}
break;
case 'W':case 'w':
if(key=='s' || key=='S'){
key = 'W';
}
break;
}
}
lastkey = key;
switch(key){
case 'D':case 'd':dx=1;dy=0;break;
case 'S':case 's':dx=0;dy=1;break;
case 'A':case 'a':dx=-1;dy=0;break;
case 'W':case 'w':dx=0;dy=-1;break;
}
//是否和自身碰撞
for(int i = 1;i<snake.size;i++){
if(snake.body[0].X == snake.body[i].X && snake.body[0].Y == snake.body[i].Y){
return;
}
}
//蛇和食物碰撞
if(snake.body[0].X == food[0] && snake.body[0].Y == food[1]){
InitFood();
snake.size++;
score++;
}
//获取蛇尾的坐标
lx = snake.body[snake.size-1].X;
ly = snake.body[snake.size-1].Y;
//蛇更新位置
for(int i = snake.size-1;i>0;i--){
snake.body[i].X = snake.body[i-1].X;
snake.body[i].Y = snake.body[i-1].Y;
}
snake.body[0].X+=dx;
snake.body[0].Y+=dy;
Sleep(100);
//system("cls");
}
}
void InitWall(){
for(int i=0;i<=HIGH;i++){
for(int j = 0;j<=WIDE;j++){
if(i==HIGH){
putchar('=');
}else if(j==WIDE){
putchar('=');
}else{
putchar(' ');
}
}
putchar('\n');
}
}
运行结果:
推荐阅读