简体   繁体   English

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

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

and I would like to preface this by saying this is NOT a current homework assignment;我想先说这不是当前的家庭作业; but it is an assignment from 3 years ago before I dropped out of school.但这是我辍学前三年前的作业。 I am self teaching and am revisiting an old assignment.我正在自学,并且正在重新审视一项旧作业。 I am NOT asking for the entire program, I'm simply looking for help building the skeleton for the initial start of the game.我不是要整个程序,我只是在寻找帮助构建游戏初始骨架的帮助。

MORE INFO: Player 1 will enter word(of any length / i have been using "Testing") for Player 2 to guess.更多信息:玩家 1 将输入单词(任何长度/我一直在使用“测试”)供玩家 2 猜测。 Player 2 will have 5 letter guesses, and 5 word guesses.玩家 2 将有 5 个字母猜测和 5 个单词猜测。 If Player 2 enters "Testing" it should be able to ignore the case between upper/lower (WITHOUT using toupper / tolower) IF: Player 2 enters more than 1 letter for a guess: "aa" make them guess again until they only guess 1 letter "a".如果玩家 2 进入“测试”,它应该能够忽略上/下之间的大小写(不使用 toupper / tolower)如果:玩家 2 输入超过 1 个字母进行猜测:“aa”让他们再次猜测,直到他们只猜测1个字母“a”。

The problems I'm facing is: I don't know where to place everything, I feel I'm mixing up or messing up the functions, and everytime I try to organize it, it only gets worse.我面临的问题是:我不知道把所有东西放在哪里,我觉得我在混淆或弄乱功能,每次我试图组织它,它只会变得更糟。 I've restarted it several times, I'm just having trouble getting it all laid out.我已经重新启动了几次,我只是无法将其全部布置好。

#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;

            }

        }
      

    }   
       
        

        
        
    }

There are several things in C++ that can assist you. C++ 中有几件事可以帮助您。 It's good to see that you're already using std::string and std::getline to deal with the user input.很高兴看到您已经在使用std::stringstd::getline来处理用户输入。 The problem seems to be that you've gotten tangled up in organizing the game logic so that it flows, and setting up structures that can help you.问题似乎是您在组织游戏逻辑以使其流动以及设置可以帮助您的结构方面陷入困境。

I did actually go ahead and write a game just for kicks.我实际上做了 go 并写了一个游戏只是为了踢球。 The hope is that I can provide some of that and describe it so you can digest in chunks, and you can see how one can build a program up a bit at a time.希望我可以提供其中的一些内容并对其进行描述,以便您可以分块消化,并且您可以看到如何一次构建一个程序。

So let's start by making a stub for the function that will actually run the game.因此,让我们首先为实际运行游戏的 function 创建一个存根。 You can call this from main .您可以从main调用它。 It simplifies the actual running of the game by separating it from the other setup and shutdown stuff.它通过将游戏与其他设置和关闭内容分开来简化游戏的实际运行。 It also means you can run several games in a row later, without having to modify the game loop.这也意味着您可以在以后连续运行多个游戏,而无需修改游戏循环。

enum GameResult {
    None,
    Win,
    Loss,
};

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

And to illustrate that point, here is the full main that I ended up writing to invoke this game.为了说明这一点,这是我最终为调用这个游戏而写的完整的main内容。 Even though it's a one-off, you can see that it can be useful.尽管它是一次性的,但您可以看到它很有用。 In this case, I chose to read the game settings from the command line:在这种情况下,我选择从命令行读取游戏设置:

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;
    }
}

So, now there's a simple framework for your game to run in. There's not much code, but it's something you can immediately start testing before moving on to fleshing out the game itself.所以,现在有一个简单的框架供您的游戏运行。代码不多,但您可以立即开始测试,然后再继续充实游戏本身。

At this point, I should mention some headers that this program will be needing.在这一点上,我应该提到该程序将需要的一些标头。 Some will have been required already.有些已经被要求了。 Others are required for stuff we're about to do.我们将要做的事情需要其他人。

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

On to the game... A helper function to turn a string into lowercase is always handy.开始游戏... 帮助 function 将字符串转换为小写字母总是很方便。 We'll definitely make use of that.我们一定会利用它。 Note that this uses a lambda function.请注意,这使用了 lambda function。 If you don't have a modern C++ compiler (with C++11 support) you can just use an ordinary function pointer instead.如果您没有现代 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;
}

