Home Tools

Published

- 6 min read

Build your own Red Light Green Light Game with Python

img of Build your own Red Light Green Light Game with Python

The game uses your laptop/PC webcam to detect motion. You are allowed to move when the green light is shown, but you should freeze when the red light is shown. If any movement is detected when there is a red light, then the game ends. Green light and red light appear alternatively at fixed intervals. In this tutorial, we are using five seconds as the interval.

Setting up a virtual environment and installing packages

In your code editor, open the terminal and create a virtual environment and activate it to install dependencies.

   python -m venv venv
cd venv/Scripts
activate
cd ../..

Now, after activating the virtual environment, we can install the necessary Python packages. cv2: OpenCV is used for video capture and motion detection. numpy: For efficient numerical operations in OpenCV Now run the below in your terminal to install the dependencies.

   pip install numpy opencv-python

Importing packages

With the environment setup and packages installed, it is time for us to get into coding mode.

   import cv2
import tkinter as tk
from threading import Thread
import time
import numpy as np
  • cv2: OpenCV is used for video capture and motion detection.
  • tkinter: Used to create the graphical user interface (GUI) for the game.
  • threading: Enables running tasks (motion detection and GUI) concurrently.
  • time: For managing delays and timing tasks.
  • numpy: For efficient numerical operations in OpenCV.

Initializing the Game

Next we initiate the logic class. The LightGame class encapsulates the game logic. The TIMER_DURATION class-level constant specifies the duration for each red or green light. Next we will setup the contructor.

   class LightGame:
    TIMER_DURATION = 5
    def __init__(self, root):
	self.root = root
	self.root.title("Red Light Green Light Game")
	self.root.geometry("300x300")
  • self.root: This is the main UI Tkinter window.
  • self.root.title and self.root.geometry: Sets the window title and size.

We leave it to you to experiment with Tkinter window styling and properties.

   self.canvas = tk.Canvas(self.root, width=300, height=300, bg="white")
self.canvas.pack()
self.light_indicator = self.canvas.create_oval(100, 100, 200, 200, fill="red")
self.timer_text = self.canvas.create_text(150, 250, text=f"Time: {self.timer_value}s", font=("Arial", 16), fill="black")

We have now added the UI elements,and it is self explanatory. Next we are initlializing some variables that track the current game status.

   self.current_light = "Red"
self.game_running = True
self.timer_value = self.TIMER_DURATION

Now, some important part. We are running the camera input for motion detection and the UI in separate threads.

   self.webcam_thread = Thread(target=self.check_motion, daemon=True)
self.webcam_thread.start()

self.webcam_thread performs the motion detection in a separate thread without interfering the UI. Post this game logic recursive functions are called

   self.toggle_light()
self.update_timer()

Toggle Light

The toggle_light is responsible for changing the colour of light at regular intervals. It takes the current colour to update the UI with other colour

   def toggle_light(self):
    if not self.game_running:
        return
	if self.current_light == "Red":
        self.current_light = "Green"
        self.canvas.itemconfig(self.light_indicator, fill="green")
    else:
        self.current_light = "Red"
        self.canvas.itemconfig(self.light_indicator, fill="red")
	self.timer_value = self.TIMER_DURATION
    self.root.after(self.TIMER_DURATION * 1000, self.toggle_light)

self.root.after, schedules the next toggle after 5 seconds. Note Python timer works in milliseconds so we do a *1000.

Timer Update

The update_timer function is responsible for updating the timer in the UI.

   def update_timer(self):
    if not self.game_running:
        return
	self.canvas.itemconfig(self.timer_text, text=f"Time: {self.timer_value}s")
    self.timer_value -= 1
	if self.timer_value >= 0:
        self.root.after(1000, self.update_timer)

The update_timer function is called again after 1 second if the timer value is not below 0. The timer text is updated on the canvas. The timer value is decremented by 1.

Motion Detection

Now comes the key part; Motion Detection.

       def check_motion(self):
        cap = cv2.VideoCapture(0)
        _, frame1 = cap.read()
        _, frame2 = cap.read()

        # Define the minimum contour area for motion detection
		# Adjust this value for sensitivity. Lower the value, higher the sensitivity
        MIN_CONTOUR_AREA = 1000
        
        while self.game_running:
            if self.current_light == "Red":
                diff = cv2.absdiff(frame1, frame2)
                gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
                blur = cv2.GaussianBlur(gray, (5, 5), 0)
                _, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
                dilated = cv2.dilate(thresh, None, iterations=3)
                contours, _ = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

                for contour in contours:
                    if cv2.contourArea(contour) > MIN_CONTOUR_AREA:
                        self.end_game()
                        break
            
            frame1 = frame2
            _, frame2 = cap.read()

            # Display the live video feed
            cv2.imshow("Live Video Feed", frame2)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                self.end_game()
                break

        cap.release()
        cv2.destroyAllWindows()
  • cv2.VideoCapture: Opens webcam for motion detection.
  • _, frame1 = cap.read() and _, frame2 = cap.read(): Captures two consecutive frames to detect differences.
  • cv2.absdiff: Computes the absolute difference between two frames.
  • cv2.cvtColor: Converts the result to grayscale.
  • cv2.GaussianBlur: Smoothens the image to reduce noise.
  • cv2.threshold: Creates a binary image for easier contour detection.
  • cv2.dilate: Enhances the white regions (motion areas).
  • cv2.findContours: Detects contours in the image.

