Note that the hour of the deadline is not the same as for homework assignments.
This project must be completed individually. We want to evaluate you on the basis of what you can do by consulting the resources listed below, and without help from anyone other than course staff. Seeking or giving aid on this assignment is prohibited (except for asking course staff); doing so constitutes academic misconduct which can have serious consequences. If you are unsure about whether something is allowed, ask. The course syllabus contains more information about the course and university policies regarding academic honesty.
In this project you will read some existing object-oriented code in order to understand how to use it. You will then write some new subclasses that interact with the existing code.
The classes in this project represent a simplified version of a system for automatically shuttling raw materials between a supply depot and a manufacturing plant.
Ask if you are unsure whether a resource falls under one of these categories, or if you think I missed an essential resource.
Contact the instructor or your TA by email, in office hours, or on discord.
This is a fictional scenario that explains the work you need to do for this project in the context of a real-world problem
You are contracted to work on a software development project for a factory that produces marbles---small glass spheres that are used for various games.
Marbles. Photo by: James Petts |
The factory makes marbles by mixing sand with trace amounts of other substances (which affect the color and melting point of the mixture), melting it in a furnace, and forming the molten mixture into spheres. Large quantities of sand are needed, so keeping the factory supplied with enough sand is an important concern.
A pile of sand. Photo by: P K |
This factory is located in building 275 of an industrial park, which is unfortunately some distance from the industrial park's supply depot where trucks can bring sand to supply the factory. Thus, arriving trucks dump sand into a supply cache (a large pile) located at the depot. Then the sand needs to be transported to the factory by a smaller monorail car (the shuttle) that runs on a track from the depot to building 275. Next to building 275, another large pile (the destination cache) holds the sand that has arrived at the factory and is waiting to be used. This is shown in the diagram below:
Poor cartoon rendering of the monorail track from supply depot to factory 275 |
The marble factory plans to automate sand transportation by the shuttle as much as possible. Engineers at the factory have already developed systems to monitor the supply cache and destination cache, and to load or unload the shuttle at these locations. What they need now is a software system to pilot the shuttle. They've decided that this will be written in Python, and developed as a single class that will run the car in a simple loop of this general form:
sc = fixtures.SupplyCache() # class that monitors the supply cache (not your responsibility)
dc = fixtures.DestinationCache() # class that monitors the destination cache (not your responsibility)
# Make the shuttle autopilot (YOUR RESPONSIBILITY), passing the objects that
# represent the supply and destination caches so it can interact with them
S = shuttle.Shuttle(supply=sc, dest=dc)
# `Shuttle` is a base class that doesn't do any piloting; you will write
# subclasses that actually do things.
# ...
# other intialization here
# ...
while True:
# Do anything needed to bring SupplyCache and DestinationCache info up to date
# ...
# Ask the shuttle to decide on and execute its next operation
S.update()
# Wait until it's time for the next update
# ...
The factory engineers have already decided on the methods that the SupplyCache
and DestinationCache
contain, and as mentioned above, those components have already been written. They have also developed a base class called Shuttle
that every piloting strategy will be a subclass of, and a list of the strategies that they want implemented as subclasses.
You have been hired to complete the development work by actually writing those subclasses of Shuttle
that interact with the other components and complete the system. The rest of this document details what that requires.
As seen in the diagram above, the monorail track leading from the supply cache to the destination cache has been given coordinates where the supply cache corresponds to x=0, the destination cache to x=9, and the track to the interval between 0 and 9. It has been decided that the shuttle movement will always be specified using positions with integer coordinates, which works well because in these coordinates the shuttle can move one unit per minute.
The program controlling the shuttle lives in a single method update()
of a subclass of Shuttle
(which is in the module shuttle
, so most code will actually refer to it by shuttle.Shuttle
). This method is called once per minute, and must decide what to do over the course of the following minute. The following actions are allowed:
update()
.As you can see, deciding what operations to perform is likely to depend on an ability to check various aspects of the environment (e.g. amount of sand at each cache, capacity of each cache, monorail shuttle capacity, etc.). There are methods for this purpose, documented below.
The starter pack is available here:
It is a ZIP archive, i.e. a group of files bundled together and compressed into a single file. The first thing you need to do is to extract the starter pack into a directory where you will do your work. Contact the instructor or TA if you need assistance extracting a ZIP file. All common operating systems come with a ZIP extractor built in.
If you want to browse the files on github, you can do that. But the ZIP is the easiest way to download all of them at once (as you must do):
It contains A FILE YOU EDIT AND SUBMIT AS YOUR PROJECT:
shuttle.py
- Contains Shuttle
class; you need to add three more classes as described below.And also a supporting file that I provide, and which you don't edit at all:
fixtures.py
- A module containing classes representing the supply cache and destination cache.Finally, there is a sample program that shows how these classes can be combined into a simulation of the entire system in operation, which you can edit and use to test your work.
simulation.py
- Simulation programtui.py
- Module used by simulation.py
This project involves adding subclasses to an existing object-oriented program. In such cases, just understanding the current state of the program is usually a significant part of the work. More generally, reading and understanding both technical documentation and existing code is an important skill.
This section contains technical documentation for classes in the starter pack you need to know about. They provide the features that an autopilot needs to do its work.
fixtures.py
¶DO NOT EDIT THIS FILE AT ALL. Reading it is a good idea, but you must use it in its unmodified form if you want your project to pass the automated tests. The code you add to shuttle.py
will make use of the classes defined in this file, which are documented below.
SupplyCache
¶Superclass: Cache
(not documented here)
Purpose: Base class of all classes that simulate the supply cache at position 0, which is a pile of sand at the supply depot.
Methods:
__init__(self,capacity)
: Initialize a supply cache with the given capacity (an integer).update(self)
: Simulate the passage of one minute. In this class, it does nothing. Subclasses used for testing may do things (like add more sand on occasion, to simulate arriving trucks).remove_material(self,amount)
: Request to remove amount
kg of sand from the supply cache. Returns the actual amount removed, which might be smaller. For example, if the supply only has 150kg at a time when you ask to remove 300kg, this function would remove all 150kg and return that number. It is the caller's responsibility to not ask for more sand than they can handle.Shuttle
is expected to remove material from the supply by calling this method.==is_full(self)
: Return a boolean indicating whether the supply is full (amount stored is equal to capacity)is_empty(self)
: Return a boolean indicating whether the supply is empty (amount stored is zero)DestinationCache
¶Superclass: Cache
(not documented here)
Purpose: Base class of all classes that simulate the destination cache at position 9, a pile of sand at the entry point of the factory.
Methods:
__init__(self,capacity)
: Initialize a supply cache with the given capacity (an integer).update(self)
: Simulate the passage of one minute. In this class, it does nothing. Subclasses used for testing may do things (like remove some sand to account for usage by the factory).add_material(self,amount)
: Request to add amount
kg of sand to the destination cache. Returns the actual amount added, which might be smaller. For example, if the destination cache only has room for 150kg more at a time when you ask to add 200kg, this function would add 150kg and return that number. It is the caller's responsibility to not ask to add more sand than they actually have on the shuttle; this function assumes that amount
kg of sand are available.Shuttle
is expected to deposit material at the destination by calling this method. is_full(self)
: Return a boolean indicating whether the destination cache is full (amount stored is equal to capacity)is_empty(self)
: Return a boolean indicating whether the destination cache is empty (amount stored is zero)SupplyCache
and DestinationCache
¶When testing your project, we'll use subclasses of SupplyCache
and DestinationCache
that simulate additional sand arriving at the supply cache, factory consumption of sand from the destination cache, or a combination thereof.
However, you are guaranteed that:
In any subclass of SupplyCache
, the only way the amount of available sand can decrease is if you call remove_material()
. However, the amount of sand available may increase between calls to Shuttle.update()
to account for new materials arriving (e.g. a truck of sand arrives and is added to the supply).
In any subclass of DestinationCache
, the only way the amount of available sand can increase is if you call add_material()
. However, the amount of sand available may decrease between calls to Shuttle.update()
to account for usage of sand by the factory.
shuttle.py
¶DO NOT EDIT THE CLASS LISTED BELOW. You will add subclasses to this file, but the existing class must not be changed.
Shuttle
¶Superclass: None
Purpose: The base class from which all monorail shuttle autopilot systems inherit. It sits at position 0 forever doing nothing.
Attributes: All of these are created/set by the constructor:
supply
: The supply cache object given to the constructor (an instance of a subclass of SupplyCache
)dest
: The destination cache object given to the constructor (an instance of a subclass of DestinationCache
)capacity
: Storage capacity in kg (never changes; set to capacity
argument of constructor)stored
: Amount of sand currently on board, in kg (initially 0)pos
: Current position (an integer between 0 and 9, inclusive, initially 0)Methods:
__init__(self,capacity,supply,dest)
: Create a new instance with the given capacity (in kg), which interacts with the given SupplyCache
instance supply
located at position 0 and the given DestinationCache
instance dest
at position 9. Sets the initial position and the initial stored amount to 0.available_capacity(self)
: Returns the amount of additional sand (in kg) the shuttle could take on at this moment.update(self)
: A method to simulate one minute of time passing; does nothing in this class, and is expected to be overridden in subclasses you write.Everything you write goes in shuttle.py
.
BackForthShuttle
¶Superclass: Shuttle
Purpose: Represents a shuttle autopilot strategy that goes back and forth between supply and destination caches. In this class it does so with minimal pauses at each end to load or unload. Subclasses could modify that behavior. It doesn't care whether it has any room for sand, or whether the supply or destination is full or empty. It does the best it can on a fixed schedule of movement.
Methods to be defined in this subclass (i.e. not just inherited):
__init__(self,...)
- Same args as the superclass constructor, but you might want to override this and add additional code to set up things like the state. (See update()
docs below.)update(self)
- Account for one minute of simulated shuttle piloting. This class uses the following strategy:update()
call, remove as much material as possible from the supply cache as possible.update()
call tried to remove material from the supply cache, begin moving toward the destination cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)update()
call arrived at the destination cache, unload as much material as possible.update()
call tried to add material to the desintation cache, begin moving back toward the supply cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)Hint:
state
that tracks what the shuttle is currently doing, and have update
check this, decide what to do, and possibly change the value of state
accordingly.Add or override other methods or attributes at your discretion.
ActOnOverflowShuttle
¶Superclass: You choose, but it must descend from Shuttle
. You might want to make it a subclass of BackForthShuttle
.
Purpose: Represents a shuttle autopilot strategy waits at the supply until the supply becomes full, then it takes as much sand as possible to the destination and comes back.
Methods to be defined in this subclass (i.e. not just inherited):
__init__(self,...)
- Same args as the superclass constructor, but you might want to override this and add additional code to set up things like the state. (See update()
docs below.)update(self)
- Account for one minute of simulated shuttle piloting. This class uses the following strategy:update()
call, check to see if the supply is full (an "overflow"). If it is, remove as much material as possible from the supply cache. If the supply isn't full, do nothing.update()
call tried to remove material from the supply cache, begin moving toward the destination cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)update()
call arrived at the destination cache, unload as much material as possible.update()
call tried to add material to the desintation cache, begin moving back toward the supply cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)Add or override other methods or attributes at your discretion.
Hint:
ActOnUnderflowShuttle
¶Superclass: You choose, but it must descend from Shuttle
. You might want to make it a subclass of BackForthShuttle
.
Purpose: Represents a shuttle autopilot strategy waits at the supply until the destination cache is empty and the supply contains some sand. It then loads sand, takes it to the destination, and returns.
Methods to be defined in this subclass (i.e. not just inherited):
__init__(self,...)
- Same args as the superclass constructor, but you might want to override this and add additional code to set up things like the state. (See update()
docs below.)update(self)
- Account for one minute of simulated shuttle piloting. This class uses the following strategy:
Wait: If the object is new, or if it has just arrived at position 0 in the previous update()
call, check to see if these conditions are satisfied:
If these conditions are satisfied, remove as much material as possible from the supply cache (i.e. request such removal). Otherwise, do nothing (i.e. wait for the next update()
call).
update()
call requested to removed material from the supply cache, begin moving toward the destination cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)update()
call arrived at the destination cache, unload as much material as possible.update()
call tried to add material to the desintation cache, begin moving back toward the supply cache, moving one unit per minute until we arrive (which spans many subsequent update()
calls)Add or override other methods or attributes at your discretion.
Hint:
ActOnOverflowShuttle
, in that it will notice and avoid a situation in which a trip to the destination would be useless: It will not attempt to remove material from an empty supply, instead waiting around until some sand was available.As explained earlier, the key is to make subclasses of Shuttle
that include their own update()
methods to do the actual work of deciding the next step.
In an earlier section we listed the things the shuttle can do in a single update()
call, and so a key part of this assignment is to determine how those actions would be accomplished by
Shuttle
subclass instance, orShuttle
subclass instance (such as supply
and dest
).Then, you need to write a function that can decide which action is needed at any given time.
These autopilot strategies are stateful (they maintain some internal state that determines what they're currently doing, and the behavior depends on that state). You will probably want to add an attribute to the Shuttle
subclass in order to track that state information. Looking back at the strategy we used to write the PatrolBot
class in Lecture 6 is probably a good starting point.
It is difficult to write an autopilot class if you can't see it working.
The program simulation.py
included in the starter pack has the ability to instantiate a SupplyCache
, a DestinationCache
, a Shuttle
subclass, and to run a simulation. The simulation uses text graphics, and expects you to press Enter to advance by one minute.
As provided, this program just makes a Shuttle
instance, which sits at the supply forever doing nothing. To test the behavior of the classes you write, edit this program and have it create an instance of BackForthShuttle
, ActOnOverflowShuttle
, or ActOnUnderflowShuttle
instead. The program also has comments showing where you'd put code to add new material to the supply, remove. The autograder will test your shuttle autopilot classes with SupplyCache
and DestinationCache
subclasses that simulate new sand arriving and sand being used by the factory (respectively), so activating these features of simulation.py
is a good idea.
Here's an example simulation where:
ActOnUnderflowShuttle
, with a capacity of 200kg
.SupplyCache
has a capacity of 900kg, starts full, and is never added to.DestinationCache
has a capacity of 500kg, starts empty, and has 10kg removed per minute to account for factory usage.This simulation is not exactly the same as the provided simulation.py
; the program shown here also prints a description of what happened in each minute.
The simulation proceeds for 141 steps before the supply is empty, after which there is nothing more for the shuttle to do.
It's a video of the terminal. Play to watch the simulation.
Contact the instructor if this video doesn't work for you!
Your project must follow the rules in the MCS 275 coding standards document. In addition:
Only submit shuttle.py
. The autograder will supply an unmodified version of fixtures.py
when testing your project. The other files provided in the starter pack are for your testing, and are not used in grading.
The only things you add to shuttle.py
should be class definitions. Do not add any code outside of class definitions (no functions that are not methods, no test code, etc.)
Make use of inheritance when you can, and when it helps make the code simpler (e.g. avoiding duplication).
Do not use float
values in any way. The only numeric type you need in this project is int
(integer).
The autograder tests your program and grades it based on its behavior. A series of tests will be run including one-step and multi-step simulations under various circumstances. These tests will look for adherence to the specifications in this document.
I will review your code and look for adherence to the coding standards and other rules given above, and I will examine your method of solving the problem. If I see that your program does not do what was requested in this document, but the error was not detected in the automated testing, a deduction may be given at this point. The scores assigned by the autograder will not be changed during manual review unless I discover some kind of intentional wrongdoing (such as an attempt to circumvent or reverse-engineer the autograder's operation).
As you write your project, test it locally on your own computer or the lab computer you use for your work. Every time you create a new Shuttle
subclass, add an instance of it to simulation.py
and try it out. It is much harder to debug a broken program based solely on reports you get from the autograder compared to working with your local Python interpreter.