University of Wales Trinity Saint David
ACCB5017 - Games Architecture and Mechanics

Traffic Light Transitions

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:

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.

  1. Declare enum class TrafficLightState with three values, RED, YELLOW, GREEN.
  2. Complete the RED case in main()'s switch so it prints RED - STOP, calls delay(5), and sets currentState to TrafficLightState::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.

TrafficLightController.cpp
#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.

TrafficLightController.cpp
#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.

TrafficLightController.cpp
#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.

  1. In main(), declare a second variable, TrafficLightState previousState = TrafficLightState::RED;, alongside currentState.
  2. At the end of the RED case, after currentState is set, set previousState to TrafficLightState::RED.
  3. At the end of the GREEN case, after currentState is set, set previousState to TrafficLightState::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.

TrafficLightController.cpp
#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.

  1. Replace the single line currentState = TrafficLightState::GREEN; in the YELLOW case with an if that sets currentState to TrafficLightState::GREEN when previousState is TrafficLightState::RED.
  2. Add an else if to that same if that sets currentState to TrafficLightState::RED when previousState is TrafficLightState::GREEN.
  3. At the end of the YELLOW case, after the if and else if, set previousState to TrafficLightState::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.

TrafficLightController.cpp
#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.