Hog 游戏

5-sided die

我知道!我会用我的
高阶函数来
掷出更高的点数。

介绍

重要提交说明:满分要求:

  • 7月1日(周三) 前完成第一阶段提交,值 1 分。
  • 7月7日(周二) 前提交完整项目。

请尽量按顺序做题,包括在进度中运行每个问题的 ok 测试,因为后面的一些问题在实现上依赖于前面的问题。

你可以与搭档一起完成本项目。

7月6日(周一) 前提交整个项目可以获得 1 个奖励分。 你可以获得项目截止日期和检查点截止日期的延期,但不能获得提前截止日期的延期,除非你是拥有作业延期便利的 DSP 学生。

在本项目中,你将为骰子游戏 Hog 开发一个模拟器和多种策略。你需要一起使用控制语句高阶函数,如第1-3讲和《Composing Programs》在线教材第1.2节到第1.6节所述。

过去学生在没有仔细阅读问题描述的情况下就尝试实现函数时,经常会遇到问题。😱 在开始编码之前,请仔细阅读每个描述。

规则

在 Hog 中,两名玩家轮流掷骰,试图成为第一个在某回合结束时总得分至少达到 GOAL 分的玩家,GOAL 默认为 100。每回合,当前玩家选择一些骰子一起掷,最多 10 个。该玩家本回合的得分是骰子点数之和。然而,掷太多骰子的玩家会面临风险:

  • Sow Sad。如果任何骰子点数为 1,当前玩家本回合的得分为 1,无论其他骰子的点数如何。
  • 示例 1: 当前玩家掷 7 个骰子,其中 5 个是 1。他们 本回合得 1 分。
  • 示例 2: 当前玩家掷 4 个骰子,全部是 3。因为 没有触发 Sow Sad,他们本回合得 12 分。

在正常的 Hog 游戏中,这些就是所有规则。为了让游戏更有趣,我们加入一些特殊规则:

  • Boar Brawl。选择掷零个骰子的玩家得分为对手分数的十位数与当前玩家分数的个位数之差的绝对值的三倍,或 1,取较大者。个位数指最右边的数字,十位数指从右数第二个数字。如果玩家分数是个位数(小于 10),则该玩家分数的十位数为 0。
  • 示例 1:

    • 当前玩家有 21 分,对手有 46 分,当前玩家选择掷零个骰子。
    • 对手分数的十位数为 4,当前玩家分数的个位数为 1
    • 因此,玩家获得 3 * abs(4 - 1) = 9 分。
  • 示例 2:

    • 当前玩家有 45 分,对手有 52 分,当前玩家选择掷零个骰子。
    • 对手分数的十位数为 5,当前玩家分数的个位数为 5
    • 因为 3 * abs(5 - 5) = 0,玩家获得 1 分。
  • 示例 3:

    • 当前玩家有 2 分,对手有 5 分,当前玩家选择掷零个骰子。
    • 对手分数的十位数为 0,当前玩家分数的个位数为 2
    • 因此,玩家获得 3 * abs(0 - 2) = 6 分。
  • Sus Fuss。 我们称一个数为sus,如果它恰好有 3 或 4 个因子(包括 1 和它本身)。 如果掷骰后当前玩家的分数是 sus 数,其分数立即增加到大于当前分数的最接近的素数。
  • 示例 1:

    • 一个玩家有 14 分,掷 2 个骰子获得 7 分。 新分数为 21,有 4 个因子:1、3、7 和 21。 因此,21 是 sus,玩家分数立即增加到 23,即下一个素数。
  • 示例 2:

    • 一个有 63 分的玩家掷 5 个骰子,本回合获得 1 分。 新分数为 64(Sow Sad 😢),有 7 个因子:1、2、4、8、16、32 和 64。 由于 64 不是 sus,玩家分数不变。
  • 示例 3:

    • 一个玩家有 49 分,掷 5 个骰子共获得 18 分。 新分数为 67,是素数,有 2 个因子:1 和 67。 由于 67 不是 sus,玩家分数不变。

下载 starter 文件

