- Introduction
- Requirements
- Installation
- How to Play
- Game Calculations and Logic
- Threads and Semaphores Implementation
- Class Implementation
- Final Considerations
RaceCondition is a Formula 1 racing simulation. The game allows the player to compete against four AI-controlled racers. The main focus of the project is the practical application of threads and semaphores to manage concurrency and synchronization between different system components.
The objective is to provide an interactive experience where the player must manage pit stop strategies, choosing appropriate tire types to win the race, while dealing with the automatic decisions of the AIs. The simulation highlights how multiple threads can operate simultaneously and how semaphores are used to control access to shared resources, such as the pit stop and the finish line.
- C++ Compiler: GCC or Clang with C++20 support.
- Make: Build utility to compile the project.
git clone https://github.com/lfelipediniz/RaceCondition.git
cd RaceCondition
make
make run
-
Racer Name When starting the game, you will need to enter the name of your racer.
-
Initial Tire Type Selection:
- Select the type of tire to start the race:
- s: SOFT - Higher speed, higher wear.
- m: MEDIUM - Intermediate speed, medium wear.
- h: HARD - Lower speed, lower wear.
- Select the type of tire to start the race:
-
Monitoring:
- The track will be displayed in the terminal, showing each racer's position.
- A leaderboard will be updated every second, displaying information such as tire wear, tire type, pit stop status, and current situation (racing, in pit stop, or disqualified).
-
Perform Pit Stop:
- When necessary, you can choose to enter the pit stop to change tires.
- To enter the pit stop, type:
- s: Switch to SOFT tires.
- m: Switch to MEDIUM tires.
- h: Switch to HARD tires.
- The pit stop will be occupied for 3 seconds, during which no other car can use it.
-
Winning Conditions:
- The race ends when all racers cross the finish line or when all have been disqualified.
- If a tire bursts (wear reaches 10), the car is disqualified from the race. When this happens, an X will appear in the racer's status to indicate they are out.
When the race ends, a message is displayed with the final results, and you will be prompted to exit the game.
Each type of tire has a specific wear rate that increases every second of the race:
Level | Code | Wear Increment per Second |
---|---|---|
🟢 Soft | s |
|
🟡 Medium | m |
|
🔴 Hard | h |
If tire wear reaches 10, the tire bursts and the car is eliminated from the race.
The speed of each car is calculated based on the tire type and accumulated wear:
Where BaseSpeed depends on the chosen tire type:
-
SOFT:
$2.1$ units -
MEDIUM:
$1.9$ units -
HARD:
$1.6$ units
- Car with SOFT Tire and Wear 3:
- Car with MEDIUM Tire and Wear 5:
- Car with HARD Tire and Wear 2:
The pit stop can be occupied by only one car.
-
Player
- The player can choose to enter the pit stop at any time to change tires, selecting between SOFT, MEDIUM, or HARD.
- Performing a pit stop pauses the car for 3 seconds, during which the tire is changed and the wear is reset.
-
Artificial Intelligences (AI):
- The AIs use a random algorithm, allowing each race to be unique.
The project uses threads and semaphores to manage concurrency between multiple racers (player and AIs) and to synchronize access to shared resources, such as the pit stop and the finish line.
Threads allow the simultaneous execution of different parts of the program, simulating the real concurrency of a race. The main threads implemented are:
-
Car Thread
- All cars have a thread to race on the track.
-
AI Threads
- Each AI car will have a thread with a controller that decides when it should enter the pit and which tire it will put on.
-
Player Thread
- This is a thread that will monitor user input to check when the player wants to enter the pit stop, which type of tire they want to put on, and thus control the car regarding pit stop entry.
-
Track Drawing Thread
- Updates the visualization of the track and the leaderboard in real-time.
Semaphores are used to control access to shared resources, allowing only one thread to access the resource at a time, preventing race conditions.
-
Pit Stop Semaphore
- Uses
std::mutex
. - Ensures that only one car (player or AI) can use the pit stop at a time.
- When a car enters the pit stop, the mutex is locked, preventing other cars from accessing the pit stop simultaneously.
- After the pit stop is completed, the mutex is released, allowing another car to use the pit stop.
- Uses
-
Finish Line Semaphore
- Uses
std::mutex
. - Ensures that only one car (player or AI) can pass the finish line at a time.
- When a car passes the finish line, the mutex is locked, preventing other cars from passing it simultaneously.
- After the car crosses the line, the mutex is released, allowing other cars to pass it.
- Uses
-
Threads
- Allow simulating the race in real-time, with multiple drivers advancing simultaneously.
- Improve the responsiveness of the game, allowing the player to interact while the race is happening.
-
Semaphores
- Ensure proper synchronization in accessing the pit stop and the finish line.
- Prevent race conditions where multiple cars attempt to access the pit stop or the finish line at the same time, which could cause RaceCondition.
- The choice of mutex semaphore was made for both cases, as there was only a need for the semaphore to assume values 0 and 1.
-
Start of the Race
- The method (
Jogo::iniciar
) creates and starts the threads of all cars and controllers (player and AIs).
- The method (
-
Car Movement
- Each car thread executes
correr()
, incrementing the distance traveled and checking tire wear.
- Each car thread executes
-
Performing Pit Stops
- When a controller decides to perform a pit stop (player or AI), it tries to acquire the
pitstopMutex
. - If the mutex is successfully captured, the car enters the pit stop, changes tires, and releases the mutex after 3 seconds.
- While the pit stop is occupied, other drivers attempting to perform a pit stop will be blocked until the mutex is released.
- When a controller decides to perform a pit stop (player or AI), it tries to acquire the
-
Passing the Finish Line
- When a car reaches the finish line, it tries to acquire the
OrdemDeChegada
mutex. - If the mutex is successfully captured, the car passes the finish line, is marked as having finished the race, and releases the critical region.
- When a car reaches the finish line, it tries to acquire the
-
End of the Race
- The race ends when all cars reach the finish line or burst their tires.
- All threads are joined, and the final standings are displayed.
The main classes of RaceCondition are Carro
, IA
, Player
, and Jogo
. Each of them has specific responsibilities in managing the race, interacting with the player, and controlling the Artificial Intelligences.
Represents each car in the race, whether it is a player's car or an AI's car.
-
Attributes
Pneu *pneu
: Type and state of the tires.atomic<float> distanciaPercorrida
: Distance traveled by the car.mutex &pitstopMutex
: Reference to the pit stop mutex.string nome
: Racer's name.atomic<bool> DentroPitStop
: Indicates if the car is in the pit stop.atomic<bool> ChegouNaLargada
: Indicates if the car has reached the finish line.atomic<bool> EstourouPneu
: Indicates if the tire has burst.mutex &OrdemDeChegada
: Mutex to manage the order of arrival.atomic<int> &PosicaoDoCarro
: Car's position when it passes the finish line.
-
Methods
void fazerPitStop(char novoPneu)
: Performs the pit stop to change tires.void correr()
: Simulates the car's movement in the race.string getNomeCarro()
: Returns the car's name.
Controls the logic of the AIs that compete in the race.
-
Attributes
string nome
: AI's name.Carro *carro
: Pointer to the car controlled by the AI.mutex &pitstopMutex
: Reference to the pit stop mutex.mutex &OrdemDeChegada
: Reference to the finish line mutex.int ResetarPneu
: Random value to determine when to perform a pit stop.atomic<int> &PosicaoDoCarro
: Car's position when it passes the finish line.
-
Methods
void controlar()
: Manages the AI's movement and pit stop strategies.
Controls the player's logic in the race.
-
Attributes
string nome
: Player's name.Carro *carro
: Pointer to the car controlled by the player.mutex &pitstopMutex
: Reference to the pit stop mutex.mutex &OrdemDeChegada
: Reference to the finish line mutex.atomic<int> &PosicaoDoCarro
: Car's position when it passes the finish line.
-
Methods
void controlar()
: Monitors player inputs to perform pit stops.
Manages the overall state of the race, including creating racers, initializing threads, and updating the visualization.
-
Attributes
Player *jogador
: Pointer to the player.mutex pitstopMutex
: Mutex to control access to the pit stop.mutex OrdemDeChegada
: Mutex to manage the order of arrival.atomic<int> PosicaoDoCarro
: Current position of the car in the race.vector<thread> threads
: Vector of running threads.vector<IA*> IAs
: Vector of participating AIs.vector<Carro*> Carros
: Vector of cars in the race.
-
Methods
void iniciar()
: Starts the race and associated threads.void desenharPista(const vector<Carro*> carros)
: Updates the visualization of the track and the leaderboard.
RaceCondition is a game that effectively utilizes the concepts of threads and semaphores in C++. Through the simulation of a Formula 1 race, the project demonstrates how to manage concurrency and synchronization between multiple threads, consistently avoiding RaceCondition.
- Enzo Tonon Morente - 14568476
- João Pedro Alves Notari Godoy - 14582076
- Letícia Barbosa Neves - 14588659
- Luiz Felipe Diniz Costa - 13782032
Good race! 🏎️🏁
This project was developed as part of the Operating Systems course. For more info about the course, visit: Operating Systems - USP
A video presentation of this project is available on YouTube: Watch the Presentation