python之探索迷宮


  探索迷宮

    探討一個與蓬勃發展的機器人領域相關的問題:走出迷宮。如果你有一個Roomba掃地機器人,或許

  能利用學到的知識對它進行重新編程。我們要解決的問題是幫助小烏龜走出虛擬的迷宮。迷宮問題源自忒修

  斯大戰牛頭怪的古希臘神話傳說。相傳,在迷宮里殺死牛頭怪之后,忒修斯用一個線團找到了迷宮的出口。

  假設小烏龜被放置在迷宮里的某個位置,我們要做的是幫助它爬出迷宮,如圖所示

 

 

    為簡單起見,假設迷宮被分成許多格,每一格要么是空的,要么被牆堵上。小烏龜只能沿着空的

  格子爬行,如果遇牆,就必須轉變方向。它需要如下的系統化過程來找到出路。

    (1)從起始位置開始,壽險向北移動一格,然后在新的位置再遞歸地重復本過程。

    (2)如果第一步往北行不通,就嘗試向南移動一格,然后在遞歸重復本過程。

    (3)如果向南也行不通,就嘗試向西移動一格,然后遞歸地重復本過程。

    (4)如果向北、向南、向西都不行,就嘗試向東移動一格,然后遞歸地重復本過程。

    (5)如果四個方向都不行,就意味着沒有出路。

    整個過程看上去非常簡單,但是有許多細節需要討論。假設遞歸過程的第一步是向北移動一格。

  根據上述過程,下一步也是向北移動一格。但是,如果北面有牆,必須根據遞歸過程的第二步向南移

  動一格。不行的是向南移動一格之后回到起點。如果繼續執行該遞歸過程,就會又向北移動一格,然

  后又退回來,從而陷入無限循環中。所以,必須通過一格策略來記住到過的地方,本例假設小烏龜一

  邊爬,一邊丟面包屑。如果往某個方向走一格之后發現有面包屑,就知道應該退回去,然后嘗試遞歸

  過程的下一步。查看這個算法的代碼時會發現,退回去就是從遞歸函數調用中返回。

    和考察其他遞歸算法時一樣,讓我們來看看上述算法的基本情況,其中一些可以根據之前的描述

  猜到。這個算法需要考慮以下4種基本情況。

    (1)小烏龜遇到牆。由於格子被牆堵上,因此無法再繼續探索。

    (2)小烏龜遇到了已經走過的格子。在這種情況下,我們不希望它繼續探索,不然會陷入循環。

    (3)小烏龜找到了出口。

    (4)四個方向都行不通。

    為了使程序運行起來,需要通過一種方式標識迷宮。我們使用turtle模塊來繪制和探索迷宮,以增

  加趣味性。迷宮對象提供下列方法,可用編寫搜索算法。

    --init--讀入一個代表迷宮數據文件,初始化迷宮的內部表示,並且找到小烏龜的起始位置。

    drawMaze在屏幕上的一個窗口中繪制迷宮。

    updatePosition更新迷宮的內部表示,並且修改小烏龜在迷宮中的位置。

    isExit檢查小烏龜的當前位置是否為迷宮的出口。

    除此之外,Maze類還重載了索引運算符[ ],以便算法訪問任一格的狀態。

    以下代買清單展示了搜索函數searchFrom的代碼。該函數接受3個參數:迷宮對象、起始行,以及

  起始列。由於該函數的每一次遞歸調用在邏輯上都是重新開始搜索的,因此定義接受3個參數非常重要。

