繁体   English   中英

循环、功能和组织方面的问题

[英]trouble with loops, functions, and organization´

我想先说这不是当前的家庭作业; 但这是我辍学前三年前的作业。 我正在自学,并且正在重新审视一项旧作业。 我不是要整个程序,我只是在寻找帮助构建游戏初始骨架的帮助。

更多信息:玩家 1 将输入单词(任何长度/我一直在使用“测试”)供玩家 2 猜测。 玩家 2 将有 5 个字母猜测和 5 个单词猜测。 如果玩家 2 进入“测试”,它应该能够忽略上/下之间的大小写(不使用 toupper / tolower)如果:玩家 2 输入超过 1 个字母进行猜测:“aa”让他们再次猜测,直到他们只猜测1个字母“a”。

我面临的问题是:我不知道把所有东西放在哪里,我觉得我在混淆或弄乱功能,每次我试图组织它,它只会变得更糟。 我已经重新启动了几次,我只是无法将其全部布置好。

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

int main()
{
    string word, wordguess, lower, dashes;
    string letterguess;
    int i = 0;
    bool GameOver = false, Validletterguess = true, Validwordguess = true;    


    cout << "Player 1, enter a word for Player 2 to guess: " << endl;
    getline(cin, word);
    cout << endl;
    cout << "Player 2, you have 5 letter guesses, and 5 word guesses." << endl;
    cout << "Guess your first letter: " << endl; 

    while (GameOver == false) // Start of Game. Setup for Round 1 letter guess and word guess.
    {           
      
        while (letterguess.length() != 1) // 1 letter only. loop until they enter 1 letter
        {
            cout << endl << "Type a single letter and press <enter>: ";
            cin >> letterguess;   // enter letter guess          

            for (int i = 0; i < letterguess.length(); i++) //ignore case of letter guess
            {
                if (letterguess.at(i) >= 'A' && letterguess.at(i) <= 'Z') 
                {
                    lower += letterguess.at(i) + 32; 

                }
                else
                {
                    lower += letterguess.at(i); 
                }  

            }  
            if (letterguess.at(i) == word.at(i) && Validletterguess == true) //if Player2 guesses a correct letter, replace the dash with letter and display location: ex. If "T" then "You guessed the 1st and 4th letter"
            {
                cout << "You guessed the first letter right!" << endl; // figure out how to display dashes? 
                dashes.at(i) = letterguess.at(i);
                cout << "Enter your first word guess: " << endl;
                cin >> wordguess;
            }
            else
                cout << "Wrong letter! Enter your first word guess: " << endl;
                cin >> wordguess;      

            if (wordguess == word & Validwordguess = true)
            {
                cout << "You guessed the word correctly in 1 try! " << endl;
                Gameover = true;

            }

        }
      

    }   
       
        

        
        
    }

C++ 中有几件事可以帮助您。 很高兴看到您已经在使用std::stringstd::getline来处理用户输入。 问题似乎是您在组织游戏逻辑以使其流动以及设置可以帮助您的结构方面陷入困境。

我实际上做了 go 并写了一个游戏只是为了踢球。 希望我可以提供其中的一些内容并对其进行描述,以便您可以分块消化,并且您可以看到如何一次构建一个程序。

因此,让我们首先为实际运行游戏的 function 创建一个存根。 您可以从main调用它。 它通过将游戏与其他设置和关闭内容分开来简化游戏的实际运行。 这也意味着您可以在以后连续运行多个游戏,而无需修改游戏循环。

enum GameResult {
    None,
    Win,
    Loss,
};

GameResult PlayHangman(const std::string& target, int wrongLetterLimit, int wrongWordLimit)
{
    return None;
}

为了说明这一点,这是我最终为调用这个游戏而写的完整的main内容。 尽管它是一次性的,但您可以看到它很有用。 在这种情况下,我选择从命令行读取游戏设置:

void ExitSyntaxMessage(int code = -1)
{
    std::cerr << "Syntax: hangman <word> [guesses [wordGuesses]]\n";
    exit(code);
}

int main(int argc, char** argv)
{
    // Get game settings
    std::string target;
    int letterGuesses = 5;
    int wordGuesses = 5;

    try {
        if (argc < 2) throw std::runtime_error("Not enough arguments");
        target = argv[1];
        if (argc > 2) letterGuesses = std::stoi(argv[2]);
        if (argc > 3) wordGuesses = std::stoi(argv[3]);
    }
    catch(...)
    {
        ExitSyntaxMessage();
    }

    // Play game
    GameResult result = PlayHangman(target, letterGuesses, wordGuesses);

    // Deliver result and exit
    switch(result)
    {
    case Win:
        std::cout << "Congratulations!\n";
        return 0;
    case Loss:
        std::cout << "Better luck next time!\n";
        return 1;
    default:
        std::cout << "Game stopped.\n";
        return -2;
    }
}

