Python 确定网格是否可解

Python 确定网格是否可解,python,recursion,Python,Recursion,我正在做一个简单的“伦巴机器人”模拟。我已经包括了下面的全部代码,尽管我只是询问关于is_furniture_valid函数的问题。有一些绿色瓷砖是家具,必须用吸尘器清理。真空吸尘器每一步都会检查下一个位置。如果该位置无效,它将随机选择一个新方向。网格是随机生成的,然后检查它们是否有效 My is_furniture_valid函数确保网格是可解的。例如,此网格()无效,因为真空无法访问所有瓷砖 每件事都按我所希望的方式进行;但是,由于is_furniture_valid函数调用递归find_a

我正在做一个简单的“伦巴机器人”模拟。我已经包括了下面的全部代码,尽管我只是询问关于is_furniture_valid函数的问题。有一些绿色瓷砖是家具,必须用吸尘器清理。真空吸尘器每一步都会检查下一个位置。如果该位置无效,它将随机选择一个新方向。网格是随机生成的,然后检查它们是否有效

My is_furniture_valid函数确保网格是可解的。例如,此网格()无效,因为真空无法访问所有瓷砖

每件事都按我所希望的方式进行;但是,由于is_furniture_valid函数调用递归find_accessible_tiles函数,因此在达到最大递归深度之前,它仅适用于小于50 x 50的网格。我还可以如何定义一个非递归函数来确保网格是可解的

代码如下:

# -*- coding: utf-8 -*-
""" Robot Vacuum Cleaner """

import Tkinter as tk 
import random

#### METHODS ####

def scale_vector(vector, velocity):
    """
    Create unit vector. Multiply each component of unit vector
    by the magnitude of the desired vector (velocity).
    """
    try:
        x = float(vector[0])/((vector[0]**2+vector[1]**2)**.5)
        y = float(vector[1])/((vector[0]**2+vector[1]**2)**.5)
        return int(x*velocity), int(y*velocity)
    except ZeroDivisionError:
        return None, None

def get_random_velocity(velocity):
    """
    Create random direction vector.
    Scale direction vector with scale_vector method.
    """
    vx, vy = None, None
    while vx == None and vy == None:
        vector = (random.random()*random.choice([-1, 1]),
                 random.random()*random.choice([-1, 1]))
        vx, vy = scale_vector(vector, velocity)
    return vx, vy

def make_grid(furniture, dimension):
    """
    Scale actual (x, y) positions down to a grid (dictionary) 
    with keys (Nx*1, Ny*1) where Nx and Ny range from 1 to dimension[0] 
    and 1 to dimension[1] respectively.
    The keys are mapped to a boolean indicating whether that tile
    is occupied with furniture (True) or not (False).
    furniture: list with pixle locations. Each element ~ (x, y, x+dx, y+dy).
    dimension: tuple, x by y dimensions (x, y).
    returns: grid = {(1, 1): False, (2, 1): True, ...}
    """
    #dx, dy are width and height of tiles.
    dx = furniture[0][2] - furniture[0][0]
    dy = furniture[0][3] - furniture[0][1]
    w, h = dx*dimension[0], dy*dimension[1]

    grid = {}
    for y in xrange(1, dimension[1]+1):
        for x in xrange(1, dimension[0]+1):
            grid[(x, y)] = False

    y_grid = 0
    for y in xrange(dy/2, h, dy):
        y_grid += 1
        x_grid = 0
        for x in xrange(dx/2, w, dx):
            x_grid += 1
            for element in furniture:
                if x >= element[0] and x <= element[2] \
                and y >= element[1] and y <= element[3]:
                    grid[(x_grid, y_grid)] = True
                    break
    return grid

def find_accessable_tiles(grid, position, l=[]):
    """
    Finds all non-furniture locations that are accessable
    when starting at position 'position'.
    *** Mutates l ***
    Assumes position is not at a point such that grid[position] == True.
    In other words, the initial positions is valid and is not occupied.
    grid: dict mapping a Grid to booleans (tiles with/without furniture).
        i.e. grid = {(1, 1): False, (2, 1): True, ...}
    position: tuple (x, y)
    l: list
    """
    l.append(position)
    x, y = position
    if (x+1, y) in grid and (x+1, y) not in l and not grid[(x+1, y)]: #right
        find_accessable_tiles(grid, (x+1, y), l)
    if (x-1, y) in grid and (x-1, y) not in l and not grid[(x-1, y)]: #left
        find_accessable_tiles(grid, (x-1, y), l)
    if (x, y+1) in grid and (x, y+1) not in l and not grid[(x, y+1)]: #down
        find_accessable_tiles(grid, (x, y+1), l)
    if (x, y-1) in grid and (x, y-1) not in l and not grid[(x, y-1)]: #up
        find_accessable_tiles(grid, (x, y-1), l)
    return l

