Implementing a Traffic Light System in C Language

The traffic lights we commonly see on the road are mainly of four types: left arrow, straight, right arrow, and a circular light. The phrase “Red means stop, green means go” is a traffic safety lesson taught since kindergarten. Now, we will simulate this using the C language and the EasyX graphics library, as shown in the effect below (for an exciting video, please see the bottom):

Implementing a Traffic Light System in C Language

【Algorithm Design Process】

1. Define the attributes related to the traffic lights, including arrow direction, color, remaining time, and display coordinates. The relevant structures, header files, and macro definitions are as follows:

#include "stdio.h"
#include "graphics.h"
#include "conio.h"
#include "math.h"
#include "time.h"

#define GRAPH_WIDTH 640 // Width of the drawing window
#define GRAPH_HEIGHT 480 // Height of the drawing window

// Arrow parameters
#define ARROW_SIZE 40 // Size of the arrow
#define ARROW_THICKNESS 10 // Thickness of the arrow line
#define ARROW_STRAIGHT_LINE 15 // Offset for the straight part of the arrow

// Arrow directions: left turn, straight, right turn
#define LEFT 0
#define STRAIGHT 1
#define RIGHT 2
#define LIGHTS_NUMS 3 // Number of traffic lights

// Countdown coordinate display offset
#define COUNTDOWN_LEFT_STEP (-120)
#define COUNTDOWN_RIGHT_STEP (120)
// Countdown display coordinates
#define COUNTDOWN_X(x) ((GRAPH_WIDTH >> 1) + (x))
#define COUNTDOWN_Y 250 

#define TIMER 60 // Timer: used to calculate traffic light state transitions
#define LEFT_GREEN_SEC 15 // Left turn green light duration in seconds
#define STRAIGHT_GREEN_SEC 30 // Straight green light duration in seconds
#define RIGHT_GREEN_SEC 15 // Right turn green light duration in seconds

// Traffic light state structure
typedef struct TrafficLight
{
    int state; // Traffic light state 0: red light 1: green light 2: yellow light
    int colour; // Traffic light color
    int direction; // Traffic light direction
    int arrowX; // Traffic light x coordinate
    int arrowY; // Traffic light y coordinate
    int countdown; // Countdown seconds
    int countdownX; // Countdown x coordinate
    int countdownY; // Countdown y coordinate
} TTrafficLight;

2. Initialize the traffic lights so that each light and its remaining time can be displayed at specific coordinates.

// Initialize traffic lights
void InitTrafficLights(TrafficLight* ptLights)
{
    for (int i = 0; i < LIGHTS_NUMS; i++, ptLights++)
    {
        if (!ptLights) return;

        ptLights->direction = i;
        ptLights->colour = BLACK; // The traffic light initially has no color: black
        ptLights->state = 0xf;

        if (LEFT == ptLights->direction)
        {
            // Left arrow coordinates and countdown number's x coordinate
            ptLights->arrowX = (GRAPH_WIDTH >> 1) - 100;
            ptLights->arrowY = (GRAPH_HEIGHT >> 1) - 20;
            ptLights->countdownX = COUNTDOWN_X(COUNTDOWN_LEFT_STEP);
        }
        else if (STRAIGHT == ptLights->direction)
        {
            // Straight arrow coordinates and countdown number's x coordinate
            ptLights->arrowX = GRAPH_WIDTH >> 1;
            ptLights->arrowY = GRAPH_HEIGHT >> 1;
            ptLights->countdownX = GRAPH_WIDTH >> 1;
        }
        else
        {
            // Right arrow coordinates and countdown number's x coordinate
            ptLights->arrowX = (GRAPH_WIDTH >> 1) + 100;
            ptLights->arrowY = (GRAPH_HEIGHT >> 1) - 20;
            ptLights->countdownX = COUNTDOWN_X(COUNTDOWN_RIGHT_STEP);
        }
        // All arrows have the same Y coordinate
        ptLights->countdownY = COUNTDOWN_Y;
    }
}

3. Draw arrows: left turn arrow, straight arrow, right turn arrow.

// Draw arrow
void DrawArrow(int direction, int x, int y, int color)
{
    // Set line style
    setlinestyle(PS_SOLID, ARROW_THICKNESS);
    setlinecolor(color);

    switch (direction)
    {
    case LEFT: // Left turn arrow
        line(x, y, x - ARROW_SIZE, y); // Straight part
        line(x - ARROW_SIZE, y, x - ARROW_SIZE + ARROW_STRAIGHT_LINE, y - ARROW_STRAIGHT_LINE);
        line(x - ARROW_SIZE, y, x - ARROW_SIZE + ARROW_STRAIGHT_LINE, y + ARROW_STRAIGHT_LINE);
        break;

    case STRAIGHT: // Straight arrow
        line(x, y, x, y - ARROW_SIZE);
        line(x, y - ARROW_SIZE, x - ARROW_STRAIGHT_LINE, y - ARROW_SIZE + ARROW_STRAIGHT_LINE);
        line(x, y - ARROW_SIZE, x + ARROW_STRAIGHT_LINE, y - ARROW_SIZE + ARROW_STRAIGHT_LINE);
        break;

    case RIGHT: // Right turn arrow
        line(x, y, x + ARROW_SIZE, y);
        line(x + ARROW_SIZE, y, x + ARROW_SIZE - ARROW_STRAIGHT_LINE, y - ARROW_STRAIGHT_LINE);
        line(x + ARROW_SIZE, y, x + ARROW_SIZE - ARROW_STRAIGHT_LINE, y + ARROW_STRAIGHT_LINE);
        break;

    default:
        break;
    }
}

