C语言探索之旅 | 第二部分第十课: 实战悬挂小人游戏答案

原文:
《C语言探索之旅》全系列
内容简介前言解方(1. 游戏的代码)解方(2. 词库的代码)第二部分第十一课预告 1. 前言
经过上一课 C语言探索之旅 | 第二部分第九课: 实战"悬挂小人"游戏 之后,相信大家都或多或少都写了自己的“悬挂小人”的游戏代码吧 。
这一课我们就来"终结"这个游戏吧 (听着怎么有点吓人…) 。
"Yes, you are ."
2. 解方(1. 游戏的代码)
如果你开始阅读这里,说明:
不管你是哪种情况,我都会介绍一下如何来完成这个游戏 。
“说不说在我,听不听在您”~
事实上,我自己花了比想象中更多的时间来完成这游戏 。
人生总是这样的,“理想丰满,现实骨感;看似美满,人艰不拆” 。
但是,我还是坚信大家是有能力独自完成这个小游戏的(如果你认真学习了之前的 C语言课程),可以去查阅网上资料,花点时间(几十分钟,几小时,几天?),这并不是一次竞赛,所以不用着急 。
我更希望您花了不少时间,最终实现了这个游戏; 比之您只花 5 分钟,然后就来看答案要好很多 。
千万不要觉得我是一蹴而就写成这个游戏的,这个游戏虽小,但也还没简单到可以在脑中构思好一切,然后“下笔如有神”: 我也是一步步写出来的 。
我们将会分 2 步来介绍我们的解方:
首先我们会演示如何一步步写游戏的主体部分,一开始我们会只有一个猜测的单词,而且是固定的;我选了 (表示“瓶子”),因为我们要测试对于单词中有大于等于两个相同字母的情况是否处理正确了( 中有 2 个 T) 。
然后我们会演示如何加入词库的处理程序,以便每一轮游戏可以从词库中随机抽取一个单词 。
牢记:重要的不是结果,而是我们思考的方式和过程 。
分析 main 函数
大家都知道,我们的 C语言程序都是由 main 函数作为入口的 。
我们也不要忘了引入一些标准库的头文件:stdio.h,.h,ctype.h(为了函数) 。
因此,我们的程序一开始会是这样的:
#include #include #include int main(int argc, char* argv[]){return 0;}
是不是很简单啊,慢慢来么 。
我们的 main 函数将控制游戏的大部分运作,并且调用我们将要写的不少函数 。
我们来声明一些必要的变量吧 。这些变量也不是一次就能全部想到的,都是写一点,想到一些 。“罗马不是一日建成的, 小人也不是一日能悬挂完的” 。
#include #include #include int main(int argc, char* argv[]){char letter = 0;// 存储用户输入的字母char secretWord[] = "BOTTLE";// 要猜测的单词int letterFound[6] = {0};// 布尔值的数组 。数组的每一个元素对应猜测单词的一个字母 。0 = 还没猜到此字母, 1 = 已猜到字母int leftTimes = 7;// 剩余猜测次数(0 = 失败)int i = 0;// 为了遍历数组,需要一个下标return 0;}
上述的变量中,起到关键作用的就是这个 int 型数组了 。这个数组用于表示猜测的单词中哪些字母已经猜到,哪些还没猜到 。
一开始,我们实现得简单些:我们的单词有 6 个字母,因此我们的数组就固定是 6 个元素的数组 。
如果元素为 0,表示对应的那个字母还没猜到;如果为 1,则表示已猜到 。随着游戏的进行,这个数组的元素值会被修改 。

C语言探索之旅 | 第二部分第十课: 实战悬挂小人游戏答案