def is_furniture_valid(furniture, dimension):
    """
    Checks to see if all non-furniture tiles can be accessed
    when starting initially at position (1, 1).
    furniture: list of (x, y, x+dx, y+dy).
    dimension: tuple, x by y dimensions (x, y).
    """
    if len(furniture) == 0: #Rooms with no furniture are valid.
        return True
    grid = make_grid(furniture, dimension)
    #Start position is (1, 1).
    accessable_tiles = find_accessable_tiles(grid, (1, 1), [])
    #Compare accessable tiles to all non-furniture tiles.
    for element in grid:
        #if a tile doesn't have furniture AND is not accessible,
        #room is not valid.
        if not grid[element] and element not in accessable_tiles:
            return False
    return True

#### OBJECT DEFINITIONS ####

class Rumba(object):
    """
    Dealing with the actual Rumba robot on the screen - red square.
    canvas: tk.Canvas object.
    position: tuple (x, y).
    width: int width of square.
    """
    def __init__(self, canvas, position, width):       
        self.can, self.width = canvas, width  
        self.Draw(position)

    def Draw(self, position):
        x, y = position
        x1, y1 = x + self.width, y + self.width
        x2, y2 = x + self.width, y - self.width
        x3, y3 = x - self.width, y - self.width
        x4, y4 = x - self.width, y + self.width

        self.vacuum = self.can.create_polygon(x1, y1, x2, y2, x3, y3, x4, y4, fill="red")
        self.line1 = self.can.create_line(x1, y1, x2, y2, fill="black")
        self.line2 = self.can.create_line(x2, y2, x3, y3, fill="black")
        self.line3 = self.can.create_line(x3, y3, x4, y4, fill="black")
        self.line4 = self.can.create_line(x1, y1, x4, y4, fill="black")

    def update_position(self, new_position):
        x, y = new_position       
        x1, y1 = x + self.width, y + self.width
        x2, y2 = x + self.width, y - self.width
        x3, y3 = x - self.width, y - self.width
        x4, y4 = x - self.width, y + self.width

        self.can.coords(self.vacuum, x1, y1, x2, y2, x3, y3, x4, y4)
        self.can.coords(self.line1, x1, y1, x2, y2)
        self.can.coords(self.line2, x2, y2, x3, y3)
        self.can.coords(self.line3, x3, y3, x4, y4)
        self.can.coords(self.line4, x1, y1, x4, y4)

