首页 > 解决方案 > 石头剪刀布有时会给出错误的结果

问题描述

新手在这里编程。今天尝试制作一个石头剪刀布游戏来练习c++中的rng。我已经多次检查代码并确保没有错误或没有重叠,但有时它仍然显示错误的结果。是因为我使用了开关吗?这是代码:

#include <cstdlib>
#include <ctime>
#include <iostream>

using namespace std;

int main() {

    char answer;
    int rng;
    int cont = 0;

    do {
        srand((unsigned)time(0));
        rng = (rand() % 3) + 1;

        switch (rng) {
        case 1:
            cout << "r for rock, p for paper, s for scissors \n";
            cout << "please enter your hand: ";
            cin >> answer;
            switch (answer) {
            case 'r':
                cout << "The computer used rock! \n";
                cout << "It's a draw.\n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 'p':
                cout << "The computer used paper! \n";
                cout << "You lose. \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 's':
                cout << "The computer used scissors! \n";
                cout << "You win! \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            }break;
        case 2:
            cout << "r for rock, p for paper, s for scissors \n";
            cout << "please enter your hand: ";
            cin >> answer;
            switch (answer) {
            case 'r':
                cout << "the computer used paper! \n";
                cout << "You lose. \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 'p':
                cout << "the computer used paper! \n";
                cout << "It's a draw. \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 's':
                cout << "the computer used paper! \n";
                cout << "You win! \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            }break;
        case 3:
            cout << "r for rock, p for paper, s for scissors \n";
            cout << "please enter your hand: ";
            cin >> answer;
            switch (answer) {
            case 'r':
                cout << "the computer used scissors! \n";
                cout << "You win! \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 'p':
                cout << "the computer used scissors! \n";
                cout << "You lose. \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            case 's':
                cout << "the computer used scissors! \n";
                cout << "It's a draw. \n";
                cout << "enter 1 to continue: ";
                cin >> cont;
                break;
            }break;
        }
    } while (cont == 1);
}

标签: c++

解决方案


你的每个案例都有很多重复的事实switch应该引发一个危险信号。有一个叫做 DRY(Don't Repeat Yourself)的原则可以让我们写出更好的代码。这是因为每次重复都是一个额外的地方,如果需要更改,您必须更改逻辑。最终你会错过一个,不同的场景会有不同的表现。它还会增加代码的大小并降低其可读性。

那么我们如何不重复自己呢?我们需要查看重复的部分,并确定是否需要将其放入循环、函数中,或者在您的情况下将其拉出。

这是您的代码,去掉了重复部分。首先,让我们讨论一下用户输入。你总是需要得到它,它不依赖于计算机选择了什么。用户的选择和计算机的选择是相互排斥的。

被拉出的另一部分是要求继续。同样,它与计算机所做的选择无关。您输入了要求继续九次的代码。那感觉不对。

#include <cstdlib>
#include <ctime>
#include <iostream>

using namespace std;

int main() {
  srand((unsigned)time(0));  // CHANGED: Only seed ONCE
  char answer;
  int rng;
  int cont = 0;

  do {
    // CHANGED: Get user's input up front
    cout << "r for rock, p for paper, s for scissors \nplease enter your hand: ";
    cin >> answer;

    rng = (rand() % 3) + 1;

    switch (rng) {
      case 1:
        switch (answer) {
          case 'r':
            cout << "The computer used rock! \n";
            cout << "It's a draw.\n";
            break;
          case 'p':
            cout << "The computer used paper! \n";
            cout << "You lose. \n";
            break;
          case 's':
            cout << "The computer used scissors! \n";
            cout << "You win! \n";
            break;
        }
        break;
      case 2:
        switch (answer) {
          case 'r':
            cout << "the computer used paper! \n";
            cout << "You lose. \n";
            break;
          case 'p':
            cout << "the computer used paper! \n";
            cout << "It's a draw. \n";
            break;
          case 's':
            cout << "the computer used paper! \n";
            cout << "You win! \n";
            break;
        }
        break;
      case 3:
        switch (answer) {
          case 'r':
            cout << "the computer used scissors! \n";
            cout << "You win! \n";
            break;
          case 'p':
            cout << "the computer used scissors! \n";
            cout << "You lose. \n";
            break;
          case 's':
            cout << "the computer used scissors! \n";
            cout << "It's a draw. \n";
            break;
        }
        break;
    }

    cout << "enter 1 to continue: ";
    cin >> cont;
  } while (cont == 1);
}

我们已经可以看到开关盒看起来更干净了。您描述的逻辑错误可能仍然存在。但我要承认为什么我不检查每个单独的结果是懒惰的。我们可以做得比嵌套开关更好。

让我们考虑一下结果:玩家赢、输或平。平局是最容易检查的。玩家的选择是否与电脑相符?这是你的 [内脏]do循环。

  do {
    // CHANGED: Get user's input up front
    // TODO: Naive input validation
    cout << "r for rock, p for paper, s for scissors \nyour choice: ";
    cin >> answer;

    rng = (rand() % 3) + 1;

    // 1: rock, 2: paper, 3: scissors
    int playerNum;
    switch (answer) {
      case ('r'):
        playerNum = 1;
        break;
      case ('p'):
        playerNum = 2;
        break;
      case ('s'):
        playerNum = 3;
        break;
      default:
        std::cerr << "Shouldn't be here\n";
    }

    if (playerNum == rng) {
      std::cout << "It's a tie!\n";
    }

    cout << "enter 1 to continue: ";
    cin >> cont;
  } while (cont == 1);

我们可以看到它仍然有点笨拙,因为计算机选择了int,而您将输入作为char. 这需要我们进行一些转换。我们可以通过匹配类型来完全消除转换的需要。我喜欢用户输入字符的想法(我认为这有助于防止复制/粘贴/提交),所以我要让计算机选择一个字符。由于我正在接触计算机如何进行选择,因此我也将放弃srand()/rand()并使用<random>,这是 C++ 获取随机值的方式。

这是到目前为止的新代码:

#include <array>
#include <iostream>
#include <random>

using namespace std;

int main() {
  // CPU setup
  constexpr std::array<char, 3> choices{'r', 'p', 's'};
  std::mt19937 prng(std::random_device{}());
  std::uniform_int_distribution<int> rps(0, 2);

  char answer;
  char cpuChoice;  // CHANGED: rng was a bad name
  int cont = 0;

  do {
    // CHANGED: Get user's input up front
    // TODO: Naive input validation
    cout << "r for rock, p for paper, s for scissors \nyour choice: ";
    cin >> answer;

    cpuChoice = choices[rps(prng)];

    if (answer == cpuChoice) {
      std::cout << "It's a tie.\n";
    }

    cout << "enter 1 to continue: ";
    cin >> cont;
  } while (cont == 1);
}

现在我们可以直接将用户输入与计算机的选择进行比较,并检测出平局。让我们看看检测胜利。

只有三种情况会导致玩家获胜。

player 'r' chooses and cpu chooses 's'
player 'p' chooses and cpu chooses 'r'
player 's' chooses and cpu chooses 'p'

所以如果我们没有平局,让我们看看我们是否赢了:

    if (answer == cpuChoice) {
      std::cout << "It's a tie.\n";
    } else if ((answer == 'r' && cpuChoice == 's') ||
               (answer == 'p' && cpuChoice == 'r') ||
               (answer == 's' && cpuChoice == 'p')) {
      std::cout << "You win!\n";
    }

让我们退后一步,将其与您的嵌套开关进行比较。要检查平局,需要查看三段不同的代码,并确保三段代码都是正确的。我检查一个。赢了也是一样。在调试方面,这更容易遵循和检查。

现在,如何处理损失。好吧,如果你没有打平,你没有赢,那么你就输了。没有必要检查我们是否做到了,如果我们做到了这一点,我们就知道我们做到了。

    if (answer == cpuChoice) {
      std::cout << "It's a tie.\n";
    } else if ((answer == 'r' && cpuChoice == 's') ||
               (answer == 'p' && cpuChoice == 'r') ||
               (answer == 's' && cpuChoice == 'p')) {
      std::cout << "You win!\n";
    } else {
      std::cout << "You lose :(\n";
    }

而这一小块代码决定了游戏的结果。如果要声明计算机选择的内容,请在此块之前和之外执行。

还有很多其他的小东西可以改变。using namespace std;是不好的做法。我曾经用那条线来教 Intro,因为我认为它可以“缓和”学生的学习。当到了下学期放弃这条线的时候,一些学生无法放手。所以要学会早点失去它。

您也不会验证用户输入,也不会考虑错误输入。我不是在谈论任何复杂的事情,只是确保输入了一个好的值。最好在它自己的功能中完成。

这是我的:

template <typename T, typename Container>
void ask_user_with_selection(const std::string& prompt, T& inputVar,
                             const Container& validOptions) {
  bool inputIsInvalid = true;
  do {
    std::cout << prompt;
    std::cin >> inputVar;
    if (std::find(validOptions.begin(), validOptions.end(), inputVar) ==
        validOptions.end()) {
      std::cout << "Please enter a valid choice.\n";
      std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
      std::cin.clear();
    } else {
      inputIsInvalid = false;
    }
  } while (inputIsInvalid);
}

当我开始学习 C++20 概念时,这个函数会有所改变。


推荐阅读