私は単純な "ルンバロボット"シミュレーションを行っています。私はis_furniture_valid関数について尋ねていますが、以下のコード全体を含めました。真空が掃除しなければならない家具である緑のタイルがあります。真空は毎回次の場所をチェックします。その場所が無効な場合は、ランダムな新しい方向が選択されます。グリッドはランダムに作成され、その後、グリッドが有効かどうかがチェックされます。グリッドが解決可能かどうかを確認する
私のis_furniture_valid関数は、グリッドが解決可能であることを保証します。たとえば、真空がすべてのタイルにアクセスできないため、このグリッド(invalid grid)は無効です。
すべてのことが私の望むように機能します。しかし、is_furniture_valid関数は再帰的find_accessable_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)
それはハハ、あまりにも見て、かなり楽しいです。上/下矢印キーを使用して、シミュレーションをスピード/スローします。
一般的には、400行以上のコードを投稿するのではなく、問題に焦点を当てた[mcve]の形式で質問を表示することができます。しかし、私はルームバがそのことをするのを見て楽しむことを認めなければなりません。 :) –