class Grid(object):
    """
    The grid that the vacuum will clean.
    canvas: tk.Canvas object.
    dimension: tuple of number of tiles (x, y).
    screen: tuple of size of canvas (w, h).
    furniture: boolean - if room will have furniture.
    """
    def __init__(self, canvas, dimension, screen, furniture=True):
        self.can, self.dimension = canvas, dimension
        self.w, self.h = screen

        self.create_tiles(furniture)

    def create_tiles(self, furniture):
        """
        Finds a valid configuration of furniture and tiles.
        Then, calls self.draw_tiles to draw configuration.
        """
        #dx, dy are width and height of tiles.
        dx, dy = self.w//self.dimension[0], self.h//self.dimension[1]

        #adjust screen size for discrepincies in forcing int divition.
        self.w, self.h = self.dimension[0]*dx, self.dimension[1]*dy
        self.can.config(width=self.w, height=self.h)

        valid = False
        while not valid:
            tiles, furniture_tiles = [], []
            for y in xrange(0, self.h, dy):
                for x in xrange(0, self.w, dx):
                    #(0, 0) is always a non-furniture tile.
                    if not furniture or random.random() <= 0.8 or (x, y) == (0, 0):                    
                        tiles.append((x, y, x+dx, y+dy))
                    else:
                        furniture_tiles.append((x, y, x+dx, y+dy))
            valid = is_furniture_valid(furniture_tiles, self.dimension)

        self.draw_tiles(tiles, furniture_tiles)

    def draw_tiles(self, tiles, furniture_tiles):
        """
        Draws a configuration of furniture and tiles.
        tiles: list of position tuples, (x, y, x+dx, y+dy).
        furniture_tiles: same as tiles but only for furniture.
        """
        self.furniture = furniture_tiles
        for element in self.furniture:
            x, y = element[0], element[1]
            dx, dy = element[2] - x, element[3] - y
            self.can.create_rectangle(x, y, x+dx, y+dy, fill="green")

        self.tiles = {}
        for element in tiles:
            x, y = element[0], element[1]
            dx, dy = element[2] - x, element[3] - y
            self.tiles[element] = [4,  
                    self.can.create_rectangle(x, y, x+dx, y+dy, fill="black")]

    def get_tile(self, position):
        x, y = position
        for element in self.tiles:
            if x >= element[0] and x <= element[2] \
            and y >= element[1] and y <= element[3]:
                return element

    def clean_tile(self, position):
        """
        Takes 4 times to clean a tile.
        Usually, vacuum will clean 2 at a time though.
        *** On some screens, 'dark grey' is lighter than 'grey'. ***
        """
        tile = self.get_tile(position)
        self.tiles[tile][0] -= 1
        if self.tiles[tile][0] == 0:
            self.can.itemconfig(self.tiles[tile][1], fill="white")
        elif self.tiles[tile][0] == 1:
            self.can.itemconfig(self.tiles[tile][1], fill="light grey")
        elif self.tiles[tile][0] == 2:
            self.can.itemconfig(self.tiles[tile][1], fill="grey")
        elif self.tiles[tile][0] == 3:
            self.can.itemconfig(self.tiles[tile][1], fill="dark grey")

    def is_grid_cleaned(self):
        for element in self.tiles.itervalues():
            if element[0] > 0:
                return False
        return True

    def get_dimension(self):
        return self.dimension
    def get_grid_size(self):
        return (self.w, self.h)
    def get_furniture(self):
        return self.furniture

class Robot(object):
    """
    Completes the numerical simulation.
    grid: a Grid object.
    canvas: a tk.Canvas object.
    v: int speed of robot.
    """
    def __init__(self, grid, canvas, v):
        self.grid = grid
        self.w, self.h = self.grid.get_grid_size()
        self.furniture = self.grid.get_furniture()

        self.v = v
        self.set_random_velocity()

        average_size = sum(self.grid.get_grid_size())/2
        average_dimension = sum(self.grid.get_dimension())/2
        self.robot_width = int((average_size/average_dimension)*0.3)
        #initial position
        self.x, self.y = self.robot_width, self.robot_width

        self.rumba = Rumba(canvas, (self.x, self.y), self.robot_width)

    def is_valid_position(self, position):
        x, y = position
        if x + self.robot_width >= self.w or x - self.robot_width <= 0:
            return False
        elif y + self.robot_width >= self.h or y - self.robot_width <= 0:
            return False
        for element in self.furniture:
            #element is of the form (x, y, x+dx, y+dy)
            if x >= element[0] and x <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False
            elif x + self.robot_width >= element[0] and x + self.robot_width <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False
            elif x - self.robot_width >= element[0] and x - self.robot_width <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False       
        return True

    def set_random_velocity(self):
        self.vx, self.vy = get_random_velocity(self.v)

    def update(self):
        """
        Checks to see if current direction is valid.
        If it is, continues, if not, picks new,
        random directions until it finds a valid direction.
        """
        x, y = self.x+self.vx, self.y+self.vy
        while (x, y) == (self.x, self.y) or not self.is_valid_position((x, y)):
            self.set_random_velocity()
            x, y = self.x+self.vx, self.y+self.vy
        self.x, self.y = x, y
        self.rumba.update_position((self.x, self.y))
        self.grid.clean_tile((self.x, self.y))

#### OBJECTS MANAGER ####

