精华内容
下载资源
问答
  • C语言编写管道代码

    千次阅读 2019-01-17 20:27:04
    管道半双工,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道 只能用于具有共同祖先进程(具有亲缘关系进程)之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子...

    什么是管道:

    管道是Unix中最古老的进程间通信的形式。

    我们把从一个进程链接到另外一个进程的一个数据流称为管道。

    本质是有固定大小的内核缓存区。

    管道的限制

    管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道

    只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以用管道。

    匿名管道pipe

    包含头文件<unistd.h>

    功能:创建一无名管道

    原型:

    int pipe(int fd[2]);

    参数

    fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端。

    返回值:成功返回0,失败返回错误代码。

    国际惯例:上个代码

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #define ERR_EXIT(m)				\
    	do						     \
    	{						     \
    		perror(m);				\
    		exit(EXIT_FAILURE);		\
    	}while(0)
    
    
    int main()
    {
    	int pipefd[2];
    	if(pipe(pipefd) == -1)
    	{
    		ERR_EXIT("pipe error");
    	}
    	pid_t pid;
    	pid = fork();
    	if(pid == 0)
    	{
    		close(pipefd[0]);
    		write(pipefd[1],"hello",5);
    		close(pipefd[1]);
    		exit(EXIT_SUCCESS);
    	}
    	close(pipefd[1]);
    	char buf[10] = {0};
    	read(pipefd[0],buf,10);
    	printf("buf = %s\n",buf);
    	return 0;
    }

    解释一波:

    本次实验是子进程写一个字符串,父进程打印出来。

    从主函数开始解释:

    	int pipefd[2];
    	if(pipe(pipefd) == -1)
    	{
    		ERR_EXIT("pipe error");
    	}
    

    第一行代码是先创建一个数组,为的就是pipe的参数。if(pipe(pipefd)==-1)这行代码的意思就是创建一个管道,如果失败的打印错误信息。

    	pid_t pid;
    	pid = fork();
    	if(pid == 0)
    	{
    		close(pipefd[0]);
    		write(pipefd[1],"hello",5);
    		close(pipefd[1]);
    		exit(EXIT_SUCCESS);
    	}
    

    解释这些代码

    这些代码的意思是,创建一个进程(第一行和第二行),第三行代码的意思是进入子进程,pid返回0表示在子进程中,返回大于0表示在父进程中。

    再看子进程的代码:

    close(pipefd[0]);这行代码的意思是,关闭读端。因为子进程是要写字符串在管道中,所以读端就没什么用了。关闭了。

    write(pipefd[1],"hello",5);这行代码的意思就是向管道中写hello这个字符串。

    第四行close(pipefd[1]);这行代码就是关闭写端。

    再来就是子进程退出。

    	close(pipefd[1]);
    	char buf[10] = {0};
    	read(pipefd[0],buf,10);
    	printf("buf = %s\n",buf);
    	return 0;

    再看这几行代码:

    这几行代码是父进程的代码,一天因为子进程执行完就退出了。所以不会执行到这里的。

    第一行代码是关闭写端。因为父进程是读子进程传来的消息,所以写端没什么用,关闭。

    第二行代码是定义一个字符串,用来存放读取到的字符串。

    第三行代码就是读取管道的信息。

    第四行就是打印。

    注:试着让子进程睡眠10s,想让父进程先执行完看看是什么效果,但是我失望了,还是这样的。就是白等了10s;

    看结果:

    这是为啥那?

    强行解释一波,下面代码是父子进程共用的代码,并不是父进程自己的代码,所以当子进程执行完才会执行的代码。(实际上是当没有数据可读时:O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。)

     

    在看一段代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #define ERR_EXIT(m)				\
    	do						     \
    	{						     \
    		perror(m);				\
    		exit(EXIT_FAILURE);		\
    	}while(0)
    
    
    int main()
    {
    	int pipefd[2];
    	if(pipe(pipefd) == -1)
    	{
    		ERR_EXIT("pipe error");
    	}
    	pid_t pid;
    	pid = fork();
    	if(pid == 0)
    	{	
    		dup2(pipefd[1],STDOUT_FILENO);
    		close(pipefd[1]);
    		close(pipefd[0]);
    		execlp("ls","ls",NULL);
    		fprintf(stderr,"error execor execte ls\n");
    		exit(EXIT_FAILURE);
    	}
    	dup2(pipefd[0],STDIN_FILENO);
    	close(pipefd[0]);
    	close(pipefd[1]);
    	execlp("wc","wc","-w",NULL);
    	fprintf(stderr,"error execute wc\n");
    	return 0;
    }
    

    这行代码的执行效果和 ls | wc -w

    的执行效果是一样的。

    看结果:

    ·重复部分不做解释,只简单说明一下:

    	if(pid == 0)
    	{	
    		dup2(pipefd[1],STDOUT_FILENO);
    		close(pipefd[1]);
    		close(pipefd[0]);
    		execlp("ls","ls",NULL);
    		fprintf(stderr,"error execor execte ls\n");
    		exit(EXIT_FAILURE);
    	}
    

    这段代码的意思是:

    dup2(pipefd[1],STDOUT_FILENO);这行代码的意思是,把文件输出描述符定义到写端。

            close(pipefd[1]);
            close(pipefd[0]);

    当重定向完以后就可以关闭通道了。

            execlp("ls","ls",NULL);
            fprintf(stderr,"error execor execte ls\n");

    这两行代码是执行ls命令,因为已经输出重定向了,进管道了。

    到了父进程

        dup2(pipefd[0],STDIN_FILENO);
    这行代码重定向了读端,

        execlp("wc","wc","-w",NULL);
    读到了ls命令再加上wc -w这样就输出了 ls | wc -w的效果。


    在实现一个例子:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #define ERR_EXIT(m)				\
    	do						     \
    	{						     \
    		perror(m);				\
    		exit(EXIT_FAILURE);		\
    	}while(0)
    
    
    int main()
    {
    	close(0);
    	open("pip.cpp",O_RDONLY);
    	close(1);
    	open("01pipe.cpp",O_WRONLY | O_CREAT | O_TRUNC,0644);
    	execlp("cat","cat",NULL);
    	return 0;
    }
    

    这行代码的意思是复制代码:

    先关闭标准输出标准输出。打开pip.cpp文件,在创建一个01pipe.cpp文件,cat命令当单独使用的时候,会把键盘输入的在打印出来,由于关闭了标准输入和输出,所以会把pip.cpp的文件输出到01pipe.cpp中。假如想打印出这个代码,可以这样做。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #define ERR_EXIT(m)				\
    	do						     \
    	{						     \
    		perror(m);				\
    		exit(EXIT_FAILURE);		\
    	}while(0)
    
    
    int main()
    {
    	close(0);
    	open("04pipe.cpp",O_RDONLY);
    	execlp("cat","cat",NULL);
    	return 0;
    }
    

     

    执行结果。

    之前写过如何打印自己的代码?这个也是可以实现的。

    https://blog.csdn.net/m0_38036750/article/details/85066109

    这篇文章是写的如何打印自己的代码,效果一样,但是这样写会更简单。

     

    管道的读写规则

    当没有数据可读时:

    O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

    如果所有管道写端对应的文件描述符被关闭,则read返回0.

    如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

    当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

    当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

     

    命名管道(FIFO)

    管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

    如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

    命名管道是一种特殊类型的文件。

    创建一个命名管道:

    命名管道可以从命令行上创建,命令行方法使用下面这个命令:

    mkfifo filename
    

    命名管道也可以从程序里创建,相关函数有:

    int mkfifo(const char *filename,mode_t mode);

    代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #define ERR_EXIT(m)				\
    	do						     \
    	{						     \
    		perror(m);				\
    		exit(EXIT_FAILURE);		\
    	}while(0)
    
    
    int main()
    {
    	mkfifo("p2",0644);
    	return 0;
    }
    

    执行结果:

    p2是一个管道文件,这个和mkfifo filename相似。

     

    匿名管道和命名管道

    匿名管道由pipe函数创建并打开。

    命名管道由mkfifo函数创建,打开用open

    FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在他们创建与打开的方式不同,一但这些工作完成之后,他们具有相同的语义。

     

     

    命名管道的打开规则

    如果当前打开操作是为读而打开FIFO时

    O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

    O_NONBLOCK enable:立刻返回成功

    如果当前打开操作是为写而打开FIFO时

    O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

    O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

     

    展开全文
  • 管道限制管道半双工,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道只能用于具有共同祖先进程(具有亲缘关系进程)之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父...

    什么是管道:

    管道是Unix中最古老的进程间通信的形式。

    我们把从一个进程链接到另外一个进程的一个数据流称为管道。

    本质是有固定大小的内核缓存区。

    管道的限制

    管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道

    只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以用管道。

    匿名管道pipe

    包含头文件

    功能:创建一无名管道

    原型:

    int pipe(int fd[2]);

    参数

    fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端。

    返回值:成功返回0,失败返回错误代码。

    国际惯例:上个代码

    #include #include #include #include #include #include #include #include #include #include #include #include #define ERR_EXIT(m)\

    do \

    { \

    perror(m);\

    exit(EXIT_FAILURE);\

    }while(0)

    int main()

    {

    int pipefd[2];

    if(pipe(pipefd) == -1)

    {

    ERR_EXIT("pipe error");

    }

    pid_t pid;

    pid = fork();

    if(pid == 0)

    {

    close(pipefd[0]);

    write(pipefd[1],"hello",5);

    close(pipefd[1]);

    exit(EXIT_SUCCESS);

    }

    close(pipefd[1]);

    char buf[10] = {0};

    read(pipefd[0],buf,10);

    printf("buf = %s\n",buf);

    return 0;

    }

    解释一波:

    本次实验是子进程写一个字符串,父进程打印出来。

    从主函数开始解释:

    int pipefd[2];

    if(pipe(pipefd) == -1)

    {

    ERR_EXIT("pipe error");

    }

    第一行代码是先创建一个数组,为的就是pipe的参数。if(pipe(pipefd)==-1)这行代码的意思就是创建一个管道,如果失败的打印错误信息。

    pid_t pid;

    pid = fork();

    if(pid == 0)

    {

    close(pipefd[0]);

    write(pipefd[1],"hello",5);

    close(pipefd[1]);

    exit(EXIT_SUCCESS);

    }

    解释这些代码

    这些代码的意思是,创建一个进程(第一行和第二行),第三行代码的意思是进入子进程,pid返回0表示在子进程中,返回大于0表示在父进程中。

    再看子进程的代码:

    close(pipefd[0]);这行代码的意思是,关闭读端。因为子进程是要写字符串在管道中,所以读端就没什么用了。关闭了。

    write(pipefd[1],"hello",5);这行代码的意思就是向管道中写hello这个字符串。

    第四行close(pipefd[1]);这行代码就是关闭写端。

    再来就是子进程退出。

    close(pipefd[1]);

    char buf[10] = {0};

    read(pipefd[0],buf,10);

    printf("buf = %s\n",buf);

    return 0;

    再看这几行代码:

    这几行代码是父进程的代码,一天因为子进程执行完就退出了。所以不会执行到这里的。

    第一行代码是关闭写端。因为父进程是读子进程传来的消息,所以写端没什么用,关闭。

    第二行代码是定义一个字符串,用来存放读取到的字符串。

    第三行代码就是读取管道的信息。

    第四行就是打印。

    注:试着让子进程睡眠10s,想让父进程先执行完看看是什么效果,但是我失望了,还是这样的。就是白等了10s;

    看结果:

    17e12c9e9c9b27ed8fa831721b74955c.png

    这是为啥那?

    强行解释一波,下面代码是父子进程共用的代码,并不是父进程自己的代码,所以当子进程执行完才会执行的代码。(实际上是当没有数据可读时:O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。)

    在看一段代码:

    #include #include #include #include #include #include #include #include #include #include #include #include #define ERR_EXIT(m)\

    do \

    { \

    perror(m);\

    exit(EXIT_FAILURE);\

    }while(0)

    int main()

    {

    int pipefd[2];

    if(pipe(pipefd) == -1)

    {

    ERR_EXIT("pipe error");

    }

    pid_t pid;

    pid = fork();

    if(pid == 0)

    {

    dup2(pipefd[1],STDOUT_FILENO);

    close(pipefd[1]);

    close(pipefd[0]);

    execlp("ls","ls",NULL);

    fprintf(stderr,"error execor execte ls\n");

    exit(EXIT_FAILURE);

    }

    dup2(pipefd[0],STDIN_FILENO);

    close(pipefd[0]);

    close(pipefd[1]);

    execlp("wc","wc","-w",NULL);

    fprintf(stderr,"error execute wc\n");

    return 0;

    }

    这行代码的执行效果和 ls | wc -w

    的执行效果是一样的。

    看结果:

    ee021a9bf23d88ae34ec31dbb82f2a5f.png

    2d90afc286505ba907efe0eaf28431ee.png

    ·重复部分不做解释,只简单说明一下:

    if(pid == 0)

    {

    dup2(pipefd[1],STDOUT_FILENO);

    close(pipefd[1]);

    close(pipefd[0]);

    execlp("ls","ls",NULL);

    fprintf(stderr,"error execor execte ls\n");

    exit(EXIT_FAILURE);

    }

    这段代码的意思是:

    dup2(pipefd[1],STDOUT_FILENO);这行代码的意思是,把文件输出描述符定义到写端。

    close(pipefd[1]);

    close(pipefd[0]);

    当重定向完以后就可以关闭通道了。

    execlp("ls","ls",NULL);

    fprintf(stderr,"error execor execte ls\n");

    这两行代码是执行ls命令,因为已经输出重定向了,进管道了。

    到了父进程

    dup2(pipefd[0],STDIN_FILENO);

    这行代码重定向了读端,

    execlp("wc","wc","-w",NULL);

    读到了ls命令再加上wc -w这样就输出了 ls | wc -w的效果。

    在实现一个例子:

    #include #include #include #include #include #include #include #include #include #include #include #include #define ERR_EXIT(m)\

    do \

    { \

    perror(m);\

    exit(EXIT_FAILURE);\

    }while(0)

    int main()

    {

    close(0);

    open("pip.cpp",O_RDONLY);

    close(1);

    open("01pipe.cpp",O_WRONLY | O_CREAT | O_TRUNC,0644);

    execlp("cat","cat",NULL);

    return 0;

    }

    这行代码的意思是复制代码:

    先关闭标准输出标准输出。打开pip.cpp文件,在创建一个01pipe.cpp文件,cat命令当单独使用的时候,会把键盘输入的在打印出来,由于关闭了标准输入和输出,所以会把pip.cpp的文件输出到01pipe.cpp中。假如想打印出这个代码,可以这样做。

    #include #include #include #include #include #include #include #include #include #include #include #include #define ERR_EXIT(m)\

    do \

    { \

    perror(m);\

    exit(EXIT_FAILURE);\

    }while(0)

    int main()

    {

    close(0);

    open("04pipe.cpp",O_RDONLY);

    execlp("cat","cat",NULL);

    return 0;

    }

    执行结果。

    a2742ae12c7c27a33a202cac9471a0a5.png

    之前写过如何打印自己的代码?这个也是可以实现的。

    https://blog.csdn.net/m0_38036750/article/details/85066109

    这篇文章是写的如何打印自己的代码,效果一样,但是这样写会更简单。

    管道的读写规则

    当没有数据可读时:

    O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

    如果所有管道写端对应的文件描述符被关闭,则read返回0.

    如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

    当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

    当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

    命名管道(FIFO)

    管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

    如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

    命名管道是一种特殊类型的文件。

    创建一个命名管道:

    命名管道可以从命令行上创建,命令行方法使用下面这个命令:

    mkfifo filename

    命名管道也可以从程序里创建,相关函数有:

    int mkfifo(const char *filename,mode_t mode);

    代码:

    #include #include #include #include #include #include #include #include #include #include #include #include #define ERR_EXIT(m)\

    do \

    { \

    perror(m);\

    exit(EXIT_FAILURE);\

    }while(0)

    int main()

    {

    mkfifo("p2",0644);

    return 0;

    }

    执行结果:

    ffcb6f97ae8aece4da96555c1478c1da.png

    p2是一个管道文件,这个和mkfifo filename相似。

    匿名管道和命名管道

    匿名管道由pipe函数创建并打开。

    命名管道由mkfifo函数创建,打开用open

    FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在他们创建与打开的方式不同,一但这些工作完成之后,他们具有相同的语义。

    命名管道的打开规则

    如果当前打开操作是为读而打开FIFO时

    O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

    O_NONBLOCK enable:立刻返回成功

    如果当前打开操作是为写而打开FIFO时

    O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

    O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

    展开全文
  • Description The Classical Maya civilization developed in what is today southern Mexico, Guatemala, Belize and northern Honduras. During its height they developed a sophisticated system for time ...
  • Problem Description Given a positive integer N, your task is to calculate the sum of the positive integers less than N which are not coprime to N. A is said to be coprime to B if A, B share no common...
  • Problem Description Do you know God's birthday? Stupid Wiskey and Michelle say they didn't knew. Now, Gods want to make a game let them guess the answer. This game is a two-player alternative game...
  • Problem Description Brute force may refer to any of several problem-solving methods involving the evaluation of multiple (or every) possible answer(s) for fitness. There is a very familiar brute-...
  • 最近学完C语言后浑身精力澎湃总想做点什么东西 (结果差点被现实打的肿透了脸qwq ) 于是四处找项目,最终锁定贪吃蛇,曾经诺基亚上小游戏给我印象还是挺深,最后在浏览其他一些大佬项目后,了解具体思路就...

           这好像是第一次发博客hhh,最近学完C语言后浑身精力澎湃总想做点什么东西 (结果差点被现实打的肿透了脸qwq ) 于是四处找项目,最终锁定贪吃蛇,曾经诺基亚上的小游戏给我的印象还是挺深的,最后在浏览其他一些大佬的项目后,了解具体思路就上手尝试做了个简陋一点的版本,由于自己也是初学者,所以 代码有很多的不足,仅供交流参考


    前言

           贪吃蛇的原理较为简单,简单版本的利用数组就能对其进行主要功能的实现,当然如果有一定的C语言基础,写出这个应该是不难的。编程环境:VScode + gcc 9.2.0(为什么vscode不能装eazyX 呜呜呜)

    思路参考这位大佬的博客:https://blog.csdn.net/zs120197/article/details/88420297
    需要思路详解可以进去看看,讲的很详细。



          效果图如下:

    初始界面:
    在这里插入图片描述


    游戏界面:
    在这里插入图片描述


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、主要功能的实现

    1.界面显示:主要通过光标定位后循环打印某些字符来组成整体画面以及一些常驻信息,然后通过函数来刷新消除显示残留。


    2.果子生成:果子分为小果子跟奖励果子,每吃相应数量的小果子就会生成一个奖励果子,通过 srand() 与 rand() 函数生成 伪随机数 来实现。


    3.控制部分:通过 _kbhit() 函数及 _getch() 函数 配合if语句进行监控键盘输入,来实现 蛇的控制以及菜单移动


    4.菜单部分:这部分我并没有用传统的输入数字用switch() 函数控制,而是模仿"诺基亚"那样用键盘控制,这部分代码或许会较为冗长,各位自己优化吧嘿嘿。


    二、部分函数提要

    1.光标定位

           由于这个程序并没有应用到 eazyX 的图形库 (为什么vscode装不了呜呜呜) ,所以需要另外的函数来进行界面内光标的定位来进行具体位置的绘制。

    // 光标定位
    void gotoxy(int x,int y)
    {
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);    //取句柄
        COORD coord;    //设置光标
        coord.X = x;
        coord.Y = y;
        SetConsoleCursorPosition(handle, coord); //定位到handle这个窗口,把光标移到coord坐标
    }
    

    需要深入了解的话参考以下:
           https://baike.baidu.com/item/SetConsoleCursorPosition/575826>
           https://baike.baidu.com/item/GetStdHandle/6909878?fr=aladdin
           https://baike.baidu.com/item/coord/4594820?fr=aladdin


    2.Windows界面设置

           谁不想让自己写的程序好看点,所以这边调用了系统函数进行黑框框的美化(没有图形库最大的美化只能这样了 ╮(╯_╰)╭ )

    // CMD图形界面设置
    void WindowsInterface()
    {
        system("color 87");
        system("title Greedy Snake V1.4");
        system("mode con:cols=120 lines=29");
    }
    

    另外关于system(“color xx”)函数:
    第一个为背景色,第二个为字体颜色
    0 = 黑色       8 = 灰色
    1 = 蓝色       9 = 淡蓝色
    2 = 绿色       A = 淡绿色
    3 = 湖蓝色   B = 淡浅绿色
    4 = 红色       C = 淡红色
    5 = 紫色       D = 淡紫色
    6 = 黄色       E = 淡黄色
    7 = 白色       F = 亮白色
    ( 试了一个小时还是前白后灰最好看 )


    三、主要模块

    1. 库函数、变量与函数定义

    所需的库函数、定义的变量及结构体如下

    #include<stdio.h>
    #include<stdlib.h>
    #include<Windows.h>
    #include<time.h>
    #include<conio.h>
    #include<time.h>
    #define maphigh 28				//定义墙的长宽
    #define mapwide 84
    unsigned short Snake_size = 500, Snake_speed = 150, Snake_lenth = 3, key = 'w';	//初始化相关
    int Scores = 0, fruitBonus = 3, BigfruitBonus = 24//与分数有关
    int coodr = 12;       		//与菜单控制有关
    struct fruit		//小果子相关
    {
        int number;
        int x;
        int y;
    }fruit;
    struct Bigfruit		//奖励果子相关
    {
        int number;
        int x;
        int y;
    }Bigfruit;
    struct snake		//蛇主体相关
    {
        int x[500];
        int y[500];
        int len;
        int speed;
    }snake;
    
    int Deathjudge();
    void Menu();
    void Game();
    void VertionInformation();
    void WindowsInterface();
    void gotoxy(int x,int y);
    void drawmenu();
    void drawmap();
    void ClearMenu();
    void ClearGames();
    void control();
    void Initialize();
    void creatfruit();
    void CreatBigFruit();
    void Score();
    //以上是所需的库函数与定义
    


    2. 游戏主体

    游戏的主体主要由绘制、控制、果子生成以及死亡判定组成。先附上游戏主体的主函数:

    // 游戏主程序
    void Game()
    {
        Initialize();
        for(;;)							//空循环实现不断运行游戏(游戏结束不关闭)
        {
            system("cls");				//系统清屏函数
            drawmap();					//游戏初始界面绘制
            getch();
            for(;;)
            {
                control();				//控制函数
                creatfruit();			//果子生成函数(包含奖励果子)
                Score();				//分数系统
                Sleep(Snake_speed);     //void sleep(int seconds),参数 seconds为要暂停的毫秒数。
                if (!Deathjudge())     //死亡条件,死亡时 Deathjudge = 0
                {
                    gotoxy(mapwide/2,maphigh/2);
                    printf("GAME OVER!!!");
                    getchar();
                    break;				//游戏结束时开启新一轮循环
                }
            }
        }
    }
    


      2.1  绘制部分

    绘制部分 利用打印与刷新来保证内容显示不出错,以下是初始界面绘制:

    // 绘制游戏界面
    void drawmap()
    {
        int i,k;
    
        //绘制地图
        for ( i = 0; i <= mapwide; i += 2)      //绘制上下边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)         //绘制左右边界
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
        //绘制蛇
        snake.len = Snake_lenth;
        snake.speed = Snake_speed;
        snake.x[0] = mapwide/2;
        snake.y[0] = maphigh/2;
    
        gotoxy(snake.x[0],snake.y[0]);          //蛇头
        printf("█");
        for ( k = 1; k < snake.len; k++)        //蛇身
        {
            snake.x[k] = snake.x[k - 1] + 2;
            snake.y[k] = snake.y[k - 1];
            gotoxy(snake.x[k],snake.y[k]);
            printf("█");
        }
        
        srand((unsigned int)time(NULL));        //srand()与rand()函数随机生成果子位置
        while(1)
        {
            fruit.x = rand() % (mapwide - 4 ) + 1;       // + 1 与果子的符号所占的字符有关
            fruit.y = rand() % (maphigh - 2 ) + 1;
            if (fruit.x % 2 == 0)
                break;
        }
        gotoxy(fruit.x,fruit.y);				//绘制小果子
        printf("*");
    
        //右侧内容标注
        gotoxy(mapwide + 8, 6);					//移动到右侧标注位置
    	printf(" W");
    	gotoxy(mapwide + 6, 8);
    	printf("A  S  D    进行控制");
        gotoxy(mapwide + 6, 12);
    	printf("按 '空格' 以暂停");
        gotoxy(mapwide + 6, 18);
    	printf("Scores : %d",Scores);
    }
    
    

           以下部分是 游戏界面刷新 ,由于绘制会产生显示残留,目前所掌握的知识也没有更好的办法,所以就只能通过刷新后再绘制来进行消除显示残留,希望后续有大佬可以优化一下,代码如下:

    // 刷新游戏界面(清屏并绘制常驻信息)
    void ClearGames()
    {
        int i,k;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
        gotoxy(snake.x[0],snake.y[0]);          //绘制蛇
        printf("█");
        for ( k = 1; k < snake.len; k++)
        {
            gotoxy(snake.x[k],snake.y[k]);
            printf("█");
        }
        gotoxy(fruit.x,fruit.y);                //绘制果子
        printf("*");
        gotoxy(mapwide + 8, 6);                 //绘制右侧信息
    	printf(" W");
    	gotoxy(mapwide + 6, 8);
    	printf("A  S  D    进行控制");
        gotoxy(mapwide + 6, 12);
    	printf("按 '空格' 以暂停");
        gotoxy(mapwide + 6, 18);
    	printf("Scores : %d",Scores);
    }
    
    


      2.2  控制部分

    控制模块由 _kihit()函数与 _getch()函数配合if 语句构成。当键盘键入WASD或空格时进行对应操作。

    // 控制模块
    void control()
    {
        int i,temp;
        if (_kbhit())           //kbhit函数监控当前是否有键盘输入
        {
            fflush(stdin);      //清空缓冲区
            temp = _getch();
            if((temp == 'a' || temp == 'A') && (key !='D' && key !='d')) //防止反方向输入导致自杀
            {
                key = temp;
            }
            if((temp == 'd' || temp == 'D') && (key !='a' && key !='A'))
            {
                key = temp;
            }
            if((temp == 'w' || temp == 'W') && (key !='S' && key !='s'))
            {
                key = temp;
            }
            if((temp == 's' || temp == 'S') && (key !='w' && key !='W'))
            {
                key = temp;
            }
            if((temp == ' '))		//实现键入'空格'暂停
            {
                getch();
            }
        }
    
        gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);     //删去蛇的最后一节
        printf("  ");
    
        for ( i = snake.len-1 ; i>0 ; i-- )
        {
            snake.x[i] = snake.x[i-1];
            snake.y[i] = snake.y[i-1];
        }
    
        switch (key)
        {
            case 'w':
            case 'W':
                snake.y[0]--;
                break;
    
            case 's':
            case 'S':
                snake.y[0]++;
                break;
    
            case 'a':
            case 'A':
                snake.x[0]-=2;
                break;
    
            case 'd':
            case 'D':
                snake.x[0]+=2;
                break;
        }
    
        gotoxy(snake.x[0],snake.y[0]);
        printf("█");
    }
    


      2.3  果子生成

           果子生成分为小果子与奖励果子,均通过srand()函数与rand()函数实现随机生成,由于先前绘制游戏界面时已生成小果子,所以只需要进行生成新果子即可,小果子生成代码如下:

    void creatfruit()
    {
        if(snake.x[0] == fruit.x && snake.y[0] == fruit.y)        //当蛇吃掉小果子
        {
            snake.len++;
            fruit.number++;
            snake.speed -= 1;			//使身长越长速度越快
            Bigfruit.x = 0;            //防止大果子一直存在(小果子被吃掉时大果子消失)
            Bigfruit.y = 0;
            if(fruit.number %5 == 0)	//当吃掉 5个小果子时,生成一个奖励果子
            {
                CreatBigFruit();
            }
            
            srand((unsigned)time(NULL));
            
            while(1)
            {
                int tag = 1;
                fruit.x = rand() % (mapwide - 4 ) + 1;
                fruit.y = rand() % (maphigh - 2 ) + 1;
    
                for (int k = 0; k < snake.len; k++)         //防止fruit生成在蛇身上
                {
                    if (snake.x[k] == fruit.x && snake.y[k] == fruit.y)     //判断是否在蛇身上
                    {
                        tag = 0;
                        break;
                    }
                }
    
                if (tag == 1 && fruit.x %2 ==0)break;		//仅有当tag = 1时的小果子坐标才会被应用
            }
        }
        gotoxy(fruit.x,fruit.y);						//取小果子坐标并打印
        printf("*");
    
        if(snake.x[0] == Bigfruit.x && snake.y[0] == Bigfruit.y)        //当蛇吃大果子时
        {
            // printf("\a");
            snake.len+=2;
            Bigfruit.number++;
            snake.speed -= 2;
            ClearGames();           //刷新函数,防止果子被吃掉后仍残留在屏幕上
            Bigfruit.x = 0;
            Bigfruit.y = 0;
        }
    }
    

    此外,生成奖励果子的代码如下:

    // 生成奖励果子(原理同上)
    void CreatBigFruit()
    {
        int ret = 1;
        srand((unsigned)time(NULL));
            while(1)
            {
                Bigfruit.x = rand() % (mapwide- 8) + 2;
                Bigfruit.y = rand() % (maphigh- 4) + 2;
    
                for (int k = 0; k < snake.len; k++)         //防止 Bigfruit生成在蛇身上
                {
                    if (snake.x[k] == Bigfruit.x && snake.y[k] == Bigfruit.y)     //判断是否在蛇身上
                    {
                        ret = 0;
                        break;
                    }
                }
                if (ret == 1 && Bigfruit.x %2 ==0)break;
            }
        gotoxy(Bigfruit.x,Bigfruit.y);
        printf("★");
    }
    


      2.4  死亡判定

    死亡判断较简单,仅需判断 蛇头坐标墙体蛇身 是否重合即可。

    // 死亡判定
    int Deathjudge()
    {
        int ret;
        if ((snake.x[0] == 0 || snake.x[0] == mapwide) || (snake.y[0] == 0 || snake.y[0] == maphigh))
            return 0;
            
        for (int i = 1; i < snake.len; i++)
        {
            if(snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i])
                return 0;
        }
        return 1;
    }
    

    3. 初始菜单

    首先绘制初始的菜单界面,由于箭头光标进行上下移动时会产生显示残留,所以需要一个菜单的刷新函数,最后就是菜单光标的控制

      3.1  绘制菜单

    绘制初始菜单界面,代码如下:

    // 绘制菜单界面
    void drawmenu()
    {
        int i;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
        gotoxy(mapwide + 9, 2);					//绘制右侧信息栏
        printf("Greedy Snake V1.4");
        gotoxy(mapwide + 8, 6);
    	printf("按 W 或 S 进行控制.");
        gotoxy(mapwide + 9, 8);
        printf("按 空格 进行选择.");
        
        gotoxy(mapwide + 9, coodr);
        printf("->");
        gotoxy(mapwide + 13, coodr);
        printf("开始游戏");
        gotoxy(mapwide + 13, coodr + 3);
        printf("版本信息");
        gotoxy(mapwide + 13, coodr + 6);
        printf("退出游戏");
    }
    

      3.2  刷新菜单

    防止光标移动后在原先位置留下残留,刷新界面后绘制常驻信息

    void ClearMenu()
    {
        int i;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);    
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
        gotoxy(mapwide + 9, 2);					//右侧信息栏
        printf("Greedy Snake V1.4");
        gotoxy(mapwide + 8, 6);
    	printf("按 W 或 S 进行控制.");
        gotoxy(mapwide + 9, 8);
        printf("按 空格 进行选择.");
        gotoxy(mapwide + 13, coodr);
        printf("开始游戏");
        gotoxy(mapwide + 13, coodr + 3);
        printf("版本信息");
        gotoxy(mapwide + 13, coodr + 6);
        printf("退出游戏");
    }
    

      3.4  菜单控制

    为了还原诺基亚的感觉, 菜单 的实现并没有直接使用switch语句接收键盘输入的数字,而是用_kbhit() 函数监控键盘输入来实现键入特定字符来上下移动"光标" 与功能选择。

    void Menu()
    {
        int i = 1, end = 3;
        int temp;
        drawmenu();         //绘制菜单
        for (;;)            //光标控制
        {
            if (_kbhit())
            {
                fflush(stdin);
                temp = _getch();
                
                if(temp == 'w' || temp == 'W')		//当检测到输入w时
                {
                    if(i>1 && i<= end)
                    {
                        ClearMenu();			//刷新菜单界面
                        i--;
                        gotoxy(mapwide + 7,coodr + 3*(i-1));
                        printf("  ");
                        printf("->");
    
                    }
                }
                if(temp == 's' || temp == 'S')		//当检测到输入s时
                {
                    if(i>=1 && i< end)
                    {
                        ClearMenu();
                        i++;
                        gotoxy(mapwide + 7,coodr + 3*(i-1));
                        printf("  ");
                        printf("->");
                    }
                }
    
                if(temp == ' ')         //空格选择功能
                {
                    break;
                }
            }
        }
    
        switch (i)
        {
        case 1:
            Game();
            break;
        
        case 2:
            VertionInformation();
            return Menu();				//回到菜单
    
        case 3:
            exit(0);					//自带的退出程序函数
        }
    }
    

    4. 主函数入口

    由于添加了菜单界面,所以主函数入口应该从菜单进入以保持循环不断。

    int main()
    {
        WindowsInterface();			//设置 CMD 参数
        Menu();
        return 0;
    }
    

    5. 源代码

    全部源代码如下:

    #include<stdio.h>
    #include<stdlib.h>
    #include<Windows.h>
    #include<time.h>
    #include<conio.h>
    #include<time.h>
    #define maphigh 28
    #define mapwide 84
    unsigned short Snake_size = 500, Snake_speed = 150, Snake_lenth = 3, key = 'w';
    int Scores = 0, fruitBonus = 3, BigfruitBonus = 24, coodr = 12;
    char vertion[15] = "V1.4";
    struct fruit
    {
        int number;
        int x;
        int y;
    }fruit;
    struct Bigfruit
    {
        int number;
        int x;
        int y;
    }Bigfruit;
    struct snake
    {
        int x[500];
        int y[500];
        int len;
        int speed;
    }snake;
    
    int Deathjudge();
    void Menu();
    void Game();
    void VertionInformation();
    void WindowsInterface();
    void gotoxy(int x,int y);
    void drawmenu();
    void drawmap();
    void ClearMenu();
    void ClearGames();
    void control();
    void Initialize();
    void creatfruit();
    void CreatBigFruit();
    void Score();
    
    int main()
    {
        WindowsInterface();
        Menu();
        return 0;
    }
    
    
    /************************************************************************************/
    // 游戏主程序
    void Game()
    {
        Initialize();
        for(;;)
        {
            system("cls");
            drawmap();
            getch();
            for(;;)
            {
                control();
                creatfruit();
                Score();
                Sleep(Snake_speed);     //void sleep(int seconds),参数 seconds 为要暂停的毫秒数。
                if (!Deathjudge())     //死亡时Deathjudge = 0
                {
                    gotoxy(mapwide/2,maphigh/2);
                    printf("GAME OVER!!!");
                    getchar();
                    break;
                }
            }
        }
    }
    /************************************************************************************/
    // CMD图形界面设置
    void WindowsInterface()
    {
        system("color 87");
        system("title Greedy Snake V1.4");
        system("mode con:cols=120 lines=29");
    }
    /************************************************************************************/
    // 初始化
    void Initialize()
    {
    	fruit.number = 0;
    	Bigfruit.number = 0;
    }
    /************************************************************************************/
    // 光标定位
    void gotoxy(int x,int y)
    {
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);    //取句柄
        COORD coord;    //设置光标
        coord.X = x;
        coord.Y = y;
        SetConsoleCursorPosition(handle, coord); //定位到handle这个窗口,把光标移到coord坐标
    }
    /************************************************************************************/
    // 绘制游戏界面
    void drawmap()
    {
        int i,k;
    
        //绘制地图
        for ( i = 0; i <= mapwide; i += 2)      //绘制上下边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)         //绘制左右边界
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
    
        //绘制蛇
        snake.len = Snake_lenth;
        snake.speed = Snake_speed;
        snake.x[0] = mapwide/2;
        snake.y[0] = maphigh/2;
    
        gotoxy(snake.x[0],snake.y[0]);          //蛇头
        printf("█");
        for ( k = 1; k < snake.len; k++)        //蛇身
        {
            snake.x[k] = snake.x[k - 1] + 2;
            snake.y[k] = snake.y[k - 1];
            gotoxy(snake.x[k],snake.y[k]);
            printf("█");
        }
    
        srand((unsigned int)time(NULL));        //随机生成果子位置
        while(1)
        {
            fruit.x = rand() % (mapwide - 4 ) + 1;       // + 1 与 * 所占的字符有关
            fruit.y = rand() % (maphigh - 2 ) + 1;
            if (fruit.x % 2 == 0)
                break;
        }
        gotoxy(fruit.x,fruit.y);
        printf("*");
    
        //右侧标注
        gotoxy(mapwide + 8, 6);
    	printf(" W");
    	gotoxy(mapwide + 6, 8);
    	printf("A  S  D    进行控制");
        gotoxy(mapwide + 6, 12);
    	printf("按 '空格' 以暂停");
        gotoxy(mapwide + 6, 18);
    	printf("Scores : %d",Scores);
    }
    /************************************************************************************/
    // 刷新游戏
    void ClearGames()
    {
        int i,k;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
        gotoxy(snake.x[0],snake.y[0]);          //绘制蛇
        printf("█");
        for ( k = 1; k < snake.len; k++)
        {
            gotoxy(snake.x[k],snake.y[k]);
            printf("█");
        }
    
        gotoxy(fruit.x,fruit.y);                //绘制果子
        printf("*");
    
        gotoxy(mapwide + 8, 6);                 //绘制右侧信息
    	printf(" W");
    	gotoxy(mapwide + 6, 8);
    	printf("A  S  D    进行控制");
        gotoxy(mapwide + 6, 12);
    	printf("按 '空格' 以暂停");
        gotoxy(mapwide + 6, 18);
    	printf("Scores : %d",Scores);
    }
    /************************************************************************************/
    // 绘制菜单界面
    void drawmenu()
    {
        int i;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
        gotoxy(mapwide + 9, 2);
        printf("Greedy Snake V1.4");
        gotoxy(mapwide + 8, 6);
    	printf("按 W 或 S 进行控制.");
        gotoxy(mapwide + 9, 8);
        printf("按 空格 进行选择.");
    
        gotoxy(mapwide + 9, coodr);
        printf("->");
        gotoxy(mapwide + 13, coodr);
        printf("开始游戏");
        gotoxy(mapwide + 13, coodr + 3);
        printf("版本信息");
        gotoxy(mapwide + 13, coodr + 6);
        printf("退出游戏");
    }
    /************************************************************************************/
    // 刷新菜单
    void ClearMenu()
    {
    
        int i;
        system("cls");
        for ( i = 0; i <= mapwide; i += 2)      //绘制边界
        {
            gotoxy(i, 0);
            printf("█");
            gotoxy(i, maphigh);
            printf("█");
        }
        for ( i = 0; i <= maphigh; i++)
        {
            gotoxy(0,i);    
            printf("█");
            gotoxy(mapwide,i);
            printf("█");
        }
    
        gotoxy(mapwide + 9, 2);
        printf("Greedy Snake V1.4");
        gotoxy(mapwide + 8, 6);
    	printf("按 W 或 S 进行控制.");
        gotoxy(mapwide + 9, 8);
        printf("按 空格 进行选择.");
    
        gotoxy(mapwide + 13, coodr);
        printf("开始游戏");
        gotoxy(mapwide + 13, coodr + 3);
        printf("版本信息");
        gotoxy(mapwide + 13, coodr + 6);
        printf("退出游戏");
    }
    /************************************************************************************/
    // 菜单控制
    void Menu()
    {
        int i = 1, end = 3;
        int temp;
    
        drawmenu();         //绘制菜单
    
        for (;;)            //光标控制
        {
            if (_kbhit())
            {
                fflush(stdin);
                temp = _getch();
    
                if(temp == 'w' || temp == 'W')
                {
                    if(i>1 && i<= end)
                    {
                        ClearMenu();
                        i--;
                        gotoxy(mapwide + 7,coodr + 3*(i-1));
                        printf("  ");
                        printf("->");
    
                    }
                }
                if(temp == 's' || temp == 'S')
                {
                    if(i>=1 && i< end)
                    {
                        ClearMenu();
                        i++;
                        gotoxy(mapwide + 7,coodr + 3*(i-1));
                        printf("  ");
                        printf("->");
                    }
                }
    
                if(temp == ' ')         //空格选择
                {
                    break;
                }
            }
        }
    
        switch (i)
        {
        case 1:
            Game();
            break;
        
        case 2:
            VertionInformation();
            return Menu();
    
        case 3:
            exit(0);
        }
    }
    /************************************************************************************/
    // 生成新果子
    void creatfruit()
    {
        if(snake.x[0] == fruit.x && snake.y[0] == fruit.y)        //当蛇吃掉小果子
        {
            snake.len++;
            fruit.number++;
            snake.speed -= 2;
            Bigfruit.x = 0;            //防止大果子一直存在(小果子被吃掉时大果子消失)
            Bigfruit.y = 0;
            if(fruit.number %5 == 0)
            {
                CreatBigFruit();
            }
            srand((unsigned)time(NULL));
            while(1)
            {
                int tag = 1;
                fruit.x = rand() % (mapwide - 4 ) + 1;
                fruit.y = rand() % (maphigh - 2 ) + 1;
    
                for (int k = 0; k < snake.len; k++)         //防止fruit生成在蛇身上
                {
                    if (snake.x[k] == fruit.x && snake.y[k] == fruit.y)     //判断是否在蛇身上
                    {
                        tag = 0;
                        break;
                    }
                }
    
                if (tag == 1 && fruit.x %2 ==0)break;
            }
        }
        gotoxy(fruit.x,fruit.y);
        printf("*");
    
        if(snake.x[0] == Bigfruit.x && snake.y[0] == Bigfruit.y)        //当蛇吃大果子时
        {
            snake.len+=2;
            Bigfruit.number++;
            snake.speed -= 3;
            ClearGames();           //防止果子被吃掉后仍残留在屏幕上
            Bigfruit.x = 0;
            Bigfruit.y = 0;
        }
    }
    /************************************************************************************/
    // 生成奖励果子
    void CreatBigFruit()
    {
        int ret = 1;
        srand((unsigned)time(NULL));
            while(1)
            {
                Bigfruit.x = rand() % (mapwide- 8) + 2;
                Bigfruit.y = rand() % (maphigh- 4) + 2;
    
                for (int k = 0; k < snake.len; k++)         //防止fruit生成在蛇身上
                {
                    if (snake.x[k] == Bigfruit.x && snake.y[k] == Bigfruit.y)     //判断是否在蛇身上
                    {
                        ret = 0;
                        break;
                    }
                }
    
                if (ret == 1 && Bigfruit.x %2 ==0)break;
            }
        gotoxy(Bigfruit.x,Bigfruit.y);
        printf("★");
    }
    /************************************************************************************/
    // 控制模块
    void control()
    {
        int i,temp;
        if (_kbhit())           //kbhit函数检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
        {
            fflush(stdin);      //清空缓冲区
            temp = _getch();
            if((temp == 'a' || temp == 'A') && (key !='D' && key !='d'))        //防止反方向导致自杀
            {
                key = temp;
            }
            if((temp == 'd' || temp == 'D') && (key !='a' && key !='A'))
            {
                key = temp;
            }
            if((temp == 'w' || temp == 'W') && (key !='S' && key !='s'))
            {
                key = temp;
            }
            if((temp == 's' || temp == 'S') && (key !='w' && key !='W'))
            {
                key = temp;
            }
            if((temp == ' '))
            {
                getch();
            }
        }
    
        gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);     //删去最后一节
        printf("  ");
    
        for ( i = snake.len-1 ; i>0 ; i-- )
        {
            snake.x[i] = snake.x[i-1];
            snake.y[i] = snake.y[i-1];
        }
    
        switch (key)
        {
            case 'w':
            case 'W':
                snake.y[0]--;
                break;
    
            case 's':
            case 'S':
                snake.y[0]++;
                break;
    
            case 'a':
            case 'A':
                snake.x[0]-=2;
                break;
    
            case 'd':
            case 'D':
                snake.x[0]+=2;
                break;
        }
    
        gotoxy(snake.x[0],snake.y[0]);
        printf("█");
        gotoxy(mapwide + 2,0);          //移走光标
    }
    /************************************************************************************/
    // 死亡判定
    int Deathjudge()
    {
        int ret;
        if ((snake.x[0] == 0 || snake.x[0] == mapwide) || (snake.y[0] == 0 || snake.y[0] == maphigh))
            return 0;
            
        for (int i = 1; i < snake.len; i++)
        {
            if(snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i])
                return 0;
        }
        return 1;
    }
    
    // 分数系统
    void Score()
    {
        Scores = fruit.number *fruitBonus + Bigfruit.number*BigfruitBonus;
        gotoxy(mapwide + 6, 18);
    	printf("Scores : %d",Scores);
    }
    /************************************************************************************/
    // 版本信息
    void VertionInformation()
    {
        ClearMenu();
    
        gotoxy(28,6);
        printf("\tGreedy Snake");
        gotoxy(28,9);
        printf("作者:北");
        gotoxy(28,12);
        printf("更新时间:2020.11.29");
        gotoxy(28,15);
        printf("版本:V1.4");
        gotoxy(28,18);
        printf("版本说明: 1.更新初始菜单界面");
        gotoxy(28,20);
        printf("\t      2.修复奖励果子一直存在的BUG");
    
        getch();
    }
    

    总结

           贪吃蛇这个小程序对于初学者而言,是个很好的练手目标,编写它花费了大约四天时间,其中调试占用了绝大多数的时间。期间也会遇到很多令人崩溃的难点,但是现在的互联网完全有能力让你自主解决这些问题,当你不断解决一个个出现的问题后,你就能显著的发现你的能力提升了不少,今后希望可以继续上手不同的项目,不断提升自己的能力

    展开全文
  • 词法分析器c语言编写 当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法? 开始听起来很自然。 毕竟,如果我们不对所有内容进行测试,那么如何知道我们的代码可以按预期工作呢? 这问题为我们...
    词法分析器c语言编写

    词法分析器c语言编写

    当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法?

    一开始听起来很自然。 毕竟,如果我们不对所有内容进行测试,那么如何知道我们的代码可以按预期工作呢?

    这个问题为我们提供了重要的线索:

    我们的代码

    我们应该只对自己的代码编写测试。

    什么是我们自己的代码?

    有时很难确定我们应该测试的代码。 这是因为我们的数据访问代码与将信息保存到使用的数据存储中或从中读取信息时所使用的库或框架紧密集成在一起。

    例如,如果我们要创建一个向Todo对象提供CRUD操作的Spring Data JPA存储库,则应创建一个扩展CrudRepository接口的接口。 TodoRepository接口的源代码如下所示:

    import org.springframework.data.repository.CrudRepository;
    
    public TodoRepository extends CrudRepository<Todo, Long> {
    
    }

    即使我们没有向存储库接口添加任何方法, CrudRepository接口也声明了许多可供使用我们存储库接口的类使用的方法。

    这些方法不是我们的代码,因为它们是由Spring Data团队实现和维护的。 我们只使用它们。

    另一方面,如果我们向存储库中添加自定义查询方法,情况将会改变。 假设我们必须找到标题等于给定搜索词的所有待办事项。 在将此查询方法添加到我们的存储库接口之后,其源代码如下所示:

    import org.springframework.data.repository.CrudRepository;
    import org.springframework.data.repository.query.Param;
    
    public TodoRepository extends CrudRepository<Todo, Long> {
    
    	@Query("SELECT t FROM Todo t where t.title=:searchTerm")
    	public List<Todo> search(@Param("searchTerm") String searchTerm)
    }

    可以很容易地断言该方法是我们自己的代码,这就是为什么我们应该对其进行测试。 但是,事实有点复杂。 即使JPQL查询是由我们编写的,Spring Data JPA也会提供将查询转发给使用过的JPA提供程序的代码。

    而且,我仍然认为该查询方法是我们自己的代码,因为其中最重要的部分是由我们编写的。

    如果要标识自己的数据访问代码,则必须找到每种方法的基本部分。 如果这部分是我们编写的,则应将该方法视为自己的代码。

    这一切都是显而易见的,更有趣的问题是:

    我们应该测试吗?

    我们的存储库接口为使用它的类提供了两种方法:

    1. 它提供了由CrudRepository接口声明的方法。
    2. 它提供了我们编写的查询方法。

    我们是否应该将集成测试编写到TodoRepository接口并测试所有这些方法?

    不,我们不应该这样做,因为

    1. CrudRepository接口声明的方法不是我们自己的代码。 这段代码是由Spring Data团队编写和维护的,他们已经确保它可以工作。 如果我们不相信他们的代码有效,那么我们就不应该使用它。
    2. 我们的应用程序可能有许多存储库接口,这些接口扩展了CrudRepository接口。 如果决定对CrudRepository接口声明的方法编写测试,则必须对所有存储库编写这些测试。 如果选择这种方式,我们将花费大量时间对其他人的代码编写测试,坦率地说,这样做是不值得的。
    3. 我们自己的代码可能是如此简单,以至于无法将测试写入我们的存储库。

    换句话说,我们应该集中精力寻找这个问题的答案:

    我们应该将集成测试写入我们的存储库方法(由我们编写的方法),还是只编写端到端测试?

    这个问题的答案取决于我们存储库方法的复杂性。 我知道复杂性是一个模糊的词,这就是为什么我们需要某种准则来帮助我们找到测试存储库方法的最佳方法的原因。

    做出此决定的一种方法是考虑测试每种可能情况所需的工作量。 这是有道理的,因为:

    1. 将集成测试写入单个存储库方法比将相同的测试写入使用存储库方法的功能所需的工作更少。
    2. 无论如何,我们都必须端对端地编写。

    这就是为什么最小化我们的投资(时间)和最大化我们的利润(测试覆盖率)的原因。 我们可以按照以下规则进行操作:

    • 如果我们只编写几个测试就可以测试所有可能的场景,那么我们就不应该浪费时间将集成测试写入我们的存储库方法。 我们应该编写端到端测试,以确保该功能按预期工作。
    • 如果我们需要编写多个测试,则应将集成测试编写到我们的存储库方法中,而仅编写一些端到端测试(烟雾测试)。

    概要

    这篇博客文章教会了我们两件事:

    • 我们不应该浪费时间将测试编写到其他人编写的数据访问框架(或库)中。 如果我们不信任该框架(或库),则不应使用它。
    • 有时我们也不应该对数据访问代码编写集成测试。 如果经过测试的代码足够简单(我们可以通过编写一些测试来涵盖所有情况),则应该通过编写端到端测试来对其进行测试。

    翻译自: https://www.javacodegeeks.com/2014/07/writing-tests-for-data-access-code-dont-test-the-framework.html

    词法分析器c语言编写

    展开全文
  • 据说魔法世界有100000种不同魔咒,哈利很难全部记住,但是为了对抗强敌,他必须在危急时刻能够调用任何一个需要魔咒,所以他需要你帮助。 给你一部魔咒词典。当哈利听到一个魔咒时,你程序必须告诉他那个...
  •  第步:所要做的是什么东西;第二步:先把大概格式输进去(如:头文件);第三步:定义变量(不用下子全部加,后面需要再往前加);第四步:函数;第五步:分析(边做边想,分析下这是否合理);第六步:编译...
  • 什么是素数? 一个大于1正整数,如果除了1和它本身以外,不能被其他正...而如何用c语言编写一个代码去判断一个数字是否素数,一道非常经典且容易出错习题。 话不多说,先上一套代码 在这里插入代码片 ...
  • 这个游戏这样,首先度度熊拥有一个公差集合{D},然后它依次写下N个数字排成一行。游戏规则很简单: 1.在当前剩下有序数组中选择X(X≥2) 个连续数字; 2.检查1 选择X个数字是否构成等差数列,且公差 d...
  • 你们大学四年接触到的第一个高级语言什么是叫做编译:将我们所编写的C语言代码转换成机器可以识别的指令的过程,叫做编译一、认识编译器1.编译器界面①工具区:编译,编译运行,查找,查找替换等功能按键区域②...
  • C语言编写Web服务器

    2021-05-06 21:37:24
    相关视频——C/C++技术教学:web 网络服务器开发!纯C语言手写web服务器,仅需 80...端口就好比一个房子门,初入这个房子必经之路。 端口号 端口通过端口号来标记,端口号只有整数,范围从0到65535。 (.
  • 一个使用C语言编写的快速,超轻量级服务器,用于CPython 2和CPython 3,它使用Marc Lehmann的高性能事件循环和Ryan Dahl的用C语言编写。 为什么很酷 bjoern目前最快,最小,最轻便的WSGI服务器,具有以下特点: ...
  • 利用c语言编写三子棋的代码,相对来说比较简单,三子棋其实就可以看作以个三行三列的二维数组,然后对其里面的每个元素进行赋值。因此我们首先需要理清楚三子棋完成的思路。 1.首先我们需要一个棋盘,所以需要...
  • C语言编写水仙花数

    万次阅读 多人点赞 2019-05-12 13:08:05
    水仙花数一个 3 位数,它每个位上数字 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153)。 代码如下: #include<stdio.h> void main() { int i,a,b,c; for(i=100;i<1000;i++)//取值范围...
  • 输入数据第一行是一个数据T,表示有T组数据。 每组数据有两个正整数A和B(A,B^9)。 Output 对于每组输入数据,输出"YES"表示可以被整除,"NO"表示不能被整除。 Sample Input 2 4 2 5 3 ...
  • 输入数据包含多组测试用例,每个测试用例第一行包含一个整数n,表示一共有n个互不相同点,接下来n行每行包含2个整数xi,yi,表示平面上第i个点x与y坐标。你可以认为:3 而且 -10000 , yi Output 对于...
  • 换行。printf(“bai”)du表示输出换行符,“”是个zhi转义字符,...扩展资料:C语言的主要特点如下:1、简洁语言C语言包含有各种控制语句仅有9种,关键字也只有32 ,程序的编写要求不严格且多以小写字母为主,对...
  • C语言编写猜数字小游戏

    千次阅读 2021-01-21 21:28:56
    经过对C语言的初步学习,为提高编程能力,可以敲一些稍费脑筋的代码,猜数字就是一个很好的选择。要想实现该游戏,那么首先我们应该知道制作的大体思路。 电脑应随机产生一个数字(为便于玩家猜的同时,增加难度和...
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    答:大学过程是一个理论过程,实践机会比较少,往往会造成理论与实践相脱节,这是国内大学教育系统通病,不过对于学生来说切不可好高骛远。一般从大三会开始接触到一些专业课程,电子相关专业会开设相关...
  • C语言 编写“剪刀石头布”小游戏

    千次阅读 多人点赞 2020-11-29 17:54:33
    是一C语言初学者,学了C语言基础后,我制作了一个小游戏:剪刀石头布。 希望大家能对我思路和代码提出小Tips(eg.更简便方法与程序)我也会虚心接受大家建议~ 一、游戏原理 “剪刀石头布”这个游戏,想必...
  • 本篇博客将介绍如何用VS2015创建环境来编写C语言代码,下面进入正文: 1.创建一个项目 先打开VS2015 选择新建项目 先选择Visual C++,然后选择空项目,接着更改名称和存储位置 比如我将名称改为test4_9,然后点击...
  • 知道他们在危险电源电压附近正在做什么的人可以使用此代码作为一个真正好交流电表起点: 这简短视频 硬件项目 许可GPL 3.0。 使用风险自负。 对人身伤害,死亡或财产损失不承担任何责任。
  • 在使用C语言编写DES算法中,为什么只有第轮输出了正确数据,其余15轮数据都错误,写函数没有问题呀,崩溃,希望各位大佬给看看!以下为代码: #include<stdio.h> ///十进制转二进制 void ...
  • 我们说boot loader通常采用汇编和C语言相结合来编写的,那能不能全部用C语言来进行编写呢?为什么? 对于这一问题的答案:不能。...那C程序中函数调用要一个什么样的环境呢?堆栈!我们写C程序时在
  • C语言编写学生成绩信息管理系统

    千次阅读 多人点赞 2020-07-06 18:27:13
    C语言设计学生成绩信息管理系统介绍代码结构体数组定义main_interface()函数add_infor()函数browse_infor()函数 介绍 软件方面采用的是Visual Studio 2019 IDE工具。 程序方面采用结构体数组,成员包括学号、...
  • 什么要用C语言写Python模块,Python不够香么?还是觉得头发还茂盛?都不是。因为C语言模块有几显而易见好处:可以使用Python调用C标准库、系统调用等;假设已经有了堆C代码实现功能,可以不用重写,岂不...
  • C语言与汇编语言的最大区别是什么,或者说,两者之间有没有...由于编写的代码非常难懂,不好维护,所以出现一些面向过程的语言,C就是其中之。C可以说是中级语言。它把高级语言的基本结构和语句与低级语言的实用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,072
精华内容 428
关键字:

c语言编写的代码是一个什么

c语言 订阅