我正在做 CS50 的 Python 人工智能入门课程,我非常喜欢它。当我运行脚本时,似乎一切正常,但 CS50 检查器发现某种边缘情况,其中我的软件在推断知识时显然找不到安全单元。 以下是 CS50 规范,重点关注未通过测试的部分:
规格
完成
中Sentence
类和MinesweeperAI
类的实现。minesweeper.py
在
类中,完成Sentence
、known_mines
、known_safes
和mark_mine
的实现。mark_safe
函数应返回known_mines
中已知为地雷的所有单元格的集合。
self.cells
函数应返回known_safes
中已知安全的所有单元格的集合。
self.cells
函数应首先检查mark_mine
是否是句子中包含的单元格之一。cell
如果
在句子中,该函数应该更新句子,以便cell
不再在句子中,但仍然代表逻辑上正确的句子,因为已知cell
是一个地雷。cell
如果句子中没有
,则无需采取任何行动。cell
函数应首先检查mark_safe
是否是句子中包含的单元格之一。cell
如果
在句子中,该函数应该更新句子,以便cell
不再在句子中,但仍然代表逻辑上正确的句子,因为已知cell
是安全的。cell
如果句子中没有
,则无需采取任何行动。cell
在
类中,完成MinesweeperAI
、add_knowledge
和make_safe_move
的实现。make_random_move
应接受add_knowledge
(表示为元组cell
)及其相应的
(i, j)
,并使用任何新信息更新count
、
self.mines
、
self.safes
和
self.moves_made
人工智能可以推断,因为已知
self.knowledge
是一个安全单元,附近有cell
地雷。count
该函数应将
标记为游戏中的动作之一。cell
该函数应将
标记为安全单元格,同时更新包含cell
的所有句子。cell
该函数应根据
和cell
的值向 AI 知识库添加一个新句子,以指示count
的邻居中的count
是地雷。确保句子中只包含状态尚未确定的单元格。cell
如果根据
中的任何句子,新单元格可以被标记为安全或地雷,那么该函数应该这样做。
self.knowledge
如果基于
中的任何句子,可以推断出新的句子(使用背景中描述的子集方法),那么这些句子也应该添加到知识库中。
self.knowledge
请注意,每当您对人工智能的知识进行任何更改时,都有可能得出以前不可能的新推论。如果可能的话,请确保将这些新的推论添加到知识库中。
这是我的代码(大部分任务已完成,所以如果你不想破坏它,请不要继续):
import itertools
import random
import copy
"""
- cut code related to setting up a board of 8x8 cells with 8 mines spread around randomly in them.
"""
class Sentence:
"""
Logical statement about a Minesweeper game
A sentence consists of a set of board cells,
and a count of the number of those cells which are mines.
"""
def __init__(self, cells, count):
self.cells = set(cells)
self.count = count
def __eq__(self, other):
return self.cells == other.cells and self.count == other.count
def __str__(self):
return f"{self.cells} = {self.count}"
def known_mines(self):
"""
Returns the set of all cells in self.cells known to be mines.
"""
if len(self.cells) == self.count != 0:
return self.cells
else:
return set()
def known_safes(self):
"""
Returns the set of all cells in self.cells known to be safe.
"""
if self.count == 0:
return self.cells
else:
return set()
def mark_mine(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be a mine.
"""
if cell in self.cells:
self.cells.remove(cell)
self.count -= 1
return True
return False
def mark_safe(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be safe.
"""
if cell in self.cells:
self.cells.remove(cell)
return True
return False
class MinesweeperAI:
"""
Minesweeper game player
"""
def __init__(self, height=8, width=8):
# Set initial height and width
self.height = height
self.width = width
# Keep track of which cells have been clicked on
self.moves_made = set()
# Keep track of cells known to be safe or mines
self.mines = set()
self.safes = set()
# List of sentences about the game known to be true
self.knowledge = []
def mark_mine(self, cell):
"""
Marks a cell as a mine, and updates all knowledge
to mark that cell as a mine as well.
"""
self.mines.add(cell)
for sentence in self.knowledge:
sentence.mark_mine(cell)
def mark_safe(self, cell):
"""
Marks a cell as safe, and updates all knowledge
to mark that cell as safe as well.
"""
self.safes.add(cell)
for sentence in self.knowledge:
sentence.mark_safe(cell)
def nearby_cells(self, cell):
"""
Returns set of cells around the given cell.
"""
# Keep count of nearby mines
cells = set()
# Loop over all cells within one row and column
for i in range(cell[0] - 1, cell[0] + 2):
for j in range(cell[1] - 1, cell[1] + 2):
# Ignore the cell itself
if (i, j) == cell:
continue
# Add cell to set if cell in bounds
if 0 <= i < self.height and 0 <= j < self.width:
cells.add((i, j))
return cells
def add_sentence(self, cells, count):
# Create new sentence based on the nearby cells and known mines and safe cells.
newSentence = Sentence(cells, count)
self.knowledge.append(newSentence)
# Check new sentence for discoveries.
for cell in copy.deepcopy(newSentence.known_safes()):
if cell not in self.safes:
self.mark_safe(cell)
for cell in copy.deepcopy(newSentence.known_mines()):
if cell not in self.mines:
self.mark_mine(cell)
# Remove empty sentences:
for sentence in self.knowledge:
if len(sentence.cells) == 0:
self.knowledge.remove(sentence)
# Add mines and safes from inferred sentences:
for sentence in self.knowledge:
if len(sentence.cells) == sentence.count:
for cell in copy.deepcopy(sentence.cells):
self.mark_mine(cell)
self.knowledge.remove(sentence)
continue
if sentence.count == 0:
for cell in copy.deepcopy(sentence.cells):
self.mark_safe(cell)
self.knowledge.remove(sentence)
continue
# Remove same sentences
updatedKnowledge = []
for sentence in self.knowledge:
if sentence not in updatedKnowledge:
updatedKnowledge.append(sentence)
self.knowledge = updatedKnowledge
# Infer knowledge based on new sentence
if len(self.knowledge) > 1:
for sentence in self.knowledge:
if sentence != newSentence:
if sentence.cells.issubset(newSentence.cells):
inferredSet = Sentence(
newSentence.cells - sentence.cells,
newSentence.count - sentence.count,
)
if inferredSet not in self.knowledge:
self.add_sentence(
newSentence.cells - sentence.cells,
newSentence.count - sentence.count,
)
if newSentence.cells.issubset(sentence.cells):
inferredSet2 = Sentence(
sentence.cells - newSentence.cells,
sentence.count - newSentence.count,
)
if inferredSet2 not in self.knowledge:
self.add_sentence(
sentence.cells - newSentence.cells,
sentence.count - newSentence.count,
)
def add_knowledge(self, cell, count):
"""
Called when the Minesweeper board tells us, for a given
safe cell, how many neighboring cells have mines in them.
This function should:
1) mark the cell as a move that has been made
2) mark the cell as safe
3) add a new sentence to the AI's knowledge base
based on the value of `cell` and `count`
4) mark any additional cells as safe or as mines
if it can be concluded based on the AI's knowledge base
5) add any new sentences to the AI's knowledge base
if they can be inferred from existing knowledge
"""
# Mark cell as the move made
self.moves_made.add(cell)
# Mark cell as safe
self.mark_safe(cell)
# Get nearby cells and substract known mines and safe cells
NearbyCells = self.nearby_cells(cell)
validNearbyCells = copy.deepcopy(NearbyCells)
for cell in NearbyCells:
if cell in self.safes:
validNearbyCells.discard(cell)
if cell in self.mines:
validNearbyCells.discard(cell)
count -= 1
# Add new sentence and infer knowledge based on added sentence
self.add_sentence(validNearbyCells, count)
通过 CS50 检查功能运行脚本后,这是输出:
:) minesweeper.py exists
:) minesweeper.py imports
:) Sentence.known_mines returns mines when conclusions possible
:) Sentence.known_mines returns no mines when no conclusion possible
:) Sentence.known_safes returns mines when conclusion possible
:) Sentence.known_safes returns no mines when no conclusion possible
:) Sentence.mark_mine marks mine when cell in sentence
:) Sentence.mark_mine does not mark mine when cell not in sentence
:) Sentence.mark_safe marks safe when cell in sentence
:) Sentence.mark_safe does not mark safe when cell not in sentence
:) MinesweeperAI.add_knowledge marks cell as a move made
:) MinesweeperAI.add_knowledge marks cell as safe
:) MinesweeperAI.add_knowledge adds sentence in middle of board
:) MinesweeperAI.add_knowledge adds sentence in corner of board
:) MinesweeperAI.add_knowledge ignores known mines when adding new sentence
:) MinesweeperAI.add_knowledge ignores known safes when adding new sentence
:) MinesweeperAI.add_knowledge infers additional safe cells
:) MinesweeperAI.add_knowledge can infer mine when given new information
:) MinesweeperAI.add_knowledge can infer multiple mines when given new information
:( MinesweeperAI.add_knowledge can infer safe cells when given new information
did not find (0, 0) in safe cells when possible to conclude safe
:) MinesweeperAI.add_knowledge combines multiple sentences to draw conclusions
救命!
我已经尝试了一切,聊天 GPT 没有一点帮助,我尝试使用 test_minesweeper.py 进行 pytest 对我的代码进行单元测试,一切看起来都很好!在所有情况下,我增加了我的知识,代码似乎运行良好。
您有多个问题需要解决。我将从最基本的开始。查看
add_sentence()
方法中的代码,其中删除空句子并查找包含已知地雷和保险箱的句子。您可以使用此代码片段进行测试。运行它并在每个 for 循环后检查 knowledge
以查看问题:
from minesweeper import Sentence
knowledge = []
safes = set()
mines = set()
s = Sentence(set(), 0)
knowledge.append(s)
s = Sentence(set(), 0)
knowledge.append(s)
s = Sentence(set([(1,1),(2,2),(3,3)]), 3)
knowledge.append(s)
s = Sentence(set([(0,0),(0,1),(0,2)]), 3)
knowledge.append(s)
s = Sentence(set([(1,0),(2,0),(3,0)]), 0)
knowledge.append(s)
s = Sentence(set([(2,1),(1,2),(1,3),(3,2)]), 0)
knowledge.append(s)
地雷/保险箱循环提示:如果您使用
continue
,则不需要 if/elif
语句。
修复该部分后,您还有更多事情需要修复。假设您在
sentences
中有这 2 个 knowledge
(按此顺序):
knowledge = []
s = Sentence(set([(0,0),(0,1),(1,0),(2,0)]), 2)
knowledge.append(s)
s = Sentence(set([(0,0),(0,1),(0,2)]), 3)
knowledge.append(s)
在
(0,0),(0,1),(0,2)
找到地雷后,您应该发现(1,0),(2,0)
中的地雷。但是,您无法通过 1 次调用 add_sentence()
来捕获它。
最后,你的最后一步(推断知识)需要努力。我在 CS50AI ED 论坛上有一篇帖子演示了这一点。使用此链接:如何调试扫雷器