class Home(object):
    """
    Manages Simulation.
    master: tk.Tk object.
    screen: tuple (width, height).
    dimension: tuple, dimension of the grid.
    """
    def __init__(self, master, screen, dimension):
        master.title("Rumba Robot")
        master.resizable(0, 0)
        try:
            master.wm_iconbitmap("ploticon.ico")
        except:
            pass
        frame = tk.Frame(master)
        frame.pack()

        v = sum(screen)//(2*sum(dimension))

        canvas = tk.Canvas(frame, width=screen[0], height=screen[1])
        canvas.pack()
        grid = Grid(canvas, dimension, screen)
        robot = Robot(grid, canvas, v)

        master.bind('<Return>', self.restart)
        master.bind('<Up>', self.fast)
        master.bind('<Down>', self.slow)

        #initialize class variables.
        self.master, self.frame = master, frame
        self.screen, self.dimension = screen, dimension
        self.robot, self.grid = robot, grid

        #self.speed adjusts frame rate. Can be manipulated with arrow keys.
        #self.count keeps track of steps.
        self.speed, self.count = 100, 0

        self.update()

    def restart(self, callback=False):
        """ Enter/Return Key """
        self.frame.destroy()
        self.__init__(self.master, self.screen, self.dimension)

    def fast(self, callback=False):
        """ Up arrow key """
        if self.speed > 5:
            self.speed -= 5
        else:
            self.speed = 1

    def slow(self, callback=False):
        """ Down arrow key """
        self.speed += 5

    def update(self):
        self.robot.update()
        self.count += 1
        self.master.title("Rumba Robot - Steps: %d" % self.count)

        if not self.grid.is_grid_cleaned():
            self.frame.after(self.speed, self.update)
        else:
            self.frame.bell()

#### SIMULATION ####

def simulate(screen, dimension):
    """ 
    screen and dimension: both tuples.
    """
    root = tk.Tk()
    Home(root, screen, dimension)
    #Center window on screen.
    root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
    root.mainloop()

if __name__ == "__main__":
    """
    Maximum dimension ~~ between (45, 45) - (50, 50) due to
    maximum recursion depth for find_accessable_tiles function.

    *** Large dimensions may take a few seconds to generate ***

    Tip: Up/Down arrow keys will speed/slow the simulation.
    Enter/Return will restart with the same screen and dimension attributes.
    """
    screen = 1000, 700
    dimension = 30, 20

    simulate(screen, dimension)
deltas = ((-1, 0), (1, 0), (0, 1), (0, -1))

def neighbor(position, delta):
    return position[0] + delta[0], position[1] + delta[1]

def find_accessable_tiles_NEW(grid, position):
    accessable = set()
    accessable.add(position)
    tile_queue = [position]
    while tile_queue:
        current = tile_queue.pop(0)
        for position in [neighbor(current, d) for d in deltas]:
            if position in grid and not grid[position] and position not in accessable:
                accessable.add(position)
                tile_queue.append(position)

    return accessable
#-*-编码:utf-8-*-
“机器人真空吸尘器”
将Tkinter作为tk导入
随机输入
####方法####
def scale_矢量(矢量、速度):
"""
创建单位向量。乘以单位向量的每个分量
通过所需矢量的大小(速度)。
"""
尝试:
x=浮点(向量[0])/((向量[0]**2+向量[1]**2)**.5)
y=浮点(向量[1])/((向量[0]**2+向量[1]**2)**.5)
返回int(x*速度),int(y*速度)
除零误差外:
返回None,None
def获取随机速度(速度):
"""
创建随机方向向量。
使用缩放向量方法缩放方向向量。
"""
vx,vy=无,无
vx==无和vy==无:
vector=(random.random()*random.choice([-1,1]),
random.random()*random.choice([-1,1]))
vx,vy=比例向量(向量,速度)
返回vx,vy
def制作网格(家具、尺寸):
"""
将实际(x,y)位置向下缩放到栅格(字典)
使用键(Nx*1,Ny*1),其中Nx和Ny的范围从1到维度[0]
和1分别对应于维度[1]。
这些键映射到一个布尔值,指示该平铺
被家具占据(对)与否(错)。
家具:列出pixle的位置。每个元素(x,y,x+dx,y+dy)。
维度:元组,x×y维度(x,y)。
返回:grid={(1,1):False,(2,1):True,…}
"""
#dx、dy是瓷砖的宽度和高度。
dx=家具[0][2]-家具[0][0]
dy=家具[0][3]-家具[0][1]
w、 h=dx*尺寸[0],dy*尺寸[1]
网格={}
对于X范围内的y(1,尺寸[1]+1):
对于x范围内的x(1,尺寸[0]+1):
网格[(x,y)]=假
y_网格=0
对于X范围内的y(dy/2,h,dy):
y_网格+=1
x_网格=0
对于x范围内的x(dx/2,w,dx):
x_网格+=1
对于家具中的元素:

如果x>=元素[0]和x=元素[1]和y,这里是
查找可访问的\u tiles
的非递归版本

