目录
今天这篇文章,我们将综合我们前面所学的知识,制作出第一个小游戏——猜数字小游戏。
一,原理及问题分析
游戏整体流程:首先我们需要一个开场的动画还有菜单,简单的介绍以下游戏的相关信息,然后在游戏的主体部分,既然要猜数字,就要生成1—100的随机数,然后用户会输入数字,我们就需要把用户输入的数字与我们生成的随机数字进行比较判断,并输入返回结果。为了让程序更加完整,一场游戏结束后我们并不会直接退出,还可以让用户选择是否继续游玩,如果继续游玩,则进入下一次游戏,如果选择退出,则退出程序。为了让程序更加清晰富有层次,我们将以上的每一个功能封装成函数,一个函数实现一个功能。
主要原理:随机数生成,输入输出,循环语句,分支语句,字符移动效果原理,函数模块化实现。
二,代码实现
万事开头main函数,无论一个程序怎么变,我们先把这个整体的框架先给写好。
2.1 开场动画实现
然后我们的思路是进入游戏以后先进行一段简陋的开场动画,其实也就是一行的字符从中间到两端不断地移动,这个问题是不是很熟悉?没错,其实就是我们上一篇文章中的练习9,具体的原理在那篇文章中已经详细将结果,不再赘述。不过与练习9不同的是,我们上面说了,每个功能都力求实现一个函数进行封装,这样子可以让我们的代码更有层次,清晰,不至于全堆积到main函数里面。所以我们定义一个函数,void flash(),它的类型是void,也就是不会返回参数,所以后面括号没有参数,然后进入这个函数里面就相当于运行一个功能,这个功能就相当于外包给函数实现,我们在主函数的外部定义这样一个函数,然后根据练习9的原理,实现我们想要输出的字符,然后在主函数里面再进行调用(调用的时候可以不用写函数的类型),于是我们的代码就变成了这样
这样子,我们的简陋的开场动画就做好了,它实现的是字符从两端到中间不断移动的过程,使用的是我们上一篇文章中练习9的原理。
2.2 游戏菜单实现
制作完了开场动画,我们接下来就要引入一个游戏菜单,给用户提供一个基本的信息。我们考虑使用一个menu()函数,专门用来实现这个输出游戏菜单的功能,它的作用就是在屏幕上输出菜单这个功能,不需要返回值,所以我们使用void类型。菜单的内容因人而异,我们也可以把我们的菜单弄得更精美,利用实现上面动态效果的Sleep函数实现菜单的动态呈现,以下是我实现的一种菜单,加入菜单后,我们的代码就变成了这样:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <windows.h>
void flash()//动画函数
{
char arr1[] = "Welcome to the Guess the Numbers game!!";
char arr2[] = "#######################################";
int left = 0;
int right = strlen(arr1) - 1;//或者用sizeof(arr1)/sizeof(arr1[0])
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(350);//引用<windows.h>头文件
system("cls");//引用<windows.h>或<stdlib.h>头文件,最好是后者
left++;
right--;
}
printf("%s\n", arr2);
putchar('\n');
putchar('\n');//换行是为了留有空间给接下来的菜单,更加美观
}
void menu()//菜单函数
{
printf("***************************************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************输入1开始游戏*************\n");
Sleep(400);
printf("*************输入0退出游戏*************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************** 版本1.0 ***************\n");
Sleep(400);
printf("*************作者:Yang210*************\n");
Sleep(400);
printf("***************************************\n");
}
int main()
{
flash();
menu();
return 0;
}
2.3 游戏流程的循环和判断部分实现
在开场动画和游戏菜单实现后,我们接下来就要进行游戏整体流程的循环和判断部分了,因为我们一开始过完开场动画和菜单后就进入到游戏主体里面,不需要什么判断条件要求,所以我们考虑使用do while循环,先无条件执行一次,这时候有两个选择,我们假设用户输入1表示游玩,而输入0表示退出游戏,所以我们需要在循环里面加入分支语句,因为用户输入的是数字,是常量,所以我们选择使用switch语句,我们需要告诉用户输入什么对应怎么样的操作,所以我们要在判断前,先使用一个printf函数输出相关信息,然后scanf函数接收用户信息,以此才能进行后续的比较,接受的信息放入一个叫做choice的变量中,因为用户输入的是1和0这两个逻辑值,所以我们循环的条件也可以直接使用choice,这样子当用户输入0的时候我们就可以跳出我们的的循环,而输入1就继续循环,并且开始我们的游戏,那么具体的代码就如下实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <windows.h>
void flash()
{
char arr1[] = "Welcome to the Guess the Numbers game!!";
char arr2[] = "#######################################";
int left = 0;
int right = strlen(arr1) - 1;//或者用sizeof(arr1)/sizeof(arr1[0])
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(350);//引用<windows.h>头文件
system("cls");//引用<windows.h>或<stdlib.h>头文件,最好是后者
left++;
right--;
}
printf("%s\n", arr2);
putchar('\n');
putchar('\n');//换行是为了留有空间给接下来的菜单,更加美观
}
void menu()
{
printf("***************************************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************输入1开始游戏*************\n");
Sleep(400);
printf("*************输入0退出游戏*************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************** 版本1.0 ***************\n");
Sleep(400);
printf("*************作者:Yang210*************\n");
Sleep(400);
printf("***************************************\n");
}
int main()
{
flash();
menu();
int choice = 0;
int i = 0;
do//游戏流程实现的循环和判断部分
if (i == 0)
{
printf("确认是否游玩?请输入数字(1/0):>");
}
else
{
printf("确认是否继续游玩?请输入数字(1/0):>");//这里是用户已经玩了一次之后下一次输出的提示,使得对话文本更加自然连贯
}
scanf("%d", &choice);
switch (choice)
{
case 1:
game();
break;
case 0:
printf("已成功退出游戏\n");
break;
default:
printf("输入错误,请重新输入!\n");
}
i++;
} while (choice);
return 0;
}
这样子,我们就实现了用户选择游玩和不游玩,游玩后是否继续游玩的循环和判断的整个流程部分,确保了游戏整体的流程的完整。
2.4 游戏主体逻辑实现(生成随机数+循环判断)
在实现了游戏的整体流程之后,如果用户选择了1,那么就意味着正式开始了游戏,也就是整一个猜数字游戏的核心逻辑所在,是整一个猜数字小游戏的灵魂,这里面就包括随机数的生成,以及把用户输入的数字与随机数进行比较反馈大了还是小了的核心玩法,所以为了实现这个逻辑,我们也考虑使用一个函数叫做game(),同样的也是void类型,因为不用返回什么值,我们只需要在程序运行的时候,当需要实现这个功能的时候,我们就会转到这个函数里面去,相当于外包实现这个功能就可以了。
那么现在问题就来了,把用户输入的数字与随机数进行比较反馈大了还是小了很好实现,一个分支语句判断就可以了,问题的关键在于我们怎么生成随机的数字?这样是我们这篇文章,这个游戏,最为关键的地方。
为了实现生成随机数的这个功能,我们就必须介绍一些新的函数,这些东西可以帮助我们生成随机数。
在C语言中,提供了一个库函数rand(),它能生成一个0~32767的数字,我们可以自行搜索或者使用MSDN文档查看,它就可以生成我们需要的随机数字。 在使用这个函数之前,我们要先引入头文件<stdlib.h>,这和system()函数需要的源文件相同,我们可以在game()里面定义一个变量ret,存储这个rand函数生成的随机数字。这样子我们生成的数字就随机了吗?我们来试着用printf把ret输出检验一下。
第一次生成的数字:
第二次生成的数字:
我们会发现这两次都是一样的随机数,也就是说rand函数直接使用它会输出看起来随机的不随机数,这样我们多玩几次之后就发现规律了就没意思玩下去了。那么,如何让rand函数生成的随机数看起来更加随机呢?
答案是我们在使用rand函数之前还需要使用另外一个函数,srand()函数,srand函数同样引用<stdlib.h>头文件,上面我们已经引过了所以这里不用再引用了。我们在srand函数的括号里设置一个起点,比如100,这样子rand函数生成的随机数就不会出现前后两次是顺序完全固定的情况,但是这种方法还是有点问题,我们来看看下面的输出结果
我们会发现上一种方法好歹生成的是不那么随机的随机数,怎么这次使用了srand函数之后演都不演了,直接变成固定数字了?作者你是故意在坑我呢?
咳咳,其实不是这样的,我们需要在srand函数里的括号里面放入一个不断变化的值,它才能让rand函数生成随机数,而什么值是不断变化的呢?我们可以看到我们电脑的右下角,电脑的时间是不断变化的,所以我们可以传进去时间戳(就是电脑时间转化出来的一个数字,我们只要知道它可是不断变化的就可以了,它的换算算法是当前时间相较于计算机起始时间的差值,换算成以秒为单位的数字,了解即可)。
我们可以使用time()函数,这个库函数就会返回时间戳,这个时间戳为调用函数的时间点和计算机起始时间之间的时间戳,time函数使用前需要引用<time.h>头文件。
于是我们就把time()函数放入srand函数的括号里,time函数有参数,但我们这样使用不用参数,我们可以写成time(NULL),NULL就表示给的是一个空值,通过搜索或者MSDN文档查看知道,time函数返回的类型是time _t,这其实是一种用结构体定义的一种类型,而它的本质是整型,也就是int,而我们的srand函数需要的返回类型是unsigned int类型,所以我们就使用强制类型转化,srand( ( unsigned int ) time(NULL) )这样子就把我们的srand函数设置好了。我们再来运行一些看一下这次设置好后输出的随机数的结果
第一次随机数字:
第二次随机数字:
看起来第一次和第二次生成的随机的数字已经不同了,并且每一次也不像上面那种方法那样是固定的数字,问题似乎解决了。可是如果你仔细观察每一次生成的数字你就会发现,它们每次都离得很近,甚至如果你输入过快的话,它们还会有一样重合的风险,这次似乎又是随机但没这么随机的数字,我们的写代码过程就是如此,一波未平一波又起,总是在不断地解决问题又产生新的问题的道路上,但是不必沮丧害怕,请记住,踏上取经路,比抵达灵山更重要。
所以让我们回归问题, 这次又是什么问题呢?其实是srand确实需要在rand函数之前调用,但是srand函数只用调用一次就可以了,所以我们的srand不要放在game函数里面,因为game函数是在游戏流程的循环内的,会不断不断的循环,而我们的srand函数设置生成器起点只需要设置一次就可以了,于是我们就可以把srand函数放在循环的外面,主函数的里面,这样子我们依然确保srand函数是在rand函数之前调用的。这样子,我们生成的,才是真真正正的随机数。
随机数的生成解决了,但是rand函数生成的随机数范围是0-32767之间,而我们要求生成的是1-100之间的随机数,又如何实现了,其实也很简单,我们只需要用rand%100就可以了,因为这样子取余100的余数永远是在0~99之间,不可能超过一百,因为超过100就可以再除进1了,然后我们再整体rand%100+1,这样子我们就保证我们的范围再1-100之间了。
解决生成随机数这个关键技术难题后,剩下的事情就很简单了,写一个循环,然后把用户输入的数字与生成的随机数比较判断,大了就提示猜大了,小了就提示猜小了,对了就提示猜对了,然后break跳出去,猜错就一直循环,直到猜对为止。于是,我们代码的完全体如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>
void flash()\\动画部分
{
char arr1[] = "Welcome to the Guess the Numbers game!!";
char arr2[] = "#######################################";
int left = 0;
int right = strlen(arr1) - 1;//或者用sizeof(arr1)/sizeof(arr1[0])
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(350);
system("cls");
left++;
right--;
}
printf("%s\n", arr2);
putchar('\n');
putchar('\n');
}
void menu()\\菜单部分
{
printf("***************************************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************输入1开始游戏*************\n");
Sleep(400);
printf("*************输入0退出游戏*************\n");
Sleep(400);
printf("***************************************\n");
Sleep(400);
printf("*************** 版本1.0 ***************\n");
Sleep(400);
printf("*************作者:Yang210*************\n");
Sleep(400);
printf("***************************************\n");
}
void game()\\游戏主体逻辑部分
{
int ret = rand() % 100 + 1;\\生成1-100随机数
int guess = 0;
printf("开始游戏,现在请输入一个数字(1-100):>");
while (1) {
scanf("%d", &guess);
if (guess > ret)
{
printf("猜大了,请再次输入:>");
}
else if (guess < ret)
{
printf("猜小了,请再次输入:>");
}
else
{
printf("恭喜你,猜对了!\n");
break;
}
}
}
int main()
{
flash();
menu();
int choice = 0;
int i = 0;
srand((unsigned int)time(NULL));\\放在循环外,主函数里,仍确保在rand之前调用
do\\游戏流程循环部分
{
if (i == 0)
{
printf("确认是否游玩?请输入数字(1/0):>");
}
else
{
printf("确认是否继续游玩?请输入数字(1/0):>");
}
scanf("%d", &choice);
switch (choice)
{
case 1:
game();
break;
case 0:
printf("已成功退出游戏\n");
break;
default:
printf("输入错误,请重新输入!\n");
}
i++;
} while (choice);
return 0;
}
在实现这个循环判断时候有两个小问题需要注意:一是不要把逻辑给写反了,二是不要把随机数生成也就是game函数中的rand放到game函数中的循环部分,因为这样子你每一次输完后里面的随机数一直在变,不是固定的,除非你第一次猜对,否则永远都猜不到。
至此,关于这个猜数字小游戏的全部部分,我们就全部实现完了,看完后可以试着自己再敲一遍,或者可以继续优化,比如输出提示的信息更加人性化自然,互动更加丰富,但总归还是对这四个核心部分的修修补补,但是,请记住,实现这个猜数字小游戏并不是我们的终点,也许事后回看我们会发现,它只不过是一个非常简单的小游戏,但是至少对于现在,我们可以这么说:它是我们学习的一小步,却是我们未来的一大步。加油吧,猜数字的小游戏虽然结束了,但是我们人生的游戏还在继续!
作者其他文章链接: