Hog 游戏
我知道!我要用我的
高阶函数来
争取更高的点数。
介绍
提交须知: 为了获得完整学分:
- 在 7 月 1 日(星期三)之前提交完成的第 1 阶段,价值 1 分。
- 在 7 月 7 日(星期二).
请尽量按顺序完成题目,并在过程中为每道题运行
ok测试,因为后面的实现可能依赖前面的题目。你可以和你的伙伴一起完成这个项目。
在 7 月 6 日(星期一)之前提交整个项目可以获得 1 个奖励积分。项目截止日期和检查点截止日期可以申请延期,但提前提交的截止日期则不行。除非您是 DSP 学生,并且有作业延期方面的特殊安排。
在这个项目中,您将开发一个骰子游戏 Hog 的模拟器和多种策略。您需要一起使用 控制语句 和 高阶函数 ,相关内容见第 1–3 讲以及教材第 1.2–1.6 节。 课本的 1.2 到 1.6 节中所述。
以前有同学没仔细读题就直接写代码,结果经常遇到麻烦。😱 在开始编写代码之前,请仔细阅读每个描述。
规则
在 Hog 游戏中,两名玩家轮流尝试成为第一个以至少 GOAL 总分结束回合的人,其中 GOAL 默认值为 100。每回合,当前玩家选择同时掷最多 10 个骰子;该回合得分通常是所有骰子点数之和。不过,骰子掷得越多,越可能触发下面的风险:
- Sow Sad。如果任何骰子的结果是 1,则当前玩家的回合得分为
1,不论其他骰子掷出了什么点数。
- 示例 1: 当前玩家掷出 7 个骰子,其中 5 个骰子的结果为 1。他们该回合得
1分。 - 示例 2: 当前玩家掷出 4 个骰子,所有骰子的结果均为 3。由于未发生逢一判负,他们该回合得
12分。
在正常的 Hog 游戏中,这些就是所有的规则。为了给游戏增添趣味,我们将加入一些特殊规则:
- 野猪乱斗。选择掷 0 个骰子的玩家,得分为“对手分数十位与自己分数个位之差的绝对值”的三倍,或 1 分,取较大者。个位是最右侧数字,十位是右起第二位;若分数不足 10,十位视为 0。
示例 1:
- 当前玩家有
21分,对手有46分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
4,当前玩家分数的个位数为1. - 因此,玩家获得
3 * abs(4 - 1) = 9分。
- 当前玩家有
示例 2:
- 当前玩家有
45分,对手有52分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
5,当前玩家分数的个位数为5. - 由于
3 * abs(5 - 5) = 0,因此玩家获得1分。
- 当前玩家有
示例 3:
- 当前玩家有
2分,对手有5分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
0,当前玩家分数的个位数为2. - 因此,玩家获得
3 * abs(0 - 2) = 6分。
- 当前玩家有
- Sus Fuss 规则。如果一个数(包括 1 和它自身在内)恰好有 3 或 4 个因数,我们称它为 sus 。若掷骰后当前玩家的总分是 sus 数,总分会立刻增加到比它大的最近素数。
示例 1:
- 某玩家原有 14 分,掷 2 个骰子得到 7 分,新总分为 21。21 有 4 个因数:1、3、7、21,因此它是 sus 数,玩家总分立即增加到下一个素数 23。
示例 2:
- 某玩家原有 63 分,掷 5 个骰子因 Sow Sad 😢 得到 1 分,新总分为 64。64 有 7 个因数:1、2、4、8、16、32、64,因此不是 sus 数,总分不再变化。
示例 3:
- 某玩家原有 49 分,掷 5 个骰子共得 18 分,新总分为素数 67,只有 1 和 67 两个因数。67 不是 sus 数,因此总分不变。
下载起始文件
请首先下载项目代码的 zip 压缩包。以下是解压缩后将在存档中看到的所有文件的列表。对于该项目,您只需修改 hog.py.
hog.py:Hog 的起始实现dice.py:用于制作和掷骰子的函数hog_gui.py:Hog 的图形用户界面 (GUI)(已更新)ucb.py:CS 61A 的实用函数hog_ui.py:Hog 的基于文本的用户界面 (UI)ok:CS 61A 自动评分器tests:供自动评分器使用的测试目录okgui_files:Web GUI 使用的各种事物的目录
您可能会注意到一些文件,除了上面列出的文件之外,这些文件是制作自动评分器和 GUI 部分工作所必需的。请不要修改除 hog.py.
后勤
项目共 10 分,其中 1 分要求在 7 月 1 日(星期三)的检查点前提交第 1 阶段。
您将提交以下文件:
hog.py
您无需修改或提交任何其他文件即可完成项目。要提交项目: 请将所需文件提交到相应的 Gradescope 作业。
完成本项目时,不得使用人工智能工具提供帮助,也不得参考网上找到的答案。
对于我们要求您完成的函数,可能会提供一些初始代码。如果您不想使用该代码,请随时删除它并从头开始。您也可以根据需要添加新的函数定义。
但是,请不要修改任何其他函数或编辑上述未列出的任何文件。这样做可能会导致您的代码无法通过我们的自动评分器测试。另外,请不要更改任何函数签名(名称、参数顺序或参数个数)。
在整个项目过程中,你应该测试代码的正确性。经常测试是一个好习惯,这样可以很容易地隔离任何问题。 但是,你不应该测试 太 频繁,以至于没有时间思考问题。
我们提供了一个 自动评分器 called ok ,以帮助您测试代码并跟踪您的进度。第一次运行自动评分器时,系统会要求您 使用 Web 浏览器登录您的 Ok 帐户。请这样做。每次运行 ok时,它都会在我们的服务器上备份您的工作和进度。
的主要用途是 ok 测试你的实现。
如果您想以交互方式测试您的代码,您可以运行:
python3 ok -q [question number] -i使用相应的问题编号(例如
01)运行。这将运行该问题的测试,直到遇到第一个失败的测试为止,然后您将有机会以交互方式测试您编写的函数。
你还可以使用 Ok 的调试输出功能:在 print 语句前加上“DEBUG:”。例如,要查看变量 x的值,可以这样写:
print(f"DEBUG: x is {x}")
这会在终端中生成输出,且不会因多余输出而导致 OK 测试失败。
图形用户界面(GUI)
我们为你提供了一个 图形用户界面 (简称 GUI)。目前游戏逻辑尚未实现,所以它还不能运行;完成 play 函数后,就能游玩完整的交互版 Hog。
完成后,可以从终端启动 GUI,并在浏览器中玩 Hog:
python3 hog_gui.py
第一阶段:游戏规则
在第一阶段,您将开发 Hog 游戏的模拟器。
问题 0
提供给您的 dice.py 文件用非纯的零参数函数表示骰子。这些函数每次调用可能返回不同结果,因此调用的副作用之一,是改变下一次调用时可能返回的值。
以下是 dice.py 中的文档,您需要阅读才能在此项目中模拟骰子。
# A dice function takes no arguments and returns a number from 1 to n
# (inclusive), where n is the number of sides on the dice.
# Fair dice produce each possible outcome with equal probability.
# Two fair dice are already defined, four_sided and six_sided,
# and are generated by the make_fair_dice function.
def make_fair_dice(sides):
"""Return a die that generates values ranging from 1 to SIDES, each with an equal chance."""
...
four_sided = make_fair_dice(4)
six_sided = make_fair_dice(6)
# Test dice are deterministic: they always cycles through a fixed
# sequence of values that are passed as arguments.
# Test dice are generated by the make_test_dice function.
def make_test_dice(...):
"""Return a die that cycles deterministically through OUTCOMES.
>>> dice = make_test_dice(1, 2, 3)
>>> dice()
1
>>> dice()
2
>>> dice()
3
>>> dice()
1
>>> dice()
2
骰子函数不接受任何参数,并返回 1 到 n(包括 1 和 n)之间的数字,其中 n 是骰子的面数。
python3 ok -q 00 -u
您可以通过键入 exit().
已知在 Windows 系统中使用 Ctrl-C 退出解锁器可能会导致问题,请尽量避免。
问题 1
实现 roll_dice 函数位于 hog.py。它接收两个参数:正整数 num_rolls(指定掷骰次数)以及
dice 函数。它返回本回合掷骰
num_rolls 次所得的分数:要么是点数之和,要么因 (Sow Sad)而得到 1 分.
- Sow Sad。如果任何骰子的结果是 1,则当前玩家的回合得分为
1,不论其他骰子掷出了什么点数。
- 示例 1: 当前玩家掷出 7 个骰子,其中 5 个骰子的结果为 1。他们该回合得
1分。 - 示例 2: 当前玩家掷出 4 个骰子,所有骰子的结果均为 3。由于未发生逢一判负,他们该回合得
12分。
要取得一次掷骰结果,请调用 dice()。你应在
dice() exactly num_rolls times 函数体中调用它。 roll_dice.
请记得,即使掷骰途中已经触发 Sow Sad,也要继续调用 dice() exactly num_rolls times ,直到完成全部次掷骰。这样才能正确模拟所有骰子同时掷出,用户界面也才能正常工作。
注意: 提供给您的
roll_dice函数以及项目中的许多其他函数使用了 默认参数值,可从函数头看出:def roll_dice(num_rolls, dice=six_sided): ...参数
dice=six_sided表示dice函数中的参数roll_dice是可选的 可选的。如果没有为它提供值,就默认使用dice,那么six_sided。例如,
roll_dice(3, four_sided)模拟的是掷 3 个四面骰子,而roll_dice(3)由于使用默认参数,它模拟掷 3 个六面骰子。
理解问题:
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 01 -u
注意: 在解锁相应问题的测试用例之前,你将无法使用
ok测试你的代码。
编写代码并检查你的工作:
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 01
请参考 调试指南的因数!
调试技巧
如果测试没有通过,就需要进行调试。 你可以直接使用 Python 来观察函数的行为。 首先,启动 Python 解释器并加载 hog.py 文件。
python3 -i hog.py
接下来,你可以使用任意数量的骰子来调用 roll_dice 函数。
>>> roll_dice(4)
你会发现,每次调用上述表达式时,结果可能都不一样,因为它模拟的是随机掷骰子的过程。 例如,如果已知骰子会依次掷出 3 和 4,那么掷两次的总点数应该是 7。
>>> fixed_dice = make_test_dice(3, 4)
>>> roll_dice(2, fixed_dice)
7
在大多数系统中,你可以通过按向上箭头,然后按 Enter 或 Return 键来再次评估相同的表达式。 要评估更早的命令,请重复按向上箭头。
如果发现问题,先修改
hog.py文件并保存。要检查修复是否生效,需要退出 Python 解释器(可以使用exit()orCtrl^D),然后重新启动解释器。无论在终端还是 Python 解释器中,按上箭头都能找回先前输入的表达式,即使已经重启。继续调试你的代码并运行
ok测试,直到它们全部通过。还有一个调试技巧:要在
ok测试失败时自动启动交互式解释器,可以使用-i选项。 例如,python3 ok -q 01 -i将运行问题 1 的测试,然后在测试失败时启动加载了hog.py的交互式解释器。
问题 2
实现 boar_brawl函数,该函数接受玩家的当前分数 player_score 和对手的当前分数 opponent_score,返回玩家掷 0 个骰子并触发 Boar Brawl 时获得的分数。
- 野猪乱斗。选择掷 0 个骰子的玩家,得分为“对手分数十位与自己分数个位之差的绝对值”的三倍,或 1 分,取较大者。个位是最右侧数字,十位是右起第二位;若分数不足 10,十位视为 0。
示例 1:
- 当前玩家有
21分,对手有46分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
4,当前玩家分数的个位数为1. - 因此,玩家获得
3 * abs(4 - 1) = 9分。
- 当前玩家有
示例 2:
- 当前玩家有
45分,对手有52分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
5,当前玩家分数的个位数为5. - 由于
3 * abs(5 - 5) = 0,因此玩家获得1分。
- 当前玩家有
示例 3:
- 当前玩家有
2分,对手有5分,而当前玩家选择掷 0 个骰子。 - 对手分数的十位数为
0,当前玩家分数的个位数为2. - 因此,玩家获得
3 * abs(0 - 2) = 6分。
- 当前玩家有
不要假设分数低于 100。编写
boar_brawl函数时,请确保它能正确处理任何非负分数。
Important: 你的代码 不应 使用
str、列表或包含方括号[]。测试用例会检查是否使用了这些。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 02 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 02
也可以在终端运行交互模式来测试 boar_brawl : python3 -i hog.py
,然后对多组输入调用 boar_brawl 。
问题 3
实现 take_turn 函数,返回一回合掷给定数量骰子所得的分数。 dice num_rolls times.
你的 take_turn 应调用已有的两个辅助函数,而不是重复它们的实现。 roll_dice 和
boar_brawl
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 03 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 03
问题 4
首先,实现 num_factors函数,它接受一个正整数 n ,并确定 n 的因数个数。
1 和
n都是n的因数!
然后,实现 sus_points 和 sus_update.
sus_points接收玩家分数,并返回应用 Sus Fuss 后的新分数;即使分数不变也要返回。例如,sus_points(5)应该返回5和sus_points(21)应该返回23。你应使用num_factors和提供的is_prime函数。sus_update返回玩家回合结束后的 总分 ,其中玩家掷出num_rolls个骰子后的总分,一并考虑 Boar Brawl 规则和 Sus Fuss 规则。你应该在此函数中使用sus_points。
提示:
- 可以查看
simple_update在文件中的实现,作为编写hog.py的起点。sus_update函数。- 请记住,
take_turn已经考虑了 Boar Brawl 规则!
- Sus Fuss 规则。如果一个数(包括 1 和它自身在内)恰好有 3 或 4 个因数,我们称它为 sus 。若掷骰后当前玩家的总分是 sus 数,总分会立刻增加到比它大的最近素数。
示例 1:
- 某玩家原有 14 分,掷 2 个骰子得到 7 分,新总分为 21。21 有 4 个因数:1、3、7、21,因此它是 sus 数,玩家总分立即增加到下一个素数 23。
示例 2:
- 某玩家原有 63 分,掷 5 个骰子因 Sow Sad 😢 得到 1 分,新总分为 64。64 有 7 个因数:1、2、4、8、16、32、64,因此不是 sus 数,总分不再变化。
示例 3:
- 某玩家原有 49 分,掷 5 个骰子共得 18 分,新总分为素数 67,只有 1 和 67 两个因数。67 不是 sus 数,因此总分不变。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 04 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 04
问题 5
实现 play 函数,该函数模拟 Hog 的完整游戏。玩家轮流掷骰子,直到其中一名玩家达到 goal 分数;函数最终返回两名玩家的最终总分。
要决定每回合掷几个骰子,需要调用当前玩家的策略函数(玩家 0 使用 strategy0 ,玩家 1 使用 strategy1). A
策略 是一个策略函数:给定玩家和对手的分数,返回当前回合要掷的骰子数。一个简单示例是 always_roll_5 ,它出现在 play.
要确定玩家在回合结束后更新的分数,请调用
update 函数。 update 函数需要输入要掷的骰子数量、当前玩家的分数、对手的分数,以及用于模拟掷骰子的函数。它返回当前玩家在回合结束后更新的分数。 update 函数的两个示例是 simple_update 和 sus_update。请记住,更新函数返回玩家回合结束后的
总分 ,而不仅是本回合增加的分数。
应用所有相关规则后,只要某位玩家在回合结束时达到或超过目标分数,游戏就结束。 play 将返回两名玩家的最终总分,玩家 0 的分数在前,玩家 1 的分数在后。
下面给出一些调用示例: play are:
play(always_roll_5, always_roll_5, simple_update)模拟两个玩家,他们每回合都掷 5 个骰子,仅使用 Sow Sad 和 Boar Brawl 规则进行游戏。play(always_roll_5, always_roll_5, sus_update)模拟两个玩家,他们每回合都掷 5 个骰子,除了 Sow Sad 和 Boar Brawl 规则外,还使用 Sus Fuss 规则进行游戏(即所有规则)。
Important: 为了使用户界面正常工作,每个回合只能调用一次策略函数。仅当轮到玩家 0 时才调用
strategy0,仅当轮到玩家 1 时才调用strategy1,此时轮到玩家 1。提示:
- 如果
who是当前玩家,则下一个玩家是1 - who.- 要调用
play(always_roll_5, always_roll_5, sus_update)并打印出每回合发生的事情,请从终端运行python3 hog_ui.py。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 05 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 05
检查点提交
确认你已经完成了第一阶段的所有题目:
python3 ok --score
当您运行 ok 命令时,仍会看到部分测试被锁定,因为整个项目尚未完成。只要正确完成此前所有题目,就能获得检查点满分。
确认无误后,上传 hog.py 到 Gradescope 的 Hog Checkpoint 作业入口 。
务必在 7 月 1 日(星期三)的检查点截止时间前提交。Gradescope 提交流程可回顾 Lab 00.
您可以在 Gradescope 提交页面点击姓名右侧的 编辑小组 并输入搭档的邮箱地址。只需一名搭档提交到 Gradescope。
恭喜!您已完成本项目的第 1 阶段!
插曲:用户界面
本项目此部分没有必做题,只有一些示例供您阅读和理解。有关剩余的项目问题,请参阅第 2 阶段。

动画由 Tristan 与 Tyler Roath 制作
打印游戏事件
我们已经为游戏构建了一个模拟器,但没有添加任何代码来描述如何向人们展示游戏事件。因此,我们构建了一个无人可以玩的电脑游戏。(太逊了!)
但是,模拟器是用小型函数表示的,我们可以用一个版本替换每个函数,该版本打印出调用时发生的事情。使用高阶函数,我们可以在不更改太多原始代码的情况下做到这一点。 hog_ui.py中提供了一个示例,我们鼓励您阅读。
提供给您的 play_and_print 函数会调用刚刚实现的同一个 play 函数,只是使用以下参数:
- 新的策略函数(例如,
printing_strategy(0, always_roll_5)),用于打印得分和掷骰次数。 - 一个新的更新函数 (
sus_update_and_print),用于打印每回合的结果。 - 一个新的骰子函数 (
printing_dice(six_sided)),用于打印掷骰子的结果。
请注意有多少原始模拟器代码可以被重用。
从终端运行 python3 hog_ui.py 会调用
play_and_print(always_roll_5, always_roll_5).
接受用户输入
内置的 input 函数等待用户输入一行文本,然后将该文本作为字符串返回。内置的 int 函数可以接受包含整数数字的字符串并返回该整数。
提供给您的 interactive_strategy 函数返回一个策略,让玩家通过调用相应界面选择每回合掷几个骰子。 input.
我们终于可以使用 play 函数来玩游戏了:
从终端运行 python3 hog_ui.py -n 1 会调用
play_and_print(interactive_strategy(0), always_roll_5),这会在人类玩家(玩家 0)和始终掷 5 的电脑策略之间进行游戏。
从终端运行 python3 hog_ui.py -n 2 会调用
play_and_print(interactive_strategy(0), interactive_strategy(1)),这会在两个人类玩家之间进行游戏。
欢迎您随意更改 hog_ui.py ,例如使用与 always_roll_5.
图形用户界面 (GUI)
我们还使用与 hog_ui.py called hog_gui.py。您可以从终端运行它:
python3 hog_gui.py
与 hog_ui.py类似,GUI 依赖于您的模拟器实现。因此,如果您的代码中存在任何错误,这些错误也会反映在 GUI 中。这意味着您也可以将 GUI 用作调试工具;但是,最好先运行测试。
如果感兴趣,可以查看 hog_gui.py 文件以及 gui_files 目录的内容,了解 GUI 的工作方式。不过 GUI 实现还使用了 JavaScript、HTML 和 CSS。
第 2 阶段:策略
本阶段将尝试改进简单的 always_roll_five
策略(总是掷 5 个骰子)。 策略 是一个接受两个参数的函数:当前玩家的得分和对手的得分。它返回玩家将掷的骰子数量,可以是 0 到 10(包括 0 和 10)。
问题 6
实现 always_roll,这是一个高阶函数,它接受骰子数
n 并返回一个始终掷指定数量骰子的策略函数。 n 个骰子的策略。因此, always_roll(5)
相当于 always_roll_5.
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 06 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 06
问题 7
策略的参数值组合数量是有限的。例如,若游戏目标分数 goal 为 100,则玩家分数只有 100 种可能值 score 值(0-99)和 100 个可能的
opponent_score (0–99),因此策略函数总共只有 10,000 种参数组合。
| 玩家分数 | 对手分数组合 |
|---|---|
| 0 | (0,0), (0,1), (0,2), ..., (0,99) |
| 1 | (1,0), (1,1), (1,2), ..., (1,99) |
| 2 | (2,0), (2,1), (2,2), ..., (2,99) |
| ... | ... |
| 98 | (98,0), (98,1), (98,2), ..., (98,99) |
| 99 | (99,0), (99,1), (99,2), ..., (99,99) |
实现 is_always_roll,它接收一个策略,判断在所有可能参数组合下是否总是掷相同数量的骰子;每个分数最大为 goal 分。
提醒: 游戏会一直进行,直到一名玩家达到
goal分(上例中为goal设置为100,也可以是任意数)。请确保答案考虑指定目标下玩家与对手分数的每一种组合。score和opponent_scoregoal.
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 07 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 07
问题 8
实现 make_averaged,这是一个高阶函数 (higher-order function),它接受一个函数 original_function 作为参数。
该函数返回另一个函数,后者接收与原函数相同的参数。 make_averaged original_function当传入一组具体参数时,新函数应使用同一组参数反复调用原函数 original_function times_called 次,并返回结果平均值。请仔细阅读 make_averaged doctest,并注意究竟有哪些值被传入函数!
Doctest 解析:仔细查看
make_averageddoctest。在这里,original_functionisroll_dice。注意这一行:averaged_dice(1, dice)。这说明传给roll_diceare(1, dice)的参数是什么(想想为什么!)。观察averaged_dice如何接收与原函数相同的参数roll_dice。参数不是直接传给roll_dice,而是传给averaged_dice。(想想如何实现!)请记住,make_averaged应适用于任何参数结构与其返回函数相同的original_function。make_averaged在本例中,掷一次骰子视为一个样本(roll_dice(1, dice))。由于times_called设为 40,因此重复采样 40 次。make_averaged随后计算这 40 次调用的平均结果。roll_dice.Important: 要实现此函数,您需要使用一种新的 Python 语法。我们希望编写一个接受任意数量参数的函数,然后使用完全相同的参数调用另一个函数。 它是这样运作的。
你可以使用
*argsall 表示传入函数的全部参数。随后可以把这些参数传给另一个函数,以相同参数调用它。*args例如:>>> def printed(f): ... def print_and_return(*args): ... result = f(*args) ... print('Result:', result) ... return result ... return print_and_return >>> printed_pow = printed(pow) >>> printed_pow(2, 8) # *args represents the arguments (2, 8) Result: 256 256 >>> printed_abs = printed(abs) >>> printed_abs(-10) # *args represents one argument (-10) Result: 10 10这里可以用
print_and_returnvia the*args语法传入任意数量的参数,也可以在*args函数内部使用print_and_return,以同一组参数发起另一次函数调用。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 08 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 08
问题 9
实现 max_scoring_num_rolls,它使用固定面数的骰子进行实验,确定在 1 到 10 次掷骰中,哪种次数能让回合平均得分最高。实现中应使用 make_averaged 和 roll_dice.
如果两种掷骰次数并列最高平均分,返回较小者。例如 3 次和 6 次并列时返回 3。
解锁测试前,阅读本题 doctest 以及 make_averaged (问题 8)的 doctest 可能会有帮助。
Important: 为了通过我们所有的测试,请确保您正在测试从 1 到 10 开始的掷骰子,而不是从 10 到 1。 在编写任何代码之前,请解锁测试以验证您对问题的理解:
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 09 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 09
运行实验
提供的 run_experiments 函数调用
max_scoring_num_rolls(six_sided) 并打印结果。你可能会发现,掷6个骰子通常能使 roll_dice 函数在使用六面骰子时得到最高平均分。
要调用函数并查看结果,请运行 hog.py 并使用 -r flag:
python3 hog.py -r
此外, run_experiments 还会将不同的策略与 always_roll(6)进行比较。你可以随意修改 run_experiments 的实现。请注意,在完成接下来的两个问题之前, boar_strategy 和 sus_strategy 的实验结果可能不准确。
一些实验可能需要长达一分钟才能运行。 你始终可以减少对 make_averaged 的调用中的试验次数,以加快实验速度。
运行实验不会影响你在项目中的得分。
问题 10
一种策略可以尝试在最有利的情况下,利用 野猪乱斗 规则掷0。 实施 boar_strategy,如果掷0能获得 至少 threshold 分,则返回0;否则返回
num_rolls 。 此策略 不应 考虑 Sus Fuss 规则。
提示:可以使用
boar_brawl函数。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 10 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 10
您应该发现,现在运行 python3 hog.py -r 显示
boar_strategy 的胜率接近 66-67%。
问题 11
更好的策略会同时利用两条特殊规则。 野猪乱斗 和 Sus Fuss 规则 规则。 例如,如果一个玩家有 53 分,而他们的对手有 60 分,掷 0 会使他们达到 62 分,这是一个 sus 数字,因此他们将以 67 分结束回合:相当于增加了 14 分!
提供给您的 sus_strategy 当掷 0 个骰子能让本回合后的总分至少比回合开始时高 至少 threshold 分时,该策略返回 0。
提示:可以使用
sus_update使用你在问题 4 中定义的函数。
编写代码前,请先解锁测试,以确认自己正确理解了题意:
python3 ok -q 11 -u
解锁完成后,你可以编写代码,并使用以下命令检查正确性:
python3 ok -q 11
您应该发现,现在运行 python3 hog.py -r 显示
sus_strategy 的胜率接近 67-69%。
选做:问题 12
实现 final_strategy,它结合了这些想法和您拥有的任何其他想法,以实现针对基线策略的高胜率。 一些建议:
- 如果您知道目标分数(默认情况下为 100),则得分超过目标没有任何好处。 检查您是否可以通过掷 0、1 或 2 个骰子来获胜。 如果您处于领先地位,您可能会决定承担更少的风险。
- 不要使用阈值,而是在掷 0 会给您带来比掷 6 个骰子更高的平均分时掷 0。
你可以通过运行 ok.
python3 ok -q 12
提交项目
在所有问题上运行 ok 以确保所有测试都已解锁并通过:
python3 ok
你也可以查看你在每个项目部分的得分情况:
python3 ok --score
确认无误后,上传 hog.py 到 Gradescope。 若要回顾提交方法,请参阅 Lab 00.
您可以在 Gradescope 提交页面点击姓名右侧的 编辑小组 并输入搭档的邮箱地址。只需一名搭档提交到 Gradescope。
恭喜您,您已经完成了 CS 61A 的第一个项目! 不妨放松一下,和朋友一起玩几局 Hog 游戏吧!