"""Module containing :class:`~song_match.song.song.Song`."""
from abc import ABC, abstractmethod
from typing import List
from cozmo.lights import Light
from song_match.cube_mat import CubeMat
from .note import Note
[docs]class Song(ABC):
"""Abstract base class for songs.
Currently only supports songs with 3 notes.
Must override **4** abstract properties:
1. ``_notes`` - A list of 3 :class:`~song_match.song.note.Note` instances in ascending order by pitch.
2. ``_sequence`` - A sequence of :class:`~song_match.song.note.Note` instances that make up the song.
3. ``_cube_lights`` - A list of 3 :class:`~cozmo.lights.Light` instances.
4. ``_difficulty_markers`` - A list of indices where the song ramps up in difficulty.
"""
[docs] def get_note(self, cube_id: int) -> Note:
"""Get the :class:`~song_match.song.note.Note` for a corresponding cube.
:param cube_id: :attr:`~cozmo.objects.LightCube.cube_id`
:return: The :class:`~song_match.song.note.Note` of the cube.
"""
mat_position = CubeMat.cube_id_to_position(cube_id)
index = self._get_index(mat_position)
return self._notes[index]
[docs] def play_note(self, cube_id: int) -> None:
"""Play the note for a corresponding cube.
:param cube_id: :attr:`~cozmo.objects.LightCube.cube_id`
:return: None
"""
note = self.get_note(cube_id)
return note.play()
[docs] def get_cube_light(self, cube_id: int) -> Light:
"""Get the :class:`~cozmo.lights.Light` for a corresponding cube.
:param cube_id: :attr:`~cozmo.objects.LightCube.cube_id`
:return: :class:`~cozmo.lights.Light` for the corresponding cube.
"""
index = self._get_index_from_mat_position(cube_id)
return self._cube_lights[index]
[docs] def get_cube_id(self, note: Note) -> int:
"""Get the Cube ID for a corresponding note.
:param note: The :class:`~song_match.song.note.Note` of the song.
:return: :attr:`~cozmo.objects.LightCube.cube_id`
"""
cube_id = self._notes.index(note) + 1
return CubeMat.cube_id_to_position(cube_id)
[docs] def get_sequence(self) -> List[Note]:
"""Get the sequence of notes.
:return: A sequence of notes.
"""
return self._sequence
[docs] def get_sequence_slice(self, end: int) -> List[Note]:
"""Get a slice of the sequence up to and including end.
:param end: The end position of the sequence.
:return: A sequence of notes up until a certain position.
"""
return self._sequence[0:end]
[docs] def is_not_finished(self, position: int) -> bool:
"""Returns whether or not the song is finished based upon the position in the sequence.
:param position: The position in the sequence of notes.
:return: True if the song is not finished. False otherwise.
"""
return not self.is_finished(position)
[docs] def is_finished(self, position: int) -> bool:
"""Returns whether or not the song is finished based upon the position in the sequence.
:param position: The position in the sequence of notes.
:return: True if the song is finished. False otherwise.
"""
return position > self.length
[docs] def get_difficulty_markers(self) -> List[int]:
"""Markers which determine at what position the song ramps up in difficulty.
There are two difficulty markers:
1. Medium
2. and Long
The game starts incrementing by 1 note at a time.
Once the game reaches medium, it increments by 2 notes at a time.
Once the game reaches long, it increments by 3 notes at a time.
:return: A list of difficulty markers.
"""
return self._difficulty_markers
[docs] def get_medium_difficulty_marker(self) -> int:
"""Get the medium difficulty length marker.
:return: Medium difficulty marker.
"""
medium_marker, long_marker = self._difficulty_markers
return medium_marker
[docs] def get_long_difficulty_marker(self) -> int:
"""Get the long difficulty length marker.
:return: Long difficulty marker.
"""
medium_marker, long_marker = self._difficulty_markers
return long_marker
[docs] def is_sequence_long(self, sequence_length: int) -> bool:
"""Get whether the length of a sequence is long.
"Long" is defined as being greater than the medium difficulty marker.
:param sequence_length: The length of a sequence of notes.
:return: Whether the sequence is long
"""
long_difficulty_marker = self.get_long_difficulty_marker()
return sequence_length > long_difficulty_marker
@property
def length(self) -> int:
"""Property for accessing the length of the song.
:return: The length of the song.
"""
return len(self._sequence)
@staticmethod
def _get_index(cube_id: int):
return cube_id - 1
@classmethod
def _get_index_from_mat_position(cls, cube_id: int):
mat_position = CubeMat.cube_id_to_position(cube_id)
return cls._get_index(mat_position)
@property
@abstractmethod
def _notes(self) -> List[Note]:
"""Returns a list of 3 notes in ascending order by pitch."""
@property
@abstractmethod
def _sequence(self) -> List[Note]:
"""Returns a sequence of notes that make up the song."""
@property
@abstractmethod
def _cube_lights(self) -> List[Light]:
"""A list of 3 lights for each cube."""
@property
@abstractmethod
def _difficulty_markers(self) -> List[int]:
"""A list of indices where the song ramps up in difficulty."""