当前位置:嗨网首页>书籍在线阅读

16-掷骰子

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

12.3 掷骰子

我们将要模拟一个非常流行的游戏——掷骰子。骰子的形式多种多样,最普遍的是使用两个6面骰子。在一些冒险游戏中,会使用5种骰子:4面、6面、8面、12面和20面。聪明的古希腊人证明了只有5种正多面体,它们的所有面都具有相同的形状和大小。各种不同类型的骰子就是根据这些正多面体发展而来。也可以做成其他面数的,但是其所有的面不会都相等,因此各个面朝上的几率就不同。

计算机计算不用考虑几何的限制,所以可以设计任意面数的电子骰子。我们先从6面开始。

我们想获得1~6的随机数。然而, rand() 生成的随机数在 0RAND_MAX 之间。 RAND_MAX 被定义在 stdlib.h 中,其值通常是 INT_MAX 。因此,需要进行一些调整,方法如下。

1.把随机数求模6,获得的整数在0~5之间。

2.结果加1,新值在1~6之间。

3.为方便以后扩展,把第1步中的数字6替换成骰子面数。

下面的代码实现了这3个步骤:

#include <stdlib.h> /* 提供rand()的原型 */
int rollem(int sides)
{
     int roll;
     roll = rand() % sides + 1;
     return roll;
}

我们还想用一个函数提示用户选择任意面数的骰子,并返回点数总和。如程序清单12.11所示。

程序清单12.11  diceroll.c 程序

/* diceroll.c -- 掷骰子模拟程序 */
                                /* 与 manydice.c 一起编译 */
#include "diceroll.h"
#include <stdio.h>
#include <stdlib.h>             /* 提供库函数 rand()的原型 */
int roll_count = 0;             /* 外部链接 */
static int rollem(int sides)    /* 该函数属于该文件私有 */
{
     int roll;
     roll = rand() % sides + 1;
     ++roll_count;                /* 计算函数调用次数 */
     return roll;
}
int roll_n_dice(int dice, int sides)
{
     int d;
     int total = 0;
     if (sides < 2)
     {
          printf("Need at least 2 sides.\n");
          return -2;
     }
     if (dice < 1)
     {
          printf("Need at least 1 die.\n");
          return -1;
     }
     for (d = 0; d < dice; d++)
          total += rollem(sides);
     return total;
}

该文件加入了新元素。第一, rollem() 函数属于该文件私有,它是 roll_n_dice() 的辅助函数。第二,为了演示外部链接的特性,该文件声明了一个外部变量 roll_count 。该变量统计调用 rollem() 函数的次数。这样设计有点蹩脚,仅为了演示外部变量的特性。第三,该文件包含以下预处理指令:

#include "diceroll.h"

如果使用标准库函数,如 rand() ,要在当前文件中包含标准头文件(对 rand() 而言要包含 stdlib.h ),而不是声明该函数。因为头文件中已经包含了正确的函数原型。我们效仿这一做法,把 roll_n_dice() 函数的原型放在 diceroll.h 头文件中。把文件名放在双引号中而不是尖括号中,指示编译器在本地查找文件,而不是到编译器存放标准头文件的位置去查找文件。“本地查找”的含义取决于具体的实现。一些常见的实现把头文件与源代码文件或工程文件(如果编译器使用它们的话)放在相同的目录或文件夹中。程序清单12.12是头文件中的内容。

程序清单12.12  diceroll.h 文件

//diceroll.h
extern int roll_count;
int roll_n_dice(int dice, int sides);

该头文件中包含一个函数原型和一个 extern 声明。由于 diceroll.c 文件包含了该文件, direroll.c 实际上包含了 roll_count 的两个声明:

extern int roll_count;     // 头文件中的声明(引用式声明)
int roll_count = 0;        // 源代码文件中的声明(定义式声明)

这样做没问题。一个变量只能有一个定义式声明,但是带 extern 的声明是引用式声明,可以有多个引用式声明。