Now it's time to expand on the PlayHangman stub.现在是时候扩展PlayHangman存根了。 It's still gonna be a stub, but we can set up a few things that we'll be needing and get those tested before proceeding.它仍然是一个存根,但我们可以设置一些我们需要的东西,并在继续之前对它们进行测试。

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;
}

That is going to be the primary structure of a single game.这将是单个游戏的主要结构。 So let's talk about it.所以让我们谈谈它。 Notice again how I'm still not going into detail.再次注意我仍然没有详细说明。 At this point, I've already thought about how I should logically be running the game.在这一点上,我已经考虑过我应该如何在逻辑上运行游戏。

Now, I should say that in reality, most people don't write code in a linear way like this from the outside in. It's more of an organic process, but I do take care to separate stuff out into logical bits, and reshuffle/organize stuff as I go.现在,我应该说,实际上,大多数人不会像这样从外到内以线性方式编写代码。这更像是一个有机过程,但我确实注意将内容分成逻辑位,然后重新洗牌/像我一样整理东西 go。 I also try not to do too much at once.我也尽量不要一次做太多。

You'll see by the way I've presented this, that I'm encouraging you to develop a solid platform in which to write your game logic.通过我介绍的方式,您会看到,我鼓励您开发一个可靠的平台来编写您的游戏逻辑。 By the time you're writing that logic, you should be able to trust that everything else already works because you tested it.当您编写该逻辑时,您应该能够相信其他一切都已经工作,因为您已经对其进行了测试。

Some things happening up there are:上面发生的一些事情是:

  • The target string is copied into a lowercase version of itself.目标字符串被复制到其自身的小写版本中。 This will be used to test the word-guesses.这将用于测试单词猜测。 There are other ways to test strings ignoring case, but this is just a simple way.还有其他方法可以忽略大小写来测试字符串,但这只是一种简单的方法。
  • Because we've built that string, we can also use it to construct a std::set containing exactly one of each unique character in that string.因为我们已经构建了该字符串,所以我们还可以使用它来构建一个std::set ,该集合恰好包含该字符串中每个唯一字符中的一个。 That's a one-liner, constructing the set from the string's iterators.这是一个单行,从字符串的迭代器构造集合。 Very neat and tidy!非常整洁!
  • We also have a set of strings called guesses -- this will track all the guesses (correct/incorrect inclusive) so that you don't get penalized for accidentally repeating something you already guessed.我们还有一组称为guesses的字符串——这将跟踪所有的猜测(包括正确/不正确),这样您就不会因意外重复您已经猜到的内容而受到惩罚。
  • There's a sanity check, which is a duplicate of what will eventually be the end-game test inside the loop.有一个健全性检查,它是循环内最终游戏测试的副本。 To be honest, that was one of the last things I added, but I've put it here because it's part of the pre-game setup, and apart from the stubbed loop, this is the entire "game" sequence.老实说,这是我最后添加的内容之一,但我把它放在这里是因为它是游戏前设置的一部分,除了存根循环之外,这是整个“游戏”序列。

Checkpoint: Game skeleton complete检查点:游戏骨架完成

At this point, you might have seen enough to go off and complete the game.至此,您可能已经看到足够的 go 关闭并完成游戏。 There are some important concepts introduced up there.上面介绍了一些重要的概念。 In particular, the idea of storing the remaining letters as a std::set might be just the kind of trick that makes everything click into place.特别是,将剩余字母存储为std::set的想法可能只是让所有内容都点击到位的技巧。

Reading from here on will complete the program.从这里开始阅读将完成该程序。 It's up to you whether you want to do that, or stop reading and have a crack at it yourself first.您是否想这样做,或者停止阅读并先自己尝试一下,这取决于您。


Let's start fleshing out some of the game loop.让我们开始充实一些游戏循环。 First, you probably wanna deal with showing the current game state and requesting input.首先,您可能想要处理显示当前游戏 state 并请求输入。 That happens in two steps.这发生在两个步骤中。 The first part builds a string by hiding characters that are not yet guessed and then outputs it.第一部分通过隐藏尚未猜到的字符来构建一个字符串,然后将其输出。 The second part is an input-validating loop that discards empty lines, ignores duplicate guesses and handles end-of-stream.第二部分是一个输入验证循环,它丢弃空行,忽略重复猜测并处理流尾。

Note that the input is converted to lowercase.请注意,输入被转换为小写。 This just simplifies things.这只是简化了事情。 Especially when checking for duplicate guesses.特别是在检查重复猜测时。

    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
   }

