## Monday

### Objects

So far we have used lots of objects and their methods. Remember an object consists of both data and methods (functions). Here are some examples of objects we have used:

• str objects: data = characters in the string; methods: `lower()`, `split()`, `isalpha()`, etc

• list objects: data = item in the list; methods: `sort()`, `append()`, `pop()`, etc

• Zelle Circle objects: data = radius, center point; methods: `draw()`, `move()`, `setFill()`, etc

#### example object: NBAPlayer

If we were storing statistics for NBA basketball players, we could use parallel lists: a list of player names, a list of points-per-game for each player, and a list of games played for each player:

```players = ["Vladimir Radmanovic","Lou Williams","Lebron James", "Kevin Durant","Kobe Bryant","Kevin Garnett"]
ppg     = [0, 11.5, 30.3, 28.5, 30.0, 19.2]  # points-per-game
games   = [2, 13, 23, 20, 12, 20]            # games played```
```for i in range(len(players)):
print("%d: %20s %4.1f %2d" % (i, players[i], ppg[i], games[i]))```

This works but is klunky. And it would be worse if we tried to store more stats (rebounds, assists, blocked shots, etc) and more players (400+ for the NBA).

A better approach would be to create a custom object (an NBAPlayer object) and store all the data for that player in that object. Then we could just have one list of NBAPlayer objects in our program. We could also write custom methods to use on those objects — anything we think we might need, such as a `ppg()` method to calculate a player’s average points-per-game, or a `playGame()` method to record new data when a player plays another game.

Here’s an example of how this class could be used:

```>>> from nbaplayer import *
>>> p1 = NBAPlayer("Jeff Knerr", "phi")
>>> print(p1)
Jeff Knerr -- Team: phi  GP:   0,  PTS:   0,  PPG:  0.0
>>> print(p1.ppg())
0
>>> p1.playGame(40)
>>> print(p1)
Jeff Knerr -- Team: phi  GP:   1,  PTS:  40,  PPG: 40.0
>>> p1.playGame(20)
>>> print(p1)
Jeff Knerr -- Team: phi  GP:   2,  PTS:  60,  PPG: 30.0
>>> print(p1.ppg())
30.0```

Notice how new objects are constructed: given a name and a team name. When the player object is first constructed and printed, the stats are initially zero. And after the `playGame()` method is called twice (Jeff plays two good games!), the stats have changed.

### syntax of classes

To create the custom object above, we will need to define a class. Think of the class definition as a template: you want to create custom objects, and the class definition says how they are to be constucted, what data is stored in each object, and what methods can be applied to these objects. Once you have your class definition, you can create as many objects of that type as you want.

Here is some of the `NBAPlayer` class used above — we will look at what it all means below.

``````class NBAPlayer(object):
"""class for single NBA player object"""

def __init__(self, name, team, gp=0, pts=0):
"""constructor for player object, given name, etc"""
self.name = name
self.team = team
self.gp  = gp         # games played
self.pts = pts        # total points scored

def __str__(self):
"""
if you print one of these objects, this is how it will look.
"""
s = "%20s -- Team: %3s" % (self.name, self.team)
s += "  GP: %3d,  PTS: %3d,  PPG: %4.1f" % (self.gp, self.pts, self.ppg())
return s

def playGame(self, points):
"""example of adding data to player object"""
self.gp += 1
self.pts += points

def ppg(self):
"""calculate player's average points per game"""
if self.gp == 0:
return 0
else:
ave = self.pts/float(self.gp)
return ave``````

Notes on the above class definition:

• the `__init__` method is called the constructor

• the constructor can have 1 or more parameters (the above has 5, but one is special)

• the first parameter in all of the methods is self, and this refers back to whatever object we are applying the method to (more about this below)

• in a program that uses this class, the constructor is called by using the name of the class (e.g., `p1 = NBAPlayer("Jeff Knerr", "phi")`)

• all `self.whatever` variables (called instance variables) in the constructor are the data stored in each object

