CS21 Lab 11: Swindle (objects)
Due Monday, May 4, before midnight
Please read through the entire lab before starting!
In this lab, you will build an e-reader called the Swindle (any resemblance to current e-readers is completely coincidental). You will use object-oriented programming to create two classes.
All of the standard style guidelines from previous labs still apply. Be sure to follow good top-down-design practices.
As you write programs, use good programming practices:
-
Use a comment at the top of the file to describe the purpose of the program (see example).
-
All programs should have a
main()function (see example). -
Use variable names that describe the contents of the variables.
-
Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.
-
Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.
-
Avoid writing any lines of code that exceed 80 columns.
-
Always work in a terminal window that is 80 characters wide (resize it to be this wide)
-
In
vscode, at the bottom right in the window, there is an indication of both the line and the column of the cursor.
-
Function Comments
All functions should include a comment explaining their purpose, parameters, return value, and describe any side effects. Please see our function example page if you are confused about writing function comments.
Goals
The goals for this lab assignment are:
-
Gain experience writing classes.
-
Gain experience reading and using classes and provided library functions.
1. Introduction
The first class you will develop is the Book class that encapsulates
one electronic book (or ebook). We have made several free
ebooks available for you to load and experiment with to get your
Book class working.
The second class you will develop is the Swindle class that
encapsulates the concept of an e-reader. You will be able to perform
several core operations of an e-reader, including buying books,
picking a book from your virtual shelf, and reading a book.
To get started, take note of the files that we have provided for you:
-
book.py— the definition of theBookclass to be completed by you -
swindle.py— the definition of theSwindleclass to be completed by you (we give you a partially implemented constructor and a few other completed methods) -
bookdb.txt— a file containing information about all of the books available for download -
ereader.py— the main program to create aSwindleand interact with the device (we give you all of this)
1.1. Sample runs
Read through the two sample runs provided below. The first one is annotated with comments so that you can see how the e-reader should operate, and understand which parts you need to implement and which parts are already implemented for you. The second one is an uninterrupted run.
1.2. Testing
-
As always, you should test your program as you go.
-
Since you’re working with three files, it’s a good idea to put the tests for your class in the corresponding file.
-
Like we saw in lecture, this can be done by writing a
main()method, and then calling it at the bottom using:if __name__ == "__main__": main()This way, your testing code will run when you execute
python3 book.pyon the command line, but it won’t run when youimportfrombookinswindle.py
2. The Book class
First, complete the Book class in the file book.py.
A Book object represents an electronic book to read on your
e-reader. You should define your class such that each book maintains
the following data (i.e., instance variables):
-
title (string) — the title of the book
-
author (string) — the author of the book
-
year (int) — the year the book was published
-
filename (string) — where the book is stored on the CS lab machines, for example:
"/usr/local/doc/alice.txt" -
bookmark (int) — the page number where the owner left off when last reading the book
Your Book class should define the following methods:
-
Constructor (
__init__)-
The constructor should take parameters for the title, author, year published, and the name of the file containing the book. It should save these variables as instance variables for the object (i.e., initializing the title, author, year, and filename instance variables).
-
The constructor should also set the bookmark instance variable to page 0.
-
-
get_title(…)-
The getter method for the title should take no parameters (other than
self), and it should return the title of the book (a string instance variable stored in the object).
-
-
get_author(…)-
The getter method for the author should take no parameters (other than
self), and it should return the author of the book (a string instance variable stored in the object).
-
-
get_year(…)-
The getter method for the publication year should take no parameters (other than
self), and it should return the publication year of the book (an int instance variable stored in the object).
-
-
get_filename(…)-
The getter method for the book’s filename should take no parameters (other than
self), and it should return the full filename of the book (a str instance variable stored in the object).
-
-
get_bookmark(…)-
The getter method for the bookmark should take no parameters (other than
self), and it should return the page number of the current bookmark (an int instance variable stored in the object).
-
-
set_bookmark(…)-
The setter method for the bookmark; it should take one parameter (in addition to
self): an int indicating the page number of the new bookmark for the book. The method should save this new page number to the bookmark instance variable stored within the object. It should not return any value.
-
-
get_text(…): method to retrieve the entire contents of the book as a single string. This method will need to:-
Open the filename stored in the
Bookobject -
Ignore any lines in the file that start with a pound sign (#)
-
Store all lines of the file in one long string. Recall that each line, when read from the file, will end with a newline character
\n; your final string should include these newline characters. -
Close the file
For example, if the file contained the "Roses are red…" poem:
# Here is a silly poem Roses are red, Violets are blue. # Second verse Sugar is sweet, And so are you!
Then
get_text(…)would return a string that looks like this, where the comment lines (starting with the pound sign) have been removed:"Roses are red,\nViolets are blue.\nSugar is sweet,\nAnd so are you!\n"
When you print this string it will look like this:
Roses are red, Violets are blue. Sugar is sweet, And so are you!
You should use the same method we have used in the past for reading a file, but you will need to combine all of the lines of text into a single string (Hint: you may need a string accumulator). Don’t forget to close the file when you’re finished reading it, before returning.
-
-
__str__: method that returns a string representation of aBookobject. Your method should return a string containing the title, author, and date published in a readable format. Use string formatting to give the title a width of 25 and the author a width of 25. When you print information about multiple books, this will line the book information up nicely as shown below:Alice in Wonderland by Lewis Carroll (1865) Gettysburg Address by Abe Lincoln (1863)Note that you will not call this method directly. Python will automatically call it whenever a
Bookobject needs to be converted to a string (e.g., when you pass the object to the functionstr(…)or when printing it withprint(…)).
2.1. Incrementally test the Book class
As you complete each method of the Book class you should test it. The
following is an example of some test code that should work and run without
errors if you’ve implemented the Book class properly. You can use this as a
starting point, but you should also add additional test code as you implement
the above methods to further test your implementation.
def main():
print("Testing the Book class...")
mybook = Book("Gettysburg Address", "Abe Lincoln", 1863,
"/usr/local/doc/GettysburgAddress")
print("Testing __str__...")
print(mybook)
print("Testing get_filename...")
print(mybook.get_filename())
print("Testing get_text...")
text = mybook.get_text()
print(text[:300]) #only print the first couple of lines
print("bookmark is: %d" % mybook.get_bookmark())
mybook.set_bookmark(12)
print("now bookmark is: %d" % mybook.get_bookmark())
################ Write additional tests below ###################
if __name__ == '__main__':
main()
Enhance this test code by adding a number of your own tests. Do not move on
to the Swindle class until you are sure that your Book class is working
correctly! Include all of your test code in the code you submit.
3. Complete the Swindle class
Next, write the Swindle class in the file swindle.py.
The Swindle class defines a Swindle e-reader device. Every instance must
maintain a few pieces of data (as instance variables):
-
owner (string) — the owner’s name
-
available_books (list of
Bookobjects) — books available for sale -
owned_books (list of
Bookobjects) — books owned by the user -
page_length (int) — the number of lines to display per page; 20 is a good default page length
The Swindle class should contain the following methods:
-
Constructor (
__init__)-
Part of this has been implemented for you, and you are responsible for completing the implementation. This constructor includes functionality for loading the database of available books). We recommend you read the code provided to familiarize yourself with the instance variables of the Swindle class and how they are initialized. You only need to add a few lines of code where the "bookdb.txt" file is opened and read in to create the list of available books; specifically, you should add code where you see the comment:
#TODO: using the information just obtained from the file, # create a Book object with this data and add it to the # available_books listWhen first creating a
Swindleobject, the user does not own any books (i.e., theowned_booksinstance variable is an empty list), and all books in the database should be considered books available for purchase (in theavailable_booksinstance variable).
-
-
display_text(…),display_page(…),get_letter(…)(provided for you)-
These three methods are provided for you and you should not modify them. You will need to make use of the
display_text(…)method in other parts of your code for this lab. Thedisplay_text(…)method takes one parameter (other thanself), which is a book object which is to be read; it then displays the text of the book, one page at a time. After each page is displayed, the user has the option of choosing to go to the next page, the previous page, or to quit. When the user selects the quit option,display_text(…)completes and returns the page number that was displayed last. You should use thedisplay_textmethod provided as-is, without modifying it.
-
-
get_owner(…)-
A getter method that returns the Swindle’s owner.
-
-
show_available(…)-
This should print all books that are available to purchase but are not yet owned. If there are no books available, print a short message saying so. See the sample output to see how the list is formatted. It takes no parameters and does not return anything.
-
-
buy(…)-
This allows the user to purchase a book. This method should print all available titles and allow the user to choose one book. Hint: use the
show_available()method as a helper. If the choice is valid, the purchased book should be added to the list of owned books and removed from the list of available books. You can use thepop()method of lists to accomplish this, as the following example demonstrates. You should print a message verifying that purchase was successful.$ python3 >>> ls = ["this", "is", "a", "test"] >>> word = ls.pop(1) #remove and return the item at index 1 >>> print(word) is >>> print(ls) ['this', 'a', 'test']
-
-
show_owned(…)-
This method takes no additional parameters (other than
self). It prints all currently owned books. If there are no currently owned books, it should print a short message saying so. See the example output for an example of how the list is formatted. The method should not return anything.
-
-
read(…)-
This method allows the user to choose a book to read. It takes no additional parameters (other than
self). This method should print all owned books and allow the user to choose one book. Hint: Use theshow_owned()method as a helper. It then calls thedisplay_text()method, using the appropriateBookobject as an argument todisplay_text(). When the user quits reading a book, set the bookmark to the current page, and print a message verifying that the bookmark was set.
-
For both the buy() and read() methods, you should validate the user’s
input to ensure that they select a valid book number from the menu. You can
assume that the user always enters an integer.
3.1. Incrementally test the Swindle class
As you complete each method of the Swindle class you should test it. Test code
should be placed in main. The below is an example of some test code you might
include at the end of your file, but you should include more test cases than
this as well:
def main():
print("Testing the Swindle class...")
myswindle = Swindle()
print("Testing show_available...")
myswindle.show_available()
print("Testing show_owned...")
myswindle.show_owned()
################ Write additional tests below ###################
if __name__ == '__main__':
main()
4. Putting it all together
Once you have written (and thoroughly tested!) the Book and
Swindle classes, according to the above requirements, you should be
able to successfully run the main e-reader program: $ python3
ereader.py
Here again is the sample run demonstrating how the e-reader should behave.
Test out all of features of your classes by trying every possible menu option from the e-reader. Be sure to gracefully handle these edge cases:
-
when the user wants to buy books, but none are available
-
when the user wants to read books, but none are available
Additional sample output showing various edge cases is here: edge cases sample run.
5. Requirements
|
The code you submit for labs is expected to follow good style practices, and to meet one of the course standards, you’ll need to demonstrate good style on six or more of the lab assignments across the semester. To meet the good style expectations, you should:
In addition, you’ll need to demonstrate good top-down design practices on two or more lab assignments across the semester. To meet the top-down design expectations, you should:
|
Your program should meet the following requirements:
-
Your implementation of the
Bookclass should be in the filebook.pyand its methods should behave as described in section 2 above. -
Your implementation of the
Swindleclass should be in the fileswindle.pyand its methods should behave as described in section 3 above. -
Your
book.pyandswindle.pyfiles should contain tests of the methods you’ve written, as mentioned in section 1 and as described in sections 2.1 and 3.1. -
Your implementation of the
buyandreadmethods should perform basic input validation, and re-prompt the user if they enter an invalid number (either negative, or larger than the number of books) -
Your implementation of the
readmethod should update the bookmark of the book when the user finishes reading. -
When using
Bookobjects from within theSwindleclass, you should not access instance variables directly, but rather only get or modify them using getter and setter methods. -
The
show_ownedandshow_availablemethods of theSwindleclass should display a message if there are no books that are owned (forshow_owned) or available to purchase (forshow_available). -
The
show_ownedandshow_availablemethods should display a numbered list of books. -
The
readandbuymethods should allow the user to enter0in order to return to the main menu without reading or buying anything. -
In the
read, method, if no books are owned, a message should be displayed saying this and the user should not be prompted to select a book to read. Similarly, in thebuymethod, if there are no books available for purchase, a short message should be displayed and the user should not be prompted to select a book to buy.
6. Optional Enhancements
This section is completely optional. Only attempt this once you have successfully completed all of the required portions of the lab. Because this is optional extension, we are leaving a lot of the details for you to figure out on your own.
If you wish to attempt this part, be sure to make copies of your orignal code as shown below. Only modify the copies, leaving the original solution alone.
$ cd cs21/labs/10 $ cp swindle.py enhancedSwindle.py $ cp ereader.py enhancedEreader.py
The original version of the e-reader starts from scratch every time, which is unrealistic. Instead, we would like the e-reader to remember the owner’s name, the books that have been purchased (and what page number the owner is on), as well as the books that are still available.
Here are several sample runs demonstrating how the enhanced e-reader should behave.
To accomplish this you will need to learn how to write to a file. Below is a short example program showing how to do this.
def main():
name = "Sedi"
age = 21
fp = open("test.txt", "w")
fp.write("%s is %d years old\n" % (name, age))
fp.write("this is a test\n")
fp.write("here's another line\n")
fp.write("goodbye\n")
fp.close()
main()
Here’s what the file test.txt would now contain:
Sedi is 21 years old this is a test here's another line goodbye
One other new python feature you’ll need is to be able to check whether a particular file exists or not. You can try this in the python intepreter:
$ python3
>>> from os.path import isfile
>>> isfile("ereader.py")
True
>>> isfile("foobar")
False
Now that you know how to write a file and how to check whether a file exists, you are ready to implement the enhanced e-reader. The basic idea is as follows.
-
Every time the user quits the e-reader, write out a status file that stores all of the relevant information about the e-reader. You can decide on the format for this status file.
-
When you start up the e-reader, check if this status file exists.
-
If not, everything works as it did in the original e-reader; you’ll need to read in the available books from the file
bookdb.txt. -
If it does exist, welcome the owner back and read in the status information from the file you created (you can ignore the file
bookdb.txt).
Without too much effort you’ll have an enhanced e-reader that is much more like an actual device.
7. Answer the Questionnaire
After each lab, please complete the short Google Forms questionnaire. Please select the right lab number (Lab 11) from the dropdown menu on the first question.
Once you’re done with that, you should run handin21 again.
Submitting lab assignments
Remember to run handin21 to turn in your lab files! You may run handin21
as many times as you want. Each time it will turn in any new work. We
recommend running handin21 after you complete each program or after you
complete significant work on any one program.
Logging out
When you’re done working in the lab, you should log out of the computer you’re using.
First quit any applications you are running, including your vscode editor, the
browser and the terminal. Then click on the logout icon
(
or
) and
choose "log out".
If you plan to leave the lab for just a few minutes, you do not need to log
out. It is, however, a good idea to lock your machine while you are gone. You
can lock your screen by clicking on the lock
icon.
PLEASE do not leave a session locked for a long period of time. Power may go
out, someone might reboot the machine, etc. You don’t want to lose any work!