World¶
The aitk.robots simulator world. All simulations begin with a world.
beacon
property
writable
¶
Get the beacon.
__init__(self, width=500, height=250, seed=0, scale=3.0, boundary_wall=True, boundary_wall_color='purple', boundary_wall_width=1, ground_color='green', ground_image_filename=None, filename=None, quiet=False, smell_cell_size=None, **kwargs)
special
¶
The constructor for the aitk.robots simulator world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
width of world in pixels |
500 |
height |
int |
height of world in pixels |
250 |
seed |
int |
random number generator seed |
0 |
scale |
float |
value to use in drawing the world |
3.0 |
boundary_wall |
bool |
draw a boundary around the world? |
True |
boundary_wall_color |
str |
color name of boundary wall |
'purple' |
boundary_wall_width |
int |
width of boundary wall |
1 |
ground_color |
str |
color name |
'green' |
ground_image_filename |
str |
image file used for backgound |
None |
filename |
str |
name of json world file |
None |
quiet |
bool |
if True, don't print any messages |
False |
smell_cell_size |
int |
is set automatically based on world size |
None |
You can also pass any valid item from the world config settings.
from aitk.robots import World
world = World(200, 200)
Source code in robots/world.py
def __init__(
self,
width=500, # type: int
height=250, # type: int
seed=0, # type: int
scale=3.0, # type: float
boundary_wall=True, # type: bool
boundary_wall_color="purple", # type: str
boundary_wall_width=1, # type: int
ground_color="green", # type: str
ground_image_filename=None, # type: str
filename=None, # type: str
quiet=False, # type: bool
smell_cell_size=None, # type: int
**kwargs
):
"""
The constructor for the aitk.robots simulator world.
Args:
width (int): width of world in pixels
height (int): height of world in pixels
seed (int): random number generator seed
scale (float): value to use in drawing the world
boundary_wall (bool): draw a boundary around the world?
boundary_wall_color (str): color name of boundary wall
boundary_wall_width (int): width of boundary wall
ground_color (str): color name
ground_image_filename (str): image file used for backgound
filename (str): name of json world file
quiet (bool): if True, don't print any messages
smell_cell_size (int): is set automatically based on world size
You can also pass any valid item from the world config settings.
```python
from aitk.robots import World
world = World(200, 200)
```
"""
# For faster-than real time display with synchronous backends,
# keep processing time below this percentage of throttle_period:
config = {
"width": width,
"height": height,
"seed": seed,
"scale": scale,
"boundary_wall": boundary_wall,
"boundary_wall_width": boundary_wall_width,
"boundary_wall_color": boundary_wall_color,
"ground_color": ground_color,
"quiet": quiet,
"smell_cell_size": smell_cell_size,
}
if filename is not None:
config["filename"] = filename
if ground_image_filename is not None:
config["ground_image_filename"] = ground_image_filename
config["walls"] = kwargs.pop("walls", [])
config["bulbs"] = kwargs.pop("bulbs", [])
config["beacon"] = kwargs.pop("beacon", None)
config["robots"] = kwargs.pop("robots", [])
config["food"] = kwargs.pop("food", [])
if len(kwargs) != 0:
raise AttributeError(
"unknown arguments for World: %s" % list(kwargs.keys())
)
self.time_step = 0.10 # seconds
self._events = []
self._show_throttle_percentage = 0.40
self._time_decimal_places = 1
self._throttle_period = 0.1
self._time_of_last_call = 0
self._step_display = "tqdm"
self.debug = False
self._watchers = []
self._robots = []
self._bulbs = []
self._beacon = None
self._backend = None
self._recording = False
self.config = config.copy()
self._initialize() # default values
self.robots = List(self._robots)
self.bulbs = List(self._bulbs)
self.reset() # from config
add_beacon(self, x, y, z=0)
¶
Add a beacon to the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
int |
the x coordinate |
required |
y |
int |
the y coordinate |
required |
z |
int |
optional, the z coordinate |
0 |
Source code in robots/world.py
def add_beacon(self, x, y, z=0):
"""
Add a beacon to the world.
Args:
x (int): the x coordinate
y (int): the y coordinate
z (int): optional, the z coordinate
"""
self._add_beacon(x, y, z)
self.update() # request draw
self.save()
add_bulb(self, color, x, y, z, brightness, name=None)
¶
Add a bulb to the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
color |
str |
the color of the light |
required |
x |
int |
the x coordinate |
required |
y |
int |
the y coordinate |
required |
z |
int |
the z coordinate |
required |
brightness |
float |
the distance in pixels of 1 standard deviation |
required |
name |
str |
the name of the bulb |
None |
Source code in robots/world.py
def add_bulb(self, color, x, y, z, brightness, name=None):
"""
Add a bulb to the world.
Args:
color (str): the color of the light
x (int): the x coordinate
y (int): the y coordinate
z (int): the z coordinate
brightness (float): the distance in pixels of 1 standard deviation
name (str): the name of the bulb
"""
self._add_bulb(color, x, y, z, brightness, name)
self.update() # request draw
self.save()
add_food(self, x, y, standard_deviation, state='on')
¶
Add food at x, y with a brightness of standard_deviation (in pixels).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
int |
the x coordinate |
required |
y |
int |
the y coordinate |
required |
standard_deviation |
float |
distance int pixels of 1 standard deviation |
required |
Source code in robots/world.py
def add_food(self, x, y, standard_deviation, state="on"):
"""
Add food at x, y with a brightness of standard_deviation
(in pixels).
Args:
x (int): the x coordinate
y (int): the y coordinate
standard_deviation (float): distance int pixels
of 1 standard deviation
"""
self._add_food(x, y, standard_deviation, state)
self._grid.need_update = True
self.update() # request draw
self.save()
add_robot(self, robot)
¶
Add a new robot to the world. If the robot's position is 0,0 then place it randomly in the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
robot |
Robot |
the robot to add |
required |
Source code in robots/world.py
def add_robot(self, robot):
"""
Add a new robot to the world. If the robot's position is 0,0 then
place it randomly in the world.
Args:
robot (Robot): the robot to add
"""
if robot not in self._robots:
if robot.x == 0 and robot.y == 0:
robot.x, robot.y, robot.a = self._find_random_pose(robot)
self._robots.append(robot)
robot.world = self
# Bounding lines form a wall:
if len(robot._bounding_lines) == 0:
print("WARNING: adding a robot with no body")
wall = Wall(robot.color, robot, *robot._bounding_lines, wtype="robot")
self._walls.append(wall)
self._complexity = self._compute_complexity()
self.update()
self.save()
else:
raise Exception("Can't add the same robot to a world more than once.")
add_wall(self, color, x1, y1, x2, y2, box=True, wtype='wall')
¶
Add a wall as a line or as a box.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
color |
str |
the color of the wall |
required |
x1 |
int |
the upper left x coordinate |
required |
y1 |
int |
the upper left y coordinate |
required |
x2 |
int |
the lower right x coordinate |
required |
y2 |
int |
the lower right y coordinate |
required |
box |
bool |
whether this is a box (True) or line (False) |
True |
wtype |
str |
"wall", "robot", or "boundary" |
'wall' |
Source code in robots/world.py
def add_wall(self, color, x1, y1, x2, y2, box=True, wtype="wall"):
"""
Add a wall as a line or as a box.
Args:
color (str): the color of the wall
x1 (int): the upper left x coordinate
y1 (int): the upper left y coordinate
x2 (int): the lower right x coordinate
y2 (int): the lower right y coordinate
box (bool): whether this is a box (True) or line (False)
wtype (str): "wall", "robot", or "boundary"
"""
p1 = Point(x1, y1)
p3 = Point(x2, y2)
if box:
## Pairs of points make Line:
p2 = Point(x2, y1)
p4 = Point(x1, y2)
wall = Wall(
Color(color),
None,
Line(p1, p2),
Line(p2, p3),
Line(p3, p4),
Line(p4, p1),
wtype=wtype,
)
else:
wall = Wall(
Color(color), None, Line(p1, p3), wtype="wall"
)
self._walls.append(wall)
self._complexity = self._compute_complexity()
self._grid.update_wall(wall)
self.update() # request draw
clear_watchers(self)
¶
Clear all of the watchers.
Source code in robots/world.py
def clear_watchers(self):
"""
Clear all of the watchers.
"""
self._watchers[:] = []
del_robot(self, robot)
¶
Remove a robot from the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
robot |
Robot |
the robot to remove |
required |
Source code in robots/world.py
def del_robot(self, robot):
"""
Remove a robot from the world.
Args:
robot (Robot): the robot to remove
"""
if not isinstance(robot, Robot):
# Then look it up by index/name/type:
robot = self.robots[robot]
for wall in list(self._walls):
if wall.robot is robot:
self._walls.remove(wall)
if robot in self._robots:
robot.world = None
self._robots.remove(robot)
self._complexity = self._compute_complexity()
self.update() # request draw
display(self, index=None, size=100)
¶
Display a picture of the world, or of a robot.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
index |
str or int |
index of robot |
None |
size |
int |
size of robot picture |
100 |
Source code in robots/world.py
def display(self, index=None, size=100):
"""
Display a picture of the world, or of a robot.
Args:
index (str or int, optional): index of robot
size (int, optional): size of robot picture
"""
picture = self.get_image(index=index, size=size)
display(picture)
draw(self)
¶
Force a redraw of the world.
Source code in robots/world.py
def draw(self):
"""
Force a redraw of the world.
"""
if self._backend is None:
return
self._grid.update(self._food)
with self._backend:
self._backend.clear()
self._backend.noStroke()
if self._ground_image is not None:
self._backend.draw_image(self._ground_image, 0, 0)
else:
self._backend.set_fill(self.ground_color)
self._backend.draw_rect(0, 0, self.width, self.height)
if len(self._food) > 0:
smell = self._grid.get_image()
smell = smell.resize((int(self.width * self.scale), int(self.height * self.scale)))
self._backend.image.paste(smell, (0, 0), smell)
## Draw all bulbs in world:
maxRange = max(self.width, self.height)
for bulb in self._get_light_sources(all=True):
if bulb.state == "on":
color = bulb.color
self._backend.line_width = 0
self._backend.noStroke()
x, y = bulb.get_position(world=True)
# Cast rays once:
all_hits = []
for ray in range(bulb.rays):
angle = ray/bulb.rays * TWO_PI
hits = cast_ray(self, bulb.robot, x, y, -angle + PI_OVER_2, maxRange)
all_hits.append(hits)
# Now draw the rings:
for i in range(bulb.draw_rings):
radius = (bulb.draw_rings - i) * 2
ray_length = bulb.brightness/30 * radius
color.alpha = (i + 1)/bulb.draw_rings * 255
self._backend.set_fill_style(color)
points = []
for ray in range(bulb.rays):
hits = all_hits[ray]
ray_length = bulb.brightness/30 * radius
if len(hits) > 0:
if hits[-1].distance < ray_length:
ray_length = hits[-1].distance
angle = ray/bulb.rays * TWO_PI
x2, y2 = rotate_around(x, y, ray_length, angle)
points.append((x2, y2))
self._backend.draw_polygon(points)
self._backend.line_width = 1
## Draw walls:
for wall in self._walls:
if wall.wtype == "wall":
c = wall.color
if len(wall.lines) == 1:
self._backend.strokeStyle(c, 5)
self._backend.draw_line(
wall.lines[0].p1.x,
wall.lines[0].p1.y,
wall.lines[0].p2.x,
wall.lines[0].p2.y,
)
else:
self._backend.set_fill(c)
self._backend.noStroke()
self._backend.beginShape()
for line in wall.lines:
self._backend.vertex(line.p1.x, line.p1.y)
self._backend.vertex(line.p2.x, line.p2.y)
self._backend.endShape()
self._backend.lineWidth(1)
self._backend.noStroke()
elif wall.wtype == "boundary":
c = wall.color
self._backend.strokeStyle(c, 3)
self._backend.draw_line(
wall.lines[0].p1.x,
wall.lines[0].p1.y,
wall.lines[0].p2.x,
wall.lines[0].p2.y,
)
self._backend.lineWidth(1)
self._backend.noStroke()
## Draw robots:
for robot in self._robots:
robot.draw(self._backend)
# Draw the beacon:
if self.beacon is not None:
if self.beacon.state == "on":
x, y = self.beacon.get_position(world=True)
self._backend.set_fill_style(Color("blue"))
self._backend.draw_circle(x, y, 5)
text = format_time(self.time)
self._backend.draw_status(text)
for items in self._draw_list:
if len(items) == 1:
command = items[0]
args = tuple()
kwargs = {}
elif len(items) == 2:
command = items[0]
args = items[1]
kwargs = {}
elif len(items) == 3:
command = items[0]
args = items[1]
kwargs = items[2]
self._backend.do_command(command, *args, **kwargs)
self._draw_watchers()
from_json(self, config)
¶
Load a json config file.
Source code in robots/world.py
def from_json(self, config):
"""
Load a json config file.
"""
self.config = config
if "quiet" in config:
self.quiet = config["quiet"]
seed = config.get("seed", 0)
self.set_seed(seed)
if "filename" in config:
self.filename = config["filename"]
if "width" in config:
self.width = config["width"]
if "height" in config:
self.height = config["height"]
if "scale" in config:
self.scale = config["scale"]
if "smell_cell_size" in config:
self.smell_cell_size = config["smell_cell_size"]
if self.smell_cell_size is None:
self.smell_cell_size = max((self.width * self.height) // 20000, 1)
if "boundary_wall" in config:
self.boundary_wall = config["boundary_wall"]
if "boundary_wall_color" in config:
self.boundary_wall_color = Color(config["boundary_wall_color"])
if "boundary_wall_width" in config:
self.boundary_wall_width = config["boundary_wall_width"]
if "ground_color" in config:
self.ground_color = Color(config["ground_color"])
if "ground_image_filename" in config:
self.set_ground_image(config["ground_image_filename"], show=False)
# Now, we create the grid:
self._grid = Grid(self.width, self.height, self.smell_cell_size)
self._grid.update_walls(self._walls)
# Add walls:
self._add_boundary_walls()
for wall in config.get("walls", []):
# Walls are either "boxes" with 4 lines, or a single line:
if "wtype" not in wall or wall["wtype"] == "box":
self.add_wall(
wall["color"],
wall["p1"]["x"],
wall["p1"]["y"],
wall["p2"]["x"],
wall["p2"]["y"],
box=True,
)
elif wall["wtype"] == "line":
self.add_wall(
wall["color"],
wall["p1"]["x"],
wall["p1"]["y"],
wall["p2"]["x"],
wall["p2"]["y"],
box=False,
)
beacon = config.get("beacon", None)
if beacon is not None:
# beacon is {x, y, z}
self._add_beacon(**beacon)
for bulb in config.get("bulbs", []):
# bulbs are {x, y, z, color, brightness}
self._add_bulb(**bulb)
for food in config.get("food", []):
# food x, y, standard_deviation, state
self._add_food(**food)
## Create robot, and add to world:
for i, robotConfig in enumerate(self.config.get("robots", [])):
# FIXME: raise if lengths don't match
if i < len(self._robots): # already a robot; let's reuse it:
robot = self._robots[i]
robot._initialize()
robot.from_json(robotConfig)
else:
robot = Robot(**robotConfig)
self.add_robot(robot)
# Create the backend if first time:
if self._backend is None:
self._backend = make_backend(self.width, self.height, self.scale)
# Update the backend if it already existed, but differs in config
self._backend.update_dimensions(self.width, self.height, self.scale)
get_bulb(self, item)
¶
Get the bulb by name or index. Equivalent to world.bulbs[item]
Parameters:
Name | Type | Description | Default |
---|---|---|---|
item |
int or string |
index or name of bulb |
required |
Source code in robots/world.py
def get_bulb(self, item):
"""
Get the bulb by name or index. Equivalent to
world.bulbs[item]
Args:
item (int or string): index or name of bulb
"""
return self.bulbs[item]
get_food(self)
¶
Return a list of the food in the world in (x, y, standard_deviation, state) form.
Source code in robots/world.py
def get_food(self):
"""
Return a list of the food in the world in
(x, y, standard_deviation, state) form.
"""
return self._food
get_ground_color_at(self, x, y, radius=1)
¶
Get the pixel(s) of the ground image at position (x,y). Requires a ground image to have already been set.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
int |
the x coordinate |
required |
y |
int |
the y coordinate |
required |
radius |
int |
size of area |
1 |
Source code in robots/world.py
def get_ground_color_at(self, x, y, radius=1):
"""
Get the pixel(s) of the ground image at position (x,y). Requires
a ground image to have already been set.
Args:
x (int): the x coordinate
y (int): the y coordinate
radius (int): size of area
"""
if self._ground_image:
results = []
for i in range(-radius, radius + 1, 1):
for j in range(-radius, radius + 1, 1):
results.append(
self._ground_image_pixels[
((x + i) * self.scale, (y + j) * self.scale)
]
)
return results
get_image(self, index=None, size=100, copy=True)
¶
Get a PIL image (copy) of the world, or of a robot.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
index |
str or int |
index of robot |
None |
size |
int |
size of robot picture |
100 |
copy |
bool |
get a copy of image if True, otherwise return a reference to world image |
True |
Source code in robots/world.py
def get_image(self, index=None, size=100, copy=True):
"""
Get a PIL image (copy) of the world, or of a robot.
Args:
index (str or int, optional): index of robot
size (int, optional): size of robot picture
copy (bool, optional): get a copy of image if True,
otherwise return a reference to world image
"""
try:
picture = self._backend.get_image(self.time)
except RuntimeError:
raise Exception("Backend is not ready yet; try again")
if index is not None:
robot = self.robots[index]
if robot:
start_x = round(max(robot.x * self.scale - size / 2, 0))
start_y = round(max(robot.y * self.scale - size / 2, 0))
rectangle = (
start_x,
start_y,
min(start_x + size, self.width * self.scale),
min(start_y + size, self.height * self.scale),
)
picture = picture.crop(rectangle)
return picture
else:
if copy:
picture = picture.copy()
return picture
get_robot(self, item)
¶
Get the robot by name or index. Equivalent to world.robots[item]
Parameters:
Name | Type | Description | Default |
---|---|---|---|
item |
int or string |
index or name of robot |
required |
Source code in robots/world.py
def get_robot(self, item):
"""
Get the robot by name or index. Equivalent to
world.robots[item]
Args:
item (int or string): index or name of robot
"""
return self.robots[item]
get_widget(self, width=None, height=None)
¶
Returns the IPython simulation widget.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
the width of the world |
None |
height |
int |
the height of the world |
None |
Source code in robots/world.py
def get_widget(self, width=None, height=None):
"""
Returns the IPython simulation widget.
Args:
width (int): the width of the world
height (int): the height of the world
"""
if isinstance(width, int):
width = "%spx" % width
if isinstance(height, int):
height = "%spx" % height
self._step_display = "notebook"
self.update()
return self._backend.get_widget(width=width, height=height)
paste_ground_image(self, image, x, y)
¶
Paste an image onto the ground image. Requires a ground image to have already been set.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
image |
Image |
a Python Image Library image |
required |
x |
int |
the x coordinate of upper lefthand corner |
required |
y |
int |
the y coordinate of upper lefthand corner |
required |
Source code in robots/world.py
def paste_ground_image(self, image, x, y):
"""
Paste an image onto the ground image. Requires
a ground image to have already been set.
Args:
image (Image): a Python Image Library image
x (int): the x coordinate of upper lefthand corner
y (int): the y coordinate of upper lefthand corner
"""
if self._ground_image:
self._ground_image.paste(image, (x, y))
record(self)
¶
Create and return a world recorder.
Source code in robots/world.py
def record(self):
"""
Create and return a world recorder.
"""
from .watchers import Recorder
recorder = Recorder(self)
self._watchers.append(recorder)
self._recording = True
return recorder
reset(self)
¶
Reloads the config from initialization, or from last save.
Source code in robots/world.py
def reset(self):
"""
Reloads the config from initialization, or from
last save.
"""
self.stop()
self._initialize()
self._reset_watchers()
self._food = []
self._events = []
self._grid = None
self.from_json(self.config)
self.time = 0.0
for robot in self._robots:
robot.reset()
# Re-add the robot's boundaries:
wall = Wall(robot.color, robot, *robot._bounding_lines, wtype="robot")
self._walls.append(wall)
self._stop = False # should stop?
self.status = "stopped"
self.update(show=False) # twice to allow robots to see each other
self.update(show=False)
self.draw() # force
run(self, function=None, time_step=None, show=True, real_time=True, show_progress=True, quiet=False, background=False, interrupt=False)
¶
Run the simulator until one of the control functions returns True or Control+C is pressed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
function |
Function |
either a single function that takes the world, or a list of functions (or None) that each take a robot. If any function returns True, then simulation will stop. |
None |
time_step |
float |
time unit to advance the world |
None |
show |
bool |
update the watchers |
True |
real_time |
bool |
run simulation in real time |
True |
show_progress |
bool |
show progress bar |
True |
quiet |
bool |
if True, do not show the status message when completed |
False |
background |
bool |
if True, run in the background. |
False |
interrupt |
bool |
if True, raise KeyboardInterrupt, otherwise just stop and continue |
False |
Source code in robots/world.py
def run(
self,
function=None,
time_step=None,
show=True,
real_time=True,
show_progress=True,
quiet=False,
background=False,
interrupt=False,
):
"""
Run the simulator until one of the control functions returns True
or Control+C is pressed.
Args:
function (Function): either a single function that takes the
world, or a list of functions (or None) that each take
a robot. If any function returns True, then simulation will
stop.
time_step (float): time unit to advance the world
show (bool): update the watchers
real_time (bool): run simulation in real time
show_progress (bool): show progress bar
quiet (bool): if True, do not show the status message when
completed
background (bool): if True, run in the background.
interrupt (bool): if True, raise KeyboardInterrupt, otherwise
just stop and continue
"""
time_step = time_step if time_step is not None else self.time_step
if background:
if self._thread is None:
kwargs = {
"function": function,
"time_step": time_step,
"show": show,
"real_time": real_time,
"show_progress": False,
"quiet": True,
"background": False
}
print("Starting world.run() in background. Use world.stop()")
self._thread = Thread(target=self.run, kwargs=kwargs)
self._thread.start()
else:
print("The world is already running in the background. Use world.stop()")
else:
return self.steps(
float("inf"), function, time_step, show, real_time, show_progress, quiet, interrupt
)
save(self)
¶
Save the current state of the world as the config.
Source code in robots/world.py
def save(self):
"""
Save the current state of the world as the config.
"""
self.config = self.to_json()
save_as(self, filename)
¶
Save the world config JSON as a new file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filename |
str |
the name of the file to save the world in |
required |
Source code in robots/world.py
def save_as(self, filename):
"""
Save the world config JSON as a new file.
Args:
filename (str): the name of the file to save
the world in
"""
if not filename.endswith(".json"):
filename = filename + ".json"
# First, save internally.
self.config = self.to_json()
with open(filename, "w") as fp:
json_dump(self.to_json(), fp, sort_keys=True, indent=4)
self.config["filename"] = filename
save_file(self)
¶
Save the current state of the world as the config, and save it back to disc if it was loaded from disk.
Source code in robots/world.py
def save_file(self):
"""
Save the current state of the world as the config, and
save it back to disc if it was loaded from disk.
"""
# First, save internally.
self.config = self.to_json()
if self.filename is not None and os.path.exists(self.filename):
with open(self.filename, "w") as fp:
json_dump(self.config, fp, sort_keys=True, indent=4)
else:
if not self.quiet:
print("Saved in memory. Use world.save_as('filename') to save to disk.")
seconds(self, seconds=5.0, function=None, time_step=None, show=True, real_time=True, show_progress=True, quiet=False, interrupt=False)
¶
Run the simulator for N seconds, or until one of the control functions returns True or Control+C is pressed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
seconds |
float |
how many simulation seconds to run |
5.0 |
function |
Function |
either a single function that takes the world, or a list of functions (or None) that each take a robot. If any function returns True, then simulation will stop. |
None |
time_step |
float |
time unit to advance the world |
None |
show |
bool |
update the watchers |
True |
real_time |
float |
run simulation in real time |
True |
show_progress |
bool |
show progress bar |
True |
quiet |
bool |
if True, do not show the status message when completed |
False |
interrupt |
bool |
if True, raise KeyboardInterrupt, otherwise just stop and continue |
False |
Source code in robots/world.py
def seconds(
self,
seconds=5.0,
function=None,
time_step=None,
show=True,
real_time=True,
show_progress=True,
quiet=False,
interrupt=False,
):
"""
Run the simulator for N seconds, or until one of the control
functions returns True or Control+C is pressed.
Args:
seconds (float): how many simulation seconds to run
function (Function): either a single function that takes the
world, or a list of functions (or None) that each take
a robot. If any function returns True, then simulation will
stop.
time_step (float): time unit to advance the world
show (bool): update the watchers
real_time (float): run simulation in real time
show_progress (bool): show progress bar
quiet (bool): if True, do not show the status message when
completed
interrupt (bool): if True, raise KeyboardInterrupt, otherwise
just stop and continue
"""
time_step = time_step if time_step is not None else self.time_step
steps = round(seconds / time_step)
return self.steps(steps, function, time_step, show, real_time, show_progress, quiet, interrupt)
set_ground_color_at(self, x, y, pen)
¶
Set the pixel(s) of the ground image at position (x,y). Requires a ground image to have already been set.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
int |
the x coordinate |
required |
y |
int |
the y coordinate |
required |
pen |
tuple |
the (color, radius) to draw with |
required |
Source code in robots/world.py
def set_ground_color_at(self, x, y, pen):
"""
Set the pixel(s) of the ground image at position (x,y). Requires
a ground image to have already been set.
Args:
x (int): the x coordinate
y (int): the y coordinate
pen (tuple): the (color, radius) to draw with
"""
if self._ground_image:
color, radius = pen
for i in range(-radius, radius + 1, 1):
for j in range(-radius, radius + 1, 1):
try:
self._ground_image_pixels[
((x * self.scale) + i, (y * self.scale) + j)
] = color.to_tuple()
except Exception:
pass
set_ground_image(self, filename, show=True)
¶
Set the background image
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filename |
str |
the name of the image |
required |
show |
bool |
update the world now if True |
True |
Source code in robots/world.py
def set_ground_image(self, filename, show=True):
"""
Set the background image
Args:
filename (str): the name of the image
show (bool): update the world now if True
"""
self.ground_image_filename = filename
self._reset_ground_image()
if show:
self.update(show=False)
self.draw() # force
set_scale(self, scale)
¶
Change the scale of the rendered world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
scale |
float |
the scale of the world (usually between 0.5 and 10) |
required |
Source code in robots/world.py
def set_scale(self, scale):
"""
Change the scale of the rendered world.
Args:
scale (float): the scale of the world (usually between 0.5 and 10)
"""
self.scale = scale
self._backend.update_dimensions(self.width, self.height, self.scale)
# Save with config
self.config["scale"] = self.scale
self.update(show=False)
self.draw() # force
set_seed(self, seed)
¶
Set the random seed.
Source code in robots/world.py
def set_seed(self, seed):
"""
Set the random seed.
"""
if seed == 0:
seed = random.randint(0, 9999999)
if not self.quiet:
print("Random seed set to:", seed)
else:
if not self.quiet:
print("Using random seed:", seed)
random.seed(seed)
self.seed = seed
self.config["seed"] = seed
steps(self, steps=1, function=None, time_step=None, show=True, real_time=True, show_progress=True, quiet=False, interrupt=False)
¶
Run the simulator for N steps, or until one of the control functions returns True or Control+C is pressed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
steps |
int |
either a finite number, or infinity |
1 |
time_step |
float |
time unit to advance the world |
None |
show |
bool |
update the watchers |
True |
real_time |
bool |
run simulation in real time |
True |
show_progress |
bool |
show progress bar |
True |
quiet |
bool |
if True, do not show the status message when completed |
False |
interrupt |
bool |
if True, raise KeyboardInterrupt, otherwise just stop and continue |
False |
Source code in robots/world.py
def steps(
self,
steps=1,
function=None,
time_step=None,
show=True,
real_time=True,
show_progress=True,
quiet=False,
interrupt=False,
):
"""
Run the simulator for N steps, or until one of the control
functions returns True or Control+C is pressed.
Args:
steps (int): either a finite number, or infinity
function (Function) either a single function that takes the
world, or a list of functions (or None) that each take
a robot. If any function returns True, then simulation will
stop.
time_step (float): time unit to advance the world
show (bool): update the watchers
real_time (bool): run simulation in real time
show_progress (bool): show progress bar
quiet (bool): if True, do not show the status message when
completed
interrupt (bool): if True, raise KeyboardInterrupt, otherwise
just stop and continue
"""
self.status = "running"
stop_values = []
time_step = time_step if time_step is not None else self.time_step
if steps == float("inf"):
step_iter = count()
else:
step_iter = range(steps)
with self._no_interrupt():
start_real_time = time.monotonic()
start_time = self.time
# If you want to show, and in google colab, then
# we use tqdm's update to force widget updates
if show and in_colab():
clear_output(wait=True)
for step in progress_bar(
step_iter,
(show_progress and not quiet) or (show and in_colab()),
self._step_display
):
if self._stop:
self.status = "stopped"
if interrupt:
raise KeyboardInterrupt()
break
if function is not None:
if isinstance(function, (list, tuple)):
if len(function) < len(self._robots):
print_once("WARNING: you have not provided a controller function for every robot")
# Deterministically run robots round-robin:
stop_values = [
function[i](self._robots[i])
for i in range(len(function))
if function[i] is not None
]
else:
stop_values = [function(self)]
stop = any(stop_values)
if stop:
break
self._step(time_step, show=show, real_time=real_time)
self.status = "stopped"
stop_real_time = time.monotonic()
stop_time = self.time
speed = (stop_time - start_time) / (stop_real_time - start_real_time)
if steps > 1 and not quiet and not self.quiet:
print(
"Simulation stopped at: %s; speed %s x real time"
% (format_time(self.time), round(speed, 2))
)
if show:
self.draw() # force to update any displays
for value in stop_values:
return value
stop(self)
¶
Stop the simulator, if in thread or not.
Source code in robots/world.py
def stop(self):
"""
Stop the simulator, if in thread or not.
"""
self._stop = True
self.status = "stopped"
if self._thread is not None:
print("Stopping thread...")
self._thread.join()
self._thread = None
summary(self)
¶
Print a summary of information about the world and all of its robots.
Source code in robots/world.py
def summary(self):
"""
Print a summary of information about the world and
all of its robots.
"""
print("World details:")
if self.filename:
print("This world was loaded from %r" % self.filename)
print("Size: %s x %s" % (self.width, self.height))
print("-" * 25)
print("Robots:")
print("-" * 25)
if len(self._robots) == 0:
print(" This world has no robots.")
else:
for i, robot in enumerate(self._robots):
print(" .robots[%s or %r]: %r" % (i, robot.name, robot))
robot.summary()
print("-" * 25)
print("Food:")
print("-" * 25)
if len(self._food) == 0:
print(" This world has no food.")
else:
for food in self._food:
print(" x: %s, y: %s, brightness: %s, state: %s" % (
food.x, food.y, food.standard_deviation, food.state))
print("-" * 25)
print("Lights:")
print("-" * 25)
if len(self._bulbs) == 0:
print(" This world has no lights.")
else:
for bulb in self._bulbs:
print(" x: %s, y: %s, brightness: %s, name: %r, color: %s" % (
bulb.x,
bulb.y,
bulb.brightness,
bulb.name,
bulb.color))
print("-" * 25)
print("Beacon:")
print("-" * 25)
if self.beacon is None:
print(" This world has no beacon.")
else:
x, y = self.beacon.get_position(world=True)
print(" x: %s, y: %s" % (x, y))
to_json(self)
¶
Get the world as a JSON dict.
Source code in robots/world.py
def to_json(self):
"""
Get the world as a JSON dict.
"""
config = {
"seed": self.seed,
"width": int(self.width),
"height": int(self.height),
"scale": self.scale,
"boundary_wall": self.boundary_wall, # bool
"boundary_wall_color": str(self.boundary_wall_color),
"boundary_wall_width": self.boundary_wall_width,
"ground_color": str(self.ground_color),
"ground_image_filename": self.ground_image_filename,
"quiet": self.quiet,
"smell_cell_size": self.smell_cell_size,
"walls": [],
"bulbs": [],
"beacon": None,
"robots": [],
"food": [],
}
if self._beacon is not None:
config["beacon"] = {
"x": self._beacon.x,
"y": self._beacon.y,
"z": self._beacon.z,
}
for wall in self._walls:
# Not a boundary wall or robot bounding box:
if wall.wtype == "wall":
if len(wall.lines) == 4:
# Box:
w = {
"color": str(wall.color),
"p1": {"x": wall.lines[0].p1.x, "y": wall.lines[0].p1.y,},
"p2": {"x": wall.lines[2].p1.x, "y": wall.lines[2].p1.y,},
"wtype": "box",
}
elif len(wall.lines) == 1:
# Line:
w = {
"color": str(wall.color),
"p1": {"x": wall.lines[0].p1.x, "y": wall.lines[0].p1.y,},
"p2": {"x": wall.lines[0].p2.x, "y": wall.lines[0].p2.y,},
"wtype": "line",
}
else:
raise Exception("invalid wall length; should be 1 or 4: %s" % len(wall.lines))
config["walls"].append(w)
for bulb in self._bulbs:
config["bulbs"].append(
{
"color": str(bulb.color),
"x": bulb.x,
"y": bulb.y,
"z": bulb.z,
"brightness": bulb.brightness,
}
)
for food in self._food:
config["food"].append(
{
"x": food.x,
"y": food.y,
"standard_deviation": food.standard_deviation,
"state": food.state,
}
)
for robot in self._robots:
config["robots"].append(robot.to_json())
return config
update(self, show=True)
¶
Update the world, robots, and devices. Optionally, draw the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
show |
bool |
if True, draw the world. |
True |
Source code in robots/world.py
def update(self, show=True):
"""
Update the world, robots, and devices. Optionally, draw the
world.
Args:
show (bool): if True, draw the world.
"""
## Update robots:
self._draw_list = self._overlay_list[:]
for robot in self._robots:
robot.update(self._draw_list)
if show:
self._request_draw()
watch(self, width=None, height=None)
¶
Display a live (dynamically updated) view of the world.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
the width of the world |
None |
height |
int |
the height of the world |
None |
Source code in robots/world.py
def watch(self, width=None, height=None):
"""
Display a live (dynamically updated) view of the world.
Args:
width (int): the width of the world
height (int): the height of the world
"""
# Force update:
self.update(show=True)
# Force draw:
self.draw()
widget = self.get_widget(width, height)
display(widget)