所以,现在有一个简单的框架供您的游戏运行。代码不多,但您可以立即开始测试,然后再继续充实游戏本身。

在这一点上,我应该提到该程序将需要的一些标头。 有些已经被要求了。 我们将要做的事情需要其他人。

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include <set>

开始游戏... 帮助 function 将字符串转换为小写字母总是很方便。 我们一定会利用它。 请注意,这使用了 lambda function。 如果您没有现代 C++ 编译器(支持 C++11),则可以使用普通的 function 指针代替。

std::string Lowercase(const std::string& s)
{
    std::string lc(s);
    std::transform(lc.begin(), lc.end(), lc.begin(),
        [](char c)->char{ return std::tolower(c); });
    return lc;
}

现在是时候扩展PlayHangman存根了。 它仍然是一个存根,但我们可以设置一些我们需要的东西,并在继续之前对它们进行测试。

GameResult PlayHangman(const std::string& target, int wrongLetterLimit, int wrongWordLimit)
{
    GameResult result = None;

    // Create lowercase target and add its characters to set of remaining letters
    std::string lcTarget = Lowercase(target);
    std::set<char> lettersRemaining(lcTarget.begin(), lcTarget.end());
    std::set<std::string> guesses;

    // Set up game parameters
    int letterGuessesRemaining = wrongLetterLimit;
    int wordGuessesRemaining = wrongWordLimit;

    // Sanity-test to ensure game is winnable
    if (wordGuessesRemaining == 0 && letterGuessesRemaining < lettersRemaining.size())
    {
        std::cout << "Game is not winnable...\n";
        return None;
    }

    // Game loop until stream error or game finishes
    bool done = false;
    while (!done)
    {
        done = true;  // The loop is a stub for now
    }

    //// ^^^ for now, just use this bit to test the game setup stuff.
    //// Make sure that your lowercase bits are working and the set of
    //// remaining letters works.  You can add some output code to debug
    //// their values and run tests from the command line to verify.

    return result;
}

这将是单个游戏的主要结构。 所以让我们谈谈它。 再次注意我仍然没有详细说明。 在这一点上,我已经考虑过我应该如何在逻辑上运行游戏。

现在,我应该说,实际上,大多数人不会像这样从外到内以线性方式编写代码。这更像是一个有机过程,但我确实注意将内容分成逻辑位,然后重新洗牌/像我一样整理东西 go。 我也尽量不要一次做太多。

通过我介绍的方式,您会看到,我鼓励您开发一个可靠的平台来编写您的游戏逻辑。 当您编写该逻辑时,您应该能够相信其他一切都已经工作,因为您已经对其进行了测试。

上面发生的一些事情是:

  • 目标字符串被复制到其自身的小写版本中。 这将用于测试单词猜测。 还有其他方法可以忽略大小写来测试字符串,但这只是一种简单的方法。
  • 因为我们已经构建了该字符串,所以我们还可以使用它来构建一个std::set ,该集合恰好包含该字符串中每个唯一字符中的一个。 这是一个单行,从字符串的迭代器构造集合。 非常整洁!
  • 我们还有一组称为guesses的字符串——这将跟踪所有的猜测(包括正确/不正确),这样您就不会因意外重复您已经猜到的内容而受到惩罚。
  • 有一个健全性检查,它是循环内最终游戏测试的副本。 老实说,这是我最后添加的内容之一,但我把它放在这里是因为它是游戏前设置的一部分,除了存根循环之外,这是整个“游戏”序列。

检查点:游戏骨架完成

至此,您可能已经看到足够的 go 关闭并完成游戏。 上面介绍了一些重要的概念。 特别是,将剩余字母存储为std::set的想法可能只是让所有内容都点击到位的技巧。

从这里开始阅读将完成该程序。 您是否想这样做,或者停止阅读并先自己尝试一下,这取决于您。


让我们开始充实一些游戏循环。 首先,您可能想要处理显示当前游戏 state 并请求输入。 这发生在两个步骤中。 第一部分通过隐藏尚未猜到的字符来构建一个字符串,然后将其输出。 第二部分是一个输入验证循环,它丢弃空行,忽略重复猜测并处理流尾。

