构建小项目(如基于文本的用户界面 (TUI) 掷骰子应用程序)将帮助你提高 Python 编程技能。您将学习如何收集和验证用户输入、从模块和包中导入代码、编写函数、使用 for 循环和条件,以及使用字符串和 print() 函数整齐地显示输出。
在本项目中,您将编写一个模拟掷骰子事件的应用程序。为此,您将使用 Python 的随机模块。
在本教程中,您将学习如何
- 使用
random.randint()
模拟掷骰子事件 - 使用内置的
input()
函数请求用户输入 - 解析并验证用户输入
- 使用
.center()
和 .join()
等方法操作字符串
您还将学习如何结构、组织、记录和运行 Python 程序和脚本的基础知识。
项目概述
您的掷骰子模拟器应用程序将有一个最小但用户友好的基于文本的用户界面(TUI),您可以通过它指定要掷的六面骰子的数量。您可以使用这个 TUI 在家里掷骰子,而不必特意买副飞行棋。
以下是该应用程序内部工作方式的说明:
要运行的任务 | 使用的工具 | 要编写的代码 |
---|---|---|
提示用户选择要掷出多少个六面骰子,然后读取用户的输入 | Python 的内置 input() 函数 | 使用适当的参数调用input() |
分析并验证用户的输入 | 字符串方法、比较运算符和条件语句 | 一个名为parse_input() |
运行掷骰子模拟 | Python 的随机 模块,特别是 randint() 函数 | 一个名为roll_dice() |
使用生成的骰子面生成 ASCII 图 | 循环、list.append() 和 str.join() | 一个名为generate_dice_faces_diagram() |
在屏幕上显示骰子面图 | Python 的内置 print() 函数 | 使用适当的参数调用print() |
考虑到这些内部运作,您将编写三个自定义函数,以提供应用程序的主要特性和功能。这些函数将定义代码的公共应用程序接口,您将调用它来实现应用程序。
为了组织掷骰子模拟器项目的代码,您将在文件系统中选择一个目录,创建一个名为 dice.py 的文件。开始创建文件吧!
前提条件
在开始构建这个掷骰子模拟项目之前,您应该熟悉以下概念和技能:
- 在 Python 中运行脚本的方法
- Python 的导入机制
- Python 数据类型的基础知识,主要是字符串和整数
- 基本数据结构,尤其是列表
- Python 变量和常量
- Python 比较运算符
- 布尔值和逻辑表达式
- 条件语句
- Python for 循环
- Python 中输入、输出和字符串格式化的基础知识
如果您在开始编码探险之前没有掌握所有必备知识,那也没关系!继续前进并开始学习,您可能会学到更多!如果您遇到困难,可以停下来查看此处链接的资源。
第 1 步:为 Python 掷骰子应用程序编写 TUI 代码
在这一步中,您将编写所需的代码,要求用户输入他们想在模拟中掷几颗骰子。您还将编写一个 Python 函数,该函数将接收用户的输入并进行验证,如果验证成功,则返回一个整数。否则,函数将再次询问用户的输入。
在命令行中接收用户输入
为了让自己的手变得更脏,你可以开始编写与用户交互的代码。这些代码将为应用程序提供基于文本的界面,并依赖于 input()。这个内置函数从命令行读取用户输入。它的 prompt 参数允许你输入输入类型的描述。
启动你喜欢的编辑器或集成开发环境,在你的 dice.py 文件中输入以下代码:
# dice.py# ~~~ App's main code block ~~~
# 1. Get and validate user's input
num_dice_input = input("How many dice do you want to roll? [1-6] ")
num_dice = parse_input(num_dice_input)
在第 5 行调用 input() 时会显示一个提示,询问用户想掷几颗骰子。根据提示,数字必须在 1 到 6(包括 6)之间。
注意:通过在第 3 行添加注释,可以将应用程序的主代码与接下来要添加的其他代码分开。
同样,第 4 行的注释也反映了你此时正在执行的具体任务。在本教程的其他章节中,你会发现更多类似的注释。这些注释是可选的,所以如果你愿意,可以随意删除它们。
第 6 行调用 parse_input(),并将返回值存储在 num_dice 中。在下一节中,您将实现该函数。
解析和验证用户输入
parse_input() 的工作是接收用户输入的字符串,检查它是否是一个有效的整数,然后返回一个 Python int 对象。请将以下内容添加到您的 dice.py 文件中,就在应用程序的主代码之前:
# dice.pydef parse_input(input_string):"""Return `input_string` as an integer between 1 and 6.Check if `input_string` is an integer number between 1 and 6.If so, return an integer with the same value. Otherwise, tellthe user to enter a valid number and quit the program."""if input_string.strip() in {"1", "2", "3", "4", "5", "6"}:return int(input_string)else:print("Please enter a number from 1 to 6.")raise SystemExit(1)# ~~~ App's main code block ~~~
# ...
下面逐行介绍这段代码的工作原理:
-
第 3 行定义了以输入字符串为参数的 parse_input()。
-
第 4 行至第 9 行提供了函数的 docstring。在函数中包含内容丰富、格式规范的 docstring 是 Python 编程的最佳实践,因为 docstrings 允许您记录您的代码。
-
第 10 行检查用户输入的骰子数是否有效。调用 .strip() 可以删除输入字符串周围多余的空格。in 运算符检查输入是否在允许掷骰子的个数范围内。在这种情况下,我们使用集合,因为 Python 数据结构中的成员检验非常有效。
-
第 11 行将输入转换为整数并返回给调用者。
-
第 13 行在屏幕上打印一条信息,提醒用户输入无效(如果适用)。
-
第 14 行以 SystemExit 异常和状态代码为 1 的方式退出应用程序,以提示出错。
使用 parse_input(),可以在命令行处理并验证用户输入。验证任何直接来自用户或任何不受信任来源的输入是应用程序可靠安全运行的关键。
注:本教程代码示例中的行号是为了便于解释。大多数情况下,它们与您最终脚本中的行号并不一致。
现在,您已经拥有了用户友好的 TUI 和适当的输入验证机制,您需要确保这些功能正常工作。这就是下面要做的事情。
试用掷骰子应用程序的 TUI
要试用迄今为止编写的代码,请打开命令行窗口并运行 dice.py 脚本:
$ python dice.py
How many dice do you want to roll? [1-6] 3$ python dice.py
How many dice do you want to roll? [1-6] 7
Please enter a number from 1 to 6.
如果输入的是 1 到 6 的整数,代码不会显示任何信息。另一方面,如果输入的数字不是有效的整数,或者超出了目标区间,那么就会显示一条信息,告诉你需要输入 1 到 6 之间的整数。
到此为止,你已经成功编写了代码,可以在命令行请求并解析用户输入。这段代码为应用程序提供了基于内置 input() 函数的 TUI。您还编写了一个函数来验证用户输入,并以整数形式返回。现在,是时候掷骰子了!
第 2 步:用 Python 模拟掷六面骰子
现在,您的掷骰子应用程序提供了一个 TUI 来接收用户输入并进行处理。好极了!为了继续构建应用程序的主要功能,您将编写 roll_dice() 函数,该函数将允许您模拟掷骰子事件。该函数将获取用户想要掷的骰子数。
Python 标准库中的随机模块提供了 randint() 函数,它可以生成给定区间内的伪随机整数。我们将利用这个函数来模拟掷骰子。
下面是实现 roll_dice() 的代码:
# dice.py
import random# ...def roll_dice(num_dice):"""Return a list of integers with length `num_dice`.Each integer in the returned list is a random number between1 and 6, inclusive."""roll_results = []for _ in range(num_dice):roll = random.randint(1, 6)roll_results.append(roll)return roll_results# ~~~ App's main code block ~~~
# ...
在这段代码中,第 2 行将 random 导入当前命名空间。通过导入,以后就可以访问 randint() 函数。下面是代码其余部分的详细说明:
-
第 6 行定义了 roll_dice(),它接收一个参数,代表在给定调用中要掷的骰子数。
-
第 7 至 11 行提供了函数的 docstring。
-
第 12 行创建了一个空列表 roll_results,用于存储模拟掷骰子的结果。
-
第 13 行定义了一个 for 循环,对用户想要掷出的每个骰子迭代一次。
-
第 14 行调用 randint() 生成一个从 1 到 6(含 6)的伪随机整数。每次迭代都会生成一个数字。这个数字代表掷六面骰的结果。
-
第 15 行将当前的掷骰子结果添加到 roll_results 中。
-
第 16 行返回掷骰子模拟结果列表。
要试用新创建的函数,请在 dice.py 文件末尾添加以下代码行:
# dice.py
# ...# ~~~ App's main code block ~~~
# 1. Get and validate user's input
num_dice_input = input("How many dice do you want to roll? [1-6] ")
num_dice = parse_input(num_dice_input)
# 2. Roll the dice
roll_results = roll_dice(num_dice)print(roll_results) # Remove this line after testing the app
在这段代码中,第 9 行以 num_dice 为参数调用 roll_dice()。第 11 行调用 print() 将结果以数字列表的形式显示在屏幕上。列表中的每个数字代表一个骰子的结果。测试完代码后,您可以删除第 11 行。
继续从命令行运行您的应用程序:
$ python dice.py
How many dice do you want to roll? [1-6] 5
[6, 1, 3, 6, 6]$ python dice.py
How many dice do you want to roll? [1-6] 2
[2, 6]
屏幕上的结果列表会有所不同,因为你正在生成自己的伪随机数。在本例中,您分别模拟了掷五颗骰子和两颗骰子。因为使用的是六面骰子,所以每个骰子的值都在 1 到 6 之间。
现在,您已经编写并试用了模拟掷骰子事件的代码,是时候继续为您的应用程序提供一种华丽的方式来显示这些结果了。这就是下一节要做的事情。
第 3 步:生成并显示骰面的 ASCII 图
此时,您的应用程序已经模拟了掷多个骰子的过程,并将结果存储为一个数字列表。不过,从用户的角度来看,数字列表并不吸引人。您需要一个更漂亮的输出,这样才能让您的应用程序看起来更专业。
在本节中,您将编写代码来生成一个图表,显示最多六个骰子的面值。为此,您将创建一些 ASCII 艺术。
~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ ● ● │ │ │ │ ● │ │ ● │
│ │ │ ● │ │ ● │ │ │
│ ● ● │ │ │ │ ● │ │ ● │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
图中的每个骰面都反映了一次模拟迭代的结果值。要开始编码构建此图的功能,您需要将一些 ASCII 图像放在一起。返回代码编辑器并添加以下内容:
# dice.py
import randomDICE_ART = {1: ("┌─────────┐","│ │","│ ● │","│ │","└─────────┘",),2: ("┌─────────┐","│ ● │","│ │","│ ● │","└─────────┘",),3: ("┌─────────┐","│ ● │","│ ● │","│ ● │","└─────────┘",),4: ("┌─────────┐","│ ● ● │","│ │","│ ● ● │","└─────────┘",),5: ("┌─────────┐","│ ● ● │","│ ● │","│ ● ● │","└─────────┘",),6: ("┌─────────┐","│ ● ● │","│ ● ● │","│ ● ● │","└─────────┘",),
}
DIE_HEIGHT = len(DICE_ART[1])
DIE_WIDTH = len(DICE_ART[1][0])
DIE_FACE_SEPARATOR = " "# ...
在第 4 行至第 47 行,您使用 ASCII 字符绘制了六个骰面。您将这些骰面存储在 DICE_ART 字典中,该字典将每个骰面映射为相应的整数值。
第 48 行定义了 DIE_HEIGHT,它表示给定骰面所占的行数。在本例中,每个面占用 5 行。同样,第 49 行定义了 DIE_WIDTH,用于保存绘制一个模面所需的列数。在本例中,宽度为 11 个字符。
最后,第 50 行定义了 DIE_FACE_SEPARATOR,用于保存空白字符。您将使用所有这些常量为您的应用程序生成并显示 ASCII 码骰面图。
生成骰面图
至此,您已经为每个骰面绘制了 ASCII 图。为了将这些片段组合成最终的图表,显示掷骰子模拟的完整结果,您将编写另一个自定义函数:
# dice.py# ...def generate_dice_faces_diagram(dice_values):"""Return an ASCII diagram of dice faces from `dice_values`.The string returned contains an ASCII representation of each die.For example, if `dice_values = [4, 1, 3, 2]` then the stringreturned looks like this:~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ ● ● │ │ │ │ ● │ │ ● ││ │ │ ● │ │ ● │ │ ││ ● ● │ │ │ │ ● │ │ ● │└─────────┘ └─────────┘ └─────────┘ └─────────┘"""# Generate a list of dice faces from DICE_ARTdice_faces = []for value in dice_values:dice_faces.append(DICE_ART[value])# Generate a list containing the dice faces rowsdice_faces_rows = []for row_idx in range(DIE_HEIGHT):row_components = []for die in dice_faces:row_components.append(die[row_idx])row_string = DIE_FACE_SEPARATOR.join(row_components)dice_faces_rows.append(row_string)# Generate header with the word "RESULTS" centeredwidth = len(dice_faces_rows[0])diagram_header = " RESULTS ".center(width, "~")dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)return dice_faces_diagram# ~~~ App's main code block ~~~
# ...
该函数的功能如下:
-
第 5 行定义了 generate_dice_faces_diagram(),其中包含一个名为 dice_values 的参数。该参数将保存调用 roll_dice() 后得到的掷骰子整数值列表。
-
第 6 至 18 行提供了函数的 docstring。
-
第 20 行创建了一个名为 dice_faces 的空列表,用于存储与输入的骰值列表相对应的骰面。这些骰面将显示在最终的 ASCII 图表中。
-
第 21 行定义了一个 for 循环来遍历骰值。
-
第 22 行从 DICE_ART 中获取与当前骰值相对应的骰面,并将其添加到骰面中。
-
第 25 行创建一个空列表来保存最终骰面图中的行。
-
第 26 行定义了一个循环,迭代从 0 到 DIE_HEIGHT - 1 的索引。 每个索引代表骰面图中给定行的索引。
-
第 27 行将 row_components 定义为一个空列表,用于保存填充给定行的骰面部分。
-
第 28 行开始嵌套 for 循环,遍历骰面。
-
第 29 行存储每一行的组件。
-
第 30 行将各行组件连接成最终的行字符串,并用空格分隔各个组件。
-
第 31 行将每个行字符串添加到包含行的列表中,这些行将形成最终的图表。
-
第 34 行创建一个临时变量,用于保存当前骰面图的宽度。
-
第 35 行创建了一个显示 RESULTS 字样的页眉。为此,它使用 str.center(),并将图的宽度和斜杠符号 (~) 作为参数。
-
第 37 行生成一个字符串,用于保存最终的骰面图。换行符 (\n) 用作行分隔符。.join()的参数是一个字符串列表,它串联了图表标题和骰面形状的字符串(行)。
-
第 38 行向调用者返回一个可直接打印的骰面图。
哇哦 这可真不少!稍后您将回过头来改进这段代码,使其更易于管理。不过在此之前,你会想试用一下你的应用程序,所以你需要完成它的主代码块的编写。
完成应用程序的主代码并掷骰子
有了 generate_dice_faces_diagram(),您现在就可以完成应用程序主代码的编写,这样就可以在屏幕上实际生成并显示骰面图了。在 dice.py 的末尾添加以下代码行:
# dice.py# ...# ~~~ App's main code block ~~~
# 1. Get and validate user's input
num_dice_input = input("How many dice do you want to roll? [1-6] ")
num_dice = parse_input(num_dice_input)
# 2. Roll the dice
roll_results = roll_dice(num_dice)
# 3. Generate the ASCII diagram of dice faces
dice_face_diagram = generate_dice_faces_diagram(roll_results)
# 4. Display the diagram
print(f"\n{dice_face_diagram}")
第 12 行以 roll_results 作为参数调用 generate_dice_faces_diagram()。该调用将生成并返回与当前掷骰子结果相对应的骰面图。第 14 行调用 print() 将图表显示在屏幕上。
更新完成后,您就可以再次运行应用程序了。返回命令行并执行以下命令:
$ python dice.py
How many dice do you want to roll? [1-6] 5~~~~~~~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~~~~~~~
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ ● ● │ │ ● ● │ │ ● ● │ │ ● │ │ ● ● │
│ ● │ │ ● ● │ │ ● ● │ │ ● │ │ │
│ ● ● │ │ ● ● │ │ ● ● │ │ ● │ │ ● ● │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
酷毙了 现在,你的掷骰子模拟器应用程序会显示一个格式精美的 ASCII 图表,显示模拟事件的结果。很棒,不是吗?
如果你回过头来看 generate_dice_faces_diagram() 的实现,你会发现它包含了一些注释,指出了相应部分的代码在做什么:
def generate_dice_faces_diagram(dice_values):# ...# Generate a list of dice faces from DICE_ARTdice_faces = []for value in dice_values:dice_faces.append(DICE_ART[value])# Generate a list containing the dice faces rowsdice_faces_rows = []for row_idx in range(DIE_HEIGHT):row_components = []for die in dice_faces:row_components.append(die[row_idx])row_string = DIE_FACE_SEPARATOR.join(row_components)dice_faces_rows.append(row_string)# Generate header with the word "RESULTS" centeredwidth = len(dice_faces_rows[0])diagram_header = " RESULTS ".center(width, "~")dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)return dice_faces_diagram
这种注释通常预示着你的代码需要重构。在下面的章节中,你将使用一种流行的重构技术,它将帮助你清理代码,使其更易于维护。
第 4 步:重构生成骰面图的代码
您的 generate_dice_faces_diagram() 函数需要解释性注释,因为它同时执行多个操作,这违反了单一责任原则。
粗略地说,这一原则是指每个函数、类或模块只能做一件事。这样,特定功能的更改就不会破坏代码的其他部分。因此,你最终会得到更健壮、更易维护的代码。
有一种名为 extract method 的重构技术,可以通过提取可以独立工作的功能来帮助你改进代码。例如,您可以提取 generate_dice_faces_diagram() 先前实现中第 20 行至第 22 行的代码,并将其放入名为 _get_dice_faces() 的非公共辅助函数中:
def _get_dice_faces(dice_values):dice_faces = []for value in dice_values:dice_faces.append(DICE_ART[value])return dice_faces
您可以在 generate_dice_faces_diagram() 中调用 _get_dice_faces() 来获取隐含功能。通过使用此技术,您可以完全重构 generate_dice_faces_diagram(),以满足单一责任原则。
注:要了解更多关于用前导下划线 (_) 命名非公共函数的信息,请查阅 Python 名称中的单下划线和双下划线。
下面是 generate_dice_faces_diagram() 的重构版本,它利用了 _get_dice_faces() 并实现了另一个名为 _generate_dice_faces_rows() 的辅助函数,以提取第 25 行至第 31 行的功能:
# dice.py# ...def generate_dice_faces_diagram(dice_values):"""Return an ASCII diagram of dice faces from `dice_values`.The string returned contains an ASCII representation of each die.For example, if `dice_values = [4, 1, 3, 2]` then the stringreturned looks like this:~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ ● ● │ │ │ │ ● │ │ ● ││ │ │ ● │ │ ● │ │ ││ ● ● │ │ │ │ ● │ │ ● │└─────────┘ └─────────┘ └─────────┘ └─────────┘"""dice_faces = _get_dice_faces(dice_values)dice_faces_rows = _generate_dice_faces_rows(dice_faces)# Generate header with the word "RESULTS" centeredwidth = len(dice_faces_rows[0])diagram_header = " RESULTS ".center(width, "~")dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)return dice_faces_diagramdef _get_dice_faces(dice_values):dice_faces = []for value in dice_values:dice_faces.append(DICE_ART[value])return dice_facesdef _generate_dice_faces_rows(dice_faces):dice_faces_rows = []for row_idx in range(DIE_HEIGHT):row_components = []for die in dice_faces:row_components.append(die[row_idx])row_string = DIE_FACE_SEPARATOR.join(row_components)dice_faces_rows.append(row_string)return dice_faces_rows# ~~~ App's main code block ~~~
# ...
新添加的辅助函数从原始函数中提取功能。现在,每个辅助函数都有自己的职责。辅助函数还允许您使用可读的描述性名称,从而无需解释性注释。
作为一名 Python 开发人员,重构代码使其更加完美是一项很好的技能。要深入了解代码重构,请查看《重构 Python 应用程序,实现简洁》。
代码重构背后的一个基本理念是,修改后的代码应与原始代码工作相同。要验证这一原则,请再次运行您的应用程序!
这样,您就完成了项目!您已经创建了一个功能齐全的 TUI 应用程序,可以模拟掷骰子事件。每次运行应用程序时,您最多可以模拟六面骰子的掷骰子过程。您甚至可以在漂亮的 ASCII 图表中看到掷出的骰子面。干得漂亮
总结
您已经编写了一个功能齐全的项目,它包括一个基于文本的用户界面应用程序,可以用 Python 来模拟掷六面骰子。通过这个项目,您学习并练习了一些基本技能,如收集和验证用户输入、导入代码、编写函数、使用循环和条件,以及在屏幕上显示格式良好的输出。
在本教程中,您将学习如何
- 使用
random.randint()
模拟掷骰子 - 使用内置的 input() 函数在命令行中获取用户输入信息
- 使用多种工具和技术解析并验证用户输入
- 使用
.center()
和.join()
等方法处理字符串
此外,您还学会了如何结构、组织、记录和运行 Python 程序和脚本。有了这些知识,您就可以更好地使用 Python 继续您的编码之旅。