项目1: Hog 游戏
Project 1: The Game of Hog

查看英文原文

5-sided die

I know! I'll use my
Higher-order functions to
Order higher rolls.

介绍

重要提交说明: 为了获得全部分数,请:

  • 9月8日星期二 前完成 Phase 1(价值1分)。
  • 9月11日星期五 前完成所有阶段。

虽然 Phase 1 的截止日期仅比其余部分早几天,但你不应该推迟完成 Phase 1。我们建议尽快开始并完成第1阶段。

整个项目可以与伙伴一起完成。

如果在 9月10日星期四 前提交整个项目,可以获得1个额外积分。

在这个项目中,你将开发一个骰子游戏 Hog 的模拟器并研究多种策略。 你需要结合使用控制语句高阶函数,具体内容参见课本的第1.2到1.6节。

规则

在 Hog 游戏中,两名玩家轮流进行回合,目标是成为第一个在回合结束时总分达到至少 100 分的玩家。在每个回合中,当前玩家选择要掷的骰子数量,最多可以选择 10 个。该玩家本回合的得分是所有骰子结果的总和。然而,掷太多骰子的玩家有风险:

  • Pig Out。如果任何一个骰子的结果是 1,当前玩家本回合的得分为 1。
  • 示例 1: 当前玩家掷7次骰子,其中5次是1。则他的最终分数为1
  • 示例 2: 当前玩家掷4次骰子,全部都是3。因为Pig Out没有发生,他的最终分数为12

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

  • Free Bacon。选择掷零个骰子的玩家将获得 k+3 分,其中 k 是 π 的小数点后第 n 位数字,n 是对手的总分。作为一个特殊情况,如果对手的得分是 n = 0,那么 k = 3(π 小数点前的数字)。
  • 示例 1:对手的得分为 0,当前玩家掷零个骰子。当前玩家将获得 3 + 3 = 6 分。
  • 示例 2:对手的得分为 1,当前玩家掷零个骰子。当前玩家将获得 1 + 3 = 4 分。
  • 示例 3:对手的得分为 2,当前玩家掷零个骰子。当前玩家将获得 4 + 3 = 7 分。
  • 示例 4:对手的得分为 42,当前玩家掷零个骰子。当前玩家将获得 9 + 3 = 12 分。
  • Swine Align。在本回合得分加到当前玩家的总分后,如果两名玩家的得分都为正且当前玩家的得分与对手的得分的最大公约数(GCD)至少为10,则当前玩家再进行一次回合。
  • 示例 1:在第一个玩家回合结束时,两名玩家的得分分别为8和36。得分的最大公约数为4,因此第一个玩家不会因为 Swine Align 规则而再进行一次回合。
  • 示例 2:在第一个玩家回合结束时,两名玩家的得分分别为20和30。得分的最大公约数为10,因此第一个玩家会再进行一次回合。
  • 示例 3:在第一个玩家回合结束时,两名玩家的得分分别为24和36。得分的最大公约数为12,因此第一个玩家会再进行一次回合。第一个玩家掷出12点,得分变为36和36。得分的最大公约数为36,因此第一个玩家会再进行一次回合。
  • 示例 4:在第一个玩家回合结束时,两名玩家的得分分别为15和0。Swine Align 规则仅在两名玩家的得分都为正时适用(不为零),因此第一个玩家不会因为 Swine Align 规则而再进行一次回合。
  • Pig Pass。在本回合得分加到当前玩家的总分后,如果当前玩家的得分低于对手的得分且两者之间的差值小于3,则当前玩家再进行一次回合。
  • 示例 1:回合结束时,当前玩家的得分为11,对手的得分为10,。由于11 > 10,当前玩家不会因为 Pig Pass 规则而再进行一次回合。
  • 示例 2:回合结束时,当前玩家的得分为7,对手的得分为10。因为 10 - 7 = 3 >= 3,当前玩家不会因为 Pig Pass 规则而再进行一次回合。
  • 示例 3:回合结束时,当前玩家的得分为28,对手的得分为30。因为 30 - 28 = 2 < 3,当前玩家会再进行一次回合。
  • 示例 4:回合结束时,当前玩家的得分为28,对手的得分为30。与示例3类似,当前玩家会再进行一次回合。如果当前玩家掷出1点,得分变为29,Pig Pass 规则再次生效,当前玩家会再进行一次回合。

