Skip to content

Latest commit

 

History

History
448 lines (321 loc) · 20.4 KB

traffic_light_server.livemd

File metadata and controls

448 lines (321 loc) · 20.4 KB

Traffic Light Server

Mix.install([
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:tested_cell, github: "brooklinjazz/tested_cell"},
  {:utils, path: "#{__DIR__}/../utils"}
])

Navigation

Return Home Report An Issue

Traffic Light Server

You're going to create a TrafficLightServer GenServer that mimics a traffic light.

  • Create a TrafficLightServer which stores the state of the traffic light :red, :green, or :yellow. It should start as :green.
  • Define a current_light/1 function which returns the current light color.
  • Define a transition/1 function which transitions the current light from its current color to the next color.
  • Prefer call over cast to avoid concurrency issues.
flowchart LR
Green --> Yellow --> Red --> Green
style Green fill:lightgreen
style Yellow fill:lightyellow
style Red fill:lightcoral
Loading

For example:

{:ok, pid} = TrafficLightServer.start_link([])

:green = TrafficLightServer.current_light(pid)

:yellow = TrafficLightServer.transition(pid)
:yellow = TrafficLightServer.current_light(pid)

:red = TrafficLightServer.transition(pid)
:red = TrafficLightServer.current_light(pid)

:green = TrafficLightServer.transition(pid)
:green = TrafficLightServer.current_light(pid)

Enter your answer in the Elixir cell below.

ExUnit.start(auto_run: false)

defmodule Assertion do
  use ExUnit.Case

  test "Traffic Light Server Exercise" do
    try do
      Process.flag(:trap_exit, true)

      defmodule TrafficLightServer do
      end

      assert Kernel.function_exported?(TrafficLightServer, :start_link, 1),
             "Ensure you define a start_link/1 function which accepts a list of options."

      assert Kernel.function_exported?(TrafficLightServer, :init, 1),
             "Ensure you define an init/1 function which returns `{:ok, initial_state}`"

      assert {:ok, pid} = TrafficLightServer.start_link([])

      assert Kernel.function_exported?(TrafficLightServer, :current_light, 1),
             "Ensure you define a current_light/1 function which accepts a pid"

      assert :green == TrafficLightServer.current_light(pid)

      assert Kernel.function_exported?(TrafficLightServer, :transition, 1),
             "Ensure you define a transition/1 function which accepts a pid"

      TrafficLightServer.transition(pid)
      assert :yellow == TrafficLightServer.current_light(pid)

      TrafficLightServer.transition(pid)
      assert :red == TrafficLightServer.current_light(pid)

      TrafficLightServer.transition(pid)
      assert :green == TrafficLightServer.current_light(pid)
    catch
      error ->
        flunk("""
          Your solution threw the following error:

          #{inspect(error)}
        """)

      :exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
        flunk("""
            GenServer crashed with the following error:

            #{inspect(error)}

            When it received: #{inspect(message)} #{message_type}

            Likely you need to define the corresponding handler for #{inspect(message)}.

            Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.

              def handle_call(:message, _from, state) do
                ...
              end

            Then ensure you call [GenServer.call/2](https://hexdocs.pm/elixir/GenServer.html#call/2), [GenServer.cast/2](https://hexdocs.pm/elixir/GenServer.html#cast/2), or otherwise send the message correctly.

              GenServer.call(pid, :message)
        """)

      :exit, error ->
        flunk("""
          Unhandled exit with the following error:

          #{inspect(error)}
        """)
    after
      # all warnings and errors are printed to the previous Kino Frame
      # to avoid cluttering the test results display.
      Process.sleep(10)
      Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
    end
  end
end

ExUnit.run()

# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
defmodule TrafficLightServer do
end

You may find the following commented cell useful for testing your solution.

# {:ok, pid} = TrafficLightServer.start_link([])

# :green = TrafficLightServer.current_light(pid)

# :yellow = TrafficLightServer.transition(pid)
# :yellow = TrafficLightServer.current_light(pid)

# :red = TrafficLightServer.transition(pid)
# :red = TrafficLightServer.current_light(pid)

# :green = TrafficLightServer.transition(pid)
# :green = TrafficLightServer.current_light(pid)

TrafficGrid

You're going to create a TrafficGrid GenServer that manages five TrafficLightServers.

  • Create a TrafficGrid which stores the pids of five TrafficLightServers.
  • Define a current_lights/1 function that returns the current light of each TrafficLightServer.
  • Define a transition/1 function that transitions one of the five lights (in order, from beginning to end).
  • Prefer call over cast to avoid concurrency issues.
