Final State Machine

I tried to code a traffic light. What I have so far is somewhat where I think, it could be a FSM. But I'm not sure about if it would be such. Also, I have some hunch, that the logic at my program could maybe better separated from the invocation of calling output and sleep function.

So here is what I have coded so far:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <thread>
#include <chrono>
#include <iostream>
#include <cstdlib>

// forward declarations
class Traffic_light;
std::ostream & operator<< (std::ostream &, const Traffic_light);

class Traffic_light
{
private:

    std::ostream & m_ostream;
    int m_state;

public:

    Traffic_light( std::ostream & os = std::cout, int state = 0 )
    : m_ostream{os}, m_state {state % 4}
    {}

    int state() const { return m_state; }

    void do_switch()
    {
        switch( m_state )
        {
            case 0:  // red
                std::this_thread::sleep_for( std::chrono::seconds(10) );
                m_state = 1;
                break;

            case 1:  // yellow
                std::this_thread::sleep_for( std::chrono::seconds(2) );
                m_state = 2;
                break;

            case 2:   // green
                std::this_thread::sleep_for( std::chrono::seconds(10) );
                m_state = 3;
                break;

            case 3:   // yellow
                std::this_thread::sleep_for( std::chrono::seconds(2) );
                m_state = 0;
                break;
        }
    }
    int run()
    {
        if( ! m_ostream )
            return EXIT_FAILURE;

        while( true )
        {
            m_ostream << *this << '\n';
            m_ostream.flush();
            do_switch();
        }
        return EXIT_SUCCESS;
    }
};

std::ostream & operator<< (std::ostream & os, const Traffic_light & tl)
{
    switch( tl.state() ) {
        case 0: return os << "red";
        case 1: return os << "yellow";
        case 2: return os << "green";
        case 3: return os << "yellow";
    }
    return os; // for compiler warning suppression
}

int main()
{
    Traffic_light tl{ std::cout, 3 };
    return tl.run();
}


Thanks for any improvement suggestions.
Using a look up table (a state table):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <map>
#include <ctime>

struct traffic_light
{
    enum class state : int {  RED, YELLOW_R2G, GREEN, YELLOW_G2R };

    traffic_light() = default ;
    traffic_light( state cs, std::ostream& stm ) : curr_state(cs), out(stm) {}

    void do_switch()
    {
        // look up the behaviour for the current state
        const auto& p = behaviour[curr_state] ;
        std::this_thread::sleep_for( p.wait_period ) ;
        curr_state = p.next ; // switch to the next state
    }

    void run( unsigned int n )
    {
        for( unsigned int i = 0 ; i < n ; ++i )
        {
            out << now() << ' ' << behaviour[curr_state].text << '\n' << std::flush ;
            do_switch() ;
        }
    }

    private:
        state curr_state = state::GREEN ;
        std::ostream& out = std::cout ;

        struct params // the parameters for each state
        {
            std::string text ;
            std::chrono::seconds wait_period ;
            state next ;
        };
        
        // this state table provides the state to behaviour map
        static std::map< state, params > behaviour ;

        static std::string now()
        {
            std::time_t t = std::time(nullptr);
            char time_stamp[128] {} ;
            std::strftime( time_stamp, sizeof(time_stamp), "%H:%M:%S", std::localtime( std::addressof(t) ) ) ;
            return time_stamp ;
        }
};

using namespace std::literals ;

std::map< traffic_light::state, traffic_light::params > traffic_light::behaviour
{
    { traffic_light::state::GREEN, traffic_light::params{ "green", 10s, traffic_light::state::YELLOW_G2R } },
    { traffic_light::state::YELLOW_G2R, traffic_light::params{ "yellow", 2s, traffic_light::state::RED } },
    { traffic_light::state::RED, traffic_light::params{ "red", 5s, traffic_light::state::YELLOW_R2G } },
    { traffic_light::state::YELLOW_R2G, traffic_light::params{ "yellow", 2s, traffic_light::state::GREEN } }
};

int main()
{
    traffic_light tl ;
    tl.run(20) ;
}
Without a true FSM (a FSM is too heavy here in my opinion; there are no conditions to lead off the 123 123 123 path, can't go red yellow green or green red yellow etc), just pure iteration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

int main()
{
enum light{green = 10, yellow = 12, red = 22 }; //the time slices can go in here 
string colors[]{"green","yellow", "red"};
int thelight = 0;
while(true)
	{
                //default is green (zero), add 1 if its more than green, add another 1 if more than yellow     
                //so that you get 0, 1, or 2 offset in the text array.  the map is cleaner, of course
		string current = colors[(thelight >= green) + (thelight >= yellow)];
		cout << "the light is " << current << endl; 		 
		std::this_thread::sleep_for( std::chrono::seconds(1));
		thelight ++;
		thelight %= red; 
	}
}


plenty of reasons why that isnt awesome though. Say you add in turn signals that trigger at random (simulating a car in the turn lane triggering a pressure plate). Then the FSM model or a better approach is certainly justified, and expecting that, the above is just for fun :) The FSM could also represent all the directions of the light, if you wanted to know what it was doing for every lane of traffic, add in crosswalks etc...
Last edited on
I just want to check if you truly mean to have two "yellow" states? Here in the USA, lights go Red->Green->Yellow->Red. There is no yellow state after red.
In the UK you have red->red/yellow->green->yellow->red
https://theorytest.org.uk/traffic-lights/

It's officially "amber". And it gets complicated when you've got a filter!
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
#include<thread>
#include<chrono>
#include<windows.h>
using namespace std;

HANDLE h = GetStdHandle( STD_OUTPUT_HANDLE );
short BLACK = 0, GREEN = 2, YELLOW = 6, RED = 4, WHITE = 15;

//======================================

void gotoxy( short x, short y ) { SetConsoleCursorPosition( h, COORD{ x, y } ); }
void colour( short n ) { SetConsoleTextAttribute( h, n  ); }

//======================================

struct State
{
   short top, middle, bottom;
   int time;
};

//--------------------------------------

void display( State st )
{
   gotoxy( 1, 10 );   colour( WHITE     );   cout << "---";
   gotoxy( 2, 11 );   colour( st.top    );   cout << 'O';
   gotoxy( 2, 12 );   colour( st.middle );   cout << 'O';
   gotoxy( 2, 13 );   colour( st.bottom );   cout << 'O';
   gotoxy( 1, 14 );   colour( WHITE     );   cout << "---";
}

//--------------------------------------

int main()
{
   short OFF = BLACK;
   State states[] = { { RED, OFF, OFF, 2 }, { RED, YELLOW, OFF, 1 }, { OFF, OFF, GREEN, 2 }, { OFF, YELLOW, OFF, 1 } };
   int i = 0;
   while( true )
   {
      display( states[i] );
      this_thread::sleep_for( chrono::seconds( states[i].time ) );
      i = ( i + 1 ) % 4;
   }
}

Topic archived. No new replies allowed.