最终成果

我们的项目解决方案可以在 hog.cs61a.org 上试玩!当你完成这个项目时,你将自己实现这个游戏的一个重要部分。

下载初始文件

要开始,请下载所有项目代码的 zip 压缩包。以下是压缩包中所有文件的列表。不过,你只需要修改 hog.py 文件。

  • hog.py:Hog 游戏的初始实现
  • dice.py:掷骰子的函数
  • hog_gui.py:Hog 游戏的图形用户界面(GUI)
  • ucb.py:CS 61A 的实用函数
  • ok:CS 61A 自动评分器
  • testsok 使用的测试目录
  • gui_files:Web GUI 使用的各种文件目录

项目安排与提交

该项目总共25分。22分用于正确性,1分用于在截止日期前提交 Phase 1,2分用于整体组成

你需要提交以下文件:

  • hog.py

你不需要修改或提交任何其他文件来完成该项目。要提交项目,请运行以下命令:

python3 ok --submit

你可以在 Ok dashboard 上查看你的提交。

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

但是,请不要修改任何其他函数。这样做可能会导致你的代码在我们的自动评分器测试中失败。 另外,请不要更改任何函数签名(名称、参数顺序或参数数量)。

在整个项目过程中,你应该测试代码的正确性。经常测试是一个好习惯,这样可以很容易地隔离任何问题。 但是,你不应该测试频繁,以至于没有时间思考问题。

我们提供了一个名为 ok自动评分器,帮助你测试代码并跟踪进度。第一次运行自动评分器时, 你将要通过浏览器登录你的 Ok 账户。请这样做。每次运行 ok 时,它都会备份你的工作和进度到我们的服务器上。

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

我们建议你在完成每个问题后提交。只有你最后一次提交的内容会被评分。对于我们来说,拥有更多的代码备份也是有用的,以防你提交遇到问题。如果你忘记提交,你的最后一次备份将自动转换为提交内容。

如果你不希望我们记录你的工作备份或进度信息,你可以运行

python3 ok --local
使用此选项时,不会有任何信息发送到我们的课程服务器。 如果你想交互式地测试你的代码,你可以运行
python3 ok -q [问题编号] -i --local
插入适当的问题编号(例如 01)。这将运行该问题的测试,遇到第一个失败的测试时,你可以交互式地测试你编写的函数。

你也可以通过在 OK 中编写

print("DEBUG:", x)
来使用调试打印功能,这将在你的终端中生成输出,而不会因为额外的输出导致 OK 测试失败。

图形用户界面

我们为你提供了一个图形用户界面(简称 GUI)。目前,它还不能工作,因为你还没有实现游戏逻辑。完成 play 函数,这样你就能游玩一个完全互动的 Hog 游戏!

完成后,你可以在终端中运行以下命令来启动 GUI:

python3 hog_gui.py

该 GUI 是一个托管在 Github 上的开源项目

Phase 1:模拟器

在第一阶段,你将开发一个 Hog 游戏的模拟器。

Problem 0 (0 分)

dice.py 文件使用无参数函数来表示骰子。这些函数是非纯函数,因为它们每次调用时可能会有不同的返回值。dice.py 的文档描述了项目中使用的两种不同类型的骰子:

  • 公平骰子,意味着它们以相等的概率产生每个可能的结果。例如:six_sided
  • 测试用骰子,循环投出传入 make_test_dice 函数的固定值序列。

在编写代码之前,请阅读 dice.py 文件,并通过解锁以下测试来检查你的理解。

python3 ok -q 00 -u --local

输出内容类似于:

=====================================================================
Assignment: Project 1: Hog
Ok, version v1.5.2
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlocking tests

At each "? ", type what you would expect the output to be.
Type exit() to quit

---------------------------------------------------------------------
Question 0 > Suite 1 > Case 1
(cases remaining: 1)