它不是递归,而是将要测试的tile推到队列的末尾,实现为名为
tile\u queue
的列表。我已将您的
l
替换为名为
accessable
的集,因为测试集成员资格比测试列表成员资格更有效;此外,集合不能有重复的成员,但我们在这里并没有真正利用该属性。因为我们不是递归,所以我们不会将
accessible
作为参数传递给
find\u accessible\u tiles
,但我们当然需要返回它

在循环中,当前磁贴的位置从队列前面弹出,计算并测试当前磁贴的4个邻居,如果邻居有效,则将其添加到可访问的
集合和
磁贴队列中

from collections import deque

def find_accessable_tiles_NEW(grid, position):
    accessable = set()
    accessable.add(position)
    tile_queue = deque()
    tile_queue.append(position)
    while tile_queue:
        current = tile_queue.popleft()
        for position in [neighbor(current, d) for d in deltas]:
            if position in grid and not grid[position] and position not in accessable:
                accessable.add(position)
                tile_queue.append(position)

    return accessable
该代码似乎工作正常,但当房间尺寸较大时,它的速度会变慢,部分原因是要测试的瓷砖数量更多,但也因为要形成的不可访问区域的范围更大。因此,您可能需要为“家具”的布局想出一个不太随机的策略,这样您就不需要进行此测试

FWIW,在开发使用随机数为随机数生成器播种的程序时,这是一个好主意,这样您就可以在相同的数据上测试各种算法的修改版本

不管怎样,代码如下:

# -*- coding: utf-8 -*-
""" Robot Vacuum Cleaner """

import Tkinter as tk 
import random

#### METHODS ####

def scale_vector(vector, velocity):
    """
    Create unit vector. Multiply each component of unit vector
    by the magnitude of the desired vector (velocity).
    """
    try:
        x = float(vector[0])/((vector[0]**2+vector[1]**2)**.5)
        y = float(vector[1])/((vector[0]**2+vector[1]**2)**.5)
        return int(x*velocity), int(y*velocity)
    except ZeroDivisionError:
        return None, None

def get_random_velocity(velocity):
    """
    Create random direction vector.
    Scale direction vector with scale_vector method.
    """
    vx, vy = None, None
    while vx == None and vy == None:
        vector = (random.random()*random.choice([-1, 1]),
                 random.random()*random.choice([-1, 1]))
        vx, vy = scale_vector(vector, velocity)
    return vx, vy

def make_grid(furniture, dimension):
    """
    Scale actual (x, y) positions down to a grid (dictionary) 
    with keys (Nx*1, Ny*1) where Nx and Ny range from 1 to dimension[0] 
    and 1 to dimension[1] respectively.
    The keys are mapped to a boolean indicating whether that tile
    is occupied with furniture (True) or not (False).
    furniture: list with pixle locations. Each element ~ (x, y, x+dx, y+dy).
    dimension: tuple, x by y dimensions (x, y).
    returns: grid = {(1, 1): False, (2, 1): True, ...}
    """
    #dx, dy are width and height of tiles.
    dx = furniture[0][2] - furniture[0][0]
    dy = furniture[0][3] - furniture[0][1]
    w, h = dx*dimension[0], dy*dimension[1]

    grid = {}
    for y in xrange(1, dimension[1]+1):
        for x in xrange(1, dimension[0]+1):
            grid[(x, y)] = False

    y_grid = 0
    for y in xrange(dy/2, h, dy):
        y_grid += 1
        x_grid = 0
        for x in xrange(dx/2, w, dx):
            x_grid += 1
            for element in furniture:
                if x >= element[0] and x <= element[2] \
                and y >= element[1] and y <= element[3]:
                    grid[(x_grid, y_grid)] = True
                    break
    return grid

def find_accessable_tiles(grid, position, l=[]):
    """
    Finds all non-furniture locations that are accessable
    when starting at position 'position'.
    *** Mutates l ***
    Assumes position is not at a point such that grid[position] == True.
    In other words, the initial positions is valid and is not occupied.
    grid: dict mapping a Grid to booleans (tiles with/without furniture).
        i.e. grid = {(1, 1): False, (2, 1): True, ...}
    position: tuple (x, y)
    l: list
    """
    l.append(position)
    x, y = position
    if (x+1, y) in grid and (x+1, y) not in l and not grid[(x+1, y)]: #right
        find_accessable_tiles(grid, (x+1, y), l)
    if (x-1, y) in grid and (x-1, y) not in l and not grid[(x-1, y)]: #left
        find_accessable_tiles(grid, (x-1, y), l)
    if (x, y+1) in grid and (x, y+1) not in l and not grid[(x, y+1)]: #down
        find_accessable_tiles(grid, (x, y+1), l)
    if (x, y-1) in grid and (x, y-1) not in l and not grid[(x, y-1)]: #up
        find_accessable_tiles(grid, (x, y-1), l)
    return l

