Classic Jukebok OOD
Requirements¶
Simulate a classic jukebox, where user can insert coin and play songs using by choosing the Albhabet and Number for that song from display board. Check this demo : https://www.youtube.com/watch?v=hcE7UBznqTE Machine administrator should be able to add and remove songs and also should be able to know popularity of songs played.
Main ideas here :
- KISS : Start with very simple structure
- Decompose what a classic jukebox means
- Comeup with classes which represent various components
- Analyse how various components work together
- Compose them back and simulate runing it via a client program
Actors and use cases¶
- Customer
- Puts a coin and gets some credit
- Chooses to play a song (a record disk) from selection
- Can enqueue more songs till there are credits
- Admin
- Adds/removes songs
- Sets up which song is played for a selection
- Can check the popularity of songs
Classes and their responsibilities¶
- RecordPlayer
- play(record)
- RecordSelector
- handles user selection input and gives out corresponsing record to record player to play
- get_record(user_selection)
- Playlist
- enqueue()
- dequeue()
- Jukebox
- makes sure all components work together
- takes input from user, passes to record selector, gets the record, gives to record player
- when song ends removes the cd and if there is another song in playlist plays it or goes in standby mode
- takes coin
- takes song selection
- checks popularity, adds record, removes record, prints selections
- JukeboxCLIDriver
- Simulate Jukebox on CLI
You can run this notebook at : https://colab.research.google.com/drive/12J9_Le_Uen0p-yZMtq0IMbRBq21NUekF
In [46]:
from collections import deque
import time
import enum
class Record(object):
def __init__(self, song_name, artist_name):
self.song_name = song_name
self.artist_name = artist_name
def __repr__(self):
return '[ Song name : %s, Singer : %s ]'%(self.song_name, self.artist_name)
class RecordPlayer(object):
def set_observer(self, observer):
self.observer = observer
def play(self, record):
print('Playing "%s" , sung by "%s"\n' % (record.song_name, record.artist_name))
time.sleep(5)
print('Finished Playing %s.\n' % record.song_name)
self.observer.notify_record_finish()
class RecordSelector(object):
def __init__(self, input_record_map):
self.input_record_map = input_record_map
def get_record(self, selection):
print('Selecting record for selection : %s' % selection)
if selection not in self.input_record_map:
return None
return self.input_record_map[selection]
class Playlist(object):
def __init__(self):
self.records = deque([])
def enqueue(self, record):
self.records.append(record)
def deque(self):
return self.records.popleft()
def size(self):
return len(self.records)
class JukeboxState(enum.Enum):
START = 0
AWAIT_COIN = 1
AWAIT_SELECTION = 2
PLAYING_SONG = 3
# TODO - make Jukebox implement an observer interface
class Jukebox(object):
def __init__(self, playlist, record_selector, record_player, admin_key):
self.playlist = playlist
self.record_selector = record_selector
record_player.observer = self
self.record_player = record_player
self.admin_key = admin_key
self.state = JukeboxState.AWAIT_COIN
self.songs_allowed = 0
self.earning = 0
self.popularity_counter = {}
def __get_coin_value(self, coin):
# find out the value of the coin
return coin
def __get_allowed_songs(self, coin):
# find out how many songs can be allowed with these many coing
# for simplicity lets assume that it maps one to one
return coin
def put_coin(self, coin):
self.songs_allowed = self.__get_allowed_songs(coin)
self.earning = self.earning + self.__get_coin_value(coin)
self.state = JukeboxState.AWAIT_SELECTION
def input_selection(self, selection):
# here we assume that user can give a selection while one song is
# already playing
record = self.record_selector.get_record(selection)
if self.state == JukeboxState.AWAIT_SELECTION:
self.songs_allowed = self.songs_allowed - 1
self.record_player.play(record)
self._increment_popularity(record)
self.state == JukeboxState.PLAYING_SONG
elif self.state == JukeboxState.PLAYING_SONG:
self.songs_allowed = self.songs_allowed - 1
self.playlist.enqueue(record)
else:
raise Exception('Cant make a selection without having putting coin')
def notify_record_finish(self):
if self.playlist.size() == 0:
if self.songs_allowed == 0:
self.state = JukeboxState.AWAIT_COIN
else:
self.state = JukeboxState.AWAIT_SELECTION
else:
record = self.playlist.dequeue()
self.record_player.play(record)
def stop(self):
raise NotImplementedError('cant stop in a single threaded simulation!')
def check_popularity(self):
popularity_list = [(k, v) for k, v in self.popularity_counter.items()]
sorted_popularity = sorted(popularity_list, key=lambda x : -x[1])
return {'top' : sorted_popularity[:5], 'bottom' : sorted_popularity[-5:]}
def _increment_popularity(self, record):
if record not in self.popularity_counter:
self.popularity_counter[record] = 1
else:
self.popularity_counter[record] = self.popularity_counter[record]+1
In [ ]:
class JukeboxCLIDriver(object):
def __init__(self):
playlist = Playlist()
self.record_map = self._build_record_map()
record_selector = RecordSelector(self.record_map)
record_player = RecordPlayer()
self.admin_key = '007'
self.jukebox = Jukebox(playlist, record_selector, record_player, self.admin_key)
record_player.set_observer(self.jukebox)
def _build_record_map(self):
a1 = Record('Ek do teen', 'Alka Yagnik')
b3 = Record('Zindagi Ki Yahi Reet Hai', 'Kishore Kumar')
return {'a1' : a1, 'b3' : b3}
def _handle_admin(self):
while True:
action = input('Choose action : 1)see popularity, 2)add record, 3)remove record, 4)see selections 5)Exit admin mode')
if action == '1':
print(self.jukebox.check_popularity())
elif action == '2':
song_name = input('Name of song')
singer_name = input('Name of singer')
selector_key = input('Selector key')
new_record = Record(song_name, singer_name)
self.record_map[selector_key] = new_record
elif action == '3':
selector_key = input('Selector key for song to be removed')
del self.record_map[selector_key]
elif action == '4':
self._print_selections()
elif action == '5':
return
def _handle_user_coin_action(self):
money = input('Enter coin (enter to reset): ')
if money == '':
self.jukebox.state = JukeboxState.START
return
self.jukebox.put_coin(int(money))
self._handle_user_selection_action()
def _print_selections(self):
print('--------------------------------------\n')
for key, rec in self.record_map.items():
print('[ %s ] [ %s ]' % (key, rec))
print('--------------------------------------\n')
def _handle_user_selection_action(self):
print('Here are our records, make a selection : \n')
self._print_selections()
selection = input('Select song by key : ')
self.jukebox.input_selection(selection)
def _handle_user(self):
while True:
if self.jukebox.state == JukeboxState.AWAIT_COIN:
self._handle_user_coin_action()
elif self.jukebox.state == JukeboxState.AWAIT_SELECTION:
self._handle_user_selection_action()
elif self.jukebox.state == JukeboxState.START:
return
def start(self):
while True:
maybe_admin_key = input('Enter key if admin otherwise press enter : ')
if maybe_admin_key == self.admin_key:
self._handle_admin()
else:
self.jukebox.state = JukeboxState.AWAIT_COIN
self._handle_user()
if __name__ == '__main__':
driver = JukeboxCLIDriver()
driver.start()
In [ ]:
No comments:
Post a Comment