>>> test_dice = make_test_dice(4, 1, 2)
>>> test_dice()
?

你需要首先根据上述描述弄清楚 test_dice 做了什么。再输入你期望的输出结果。

你可以通过输入 exit() 来退出解锁程序。在 Windows 上使用 Ctrl-C 退出解锁程序已知会导致问题,因此请避免这样做。

Problem 0 提示视频

这个视频为解决 Hog 游戏第一阶段的问题提供了一些有用的指导。

Problem 1 (2 分)

hog.py 文件中实现 roll_dice 函数。它接受两个参数:一个名为 num_rolls 的正整数,表示要掷骰子的次数,以及一个 dice 函数。它返回在一个回合中掷骰子该次数所获得的分数:要么是结果的总和,要么是 1(Pig Out)。

Pig Out 规则:

  • Pig Out。如果任何一个骰子的结果是 1,当前玩家本回合的得分为 1。

    • 示例 1: 当前玩家掷7次骰子,其中5次是1。则他的最终分数为1
    • 示例 2: 当前玩家掷4次骰子,全部都是3。因为Pig Out没有发生,他的最终分数为12

可以调用 dice()来获得掷一次骰子的结果。你应该在 roll_dice 函数体内调用 dice() num_rolls 次。即使在掷骰子过程中触发 Pig Out规则,也要记得调用 dice() num_rolls 次。 这样,你才能正确模拟所有骰子一起掷的情况。

理解问题

在编写代码之前,你需要解锁测试以验证你对问题的理解。注意:在解锁相应问题的测试用例之前,你将无法使用 OK 测试你的代码

python3 ok -q 01 -u --local

编写代码并检查你的工作

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 01 --local

Problem 1 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

如果测试未通过,你应该进行调试。你可以直接使用 Python 观察函数的行为。首先,启动 Python 解释器并加载 hog.py 文件。

python3 -i hog.py

然后,你可以调用 roll_dice 函数,传入任意数量的骰子。roll_dice 函数有一个默认参数值 dice,它是一个随机的六面骰子函数。因此,以下对 roll_dice 的调用模拟了掷四个公平的六面骰子。

>>> roll_dice(4)

你会发现,每次调用上面的表达式时,结果可能会有所不同,因为它模拟随机掷骰子。你也可以使用测试骰子来提前固定骰子的结果。例如,当你知道骰子将出现 3 和 4 时,掷两次应该得到总结果 7。

>>> fixed_dice = make_test_dice(3, 4)
>>> roll_dice(2, fixed_dice)
7

在大多数系统上,你可以通过按上箭头键( ↑ ),然后按回车键来重新计算相同的表达式。要运行更早的命令,请反复按上箭头键。

如果发现问题,你需要修改 hog.py 文件,保存文件,重新启动 Python,然后重新计算表达式。即使重新启动 Python,按上箭头键也能让你访问之前的表达式。

继续调试你的代码并运行 ok 测试,直到它们全部通过。你应该遵循这个理解问题、实现解决方案、测试和调试的过程来解决这个项目中的所有问题。

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

Problem 2 (1 分)

实现 free_bacon 函数,它接受对手当前的 score 并返回掷 0 次骰子所得的分数。假设 score 小于 100。

Free Bacon 规则:

  • Free Bacon。选择掷零个骰子的玩家将获得 k+3 分,其中 k 是 π 的小数点后第 n 位数字,n 是对手的总分。作为一个特殊情况,如果对手的得分是 n = 0,那么 k = 3(π小数点前的数字)。

    • 示例 1:对手的得分为 0,当前玩家掷零个骰子。当前玩家将获得 3 + 3 = 6 分。
    • 示例 2:对手的得分为 1,当前玩家掷零个骰子。当前玩家将获得 1 + 3 = 4 分。
    • 示例 3:对手的得分为 2,当前玩家掷零个骰子。当前玩家将获得 4 + 3 = 7 分。
    • 示例 4:对手的得分为 42,当前玩家掷零个骰子。当前玩家将获得 9 + 3 = 12 分。