• the `__str__` method is used whenever an object is printed (e.g., `print(p1)`)

• the `__str__` method should return a string (not print a string)

• when you design a new object, you can write the constructor to have as many parameters as you want, have as many instance variables as you need, have the object print however you want (using the `__str__` method), and have as many other methods as you see fit!

• if this were in a file called `nbaplayer.py`, we could use it in other programs by adding `from nbaplayer import *` to the other programs

#### that `self` parameter

Notice that the constructor above has 5 parameters (self, name, team, gp=0, pts=0), but when it is called in the program (`p1 = NBAPlayer("Jeff Knerr", "phi")`), only 2 arguments are used. The argument that corresponds to the `self` parameter is always implied. The `self` parameter simply refers back to which object we are talking about (e.g., `p1`).

Also, the last two parameters (gp=0, pts=0) are a way to include default arguments. When constructing objects, if they are specified, they will have those values (ex: `p1 = NBAPlayer("Jeff","phi",10,200)`), and if not specified, they will have the default values (in this case, both zero).

If `self` is confusing to you, you are not alone! For now, just make sure all methods in the class definition have `self` as their first parameter.

Also notice, any `self.whatever` variable created in `__init__` can be used in all other methods in the class, without being passed as parameters. For example, the `playGame()` method updates `self.gp` and `self.pts`.

Suppose we want to add some additional functionality to our `NBAPlayer` class (maybe we are creating the software behind nba.com or espn.com/nba). Players are often traded from one team to another, so we would like to be able to do something like this:

```>>> from nbaplayer import *
>>> p1 = NBAPlayer("Jeff Knerr", "phi")
>>> p1.playGame(20)
>>> p1.playGame(10)
>>> p1.playGame(3)     # Jeff not playing well....let's trade him
>>> print(p1)
Jeff Knerr -- Team: bos  GP:   3,  PTS:  33,  PPG: 11.0```

Can you add the `trade()` method to the above class? As used above, it has one argument (the new team), so the method should have two parameters: `self` and some variable to hold the value of the new team (maybe `newteam`??). And the only thing this method does is change the value of the `self.team` instance variable.

Here’s the new method:

``````    def trade(self, newteam):
"""change team of player"""
self.team = newteam``````

#### adding a new instance variable

What needs to change if we want to keep track of another statistic, such as number of rebounds? That requires another instance variable (`self.rebounds`) in the constructor, as well as modifying the `playGame()` method (add a `rebounds` parameter, and update `self.rebounds`). And like `ppg()`, we might want to make a whole new method (`rpg()`?) to calculate and return the average rebounds-per-game. You may also want to change the `__str__` method to include the rebounding stats.

### write the `Cake(..)` class

Write a `Cake` class that works with the following test code:

```>>> from cake import *
>>> c1 = Cake("Chocolate")
>>> c1.slice(8)
>>> print(c1)
Chocolate cake (slices left: 8).
>>> c2 = Cake("Carrot")
>>> c2.slice(12)
>>> print(c2)
Carrot cake (slices left: 12).
>>> c1.serve()
Here's your slice of Chocolate cake!
>>> print(c1)
Chocolate cake (slices left: 7).```

And make sure your cake class accounts for running out of slices!

```>>> for i in range(10):
...    c1.serve()
...
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
Here's your slice of Chocolate cake!
No Chocolate cake left! :(
No Chocolate cake left! :(
No Chocolate cake left! :(```

## Wednesday

### finish the cake class

What if we want to prevent slicing the cake twice? Can you check to see if the cake was already sliced??

