扫雷 AI - 推断安全单元知识的某种边缘情况的问题

问题描述 投票:0回答:1

我正在做 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
      count
      的值向 AI 知识库添加一个新句子,以指示
      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 对我的代码进行单元测试,一切看起来都很好!在所有情况下,我增加了我的知识,代码似乎运行良好。

python artificial-intelligence cs50 inference minesweeper
1个回答
0
投票

您有多个问题需要解决。我将从最基本的开始。查看

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 论坛上有一篇帖子演示了这一点。使用此链接:如何调试扫雷器

© www.soinside.com 2019 - 2024. All rights reserved.