提供的代码中包含了一个整数 FIRST_101_DIGITS_OF_PI,它存储了 pi。解决这个问题的一种方法是将 pi 截断到只有 score+1 位数字,然后返回 pi 的最后一位数字加上 3。

提示: 一个整数 n 如果小于 pow(10, k+1) 但大于或等于 pow(10, k),则它有 k+1 位数字。

你可以以任何你想要的方式实现这个函数。你不必遵循这个推荐的方法或使用提供的起始代码。然而,你不能在实现中使用 for 循环或方括号 [ ],因为我们还没有讲到这些内容。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 02 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 02 --local

You can also test free_bacon interactively by entering python3 -i hog.py in the terminal and then calling free_bacon with various inputs.

你也可以通过在终端中输入 python3 -i hog.py 以交互方式测试 free_bacon 函数,你可以使用不同的输入来调用 free_bacon 函数。

Problem 2 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

Problem 3 (2 分)

Implement the take_turn function, which returns the number of points scored for a turn by rolling the given dice num_rolls times.

Your implementation of take_turn should call both roll_dice and free_bacon when possible.

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

你的 take_turn 实现应在可能的情况下调用 roll_dicefree_bacon

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 03 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 03 --local

Problem 3 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

Problem 4a (1 分)

实现 swine_align 函数,该函数接受当前玩家和对手的得分,并返回当前玩家是否会因为 Swine Align 规则而再进行一次回合。注意: 在某一玩家得分为 0 的特殊情况下,返回 False

Swine Align 规则:

  • Swine Align。在本回合得分加到当前玩家的总分后,如果两名玩家的得分都为正且当前玩家的得分与对手的得分的最大公约数(GCD)至少为10,则当前玩家再进行一次回合。

    • 示例 1:在第一个玩家回合结束时,两名玩家的得分分别为8和36。得分的最大公约数为4,因此第一个玩家不会因为 Swine Align 规则而再进行一次回合。
    • 示例 2:在第一个玩家回合结束时,两名玩家的得分分别为20和30。得分的最大公约数为10,因此第一个玩家会再进行一次回合。
    • 示例 3:在第一个玩家回合结束时,两名玩家的得分分别为24和36。得分的最大公约数为12,因此第一个玩家会再进行一次回合。第一个玩家掷出12点,得分变为36和36。得分的最大公约数为36,因此第一个玩家会再进行一次回合。
    • 示例 4:在第一个玩家回合结束时,两名玩家的得分分别为15和0。Swine Align 规则仅在两名玩家的得分都为正时适用(不为零),因此第一个玩家不会因为 Swine Align 规则而再进行一次回合。

提示:当且仅当 nd 的倍数时,表达式 n % d == 0 的值为真。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 04a -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 04a --local

Problem 4a 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

Problem 4b (1 分)

实现 pig_pass 函数,该函数接受当前玩家和对手的得分,并返回当前玩家是否会因为 Pig Pass 规则而再进行一次回合。

Pig Pass 规则:

  • Pig Pass。在本回合得分加到当前玩家的总分后,如果当前玩家的得分低于对手的得分且两者之间的差值小于3,则当前玩家再进行一次回合。

    • 示例 1:回合结束时,当前玩家的得分为11,对手的得分为10,。由于11 > 10,当前玩家不会因为 Pig Pass 规则而再进行一次回合。
    • 示例 2:回合结束时,当前玩家的得分为7,对手的得分为10。因为 10 - 7 = 3 >= 3,当前玩家不会因为 Pig Pass 规则而再进行一次回合。
    • 示例 3:回合结束时,当前玩家的得分为28,对手的得分为30。因为 30 - 28 = 2 < 3,当前玩家会再进行一次回合。
    • 示例 4:回合结束时,当前玩家的得分为28,对手的得分为30。与示例3类似,当前玩家会再进行一次回合。如果当前玩家掷出1点,得分变为29,Pig Pass 规则再次生效,当前玩家会再进行一次回合。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 04b -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 04b --local

Problem 4b 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

Problem 5 (3 分)

实现 play 函数,该函数模拟完整的 Hog 游戏。玩家轮流掷骰子,直到其中一名玩家达到 goal 分数。