4. Display remaining time: below the arrow, allowing drivers to intuitively judge in advance. The design of the relevant font size and display area is as follows.

// Countdown number display
void ShowCountDown(int x, int y, int number, int color)
{
    char str[256] = {0};
    sprintf_s(str, sizeof(str), "%d", number);

    settextstyle(50, 0, "Arial");
    settextcolor(color);

    RECT r = { x - 25, y, x + 25, y + 60 };
    drawtext(str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

5. Real-time update of the traffic light state transitions. The waiting time for each light is defined arbitrarily here; readers can modify it according to actual conditions.

// Update traffic light state
void UpdateTrafficLights(TTrafficLight *ptLights, unsigned int *timer)
{
    if (!ptLights || !timer) return;

    // Traffic light cycle: 60 seconds for one cycle
    unsigned int cycle = *timer % TIMER;
    (*timer)++;

    // Left turn light control
    if (LEFT_GREEN_SEC > cycle)
    {
        ptLights[LEFT].state = 1; // Green light
        ptLights[LEFT].countdown = LEFT_GREEN_SEC - cycle;
    }
    else
    {
        ptLights[LEFT].state = 0; // Red light
        ptLights[LEFT].countdown = TIMER - cycle;
    }

    // Straight light control
    cycle = (cycle + STRAIGHT_GREEN_SEC) % TIMER;
    if (cycle < STRAIGHT_GREEN_SEC)
    {
        ptLights[STRAIGHT].state = 1; // Green light
        ptLights[STRAIGHT].countdown = STRAIGHT_GREEN_SEC - cycle;
    }
    else
    {
        ptLights[STRAIGHT].state = 0; // Red light
        ptLights[STRAIGHT].countdown = TIMER - cycle;
    }

    // Right turn light control
    cycle = (cycle + RIGHT_GREEN_SEC) % TIMER;
    if (cycle < RIGHT_GREEN_SEC)
    {
        ptLights[RIGHT].state = 1; // Green light
        ptLights[RIGHT].countdown = RIGHT_GREEN_SEC - cycle;
    }
    else
    {
        ptLights[RIGHT].state = 0; // Red light
        ptLights[RIGHT].countdown = TIMER - cycle;
    }
}

6. Color switching: The green light turns yellow in the last three seconds. A friendly reminder: do not run the yellow light.

// Color change
void ColorChange(TrafficLight *ptLight, unsigned int direction)
{
    if (!ptLight || LEFT > direction || RIGHT < direction) return;

    if (3 >= ptLight[direction].countdown && 0 != ptLight[direction].state)
    {
        // The last three seconds of the green light are yellow
        ptLight[direction].colour = YELLOW;
    }
    else
    {
        // 0: red light 1: green light 2: yellow light
        ptLight[direction].colour = ptLight[direction].state == 0 ? RED : ptLight[direction].state == 1 ? GREEN : YELLOW;
    }
}

7. Run the traffic light and display it on the screen.

void TrafficLightRun()
{
    initgraph(GRAPH_WIDTH, GRAPH_HEIGHT);

    TrafficLight tLights[LIGHTS_NUMS]; // States of three traffic lights
    unsigned int timer = 0;
    unsigned int curTime;
    unsigned int lastTime = clock() / CLOCKS_PER_SEC; // Record the last update time

    InitTrafficLights(tLights);

    BeginBatchDraw();
    for (;;)
    {
        // Update once per second
        curTime = clock() / CLOCKS_PER_SEC;
        if (curTime - lastTime >= 1)
        {
            UpdateTrafficLights(tLights, &timer);
            lastTime = clock() / CLOCKS_PER_SEC;

            cleardevice();

            // Draw each arrow and display remaining time based on traffic light state
            for (int i = 0; i < LIGHTS_NUMS; i++)
            {
                ColorChange(tLights, i);
                DrawArrow(i, tLights[i].arrowX, tLights[i].arrowY, tLights[i].colour);
                ShowCountDown(tLights[i].countdownX, tLights[i].countdownY, tLights[i].countdown, tLights[i].colour);
            }
        }
        FlushBatchDraw();
    }

    EndBatchDraw();
    closegraph();
}

8. Main function

int main()
{
    TrafficLightRun();
    return 0;
}

【Running Result】 (Video):

Summary: The code logic is relatively simple, and it can be designed based on real-world scenarios. There are still many areas in the code that can be optimized. Additionally, a nice effect can be added, which is to make the yellow light flash in the last three seconds before turning red, achieving a real-world warning effect to avoid running a red light.

Debugging Environment: Windows Visual Studio 2022

Leave a Comment