flowchart
TG[TrafficGrid]
TLS1[TrafficLightServer]
TLS2[TrafficLightServer]
TLS3[TrafficLightServer]
TLS4[TrafficLightServer]
TLS5[TrafficLightServer]
G1[Green]
G2[Green]
G3[Green]
G4[Green]
G5[Green]
Y1[Yellow]
Y2[Yellow]
Y3[Yellow]
Y4[Yellow]
Y5[Yellow]
R1[Red]
R2[Red]
R3[Red]
R4[Red]
R5[Red]

TG --> TLS1
TG --> TLS2
TG --> TLS3
TG --> TLS4
TG --> TLS5

TLS1 --> G1 --> Y1 --> R1 --> G1
TLS2 --> G2 --> Y2 --> R2 --> G2
TLS3 --> G3 --> Y3 --> R3 --> G3
TLS4 --> G4 --> Y4 --> R4 --> G4
TLS5 --> G5 --> Y5 --> R5 --> G5

style G1 fill:lightgreen
style G2 fill:lightgreen
style G3 fill:lightgreen
style G4 fill:lightgreen
style G5 fill:lightgreen
style Y1 fill:lightyellow
style Y2 fill:lightyellow
style Y3 fill:lightyellow
style Y4 fill:lightyellow
style Y5 fill:lightyellow
style R1 fill:lightcoral
style R2 fill:lightcoral
style R3 fill:lightcoral
style R4 fill:lightcoral
style R5 fill:lightcoral
Loading

For example:

{:ok, pid} = TrafficGrid.start_link([])


# all lights start green
[:green, :green, :green, :green, :green] = TrafficGrid.current_lights(pid)

# transition the first light from green to yellow
[:yellow, :green, :green, :green, :green] = TrafficGrid.transition(pid)
[:yellow, :green, :green, :green, :green] = TrafficGrid.current_lights(pid)

# transition the second light from green to yellow
[:yellow, :yellow, :green, :green, :green] = TrafficGrid.transition(pid)
[:yellow, :yellow, :green, :green, :green] = TrafficGrid.current_lights(pid)

# transition the third light from green to yellow
[:yellow, :yellow, :yellow, :green, :green] = TrafficGrid.transition(pid)
[:yellow, :yellow, :yellow, :green, :green] = TrafficGrid.current_lights(pid)

# transition the fourth light from green to yellow
[:yellow, :yellow, :yellow, :yellow, :green] = TrafficGrid.transition(pid)
[:yellow, :yellow, :yellow, :yellow, :green] = TrafficGrid.current_lights(pid)

# transition the fifth light from green to yellow
[:yellow, :yellow, :yellow, :yellow, :yellow] = TrafficGrid.transition(pid)
[:yellow, :yellow, :yellow, :yellow, :yellow] = TrafficGrid.current_lights(pid)

# transition the first light from yellow to red
[:red, :yellow, :yellow, :yellow, :yellow] = TrafficGrid.transition(pid)
[:red, :yellow, :yellow, :yellow, :yellow] = TrafficGrid.current_lights(pid)

# ... etc

Enter your answer in the Elixir cell below.

ExUnit.start(auto_run: false)