开始之前,下载所有项目代码的 zip 压缩包。 以下是解压后你将在压缩包中看到的所有文件列表。在本项目中,你只需要修改 hog.py

  • hog.py:Hog 的 starter 实现
  • dice.py:用于制作和掷骰子的函数
  • hog_gui.py:Hog 的图形用户界面(GUI)(已更新)
  • ucb.py:CS 61A 的实用函数
  • hog_ui.py:Hog 的文本用户界面(UI)
  • ok:CS 61A 自动评分器
  • testsok 使用的测试目录
  • gui_files:Web GUI 使用的各种文件的目录

你可能还会看到上面未列出的其他文件——那些是使自动评分器和 GUI 部分功能正常工作所必需的。请不要修改 hog.py 以外的任何文件。

注意事项

本项目满分 10 分,其中 1 分用于在检查点日期 7月1日(周三)前提交第一阶段。

你需要提交以下文件:

  • hog.py

你不需要修改或提交任何其他文件来完成本项目。要提交项目,请将所需文件提交到相应的 Gradescope 作业。

你不得使用人工智能工具帮助你完成本项目,也不得参考互联网上的解决方案。

对于我们要求你完成的函数,可能会提供一些初始代码。如果你不想使用这些代码,可以随意删除并从头开始。你也可以根据需要添加新的函数定义。

但是,请不要修改任何其他函数或编辑上面未列出的任何文件。这样做可能导致你的代码无法通过我们的自动评分器测试。另外,请不要更改任何函数签名(名称、参数顺序或参数数量)。

在整个项目中,你应该测试代码的正确性。经常测试是个好习惯,这样可以容易地隔离任何问题。但是,你也不应该测试频繁,要给自己留出思考问题的时间。

我们提供了一个名为 ok自动评分器来帮助你测试代码和跟踪进度。第一次运行自动评分器时,系统会要求你使用网络浏览器登录你的 Ok 账户。请照做。每次运行 ok 时,它会在我们的服务器上备份你的工作和进度。

ok 的主要目的是测试你的实现。

如果你想交互式地测试代码,可以运行

 python3 ok -q [题号] -i 
插入相应的题号(例如 01)。 这将运行该题的测试直到第一个失败的测试,然后让你有机会交互式地测试你编写的函数。

你也可以在 OK 中使用调试打印功能,在 print 语句前加上 "DEBUG:" 前缀。 例如,如果你想检查变量 x 的值,可以写:

 print(f"DEBUG: x is {x}") 
这将在终端中产生输出而不会因额外输出导致 OK 测试失败。

图形用户界面

