循环、函数和组织方面的问题



我想先说这不是当前的家庭作业;但这是我辍学前3年前的作业。我正在自学,正在重温旧作业。我不是在要求整个程序,我只是在寻求帮助为游戏的初始开始构建骨架。

更多信息: 玩家 1 将输入单词(任何长度/我一直在使用"测试")供玩家 2 猜测。玩家 2 将有 5 个字母猜测和 5 个单词猜测。如果玩家 2 进入"测试",它应该能够忽略上/下之间的情况(不使用上/下) IF:玩家 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来处理用户输入。 问题似乎在于,你已经纠结于组织游戏逻辑,使其流动,并设置可以帮助你的结构。

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

因此,让我们首先为实际运行游戏的函数创建一个存根。 您可以从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>

进入游戏... 将字符串转换为小写的帮助程序函数总是很方便。 我们一定会利用这一点。 请注意,这使用 lambda 函数。 如果您没有现代C++编译器(支持 C++11),则可以改用普通函数指针。

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

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

现在,我应该说,在现实中,大多数人不会像这样从外到内以线性方式编写代码。 这更像是一个有机的过程,但我确实注意将东西分成逻辑部分,并在我进行时重新洗牌/组织东西。 我也尽量不要一次做太多。

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

那里发生的一些事情是:

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

检查点:游戏骨架完成

在这一点上,你可能已经看到了足够多的东西来完成游戏。 上面介绍了一些重要的概念。 特别是,将剩余的字母存储为std::set的想法可能只是使所有内容都到位的技巧。

从这里开始阅读将完成该计划。 这取决于你是想这样做,还是停止阅读并先自己破解它。


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

请注意,输入将转换为小写。 这只会简化事情。 尤其是在检查重复猜测时。

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 书签。

如果没有别的,我希望这能让你对任务分解成你现在关心的小部分,以及你以后可以担心的其他事情有一些见解。 以这种方式迭代构建程序使您能够定期测试代码,并增加发现愚蠢错误的机会,这些错误可能会在以后阻碍您。 看到初学者只是一口气写出整个程序,运行它,然后因为它不"工作"而吓坏了,这是很常见的。

最新更新