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 StackPokemon ServerTraffic Light Server
You’re going to create a TrafficLightServer GenServer that mimics a traffic light.
flowchart LR
G((1))
Y((2))
R((3))
G --> Y --> R --> G
style G fill: lightgreen
style Y fill: lightyellow
style R fill: coral
Server Callbacks
The TrafficLightServer should define some Server API callback functions using GenServer.handle_call/3 and GenServer.init/1.
The TrafficLightServer‘s state should start as :green, then you should be able to send it a :transition message that will transition the state from :green, to :yellow, to :red.
The TrafficLightServer should also be able to receive a :current_light message which returns the current light.
{:ok, pid} = GenServer.start_link(TrafficLightServer, [])
:green = GenServer.call(pid, :current_light)
:yellow = GenServer.call(pid, :transition)
:red = GenServer.call(pid, :transition)
:green = GenServer.call(pid, :transition)
Client API
The TrafficLightServer should define some Client API functions transition/1, start_link/1 and current_light/1 that abstract the underlying GenServer implementation details.
{:ok, pid} = TrafficLightServer.start_link([])
:green = TrafficLightServer.current_light(pid)
:yellow = TrafficLightServer.transition(pid)
:red = TrafficLightServer.transition(pid)
:green = TrafficLightServer.transition(pid)
Example Solution
defmodule TrafficLightServer 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
Implement the TrafficLightServer as documented below. You will also need to define the GenServer callback functions GenServer.init/1 and GenServer.handle_call/3.
defmodule TrafficLightServer do
@moduledoc """
Documentation for `TrafficLightServer`
"""
use GenServer
@doc """
Start a TrafficLightServer linked to the current caller process.
"""
def start_link(opts) do
end
@doc """
Transition the `TrafficLightServer`'s state.
:green -> :yellow -> :red
"""
def transition(pid) do
end
@doc """
Get the `TrafficLightServer`'s current state.
"""
def current_light(pid) do
end
end
Bonus: TrafficGrid
You’re going to create a TrafficGrid GenServer that manages five TrafficLightServers.
-
Create a
TrafficGridwhich stores the pids of fiveTrafficLightServers. -
Define a
current_lights/1function that returns the current light of eachTrafficLightServer. -
Define a
transition/1function that transitions one of the five lights (in order, from beginning to end). -
Prefer
callovercastto 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
transition/1 Examples:
iex> {:ok, pid} = TrafficGridServer.start_link([])
iex> TrafficGridServer.transition(pid)
[:yellow, :green, :green, :green, :green]
iex> TrafficGridServer.transition(pid)
[:yellow, :yellow, :green, :green, :green]
iex> TrafficGridServer.transition(pid)
[:yellow, :yellow, :yellow, :green, :green]
iex> TrafficGridServer.transition(pid)
[:yellow, :yellow, :yellow, :yellow, :green]
iex> TrafficGridServer.transition(pid)
[:yellow, :yellow, :yellow, :yellow, :yellow]
iex> TrafficGridServer.transition(pid)
[:red, :yellow, :yellow, :yellow, :yellow]
iex> TrafficGridServer.transition(pid)
[:red, :red, :yellow, :yellow, :yellow]
current_lights/1 Examples:
iex> {:ok, pid} = TrafficGridServer.start_link([])
iex> TrafficGridServer.current_lights(pid)
[:green, :green, :green, :green, :green]
Example Solution
defmodule TrafficGridServer 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} = TrafficLightServer.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)
TrafficLightServer.transition(light_pid)
lights = Enum.map(state.light_pids, &TrafficLightServer.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, fn light_pid -> TrafficLightServer.current_light(light_pid) end)
{:reply, lights, state}
end
end
Implement the TrafficGrid module as documented below. You will also have to define callback functions for the GenServer‘s Client API.
defmodule TrafficGridServer do
use GenServer
@doc ~S"""
Start the `TrafficGridServer`.
## Examples
TrafficGridServer.start_link([])
{:ok, pid}
"""
def start_link(_opts) do
end
@doc """
transition the current light state of the next light to transition.
## Examples
TrafficGridServer.transition(pid)
[:yellow, :green, :green, :green, :green]
"""
def transition(grid_pid) do
end
@doc """
Retrieve the current light state of each managed `TrafficLightServer`.
## Examples
TrafficGridServer.current_lights(pid)
[:green, :green, :green, :green, :green]
"""
def current_lights(grid_pid) do
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.