首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >模拟骰子小游戏

模拟骰子小游戏
EN

Code Review用户
提问于 2013-05-20 23:59:32
回答 1查看 3.2K关注 0票数 16

我现在想养成良好的编码习惯。下面的代码是在严格的约束下编写的。我们在课堂上做作业不是为了讨论“真实世界”的最佳实践--我们只是在很少或没有反馈的情况下得到字母等级。有哪些好问题的例子,我可以问自己,以确定什么时候,为一个分配的代码将重新编写在现实世界的应用程序?

(我对内存管理也很感兴趣,我们还没有在课堂上讨论过。任何内存管理提示re:下面的代码将是超级感谢!)

注意:这是一个完成的任务,我并不是在寻找帮助来完成它,只是帮助尽早养成良好的习惯。

任务:

  • 模拟5500个骰子游戏并打印结果。
  • 除了main()之外,只使用一个函数-- dice_roll()。所有的计算都是内联的。
  • 任务包括:enums、输出精度以及rand()srand()函数。
  • 使用enum定义赢、输,并继续给出每个滚动的结果。
  • 使用const数据大小:int SIZEint ROLLS

请随时提供您认为合适的任何评论/问题。

代码语言:javascript
复制
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <ctime>

// this is what will hold our data during the games
struct nodeType
{
  int num_rolls;
  int win;
  int loss;
  nodeType* link;
};

// these pointers will be used to traverse and perform operations
// on the nodes as we figure out what to do with them
nodeType *first, *last, *current, *trailCurrent, *newNode, *temp;

int roll_dice();


int main()
{
  enum gameOutcome { CONTINUE, WIN, LOSE };
  gameOutcome game_result;
  const int MAX_GAMES(5500);
  int sum, point, roll;
  first = NULL; // start of our list
  last = NULL; // end of our list

  srand(time(0)); // give rand() new seed

  for (int i = 1; i <= MAX_GAMES; ++i) // this for loop simulates all games
  {
    sum = roll_dice();

    newNode = new nodeType; // create new node for this game
    newNode->link = NULL; // make sure it doesn't point to anything

    switch (sum) // first roll test
    {
      case 7:
        game_result = WIN;
        roll = 1;
      case 11:
        game_result = WIN;
        roll = 1;
        break;
      case 2:
        game_result = LOSE;
        roll = 1;
      case 3:
        game_result = LOSE;
        roll = 1;
      case 12:
        game_result = LOSE;
        roll = 1;
        break;
      default:
        game_result = CONTINUE;
        point = sum;
        roll = 1;
        break;
    } // end of switch

    while (game_result == CONTINUE) // if no win/lose after 1st roll
    {
      sum = roll_dice();
      ++roll;

      if (sum == point)
        game_result = WIN;
      else if (sum == 7)
        game_result = LOSE;
    }

    // these assignments prepare our node fields to accept the
    // game outcome data
    newNode->num_rolls = roll;
    newNode->win = 0;
    newNode->loss = 0;

    if(game_result == WIN)
      newNode->win = 1; // adds one win for unique # of rolls

    else
      newNode->loss = 1; // adds one loss for unique # of rolls

    if(first == NULL) // if empty list creates list on first roll
    {
      first = newNode;
      last = newNode;
    }

    else
    {
      current = first;  // starts search at beginning of list
      int found(0);     // use for list elem search

      while (current != NULL && found < 1) // search to insert ascending order
      {
        if (current->num_rolls == roll) // has a game w/ this number of rolls
        {                               // been played?
          if (game_result == WIN)
            current->win += 1;          // if so add one win to that "row"
          else
            current->loss += 1;         // if so add one loss to that "row"
          found = 1;
        }

        else if (current->num_rolls > roll) // if game's #rolls is < than some
          found = 2;                        // #rolls in the list

        else
        {
          trailCurrent = current;
          current = current->link; // advances the search one node
        }
      } // end of while

      if (found == 1)
        delete[] newNode; // if #rolls for complete game already exists,
                          // delete this node. this is like "deduping" a
                          // database to first normal form

      else if (current == first) // inserts node at beginning of list
      {
        newNode->link = first;
        first = newNode;
      }
      else
      {
        trailCurrent->link = newNode; // inserts node in middle of list
        newNode->link = current;

        if (current == NULL) // if it's the biggest #rolls so far
          last = newNode;    // insert node at end of list
      } // end of last else
    } // end of 1st else
  } // end of main for loop

  int sum_wins(0), sum_loss(0);
  current = first; // set to first node in list before iterating over it
  while (current != NULL) // print results and sum wins/losses
  {
    std::cout << std::setw(4) << current->win << " games won and "
              << std::setw(3) << current->loss << " games lost on roll "
               << current->num_rolls << std::endl;
    sum_wins += current->win; // summing wins for use below
    sum_loss += current->loss; // summing losses for use below
    current = current->link;
  }

  // calculate the odds based on game results
  std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint)
            << "\nodds of winning are " << sum_wins << " / "
            << sum_wins + sum_loss << " = " << std::setprecision(2)
            << 100.0 * sum_wins / (sum_wins + sum_loss) << "%." << std::endl;

  // calculate avg num rolls per game
  double avg_length(0);
  current = first;
  while (current != NULL)
  {
    avg_length += (current->win + current->loss) * current->num_rolls;
    current = current->link;
  }
  std::cout << "avg num rolls/game is " << std::setprecision(2)
            << avg_length / 5500.00 << " rolls." << std::endl;

  while (first != NULL) // destroy list
  {
    temp = first;
    first = first->link;
    delete[] temp;
  }

  last = NULL;

  std::cout << "press RET to exit";
  std::cin.get();

  return 0;
} // end of int main()