def is_furniture_valid(furniture, dimension):
    """
    Checks to see if all non-furniture tiles can be accessed
    when starting initially at position (1, 1).
    furniture: list of (x, y, x+dx, y+dy).
    dimension: tuple, x by y dimensions (x, y).
    """
    if len(furniture) == 0: #Rooms with no furniture are valid.
        return True
    grid = make_grid(furniture, dimension)
    #Start position is (1, 1).
    accessable_tiles = find_accessable_tiles(grid, (1, 1), [])
    #Compare accessable tiles to all non-furniture tiles.
    for element in grid:
        #if a tile doesn't have furniture AND is not accessible,
        #room is not valid.
        if not grid[element] and element not in accessable_tiles:
            return False
    return True

#### OBJECT DEFINITIONS ####

class Rumba(object):
    """
    Dealing with the actual Rumba robot on the screen - red square.
    canvas: tk.Canvas object.
    position: tuple (x, y).
    width: int width of square.
    """
    def __init__(self, canvas, position, width):       
        self.can, self.width = canvas, width  
        self.Draw(position)

    def Draw(self, position):
        x, y = position
        x1, y1 = x + self.width, y + self.width
        x2, y2 = x + self.width, y - self.width
        x3, y3 = x - self.width, y - self.width
        x4, y4 = x - self.width, y + self.width

        self.vacuum = self.can.create_polygon(x1, y1, x2, y2, x3, y3, x4, y4, fill="red")
        self.line1 = self.can.create_line(x1, y1, x2, y2, fill="black")
        self.line2 = self.can.create_line(x2, y2, x3, y3, fill="black")
        self.line3 = self.can.create_line(x3, y3, x4, y4, fill="black")
        self.line4 = self.can.create_line(x1, y1, x4, y4, fill="black")

    def update_position(self, new_position):
        x, y = new_position       
        x1, y1 = x + self.width, y + self.width
        x2, y2 = x + self.width, y - self.width
        x3, y3 = x - self.width, y - self.width
        x4, y4 = x - self.width, y + self.width

        self.can.coords(self.vacuum, x1, y1, x2, y2, x3, y3, x4, y4)
        self.can.coords(self.line1, x1, y1, x2, y2)
        self.can.coords(self.line2, x2, y2, x3, y3)
        self.can.coords(self.line3, x3, y3, x4, y4)
        self.can.coords(self.line4, x1, y1, x4, y4)

