In the previous part of the series, we implemented curved tracks and movement along them. This time, we will finally look at branching train lines.

We have already used a graph to store our layout, so we can have multiple connected track. We just have not used it so far. Let me show you the function that initializes the layout I have been using in the samples.

initialLayout : Layout
initialLayout =
    Graph.empty
        |> insertEdgeData 0 1 (StraightTrack { length = 75.0 })
        |> insertEdgeData 1 2 (CurvedTrack { radius = 300.0, angle = 15.0 })

We are inserting edges so that track 0 connects to track 1, which then connectors to track 2. But now let’s add a branching track.

        |> insertEdgeData 1 3 (StraightTrack { length = 75.0 })

That was easy.

If we want to switch the track the train is using, we have to keep track which of the possible connections is active. Remember how we defined the layout graph?

type alias Layout =
    Graph Int () Track

We specified that we don’t want to store any particular data for vertices. Now is the time to change that: There are different switch types, simple ones but also crossings for example. Let’s define a type for switches and store it with the vertices in the layout graph. A switch is a list of configurations of routes that can be active, and “switching” means to change from one of the configurations to another one. A route is a pair of vertices that determines from where to where the route leads.

type alias Layout =
    Graph Int Switch Track

type alias Switch =
    { configs : List (List ( Int, Int )) }

Let’s write a function that returns all the switches in the layout. We want all the vertices in the layout graph that have switch data.

switches : Layout -> List ( Int, Switch )
switches layout =
    Graph.nodes layout
        -- Convert from a list of pairs with a Maybe inside to a list of Maybes.
        |> List.map (\( vertex, data ) -> Maybe.map (\switch -> ( vertex, switch )) data)
        -- Filter out the Nothings, the vertices that are not switches.
        |> Maybe.Extra.values

Finally, we need to add the switch information to the initial layout.

initialLayout : Layout
initialLayout =
    Graph.empty
        |> insertEdgeData 0 1 (StraightTrack { length = 75.0 })
        |> insertEdgeData 1 2 (CurvedTrack { radius = 300.0, angle = 15.0 })
        |> insertEdgeData 1 3 (StraightTrack { length = 75.0 })
        |> insertData 1 (Switch [ [ ( 0, 2 ) ], [ ( 0, 3 ) ] ])

Next

This was a bit hard. We are beginning to utilize the mechanisms of functional programming to make our program more concise. If you think that the switches function takes a lot of computation, keep in mind that it only needs to be evaluated once for a given layout. Elm knows that its result will never change unless the layout is replaced with another one. So it can optimize away repeated calls to the function wherever we need them.

In the next part, we will make the switches actually switchable.