diff --git a/lesson_07/homework/my_music_band/README.md b/lesson_07/homework/my_music_band/README.md index b58fab1..ab913da 100644 --- a/lesson_07/homework/my_music_band/README.md +++ b/lesson_07/homework/my_music_band/README.md @@ -2,22 +2,26 @@ Давайте соберём музыкальную группу и сыграем небольшой концерт :) -Группа (Band) состоит из трёх музыкантов: +Группа (Band) состоит из трёх музыкантов: + - вокалист (Vocalist), - гитарист (Guitarist), - и барабанщик (Drummer). У каждого музыканта своя партия, которая представлена списком звуков: + ```elixir sounds = [:BOOM, :Ts, :Doom, :Ts] ``` Музыкант изучает эту партию: + ```elixir {:ok, drummer} = Drummer.init(sounds) ``` И затем играет бесконечно по кругу: + ```elixir {:BOOM, drummer} = Drummer.next(drummer) {:Ts, drummer} = Drummer.next(drummer) @@ -29,6 +33,7 @@ sounds = [:BOOM, :Ts, :Doom, :Ts] ``` У каждого музыканта свой набор звуков, которые он может исполнить. Смотри `MyMusicBand.Model.Sound`. Если дать музыканту партию с другими звуками, то он не сможет её играть: + ```elixir sounds = [:BOOM, :Ts, :Doom, :BoBoom, :Ts, :Ts, :Woooom, :Ts] {:error, [{4, :BoBoom}, {7, :Woooom}]} = Drummer.init(sounds) @@ -37,6 +42,7 @@ sounds = [:BOOM, :Ts, :Doom, :BoBoom, :Ts, :Ts, :Woooom, :Ts] То есть, функция инициализации должна проверить все звуки, и вернуть список неправильных звуков с указанием их позиции в списке. (Позиции считаются от 1 а не от 0). Когда каждый музыкант готов играть свою партию: + ```elixir {:ok, vocalist} = Vocalist.init([:'A-a-a', :'O-o-o', ...]) {:ok, guitarist} = Guitarist.init([:A, :D, ...]) @@ -44,6 +50,7 @@ sounds = [:BOOM, :Ts, :Doom, :BoBoom, :Ts, :Ts, :Woooom, :Ts] ``` их можно собрать вместе: + ```elixir band = Band.init() @@ -53,6 +60,7 @@ band = ``` и играть все партии вместе: + ```elixir {[:'A-a-a', :A, :BOOM], band} = Band.next(band) {[:'O-o-o', :D, :Ts], band} = Band.next(band) @@ -60,6 +68,6 @@ band = При этом нужно учесть, что партии могут быть разной длины. -Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании). +Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании). Если считаете нужным, можете добавить ещё какие-то модули. diff --git a/lesson_07/homework/my_music_band/lib/band.ex b/lesson_07/homework/my_music_band/lib/band.ex index 28df48f..d8e89a2 100644 --- a/lesson_07/homework/my_music_band/lib/band.ex +++ b/lesson_07/homework/my_music_band/lib/band.ex @@ -1,9 +1,29 @@ defmodule MyMusicBand.Band do + alias MyMusicBand.Player - def init(), do: nil + defstruct players: [] - def add_player(arg1, arg2), do: nil + @type t() :: %__MODULE__{ + players: list(Player.t()) + } - def next(arg), do: nil + @spec init() :: t() + def init(), do: %__MODULE__{} -end + @spec add_player(t(), Player.t()) :: t() + def add_player(band, player) do + %{band | players: band.players ++ [player]} + end + + @spec next(t()) :: {list(atom), t()} + def next(band) do + {sounds, updated_players} = + Enum.map(band.players, fn player -> + player.__struct__.next(player) + end) + |> Enum.unzip() + + new_band = %{band | players: updated_players} + {sounds, new_band} + end +end diff --git a/lesson_07/homework/my_music_band/lib/drummer.ex b/lesson_07/homework/my_music_band/lib/drummer.ex index 02dff0e..d9f3341 100644 --- a/lesson_07/homework/my_music_band/lib/drummer.ex +++ b/lesson_07/homework/my_music_band/lib/drummer.ex @@ -1,7 +1,3 @@ defmodule MyMusicBand.Drummer do - - def init(arg), do: nil - - def next(arg), do: nil - -end + use MyMusicBand.Model.Musician, validator: &MyMusicBand.Model.Sound.is_drum/1 +end diff --git a/lesson_07/homework/my_music_band/lib/guitarist.ex b/lesson_07/homework/my_music_band/lib/guitarist.ex index b112984..eae3389 100644 --- a/lesson_07/homework/my_music_band/lib/guitarist.ex +++ b/lesson_07/homework/my_music_band/lib/guitarist.ex @@ -1,7 +1,3 @@ defmodule MyMusicBand.Guitarist do - - def init(arg), do: nil - - def next(arg), do: nil - -end + use MyMusicBand.Model.Musician, validator: &MyMusicBand.Model.Sound.is_guitar/1 +end diff --git a/lesson_07/homework/my_music_band/lib/model/musician.ex b/lesson_07/homework/my_music_band/lib/model/musician.ex new file mode 100644 index 0000000..bca5e6d --- /dev/null +++ b/lesson_07/homework/my_music_band/lib/model/musician.ex @@ -0,0 +1,49 @@ +defmodule MyMusicBand.Model.Musician do + @moduledoc """ + Defines the behavior for all musicians in the band. + """ + defmacro __using__(opts) do + validator_fun = Keyword.fetch!(opts, :validator) + + quote do + alias MyMusicBand.Model.Sound + @type t :: %{sounds: list(atom), current_stream: Enumerable.t()} + defstruct [:sounds, :current_stream] + + @spec init(Sound.sound()) :: + {:ok, t()} | {:error, {non_neg_integer(), atom()}} + def init(sounds) do + case validate_sounds(sounds) do + [] -> {:ok, %__MODULE__{sounds: sounds, current_stream: Stream.cycle(sounds)}} + errors_sounds -> {:error, errors_sounds} + end + end + + @spec next(t()) :: {Sound.sound(), t()} + def next(%{current_stream: stream} = musician) do + {sound, new_stream} = get_sound(stream) + new_musician = %{musician | current_stream: new_stream} + {sound, new_musician} + end + + defp get_sound(stream) do + case stream |> Stream.take(1) |> Enum.to_list() do + [sound] -> + {sound, stream |> Stream.drop(1)} + + _ -> + {nil, stream} + end + end + + defp validate_sounds(sounds) do + sounds + |> Enum.with_index(1) + |> Enum.reduce([], fn {sound, index}, acc -> + if unquote(validator_fun).(sound), do: acc, else: [{index, sound} | acc] + end) + |> Enum.reverse() + end + end + end +end diff --git a/lesson_07/homework/my_music_band/lib/model/sound.ex b/lesson_07/homework/my_music_band/lib/model/sound.ex index 8fad2b0..2a6a16c 100644 --- a/lesson_07/homework/my_music_band/lib/model/sound.ex +++ b/lesson_07/homework/my_music_band/lib/model/sound.ex @@ -1,23 +1,32 @@ defmodule MyMusicBand.Model.Sound do + @moduledoc """ + Identifies valid sounds for each type of musician. + """ - @type silence() :: :' ' + @type silence() :: :" " + @silence :" " - @type note_a() :: :'A-a-a' - @type note_c() :: :'O-o-o' - @type note_e() :: :'E-e-e' - @type note_g() :: :'Wooo' + @type note_a() :: :"A-a-a" + @type note_c() :: :"O-o-o" + @type note_e() :: :"E-e-e" + @type note_g() :: :Wooo @type vocal_sound() :: note_a() | note_c() | note_e() | note_g() | silence() + @vocal_sounds [:"A-a-a", :"O-o-o", :"E-e-e", :Wooo, @silence] + def is_vocal(sound), do: sound in @vocal_sounds @type accord_a() :: :A @type accord_d() :: :D @type accord_e() :: :E - @type guitar_sound:: accord_a() | accord_d() | accord_e() | silence() + @type guitar_sound() :: accord_a() | accord_d() | accord_e() | silence() + @guitar_sounds [:A, :D, :E, @silence] + def is_guitar(sound), do: sound in @guitar_sounds @type kick() :: :BOOM @type snare() :: :Doom @type hi_hat() :: :Ts - @type drum_sound :: kick() | snare() | hi_hat() | silence() + @type drum_sound() :: kick() | snare() | hi_hat() | silence() + @drum_sound [:BOOM, :Doom, :Ts, @silence] + def is_drum(sound), do: sound in @drum_sound @type sound() :: vocal_sound() | guitar_sound() | drum_sound() - end diff --git a/lesson_07/homework/my_music_band/lib/vocalist.ex b/lesson_07/homework/my_music_band/lib/vocalist.ex index 870d93e..7e6c900 100644 --- a/lesson_07/homework/my_music_band/lib/vocalist.ex +++ b/lesson_07/homework/my_music_band/lib/vocalist.ex @@ -1,7 +1,3 @@ defmodule MyMusicBand.Vocalist do - - def init(arg), do: nil - - def next(arg), do: nil - -end + use MyMusicBand.Model.Musician, validator: &MyMusicBand.Model.Sound.is_vocal/1 +end