# MITx edx 6.00.2x python 2 code, need to fix a bug

• Python
Gold Member
Hi people,
I've "enrolled" into the edx MITx 6.00.2x course on python and data science, but I don't plan to get a certificate and I started so late that all the problems I'm facing aren't due anymore so I think this gives me the right to seek help here.
Basically the task is to simulate some different kinds of robots that clean an mxn room and perform some statistics to gather information on how fast they are at cleaning the room, in order to pick the best strategy. That is, each kind of bot has a different cleaning strategy.
The whole code comes with a skeletton and we must fill in the blank. So far my code runs without error (I got inspired/helped a lot by other's code that I found on github), but I noticed a bug.

Basically the bots have a speed. If I increase the speed say from 1.0 to 5.0, the bots indeed move farther away within 1 time_counter, as it should. The problem is that with my current code, the bots only clean their current tile (i.e. position in room) and the tile they land on. With speed = 1.0 this causes no problem, but with speed 5.0 this means that the bots don't clean all the intermediary tiles between their current position and the one they will land on within the time_counter! I don't really see an easy fix to my code but I'm sure there are some... Any help is appreciated. Here's the code:
Code:
# 6.00.2x Problem Set 2: Simulating robots

import math
import random

import ps2_visualize
import pylab

# For Python 2.7:
from ps2_verify_movement27 import testRobotMovement

# If you get a "Bad magic number" ImportError, you are not using
# Python 2.7 and using most likely Python 2.6:

# === Provided class Position
class Position(object):
"""
A Position represents a location in a two-dimensional room.
"""
def __init__(self, x, y):
"""
Initializes a position with coordinates (x, y).
"""
self.x = x
self.y = y

def getX(self):
return self.x

def getY(self):
return self.y

def getNewPosition(self, angle, speed):
"""
Computes and returns the new Position after a single clock-tick has
passed, with this object as the current position, and with the
specified angle and speed.

Does NOT test whether the returned position fits inside the room.

angle: number representing angle in degrees, 0 <= angle < 360
speed: positive float representing speed

Returns: a Position object representing the new position.
"""
old_x, old_y = self.getX(), self.getY()
angle = float(angle)
# Compute the change in position
# Add that to the existing position
new_x = old_x + delta_x
new_y = old_y + delta_y
return Position(new_x, new_y)

def __str__(self):
return "(%0.2f, %0.2f)" % (self.x, self.y)

# === Problem 1
class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.

A room has a width and a height and contains (width * height) tiles. At any
particular time, each of these tiles is either clean or dirty.
"""
DIRTY = 0
CLEAN = 1
def __init__(self, width, height):
"""
Initializes a rectangular room with the specified width and height.

Initially, no tiles in the room have been cleaned.

width: an integer > 0
height: an integer > 0
"""
self.width = width
self.height = height
self.room = [[RectangularRoom.DIRTY for horizontal in range(height)] for vertical in range(width)]
# Or:
#self.room = []
#for vertical in range(height):
#  inner_list = []
#  for horizontal in range(width):
#  inner_list.append(RectangularRoom.DIRTY)
#  self.room.append(inner_list)

def cleanTileAtPosition(self, pos):
"""
Mark the tile under the position POS as cleaned.

Assumes that POS represents a valid position inside this room.

pos: a Position
"""
x = int(pos.x)
y = int(pos.y)
self.room[x][y] = RectangularRoom.CLEAN

def isTileCleaned(self, m, n):
"""
Return True if the tile (m, n) has been cleaned.

Assumes that (m, n) represents a valid tile inside the room.

m: an integer
n: an integer
returns: True if (m, n) is cleaned, False otherwise
"""
if self.room[m][n] == RectangularRoom.DIRTY:
return False
else:
return True

def getNumTiles(self):
"""
Return the total number of tiles in the room.

returns: an integer
"""
return self.width * self.height

def getNumCleanedTiles(self):
"""
Return the total number of clean tiles in the room.

returns: an integer
"""
CleanTileCounter = 0
for horizontal in self.room:
for h in horizontal:
if h == RectangularRoom.CLEAN:
CleanTileCounter += 1
return CleanTileCounter

def getRandomPosition(self):
"""
Return a random position inside the room.

returns: a Position object.
"""
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
return Position(x,y)

def isPositionInRoom(self, pos):
"""
Return True if pos is inside the room.

pos: a Position object.
returns: True if pos is in the room, False otherwise.
"""
x = pos.getX()
y = pos.getY()
if x >= 0 and x < self.width and y >= 0 and y < self.height:
return True
else:
return False

class Robot(object):
"""
Represents a robot cleaning a particular room.

At all times the robot has a particular position and direction in the room.
The robot also has a fixed speed.

Subclasses of Robot should provide movement strategies by implementing
updatePositionAndClean(), which simulates a single time-step.
"""
def __init__(self, room, speed):
"""
Initializes a Robot with the given speed in the specified room. The
robot initially has a random direction and a random position in the
room. The robot cleans the tile it is on.

room:  a RectangularRoom object.
speed: a float (speed > 0)
"""
self.room = room
self.speed = speed
self.position = room.getRandomPosition()
self.direction = random.randint(0,360-1)
self.room.cleanTileAtPosition(self.position)

def getRobotPosition(self):
"""
Return the position of the robot.

returns: a Position object giving the robot's position.
"""
return self.position # I don't understand why this doesn't return a random position, by the way it's defined above

def getRobotDirection(self):
"""
Return the direction of the robot.

returns: an integer d giving the direction of the robot as an angle in
degrees, 0 <= d < 360.
"""
return self.direction  # Idem than above

def setRobotPosition(self, position):
"""
Set the position of the robot to POSITION.

position: a Position object.
"""
self.position = position  # I don't understand why it's not POSITION = self.position

def setRobotDirection(self, direction):
"""
Set the direction of the robot to DIRECTION.

direction: integer representing an angle in degrees
"""
self.direction = direction

def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.

Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
raise NotImplementedError # don't change this!

# === Problem 2
class StandardRobot(Robot):
"""
A StandardRobot is a Robot with the standard movement strategy.

At each time-step, a StandardRobot attempts to move in its current
direction; when it would hit a wall, it *instead* chooses a new direction
randomly.
"""
def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.

Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
newPosition = self.position.getNewPosition(self.direction , self.speed)
#i If the robot hits the wall and newPosition is outside the room, we choose a random direction and get new position until it fits in the room
while not self.room.isPositionInRoom(newPosition):
self.direction = random.randint(1, 360 - 1)
newPosition = self.position.getNewPosition(self.direction , self.speed)
#The robot is now inside the room, so we pick that new position as valid and we clean the tile
self.setRobotPosition(newPosition)
self.room.cleanTileAtPosition(newPosition)

# Uncomment this line to see your implementation of StandardRobot in action!
#testRobotMovement(StandardRobot, RectangularRoom)

# === Problem 3
def runSimulation(num_robots, speed, width, height, min_coverage, num_trials,
robot_type):
"""
Runs NUM_TRIALS trials of the simulation and returns the mean number of
time-steps needed to clean the fraction MIN_COVERAGE of the room.

The simulation is run with NUM_ROBOTS robots of type ROBOT_TYPE, each with
speed SPEED, in a room of dimensions WIDTH x HEIGHT.

num_robots: an int (num_robots > 0)
speed: a float (speed > 0)
width: an int (width > 0)
height: an int (height > 0)
min_coverage: a float (0 <= min_coverage <= 1.0)
num_trials: an int (num_trials > 0)
robot_type: class of robot to be instantiated (e.g. StandardRobot or
RandomWalkRobot)
"""
results = []
for trial in range(num_trials):
anim = ps2_visualize.RobotVisualization(num_robots, width, height)  # Animation
time_counter=0
room = RectangularRoom(width, height)
coverage = room.getNumCleanedTiles() / float(room.getNumTiles())
robots = [robot_type(room, speed) for robot in range(num_robots)]
while coverage < min_coverage:
anim.update(room, robots)  # Animation
for robot in robots:
robot.updatePositionAndClean()
coverage = float(room.getNumCleanedTiles()) / float(room.getNumTiles())
time_counter +=1
if coverage == min_coverage:
break
results.append(time_counter)
# Now I make the average of that list, to get a feeling on how good/bad is the bot
anim.done()  #Animation
return sum(results) / float(len(results))
# Uncomment this line to see how much your simulation takes on average
print  runSimulation(1, 3.0, 10, 10, 0.9, 100, StandardRobot)

jedishrfu
Mentor
why not run the code with the 1 unit counter scheme and then show stuff every mod 5 == 0 of the counter.

like having a fine counter and a coarse counter.

• fluidistic
Gold Member
why not run the code with the 1 unit counter scheme and then show stuff every mod 5 == 0 of the counter.

like having a fine counter and a coarse counter.
That would have been a good idea, but I have been told that it's a feature and not a bug. And that I should think about speed as "step size rather than velocity"...