Reactive Programming is gaining momentum
these days and here is my minimal contribution to get more people interested,
which hopefully will rise average software products quality in “The Industry”.
This is part 1 of “Reactive traffic lights” series where I’m going
to build automated traffic lights
using RxJs .
It’s just a “case-study”
so you’re always welcome to comment on things may be done better
or more idiomatic way.
Let’s list some basic requirements:
There must be 2 automatic lights: for cars and for pedestrians;
Lights for cars have 3 colors: red, yellow, green;
Pedestrian lights have 2 colors: red and green;
Green light should blink before switch.
Visualization
We will “render” our traffic lights using simple blocks with circles inside:
For Vehicles
<div class= "t-lights t-lights-h t-lights-3" >
<div class= "t-light red" ></div>
<div class= "t-light yellow" ></div>
<div class= "t-light green" ></div>
</div>
For Pedestrians
<div class= "t-lights t-lights-h t-lights-3" >
<div class= "t-light red" ></div>
<div class= "t-light green" ></div>
</div>
Css source .
Bootstrap
The only dependency we have - is Rx library:
<script type= "text/javascript" src= "https://unpkg.com/@reactivex/rxjs/dist/global/Rx.js" > <!-- --> </script>
First - lets build default static version where cars light is always green.
As pedestrians light is simpler: have only 2 colors excluding each other
we can define it depending on cars light.
/**
* Returns a css class toggle function, on a given element
* @param {String} cssClass
* @returns {function(node:Object):void}
*/
function toggleCssClass ( cssClass ) {
return function ( element ) {
return function ( add ) {
if ( add ) {
element . classList . add ( cssClass );
} else {
element . classList . remove ( cssClass );
}
}
}
}
/**
* Returns an innerHTML setter function
* @param element
* @returns {function(html:String):void}
*/
function setInnerHtml ( element ) {
return function ( html ) {
element . innerHTML = html ;
}
}
( function () {
var carsRed = Rx . Observable . of ( false );
var carsYellow = Rx . Observable . of ( false );
var carsGreen = Rx . Observable . of ( true );
var pedestrianGreen = carsRed ;
var pedestrianRed = pedestrianGreen . map ( function ( green ) {
return ! green ;
});
// Visualize
var carsRedLight = document . querySelector ( ' #cars_lights div.t-light.red ' );
var carsYellowLight = document . querySelector ( ' #cars_lights div.t-light.yellow ' );
var carsGreenLight = document . querySelector ( ' #cars_lights div.t-light.green ' );
var pedestrianRedLight = document . querySelector ( ' #pd_lights div.t-light.red ' );
var pedestrianGreenLight = document . querySelector ( ' #pd_lights div.t-light.green ' );
// helper to toggle .on css class on light bulbs
var onClassToggle = toggleCssClass ( " on " );
carsRed . forEach ( onClassToggle ( carsRedLight ));
carsYellow . forEach ( onClassToggle ( carsYellowLight ));
carsGreen . forEach ( onClassToggle ( carsGreenLight ));
pedestrianGreen . forEach ( onClassToggle ( pedestrianGreenLight ));
pedestrianRed . forEach ( onClassToggle ( pedestrianRedLight ));
})();
Now green light for cars must be lighter and same for pedestrians red.
React to time
Now let’s make it work, based on a timer
var periodLength = 60 ; // full iteration time, ticks
var carsPeriod = 40 ; // green + yellow period for cars
var greenBlinkPeriod = 5 ;
var yellowPeriod = 3 ;
Define the timer
Timer is the single signal sources needed for this case, other values will be derived from it.
// Define ticks source, with 500ms delay, normalized to our period length: [0..59]
var periodTimer = Rx . Observable . timer ( 0 , 500 )
. map ( function ( t ) {
return t % periodLength ;
});
Vehicles lights
Green for vehicles will be on first 32 ticks, next 5 - will blink
and next 3 - yellow will be on:
// Green signal will take first 37 ticks
var carsGreen = periodTimer
. map ( function ( t ) {
// last 5 ticks blinking green
return t < ( carsPeriod - yellowPeriod )
&& (
t < ( carsPeriod - yellowPeriod - greenBlinkPeriod )
|| t % 2 == 0 // blinking
);
});
Yellow will be on last 3 ticks for both - vehicles and pedestrians periods:
// yellow will take last 3 ticks before switch to red
var carsYellow = periodTimer
. map ( function ( t ) {
// last 3 ticks for yellow
return ( t < carsPeriod && t > ( carsPeriod - yellowPeriod ))
|| ( t < periodLength && t > ( periodLength - yellowPeriod ));
});
Red - on for full pedestrian period:
// red
var carsRed = periodTimer
. map ( function ( t ) {
return t >= carsPeriod ;
});
Pedestrian lights
Pedestrian green is on while red for vehicles and is blinking last 5 ticks:
var pedestrianGreen = carsRed ;
var pedGreenBlink = periodTimer
. map ( function ( t ) {
// return true to turn green light off
return t >= ( periodLength - yellowPeriod ) && t % 2 != 0
});
var pedestrianGreenBlinking = Rx . Observable
. combineLatest ( pedestrianGreen , pedGreenBlink )
. map ( function ( arr ) {
// is green an not blinking
return arr [ 0 ] && ! arr [ 1 ];
});
Pedestrian red is opposite to green:
var pedestrianRed = pedestrianGreen . map ( function ( green ) {
return ! green ;
});
Additionally let’s add some countdowns. As our timer is running twice a second
we will round our countdown to seconds dividing by 2:
var pedestrianGreenCountdown = periodTimer
. map ( function ( t ) {
if ( t >= carsPeriod ) {
return Math . round (( periodLength - t ) / 2 );
}
return 0 ;
});
var pedestrianRedCountdown = periodTimer
. map ( function ( t ) {
if ( t < carsPeriod ) {
return Math . round (( carsPeriod - t ) / 2 )
}
return 0 ;
});
And finally - connect all dots:
// Visualize
var carsRedLight = document . querySelector ( ' #cars_lights div.t-light.red ' );
var carsYellowLight = document . querySelector ( ' #cars_lights div.t-light.yellow ' );
var carsGreenLight = document . querySelector ( ' #cars_lights div.t-light.green ' );
var pedestrianRedLight = document . querySelector ( ' #pd_lights div.t-light.red ' );
var pedestrianGreenLight = document . querySelector ( ' #pd_lights div.t-light.green ' );
// helper to toggle .on css class on light bulbs
var onClassToggle = toggleCssClass ( " on " );
// Connecting the dots: vehicle lights
carsRed . forEach ( onClassToggle ( carsRedLight ));
carsYellow . forEach ( onClassToggle ( carsYellowLight ));
carsGreen . forEach ( onClassToggle ( carsGreenLight ));
// pedestrian lights
pedestrianGreenBlinking . forEach ( onClassToggle ( pedestrianGreenLight ));
pedestrianRed . forEach ( onClassToggle ( pedestrianRedLight ));
// Countdowns
pedestrianGreenCountdown . forEach ( setInnerHtml ( pedestrianGreenLight ));
pedestrianRedCountdown . forEach ( setInnerHtml ( pedestrianRedLight ));
Live Demo
Vehicles
Pedestrians
Css source .
Javascript source .
Conclusion
By defining our problems as signal streams and transformations it becomes
really easy to compose the solution using great libraries like RxJs .
Of course being reactive means much more than we seen in this simple example,
and I highly recommend you go deeper .
In the next “episode” I’m planning to transform this example to a
“Pelican Crossing ”.