If a contour’s area exceeds the MIN_CONTOUR_AREA threshold during a red light, the game ends. The logic behind using of contour area works likea find n differences between two images. Here, the two images are the two frames, and the difference is caused motion. cv2.imshow() displays the camera feed which you’ll generally see in any other OpenCV application. The game ends either through motion detection during red light or by pressing ‘q’. It calls the end_game() function.

Ending the Game

   def end_game(self):
    self.game_running = False
    self.canvas.create_text(150, 150, text="Game Over!", font=("Arial", 24), fill="black")
    self.root.after(500, self.root.destroy)

The end_game function sets the game_running status variable to False, displays “Game Over!”, and closes the Tkinter UI window.

Main Execution

   if __name__ == "__main__":
    root = tk.Tk()
    game = LightGame(root)
    root.mainloop()

The main function as you all might know, initiates the Tkinter UI window, and also calls the LightGame class constructor to start the game.

Complete Code

   import cv2
import tkinter as tk
from threading import Thread
import time
import numpy as np


class LightGame:
    TIMER_DURATION = 5  # Change the timer value based on your preference

    def __init__(self, root):
        self.root = root
        self.root.title("Red Light Green Light Game")
        self.root.geometry("300x300")
        
        self.canvas = tk.Canvas(self.root, width=300, height=300, bg="white")
        self.canvas.pack()
        
        self.current_light = "Red"
        self.game_running = True
        self.timer_value = self.TIMER_DURATION  # Use the class-level constant
        
        self.light_indicator = self.canvas.create_oval(100, 100, 200, 200, fill="red")
        self.timer_text = self.canvas.create_text(150, 250, text=f"Time: {self.timer_value}s", font=("Arial", 16), fill="black")
        
        self.webcam_thread = Thread(target=self.check_motion, daemon=True)
        self.webcam_thread.start()
        
        self.toggle_light()
        self.update_timer()

    def toggle_light(self):
        if not self.game_running:
            return
        
        # Toggle light color
        if self.current_light == "Red":
            self.current_light = "Green"
            self.canvas.itemconfig(self.light_indicator, fill="green")
        else:
            self.current_light = "Red"
            self.canvas.itemconfig(self.light_indicator, fill="red")
        
        # Reset the timer every light change
        self.timer_value = self.TIMER_DURATION
        self.root.after(self.TIMER_DURATION * 1000, self.toggle_light)

    def update_timer(self):
        if not self.game_running:
            return
        
        self.canvas.itemconfig(self.timer_text, text=f"Time: {self.timer_value}s")
        self.timer_value -= 1
        
        # Update the timer every second and ensure it syncs with light toggle
        if self.timer_value >= 0:
            self.root.after(1000, self.update_timer)

    def check_motion(self):
        cap = cv2.VideoCapture(0)
        _, frame1 = cap.read()
        _, frame2 = cap.read()

        # Define the minimum contour area for motion detection
		# Adjust this value for sensitivity. Lower the value, higher the sensitivity
        MIN_CONTOUR_AREA = 1000
        
        while self.game_running:
            if self.current_light == "Red":
                diff = cv2.absdiff(frame1, frame2)
                gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
                blur = cv2.GaussianBlur(gray, (5, 5), 0)
                _, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
                dilated = cv2.dilate(thresh, None, iterations=3)
                contours, _ = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

                for contour in contours:
                    if cv2.contourArea(contour) > MIN_CONTOUR_AREA:
                        self.end_game()
                        break
            
            frame1 = frame2
            _, frame2 = cap.read()

            # Display the live video feed
            cv2.imshow("Live Video Feed", frame2)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                self.end_game()
                break

        cap.release()
        cv2.destroyAllWindows()

    def end_game(self):
        self.game_running = False
        self.canvas.create_text(150, 150, text="Game Over!", font=("Arial", 24), fill="black")
        self.root.after(500, self.root.destroy)


if __name__ == "__main__":
    root = tk.Tk()
    game = LightGame(root)
    root.mainloop()

Until we meet you next time with another interesting project, happy coding guys!!

Related Posts

There are no related posts yet. 😢