我们为你提供了一个图形用户界面(简称 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

通过解锁以下测试来检查你的理解。

python3 ok -q 00 -u

你可以输入 exit() 退出解锁器。

在 Windows 上按 Ctrl-C 退出解锁器已知会导致问题,请避免这样做。

问题 1

实现 hog.py 中的 roll_dice 函数。它接受两个参数:一个名为 num_rolls 的正整数,指定掷骰子的次数,和一个 dice 函数。它返回在一回合中掷骰 num_rolls 次所得的分数:点数之和或 1 (Sow Sad)

  • Sow Sad。如果任何骰子点数为 1,当前玩家本回合的得分为 1,无论其他骰子的点数如何。
  • 示例 1:当前玩家掷 7 个骰子,其中 5 个为 1。本回合得 1 分。
  • 示例 2:当前玩家掷 4 个骰子,全部为 3。由于没有触发 Sow Sad,本回合得 12 分。

要获取单次掷骰结果,调用 dice()。你应该在 roll_dice 的函数体中调用 dice() 恰好 num_rolls

记住,即使 Sow Sad 在掷骰过程中发生,也要恰好调用 dice() num_rolls。这样做才能正确模拟 所有骰子一起掷出(用户界面也会正常工作)。

注意: roll_dice 函数以及项目中许多其他函数 使用了默认参数值——你可以在函数头中看到:

def roll_dice(num_rolls, dice=six_sided): ...

参数 dice=six_sided 表示 roll_dice 函数中的 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

在大多数系统上,你可以按上箭头然后按回车来再次执行相同的表达式。要执行更早的命令,反复按上箭头。

如果发现问题,首先需要修改 hog.py 文件来修复问题,然后保存文件。然后,要检查修复是否有效,你需要通过 exit()Ctrl^D 退出 Python 解释器,然后重新运行解释器来测试你做的更改。在终端和 Python 解释器中按上箭头应该可以让你访问之前的表达式,即使重启了 Python。

继续调试代码并运行 ok 测试,直到全部通过。

另一个调试技巧:要在 ok 测试失败时自动启动交互式解释器,使用 -i。例如,python3 ok -q 01 -i 将运行问题 1 的测试,然后在测试失败时启动一个加载了 hog.py 的交互式解释器。

问题 2

实现 boar_brawl,它接受玩家当前分数 player_score 和对手当前分数 opponent_score,返回玩家掷 0 个骰子时触发 Boar Brawl 所得的分数。

  • Boar Brawl。选择掷零个骰子的玩家得分为对手分数的十位数与当前玩家分数的个位数之差的绝对值的三倍,或 1,取较大者。个位数指最右边的数字,十位数指从右数第二个数字。如果玩家分数是个位数(小于 10),则该玩家分数的十位数为 0。
  • 示例 1:

    • 当前玩家有 21 分,对手有 46 分,当前玩家 选择掷零个骰子。
    • 对手分数的十位数为 4 ,当前玩家分数的个位数为 1.
    • 因此,玩家获得 3 * abs(4 - 1) = 9 分。
  • 示例 2:

    • 当前玩家有 45 分,对手有 52 分,当前玩家 选择掷零个骰子。
    • 对手分数的十位数为 5 ,当前玩家分数的个位数为 5.
    • 因为 3 * abs(5 - 5) = 0,玩家获得 1 分。
  • 示例 3:

    • 当前玩家有 2 分,对手有 5 分,当前玩家 选择掷零个骰子。
    • 对手分数的十位数为 0 ,当前玩家分数的个位数为 2.
    • 因此,玩家获得 3 * abs(0 - 2) = 6 分。

不要假设分数低于 100。编写 boar_brawl 函数使其对任何非负分数都能正确工作。

重要:你的实现不应使用 str、列表或包含方括号 [ ]。测试用例会检查是否使用了这些。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 02 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 02

你也可以通过在终端运行 python3 -i hog.py 并在各种输入上调用 boar_brawl 来交互式地测试。

问题 3

实现 take_turn 函数,返回通过掷给定 dice num_rolls 次所得的回合分数。

你的 take_turn 实现应该调用 roll_diceboar_brawl 函数,而不是重复它们的实现。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 03 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 03


问题 4

首先,实现 num_factors,它接受一个正整数 n 并确定 n 的因子数量。

1 和 n 都是 n 的因子!

然后,实现 sus_pointssus_update

  • sus_points 接受玩家的分数并返回应用 Sus Fuss 规则后的新分数,即使分数保持不变。例如,sus_points(5) 应返回 5sus_points(21) 应返回 23。你应在实现中使用 num_factors 和提供的 is_prime 函数。
  • sus_update 返回玩家掷 num_rolls 个骰子后的总分数,同时考虑 Boar Brawl 和 Sus Fuss。你应在此函数中使用 sus_points

提示

  • 你可以查看 hog.py 中提供的 simple_update 实现,并将其作为 sus_update 函数的起点。
  • 记住 take_turn 已经考虑了 Boar Brawl 规则!
  • Sus Fuss. We call a number sus if it has exactly 3 or 4 factors, including 1 and the number itself. If, after rolling, the current player's score is a sus number, their score instantly increases to the closest prime number greater than their current score.
  • 示例 1:

    • 一个玩家有 14 分,掷 2 个骰子获得 7 分。 新分数为 21, 有 4 个因子: 1, 3, 7, and 21. 因此,该数是 sus,玩家分数立即增加到 23, the next prime number.
  • 示例 2:

    • A player with 63 points rolls 5 个骰子获得 1 point from their turn. 新分数为 64 (Sow Sad 😢), 有 7 个因子: 1, 2, 4, 8, 16, 32, and 64. 因为 64 is not sus, the score of the player is unchanged.
  • 示例 3:

    • 一个玩家有 49 分,掷 5 个骰子共获得 18 分。 新分数为 67, 是素数,有 2 个因子: 1 and 67. 因为 67 is not sus, the score of the player is unchanged.

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 04 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 04

问题 5

实现 play 函数,模拟一整局 Hog 游戏。玩家轮流掷骰,直到其中一方达到 goal 分数。函数然后返回双方玩家的最终分数。

要确定每回合掷多少个骰子,调用当前玩家的策略函数(玩家0使用 strategy0,玩家1使用 strategy1)。策略是一个函数,给定玩家分数和对手分数,返回当前玩家在该回合要掷的骰子数量。一个简单的策略示例是 always_roll_5,出现在 play 上方。

要确定玩家回合后的更新分数,调用 update 函数。update 函数接受要掷的骰子数量、当前玩家分数、对手分数和用于模拟掷骰的骰子函数。它返回当前玩家回合后的更新分数。两个 update 函数示例是 simple_updatesus_update。记住,update 函数返回玩家回合后的总分数,而不仅仅是分数变化。

当一方在其回合结束后(在所有适用规则应用之后)达到或超过目标分数时,游戏结束。play 将返回双方玩家的最终总分,玩家0的分数在前,玩家1的分数在后。

play 的一些示例调用:

  • play(always_roll_5, always_roll_5, simple_update) 模拟两个每回合总是掷 5 个骰子的玩家,仅使用 Sow Sad 和 Boar Brawl 规则。
  • play(always_roll_5, always_roll_5, sus_update) 模拟两个每回合总是掷 5 个骰子的玩家,使用 Sus Fuss 规则以及 Sow Sad 和 Boar Brawl 规则(即所有规则)。

重要提示:为了使用户界面正常工作,策略函数每回合应只被调用一次。只在玩家0的回合调用 strategy0,只在玩家1的回合调用 strategy1

提示

  • 如果 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 检查点作业来提交检查点 请确保在截止日期 07/01 星期三之前提交。 有关如何提交到 Gradescope 的回顾,请参阅 实验 00

你可以通过点击 Edit Group 并输入队友的邮箱地址来添加搭档到你的 Gradescope 提交中。 只需一位搭档提交到 Gradescope 即可。

恭喜!你已完成本项目的第一阶段!


插曲:用户界面

本项目这一部分没有必做题,只是一些供你阅读和理解的示例。剩余的项目问题请见第二阶段。

Animated die

动画由 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)