class Grid(object):
    """
    The grid that the vacuum will clean.
    canvas: tk.Canvas object.
    dimension: tuple of number of tiles (x, y).
    screen: tuple of size of canvas (w, h).
    furniture: boolean - if room will have furniture.
    """
    def __init__(self, canvas, dimension, screen, furniture=True):
        self.can, self.dimension = canvas, dimension
        self.w, self.h = screen

        self.create_tiles(furniture)

    def create_tiles(self, furniture):
        """
        Finds a valid configuration of furniture and tiles.
        Then, calls self.draw_tiles to draw configuration.
        """
        #dx, dy are width and height of tiles.
        dx, dy = self.w//self.dimension[0], self.h//self.dimension[1]

        #adjust screen size for discrepincies in forcing int divition.
        self.w, self.h = self.dimension[0]*dx, self.dimension[1]*dy
        self.can.config(width=self.w, height=self.h)

        valid = False
        while not valid:
            tiles, furniture_tiles = [], []
            for y in xrange(0, self.h, dy):
                for x in xrange(0, self.w, dx):
                    #(0, 0) is always a non-furniture tile.
                    if not furniture or random.random() <= 0.8 or (x, y) == (0, 0):                    
                        tiles.append((x, y, x+dx, y+dy))
                    else:
                        furniture_tiles.append((x, y, x+dx, y+dy))
            valid = is_furniture_valid(furniture_tiles, self.dimension)

        self.draw_tiles(tiles, furniture_tiles)

    def draw_tiles(self, tiles, furniture_tiles):
        """
        Draws a configuration of furniture and tiles.
        tiles: list of position tuples, (x, y, x+dx, y+dy).
        furniture_tiles: same as tiles but only for furniture.
        """
        self.furniture = furniture_tiles
        for element in self.furniture:
            x, y = element[0], element[1]
            dx, dy = element[2] - x, element[3] - y
            self.can.create_rectangle(x, y, x+dx, y+dy, fill="green")

        self.tiles = {}
        for element in tiles:
            x, y = element[0], element[1]
            dx, dy = element[2] - x, element[3] - y
            self.tiles[element] = [4,  
                    self.can.create_rectangle(x, y, x+dx, y+dy, fill="black")]

    def get_tile(self, position):
        x, y = position
        for element in self.tiles:
            if x >= element[0] and x <= element[2] \
            and y >= element[1] and y <= element[3]:
                return element

    def clean_tile(self, position):
        """
        Takes 4 times to clean a tile.
        Usually, vacuum will clean 2 at a time though.
        *** On some screens, 'dark grey' is lighter than 'grey'. ***
        """
        tile = self.get_tile(position)
        self.tiles[tile][0] -= 1
        if self.tiles[tile][0] == 0:
            self.can.itemconfig(self.tiles[tile][1], fill="white")
        elif self.tiles[tile][0] == 1:
            self.can.itemconfig(self.tiles[tile][1], fill="light grey")
        elif self.tiles[tile][0] == 2:
            self.can.itemconfig(self.tiles[tile][1], fill="grey")
        elif self.tiles[tile][0] == 3:
            self.can.itemconfig(self.tiles[tile][1], fill="dark grey")

    def is_grid_cleaned(self):
        for element in self.tiles.itervalues():
            if element[0] > 0:
                return False
        return True

    def get_dimension(self):
        return self.dimension
    def get_grid_size(self):
        return (self.w, self.h)
    def get_furniture(self):
        return self.furniture

class Robot(object):
    """
    Completes the numerical simulation.
    grid: a Grid object.
    canvas: a tk.Canvas object.
    v: int speed of robot.
    """
    def __init__(self, grid, canvas, v):
        self.grid = grid
        self.w, self.h = self.grid.get_grid_size()
        self.furniture = self.grid.get_furniture()

        self.v = v
        self.set_random_velocity()

        average_size = sum(self.grid.get_grid_size())/2
        average_dimension = sum(self.grid.get_dimension())/2
        self.robot_width = int((average_size/average_dimension)*0.3)
        #initial position
        self.x, self.y = self.robot_width, self.robot_width

        self.rumba = Rumba(canvas, (self.x, self.y), self.robot_width)

    def is_valid_position(self, position):
        x, y = position
        if x + self.robot_width >= self.w or x - self.robot_width <= 0:
            return False
        elif y + self.robot_width >= self.h or y - self.robot_width <= 0:
            return False
        for element in self.furniture:
            #element is of the form (x, y, x+dx, y+dy)
            if x >= element[0] and x <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False
            elif x + self.robot_width >= element[0] and x + self.robot_width <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False
            elif x - self.robot_width >= element[0] and x - self.robot_width <= element[2]:
                if y >= element[1] and y <= element[3]:
                    return False
                elif y + self.robot_width >= element[1] and y + self.robot_width <= element[3]:
                    return False
                elif y - self.robot_width >= element[1] and y - self.robot_width <= element[3]:
                    return False       
        return True

    def set_random_velocity(self):
        self.vx, self.vy = get_random_velocity(self.v)

    def update(self):
        """
        Checks to see if current direction is valid.
        If it is, continues, if not, picks new,
        random directions until it finds a valid direction.
        """
        x, y = self.x+self.vx, self.y+self.vy
        while (x, y) == (self.x, self.y) or not self.is_valid_position((x, y)):
            self.set_random_velocity()
            x, y = self.x+self.vx, self.y+self.vy
        self.x, self.y = x, y
        self.rumba.update_position((self.x, self.y))
        self.grid.clean_tile((self.x, self.y))

#### OBJECTS MANAGER ####

