作者 謝恩銘,公眾號(hào)「程序員聯(lián)盟」(微信號(hào):coderhub)。
轉(zhuǎn)載請(qǐng)注明出處缔俄。
原文:http://www.reibang.com/p/b239b1774f4b
《C語言探索之旅》全系列
內(nèi)容簡(jiǎn)介
- 前言
- 解方(1. 游戲的代碼)
- 解方(2. 詞庫(kù)的代碼)
- 第二部分第十一課預(yù)告
1. 前言
經(jīng)過上一課 C語言探索之旅 | 第二部分第九課: 實(shí)戰(zhàn)"懸掛小人"游戲 之后炉媒,相信大家都或多或少都寫了自己的“懸掛小人”的游戲代碼吧。
這一課我們就來"終結(jié)"這個(gè)游戲吧 (聽著怎么有點(diǎn)嚇人...)驱入。
"Yes, you are terminated."
2. 解方(1. 游戲的代碼)
如果你開始閱讀這里赤炒,說明:
- 或者你寫完了游戲氯析,想來看看我們?cè)趺磳憽?/li>
- 或者你沒完成這個(gè)游戲,想來看看怎么寫莺褒。
不管你是哪種情況掩缓,我都會(huì)介紹一下如何來完成這個(gè)游戲。
“說不說在我遵岩,聽不聽在您”~
事實(shí)上你辣,我自己花了比想象中更多的時(shí)間來完成這游戲。
人生總是這樣的尘执,“理想豐滿舍哄,現(xiàn)實(shí)骨感;看似美滿誊锭,人艱不拆”表悬。
但是,我還是堅(jiān)信大家是有能力獨(dú)自完成這個(gè)小游戲的(如果你認(rèn)真學(xué)習(xí)了之前的 C語言課程)丧靡,可以去查閱網(wǎng)上資料蟆沫,花點(diǎn)時(shí)間(幾十分鐘,幾小時(shí)温治,幾天饭庞?),這并不是一次競(jìng)賽熬荆,所以不用著急舟山。
我更希望您花了不少時(shí)間,最終實(shí)現(xiàn)了這個(gè)游戲惶看; 比之您只花 5 分鐘捏顺,然后就來看答案要好很多。
千萬不要覺得我是一蹴而就寫成這個(gè)游戲的纬黎,這個(gè)游戲雖小幅骄,但也還沒簡(jiǎn)單到可以在腦中構(gòu)思好一切,然后“下筆如有神”: 我也是一步步寫出來的本今。
我們將會(huì)分 2 步來介紹我們的解方:
首先我們會(huì)演示如何一步步寫游戲的主體部分拆座,一開始我們會(huì)只有一個(gè)猜測(cè)的單詞,而且是固定的冠息;我選了 BOTTLE(表示“瓶子”)挪凑,因?yàn)槲覀円獪y(cè)試對(duì)于單詞中有大于等于兩個(gè)相同字母的情況是否處理正確了(BOTTLE 中有 2 個(gè) T)。
然后我們會(huì)演示如何加入詞庫(kù)的處理程序逛艰,以便每一輪游戲可以從詞庫(kù)中隨機(jī)抽取一個(gè)單詞躏碳。
牢記:重要的不是結(jié)果,而是我們思考的方式和過程散怖。
分析 main 函數(shù)
大家都知道菇绵,我們的 C語言程序都是由 main 函數(shù)作為入口的肄渗。
我們也不要忘了引入一些標(biāo)準(zhǔn)庫(kù)的頭文件:stdio.h,stdlib.h咬最,ctype.h(為了 toupper 函數(shù))翎嫡。
因此,我們的程序一開始會(huì)是這樣的:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
return 0;
}
是不是很簡(jiǎn)單啊永乌,慢慢來么惑申。
我們的 main 函數(shù)將控制游戲的大部分運(yùn)作,并且調(diào)用我們將要寫的不少函數(shù)翅雏。
我們來聲明一些必要的變量吧圈驼。這些變量也不是一次就能全部想到的,都是寫一點(diǎn)枚荣,想到一些碗脊√浼纾“羅馬不是一日建成的, 小人也不是一日能懸掛完的”橄妆。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
char letter = 0; // 存儲(chǔ)用戶輸入的字母
char secretWord[] = "BOTTLE"; // 要猜測(cè)的單詞
int letterFound[6] = {0}; // 布爾值的數(shù)組。數(shù)組的每一個(gè)元素對(duì)應(yīng)猜測(cè)單詞的一個(gè)字母祈坠。0 = 還沒猜到此字母, 1 = 已猜到字母
int leftTimes = 7; // 剩余猜測(cè)次數(shù)(0 = 失敽δ搿)
int i = 0; // 為了遍歷數(shù)組,需要一個(gè)下標(biāo)
return 0;
}
上述的變量中赦拘,起到關(guān)鍵作用的就是 letterFound 這個(gè) int 型數(shù)組了慌随。這個(gè)數(shù)組用于表示猜測(cè)的單詞中哪些字母已經(jīng)猜到,哪些還沒猜到躺同。
一開始阁猜,我們實(shí)現(xiàn)得簡(jiǎn)單些:我們的單詞 BOTTLE 有 6 個(gè)字母,因此我們的數(shù)組就固定是 6 個(gè)元素的數(shù)組蹋艺。
如果元素為 0剃袍,表示對(duì)應(yīng)的那個(gè)字母還沒猜到;如果為 1捎谨,則表示已猜到民效。隨著游戲的進(jìn)行,這個(gè)數(shù)組的元素值會(huì)被修改涛救。
例如畏邢,如果當(dāng)下我們玩游戲直到:
B*TT*E
那么,letterFound 這個(gè)數(shù)組的值應(yīng)該是這樣:
101101
之后我們要測(cè)試游戲的一輪是否已經(jīng)勝利也就比較簡(jiǎn)單了:只需要測(cè)試 letterFound 數(shù)組的所有元素是否都等于 1检吆。
我們就來寫判斷一輪是否勝利的函數(shù)吧舒萎,取名為 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 函數(shù)的參數(shù)是一個(gè) int 型數(shù)組虱肄,我們?cè)?main 函數(shù)中調(diào)用 win 函數(shù)時(shí),會(huì)將我們的 letterFound 數(shù)組傳給它交煞。
這個(gè)函數(shù)很簡(jiǎn)單:遍歷數(shù)組咏窿,只要還有一個(gè)元素為 0,那游戲還沒勝利素征;如果所有元素都為 1集嵌,則游戲勝利。
為了與此函數(shù)搭配御毅,我們還需要寫一個(gè)函數(shù)根欧,起名叫 researchLetter,這個(gè)函數(shù)將有兩個(gè)功能:
返回一個(gè)布爾值(在 C語言里用 int 型表示)端蛆,用于表示所猜的字母是否存在于單詞中凤粗。
更新 letterFound 數(shù)組的元素,如果所猜的字母在單詞中今豆,那么就把對(duì)應(yīng)的元素值修改為 1嫌拣。
int researchLetter(char letter, char secretWord[], int letterFound[])
{
int i = 0;
int correctLetter = 0; // 0 表示字母不在單詞里,1 表示字母在單詞里
// 遍歷單詞數(shù)組 secretWord呆躲,以判斷所猜字母是否在單詞中
for (i = 0 ; secretWord[i] != '\0' ; i++)
{
if (letter == secretWord[i]) // 如果字母在單詞中
{
correctLetter = 1; // 表示猜對(duì)了一個(gè)字母
letterFound[i] = 1; // 對(duì)于所有等于所猜字母的數(shù)組位置异逐,都使其數(shù)值變?yōu)?1
}
}
return correctLetter;
}
researchLetter 這個(gè)函數(shù)的好處還在于:不會(huì)在找到第一個(gè)存在的字母后就停止,而會(huì)繼續(xù)查找插掂,所以對(duì)于像 BOTTLE 這樣有兩個(gè)字母相同的單詞就可以一次揭示兩個(gè) T 了灰瞻。
好,寫完這兩個(gè)函數(shù)(放在 main 函數(shù)后面)辅甥,我們繼續(xù)寫我們的 main 函數(shù)酝润。我們添加一句歡迎詞:
printf("歡迎來到懸掛小人游戲!\n");
然后添加一個(gè)主循環(huán)璃弄,是一個(gè) while 循環(huán):
while (leftTimes > 0 && !win(letterFound))
{
}
每輪游戲在 leftTimes(剩余猜測(cè)機(jī)會(huì))大于 0 并且還沒勝利的情況下要销,是不會(huì)停止的。
- 如果剩余次數(shù)為 0谢揪,則本輪游戲失敗蕉陋。
- 如果勝利,那本輪就贏了拨扶。
在這兩種情況下凳鬓,都要停止游戲。
我們?cè)?while 循環(huán)里添加如下代碼:
printf("\n\n您還剩 %d 次機(jī)會(huì)", leftTimes);
printf("\n神秘單詞是什么呢 ? ");
/* 我們顯示猜測(cè)的單詞患民,將還沒猜到的字母用*表示例如 : *O**LE */
for (i = 0 ; i < 6 ; i++)
{
if (letterFound[i]) // 如果第 i+1 個(gè)字母已經(jīng)猜到
printf("%c", secretWord[i]); // 打印出來
else
printf("*"); // 還沒猜到缩举,打印一個(gè)星號(hào) *
}
上面的代碼用于:
- 打印剩余機(jī)會(huì)數(shù)。
- 打印單詞(其中還沒猜到的字母用星號(hào)
*
表示)。
接下來仅孩,我們寫請(qǐng)求用戶輸入一個(gè)字母的代碼:
printf("\n輸入一個(gè)字母 : ");
letter = readCharacter();
還記得我們之前寫的函數(shù) readCharacter 嗎托猩?它用于讀取用戶的第一個(gè)輸入的字母,讀到回車符結(jié)束辽慕,而且它會(huì)把該字母轉(zhuǎn)成大寫京腥。
// 如果用戶輸入的字母不存在于單詞中
if (!researchLetter(letter, secretWord, letterFound))
{
leftTimes--; // 將剩余猜測(cè)機(jī)會(huì)數(shù)減 1
}
以上代碼調(diào)用 researchLetter 函數(shù)在單詞中查找用戶輸入的字母,如果沒找到溅蛉,則剩余猜測(cè)機(jī)會(huì)數(shù)扣除一次公浪。
如果字母存在于單詞中,則 researchLetter 函數(shù)還會(huì)更新 letterFound 數(shù)組(每個(gè)元素對(duì)應(yīng)了神秘單詞的每一個(gè)字母的猜測(cè)情況)船侧,將其中對(duì)應(yīng)的 0(還沒猜到)改為 1(已經(jīng)猜到)欠气。
這樣,win 函數(shù)在判斷的時(shí)候镜撩,如果 letterFound 數(shù)組的每一個(gè)元素都為 1预柒,則返回 1,表示本輪勝利袁梗,猜到單詞的全部字母了宜鸯。
暫時(shí),while 循環(huán)體的內(nèi)容就到這里了围段,然后我們還要寫跳出 while 循環(huán)之后的代碼(或者勝利或者失敼艘怼):
if (win(letterFound))
printf("\n\n勝利了! 神秘單詞是 : %s\n", secretWord);
else
printf("\n\n失敗了! 神秘單詞是 : %s\n", secretWord);
游戲主體部分的代碼就到這里了,給出我們到目前為止的完整程序:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int win(int letterFound[]);
int researchLetter(char letter, char secretWord[], int letterFound[]);
char readCharacter();
int main(int argc, char* argv[])
{
char letter = 0; // 存儲(chǔ)用戶輸入的字母
char secretWord[] = "BOTTLE"; // 要猜測(cè)的單詞
int letterFound[6] = {0}; // 布爾值的數(shù)組奈泪。數(shù)組的每一個(gè)元素對(duì)應(yīng)猜測(cè)單詞的一個(gè)字母。0 = 還沒猜到此字母灸芳,1 = 已猜到字母
int leftTimes = 7; // 剩余猜測(cè)次數(shù)(0 = 失斃晕Α)
int i = 0; // 為了遍歷數(shù)組,需要一個(gè)下標(biāo)
printf("歡迎來到懸掛小人游戲烙样!\n");
while (leftTimes > 0 && !win(letterFound))
{
printf("\n\n您還剩 %d 次機(jī)會(huì)", leftTimes);
printf("\n神秘單詞是什么呢 ? ");
/* 我們顯示猜測(cè)的單詞冯遂,將還沒猜到的字母用 * 表示例如 : *O**LE */
for (i = 0 ; i < 6 ; i++)
{
if (letterFound[i]) // 如果第 i+1 個(gè)字母已經(jīng)猜到
printf("%c", secretWord[i]); // 打印出來
else
printf("*"); // 還沒猜到,打印一個(gè)*
}
printf("\n輸入一個(gè)字母 : ");
letter = readCharacter();
// 如果用戶輸入的字母不存在于單詞中
if (!researchLetter(letter, secretWord, letterFound))
{
leftTimes--; // 將剩余猜測(cè)機(jī)會(huì)數(shù)減 1
}
}
if (win(letterFound))
printf("\n\n勝利了! 神秘單詞是 : %s\n", secretWord);
else
printf("\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 表示字母在單詞里
// 遍歷單詞數(shù)組 secretWord,以判斷所猜字母是否在單詞中
for (i = 0 ; secretWord[i] != '\0' ; i++)
{
if (letter == secretWord[i]) // 如果字母在單詞中
{
correctLetter = 1; // 表示猜對(duì)了一個(gè)字母
letterFound[i] = 1; // 對(duì)于所有等于所猜字母的數(shù)組位置批狱,都將其數(shù)值變?yōu)?
}
}
return correctLetter;
}
char readCharacter()
{
char character = 0;
character = getchar(); // 讀取一個(gè)字母
character = toupper(character); // 把這個(gè)字母轉(zhuǎn)成大寫
// 讀取其他的字符裸准,直到 \n(為了忽略它)
while (getchar() != '\n')
;
return character; // 返回讀到的第一個(gè)字母
}
這一部分的程序,你可以將其存放在一個(gè) .c
文件中赔硫,例如叫 hangman.c炒俱。
然后用 gcc 編譯(如果是在 IDE 里面,例如 CodeBlocks,那直接點(diǎn)擊編譯運(yùn)行):
gcc hangman.c -o hangman
運(yùn)行:
./hangman
接下來我們要開始第二部分:詞庫(kù)的代碼权悟。
根據(jù)這部分的代碼砸王,我們還會(huì)接著修改和添加 main 函數(shù)的內(nèi)容。
好吧峦阁,稍作休息谦铃,繼續(xù)前進(jìn)!
3. 解方(2. 詞庫(kù)的代碼)
我們已經(jīng)編寫了游戲主體部分的基本代碼榔昔,但是我們的游戲目前還不能做到每輪隨機(jī)抽取一個(gè)單詞荷辕。
因此,接下來我們就帶大家編寫處理詞庫(kù)的代碼件豌。
首先疮方,我們需要?jiǎng)?chuàng)建一個(gè)文件,用于存放所有的單詞茧彤。
在 Linux / Unix / macOS 操作系統(tǒng)下骡显,我們都可以直接創(chuàng)建一個(gè)不帶后綴名的文件。在 Windows 下可以創(chuàng)建 .txt 結(jié)尾的文本文件曾掂。
我寫這個(gè)游戲是在 Linux 系統(tǒng)下惫谤,所以直接用 Vim 或 Emacs 或其他編輯器創(chuàng)建一個(gè)文件, 位于我們?cè)次募南嗤夸浵拢篸ictionary珠洗。
在里面寫入以下單詞(每行一個(gè)溜歪,用回車符隔開):
YOU
MOTHER
LOVE
PANDA
BOTTLE
FUNNY
HONEY
LIKE
JAZZ
MUSIC
BREAD
APPLE
WATER
PEOPLE
DOG
CAT
GLASS
SKY
GOD
ZERO
當(dāng)然了,我這里只是舉個(gè)例子许蓖,你可以創(chuàng)建屬于自己的詞庫(kù)蝴猪。
新建兩個(gè)文件
處理這個(gè)文件的代碼將會(huì)不少(至少,我是這么預(yù)感的)膊爪,因此自阱,我們新建一個(gè) .c 源文件,可以命名為 dictionary.c米酬。
順便沛豌,我們也創(chuàng)建 dictionary.h 這個(gè)頭文件,其中存放 dictionary.c 中的函數(shù)的原型赃额,這樣我們?cè)?main 函數(shù)里就可以通過
#include "dictionary.h"
來引入這些函數(shù)的定義了加派。
在 dictionary.c 中,首先我們引入一些頭文件:
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // 我們需要這里面的隨機(jī)數(shù)函數(shù)跳芳,還記得我們的第一個(gè)小游戲“或多或少”嗎芍锦?
#include <string.h> // 我們需要 strlen 這個(gè)計(jì)算字符串長(zhǎng)度的函數(shù)
#include "dictionary.h"
chooseWord 函數(shù)
這個(gè)函數(shù)用于從文件 dictionary 中隨機(jī)選取一個(gè)單詞,此函數(shù)只有一個(gè)參數(shù): 指向內(nèi)存中可以寫入單詞的地址的指針筛严,這個(gè)指針實(shí)參將由 main 函數(shù)提供醉旦。
函數(shù)返回值是 int 變量:1 表示一切順利饶米;0 表示出現(xiàn)錯(cuò)誤。
此函數(shù)的開頭是這樣:
int chooseWord(char *wordChosen)
{
FILE* dictionary = NULL; // 指向我們的文件 dictionary(詞庫(kù))的文件指針
int wordNum = 0; // 詞庫(kù)中單詞總數(shù)
int chosenWordNum = 0; // 選中的單詞編號(hào)
int i = 0; // 下標(biāo)
int characterRead = 0; // 讀入的字符
}
聲明了一些變量车胡,我們接著寫:
dictionary = fopen("dictionary", "r"); // 以只讀模式打開詞庫(kù)(dictionary 文件)
if (dictionary == NULL) // 如果打開文件不成功
{
printf("\n無法裝載詞庫(kù)\n");
return 0; // 返回 0 表示出錯(cuò)
}
這段代碼不難吧檬输,就是嘗試打開詞庫(kù)(dictionary 文件),并檢測(cè) dictionary 文件指針是否為 NULL匈棘。
如果為 NULL丧慈,表示打開失敗。如果打開文件失敗主卫,則程序中止逃默,因?yàn)闆]有進(jìn)行下去的必要了。
// 統(tǒng)計(jì)詞庫(kù)中的單詞總數(shù)簇搅,也就是統(tǒng)計(jì)回車符 `\n` 的數(shù)目
do
{
characterRead = fgetc(dictionary);
if (characterRead == '\n')
wordNum++;
} while (characterRead != EOF);
上面這段代碼中完域,我們借助 fgetc 函數(shù)遍歷整個(gè)文件(一個(gè)字符一個(gè)字符讀取)瘩将。
我們統(tǒng)計(jì)讀到的回車符(\n
)的數(shù)目吟税,每讀到一個(gè) \n
,我們對(duì) wordNum(單詞總數(shù))的值加 1姿现。
我們通過以上代碼肠仪,就可以知道詞庫(kù)中的單詞總數(shù)了,就是 wordNum 的值备典。
然后异旧,我們需要一個(gè)函數(shù),根據(jù) wordNum 的值計(jì)算一個(gè)偽隨機(jī)數(shù)出來提佣,作為隨機(jī)選取的單詞編號(hào)吮蛹,我們就來寫一個(gè)函數(shù),命名為:randomNum镐依。
randomNum 函數(shù)
此函數(shù)里的代碼我們之前編寫第一個(gè) C語言小游戲: “或多或少” 時(shí)已經(jīng)用過了匹涮,就是簡(jiǎn)單的偽隨機(jī)數(shù)生成。
作用:用于返回一個(gè)介于 0 ~ (單詞總數(shù) - 1) 之間的隨機(jī)數(shù)槐壳。
int randomNum(int maxNum)
{
srand(time(NULL));
return (rand() % maxNum);
}
寫好了 randomNum 函數(shù),我們立即來使用它:
chosenWordNum = randomNum(wordNum); // 隨機(jī)選取一個(gè)單詞(編號(hào))
接著喜每,我們需要重新回到文件開始處來進(jìn)行讀取务唐,為了回到文件開始處,可以調(diào)用函數(shù) rewind带兜。
// 我們重新從文件開始處讀确愕选(rewind 函數(shù)),直到遇到選中的那個(gè)單詞
rewind(dictionary);
while (chosenWordNum > 0)
{
characterRead = fgetc(dictionary);
if (characterRead == '\n')
chosenWordNum--;
}
/* 文件指針已經(jīng)指向正確位置刚照,我們就用fgets來讀取那一行(也就是那個(gè)選中的單詞)*/
fgets(wordChosen, 100, dictionary);
// 放置 \0 字符用于表示字符串結(jié)束
wordChosen[strlen(wordChosen) - 1] = '\0';
fclose(dictionary);
return 1; // 一切順利刑巧,返回1
dictionary.h 文件
其中包含我們的 dictionary.c 中的函數(shù)原型,內(nèi)容如下:
#ifndef DICTIONARY_H
#define DICTIONARY_H
int chooseWord(char *wordChosen);
int randomNum(int maxNum);
#endif
完整的 dictionary.c 文件
/*
懸掛小人游戲
dictionary.c
------------
這里定義了兩個(gè)函數(shù):
1. chooseWord 用于每輪從 dictionary 文件中隨機(jī)抽取一個(gè)單詞
2. randomNum 用于返回一個(gè)介于 0 ~ (單詞總數(shù) - 1) 之間的隨機(jī)數(shù)
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "dictionary.h"
int chooseWord(char *wordChosen)
{
FILE* dictionary = NULL; // 指向我們的文件 dictionary 的文件指針
int wordNum = 0; // 單詞總數(shù)
int chosenWordNum = 0; // 選中的單詞編號(hào)
int i = 0; // 下標(biāo)
int characterRead = 0; // 讀入的字符
dictionary = fopen("dictionary", "r"); // 以只讀模式打開詞庫(kù)(dictionary 文件)
if (dictionary == NULL) // 如果打開文件不成功
{
printf("\n無法裝載詞庫(kù)\n");
return 0; // 返回 0 表示出錯(cuò)
}
// 統(tǒng)計(jì)詞庫(kù)中的單詞總數(shù),也就是統(tǒng)計(jì)回車符 \n 的數(shù)目
do
{
characterRead = fgetc(dictionary);
if (characterRead == '\n')
wordNum++;
} while (characterRead != EOF);
chosenWordNum = randomNum(wordNum); // 隨機(jī)選取一個(gè)單詞(編號(hào))
// 我們重新從文件開始處讀劝〕(rewind 函數(shù))吠冤,直到遇到選中的那個(gè)單詞
rewind(dictionary);
while (chosenWordNum > 0)
{
characterRead = fgetc(dictionary);
if (characterRead == '\n')
chosenWordNum--;
}
/* 文件指針已經(jīng)指向正確位置,我們就用fgets來讀取那一行(也就是那個(gè)選中的單詞)*/
fgets(wordChosen, 100, dictionary);
// 放置 \0 字符用于表示字符串結(jié)束
wordChosen[strlen(wordChosen) - 1] = '\0';
fclose(dictionary);
return 1; // 一切順利恭理,返回1
}
int randomNum(int maxNum)
{
srand(time(NULL));
return (rand() % maxNum);
}
修改 hangman.c 文件
現(xiàn)在拯辙,既然我們的處理詞庫(kù)的函數(shù)已經(jīng)寫完了,也就是在 dictionary.c 中颜价,那么我們需要相應(yīng)地修改我們的 hangman.c 文件中的 main 函數(shù)和其他幾個(gè)子函數(shù):
有了之前所有課程的知識(shí)涯保,靠著注釋,應(yīng)該不難看懂周伦。
完整的 hangman.c 文件
/*
懸掛小人游戲
main.c
------------
游戲的主體代碼
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "dictionary.h"
int win(int letterFound[], long wordSize);
int researchLetter(char letter, char secretWord[], int letterFound[]);
char readCharacter();
int main(int argc, char* argv[])
{
char letter = 0; // 存儲(chǔ)用戶輸入的字母
char secretWord[100] = {0}; // 要猜測(cè)的單詞
int *letterFound = NULL; // 布爾值的數(shù)組. 數(shù)組的每一個(gè)元素對(duì)應(yīng)猜測(cè)單詞的一個(gè)字母夕春。0 = 還沒猜到此字母, 1 = 已猜到字母
int leftTimes = 7; // 剩余猜測(cè)次數(shù) (0 = 失敗)
int i = 0; // 下標(biāo)
long wordSize = 0; // 單詞的長(zhǎng)度(字母數(shù)目)
printf("歡迎來到懸掛小人游戲!\n");
// 從詞庫(kù)(文件 dictionary)中隨機(jī)選取一個(gè)單詞
if (!chooseWord(secretWord))
exit(0); // 退出游戲
// 獲取單詞的長(zhǎng)度
wordSize = strlen(secretWord);
letterFound = malloc(wordSize * sizeof(int)); // 動(dòng)態(tài)分配數(shù)組的大小专挪,因?yàn)槲覀円婚_始不知道單詞長(zhǎng)度
if (letterFound == NULL)
exit(0);
// 初始化布爾值數(shù)組及志,都置為 0,表示還沒有字母被猜到
for (i = 0 ; i < wordSize ; i++)
letterFound[i] = 0;
// 主while循環(huán)狈蚤,如果還有猜測(cè)機(jī)會(huì)并且還沒勝利困肩,繼續(xù)
while (leftTimes > 0 && !win(letterFound, wordSize))
{
printf("\n\n您還剩 %d 次機(jī)會(huì)", leftTimes);
printf("\n神秘單詞是什么呢 ? ");
/* 我們顯示猜測(cè)的單詞,將還沒猜到的字母用*表示
例如 : *O**LE */
for (i = 0 ; i < wordSize ; i++)
{
if (letterFound[i]) // 如果第 i+1 個(gè)字母已經(jīng)猜到
printf("%c", secretWord[i]); // 打印出來
else
printf("*"); // 還沒猜到脆侮,打印一個(gè)*
}
printf("\n輸入一個(gè)字母 : ");
letter = readCharacter();
// 如果用戶輸入的字母不存在于單詞中
if (!researchLetter(letter, secretWord, letterFound))
{
leftTimes--; // 將剩余猜測(cè)機(jī)會(huì)數(shù)減 1
}
}
if (win(letterFound, wordSize))
printf("\n\n勝利了! 神秘單詞是 : %s\n", secretWord);
else
printf("\n\n失敗了! 神秘單詞是 : %s\n", secretWord);
return 0;
}
// 判斷是否勝利
int win(int letterFound[], long wordSize)
{
int i = 0;
int win = 1; // 1 為勝利锌畸,0 為失敗
for (i = 0 ; i < wordSize ; 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 表示字母在單詞里
// 遍歷單詞數(shù)組 secretWord靖避,以判斷所猜字母是否在單詞中
for (i = 0 ; secretWord[i] != '\0' ; i++)
{
if (letter == secretWord[i]) // 如果字母在單詞中
{
correctLetter = 1; // 表示猜對(duì)了一個(gè)字母
letterFound[i] = 1; // 對(duì)于所有等于所猜字母的數(shù)組位置潭枣,都將其數(shù)值變?yōu)?
}
}
return correctLetter;
}
char readCharacter()
{
char character = 0;
character = getchar(); // 讀取一個(gè)字母
character = toupper(character); // 把這個(gè)字母轉(zhuǎn)成大寫
// 讀取其他的字符,直到 \n (為了忽略它)
while (getchar() != '\n')
;
return character; // 返回讀到的第一個(gè)字母
}
好了幻捏,這個(gè)小游戲已經(jīng)寫完了盆犁,用 gcc 編譯并運(yùn)行看看吧!
gcc dictionary.c hangman.c -o hangman
然后:
./hangman
4. 第二部分第十一課預(yù)告
今天的課就到這里篡九,一起加油吧谐岁!
下一課:C語言探索之旅 | 第二部分第十一課:練習(xí)題和習(xí)作
我是 謝恩銘,公眾號(hào)「程序員聯(lián)盟」(微信號(hào):coderhub)運(yùn)營(yíng)者榛臼,慕課網(wǎng)精英講師 Oscar 老師伊佃,終生學(xué)習(xí)者。
熱愛生活沛善,喜歡游泳航揉,略懂烹飪。
人生格言:「向著標(biāo)桿直跑」