import{_ as d,D as l,c as a,j as e,a as o,I as n,a4 as p,o as t}from"./chunks/framework.DtvhUNIn.js";const x=JSON.parse('{"title":"项目:扫雷,骑士与流氓问题","description":"","frontmatter":{},"headers":[],"relativePath":"技术资源汇总(杭电支持版)/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题.md","filePath":"技术资源汇总(杭电支持版)/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题.md"}'),s={name:"技术资源汇总(杭电支持版)/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题.md"},i=e("h1",{id:"项目-扫雷-骑士与流氓问题",tabindex:"-1"},[o("项目:扫雷,骑士与流氓问题 "),e("a",{class:"header-anchor",href:"#项目-扫雷-骑士与流氓问题","aria-label":'Permalink to "项目:扫雷,骑士与流氓问题"'},"​")],-1),r=e("div",{class:"warning custom-block"},[e("p",{class:"custom-block-title"},"😋 我们为你提供了两个简单有趣的项目,帮助你进行知识巩固,请认真阅读文档内容。"),e("p",null,"如果你卡住了,请记得回来阅读文档,或请求身边人的帮助。")],-1),h={class:"tip custom-block"},m=e("p",{class:"custom-block-title"},"📥",-1),u=p(`

pip3 install -r requirements.txt

骑士与流氓问题

背景

在 1978 年,逻辑学家雷蒙德·斯穆里安(Raymond Smullyan)出版了《这本书叫什么名字?》,这是一本逻辑难题的书。在书中的谜题中,有一类谜题被斯穆里安称为“骑士与流氓”谜题。

在骑士与流氓谜题中,给出了以下信息:每个角色要么是骑士,要么是流氓。骑士总是会说实话:如果骑士陈述了一句话,那么这句话就是真的。相反,流氓总是说谎:如果流氓陈述了一个句子,那么这个句子就是假的。

谜题的目标是,给出每个角色说的一组句子,确定每个角色是骑士还是流氓。

比如,这里有一个简单的谜题只有一个名为 A 的角色。A 说:“我既是骑士又是流氓。”

从逻辑上讲,我们可以推断,如果 A 是骑士,那么这句话一定是真的。但我们知道这句话不可能是真的,因为 A 不可能既是骑士又是流氓——我们知道每个角色要么是骑士,要么是流氓,不会出现是流氓的骑士或是骑士的流氓。所以,我们可以得出结论,A 一定是流氓。

那个谜题比较简单。随着更多的字符和更多的句子,谜题可以变得更加棘手!你在这个问题中的任务是确定如何使用命题逻辑来表示这些谜题,这样一个运行模型检查算法的人工智能可以为我们解决这些谜题。

理解

看一下 logic.py,你可能还记得讲义的内容。无需了解此文件中的所有内容,但请注意,此文件为不同类型的逻辑连接词定义了多个类。这些类可以相互组合,所以表达式 And(Not(A), Or(B, C)) 代表逻辑语句:命题 A 是不正确的,同时,命题 B 或者命题 C 是正确的。(这里的“或”是同或,不是异或)

回想一下 logic.py,它还包含一个 函数 model_checkmodel_check 输入知识库和查询结论。知识库是一个逻辑命题:如果知道多个逻辑语句,则可以将它们连接在一个表达式中。递归考虑所有可能的模型,如果知识库推理蕴含查询结论,则返回 True,否则返回 False

现在,看看 puzzle.py,在顶部,我们定义了六个命题符号。例如,AKnight 表示“A 是骑士”的命题,AKnave 而表示“A 是流氓”的句子。我们也为字符 B 和 C 定义了类似的命题符号。

接下来是四个不同的知识库 knowledge0, knowledge1, knowledge2, and knowledge3,它们将分别包含推断即将到来的谜题 0、1、2 和 3 的解决方案所需的知识。请注意,目前,这些知识库中的每一个都是空的。这就是你进来的地方!

这个 puzzle.pymain 函数在所有谜题上循环,并使用模型检查来计算,给定谜题的知识,无论每个角色是骑士还是无赖,打印出模型检查算法能够得出的任何结论。

明确

将知识添加到知识库 knowledge0, knowledge1, knowledge2, 和 knowledge3 中,以解决以下难题。

上述每个谜题中,每个角色要么是骑士,要么是流氓。骑士说的每一句话都是真的,流氓说的每一句话都是假的。

一旦你完成了一个问题的知识库,你应该能够运行 python puzzle.py 来查看谜题的解决方案。

提示

对于每个知识库,你可能想要编码两种不同类型的信息:(1)关于问题本身结构的信息(即骑士与流氓谜题定义中给出的信息),以及(2)关于角色实际说了什么的信息。

考虑一下,如果一个句子是由一个角色说出的,这意味着什么。在什么条件下这句话是真的?在什么条件下这个句子是假的?你如何将其表达为一个合乎逻辑的句子?

每个谜题都有多个可能的知识库,可以计算出正确的结果。你应该尝试选择一个能对谜题中的信息进行最直接的知识库,而不是自己进行逻辑推理。你还应该考虑谜题中信息最简洁的表达方式是什么。

例如,对于谜题 0,设置 knowledge0=AKnave 将产生正确的输出,因为通过我们自己的推理,我们知道 A 一定是一个无赖。但这样做违背了这个问题的精神:目标是让你的人工智能为你做推理。

您不需要(也不应该)修改 logic.py 来完成这个问题。

扫雷

写一个 AI 来玩扫雷游戏。

背景

扫雷游戏

扫雷器是一款益智游戏,由一个单元格网格组成,其中一些单元格包含隐藏的“地雷”。点击包含地雷的单元格会引爆地雷,导致用户输掉游戏。单击“安全”单元格(即不包含地雷的单元格)会显示一个数字,指示有多少相邻单元格包含地雷,其中相邻单元格是指从给定单元格向左、向右、向上、向下或对角线一个正方形的单元格。 例如,在这个 3x3 扫雷游戏中,三个 1 值表示这些单元格中的每个单元格都有一个相邻的单元格,该单元格是地雷。四个 0 值表示这些单元中的每一个都没有相邻的地雷。

给定这些信息,玩家根据逻辑可以得出结论,右下角单元格中一定有地雷,左上角单元格中没有地雷,因为只有在这种情况下,其他单元格上的数字标签才会准确。

游戏的目标是标记(即识别)每个地雷。在游戏的许多实现中,包括本项目中的实现中,玩家可以通过右键单击单元格(或左键双击,具体取决于计算机)来标记地雷。

命题逻辑

你在这个项目中的目标是建立一个可以玩扫雷游戏的人工智能。回想一下,基于知识的智能主体通过考虑他们的知识库来做出决策,并根据这些知识做出推断。

我们可以表示人工智能关于扫雷游戏的知识的一种方法是,使每个单元格成为命题变量,如果单元格包含地雷,则为真,否则为假。

我们现在掌握了什么信息?我们现在知道八个相邻的单元格中有一个是地雷。因此,我们可以写一个逻辑表达式,如下所示,表示其中一个相邻的单元格是地雷。

但事实上,我们知道的比这个表达所说的要多。上面的逻辑命题表达了这样一种观点,即这八个变量中至少有一个是真的。但我们可以做一个更有力的陈述:我们知道八个变量中有一个是真的。这给了我们一个命题逻辑命题,如下所示。

txt
Or(
    And(A, Not(B), Not(C), Not(D), Not(E), Not(F), Not(G), Not(H)),
    And(Not(A), B, Not(C), Not(D), Not(E), Not(F), Not(G), Not(H)),
    And(Not(A), Not(B), C, Not(D), Not(E), Not(F), Not(G), Not(H)),
    And(Not(A), Not(B), Not(C), D, Not(E), Not(F), Not(G), Not(H)),
    And(Not(A), Not(B), Not(C), Not(D), E, Not(F), Not(G), Not(H)),
    And(Not(A), Not(B), Not(C), Not(D), Not(E), F, Not(G), Not(H)),
    And(Not(A), Not(B), Not(C), Not(D), Not(E), Not(F), G, Not(H)),
    And(Not(A), Not(B), Not(C), Not(D), Not(E), Not(F), Not(G), H)
)

这是一个相当复杂的表达!这只是为了表达一个单元格中有 1 意味着什么。如果一个单元格有 2、3 或其他值,这个表达式可能会更长。

试图对这类问题进行模型检查也会很快变得棘手:在 8x8 网格(微软初级游戏模式使用的大小)上,我们有 64 个变量,因此需要检查$2^{64}$个可能的模型——太多了,计算机无法在任何合理的时间内计算。对于这个问题,我们需要更好地表达知识。

知识表示

相反,我们将像下面这样表示人工智能知识的每一句话。

这种表示法中的每个逻辑命题都有两个部分:一个是网格中与提示数字有关的一组单元格 cell,另一个是数字计数 count,表示这些单元格中有多少是地雷。上面的逻辑命题说,在单元格 A、B、C、D、E、F、G 和 H 中,正好有 1 个是地雷。

为什么这是一个有用的表示?在某种程度上,它很适合某些类型的推理。考虑下面的游戏。

利用左下数的知识,我们可以构造命题 {D,E,G}=0,意思是在 D、E 和 G 单元中,正好有 0 个是地雷。凭直觉,我们可以从这句话中推断出所有的单元格都必须是安全的。通过推理,每当我们有一个 count 为 0 的命题时,我们就知道该命题的所有 cell 都必须是安全的。

同样,考虑下面的游戏。

我们的人工智能会构建命题 {E,F,H}=3。凭直觉,我们可以推断出所有的 E、F 和 H 都是地雷。更一般地说,任何时候 cell 的数量等于 count,我们都知道这个命题的所有单元格都必须是地雷。

一般来说,我们只希望我们的命题是关于那些还不知道是安全的还是地雷的 cell。这意味着,一旦我们知道一个单元格是否是地雷,我们就可以更新我们的知识库来简化它们,并可能得出新的结论。

例如,如果我们的人工智能知道命题 {A,B,C}=2,那么我们还没有足够的信息来得出任何结论。但如果我们被告知 C 是安全的,我们可以将 C 从命题中完全删除,留下命题 {A,B}=2(顺便说一句,这确实让我们得出了一些新的结论)

同样,如果我们的人工智能知道命题 {A,B,C}=2,并且我们被告知 C 是一颗地雷,我们可以从命题中删除 C,并减少计数的值(因为 C 是导致该计数的地雷),从而得到命题 {A、B}=1。这是合乎逻辑的:如果 A、B 和 C 中有两个是地雷,并且我们知道 C 是地雷,那么 A 和 B 中一定有一个是地雷。

如果我们更聪明,我们可以做最后一种推理。

考虑一下我们的人工智能根据中间顶部单元格和中间底部单元格会知道的两个命题。从中上角的单元格中,我们得到 {A,B,C}=1。从底部中间单元格中,我们得到 {A,B,C,D,E}=2。从逻辑上讲,我们可以推断出一个新的知识,即 {D,E}=1。毕竟,如果 A、B、C、D 和 E 中有两个是地雷,而 A、B 和 C 中只有一个是地雷的话,那么 D 和 E 必须是另一个地雷。

更一般地说,任何时候我们有两个命题满足 set1=count1set2=count2,其中 set1set2 的子集,那么我们可以构造新的命题 set2-set1=count2-count1。考虑上面的例子,以确保你理解为什么这是真的。

因此,使用这种表示知识的方法,我们可以编写一个人工智能智能主体,它可以收集有关扫雷的知识,并希望选择它知道安全的单元格!

理解

这个项目有两个主要文件:runner.pyminesweeper.pyminesweeper.py 包含游戏本身和 AI 玩游戏的所有逻辑。runner.py 已经为你实现,它包含了运行游戏图形界面的所有代码。一旦你完成了 minesweeper.py 中所有必需的功能,你就可以运行 python runner.py 来玩扫雷了(或者让你的 AI 为你玩)!

让我们打开 minesweeper.py 来了解提供了什么。这个文件中定义了三个类,Minesweeper,负责处理游戏;Sentence,表示一个既包含一组 cell 又包含一个 count 的逻辑命题;以及 MinesweeperAI,它处理根据知识做出的推断。

Minesweeper 类已经完全实现了。请注意,每个单元格都是一对 (i,j),其中 i 是行号 (范围从 0height-1),j 是列号 (范围从 0width-1)。

Sentence 类将用于表示背景中描述的形式的逻辑命题。每个命题中都有一组 cell,以及 count 表示其中有多少单元格是地雷。该类还包含函数 known_minesknown_safes,用于确定命题中的任何单元格是已知的地雷还是已知的安全单元格。它还包含函数 mark_minemark_safe,用于响应有关单元格的新信息来更新命题。

最后,MinesweeperAI 类将实现一个可以玩扫雷的 AI。AI 类跟踪许多值。self.moves_made 包含一组已经点击过的所有单元格,因此人工智能知道不要再选择这些单元格。self.mines 包含一组已知为地雷的所有单元格。self.safes 包含一组已知安全的所有单元格。而 self.knowledge 包含了人工智能知道是真的所有命题的列表。

mark_mine 函数为 self.mines 添加了一个单元格,因此 AI 知道这是一个地雷。它还循环遍历人工智能知识中的所有命题,并通知每个命题该单元格是地雷,这样,如果命题包含有关地雷的信息,它就可以相应地更新自己。mark_safe 函数也做同样的事情,只是针对安全单元格。

剩下的函数 add_knowledgemake_safe_movemake_random_move 由你完成!

明确

完成 minesweeper.py 中的 Sentence 类和 MinesweeperAI 类的实现。 在 Sentence 类中,完成 known_minesknown_safesmark_minemark_safe 的实现。

MinesweeperAI 类中,完成 add_knowledgemake_safe_movemake_random_move 的实现。

提示

`,78);function _(A,N,k,f,b,w){const c=l("Download");return t(),a("div",null,[i,r,e("div",h,[m,e("p",null,[o("本节附件下载 "),n(c,{url:"https://cdn.xyxsw.site/code/2-Projects.zip"})])]),u])}const C=d(s,[["render",_]]);export{x as __pageData,C as default};