Overview
This lab builds a console program that drives a traffic light through RED, YELLOW and GREEN entirely on its own, with no player input, using a real five-second delay between each change. You will build the switch statement inside TrafficLightController.cpp one case at a time, starting with a version where YELLOW always moves on to GREEN, running it long enough to see the light get stuck alternating between yellow and green and never turning red again, and then fixing that by having YELLOW consult a previousState variable to pick the correct next state depending on which state the light was in immediately before.
Learning Objectives
By the end of this lab you will be able to:
- Declare an enum class to represent a fixed set of traffic light states
- Drive continuous state transitions from inside an infinite loop using a real time delay, with no user input involved
- Recognise a switch case that sends the machine to the same next state no matter how it was reached, and predict the loop that causes
- Record a previousState variable that remembers which state the machine was in immediately before the current one
- Use previousState inside a single switch case to choose between two different next states, producing a repeating three-state cycle
Concept
Every state machine built so far has waited for the player to type a choice before moving to a new state. A traffic light cannot do that. It has to keep cycling through RED, YELLOW and GREEN on its own, on a fixed real-world clock, with nobody pressing a key. That rules out the std::cin-driven loop used earlier and calls for a loop that drives itself, an infinite while (true) that pauses for a real interval between transitions using std::this_thread::sleep_for(std::chrono::seconds(n)) before moving on.
A second problem shows up once that loop is running. RED and GREEN each have exactly one next state. YELLOW does not, since it has to send the light on to GREEN after RED, but back to RED after GREEN. A switch case can only look at currentState, which by definition is always YELLOW at that point, so currentState alone cannot tell the two situations apart. The fix is to remember one extra fact in a second variable, previousState, updated at the end of every case right before currentState changes again. Inside the YELLOW case, an if and else if comparing previousState against TrafficLightState::RED and TrafficLightState::GREEN is then enough to pick the correct next state, turning one case into two different transitions depending on how it was reached.
Tasks
Task 1 - The enum, the delay helper and turning the light red
Create a new Console App (C++) project in Visual Studio the same way as in the Environment Setup lab, naming it TrafficLightDemo. Use Add > New Item to add a new C++ source file named TrafficLightController.cpp, and write the code below.
- Declare
enum class TrafficLightStatewith three values,RED,YELLOW,GREEN. - Complete the
REDcase inmain()'sswitchso it printsRED - STOP, callsdelay(5), and setscurrentStatetoTrafficLightState::YELLOW.
Success condition: building and running the project prints RED - STOP once, five seconds after the program starts, and then falls silent. That silence is expected. Nothing handles TrafficLightState::YELLOW yet, so the loop just spins with no output until the program is closed manually.
#include <iostream>
#include <chrono>
#include <thread>
enum class TrafficLightState
{
// TODO: declare three values, RED, YELLOW, GREEN
};
void delay(int seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds));
}
int main()
{
TrafficLightState currentState = TrafficLightState::RED;
while (true)
{
switch (currentState)
{
case TrafficLightState::RED:
// TODO: print "RED - STOP", call delay(5), then set
// currentState to TrafficLightState::YELLOW
break;
}
}
}
Task 2 - A first attempt at yellow
Open TrafficLightController.cpp. Add a case TrafficLightState::YELLOW below the RED case that prints YELLOW - GET READY, calls delay(5), and sets currentState to TrafficLightState::GREEN.
Success condition: running the program now prints RED - STOP, waits five seconds, prints YELLOW - GET READY, waits five seconds, and then falls silent again. TrafficLightState::GREEN is not handled yet, so that second silence is expected too.
#include <iostream>
#include <chrono>
#include <thread>
enum class TrafficLightState
{
RED,
YELLOW,
GREEN
};
void delay(int seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds));
}
int main()
{
TrafficLightState currentState = TrafficLightState::RED;
while (true)
{
switch (currentState)
{
case TrafficLightState::RED:
std::cout << "RED - STOP" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
break;
case TrafficLightState::YELLOW:
// TODO: print "YELLOW - GET READY", call delay(5), then set
// currentState to TrafficLightState::GREEN
break;
}
}
}
Task 3 - Turning the light green
Open TrafficLightController.cpp. Add a case TrafficLightState::GREEN below the YELLOW case that prints GREEN - GO, calls delay(5), and sets currentState to TrafficLightState::YELLOW.
Success condition: the program now runs indefinitely with no more silent gaps, printing RED - STOP, YELLOW - GET READY and GREEN - GO in a repeating five-second sequence. Let it run for several cycles before moving on to the next task.
#include <iostream>
#include <chrono>
#include <thread>
enum class TrafficLightState
{
RED,
YELLOW,
GREEN
};
void delay(int seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds));
}
int main()
{
TrafficLightState currentState = TrafficLightState::RED;
while (true)
{
switch (currentState)
{
case TrafficLightState::RED:
std::cout << "RED - STOP" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
break;
case TrafficLightState::YELLOW:
std::cout << "YELLOW - GET READY" << std::endl;
delay(5);
currentState = TrafficLightState::GREEN;
break;
case TrafficLightState::GREEN:
// TODO: print "GREEN - GO", call delay(5), then set
// currentState to TrafficLightState::YELLOW
break;
}
}
}
Task 4 - Watching the light get stuck
Keep watching the console output for at least four or five more complete cycles instead of closing the program straight after the last task.
Success condition: RED - STOP only ever printed once, right at the very start. Every cycle after that alternates forever between YELLOW - GET READY and GREEN - GO, and the light never turns red again.
Look at the YELLOW case to see why. It always sets currentState to TrafficLightState::GREEN, no matter whether the light just arrived from RED or from GREEN. The very first time the machine passes through GREEN, every later visit to YELLOW sends it straight back to GREEN again, and RED can never be reached a second time.
Task 5 - Recording the previous state
Open TrafficLightController.cpp.
- In
main(), declare a second variable,TrafficLightState previousState = TrafficLightState::RED;, alongsidecurrentState. - At the end of the
REDcase, aftercurrentStateis set, setpreviousStatetoTrafficLightState::RED. - At the end of the
GREENcase, aftercurrentStateis set, setpreviousStatetoTrafficLightState::GREEN.
Success condition: previousState now tracks the light's history, but nothing reads it yet. Building and running the program produces exactly the same stuck yellow and green cycle as before. That is expected, the fix comes in the next task.
#include <iostream>
#include <chrono>
#include <thread>
enum class TrafficLightState
{
RED,
YELLOW,
GREEN
};
void delay(int seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds));
}
int main()
{
TrafficLightState currentState = TrafficLightState::RED;
// TODO: declare TrafficLightState previousState = TrafficLightState::RED;
while (true)
{
switch (currentState)
{
case TrafficLightState::RED:
std::cout << "RED - STOP" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
// TODO: set previousState to TrafficLightState::RED
break;
case TrafficLightState::YELLOW:
std::cout << "YELLOW - GET READY" << std::endl;
delay(5);
currentState = TrafficLightState::GREEN;
break;
case TrafficLightState::GREEN:
std::cout << "GREEN - GO" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
// TODO: set previousState to TrafficLightState::GREEN
break;
}
}
}
Task 6 - Fixing yellow with previousState
Open TrafficLightController.cpp.
- Replace the single line
currentState = TrafficLightState::GREEN;in theYELLOWcase with anifthat setscurrentStatetoTrafficLightState::GREENwhenpreviousStateisTrafficLightState::RED. - Add an
else ifto that sameifthat setscurrentStatetoTrafficLightState::REDwhenpreviousStateisTrafficLightState::GREEN. - At the end of the
YELLOWcase, after theifandelse if, setpreviousStatetoTrafficLightState::YELLOW.
Success condition: running the program for several cycles now prints RED - STOP, YELLOW - GET READY, GREEN - GO, YELLOW - GET READY, RED - STOP, and repeats that same five-print sequence indefinitely. RED - STOP reappears every cycle instead of only once at the start.
The diagram below traces exactly this path through the YELLOW case now that it consults previousState.
#include <iostream>
#include <chrono>
#include <thread>
enum class TrafficLightState
{
RED,
YELLOW,
GREEN
};
void delay(int seconds)
{
std::this_thread::sleep_for(std::chrono::seconds(seconds));
}
int main()
{
TrafficLightState currentState = TrafficLightState::RED;
TrafficLightState previousState = TrafficLightState::RED;
while (true)
{
switch (currentState)
{
case TrafficLightState::RED:
std::cout << "RED - STOP" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
previousState = TrafficLightState::RED;
break;
case TrafficLightState::YELLOW:
std::cout << "YELLOW - GET READY" << std::endl;
delay(5);
// TODO: replace this line with an if / else if on previousState
currentState = TrafficLightState::GREEN;
// TODO: set previousState to TrafficLightState::YELLOW
break;
case TrafficLightState::GREEN:
std::cout << "GREEN - GO" << std::endl;
delay(5);
currentState = TrafficLightState::YELLOW;
previousState = TrafficLightState::GREEN;
break;
}
}
}
Solution
Try the tasks above before revealing the solution.