Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Traffic Light Server

exercises/traffic_light_server.livemd

Traffic Light Server

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.9", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"}
])

Navigation

Home Report An Issue Tested StackSupervisors

Traffic Light Server

You’re going to create a TrafficLights mix project that manages traffic.

mix new traffic_lights

Create a TrafficLights.Light GenServer that mimics a traffic light transitioning from green to yellow to red.

flowchart LR
  G((1))
  Y((2))
  R((3))
  G --> Y --> R --> G
  style G fill: lightgreen
  style Y fill: lightyellow
  style R fill: coral

This is also a simple example of building a Finite-state machine using GenServer.

Requirements

  • The initial traffic light state should start as :green. Store light states as atoms :green, :yellow, and :red.
  • Handle an asynchronous :transition message to transition the current light.
  • Handle a synchronous :current_light message to retrieve the current light.
  • Create the transition/1 and current_light/1 messages as documented below.
  • Write a full suite of tests for the TrafficLights module.
{:ok, pid} = TrafficLights.Light.start_link([])

:green = TrafficLights.Light.current_light(pid)
:ok = TrafficLights.Light.transition(pid)

:yellow = TrafficLights.Light.current_light(pid)
:ok = TrafficLights.Light.transition(pid)

:red = TrafficLights.Light.current_light(pid)
:ok = TrafficLights.Light.transition(pid)

:green = TrafficLights.Light.current_light(pid)

Example Solution

defmodule TrafficLights.Light do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, [])
  end

  def transition(pid) do
    GenServer.call(pid, :transition)
  end

  def current_light(pid) do
    GenServer.call(pid, :current_light)
  end

  @impl true
  def init(_opts) do
    {:ok, :green}
  end

  @impl true
  def handle_call(:transition, _from, state) do
    next_state =
      case state do
        :green -> :yellow
        :yellow -> :red
        :red -> :green
      end

    {:reply, next_state, next_state}
  end

  @impl true
  def handle_call(:current_light, _from, state) do
    {:reply, state, state}
  end
end

TrafficGrid

Create a TrafficLights.Grid GenServer that manages five TrafficLights.Light processes.

flowchart
TG[TrafficLights.Grid]
TLS1[TrafficLights.Light]
TLS2[TrafficLights.Light]
TLS3[TrafficLights.Light]
TLS4[TrafficLights.Light]
TLS5[TrafficLights.Light]
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

Requirements

  • The initial state of the grid should contain a list with five TrafficLights.Light pids in addition to any other state you want to track
  • Create a current_lights/1 and transition/1 function as documented below.
  • Write a full suite of tests.
{:ok, pid} = TrafficLights.Grid.start_link([])

:ok = TrafficLights.Grid.transition(pid)

[:yellow, :green, :green, :green, :green] = TrafficLights.Grid.current_lights(pid)

:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)

[:red, :yellow, :yellow, :yellow, :yellow] = TrafficLights.Grid.current_lights(pid)

:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)
:ok = TrafficLights.Grid.transition(pid)

[:green, :red, :red, :red, :red] = TrafficLights.Grid.current_lights(pid)

Example Solution

defmodule TrafficLights.Grid do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, [])
  end

  def transition(grid_pid) do
    GenServer.call(grid_pid, :transition)
  end

  def current_lights(grid_pid) do
    GenServer.call(grid_pid, :current_lights)
  end

  @impl true
  def init(_opts) do
    light_pids =
      Enum.map(1..5, fn _ ->
        {:ok, pid} = TrafficLights.Light.start_link([])
        pid
      end)

    {:ok, %{light_pids: light_pids, transition_index: 0}}
  end

  @impl true
  def handle_call(:transition, _from, state) do
    light_pid = Enum.at(state.light_pids, state.transition_index)
    TrafficLights.Light.transition(light_pid)

    lights = Enum.map(state.light_pids, &TrafficLights.Light.current_light/1)
    next_transition_index = rem(state.transition_index + 1, length(state.light_pids))

    {:reply, lights, %{state | transition_index: next_transition_index}}
  end

  @impl true
  def handle_call(:current_lights, _from, state) do
    lights = Enum.map(state.light_pids, &TrafficLights.Light.current_light/1)

    {:reply, lights, state}
  end
end

Commit Your Progress

DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.

Run git status to ensure there are no undesirable changes. Then run the following in your command line from the curriculum folder to commit your progress.

$ git add .
$ git commit -m "finish Traffic Light Server exercise"
$ git push

We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.

We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.

Navigation

Home Report An Issue Tested StackSupervisors