使用 roll_n_dice() 函数的程序都要包含 diceroll.h 头文件。包含该头文件后,程序便可使用 roll_n_dice() 函数和 roll_count 变量。如程序清单12.13所示。

程序清单12.13  manydice.c 文件

/* manydice.c -- 多次掷骰子的模拟程序 */
/* 与 diceroll.c 一起编译*/
#include <stdio.h>
#include <stdlib.h>          /* 为库函数 srand() 提供原型    */
#include <time.h>            /* 为 time() 提供原型            */
#include "diceroll.h"        /* 为roll_n_dice()提供原型,为roll_count变量提供声明 */
int main(void)
{
     int dice, roll;
     int sides;
     int status;
     srand((unsigned int) time(0));    /* 随机种子 */
     printf("Enter the number of sides per die, 0 to stop.\n");
     while (scanf("%d", &sides) == 1 && sides > 0)
     {
          printf("How many dice?\n");
          if ((status = scanf("%d", &dice)) != 1)
          {
               if (status == EOF)
                    break;            /* 退出循环 */
               else
               {
                    printf("You should have entered an integer.");
                    printf(" Let's begin again.\n");
                    while (getchar() != '\n')
                           continue;        /* 处理错误的输入 */
                   printf("How many sides? Enter 0 to stop.\n");
                   continue;            /* 进入循环的下一轮迭代 */
               }
          }
          roll = roll_n_dice(dice, sides);
          printf("You have rolled a %d using %d %d-sided dice.\n",
               roll, dice, sides);
          printf("How many sides? Enter 0 to stop.\n");
     }
     printf("The rollem() function was called %d times.\n",
          roll_count);           /* 使用外部变量 */
     printf("GOOD FORTUNE TO YOU!\n");
     return 0;
}

要与包含程序清单12.11的文件一起编译该文件。可以把程序清单12.11、12.12和12.13都放在同一文件夹或目录中。运行该程序,下面是一个输出示例:

Enter the number of sides per die, 0 to stop.
6
How many dice?
2
You have rolled a 12 using 2 6-sided dice.
How many sides? Enter 0 to stop.
6
How many dice?
2
You have rolled a 4 using 2 6-sided dice.
How many sides? Enter 0 to stop.
6
How many dice?
2
You have rolled a 5 using 2 6-sided dice.
How many sides? Enter 0 to stop.
0
The rollem() function was called 6 times.
GOOD FORTUNE TO YOU!

因为该程序使用了 srand() 随机生成随机数种子,所以大多数情况下,即使输入相同也很难得到相同的输出。注意, manydice.c 中的 main() 访问了定义在 diceroll.c 中的 roll_count 变量。

有3种情况可以导致外层 while 循环结束: side 小于 1 、输入类型不匹配(此时 scanf() 返回 0 )、遇到文件结尾(返回值是 EOF )。对于读取骰子的点数,该程序处理文件结尾的方式(退出 while 循环)与处理类型不匹配(进入循环的下一轮迭代)的情况不同。

可以通过多种方式使用 roll_n_dice()sides 等于 2 时,程序模仿掷硬币,“正面朝上”为 2 ,“反面朝上”为 1 (或者反过来表示也行)。很容易修改该程序单独显示点数的结果,或者构建一个骰子模拟器。如果要掷多次骰子(如在一些角色扮演类游戏中),可以很容易地修改程序以输出类似的结果:

Enter the number of sets; enter q to stop.
18
How many sides and how many dice?
6 3
Here are 18 sets of 3 6-sided throws.
12 10 6 9 8 14 8 15 9 14 12 17 11 7 10
13 8 14
How many sets? Enter q to stop.
q

rand1()rand() (不是 rollem() )还可以用来创建一个猜数字程序,让计算机选定一个数字,你来猜。读者感兴趣的话可以自己编写这个程序。