文章插图
例如,如果当下我们玩游戏直到:
B*TT*E
那么,这个数组的值应该是这样:
101101
之后我们要测试游戏的一轮是否已经胜利也就比较简单了:只需要测试数组的所有元素是否都等于 1 。
我们就来写判断一轮是否胜利的函数吧,取名为 win(表示 “胜利”)好了 。
int win(int letterFound[]){int i = 0;int win = 1;// 1 为胜利,0 为失败for (i = 0 ; i < 6 ; i++){if (letterFound[i] == 0)win = 0;}return win;}
可以看到,我们的 win 函数的参数是一个 int 型数组,我们在 main 函数中调用 win 函数时,会将我们的数组传给它 。
这个函数很简单:遍历数组,只要还有一个元素为 0,那游戏还没胜利;如果所有元素都为 1,则游戏胜利 。
为了与此函数搭配,我们还需要写一个函数,起名叫,这个函数将有两个功能:
返回一个布尔值(在 C语言里用 int 型表示),用于表示所猜的字母是否存在于单词中 。
更新数组的元素,如果所猜的字母在单词中,那么就把对应的元素值修改为 1 。
int researchLetter(char letter, char secretWord[], int letterFound[]){int i = 0;int correctLetter = 0;// 0 表示字母不在单词里,1 表示字母在单词里// 遍历单词数组 secretWord,以判断所猜字母是否在单词中for (i = 0 ; secretWord[i] != '\0' ; i++){if (letter == secretWord[i])// 如果字母在单词中{correctLetter = 1;// 表示猜对了一个字母letterFound[i] = 1;// 对于所有等于所猜字母的数组位置,都使其数值变为 1}}return correctLetter;}
这个函数的好处还在于:不会在找到第一个存在的字母后就停止,而会继续查找,所以对于像这样有两个字母相同的单词就可以一次揭示两个 T 了 。
好,写完这两个函数(放在 main 函数后面),我们继续写我们的 main 函数 。我们添加一句欢迎词:
printf("欢迎来到悬挂小人游戏!\n");
然后添加一个主循环,是一个 while 循环:
while (leftTimes > 0 && !win(letterFound)){}
每轮游戏在 (剩余猜测机会)大于 0 并且还没胜利的情况下,是不会停止的 。
在这两种情况下,都要停止游戏 。
我们在 while 循环里添加如下代码:
printf("\n\n您还剩 %d 次机会", leftTimes);printf("\n神秘单词是什么呢 ? ");/* 我们显示猜测的单词,将还没猜到的字母用*表示例如 : *O**LE */for (i = 0 ; i < 6 ; i++){if (letterFound[i])// 如果第 i+1 个字母已经猜到printf("%c", secretWord[i]);// 打印出来elseprintf("*");// 还没猜到,打印一个星号 *}
上面的代码用于:
接下来,我们写请求用户输入一个字母的代码:
printf("\n输入一个字母 : ");letter = readCharacter();
还记得我们之前写的函数吗?它用于读取用户的第一个输入的字母,读到回车符结束,而且它会把该字母转成大写 。
// 如果用户输入的字母不存在于单词中if (!researchLetter(letter, secretWord, letterFound)){leftTimes--;// 将剩余猜测机会数减 1}
以上代码调用函数在单词中查找用户输入的字母,如果没找到,则剩余猜测机会数扣除一次 。
如果字母存在于单词中,则函数还会更新数组(每个元素对应了神秘单词的每一个字母的猜测情况),将其中对应的 0(还没猜到)改为 1(已经猜到) 。
这样,win 函数在判断的时候,如果数组的每一个元素都为 1,则返回 1,表示本轮胜利,猜到单词的全部字母了 。
暂时,while 循环体的内容就到这里了,然后我们还要写跳出 while 循环之后的代码(或者胜利或者失败):
C语言探索之旅 | 第二部分第十课: 实战悬挂小人游戏答案

文章插图
if (win(letterFound))printf("\n\n胜利了! 神秘单词是 : %s\n", secretWord);elseprintf("\n\n失败了! 神秘单词是 : %s\n", secretWord);
游戏主体部分的代码就到这里了,给出我们到目前为止的完整程序:
#include #include #include int win(int letterFound[]);int researchLetter(char letter, char secretWord[], int letterFound[]);char readCharacter();int main(int argc, char* argv[]){char letter = 0;// 存储用户输入的字母char secretWord[] = "BOTTLE";// 要猜测的单词int letterFound[6] = {0};// 布尔值的数组 。数组的每一个元素对应猜测单词的一个字母 。0 = 还没猜到此字母,1 = 已猜到字母int leftTimes = 7;// 剩余猜测次数(0 = 失败)int i = 0;// 为了遍历数组,需要一个下标printf("欢迎来到悬挂小人游戏!\n");while (leftTimes > 0 && !win(letterFound)){printf("\n\n您还剩 %d 次机会", leftTimes);printf("\n神秘单词是什么呢 ? ");/* 我们显示猜测的单词,将还没猜到的字母用 * 表示例如 : *O**LE */for (i = 0 ; i < 6 ; i++){if (letterFound[i])// 如果第 i+1 个字母已经猜到printf("%c", secretWord[i]);// 打印出来elseprintf("*");// 还没猜到,打印一个*}printf("\n输入一个字母 : ");letter = readCharacter();// 如果用户输入的字母不存在于单词中if (!researchLetter(letter, secretWord, letterFound)){leftTimes--;// 将剩余猜测机会数减 1}}if (win(letterFound))printf("\n\n胜利了! 神秘单词是 : %s\n", secretWord);elseprintf("\n\n失败了! 神秘单词是 : %s\n", secretWord);return 0;}int win(int letterFound[]){int i = 0;int win = 1;// 1 为胜利,0 为失败for (i = 0 ; i < 6 ; i++){if (letterFound[i] == 0)win = 0;}return win;}int researchLetter(char letter, char secretWord[], int letterFound[]){int i = 0;int correctLetter = 0;// 0 表示字母不在单词里,1 表示字母在单词里// 遍历单词数组 secretWord,以判断所猜字母是否在单词中for (i = 0 ; secretWord[i] != '\0' ; i++){if (letter == secretWord[i])// 如果字母在单词中{correctLetter = 1;// 表示猜对了一个字母letterFound[i] = 1; // 对于所有等于所猜字母的数组位置,都将其数值变为1}}return correctLetter;}char readCharacter(){char character = 0;character = getchar();// 读取一个字母character = toupper(character);// 把这个字母转成大写// 读取其他的字符,直到 \n(为了忽略它)while (getchar() != '\n');return character;// 返回读到的第一个字母}
这一部分的程序,你可以将其存放在一个 .c 文件中,例如叫 .c 。
然后用 gcc 编译(如果是在 IDE 里面,例如,那直接点击编译运行):
gcc hangman.c -o hangman
运行:
./hangman
接下来我们要开始第二部分:词库的代码 。
根据这部分的代码,我们还会接着修改和添加 main 函数的内容 。
好吧,稍作休息,继续前进!
3. 解方(2. 词库的代码)
我们已经编写了游戏主体部分的基本代码,但是我们的游戏目前还不能做到每轮随机抽取一个单词 。
因此,接下来我们就带大家编写处理词库的代码 。
首先,我们需要创建一个文件,用于存放所有的单词 。
在 Linux / Unix / macOS 操作系统下,我们都可以直接创建一个不带后缀名的文件 。在下可以创建 .txt 结尾的文本文件 。
我写这个游戏是在 Linux 系统下,所以直接用 Vim 或 Emacs 或其他编辑器创建一个文件,位于我们源文件的相同目录下: 。
在里面写入以下单词(每行一个,用回车符隔开):
YOUMOTHERLOVEPANDABOTTLEFUNNYHONEYLIKEJAZZMUSICBREADAPPLEWATERPEOPLEDOGCATGLASSSKYGODZERO
当然了,我这里只是举个例子,你可以创建属于自己的词库 。
新建两个文件
处理这个文件的代码将会不少(至少,我是这么预感的),因此,我们新建一个 .c 源文件,可以命名为 .c 。
顺便,我们也创建 .h 这个头文件,其中存放 .c 中的函数的原型,这样我们在 main 函数里就可以通过
#include "dictionary.h"
来引入这些函数的定义了 。
在 .c 中,首先我们引入一些头文件:
#include #include #include // 我们需要这里面的随机数函数,还记得我们的第一个小游戏“或多或少”吗? #include// 我们需要 strlen 这个计算字符串长度的函数#include "dictionary.h"
函数
这个函数用于从文件中随机选取一个单词,此函数只有一个参数: 指向内存中可以写入单词的地址的指针,这个指针实参将由 main 函数提供 。
函数返回值是 int 变量:1 表示一切顺利;0 表示出现错误 。
此函数的开头是这样:
【C语言探索之旅 | 第二部分第十课: 实战悬挂小人游戏答案】int chooseWord(char *wordChosen){FILE* dictionary = NULL;// 指向我们的文件 dictionary(词库)的文件指针int wordNum = 0;// 词库中单词总数int chosenWordNum = 0;// 选中的单词编号int i = 0;// 下标int characterRead = 0;// 读入的字符}