int roll_dice()
{
  return (rand() % 6) + (rand() % 6) + 2;
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2013-05-21 03:08:29

有很多事情你想问自己。第一个问题是,“在现实世界中,这些要求有意义吗?”在这种情况下,答案是强调“不”。

除了main()之外,只使用一个函数-- dice_roll()。所有的计算都是内联的。

这是一个可怕的要求,无论是谁写了这份作业,都需要用常识的蝙蝠来解决。这是从一开始就教不好的练习。拥有一个巨大的main函数是不可取的。

要问的第二个问题是,“我是使用C++,还是使用Cstd::cout一起使用,而不是printf?”这很可能不是你的错--任何决定将学生的所有代码打包到main函数中的老师,充其量都是在做一项可疑的工作。

让我们解决这两个问题。如果你想学习,我要说的是扔掉这些要求中的一些。该方案的真正要求是:

模拟5500个骰子游戏并打印结果。

在这里,需求本身导致程序结构相对容易崩溃。我们将保持它的简单性,并且说我们总共需要4个函数:

  • 一个模拟骰子卷- roll_dice() (已经存在)的函数
  • 一个用来模拟游戏并将结果存储在某种数据结构中的函数--让我们称之为run_simulation。这将以要玩的游戏数量作为参数。
  • 一种基于游戏计算和打印统计数据的函数。这将从以前的函数中获取数据结构。
  • 协调上述功能的main

C++的优点之一是标准库中提供了丰富的数据结构。它们通常为您执行内存管理。它不是在需求中列出的,但是从您的代码中,您想要存储给定数量的按顺序排列的滚动的赢/输数。

同样,我不确定您在数据结构方面有多少经验,但是,标准库很适合这个需求:std::map。映射以一种有效的方式存储键/值对,因此键查找是快速的。

好的,我们将一如既往地开始:定义我们的enum和用来存储游戏数据的东西--调用它(缺乏想象力) game_data

代码语言:javascript
复制
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <ctime>
#include <map>    // <--- Additional include so we can use `std::map`

enum game_outcome { CONTINUE, WIN, LOSE };

struct game_data
{
    int win;
    int loss;

    game_data()
      : win(0),
        loss(0)
    { } 

    game_data(int w, int l)
      : win(w),
        loss(l)
    { }
};

上面,我们为我们的game_data类型定义了一些构造函数。现在,我们将定义我们的map类型:

代码语言:javascript
复制
typedef std::map<int, game_data> result_list;

这表明result_list类型是从整数到键的映射。在这里,int键将是滚动的数量。map的一个好处是它按排序顺序存储东西--所以我们的排序是免费的。

代码语言:javascript
复制
int roll_dice()
{
    return (rand() % 6) + (rand() % 6) + 2;
}

result_list run_simulation(int num_games)
{
    result_list results;
    static game_outcome outcome[] = {CONTINUE, CONTINUE, LOSE, LOSE, CONTINUE, CONTINUE, 
                                     CONTINUE, WIN, CONTINUE, CONTINUE, CONTINUE, WIN,
                                     LOSE};

    for(int i = 0; i < num_games; ++i) {

        int roll = 1;
        int sum = roll_dice();
        game_outcome out = outcome[sum];

        while (out == CONTINUE) {
            int point = sum;
            sum = roll_dice();
            ++roll;
            if(sum == point) {
                out = WIN;
            }
            else if(sum == 7) {
                out = LOSE;
            }
        }
        game_data& d = results[roll];
        (out == WIN) ? ++d.win : ++d.loss;
    }    

    return results;
}

首先,我们已经用数组查找代替了switch语句,这使得代码更加简洁,并且可以说不容易出错(在开关语句的末尾很容易忘记break )。除了一些重新排序之外,while循环基本上没有变化。最主要的改变是用这两行代码替换了很多代码:

代码语言:javascript
复制
game_data& d = results[roll];
(out == WIN) ? ++d.win : ++d.loss;

map中的查找可以使用[]语法完成(非常类似于数组)。如果本例中不存在密钥,则插入该键。因此,我们使用rolls查找数据。例如,如果rolls为3,这将找到映射中的game_data类,其中包含前面的结果。然后,我们只需根据结果增加正确的winloss

现在,我们需要编写一个函数,它将获取地图中的内容,并从其中打印出一些信息:让我们将其命名为result_statistics

代码语言:javascript
复制
void result_statistics(const result_list& results, int num_games)
{
    int wins = 0;
    int losses = 0;
    double avg_length = 0;

    for(auto const & p : results) {
        std::cout << std::setw(4) << p.second.win << " games won and " 
                  << std::setw(3) << p.second.loss << " games lost on roll "
                  << p.first << "\n";
        wins += p.second.win;
        losses += p.second.loss;
        avg_length += (p.second.win + p.second.loss) * p.first;
    }
    int total_games = wins + losses;
    std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) 
              << "\nodds of winning are " << wins << " / "
              << total_games << " = " << std::setprecision(2)
              << 100.0 * wins / total_games << "%." << "\n";
    std::cout << "avg num rolls/game is " << std::setprecision(2)
              << avg_length / num_games << " rolls." << "\n";

}

打印代码基本上是一样的。

最后,我们的新main函数:

代码语言:javascript
复制
int main()
{
    const int kGames = 5500;
    srand(time(nullptr));
    result_list r = run_simulation(kGames);
    result_statistics(r, kGames);
}

请注意,我们现在可以(至少在较高级别上)跟踪代码流。我们定义游戏的总数,种子随机生成器,运行模拟,并计算统计从这些运行。以这种方式将事情分解,使事情更容易推理并在代码中遵循。

即使其中的一些(或大部分)对您来说还没有意义,我认为主要的事情是,如果您被告知将所有东西都塞进一个main函数中,那么如果这是必需的,就为您的任务做。然后,返回并将其分解为更小的函数,每个函数都有一个特定的、定义明确的、单一的用途。比较这两个人的代码。哪一个更容易理解?研究标准库,尽可能多地教自己(基本容器可能是目前最重要的事情)。最后,看看作业规格。它们都有意义吗?它们会导致多少可读性的代码吗?如果你有自由统治,你会采取什么不同的做法?

票数 22
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/26395

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档