```>>> from cake import *
>>> c = Cake("Carrot")
>>> c.slice(8)
>>> c.slice(10)

One way to do this is to add a new instance variable, to keep track of if the cake has already been sliced.

Here’s an example of the full `Cake` class:

``````class Cake(object):
"""cake class"""

def __init__(self, flavor):
"""constructor for cake objects, given flavor"""
self.flavor = flavor
self.slices = 0
self.sliced = False   # initially, cakes have not been sliced

def __str__(self):
"""must RETURN A STRING!!!"""
if self.sliced:
s = "%s cake (slices left: %d)." % (self.flavor,self.slices)
else:
s = "%s cake (unsliced)." % (self.flavor)
return s

def slice(self, nslices):
"""cut the cake into nslices"""
if self.sliced:
elif nslices<1 or nslices>50:
print("That's a crazy number of slices!")
else:
self.slices = nslices
self.sliced = True

def serve(self):
"""serve out one slice of cake"""
if self.slices > 0:
print("Here's your slice of %s cake!" % self.flavor)
self.slices -= 1
else:
print("No %s cake left! :( " % self.flavor)

def getFlavor(self):
"""getter for flavor data"""
return self.flavor

def getSlices(self):
"""getter for slice data"""
return self.slices``````

### zturtle class

Turtle graphics is another way to draw things, but instead of graphics objects (like Circles and Rectangles), we control a pen (or a turtle), and tell it to move forward or change it’s heading. It’s called turtle graphics because, like a turtle walking across the sand, it leaves a trail.

Here’s and pseudo-code example of drawing a square using turtle graphics:

```create a turtle object
for i in range(4):
move forward (and draw) a certain amount
turn by 90 degrees```

Let’s create a turtle object using the Zelle Graphics Library.

If you run `update21`, you should get a `zturtle.py` file. That file includes a `ZTurtle` class with a contructor and the `forward(..)` function. Your job is to finish the Zturtle class — write any of the methods below that aren’t already done (see the `zturtle.py` file). Also, see `testturtle.py` for an example of how the `ZTurtle` object is used. And start by writing the methods that are needed in that file (`turn(degrees)` and `setColor(color)`).

Here’s the full documentation for the `ZTurtle` class:

```class ZTurtle(object)
|  Zelle Graphics Turtle
|
|  __init__(self, x, y, window)
|      construct new turtle object with given x,y position
|
|  __str__(self)
|      return a string with turtle info (x,y,heading,tail status)
|
|  down(self)
|      lower the tail
|
|  forward(self, ds)
|      move forward a distance ds, draw if tail is down
|
|
|  getX(self)
|      return the current x coordinate
|
|  getY(self)
|      return the current y coordinate
|
|  moveto(self, x, y)
|      move turtle, without drawing, to given x,y location
|
|  setColor(self, color)
|      set the drawing color
|
|
|  turn(self, amount)
|      change the heading by the given amount (degrees)
|
|  up(self)
|      raise the tail```

Once you have the zturtle class working, edit/try a few of these programs (they each have starter code in them, you just need to finish them):

`testturtle.py`

`spiral.py`

`boxspiral.py`

`star.py`

`walk.py`

`tree.py`

`koch.py`

## Friday

No class — Thanksgiving Break!

## Swatify Music App

Suppose we are writing a music app that has songs and playlists. We want the user to be able to make and edit playlists of songs, and, obviously, play the songs on the playlist.

Here’s a simplified example of how the objects could be used (see `swatify.py`):

``````from song import *
from playlist import *

def main():
p1 = Playlist("Jeff's Awesome Playlist")
# show initial, empty playlist
print(p1)

print(p1)

# toggle the shuffle, play the playlist
p1.toggleShuffle()
p1.play()

"""read in song data from file, one song per line"""
#title, running time, artist, date-of-purchase, number-of-plays
songs = []
inf = open(fn,"r")
for line in inf:
data = line.strip().split(",")
title = data[0]
rtime = data[1]
artist = data[2]
dop = data[3]
nplays = data[4]
# all song objects have a song title, artist, running time, and number of plays
s = Song(title,artist,rtime,int(nplays))
songs.append(s)
inf.close()
return songs

main()``````

And here is the output of the above code (just an example of how it might look):

```$python3 swatify.py Playlist: Jeff's Awesome Playlist -- total running time: 0hr:0min:0sec [shuffle=False, public=True] No songs in this playlist... Playlist: Jeff's Awesome Playlist -- total running time: 0hr:10min:25sec [shuffle=False, public=True] 1: The A Team by Ed Sheeran 2: Almost Like Being In Love by Frank Sinatra 3: Badlands by Bruce Springsteen playing Badlands...by Bruce Springsteen playing The A Team...by Ed Sheeran playing Almost Like Being In Love...by Frank Sinatra``` We’ll start by making the Song and Playlist classes. ### the `Song(..)` class Each Song object should have these instance variables: • title: title of the song • artist: song artist • running time: length of the song — a string, like '3:05' • number of plays: how many times the song has been played (an integer) Here’s the full documentation for the Song class: ```class Song(builtins.object) | Song object class | | Methods defined here: | | __init__(self, title, artist, rtime, nplays) | constructor, given title, artist, running time, number of plays | | __str__(self) | should return a string with song info | | getArtist(self) | getter for song artist | | getPlays(self) | getter for number of times song has been played | | getRTime(self) | getter for running time of song (string, like '4:02') | | getSeconds(self) | getter for running time of song in seconds (int, like 242) | | getTitle(self) | getter for title of song | | play(self) | dummy 'play' method, just says playing song and calls sleep(1) | (also increments number of plays)``` If you look in the `songs.csv` file, you’ll see each song in there has a title, a running time (a string, like '5:37'), an artist, a date-of-purchase (which we are not using), and a number (integer) for how many times the song has been played. Your job is to add the above class and methods to `song.py`, so we can create Song objects from the (fake) data in songs.csv. Here is some test code you could use to make sure your Song class is working: `````` s = Song("A","B","4:30",10) print(s) s.play() print(s) print(s.getTitle()) print(s.getArtist()) print(s.getRTime()) print(s.getPlays()) print(s.getSeconds())`````` And running it would show this: ```$ python3 song.py
A by B (4:30, 10)
playing A...by B
A by B (4:30, 11)
A
B
4:30
11
270```

The only tricky method is `getSeconds()` which returns the running time as an integer number of seconds. In the above example, the running time is '4:30', which `getSeconds()` uses to calculate and return the total number of seconds (270).

### the `Playlist(..)` class

Once you have the `Song` class working, write the playlist class.

The user should be able to create a new playlist, given the title of the playlist. They should also be able to add songs to the playlist. All Playlist objects should have the following instance variables:

• title: title of the playlist

• songs: list of songs in the playlist (initially empty)

• time: running time of full playlist (all songs) in seconds (initially 0)

• public: boolean for playlist status (defaults to True (public playlist))

• shuffle: boolean for playing the songs (initially False, so play in order)

Here’s the documentation for the Playlist objects:

```class Playlist(builtins.object)
|  Playlist class
|
|  Methods defined here:
|
|  __init__(self, title)
|      constructor for Playlist objects, given title of playlist
|
|  __str__(self)
|      should return a string with playlist info
|
|      add given song to the playlist
|
|  play(self)
|      play the playlist (call each song's play method)
|
|  toggleShuffle(self)
|      toggle the shuffle instance variable (boolean)```

And here’s some test code to check your playlist objects:

``````  p1 = Playlist("Best Playlist Ever")
print(p1)
s = Song("A","B","4:30",10)
print(p1)``````

and the output (depending on how you wrote your str method):

```Playlist: Best Playlist Ever -- total running time: 0hr:0min:0sec
[shuffle=False, public=True]
No songs in this playlist...

Playlist: Best Playlist Ever -- total running time: 0hr:9min:0sec
[shuffle=False, public=True]
1: A by B
2: A by B```

### the swatify app

Once you have your song and playlist objects working, try the `swatify.py` app to make sure it works.

### if you have time

Add a menu() function to the swatify app to give the user 4 options: add, show, play, quit. Here’s one example of how this could work:

```$python3 swatify.py add|show|play|quit What do you want to do? show Playlist: Jeff's Awesome Playlist -- total running time: 0hr:0min:0sec [shuffle=False, public=True] No songs in this playlist... add|show|play|quit What do you want to do? add 1. The A Team by Ed Sheeran 2. A.M. Radio by Everclear 3. Almost Like Being In Love by Frank Sinatra 4. Animal Song by Loudon Wainwright III 5. Badlands by Bruce Springsteen 6. Breakdown by Tom Petty & The Heartbreakers 7. Do You Feel by The Rocket Summer 8. Everyday Superhero by Smash Mouth 9. Gonna Fly Now by Bill Conti 10. It's On by Superchick 11. Like a Rolling Stone by Bob Dylan 12. Message In A Bottle by The Police 13. Since U Been Gone by Kelly Clarkson 14. Sweet Caroline by Glee Cast 15. Take It Easy by Eagles 16. This Is The Life by Hannah Montana 17. Welcome To The Machine by Pink Floyd 18. You Get What You Give by New Radicals Which song do you want to add? 5 add|show|play|quit What do you want to do? add 1. The A Team by Ed Sheeran 2. A.M. Radio by Everclear 3. Almost Like Being In Love by Frank Sinatra 4. Animal Song by Loudon Wainwright III 5. Badlands by Bruce Springsteen 6. Breakdown by Tom Petty & The Heartbreakers 7. Do You Feel by The Rocket Summer 8. Everyday Superhero by Smash Mouth 9. Gonna Fly Now by Bill Conti 10. It's On by Superchick 11. Like a Rolling Stone by Bob Dylan 12. Message In A Bottle by The Police 13. Since U Been Gone by Kelly Clarkson 14. Sweet Caroline by Glee Cast 15. Take It Easy by Eagles 16. This Is The Life by Hannah Montana 17. Welcome To The Machine by Pink Floyd 18. You Get What You Give by New Radicals Which song do you want to add? 10 add|show|play|quit What do you want to do? show Playlist: Jeff's Awesome Playlist -- total running time: 0hr:7min:32sec [shuffle=False, public=True] 1: Badlands by Bruce Springsteen 2: It's On by Superchick add|show|play|quit What do you want to do? play playing Badlands...by Bruce Springsteen playing It's On...by Superchick add|show|play|quit What do you want to do? quit$```

### if you have lots of time

What other features would you add to this app and the classes? Here are some ideas:

• delete(song) method for the playlist class (allow user to delete a song from a playlist)

• edit(playlist) function for the swatify app (allow user to select songs to move around or delete, or add more songs)

• toggle method for the `self.public` instance variable in the playlist class

• add a search(string) function to swatify app to allow user to search for songs (e.g., show all songs by a given artist), then have the option of adding them to a playlist

### make it really play sound

Let’s add a real `play()` method to the Song class!

I put a new `songs.csv` file and some mp3 files in `/scratch/knerr/swatify`. You can see these with this command: `ls /scratch/knerr/swatify`

And you can see the songs.csv file with this (in a terminal):

`cat /scratch/knerr/swatify/songs.csv`

If you do that, you’ll notice an additional item on each line: an mp3 file. We need to modify the `jk_song.py` file to add this additional item:

1. add an extra parameter to __init__:

`` def __init__(self,title,artist,rtime,nplays,audiofile):``
2. and add an extra instance variable to init:

`` self.audiofile = audiofile``
3. add "import subprocess" at the top of the file (so we can call unix commands from python)

4. change the `play()` method to do this instead of sleeping:

`` subprocess.call("mplayer %s" % self.audiofile, shell=True)``

The `subprocess.call()` line just runs the `mplayer` command on the given audiofile (just like we did on the command line).

Now try running `jk_swatify.py` to see if it works

Also note, if you try this on other lab computers, sound may or may not work. Here’s a help page we have if you are trying to get sound to work on the CS lab machines (see the "Room 256" section): audio help page