我们还提供了一个基于 Web 的图形用户界面,使用了与 hog_ui.py 类似的方法,名为 hog_gui.py。你可以从终端运行它:

python3 hog_gui.py

hog_ui.py 一样,GUI 依赖于你的模拟器实现,所以如果你的代码有 bug,它们会反映在 GUI 中。这意味着你也可以将 GUI 用作调试工具;不过,最好先运行测试。

如果你想的话,可以查看 hog_gui.py 文件和项目版本中包含的 gui_files 目录内容,了解 GUI 的工作原理。不过,GUI 实现使用了其他几种编程语言:Javascript、HTML 和 CSS。


第二阶段:策略

在这一阶段,你将尝试各种方法来改进总是掷五个骰子的简单 always_roll_five 策略。策略是一个函数,接受两个参数:当前玩家的分数和对手的分数。它 返回玩家要掷的骰子数量,可以是 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,但可以是任何数字)。 确保你的解决方案考虑了指定 goalscoreopponent_score 的每一种可能组合。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 07 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 07

问题 8

实现 make_averaged,这是一个高阶函数,接受一个函数 original_function 作为参数。

make_averaged 的返回值是一个接受与 original_function 相同参数的函数。当使用特定参数调用时,该函数应重复 在相同参数上调用 original_function times_called 次,并返回结果的平均值。 看一下 make_averaged 的 doctest。注意跟踪传入函数的值!