为了确定每回合掷多少骰子,每个玩家使用他们各自的策略(玩家 0 使用 strategy0,玩家 1 使用 strategy1)。strategy 是一个函数,给定玩家的得分和对手的得分,返回当前玩家在该回合中将掷的骰子数量。每回合只调用一次 strategy 函数(否则可能会破坏 GUI)。 不用担心 strategy 的实现,你将在第三阶段完成它。

当游戏结束时,play 返回两名玩家的最终总得分,玩家 0 的得分在前,玩家 1 的得分在后。

提示:

  • 你应该调用你已经实现的函数。
  • 调用 take_turn 时传入三个参数。每回合只调用一次 take_turn
  • 调用 extra_turn 来确定当前玩家是否会因为 Swine Align 或 Pig Pass 规则而再进行一次回合。
  • 你可以通过调用提供的函数 other 来获取另一名玩家的编号(0 或 1)。
  • 你可以暂时忽略 play 函数的 say 参数。你将在项目的第二阶段使用它。

规则澄清:一个玩家可以连续进行超过两次回合。例如,如果他们第一次回合后的得分是 10 比 20,他们会因为 Swine Align 规则再进行一次回合。如果他们得了 8 分,现在得分是 18 比 20,他们会因为 Pig Pass 规则再进行第三次回合。如果他们得了 12 分,现在得分是 30 比 20,他们会再进行第四次回合。

注意:Swine Align 和 Pig Pass 规则不可能同时生效。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 05 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 05 --local

完成后,你将能够玩游戏的图形版本。我们提供了一个名为 hog_gui.py 的文件,你可以在终端中运行:

python3 hog_gui.py

GUI 依赖于你的实现,所以如果你的代码中有任何错误,它们将在 GUI 中反映出来。这意味着你也可以使用 GUI 作为调试工具;然而,最好先运行测试。

Make sure to submit your work so far before the checkpoint deadline:

python3 ok --submit

Check to make sure that you did all the problems in Phase 1:

python3 ok --score

Problem 5 提示视频

这个视频为解决 Hog 游戏这一阶段的问题提供了一些有用的指导。

恭喜你!你已经完成了这个项目的Phase 1!

Phase 2: 评论

在第二阶段,你将实现评论函数,这些函数会在每个回合后打印关于游戏的评论,例如,"22 分!这是玩家1 迄今为止最大的得分!"

commentary 函数接受两个参数,玩家0 的当前得分和玩家1 的当前得分。它可以基于当前得分和其父环境中的任何其他信息打印评论。由于评论可能会根据游戏中的当前得分情况在每个回合中有所不同,commentary 函数总是返回另一个 commentary 函数,以便在下一个回合中调用。commentary 函数的唯一副作用应该是打印。

评论示例

hog.py 中的 say_scores 函数是一个 commentary 函数的示例,它宣布两名玩家的得分。注意,say_scores 返回它自己,这意味着每个回合都会调用相同的 commentary 函数。

def say_scores(score0, score1):
    """A commentary function that announces the score for each player."""
    print("Player 0 now has", score0, "and Player 1 now has", score1)
    return say_scores

The function announce_lead_changes is an example of a higher-order function that returns a commentary function that tracks lead changes. A different commentary function will be called each turn.

def announce_lead_changes(last_leader=None):
    """Return a commentary function that announces lead changes.

    >>> f0 = announce_lead_changes()
    >>> f1 = f0(5, 0)
    Player 0 takes the lead by 5
    >>> f2 = f1(5, 12)
    Player 1 takes the lead by 7
    >>> f3 = f2(8, 12)
    >>> f4 = f3(8, 13)
    >>> f5 = f4(15, 13)
    Player 0 takes the lead by 2
    """
    def say(score0, score1):
        if score0 > score1:
            leader = 0
        elif score1 > score0:
            leader = 1
        else:
            leader = None
        if leader != None and leader != last_leader:
            print('Player', leader, 'takes the lead by', abs(score0 - score1))
        return announce_lead_changes(leader)
    return say