def searchFrom(maze, startRow, startColumn):
    # 從初始位置開始嘗試四個方向,直到找到出路。
    # 1. 遇到障礙
    if maze[startRow][startColumn] == OBSTACLE:
        return False
    # 2. 發現已經探索過的路徑或死胡同
    if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn]== DEAD_END:
        return False
    # 3. 發現出口
    if maze.isExit(startRow, startColumn):
        maze.updatePosition(startRow, startColumn, PART_OF_PATH)#顯示出口位置,注釋則不顯示此點
        return True
    maze.updatePosition(startRow, startColumn, TRIED)#更新迷宮狀態、設置海龜初始位置並開始嘗試
    # 4. 依次嘗試每個方向
    found = searchFrom(maze, startRow - 1, startColumn) or \
            searchFrom(maze, startRow + 1, startColumn) or \
            searchFrom(maze, startRow, startColumn - 1) or \
            searchFrom(maze, startRow, startColumn + 1)
    if found:                                                    #找到出口
        maze.updatePosition(startRow, startColumn, PART_OF_PATH)#返回其中一條正確路徑
    else:                                                        #4個方向均是死胡同
        maze.updatePosition(startRow, startColumn, DEAD_END)
    return found

    該函數做的第一件事就是調用updatePosition(第2行)。這樣做是為了對算法進行可視化,以便我們

  看到小烏龜如何在迷宮中尋找出口。接着,該函數檢查前3中基本情況:是否遇到了牆(第5行)是否遇到

  了已經走過的格子(第8行)是否找到了出口(第11行)如果沒有一種情況符合,則繼續遞歸搜索。

    遞歸搜素調用了4個searchFrom。很難預測一共會進行多少個遞歸調用,這是因為它們都是用布爾運

  算符or連接起來的。如果第一次調用searchFrom后返回Ture,那么就不必進行后續的調用。可以這樣理解:

  向北移動一格是離開迷宮的路徑的一步。如果向北沒有能夠走出迷宮,那么嘗試下一個遞歸調用,即向南移

  動。如果向南失敗了,就嘗試向西,最后向東。如果所有的遞歸調用都失敗了,就說明遇到了死胡同。請下

  載或自己輸入代碼,改變4個遞歸調用的順序,看看結果如何。

    Maze類的方法定義,--init--方法只接受一個參數,即文件名。該文本文件包含迷宮的信息,其中+代表

  牆,空格代表空格子,S代表起始位置。迷宮內部表示是一個列表,其元素也是列表。實例變量mazelist的每

  一行是一個列表,其中每一格包含一個字符。其內部如下。

 

   

    drawMaze方法使用以上內部表示在屏幕上繪制初始迷宮。

    updatePosition方法使用相同的內部表示檢查小烏龜是否遇到牆。同時,它會更改內部表示,使用.和-來分

  別表示小烏龜遇到了走過的格子和死胡同。此外,updatePosition方法還使用輔助函數moveTurtle和dropBread

  crumb來更新屏幕上的信息。

    isExit方法檢查小烏龜的當前位置是否為出口,條件是小烏龜已經爬到迷宮邊緣:第0行、第0列、最后一行

  或者最后一列。

import turtle

