C語言探索之旅 | 第二部分第十課: 實(shí)戰(zhàn)"懸掛小人"游戲答案

作者 謝恩銘,公眾號(hào)「程序員聯(lián)盟」(微信號(hào):coderhub)。
轉(zhuǎn)載請(qǐng)注明出處缔俄。
原文:http://www.reibang.com/p/b239b1774f4b

《C語言探索之旅》全系列

內(nèi)容簡(jiǎn)介


  1. 前言
  2. 解方(1. 游戲的代碼)
  3. 解方(2. 詞庫(kù)的代碼)
  4. 第二部分第十一課預(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 步來介紹我們的解方:

  1. 首先我們會(huì)演示如何一步步寫游戲的主體部分拆座,一開始我們會(huì)只有一個(gè)猜測(cè)的單詞,而且是固定的冠息;我選了 BOTTLE(表示“瓶子”)挪凑,因?yàn)槲覀円獪y(cè)試對(duì)于單詞中有大于等于兩個(gè)相同字母的情況是否處理正確了(BOTTLE 中有 2 個(gè) T)。

  2. 然后我們會(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è)功能:

  1. 返回一個(gè)布爾值(在 C語言里用 int 型表示)端蛆,用于表示所猜的字母是否存在于單詞中凤粗。

  2. 更新 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)桿直跑」

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末金刁,一起剝皮案震驚了整個(gè)濱河市帅涂,隨后出現(xiàn)的幾起案子议薪,更是在濱河造成了極大的恐慌,老刑警劉巖媳友,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐绒,死亡現(xiàn)場(chǎng)離奇詭異百宇,居然都是意外死亡沪么,警方通過查閱死者的電腦和手機(jī)腔召,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搂抒,“玉大人艇搀,你說我怎么就攤上這事∏缶В” “怎么了焰雕?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芳杏。 經(jīng)常有香客問我矩屁,道長(zhǎng),這世上最難降的妖魔是什么爵赵? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任吝秕,我火速辦了婚禮,結(jié)果婚禮上空幻,老公的妹妹穿的比我還像新娘烁峭。我一直安慰自己,他們只是感情好秕铛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布约郁。 她就那樣靜靜地躺著,像睡著了一般但两。 火紅的嫁衣襯著肌膚如雪鬓梅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天谨湘,我揣著相機(jī)與錄音绽快,去河邊找鬼。 笑死紧阔,一個(gè)胖子當(dāng)著我的面吹牛谎僻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寓辱,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赤拒!你這毒婦竟也來了秫筏?” 一聲冷哼從身側(cè)響起诱鞠,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎这敬,沒想到半個(gè)月后航夺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崔涂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年阳掐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冷蚂。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缭保,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝙茶,到底是詐尸還是另有隱情艺骂,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布隆夯,位于F島的核電站钳恕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蹄衷。R本人自食惡果不足惜忧额,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愧口。 院中可真熱鬧睦番,春花似錦、人聲如沸调卑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恬涧。三九已至注益,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溯捆,已是汗流浹背丑搔。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留提揍,地道東北人啤月。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像劳跃,于是被迫代替她去往敵國(guó)和親谎仲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容