Doctest 详细讲解: 仔细看 make_averaged 的 doctest。这里,original_functionroll_dice。 注意 averaged_dice(1, dice) 这一行。这意味着 roll_dice 的参数是 (1, dice)(想想为什么!)观察 averaged_dice 如何接受与 roll_dice 相同的参数。参数不直接传给 roll_dice,而是传给 averaged_dice。(想想这如何实现!)请记住,make_averaged 应该适用于任何与 make_averaged 返回的函数共享相同参数结构的 original_function。 在这个例子中,掷一个骰子被视为一次采样(roll_dice(1, dice))。因为 times_called 设为 40,这个采样重复 40 次。make_averaged 函数然后计算这 40 次调用 roll_dice 的平均结果。

重要提示: 要实现这个函数,你需要使用一个新的 Python 语法。 我们想写一个接受任意数量参数的函数,然后用完全相同的参数调用另一个函数。下面是它的工作方式。

不用列出函数的形式参数,你可以写 *args,它表示传递给函数的所有参数。然后我们可以通过将这些 *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

这里,我们可以通过 *args 语法向 print_and_return 传递任意数量的参数。我们也可以在 print_and_return 函数内部使用 *args 来用相同的参数调用另一个函数。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 08 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 08

问题 9

实现 max_scoring_num_rolls,使用固定面数的骰子运行实验,确定掷骰数(1 到 10)中哪个能给出最大的平均 回合得分。你的实现应使用 make_averagedroll_dice

如果两个掷骰数的平均得分并列最大,返回 较小的数。例如,如果 3 和 6 达到相同的最大平均得分, 返回 3。

在做解锁测试之前,你可能会发现阅读本题和 make_averaged(问题 8)的 doctest 会很有帮助。

重要提示:为了通过所有测试,请确保你 从 1 到 10 测试骰子掷数,而不是从 10 到 1。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 09 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 09

运行实验

提供的 run_experiments 函数调用 max_scoring_num_rolls(six_sided) 并打印结果。你很可能会发现 使用六面骰时掷 6 个骰子能使 roll_dice 的结果最大化。

要调用此函数并查看结果,使用 -r 标志运行 hog.py

python3 hog.py -r

此外,run_experiments 将各种策略与 always_roll(6) 进行比较。 欢迎你随意修改 run_experiments 的实现。 注意,在你实现接下来两个问题中的 boar_strategysus_strategy 之前, 运行实验的结果将不准确。

某些实验可能需要长达一分钟才能运行完。你可以随时减少 make_averaged 调用中的试验次数来加快实验。

运行实验不会影响你的项目分数。


问题 10

策略可以利用 Boar Brawl 规则,在最有利的时候选择掷 0 个骰子。实现 boar_strategy,当掷 0 能获得至少 threshold 分时返回 0,否则返回 num_rolls。此策略不应同时考虑 Sus Fuss 规则。

提示:你可以使用你在问题 2 中定义的 boar_brawl 函数。

在编写任何代码之前,解锁测试以验证你对问题的理解:

python3 ok -q 10 -u

解锁完成后,开始实现你的解决方案。你可以用以下命令检查正确性:

python3 ok -q 10

你应该会发现运行 python3 hog.py -r 后,boar_strategy 的胜率约为 66-67%。

问题 11

更好的策略会同时利用 Boar BrawlSus Fuss 的组合效果。 例如,如果玩家有 53 分而对手有 60 分,掷 0 会让他们得到 62 分,这是一个 sus 数字,因此他们将以 67 分结束 回合:获得了 67 - 53 = 14 分!

sus_strategy 在掷 0 能获得比回合开始时玩家分数至少threshold 分时返回 0。

提示:你可以使用你在问题 4 中定义的 sus_update 函数。

在编写任何代码之前,解锁测试以验证你对问题的理解:

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 来提交本作业。有关如何操作的回顾,请参阅 实验 00

你可以通过点击 Edit Group 并输入搭档的邮箱地址来添加搭档到你的 Gradescope 提交中。只需一位搭档提交到 Gradescope 即可。

恭喜,你已经完成了你的第一个 CS 61A 项目! 如果你还没有的话,放松一下,和朋友玩几局 Hog 吧。