你还应该理解 both 函数,它接受两个 commentary 函数(fg),并返回一个新的 commentary 函数。这个返回的 commentary 函数会返回另一个 commentary 函数,该函数按顺序调用 fg 返回的函数。

def both(f, g):
    """Return a commentary function that says what f says, then what g says.

    NOTE: the following game is not possible under the rules, it's just
    an example for the sake of the doctest

    >>> h0 = both(say_scores, announce_lead_changes())
    >>> h1 = h0(10, 0)
    Player 0 now has 10 and Player 1 now has 0
    Player 0 takes the lead by 10
    >>> h2 = h1(10, 8)
    Player 0 now has 10 and Player 1 now has 8
    >>> h3 = h2(10, 17)
    Player 0 now has 10 and Player 1 now has 17
    Player 1 takes the lead by 7
    """
    def say(score0, score1):
        return both(f(score0, score1), g(score0, score1))
    return say

Phase 2 提示视频

This video provides some helpful direction for tackling the problems on this phase of Hog.

Problem 6 (2 分)

更新你的 play 函数,使其在每个回合结束时调用一个 commentary 函数。调用 commentary 函数的返回值将是下一个回合要调用的 commentary 函数。

例如,say(score0, score1) 应该在第一个回合结束时被调用。它的返回值(另一个评论函数)应该在第二个回合结束时被调用。每个连续的回合,调用前一个回合的 commentary 函数返回的函数。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 06 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 06 --local

Problem 7 (3 分)

实现 announce_highest 函数,这是一个高阶函数,返回一个 commentary 函数。该 commentary 函数会在特定玩家在一轮中获得的分数超过以往任何一轮时进行播报。例如,announce_highest(1) 会完全忽略玩家0,仅打印关于玩家1 的信息。(它的返回值也是如此;另一个仅关于玩家1 的评论函数。)为了计算分数的增加,它需要将上一轮的分数(last_score)与当前轮的分数进行比较,感兴趣的玩家由 who 参数指定。此函数还必须跟踪该玩家迄今为止的最高分数增加,该值存储为 running_high

announce_highest 的播报方式非常具体,你的实现应与提供的 doctests 匹配。在播报分数增加时,不必担心单复数问题;你只需在两种情况下都使用“point(s)”。

提示: 提供给您的 announce_lead_changes 函数是一个如何使用 commentary 函数跟踪信息的示例。如果你卡住了,请先确保你理解 announce_lead_changes 的工作原理。


提示。 如果你遇到 local variable [var] reference before assignment 错误:

这是因为在 Python 中,通常不允许修改父帧中定义的变量。解释器认为你试图在当前帧中定义一个新变量,而不是重新赋值 [var]。我们将在未来的课程中学习如何解决这个问题,但这个问题并不需要用到这些知识。

要解决此问题,你有两种选择:

1) 与其重新赋值 [var],不如创建一个新变量来保存新值。在未来的计算中使用该新变量。

2) 对于这个问题,可以通过不使用赋值语句来避免此问题。可以将新值作为参数传递给 announce_highest 函数。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 07 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 07 --local

完成后,你将在 GUI 中看到评论:

python3 hog_gui.py

GUI 中的评论是通过将以下函数作为 playsay 参数传递生成的。

both(announce_highest(0), both(announce_highest(1), announce_lead_changes()))

恭喜你!你已经完成了这个项目的Phase 2!

Phase 3: 策略

在第三阶段,你将尝试在总是投掷固定数量骰子的策略之上开发更好的策略。首先,你需要开发一些工具来评估策略的好坏。

Phase 3 提示视频

This video provides some helpful direction for tackling the problems on this phase of Hog.

Problem 8 (2 分)

实现 make_averaged 函数,这是一个高阶函数,它接受一个函数 original_function 作为参数。它返回另一个函数,该函数接受与 original_function 相同数量的参数(即最初传递给 make_averaged 的函数)。返回的函数与输入函数的不同之处在于,它返回的是在相同参数下重复调用 original_function 的平均值。该函数应总共调用 original_function trials_count 次,并返回结果的平均值。

为了实现这个函数,你需要使用一个新的 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)
Result: 256
256
>>> printed_abs = printed(abs)
>>> printed_abs(-10)
Result: 10
10