defmodule Assertion do
  use ExUnit.Case

  test "Traffic Grid Exercise" do
    try do
      Process.flag(:trap_exit, true)

      defmodule TrafficGrid do
      end

      assert Kernel.function_exported?(TrafficGrid, :start_link, 1),
             "Ensure you define a start_link/1 function which accepts a list of options."

      assert Kernel.function_exported?(TrafficGrid, :init, 1),
             "Ensure you define an init/1 function which returns `{:ok, initial_state}`"

      assert {:ok, pid} = TrafficGrid.start_link([])

      assert Kernel.function_exported?(TrafficGrid, :current_lights, 1),
             "Ensure you define a current_lights/1 function which accepts a pid"

      assert [:green, :green, :green, :green, :green] == TrafficGrid.current_lights(pid)

      assert Kernel.function_exported?(TrafficGrid, :transition, 1),
             "Ensure you define a transition/1 function which accepts a pid"

      assert [:yellow, :green, :green, :green, :green] == TrafficGrid.transition(pid)
      assert [:yellow, :green, :green, :green, :green] == TrafficGrid.current_lights(pid)

      assert [:yellow, :yellow, :green, :green, :green] == TrafficGrid.transition(pid)
      assert [:yellow, :yellow, :green, :green, :green] == TrafficGrid.current_lights(pid)

      assert [:yellow, :yellow, :yellow, :green, :green] == TrafficGrid.transition(pid)
      assert [:yellow, :yellow, :yellow, :green, :green] == TrafficGrid.current_lights(pid)

      assert [:yellow, :yellow, :yellow, :yellow, :green] == TrafficGrid.transition(pid)
      assert [:yellow, :yellow, :yellow, :yellow, :green] == TrafficGrid.current_lights(pid)

      assert [:yellow, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
      assert [:yellow, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

      assert [:red, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
      assert [:red, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

      assert [:red, :red, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
      assert [:red, :red, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

      assert [:red, :red, :red, :yellow, :yellow] == TrafficGrid.transition(pid)
      assert [:red, :red, :red, :yellow, :yellow] == TrafficGrid.current_lights(pid)

      assert [:red, :red, :red, :red, :yellow] == TrafficGrid.transition(pid)
      assert [:red, :red, :red, :red, :yellow] == TrafficGrid.current_lights(pid)

      assert [:red, :red, :red, :red, :red] == TrafficGrid.transition(pid)
      assert [:red, :red, :red, :red, :red] == TrafficGrid.current_lights(pid)

      assert [:green, :red, :red, :red, :red] == TrafficGrid.transition(pid)
      assert [:green, :red, :red, :red, :red] == TrafficGrid.current_lights(pid)
    catch
      error ->
        flunk("""
          Your solution threw the following error:

          #{inspect(error)}
        """)

      :exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
        flunk("""
            GenServer crashed with the following error:

            #{inspect(error)}

            When it received: #{inspect(message)} #{message_type}

            Likely you need to define the corresponding handler for #{inspect(message)}.

            Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.

              def handle_call(:message, _from, state) do
                ...
              end

            Then ensure you call [GenServer.call/2](https://hexdocs.pm/elixir/GenServer.html#call/2), [GenServer.cast/2](https://hexdocs.pm/elixir/GenServer.html#cast/2), or otherwise send the message correctly.

              GenServer.call(pid, :message)
        """)

      :exit, error ->
        flunk("""
          Unhandled exit with the following error:

          #{inspect(error)}
        """)
    after
      # all warnings and errors are printed to the previous Kino Frame
      # to avoid cluttering the test results display.
      Process.sleep(10)
      Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
    end
  end
end

ExUnit.run()

# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
defmodule TrafficGrid do
end

You may find the following commented cell useful for testing your solution.

# {:ok, pid} = TrafficGrid.start_link([])

# [:green, :green, :green, :green, :green] == TrafficGrid.current_lights(pid)

# [:yellow, :green, :green, :green, :green] == TrafficGrid.transition(pid)
# [:yellow, :green, :green, :green, :green] == TrafficGrid.current_lights(pid)

# [:yellow, :yellow, :green, :green, :green] == TrafficGrid.transition(pid)
# [:yellow, :yellow, :green, :green, :green] == TrafficGrid.current_lights(pid)

# [:yellow, :yellow, :yellow, :green, :green] == TrafficGrid.transition(pid)
# [:yellow, :yellow, :yellow, :green, :green] == TrafficGrid.current_lights(pid)

# [:yellow, :yellow, :yellow, :yellow, :green] == TrafficGrid.transition(pid)
# [:yellow, :yellow, :yellow, :yellow, :green] == TrafficGrid.current_lights(pid)

# [:yellow, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
# [:yellow, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

# [:red, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
# [:red, :yellow, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

# [:red, :red, :yellow, :yellow, :yellow] == TrafficGrid.transition(pid)
# [:red, :red, :yellow, :yellow, :yellow] == TrafficGrid.current_lights(pid)

# [:red, :red, :red, :yellow, :yellow] == TrafficGrid.transition(pid)
# [:red, :red, :red, :yellow, :yellow] == TrafficGrid.current_lights(pid)

# [:red, :red, :red, :red, :yellow] == TrafficGrid.transition(pid)
# [:red, :red, :red, :red, :yellow] == TrafficGrid.current_lights(pid)

# [:red, :red, :red, :red, :red] == TrafficGrid.transition(pid)
# [:red, :red, :red, :red, :red] == TrafficGrid.current_lights(pid)

# [:green, :red, :red, :red, :red] == TrafficGrid.transition(pid)
# [:green, :red, :red, :red, :red] == TrafficGrid.current_lights(pid)

Commit Your Progress

Run the following in your command line from the beta_curriculum folder to track and save your progress in a Git commit.

$ git add .
$ git commit -m "finish traffic light server exercise"

Up Next

Previous Next
Generic Server Rock Paper Scissors GenServer