Once again, we have expanded on the implementation and now have something to test.再一次,我们扩展了实现,现在有一些东西要测试。 So make sure it all compiles and works the way you want it to.因此,请确保一切都按照您想要的方式编译和工作。 Obviously the game will run forever right now, but that's fine -- you can just terminate the process.显然游戏现在将永远运行,但这很好——你可以终止该过程。

When you're happy, move on to the actual logic.当您感到高兴时,请继续执行实际逻辑。 This is where we start putting together everything that has already been set up.这是我们开始将所有已经设置好的东西放在一起的地方。

Thanks to our input loop, we now know that the input is now a new guess comprising either 1 letter or a word.由于我们的输入循环,我们现在知道输入现在是一个新的猜测,包括 1 个字母或一个单词。 So I start by branching for either the letter guess or the word guess.因此,我首先针对字母猜测或单词猜测进行分支。 You should start to see a pattern here, right?你应该开始在这里看到一个模式,对吧? Once again, I write an empty section of code to do something, and then start actually filling it in...再一次,我写了一段空白的代码来做某事,然后开始实际填充它......

        // 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
            }
        }

So, the letter test... Recall we already built the lettersRemaining set and tested it.所以,字母测试......回想一下,我们已经构建了lettersRemaining集并对其进行了测试。 And those are the only ones obscured by dashes in the prompt.这些是提示中唯一被破折号遮挡的内容。 So it then becomes trivial to determine whether they guessed one.因此,确定他们是否猜到了一个变得微不足道。 If it's in the set, they guessed correctly and you remove it from the set.如果它在集合中,他们猜对了,你将它从集合中移除。 Otherwise, they burn up one of their guesses.否则,他们会烧毁他们的猜测之一。

Because the input is already lowercase, we can use the letter verbatim to search within the values stored in the set (which are also lowercase).因为输入已经是小写的,我们可以使用字母逐字搜索存储在集合中的值(也是小写的)。

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

The word test is even easier.单词测试更容易。 Recall that we stored a lowercase version of the target word already, and the input was also converted to lowercase.回想一下,我们已经存储了目标单词的小写版本,并且输入也被转换为小写。 So we just compare.所以我们只是比较。 You see how all this lowercase business has actually made life less complicated?您看到所有这些小写业务实际上如何使生活变得不那么复杂了吗?

                // 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;
                }

We are quite literally almost done.我们几乎完成了。 The only thing left to do is check whether the game should stop due to being won or lost.剩下要做的就是检查游戏是否应该因输赢而停止。 That's the last part of the game loop.这是游戏循环的最后一部分。

Because the code handling a correct word guess is also polite and clears the lettersRemaining set, we can use that as a test for a winning condition regardless of whether a letter or word was guessed.因为处理正确单词猜测的代码也是礼貌的并且清除了lettersRemaining集,所以我们可以将其用作获胜条件的测试,无论是否猜测了字母或单词。

You'll also see that bit of logic again for the game losing condition.您还将再次看到游戏失败条件的那种逻辑。 Recall that from before the main loop where we checked if it was even possible to win.回想一下,在我们检查是否有可能获胜的主循环之前。

        // 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;
        }

I hope this has been helpful, that you've been able to follow along, and that you understand the breakdown and explanation.我希望这对您有所帮助,您已经能够跟进,并且您了解故障和解释。 This is generally how I approach programming.这通常是我处理编程的方式。 I like to build up pieces of code that I can rely on, instead of getting lost in some details and overlooking more fundamental things.我喜欢构建我可以依赖的代码片段,而不是迷失在一些细节中而忽略更基本的事情。

There may be some techniques, language features or parts of the standard library used here that you have not encountered before.此处使用的某些技术、语言特性或标准库的某些部分可能是您以前没有遇到过的。 That's fine -- you can use that to learn, experiment and research online.很好——你可以用它来在线学习、实验和研究。 Keep https://cppreference.com bookmarked in your browser.在浏览器中保留https://cppreference.com书签。

If nothing else, I hope that this gives you some insight in breaking down tasks into small bits that you care about now, and other stuff that you can worry about later.如果不出意外,我希望这能让您深入了解将任务分解为您现在关心的小部分,以及您以后可以担心的其他内容。 Building up a program iteratively this way enables you to test code regularly and increases your chances of finding silly mistakes that could hamstring you later.以这种方式迭代地构建程序使您能够定期测试代码,并增加发现愚蠢错误的机会,这些错误可能会在以后妨碍您。 It is so common to see beginners just write a whole program in one hit, run it, then freak out because it doesn't "work".看到初学者一键编写一个完整的程序,运行它,然后因为它不“工作”而吓坏了,这是很常见的。

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

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