Traffic Light Server
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.8.0", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Navigation
Traffic 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
Mark As Completed
file_name = Path.basename(Regex.replace(~r/#.+/, __ENV__.file, ""), ".livemd")
save_name =
case Path.basename(__DIR__) do
"reading" -> "traffic_light_server_reading"
"exercises" -> "traffic_light_server_exercise"
end
progress_path = __DIR__ <> "/../progress.json"
existing_progress = File.read!(progress_path) |> Jason.decode!()
default = Map.get(existing_progress, save_name, false)
form =
Kino.Control.form(
[
completed: input = Kino.Input.checkbox("Mark As Completed", default: default)
],
report_changes: true
)
Task.async(fn ->
for %{data: %{completed: completed}} <- Kino.Control.stream(form) do
File.write!(
progress_path,
Jason.encode!(Map.put(existing_progress, save_name, completed), pretty: true)
)
end
end)
form
Commit Your Progress
Run the following in your command line from the curriculum folder to track and save your progress in a Git commit.
Ensure that you do not already have undesired or unrelated changes by running git status or by checking the source control tab in Visual Studio Code.
$ git checkout -b traffic-light-server-exercise
$ git add .
$ git commit -m "finish traffic light server exercise"
$ git push origin traffic-light-server-exercise
Create a pull request from your traffic-light-server-exercise branch to your solutions branch.
Please do not create a pull request to the DockYard Academy repository as this will spam our PR tracker.
DockYard Academy Students Only:
Notify your teacher by including @BrooklinJazz in your PR description to get feedback.
You (or your teacher) may merge your PR into your solutions branch after review.
If you are interested in joining the next academy cohort, sign up here to receive more news when it is available.
Up Next
| Previous | Next |
|---|---|
| Stack Server | Pokemon Server |