# 迷宮類
class Maze(object):
    # 讀取迷宮數據,初始化迷宮內部,並找到海龜初始位置。
    def __init__(self, mazeFileName):
        rowsInMaze = 0                            #初始化迷宮行數
        columnsInMaze = 0                         #初始化迷宮列數
        self.mazelist = []                        #初始化迷宮列表
        mazeFile = open(mazeFileName, 'r')        #讀取迷宮文件
        for line in mazeFile:                    #按行讀取
            rowList = []                         #初始化行列表
            col = 0                             #初始化列
            for ch in line[:-1]:                #這樣會丟失最后一列

                rowList.append(ch)                #添加到行列表
                if ch == 'S':                    #S為烏龜初始位置,即迷宮起點
                    self.startRow = rowsInMaze    #烏龜初始行
                    self.startCol = col         #烏龜初始列
                col = col + 1                     #下一列
            rowsInMaze = rowsInMaze + 1         #下一行
            self.mazelist.append(rowList)        #行列表添加到迷宮列表
            columnsInMaze = len(rowList)         #獲取迷宮總列數
        print(self.mazelist)
        self.rowsInMaze = rowsInMaze             #設置迷宮總行數
        self.columnsInMaze = columnsInMaze        #設置迷宮總列數
        self.xTranslate = -columnsInMaze/2         #設置迷宮左上角的初始x坐標
        self.yTranslate = rowsInMaze/2             #設置迷宮左上角的初始y坐標
        self.t = turtle.Turtle()                #創建一個海龜對象
        #給當前指示點設置樣式(類似鼠標箭頭),海龜形狀為參數指定的形狀名,指定的形狀名應存在於TurtleScreen的shape字典中。多邊形的形狀初始時有以下幾種:"arrow", "turtle", "circle", "square", "triangle", "classic"。
        self.t.shape('turtle')
        self.wn = turtle.Screen()                #創建一個能在里面作圖的窗口
        # 設置世界坐標系,原點在迷宮正中心。參數依次為畫布左下角x軸坐標、左下角y軸坐標、右上角x軸坐標、右上角y軸坐標
        self.wn.setworldcoordinates(-(columnsInMaze -1)/2 - 0.5, -(rowsInMaze - 1)/2 - 0.5, (columnsInMaze - 1)/2 + 0.5, (rowsInMaze - 1)/2 + 0.5)

    # 在屏幕上繪制迷宮
    def drawMaze(self):
        self.t.speed(20)                        #繪圖速度
        for y in range(self.rowsInMaze):        #按單元格依次循環迷宮
            for x in range(self.columnsInMaze):
                if self.mazelist[y][x] == OBSTACLE:    #如果迷宮列表的該位置為障礙物,則畫方塊
                    self.drawCenteredBox(x + self.xTranslate, -y + self.yTranslate, 'orange')

    # 畫方塊
    def drawCenteredBox(self, x, y, color):
        self.t.up()                                #畫筆抬起
        self.t.goto(x - 0.5, y - 0.5)            #前往參數位置,此處0.5偏移量的作用是使烏龜的探索路線在單元格的正中心位置
        self.t.color(color)                        #方塊邊框為橙色
        self.t.fillcolor('green')                #方塊內填充綠色
        self.t.setheading(90)                    #設置海龜的朝向,標准模式:0 - 東,90 - 北,180 - 西,270 - 南。logo模式:0 - 北,90 - 東,180 - 南,270 - 西。
        self.t.down()                            #畫筆落下
        self.t.begin_fill()                        #開始填充
        for i in range(4):                        #畫方塊邊框
            self.t.forward(1)                    #前進1個單位
            self.t.right(90)                    #右轉90度
        self.t.end_fill()                        #結束填充

    # 移動海龜
    def moveTurtle(self, x, y):
        self.t.up()                                #畫筆抬起
        # setheading()設置海龜朝向,towards()從海龜位置到由(x, y),矢量或另一海龜位置連線的夾角。此數值依賴於海龜初始朝向,由"standard"、"world"或"logo" 模式設置所決定。
        self.t.setheading(self.t.towards(x + self.xTranslate, -y + self.yTranslate))
        self.t.goto(x + self.xTranslate, -y + self.yTranslate)    #前往目標位置

    # 畫路徑圓點
    def dropBreadcrumb(self, color):
        self.t.dot(color)                        #dot(size=None, color)畫路徑圓點

    # 用以更新迷宮內的狀態及在窗口中改變海龜位置,行列參數為烏龜的初始坐標。
    def updatePosition(self, row, col, val):
        self.mazelist[row][col] = val             #設置該標記狀態為當前單元格的值
        self.moveTurtle(col, row)                #移動海龜
        if val == PART_OF_PATH:                 #其中一條成功路徑的圓點的顏色
            color = 'green'
        elif val == TRIED:                        #嘗試用的圓點的顏色
            color = 'black'
        elif val == DEAD_END:                    #死胡同用的圓點的顏色
            color = 'red'
        self.dropBreadcrumb(color)                #畫路徑圓點並上色

    # 用以判斷當前位置是否為出口。
    def isExit(self, row, col):
        return (row == 0 or row == self.rowsInMaze - 1 or col == 0 or col == self.columnsInMaze - 1) #根據海龜位置是否在迷宮的4個邊線位置判斷

    # 返回鍵對應的值,影響searchFrom()中maze[startRow][startColumn]值的獲取
    def __getitem__(self, key):
        return self.mazelist[key]

if __name__ == '__main__':
    PART_OF_PATH = 'O'            #部分路徑
    TRIED = '.'                    #嘗試
    OBSTACLE = '+'                #障礙
    DEAD_END = '-'                #死胡同
    myMaze = Maze('maze.txt')#實例化迷宮類,maze文件是使用“+”字符作為牆壁圍出空心正方形空間,並用字母“S”來表示起始位置的迷宮文本文件。
    myMaze.drawMaze()            #在屏幕上繪制迷宮。
    searchFrom(myMaze, myMaze.startRow, myMaze.startCol)    #探索迷宮

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM