Skip to content

Commit 1c55935

Browse files
committed
Adding a traffic jam simulation.
1 parent a3c9276 commit 1c55935

13 files changed

+554
-0
lines changed

traffic_jam/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/_build
2+
/cover
3+
/deps
4+
erl_crash.dump
5+
*.ez

traffic_jam/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# TrafficJam
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
8+
9+
1. Add traffic_jam to your list of dependencies in `mix.exs`:
10+
11+
def deps do
12+
[{:traffic_jam, "~> 0.0.1"}]
13+
end
14+
15+
2. Ensure traffic_jam is started before your application:
16+
17+
def application do
18+
[applications: [:traffic_jam]]
19+
end
20+

traffic_jam/config/config.exs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
use Mix.Config
4+
5+
# This configuration is loaded before any dependency and is restricted
6+
# to this project. If another project depends on this project, this
7+
# file won't be loaded nor affect the parent project. For this reason,
8+
# if you want to provide default values for your application for
9+
# 3rd-party users, it should be done in your "mix.exs" file.
10+
11+
# You can configure for your application as:
12+
#
13+
# config :traffic_jam, key: :value
14+
#
15+
# And access this configuration in your application as:
16+
#
17+
# Application.get_env(:traffic_jam, :key)
18+
#
19+
# Or configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
config :traffic_jam, road_width: 1_000
25+
config :traffic_jam, road_height: 100
26+
config :traffic_jam, car_width: 5
27+
28+
config :traffic_jam, paint_delay: 100
29+
config :traffic_jam, car_delay: 250
30+
31+
config :traffic_jam, cars: 50
32+
33+
# It is also possible to import configuration files, relative to this
34+
# directory. For example, you can emulate configuration per environment
35+
# by uncommenting the line below and defining dev.exs, test.exs and such.
36+
# Configuration from the imported file will override the ones defined
37+
# here (which is why it is important to import them last).
38+
#
39+
# import_config "#{Mix.env}.exs"

traffic_jam/lib/traffic_jam.ex

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule TrafficJam do
2+
use Application
3+
4+
# See http://elixir-lang.org/docs/stable/elixir/Application.html
5+
# for more information on OTP Applications
6+
def start(_type, _args) do
7+
import Supervisor.Spec, warn: false
8+
9+
road_width = Application.fetch_env!(:traffic_jam, :road_width)
10+
road_height = Application.fetch_env!(:traffic_jam, :road_height)
11+
car_width = Application.fetch_env!(:traffic_jam, :car_width)
12+
paint_delay = Application.fetch_env!(:traffic_jam, :paint_delay)
13+
cars = Application.fetch_env!(:traffic_jam, :cars)
14+
15+
children = [
16+
# Define workers and child supervisors to be supervised
17+
supervisor(TrafficJam.CarSupervisor, [ ]),
18+
worker(TrafficJam.Road, [road_width, car_width, cars]),
19+
worker(
20+
TrafficJam.Window,
21+
[paint_delay, {road_width, road_height}, car_width]
22+
)
23+
]
24+
25+
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
26+
# for other strategies and supported options
27+
opts = [strategy: :one_for_all, name: TrafficJam.Supervisor]
28+
Supervisor.start_link(children, opts)
29+
end
30+
end

traffic_jam/lib/traffic_jam/canvas.ex

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
defmodule TrafficJam.Canvas do
2+
@behaviour :wx_object
3+
4+
@black {0, 0, 0, 255}
5+
@white {255, 255, 255, 255}
6+
7+
defstruct ~w[panel car_width]a
8+
9+
require Logger
10+
require Record
11+
Record.defrecordp(
12+
:wx, Record.extract(:wx, from_lib: "wx/include/wx.hrl")
13+
)
14+
Record.defrecordp(
15+
:wxPaint, Record.extract(:wxPaint, from_lib: "wx/include/wx.hrl")
16+
)
17+
Record.defrecordp(
18+
:wxKey, Record.extract(:wxKey, from_lib: "wx/include/wx.hrl")
19+
)
20+
21+
# Client API
22+
23+
def start_link(parent, car_width) do
24+
:wx_object.start_link(__MODULE__, [parent, car_width], [ ])
25+
end
26+
27+
# Server API
28+
29+
def init(args) do
30+
:wx.batch(fn -> do_init(args) end)
31+
end
32+
33+
def handle_call(:shutdown, _from, state = %__MODULE__{panel: panel}) do
34+
:wxPanel.destroy(panel)
35+
{:reply, :ok, state}
36+
end
37+
def handle_call(message, _from, state) do
38+
Logger.debug "Unhandled call: #{inspect message}"
39+
{:reply, :ok, state}
40+
end
41+
42+
def handle_cast(message, state) do
43+
Logger.debug "Unhandled cast: #{inspect message}"
44+
{:noreply, state}
45+
end
46+
47+
def handle_sync_event(wx(event: wxPaint()), _paint_event, state) do
48+
paint(state)
49+
:ok
50+
end
51+
52+
def handle_event(
53+
wx(
54+
event: wxKey(
55+
keyCode: key,
56+
controlDown: control,
57+
shiftDown: shift,
58+
metaDown: meta,
59+
altDown: alt
60+
)
61+
),
62+
state
63+
) do
64+
new_state = do_key_down(key, shift, alt, control, meta, state)
65+
{:noreply, new_state}
66+
end
67+
def handle_event(wx, state) do
68+
Logger.debug "Unhandled event: #{inspect wx}"
69+
{:noreply, state}
70+
end
71+
72+
def handle_info(info, state) do
73+
Logger.debug "Unhandled info: #{inspect info}"
74+
{:noreply, state}
75+
end
76+
77+
def code_change(_old_vsn, _state, _extra) do
78+
{:error, :not_implemented}
79+
end
80+
81+
def terminate(_reason, _state) do
82+
:ok
83+
end
84+
85+
# Helpers
86+
87+
defp do_init([parent, car_width]) do
88+
panel = :wxPanel.new(parent)
89+
90+
:wxFrame.connect(panel, :paint, [:callback])
91+
:wxPanel.connect(panel, :key_down)
92+
93+
{panel, %__MODULE__{panel: panel, car_width: car_width}}
94+
end
95+
96+
defp do_key_down(key, shift, alt, control, meta, state) do
97+
case key do
98+
?Q when not (shift or alt or control or meta) -> System.halt(0)
99+
_ -> :ok
100+
end
101+
state
102+
end
103+
104+
defp paint(%__MODULE__{panel: panel, car_width: car_width}) do
105+
{width, height} = :wxPanel.getClientSize(panel)
106+
drawing_context = :wxPaintDC.new(panel)
107+
108+
paint_road(drawing_context, width, height)
109+
paint_cars(drawing_context, car_width)
110+
111+
:wxPaintDC.destroy(drawing_context)
112+
end
113+
114+
defp paint_road(context, width, height) do
115+
:wxDC.setPen(context, :wxPen.new(@black))
116+
:wxDC.setBrush(context, :wxBrush.new(@black))
117+
:wxDC.drawRectangle(context, {0, 0}, {width, height})
118+
end
119+
120+
defp paint_cars(context, car_width) do
121+
TrafficJam.Road.get_cars
122+
|> Enum.each(fn car ->
123+
:wxDC.setPen(context, :wxPen.new(@white))
124+
:wxDC.setBrush(context, :wxBrush.new(@white))
125+
:wxDC.drawRectangle(context, {car, 49}, {car_width, 2})
126+
end)
127+
end
128+
end

traffic_jam/lib/traffic_jam/car.ex

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
defmodule TrafficJam.Car do
2+
defstruct ~w[position car_width road_width speed leader]a
3+
4+
@max_speed 3.0
5+
6+
use GenServer
7+
require Logger
8+
9+
# Client API
10+
11+
def start_link(position, car_width, road_width, leader) do
12+
GenServer.start_link(__MODULE__, [position, car_width, road_width, leader])
13+
end
14+
15+
def assign_leader(car, leader) do
16+
GenServer.cast(car, {:assign_leader, leader})
17+
end
18+
19+
def get_lead(pid, position) do
20+
GenServer.call(pid, {:get_lead, position})
21+
end
22+
23+
# Server API
24+
25+
def init([position, car_width, road_width, leader]) do
26+
GenServer.cast(self, :finish_init)
27+
initial_state = %__MODULE__{
28+
position: position,
29+
car_width: car_width,
30+
road_width: road_width,
31+
speed: :rand.uniform * @max_speed,
32+
leader: leader
33+
}
34+
{:ok, initial_state}
35+
end
36+
37+
def handle_call(
38+
{:get_lead, follower_position},
39+
_from,
40+
state = %__MODULE__{position: position, road_width: road_width}
41+
) do
42+
adjusted_position =
43+
if position < follower_position, do: position + road_width, else: position
44+
{:reply, adjusted_position - follower_position, state}
45+
end
46+
def handle_call(message, _from, state) do
47+
Logger.debug "Unhandled call: #{inspect message}"
48+
{:reply, :ok, state}
49+
end
50+
51+
def handle_cast(:finish_init, state = %__MODULE__{position: position}) do
52+
TrafficJam.Road.place_car(position)
53+
:timer.send_interval(333, :move)
54+
{:noreply, state}
55+
end
56+
def handle_cast({:assign_leader, leader}, state) do
57+
{:noreply, %__MODULE__{state | leader: leader}}
58+
end
59+
def handle_cast(message, state) do
60+
Logger.debug "Unhandled cast: #{inspect message}"
61+
{:noreply, state}
62+
end
63+
64+
def handle_info(
65+
:move,
66+
state = %__MODULE__{
67+
position: position,
68+
car_width: car_width,
69+
road_width: road_width,
70+
speed: speed,
71+
leader: leader
72+
}
73+
) do
74+
new_speed = adjust_speed(position, car_width, speed, leader)
75+
new_position = move(position, road_width, new_speed)
76+
update_position(position, new_position)
77+
{:noreply, %__MODULE__{state | position: new_position}}
78+
end
79+
def handle_info(message, state) do
80+
Logger.debug "Unhandled info: #{inspect message}"
81+
{:noreply, state}
82+
end
83+
84+
# Helpers
85+
86+
defp adjust_speed(position, car_width, speed, leader) do
87+
bumped = speed + 0.5
88+
current_max =
89+
case TrafficJam.Car.get_lead(leader, position) do
90+
distance when distance < car_width + 1 + @max_speed ->
91+
Enum.max([0, distance - (car_width + 1)])
92+
_distance ->
93+
@max_speed
94+
end
95+
if bumped > current_max, do: current_max, else: bumped
96+
end
97+
98+
defp move(position, road_width, speed) do
99+
case position + speed do
100+
new_position when new_position >= road_width -> new_position
101+
new_position -> new_position
102+
end
103+
end
104+
105+
defp update_position(old_position, new_position) do
106+
trunced_old_position = trunc(old_position)
107+
trunced_new_position = trunc(new_position)
108+
if trunced_old_position != trunced_new_position do
109+
TrafficJam.Road.move_car(trunced_old_position, trunced_new_position)
110+
end
111+
end
112+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule TrafficJam.CarSupervisor do
2+
def start_link do
3+
import Supervisor.Spec
4+
5+
children = [
6+
worker(TrafficJam.Car, [ ], restart: :transient)
7+
]
8+
9+
Supervisor.start_link(
10+
children,
11+
strategy: :simple_one_for_one,
12+
name: __MODULE__
13+
)
14+
end
15+
end

0 commit comments

Comments
 (0)