做者 谢恩铭,公众号「程序员联盟」(微信号:coderhub)。 转载请注明出处。 原文:www.jianshu.com/p/6cbf45266…程序员
《C语言探索之旅》全系列编程
第二部分的理论知识基本讲完了。上一课咱们经历了颇有意思的 C语言探索之旅 | 第二部分第八课:动态分配 。数组
这一课咱们来实战一下,要实现的游戏叫“悬挂小人”。bash
这个“小人”,不是“君子和小人”的小人。是 little man(小小的人)的意思。微信
读者:“你有必要这么强调吗?简直无聊嘛...”函数
好的,话休絮烦...学习
俗语说得好:“实践是必要的!”测试
对于你们来讲这又尤其重要,由于咱们刚刚结束了一轮 C语言的高级技术的“猛烈进攻”,须要好好复习一下,消化消化。优化
不论你多厉害,在编程领域,不实践是永远不行的。尽管你可能读懂了以前的全部课程,可是若是不配合必定的实践,是不能深入理解的。ui
之前我大学里入门编程之前看 C语言的书,以为看懂了,可是一上手要写程序,就像挤牙膏同样费劲。
此次的实战练习,咱们一块儿来实现一个小游戏:“悬挂小人”,或叫 “上吊游戏”。英语叫 HangMan,是挺著名的一个休闲益智游戏。
虽然说是游戏,可是比较惋惜的是还不能有图形界面 (不过课程后面会说怎么实如今控制台绘制小人,其实也能够实现简陋的“图形化”): 由于 C语言自己不具有绘制 GUI(Graphical User Interface 的缩写,表示“图形用户接口”)的能力,须要引入第三方的库。
悬挂小人游戏是一个经典的字母游戏,在规定步数内一个字母一个字母地猜单词,直到猜出整个单词。
因此咱们的游戏暂时仍是以控制台的形式(黑框框)与你们见面,固然若是你会图形编程,也能够把这个游戏扩展成图形界面的。
相信很多读者应该见过这个游戏的图形界面版本,就是每猜错一个字母画一笔,直到用完规定次数,小人被“吊死”。
这个实战的目的是让咱们能够复习以前学过的全部 C语言知识:指针,字符串,文件读写,结构体,数组,等等,都是好家伙!
既然是出题目的实战,那么就须要委屈你们按照个人题目要求来编写这个游戏啦。
好,就来公布咱们的题目要求:
游戏每一轮有 7 次(次数能够设置,不必定是 7 次)猜想的机会,用完则此轮失败。
每轮会从字典中随机抽取一个单词供玩家猜,初始时单词是以若干个星号(*
)的方式来表示。说明全部字母都还隐藏着。
字典的全部单词储存在一个文本文件中(在 Windows 下一般是 txt 文件,在 Unix/Linux/macOS 下通常能够是任意后缀名的文件)。
每猜错一个字母就扣掉一次机会,猜对一个字母不扣除机会数。猜对的字母会显示在屏幕上的单词中,替换掉星号。
假设要猜的单词是 OSCAR。
假设咱们给程序输入一个字母 B(猜的第一个字母),程序会验证字母是否在这个单词里。
有两种状况:
所猜的字母在单词中,此时程序会显示这个单词,不是所有显示,而是显示猜到的那些字母,其余的还未猜到的字母用 *
表示。
所猜的字母不在单词中(目前的状况,由于字母 B 不在单词 OSCAR 中),此时程序会告诉玩家“你猜错了”,剩余的机会数会被扣除一个。若是剩余机会数变为 0,游戏结束。
在图形化的“悬挂小人”(Hangman)游戏中,每猜一次会有一个小人被画出来。咱们的游戏,虽然还不能真正实现图形化,可是若是优化一下,也能够在控制台实现相似这样的效果:
假设玩家输入一个 C,由于 C 在单词 OSCAR 中,那么程序不会扣除玩家的剩余机会数,并且会显示已猜到的字母,以下:
单词:**C**
复制代码
若是玩家继续输入,这回输入的是 O,那么程序会显示以下:
单词:O*C**
复制代码
有一些单词中,同一个字母会出现屡次。好比在 APPLE(表示“苹果”)中,P 这个字母就出现了 2 次;在 ELEGANCE(表示“优雅”)中,E 这个字母出现了 3 次。
Hangman 游戏对此的规则很简单:只要猜出一个字母,其余重复的字母会同时显示。
假如要猜的单词是 ELEGANCE,用户输入了一个 E,那么会以下显示:
单词:E*E****E
复制代码
欢迎来到悬挂小人游戏!
您还剩 7 次机会
神秘单词是什么呢?*****
输入一个字母:E
您还剩 6 次机会
神秘单词是什么呢?*****
输入一个字母:S
您还剩 6 次机会
神秘单词是什么呢?*S***
输入一个字母:R
您还剩 6 次机会
神秘单词是什么呢?*S**R
输入一个字母:
复制代码
游戏就会这样进行下去,直到玩家在 7 个机会用完前猜到单词,或者用完 7 个机会还没猜到单词,游戏结束。
例如:
您还剩 2 次机会
神秘单词是什么呢?OS*AR
输入一个字母:C
胜利了!神秘单词是:OSCAR
复制代码
在控制台中让程序读入一个字母,看起来简单,但其实暗藏玄机。不信咱们来试一下。
要输入一个字母,通常你们会认为是这样作:
scanf("%c", &myLetter);
复制代码
确实是不错的,由于 %c
标明了等待用户输入一个字符。输入的字符会储存在 myLetter 这个变量(类型是 char)中。
若是咱们只写一个 scanf,那是没问题的。可是假若有好几个 scanf,会怎么样呢?咱们来测试一下:
int main(int argc, char* argv[])
{
char myLetter = 0;
scanf("%c", &myLetter);
printf("%c", myLetter);
scanf("%c", &myLetter);
printf("%c", myLetter);
return 0;
}
复制代码
照咱们的设想,上述程序应该会请求用户输入一个字符,再打印出来: 进行两次。
测试一下,实际状况是怎么样的呢?你输入了一个字符,没错,而后呢...
程序为你打印出来了你输入的那个字符,假如你输入的是 a,那么程序输出
a
复制代码
而后程序就退出了,没有下文了。为何不提示我输入第二个字符了呢?就好像它忽略了第二个 scanf 同样。到底发生了什么呢?
事实上,当你在控制台(console)里面输入时,你输入的内容都被记录到内存的某处,固然也包括按下 Enter 键(回车键)时产生的输入:
\n
复制代码
所以,你先输入了一个字符(例如 a),而后你按了一下回车键:
字符 a 就被第一个 scanf 取走了,第二个 scanf 则把你的回车键(\n
)取走了。
为了不这个问题,咱们写一个函数 readCharacter()
来处理:
char readCharacter()
{
char character = 0;
character = getchar(); // 读取输入的第一个字母
character = toupper(character); // 把这个字母转成大写
// 读取其余的字符,直到 \n (为了忽略它们)
while (getchar() != '\n')
;
return character; // 返回读到的第一个字母
}
复制代码
能够看到,以上程序中,咱们使用了 getchar 函数,这个函数是在标准库的 stdio.h 中,用于读取一个用户输入的字符,效果至关于
scanf("%c", &letter);
复制代码
而后,咱们又用到了一个在本课程中还没学习过的函数:toupper。
根据字面意思 to + upper 是英语“转换为大写”的意思,因此这个函数就是用于把一个字母转成大写字母。
看到了吧,若是函数名起得好,几乎就不须要注释,看名字就知道大体是干什么的(论编程命名的重要性)。
借着 toupper 这个函数,玩家就能够输入小写字母或者大写字母了,由于在“悬挂小人”游戏中,咱们显示的单词中的字母都是大写的。
toupper 这个函数定义在 ctype.h 这个标准库的头文件中,因此须要
#include <ctype.h>
复制代码
继续看咱们的函数,能够看到其中最关键的地方是:
while (getchar() != '\n')
;
复制代码
这一小段代码使得咱们能够清除第一个输入的字母外的其余字符,直到碰见 \n
(回车符)。
函数返回的就是第一个输入的字母,这样能够保证再也不受回车符的影响了。
咱们用了一个 while 循环,而循环体部分只有一个分号(;
),很简洁吧。
也许你会问,以前的课程中 while 循环的循环体不是由大括号围起来的么,怎么这里只有一个分号呢?
事实上,这个分号就至关于
{
}
复制代码
就是空循环体,什么都不作,因此其实以上的代码至关于:
while (getchar() != '\n')
{
}
复制代码
可是分号比大括号写起来更简单么,不要忘了程序员是懂得如何偷懒的一群人!
此 while 循环一直执行,直到用户输入回车符,其余的字符都被从内存中清除了,咱们称其为 “清空缓冲区”。
所以:
为了在咱们的程序中每次读取用户输入的一个字母,咱们不要使用
scanf("%c", &myLetter);
复制代码
而需要借助咱们写的函数:
myLetter = readCharacter();
复制代码
因而,咱们的测试程序变成这样:
#include <stdio.h>
#include <ctype.h>
char readCharacter()
{
char character = 0;
character = getchar(); // 读取一个字母
character = toupper(character); // 把这个字母转成大写
// 读取其余的字符,直到 \n (为了忽略它)
while (getchar() != '\n')
;
return character; // 返回读到的第一个字母
}
int main(int argc, char* argv[])
{
char myLetter = 0;
myLetter = readCharacter();
printf("%c\n", myLetter);
myLetter = readCharacter();
printf("%c\n", myLetter);
return 0;
}
复制代码
运行,输出相似以下(假如用户输入 o,回车;输入 k,回车):
o
O
k
K
复制代码
由于咱们的游戏是一步步写成的,因此一开始,确定先写简单的,再逐步完善游戏。
所以,猜想的单词一开始咱们只用一个。因此,咱们一开始会这么写:
char secretWord[] = "BOTTLE";
复制代码
你会说:“这样不是很无聊嘛,猜想的单词老是这一个”。
是的,但以后咱们确定会扩展。一开始这样作是为了避免把问题复杂化,一次作一件事情,慢慢来么。
以后若是猜想一个单词的代码能够运行了,咱们再用一个文件来储存全部可能的单词,这个文件能够起名为 dictionary(表示“字典”)。
那什么是字典或词库呢?
在咱们的游戏里,就是一个文件,文件中的每一行存放了一个单词,以后咱们的程序会随机今后文件中抽取一个单词来做为每一轮的猜想单词。
词库是相似这样的:
YOU
MOTHER
LOVE
PANDA
BOTTLE
FUNNY
HONEY
LIKE
JAZZ
MUSIC
BREAD
APPLE
WATER
PEOPLE
复制代码
至于这个文件里有多少单词,由于咱们的词库是可扩展的(以后确定能够添加新的单词),因此其实只要统计回车符(\n
)的数目就能够,由于是每行一个单词。
好了,游戏的基本点咱们介绍到这里,其实有了前面全部课程的基础,你已经有能力来完成这个看似有点复杂的游戏了,不过要组织得好仍是不那么容易的,你能够用多个函数来实现不一样的功能。
加油,坚持不懈就是胜利,期待你的成果!
若是你是在 Windows 下用 CodeBlocks 等 IDE 来编译的,那么请将字典文件 dictionary 改为 dictionary.txt。 由于 Windows 的文件储存形式和 Linux/Unix/macOS 有些不同。
目前来讲,咱们只让玩家玩一轮,若是能加一个循环,使得游戏每次询问玩家是否要再玩一次,那“真真是极好的”。
目前仍是单机模式,能够建立一个二人模式,就是一个玩家输入一个单词,第二个玩家来猜。
为何不用 printf 函数来打印(绘制)一个悬挂小人呢?在每次咱们猜错的时候,就把它画出来,每错一个,多画一笔,这样能够增长乐趣,能够用以下的代码:
if (猜错1个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错2个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | |\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错3个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | \\|\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错4个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | \\|/\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错5个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | \\|/\n");
printf(" | |\n");
printf(" |\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错6个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | \\|/\n");
printf(" | |\n");
printf(" | /\n");
printf(" |\n");
printf("_|__\n");
}
else if (猜错7个字母)
{
printf(" _____\n");
printf(" | |\n");
printf(" | O\n");
printf(" | \\|/\n");
printf(" | |\n");
printf(" | / \\\n");
printf(" |\n");
printf("_|__\n");
}
复制代码
上面代码中的空格也许不一样平台的显示不同,可能须要你们自行调整。
若是 7 次机会所有用完,则小人挂掉,游戏结束。
请你们花点时间,好好理解这个游戏,而且尽量地改进它。若是你能够不看咱们的答案,而本身完成游戏和改进,那么你会收获不少的!
今天的课就到这里,一块儿加油吧!
下一课咱们就会公布悬挂小人游戏的解题思路和答案咯。
下一课:C语言探索之旅 | 第二部分第十课: 实战"悬挂小人"游戏 答案
我是 谢恩铭,公众号「程序员联盟」(微信号:coderhub)运营者,慕课网精英讲师 Oscar 老师,终生学习者。 热爱生活,喜欢游泳,略懂烹饪。 人生格言:「向着标杆直跑」