class Home(object):
    """
    Manages Simulation.
    master: tk.Tk object.
    screen: tuple (width, height).
    dimension: tuple, dimension of the grid.
    """
    def __init__(self, master, screen, dimension):
        master.title("Rumba Robot")
        master.resizable(0, 0)
        try:
            master.wm_iconbitmap("ploticon.ico")
        except:
            pass
        frame = tk.Frame(master)
        frame.pack()

        v = sum(screen)//(2*sum(dimension))

        canvas = tk.Canvas(frame, width=screen[0], height=screen[1])
        canvas.pack()
        grid = Grid(canvas, dimension, screen)
        robot = Robot(grid, canvas, v)

        master.bind('<Return>', self.restart)
        master.bind('<Up>', self.fast)
        master.bind('<Down>', self.slow)

        #initialize class variables.
        self.master, self.frame = master, frame
        self.screen, self.dimension = screen, dimension
        self.robot, self.grid = robot, grid

        #self.speed adjusts frame rate. Can be manipulated with arrow keys.
        #self.count keeps track of steps.
        self.speed, self.count = 100, 0

        self.update()

    def restart(self, callback=False):
        """ Enter/Return Key """
        self.frame.destroy()
        self.__init__(self.master, self.screen, self.dimension)

    def fast(self, callback=False):
        """ Up arrow key """
        if self.speed > 5:
            self.speed -= 5
        else:
            self.speed = 1

    def slow(self, callback=False):
        """ Down arrow key """
        self.speed += 5

    def update(self):
        self.robot.update()
        self.count += 1
        self.master.title("Rumba Robot - Steps: %d" % self.count)

        if not self.grid.is_grid_cleaned():
            self.frame.after(self.speed, self.update)
        else:
            self.frame.bell()

#### SIMULATION ####

def simulate(screen, dimension):
    """ 
    screen and dimension: both tuples.
    """
    root = tk.Tk()
    Home(root, screen, dimension)
    #Center window on screen.
    root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
    root.mainloop()

if __name__ == "__main__":
    """
    Maximum dimension ~~ between (45, 45) - (50, 50) due to
    maximum recursion depth for find_accessable_tiles function.

    *** Large dimensions may take a few seconds to generate ***

    Tip: Up/Down arrow keys will speed/slow the simulation.
    Enter/Return will restart with the same screen and dimension attributes.
    """
    screen = 1000, 700
    dimension = 30, 20

    simulate(screen, dimension)
deltas = ((-1, 0), (1, 0), (0, 1), (0, -1))

def neighbor(position, delta):
    return position[0] + delta[0], position[1] + delta[1]

def find_accessable_tiles_NEW(grid, position):
    accessable = set()
    accessable.add(position)
    tile_queue = [position]
    while tile_queue:
        current = tile_queue.pop(0)
        for position in [neighbor(current, d) for d in deltas]:
            if position in grid and not grid[position] and position not in accessable:
                accessable.add(position)
                tile_queue.append(position)

    return accessable

出于测试目的,我发现添加一些
print
调用到
create_tiles
非常有用。我们为每个经过测试的房间布置打印一个点,这样我们就知道程序实际上在做些什么。:)

在脚本顶部,使Python 2中的
print
函数可用


正如augurar在评论中提到的,使用简单的列表作为队列不是很有效:当您从队列前面弹出一个元素时,所有其他元素都必须向下移动。的确,该操作是以C速度进行的,因此比使用Python循环要快,但是避免这种情况仍然是一个好主意,尤其是当队列可能很大时,就像这里一样

幸运的是,标准Python库在
collections
模块中提供了一个名为deque的队列对象。下面是使用
deque
查找可访问的\u tiles\u NEW

from collections import deque

def find_accessable_tiles_NEW(grid, position):
    accessable = set()
    accessable.add(position)
    tile_queue = deque()
    tile_queue.append(position)
    while tile_queue:
        current = tile_queue.popleft()
        for position in [neighbor(current, d) for d in deltas]:
            if position in grid and not grid[position] and position not in accessable:
                accessable.add(position)
                tile_queue.append(position)

    return accessable

我只是做了一个速度测试比较;
deque
版本在我最初的
列表
版本的大约2/3时间内找到了一个有效的网格,该版本具有相同的随机数种子。当然,实际的速度差异会因随机数种子和房间尺寸的不同而有所不同,但是
deque
版本的速度会一直更快。

通常,如果你能以一种专注于你的问题的形式提出你的问题,而不是发布400多行代码,那么最好是这样。但我必须承认,看着Roomba做自己的事情是很有趣的删除列表的第一个元素是一个
O(n)
操作。改用。@augurar:说得好;我猜