This worksheet focuses on subclasses and inheritance. Part of it involves extending the robot simulation from lectures 5 and 6.
These things might be helpful while working on the problems. Remember that for worksheets, we don't strictly limit what resources you can consult, so these are only suggestions.
Project 1 is due on 10 February. To prepare for working on it, please download and extract ths starter pack, which is a ZIP file:
You don't just want to view the contents of the ZIP file in Windows explorer; it's important to actually extract the files so they exist in a directory where you can do your project work.
When you've extracted the starter pack, check that you know the location of simulation.py
and that you can run it in the terminal.
The point of asking you to do it during lab is to ensure the TA can help you if you run into any problems.
Download these files related to the robot simulation from the course sample code repository and put them in a directory where you'll do your work for this problem.
Then, build these new robots in bots.py
that are subclasses of Bot
:
class DelayMarchBot
Vector(1,0)
, but any direction can be specified in an optional constructor argument)class ParallelogramPatrolBot
v1
and v2
and two integers n1
and n2
.n1
steps in direction v1
, thenn2
steps in direction v2
, thenn1
steps in direction -v1
, thenn2
steps in direction -v2
, thenRandomItinerantBot
random
module to decide.)direction
, which is a Vector2
randomly selected from a list of four vectors representing up, down, left, and right.length
, which is the number of steps it will take in this directionlength
steps, then it switches back to waiting mode.startle()
that, when called, will make it so that the robot switches to walking mode the next time update()
is called.Add these robots to the simulation and confirm they exhibit the expected behavior. Use class attributes to give the new robot classes their own symbols.
class DelayMarchBot(Bot):
"""Robot that waits, and then proceeds at a constant pace in a consistent direction"""
symbol = "M"
def __init__(self, position, wait_time, direction=plane.Vector2(1,0)):
"""Initialize as a bot. Also save `direction` and `wait_time` as attributes."""
super().__init__(position)
self.direction = direction
self.wait_time = wait_time
def update(self):
"""Wait for given `wait_time`. Else, move in given direction."""
if self.wait_time>0: # Do nothing if we still need to wait
self.wait_time -= 1
else:
self.move(self.direction) # After the waiting is done, march!
class ParallelogramPatrolBot(Bot):
"""Robot that patrols perimeter of a parallelogram defined by starting args"""
symbol = "\u25B1" # Unicode symbol for parallelogram: ▱
def __init__(self, position, v1, v2, n1, n2):
"""Save given args. Initialize step_count to track distance patrolled"""
super().__init__(position)
self.v1 = v1
self.v2 = v2
self.n1 = n1
self.n2 = n2
self.step_count = 0 # We use this int to track state
# Could also use a state variable like in PatrolBot
def update(self):
"""March in direction v1 for n1 steps, v2 for n2 steps,
-v1 for n1 steps, -v2 for n2 steps.
Reset step_count after 1 full patrol"""
# Reset step count after going around the parallelogram
if self.step_count == 2 * (self.n1 + self.n2):
self.step_count = 0
# Decide which side of the parallelogram bot is on, then move
if self.step_count <= self.n1:
self.move(self.v1)
elif self.step_count <= self.n1 + self.n2:
self.move(self.v2)
elif self.step_count <= (2 * self.n1) + self.n2:
self.move( - self.v1)
else:
self.move( - self.v2)
self.step_count += 1
class RandomIterantBot(Bot):
"""Randomly changes between walking and waiting mode.
When walking, randomly decides on direction and length."""
symbol = "R"
steps = [
plane.Vector2(1, 0),
plane.Vector2(-1, 0),
plane.Vector2(0, 1),
plane.Vector2(0, -1),
]
def __init__(self, position):
"""Start in waiting mode. Keep a list of vectors in each direction to randomly choose from."""
super().__init__(position)
self.state = "waiting"
def update(self):
"""If in waiting mode, there is a 5% chance to change to walking mode.
When in walking mode, move desired number of steps either up/down/left/right"""
if self.state == "waiting":
if random.random() <= 0.05: # 5% chance
self.startle()
else:
self.move(self.direction)
self.step_count += 1
if self.step_count == self.length:
self.state = "waiting"
def startle(self):
"""Set robot to walking mode"""
self.state = "walking"
self.direction = random.choice(self.steps)
self.step_count = 0
self.length = random.randint(1, 5) # Total steps can be between 1 and 5 (inclusive)
UnitVector2
¶In plane.py add a subclass UnitVector2
of Vector2
that represents a unit vector (a vector of length 1) in a specified direction. The constructor should accept a single float theta
instead of the two coordinates x
and y
that are expected by the Vector2
constructor. The constructor should then initialize the object so that the x
coordinate is cos(theta)
and the y
coordinate is sin(theta)
. All three quantities (theta
, x
, y
) should be stored as instance attributes.
The functions sin
and cos
are found in the math
module.
Also, recall that Vector2
objects support addition and scalar multiplication. But the sum of two unit vectors is usually not a unit vector, nor is a scalar multiple of a unit vector. Is this going to cause problems? If you add two UnitVector2
instances, do you get a UnitVector2
or Vector2
?
class UnitVector2(Vector2):
"""Subclass of Vector2 with length 1"""
def __init__(self, theta):
"""Initialize the angle theta. Use angle to determine x and y."""
self.theta = theta
x = math.cos(self.theta)
y = math.sin(self.theta)
super().__init__(x,y)
def __str__(self):
"""Same string representation as usual, but replace Vector2 with UnitVector2."""
return "UnitVector2({},{})".format(self.x, self.y)
UnitVector2
class is as follows:v1 = UnitVector2(0)
v2 = UnitVector2(math.pi/2)
v1
has (x,y)
values (1,0)
, and v2
has (x,y)
values (0,1)
. Then the following commands show us that we get instances of Vector2
when using addition or scalar multiplication:print(v1 + v2)
print(5 * v1)
Work on these open-ended problems if you finish the exercises above. We don't plan to include solutions to these in the worksheet solutions.
Make a robot class that behaves like WanderBot
or FastWanderBot
, but which allows any list of possible direction
vectors to be given as an argument to the constructor. The robot then chooses a random element of the provided list of vectors for each step.
Make a robot class (a subclass of DestructBot
) that stands still for a specified number of steps and then deactivates. But before it does so, this class calls a user-specified function. The function is given as an argument to the constructor. So, for example:
def bye():
"""Robot says goodbye"""
print("Thanks for including me in this simulation. My battery is running low so if it's OK with you I'll just power down now. Bye.")
R = bots.NotifyDestructBot(position=Point(3,3),active_time=10,action=bye)
# ... code to run the simulation ...
should make a robot that sits at position (3,3) for 10 steps, prints a message, and then deactivates.
The action
argument of the constructor should default to None
, and the class should know to not do anything if action==None
. That way, any code that works with DestructBot
will also work with NotifyDestructBot
.