欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言学习第016课——项目实训

程序员文章站 2022-05-12 16:22:26
...

贪吃蛇项目

需求:在控制台上,跑一个贪吃蛇,可以按照上下左右方向移动,可以吃食物增加长度,撞墙或者撞到自身结束游戏

参考资料:

一、控制光标的位置和显示

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;
}

运行结果:
C语言学习第016课——项目实训
可以实现根据坐标实现光标移动打印
有这个函数,贪吃蛇出现在屏幕的任意地方就可以实现了
以上函数使用完,发现总有“按任意键退出程序”这种东西,我们可以在代码最后,简单的使用一个getchar,意思是我这里还没结束,还要等键盘输入呢,你程序结束的信息不要出来。

int main(){
    COORD coord;
    coord.X = 20;
    coord.Y = 7;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
    putchar('h');
    getchar();
    return 0;
}

C语言学习第016课——项目实训
可以看到,getchar是可以解决问题的,但是h后面总有一个光标,闪来闪去的,很烦,解决一下
可以在main中最前面加入此段代码,意为让光标不可见

	CONSOLE_CURSOR_INFO cci;		声明一个控制台光标信息结构体
	cci.dwSize = sizeof(cci);		将里面的信息改一下,
	cci.bVisible = FALSE;
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);		再设置回去

C语言学习第016课——项目实训
可以看到,光标没有了

二、控制键盘输入

贪吃蛇玩法是,按一下方向键,他拐一下弯,如果不按,他就一直走,
使用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');
    }
}

运行结果:
C语言学习第016课——项目实训