-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathps3.py
503 lines (410 loc) · 16.8 KB
/
ps3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# -*- coding: utf-8 -*-
# Problem Set 3: Simulating robots
# Name:
# Collaborators (discussion):
# Time:
import math
import random
import ps3_visualize
import pylab
# For python 2.7:
from ps3_verify_movement27 import test_robot_movement
# === Provided class Position
class Position(object):
"""
A Position represents a location in a two-dimensional room, where
coordinates are given by floats (x, y).
"""
def __init__(self, x, y):
"""
Initializes a position with coordinates (x, y).
"""
self.x = x
self.y = y
def get_x(self):
return self.x
def get_y(self):
return self.y
def get_new_position(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: float 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.get_x(), self.get_y()
# Compute the change in position
delta_y = speed * math.cos(math.radians(angle))
delta_x = speed * math.sin(math.radians(angle))
# 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 "Position: " + str(math.floor(self.x)) + ", " + str(math.floor(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. Each tile
has some fixed amount of dirt. The tile is considered clean only when the amount
of dirt on this tile is 0.
"""
def __init__(self, width, height, dirt_amount):
"""
Initializes a rectangular room with the specified width, height, and
dirt_amount on each tile.
width: an integer > 0
height: an integer > 0
dirt_amount: an integer >= 0
"""
self.width = width
self.height = height
self.dirt_amount = dirt_amount
self.tiles = {}
for i in range(width):
for j in range(height):
self.tiles[(i,j)] = dirt_amount
def clean_tile_at_position(self, pos, capacity):
"""
Mark the tile under the position pos as cleaned by capacity amount of dirt.
Assumes that pos represents a valid position inside this room.
pos: a Position object
capacity: the amount of dirt to be cleaned in a single time-step
can be negative which would mean adding dirt to the tile
Note: The amount of dirt on each tile should be NON-NEGATIVE.
If the capacity exceeds the amount of dirt on the tile, mark it as 0.
"""
pos_key = (int(floor(pos.get_x())),int(floor(pos.get_y())))
self.tiles[pos_key] -= capacity
if self.tiles[pos_key] < 0:
self.tiles[pos_key] = 0
def is_tile_cleaned(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 the tile (m, n) is cleaned, False otherwise
Note: The tile is considered clean only when the amount of dirt on this
tile is 0.
"""
return self.tiles[(m,n)] == 0
def get_num_cleaned_tiles(self):
"""
Returns: an integer; the total number of clean tiles in the room
"""
clean_tiles = 0
for i in range(self.width):
for j in range(self.height):
clean_tiles += (self.tiles[(i,j)]==0)
return clean_tiles
def is_position_in_room(self, pos):
"""
Determines if pos is inside the room.
pos: a Position object.
Returns: True if pos is in the room, False otherwise.
"""
if pos.get_x() >= 0 and pos.get_x() < self.width and pos.get_y() >= 0 and pos.get_y() < self.height:
return True
else:
return False
def get_dirt_amount(self, m, n):
"""
Return the amount of dirt on the tile (m, n)
Assumes that (m, n) represents a valid tile inside the room.
m: an integer
n: an integer
Returns: an integer
"""
return self.tiles[(m,n)]
def get_num_tiles(self):
"""
Returns: an integer; the total number of tiles in the room
"""
# do not change -- implement in subclasses.
return self.width*self.height
def is_position_valid(self, pos):
"""
pos: a Position object.
returns: True if pos is in the room and (in the case of FurnishedRoom)
if position is unfurnished, False otherwise.
"""
# do not change -- implement in subclasses
raise NotImplementedError
def get_random_position(self):
"""
Returns: a Position object; a random position inside the room
"""
# do not change -- implement in subclasses
raise NotImplementedError
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 and a fixed cleaning capacity.
Subclasses of Robot should provide movement strategies by implementing
update_position_and_clean, which simulates a single time-step.
"""
def __init__(self, room, speed, capacity):
"""
Initializes a Robot with the given speed and given cleaning capacity in the
specified room. The robot initially has a random direction and a random
position in the room.
room: a RectangularRoom object.
speed: a float (speed > 0)
capacity: a positive interger; the amount of dirt cleaned by the robot
in a single time-step
"""
raise NotImplementedError
def get_robot_position(self):
"""
Returns: a Position object giving the robot's position in the room.
"""
raise NotImplementedError
def get_robot_direction(self):
"""
Returns: a float d giving the direction of the robot as an angle in
degrees, 0.0 <= d < 360.0.
"""
raise NotImplementedError
def set_robot_position(self, position):
"""
Set the position of the robot to position.
position: a Position object.
"""
raise NotImplementedError
def set_robot_direction(self, direction):
"""
Set the direction of the robot to direction.
direction: float representing an angle in degrees
"""
raise NotImplementedError
def update_position_and_clean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new random position (if the new position is invalid,
rotate once to a random new direction, and stay stationary) and mark the tile it is on as having
been cleaned by capacity amount.
"""
# do not change -- implement in subclasses
raise NotImplementedError
# === Problem 2
class EmptyRoom(RectangularRoom):
"""
An EmptyRoom represents a RectangularRoom with no furniture.
"""
def get_num_tiles(self):
"""
Returns: an integer; the total number of tiles in the room
"""
raise NotImplementedError
def is_position_valid(self, pos):
"""
pos: a Position object.
Returns: True if pos is in the room, False otherwise.
"""
raise NotImplementedError
def get_random_position(self):
"""
Returns: a Position object; a valid random position (inside the room).
"""
raise NotImplementedError
class FurnishedRoom(RectangularRoom):
"""
A FurnishedRoom represents a RectangularRoom with a rectangular piece of
furniture. The robot should not be able to land on these furniture tiles.
"""
def __init__(self, width, height, dirt_amount):
"""
Initializes a FurnishedRoom, a subclass of RectangularRoom. FurnishedRoom
also has a list of tiles which are furnished (furniture_tiles).
"""
# This __init__ method is implemented for you -- do not change.
# Call the __init__ method for the parent class
RectangularRoom.__init__(self, width, height, dirt_amount)
# Adds the data structure to contain the list of furnished tiles
self.furniture_tiles = []
def add_furniture_to_room(self):
"""
Add a rectangular piece of furniture to the room. Furnished tiles are stored
as (x, y) tuples in the list furniture_tiles
Furniture location and size is randomly selected. Width and height are selected
so that the piece of furniture fits within the room and does not occupy the
entire room. Position is selected by randomly selecting the location of the
bottom left corner of the piece of furniture so that the entire piece of
furniture lies in the room.
"""
# This addFurnitureToRoom method is implemented for you. Do not change it.
furniture_width = random.randint(1, self.width - 1)
furniture_height = random.randint(1, self.height - 1)
# Randomly choose bottom left corner of the furniture item.
f_bottom_left_x = random.randint(0, self.width - furniture_width)
f_bottom_left_y = random.randint(0, self.height - furniture_height)
# Fill list with tuples of furniture tiles.
for i in range(f_bottom_left_x, f_bottom_left_x + furniture_width):
for j in range(f_bottom_left_y, f_bottom_left_y + furniture_height):
self.furniture_tiles.append((i,j))
def is_tile_furnished(self, m, n):
"""
Return True if tile (m, n) is furnished.
"""
raise NotImplementedError
def is_position_furnished(self, pos):
"""
pos: a Position object.
Returns True if pos is furnished and False otherwise
"""
raise NotImplementedError
def is_position_valid(self, pos):
"""
pos: a Position object.
returns: True if pos is in the room and is unfurnished, False otherwise.
"""
raise NotImplementedError
def get_num_tiles(self):
"""
Returns: an integer; the total number of tiles in the room that can be accessed.
"""
raise NotImplementedError
def get_random_position(self):
"""
Returns: a Position object; a valid random position (inside the room and not in a furnished area).
"""
raise NotImplementedError
# === Problem 3
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 or furtniture, it *instead*
chooses a new direction randomly.
"""
def update_position_and_clean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new random position (if the new position is invalid,
rotate once to a random new direction, and stay stationary) and clean the dirt on the tile
by its given capacity.
"""
raise NotImplementedError
# Uncomment this line to see your implementation of StandardRobot in action!
#test_robot_movement(StandardRobot, EmptyRoom)
#test_robot_movement(StandardRobot, FurnishedRoom)
# === Problem 4
class FaultyRobot(Robot):
"""
A FaultyRobot is a robot that will not clean the tile it moves to and
pick a new, random direction for itself with probability p rather
than simply cleaning the tile it moves to.
"""
p = 0.15
@staticmethod
def set_faulty_probability(prob):
"""
Sets the probability of getting faulty equal to PROB.
prob: a float (0 <= prob <= 1)
"""
FaultyRobot.p = prob
def gets_faulty(self):
"""
Answers the question: Does this FaultyRobot get faulty at this timestep?
A FaultyRobot gets faulty with probability p.
returns: True if the FaultyRobot gets faulty, False otherwise.
"""
return random.random() < FaultyRobot.p
def update_position_and_clean(self):
"""
Simulate the passage of a single time-step.
Check if the robot gets faulty. If the robot gets faulty,
do not clean the current tile and change its direction randomly.
If the robot does not get faulty, the robot should behave like
StandardRobot at this time-step (checking if it can move to a new position,
move there if it can, pick a new direction and stay stationary if it can't)
"""
raise NotImplementedError
#test_robot_movement(FaultyRobot, EmptyRoom)
# === Problem 5
def run_simulation(num_robots, speed, capacity, width, height, dirt_amount, 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 the input speed and capacity in a room of dimensions width x height
with the dirt dirt_amount on each tile.
num_robots: an int (num_robots > 0)
speed: a float (speed > 0)
capacity: an int (capacity >0)
width: an int (width > 0)
height: an int (height > 0)
dirt_amount: an int
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
FaultyRobot)
"""
raise NotImplementedError
# print ('avg time steps: ' + str(run_simulation(1, 1.0, 1, 5, 5, 3, 1.0, 50, StandardRobot)))
# print ('avg time steps: ' + str(run_simulation(1, 1.0, 1, 10, 10, 3, 0.8, 50, StandardRobot)))
# print ('avg time steps: ' + str(run_simulation(1, 1.0, 1, 10, 10, 3, 0.9, 50, StandardRobot)))
# print ('avg time steps: ' + str(run_simulation(1, 1.0, 1, 20, 20, 3, 0.5, 50, StandardRobot)))
# print ('avg time steps: ' + str(run_simulation(3, 1.0, 1, 20, 20, 3, 0.5, 50, StandardRobot)))
# === Problem 6
#
# ANSWER THE FOLLOWING QUESTIONS:
#
# 1)How does the performance of the two robot types compare when cleaning 80%
# of a 20x20 room?
#
#
# 2) How does the performance of the two robot types compare when two of each
# robot cleans 80% of rooms with dimensions
# 10x30, 20x15, 25x12, and 50x6?
#
#
def show_plot_compare_strategies(title, x_label, y_label):
"""
Produces a plot comparing the two robot strategies in a 20x20 room with 80%
minimum coverage.
"""
num_robot_range = range(1, 11)
times1 = []
times2 = []
for num_robots in num_robot_range:
print ("Plotting", num_robots, "robots...")
times1.append(run_simulation(num_robots, 1.0, 1, 20, 20, 3, 0.8, 20, StandardRobot))
times2.append(run_simulation(num_robots, 1.0, 1, 20, 20, 3, 0.8, 20, FaultyRobot))
pylab.plot(num_robot_range, times1)
pylab.plot(num_robot_range, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'FaultyRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
def show_plot_room_shape(title, x_label, y_label):
"""
Produces a plot showing dependence of cleaning time on room shape.
"""
aspect_ratios = []
times1 = []
times2 = []
for width in [10, 20, 25, 50]:
height = 300/width
print ("Plotting cleaning time for a room of width:", width, "by height:", height)
aspect_ratios.append(float(width) / height)
times1.append(run_simulation(2, 1.0, 1, width, height, 3, 0.8, 200, StandardRobot))
times2.append(run_simulation(2, 1.0, 1, width, height, 3, 0.8, 200, FaultyRobot))
pylab.plot(aspect_ratios, times1)
pylab.plot(aspect_ratios, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'FaultyRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
#show_plot_compare_strategies('Time to clean 80% of a 20x20 room, for various numbers of robots','Number of robots','Time / steps')
#show_plot_room_shape('Time to clean 80% of a 300-tile room for various room shapes','Aspect Ratio', 'Time / steps')