请仔细阅读 make_averaged 的 docstring,并理解它应该如何工作的。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 08 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 08 --local

Problem 9 (2 分)

实现 max_scoring_num_rolls 函数,该函数通过实验来确定在1到10次掷骰中,哪一种次数能使一个回合的平均得分最大化。你的实现应使用 make_averagedroll_dice 函数。

如果有两种掷骰次数获得相同的最大平均得分,则返回较小的次数。例如,如果3次和6次掷骰都达到了最大平均得分,则返回3。

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 09 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 09 --local

要在随机骰子上运行此实验,请使用 -r 选项调用 run_experiments

python3 hog.py -r

运行实验 在本项目的剩余部分,你可以根据需要更改 run_experiments 的实现。通过调用 average_win_rate,你可以评估各种Hog策略。例如,将第一个 if False: 更改为 if True:,以评估 always_roll(8) 与基础策略 always_roll(6)

某些实验可能需要长达几分钟的时间才能完成。你可以通过减少make_averaged中调用试验的次数来加快实验速度。

运行实验不会影响你在项目中的得分。

Problem 10 (1 分)

一种策略可以通过在最有利时掷 0 次骰子来利用 Free Bacon 规则。请实现 bacon_strategy 函数,当掷 0 次骰子能够获得至少 cutoff 分时返回 0,否则返回 num_rolls

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 10 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 10 --local

Once you have implemented this strategy, change run_experiments to evaluate your new strategy against the baseline. Is it better than just rolling 4?

实现了这个策略后,修改 run_experiments 以评估你的新策略与基础策略。它是否比仅仅掷 4 次骰子更好?

Problem 11 (2 分)

一种策略还可以利用 Pig PassSwine Align 规则。在可以触发额外回合时,这种额外回合策略总是选择掷 0 次骰子。在其他情况下,如果掷 0 次骰子能够获得至少 cutoff 分,则掷 0 次骰子。否则,该策略掷 num_rolls 次骰子。

提示: 你可以使用在 Problem 10 中定义的 bacon_strategy 函数

在编写代码之前,你需要解锁测试以验证你对问题的理解。

python3 ok -q 11 -u --local

解锁完成后,你可以编写代码,并使用以下命令检查正确性:

python3 ok -q 11 --local

Once you have implemented this strategy, update run_experiments to evaluate your new strategy against the baseline. You should find that it gives a significant edge over always_roll(4).

实现了这个策略之后,修改 run_experiments 以评估你的新策略与基础策略。你会发现它比 always_roll(4) 策略有显著的优势。

可选: Problem 12 (0 分)

  • extra_turn_strategy 是一个很好的默认策略。
  • 得分超过 100 分没有意义。检查你是否可以通过掷 0、1 或 2 次骰子获胜。如果你处于领先地位,你可能需要降低风险。
  • 尝试总是触发额外回合。
  • 仔细选择 num_rollscutoff 参数。
  • 选择最有可能赢得游戏的操作。

你可以通过运行 Ok 来检查你的最终策略是否有效。

python3 ok -q 12 --local

你最终还可以通过运行以下命令来检查你的最终胜率:

python3 calc.py

这将弹出一个窗口,要求你确认身份,然后它会打印出你的最终策略的胜率。

此时,运行整个自动评分器,查看是否有任何测试未通过。

python3 ok --local

一旦你满意了,提交到 Ok 以完成项目。

python3 ok --submit

If you have a partner, make sure to add them to the submission on okpy.org.

Check to make sure that you did all the problems by running

python3 ok --score

You can also play against your final strategy with the graphical user interface:

python3 hog_gui.py

The GUI will alternate which player is controlled by you.

Congratulations, you have reached the end of your first CS 61A project! If you haven't already, relax and enjoy a few games of Hog with a friend.

/proj/hog_contest

Hog Strategy Contest

If you're interested, you can take your implementation of Hog one step further by participating in the Hog Contest, where you play your final_strategy against those of other students. The winning strategies will receive extra credit and will be recognized in future semesters!

To see more, read the contest description. Or simply check out the leaderboard.