Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lesson_07/homework/my_music_band/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -37,13 +42,15 @@ 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, ...])
{:ok, drummer} = Drummer.init([:BOOM, :Ts, ...])
```

их можно собрать вместе:

```elixir
band =
Band.init()
Expand All @@ -53,13 +60,14 @@ band =
```

и играть все партии вместе:

```elixir
{[:'A-a-a', :A, :BOOM], band} = Band.next(band)
{[:'O-o-o', :D, :Ts], band} = Band.next(band)
```

При этом нужно учесть, что партии могут быть разной длины.

Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании).
Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании).

Если считаете нужным, можете добавить ещё какие-то модули.
28 changes: 24 additions & 4 deletions lesson_07/homework/my_music_band/lib/band.ex
Original file line number Diff line number Diff line change
@@ -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
8 changes: 2 additions & 6 deletions lesson_07/homework/my_music_band/lib/drummer.ex
Original file line number Diff line number Diff line change
@@ -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
8 changes: 2 additions & 6 deletions lesson_07/homework/my_music_band/lib/guitarist.ex
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions lesson_07/homework/my_music_band/lib/model/musician.ex
Original file line number Diff line number Diff line change
@@ -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
25 changes: 17 additions & 8 deletions lesson_07/homework/my_music_band/lib/model/sound.ex
Original file line number Diff line number Diff line change
@@ -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
8 changes: 2 additions & 6 deletions lesson_07/homework/my_music_band/lib/vocalist.ex
Original file line number Diff line number Diff line change
@@ -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