请注意,输入被转换为小写。 这只是简化了事情。 特别是在检查重复猜测时。

    while (!done)
    {
        // Create prompt from original string with a dash for each hidden character
        std::string prompt(target);
        for(char& c : prompt)
        {
            if (lettersRemaining.count(std::tolower(c)) != 0) c = '-';
        }
        std::cout << prompt << "\n";

        // Get input
        std::string input;
        for (bool validInput = false; !validInput && !done; )
        {
            std::cout << "> " << std::flush;
            if (!std::getline(std::cin, input))
            {
                done = true;
            }
            else if (!input.empty())
            {
                input = Lowercase(input);
                validInput = guesses.insert(input).second;
                if (!validInput)
                {
                    std::cout << "You already guessed that!\n";
                }
            }
        }
        if (done)
            continue;

        // TODO: Process guess, update game state, and check end-game conditions
   }

再一次,我们扩展了实现,现在有一些东西要测试。 因此,请确保一切都按照您想要的方式编译和工作。 显然游戏现在将永远运行,但这很好——你可以终止该过程。

当您感到高兴时,请继续执行实际逻辑。 这是我们开始将所有已经设置好的东西放在一起的地方。

由于我们的输入循环,我们现在知道输入现在是一个新的猜测,包括 1 个字母或一个单词。 因此,我首先针对字母猜测或单词猜测进行分支。 你应该开始在这里看到一个模式,对吧? 再一次,我写了一段空白的代码来做某事,然后开始实际填充它......

        // Check the guessed letter or word
        bool correctGuess = false;
        if (input.size() == 1)
        {
            if (letterGuessesRemaining == 0)
            {
                std::cout << "No more letter guesses remain.\n";
            }
            else
            {
                // Test the guessed letter
            }
        }
        else
        {
            if (wordGuessesRemaining == 0)
            {
                std::cout << "No more word guesses remain.\n";
            }
            else
            {
                // Test the guessed word
            }
        }

所以,字母测试......回想一下,我们已经构建了lettersRemaining集并对其进行了测试。 这些是提示中唯一被破折号遮挡的内容。 因此,确定他们是否猜到了一个变得微不足道。 如果它在集合中,他们猜对了,你将它从集合中移除。 否则,他们会烧毁他们的猜测之一。

因为输入已经是小写的,我们可以使用字母逐字搜索存储在集合中的值(也是小写的)。

                // Test the guessed letter
                char letter = input[0];
                if (lettersRemaining.count(letter) != 0)
                {
                    correctGuess = true;
                    lettersRemaining.erase(letter);
                }
                else
                {
                    std::cout << "Nope!\n";
                    --letterGuessesRemaining;
                }

单词测试更容易。 回想一下,我们已经存储了目标单词的小写版本,并且输入也被转换为小写。 所以我们只是比较。 您看到所有这些小写业务实际上如何使生活变得不那么复杂了吗?

                // Test the guessed word
                if (input == lcTarget)
                {
                    correctGuess = true;
                    lettersRemaining.clear();  //<-- we can use this to test for a win
                }
                else
                {
                    std::cout << "Nope!\n";
                    --wordGuessesRemaining;
                }

我们几乎完成了。 剩下要做的就是检查游戏是否应该因输赢而停止。 这是游戏循环的最后一部分。

因为处理正确单词猜测的代码也是礼貌的并且清除了lettersRemaining集,所以我们可以将其用作获胜条件的测试,无论是否猜测了字母或单词。

您还将再次看到游戏失败条件的那种逻辑。 回想一下,在我们检查是否有可能获胜的主循环之前。

        // If guessed incorrectly, show remaining attempts
        if (!correctGuess)
        {
            std::cout << "\nAttempts remaining: "
                << letterGuessesRemaining << " letters, "
                << wordGuessesRemaining << " words.\n";
        }            

        // Check if game is complete
        if (lettersRemaining.empty())
        {
            std::cout << target << "\n";
            result = Win;
            done = true;
        }
        else if (wordGuessesRemaining == 0 && letterGuessesRemaining < lettersRemaining.size())
        {
            std::cout << target << "\n";
            result = Loss;
            done = true;
        }

我希望这对您有所帮助,您已经能够跟进,并且您了解故障和解释。 这通常是我处理编程的方式。 我喜欢构建我可以依赖的代码片段,而不是迷失在一些细节中而忽略更基本的事情。

此处使用的某些技术、语言特性或标准库的某些部分可能是您以前没有遇到过的。 很好——你可以用它来在线学习、实验和研究。 在浏览器中保留https://cppreference.com书签。

如果不出意外,我希望这能让您深入了解将任务分解为您现在关心的小部分,以及您以后可以担心的其他内容。 以这种方式迭代地构建程序使您能够定期测试代码,并增加发现愚蠢错误的机会,这些错误可能会在以后妨碍您。 看到初学者一键编写一个完整的程序,运行它,然后因为它不“工作”而吓坏了,这是很常见的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM