Skip to content

Instantly share code, notes, and snippets.

@ninjadynamics
Last active June 12, 2024 14:15
Show Gist options
  • Save ninjadynamics/8e5541533eebb9fcfc391385d06f43cc to your computer and use it in GitHub Desktop.
Save ninjadynamics/8e5541533eebb9fcfc391385d06f43cc to your computer and use it in GitHub Desktop.
Drag Racing Simulator (raylib)
/*
MIT License
Copyright (c) Ninja Dynamics
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// NOTE:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Most of the code you see here has been written by ChatGPT models GPT-4 and GPT-4o.
// You can add more car templates by copy-pasting an existing template to ChatGPT and
// ask it to generate a new one. To change cars, just modify the respective pointers
// at the beginning of the function.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#include "raylib.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#define PLAYER_1 AI // HUMAN
#define PLAYER_2 AI // HUMAN
#define TRACK_LENGTH 402.0f
// Car sprites
Texture2D civicSprite;
Texture2D skylineSprite;
// Control types
enum { AI, HUMAN };
// Car indexes
enum { CAR_A, CAR_B, NUM_CARS };
// Define control flags
bool driver[NUM_CARS];
// Track completion
static float carPosition[NUM_CARS] = {0.0f, 0.0f};
// Define a structure to represent the Car
typedef struct Car {
float horsepower;
float torque; // Maximum torque in Nm
float torqueCurve[10]; // Torque curve (simplified)
float displacement; // in liters
int numberOfCylinders;
float rpm; // revolutions per minute
float maxRpm; // maximum revolutions per minute
float fuelConsumption; // liters per hour
int gear; // current gear (0 for Neutral)
float gearRatios[6]; // gear ratios for N, 1st to 5th gear
float finalDriveRatio; // final drive ratio
float vehicleSpeed; // vehicle speed in km/h
float wheelRadius; // wheel radius in meters
float carWeight; // car weight in kilograms
int running; // 1 if the car is running, 0 if the car is off
char model[64]; // car make and model
} Car;
// Read-only parameters for Honda Civic
static const Car HondaCivic = {
.horsepower = 160.0f,
.torque = 152.0f,
.torqueCurve = {120, 135, 145, 150, 152, 150, 145, 140, 135, 130}, // Simplified torque curve
.displacement = 1.6f,
.numberOfCylinders = 4,
.rpm = 0.0f,
.maxRpm = 7600.0f,
.fuelConsumption = 0.0f,
.gear = 0, // Start in Neutral
.gearRatios = {0.0f, 3.230f, 1.900f, 1.360f, 1.034f, 0.787f}, // Including neutral (0.0f)
.finalDriveRatio = 4.400f,
.vehicleSpeed = 0.0f,
.wheelRadius = 0.3f,
.carWeight = 1200.0f,
.running = 0, // Car is initially off
.model = "1998 Honda Civic Si"
};
// Read-only parameters for Volkswagen Golf GTI
static const Car VolkswagenGolfGTI = {
.horsepower = 150.0f,
.torque = 162.0f,
.torqueCurve = {130, 140, 150, 160, 162, 160, 158, 155, 150, 145}, // Simplified torque curve
.displacement = 1.8f,
.numberOfCylinders = 4,
.rpm = 0.0f,
.maxRpm = 6800.0f,
.fuelConsumption = 0.0f,
.gear = 0, // Start in Neutral
.gearRatios = {0.0f, 3.300f, 1.944f, 1.233f, 0.853f, 0.697f}, // Including neutral (0.0f)
.finalDriveRatio = 4.235f,
.vehicleSpeed = 0.0f,
.wheelRadius = 0.3f,
.carWeight = 1250.0f,
.running = 0, // Car is initially off
.model = "1998 Volkswagen Golf GTI"
};
// Read-only parameters for Nissan Skyline R34 GTR
static const Car NissanSkyline = {
.horsepower = 276.0f,
.torque = 392.0f, // Maximum torque in Nm
.torqueCurve = {300, 350, 375, 380, 390, 392, 390, 385, 380, 375}, // Simplified torque curve
.displacement = 2.6f,
.numberOfCylinders = 6,
.rpm = 0.0f,
.maxRpm = 8000.0f,
.fuelConsumption = 0.0f,
.gear = 0, // Start in Neutral
.gearRatios = {0.0f, 3.83f, 2.36f, 1.69f, 1.31f, 1.0f}, // Including neutral (0.0f)
.finalDriveRatio = 4.11f,
.vehicleSpeed = 0.0f,
.wheelRadius = 0.3f,
.carWeight = 1560.0f,
.running = 0, // Car is initially off
.model = "1999 Nissan Skyline R34 GTR"
};
// Read-only parameters for Mitsubishi Eclipse GSX (1999)
static const Car MitsubishiEclipse = {
.horsepower = 210.0f,
.torque = 290.0f, // Maximum torque in Nm
.torqueCurve = {220, 240, 260, 280, 290, 290, 285, 280, 275, 270}, // Simplified torque curve
.displacement = 2.0f,
.numberOfCylinders = 4,
.rpm = 0.0f,
.maxRpm = 7000.0f,
.fuelConsumption = 0.0f,
.gear = 0, // Start in Neutral
.gearRatios = {0.0f, 3.166f, 1.833f, 1.217f, 0.888f, 0.741f}, // Including neutral (0.0f)
.finalDriveRatio = 4.15f,
.vehicleSpeed = 0.0f,
.wheelRadius = 0.3f,
.carWeight = 1450.0f,
.running = 0, // Car is initially off
.model = "1999 Mitsubishi Eclipse GSX"
};
// Global car array
Car cars[NUM_CARS];
// Optimal gear change RPM arrays for both cars
float gearUpRPM[NUM_CARS][6];
// Function to initialize a Car
void initializeCar(Car *car, const Car *carTemplate) {
memcpy(car, carTemplate, sizeof(Car));
}
// Function to calculate the optimal gear change RPM
void calculateGearUpRPM(int carIndex) {
for (int i = 1; i < 5; i++) {
gearUpRPM[carIndex][i] = cars[carIndex].maxRpm * cars[carIndex].gearRatios[i + 1] / cars[carIndex].gearRatios[i];
}
gearUpRPM[carIndex][5] = cars[carIndex].maxRpm;
}
// Virtual key state arrays for both players
bool virtualKeyDown[NUM_CARS][512] = {0};
bool virtualKeyPressed[NUM_CARS][512] = {0};
// Virtual key wrapper functions
bool isVirtualKeyDown(int carIndex, int key) {
return virtualKeyDown[carIndex][key] || (IsKeyDown(key) && driver[carIndex] == HUMAN);
}
bool isVirtualKeyPressed(int carIndex, int key) {
return virtualKeyPressed[carIndex][key] || (IsKeyPressed(key) && driver[carIndex] == HUMAN);
}
// Function to shift up the gear
void shiftUp(int carIndex) {
if (isVirtualKeyDown(carIndex, KEY_SPACE) && cars[carIndex].gear < 5) {
cars[carIndex].gear++;
// Adjust RPM based on gear ratio change if the clutch is not pressed
if (cars[carIndex].running && cars[carIndex].gear > 0 && cars[carIndex].gear < 6) {
if (cars[carIndex].gearRatios[cars[carIndex].gear] != 0) {
cars[carIndex].rpm *= (cars[carIndex].gearRatios[cars[carIndex].gear - 1] / cars[carIndex].gearRatios[cars[carIndex].gear]);
if (cars[carIndex].rpm > cars[carIndex].maxRpm) {
cars[carIndex].rpm = cars[carIndex].maxRpm;
}
}
}
}
}
// Function to shift down the gear
void shiftDown(int carIndex) {
if (isVirtualKeyDown(carIndex, KEY_SPACE) && cars[carIndex].gear > 0) {
cars[carIndex].gear--;
// Adjust RPM based on gear ratio change if the clutch is not pressed
if (cars[carIndex].running && cars[carIndex].gear >= 0 && cars[carIndex].gear < 5) {
if (cars[carIndex].gearRatios[cars[carIndex].gear] != 0) {
cars[carIndex].rpm *= (cars[carIndex].gearRatios[cars[carIndex].gear + 1] / cars[carIndex].gearRatios[cars[carIndex].gear]);
}
}
}
}
// Function to handle starting and stopping the car
void handleStartStop(int carIndex) {
if (isVirtualKeyPressed(carIndex, KEY_ENTER)) {
cars[carIndex].running = !cars[carIndex].running;
if (cars[carIndex].running) {
cars[carIndex].rpm = 1000.0f; // Start the car at idle RPM
} else {
cars[carIndex].rpm = 0.0f; // Turn off the car
}
}
}
// Function to handle gear shifting
void handleGearShifting(int carIndex) {
if (isVirtualKeyPressed(carIndex, KEY_RIGHT)) {
shiftUp(carIndex);
}
if (isVirtualKeyPressed(carIndex, KEY_LEFT)) {
shiftDown(carIndex);
}
}
// Function to calculate torque based on RPM
float calculateTorque(float rpm, float maxRpm, float torqueCurve[10]) {
int index = (rpm / maxRpm) * 9; // Simplified index for torque curve
if (index < 0) index = 0;
if (index > 9) index = 9;
return torqueCurve[index];
}
// Function to handle acceleration
// Function to handle acceleration
void handleAcceleration(int carIndex, float deltaTime, float gearRatio) {
if (cars[carIndex].gear > 0 && !isVirtualKeyDown(carIndex, KEY_SPACE)) { // Only accelerate if the clutch is not pressed
float torque = calculateTorque(cars[carIndex].rpm, cars[carIndex].maxRpm, cars[carIndex].torqueCurve);
float force = (torque * gearRatio * cars[carIndex].finalDriveRatio) / cars[carIndex].wheelRadius; // Torque to force
float acceleration = force / cars[carIndex].carWeight; // F = ma -> a = F / m
cars[carIndex].vehicleSpeed += acceleration * deltaTime * 3.6f; // m/s to km/h
// Calculate RPM based on speed and gear ratio
cars[carIndex].rpm = (cars[carIndex].vehicleSpeed * 1000.0f * gearRatio * cars[carIndex].finalDriveRatio) / (2 * PI * cars[carIndex].wheelRadius * 60.0f);
if (cars[carIndex].rpm > cars[carIndex].maxRpm) {
cars[carIndex].rpm = cars[carIndex].maxRpm;
cars[carIndex].vehicleSpeed = (cars[carIndex].maxRpm * 2 * PI * cars[carIndex].wheelRadius * 60.0f) / (1000.0f * gearRatio * cars[carIndex].finalDriveRatio);
}
} else if (isVirtualKeyDown(carIndex, KEY_SPACE) || cars[carIndex].gear == 0) {
// Increase RPM in neutral or with clutch pressed
cars[carIndex].rpm += 5000.0f * deltaTime; // RPM increase rate in neutral or with clutch pressed
if (cars[carIndex].rpm > cars[carIndex].maxRpm) {
cars[carIndex].rpm = cars[carIndex].maxRpm;
}
// Coasting in neutral or with clutch pressed
if (cars[carIndex].gear > 0) { // If the car is in gear, apply rolling resistance
cars[carIndex].vehicleSpeed -= 2.0f * deltaTime; // Simulate rolling resistance when clutch is pressed
if (cars[carIndex].vehicleSpeed < 0) {
cars[carIndex].vehicleSpeed = 0;
}
}
}
}
// Function to handle braking
void handleBraking(int carIndex, float deltaTime, float gearRatio) {
cars[carIndex].vehicleSpeed -= 100.0f * deltaTime; // Increased deceleration rate for braking
if (cars[carIndex].vehicleSpeed < 0) {
cars[carIndex].vehicleSpeed = 0;
}
// Update RPM based on new speed
if (cars[carIndex].gear > 0 && !isVirtualKeyDown(carIndex, KEY_SPACE)) {
cars[carIndex].rpm = (cars[carIndex].vehicleSpeed * 1000.0f * gearRatio * cars[carIndex].finalDriveRatio) / (2 * PI * cars[carIndex].wheelRadius * 60.0f);
if (cars[carIndex].rpm < 1000) {
cars[carIndex].rpm = 0.0f; // Turn off the car if RPM goes below 1000 in gear
cars[carIndex].running = 0;
}
} else {
// Reduce RPM in neutral or with clutch pressed
cars[carIndex].rpm -= 3000.0f * deltaTime; // RPM decrease rate in neutral or with clutch pressed
if (cars[carIndex].rpm < 1000) {
cars[carIndex].rpm = 1000;
}
}
if (cars[carIndex].rpm > cars[carIndex].maxRpm) {
cars[carIndex].rpm = cars[carIndex].maxRpm;
}
}
// Function to handle coasting
void handleCoasting(int carIndex, float deltaTime, float gearRatio) {
if (cars[carIndex].gear > 0 && !isVirtualKeyDown(carIndex, KEY_SPACE)) {
cars[carIndex].vehicleSpeed -= 2.0f * deltaTime; // Slow deceleration rate for coasting in gear
if (cars[carIndex].vehicleSpeed < 0) {
cars[carIndex].vehicleSpeed = 0;
}
// Update RPM based on new speed
cars[carIndex].rpm = (cars[carIndex].vehicleSpeed * 1000.0f * gearRatio * cars[carIndex].finalDriveRatio) / (2 * PI * cars[carIndex].wheelRadius * 60.0f);
if (cars[carIndex].rpm < 1000) {
cars[carIndex].rpm = 0.0f; // Turn off the car if RPM goes below 1000 in gear
cars[carIndex].running = 0;
}
} else {
// Coasting in neutral or with clutch pressed
cars[carIndex].vehicleSpeed -= 2.0f * deltaTime; // Simulate rolling resistance when clutch is pressed
if (cars[carIndex].vehicleSpeed < 0) {
cars[carIndex].vehicleSpeed = 0;
}
// Reduce RPM in neutral or with clutch pressed
cars[carIndex].rpm -= 3000.0f * deltaTime; // RPM decrease rate in neutral or with clutch pressed
if (cars[carIndex].rpm < 1000) {
cars[carIndex].rpm = 1000;
}
}
if (cars[carIndex].rpm > cars[carIndex].maxRpm) {
cars[carIndex].rpm = cars[carIndex].maxRpm;
}
}
// Function to update speed and RPM
void updateSpeedAndRPM(int carIndex, float deltaTime, float gearRatio) {
if (isVirtualKeyDown(carIndex, KEY_UP) && cars[carIndex].running) {
handleAcceleration(carIndex, deltaTime, gearRatio);
} else if (isVirtualKeyDown(carIndex, KEY_DOWN)) {
handleBraking(carIndex, deltaTime, gearRatio);
} else {
handleCoasting(carIndex, deltaTime, gearRatio);
}
}
// Function to simulate car behavior
void simulateCar(int carIndex, float deltaTime) {
float gearRatio = (cars[carIndex].gear == 0) ? 1.0f : cars[carIndex].gearRatios[cars[carIndex].gear];
handleStartStop(carIndex);
if (!cars[carIndex].running) {
handleGearShifting(carIndex);
cars[carIndex].vehicleSpeed = 0.0f;
cars[carIndex].fuelConsumption = 0.0f;
return;
}
updateSpeedAndRPM(carIndex, deltaTime, gearRatio);
handleGearShifting(carIndex);
// Simulate fuel consumption based on RPM and engine load
cars[carIndex].fuelConsumption = (cars[carIndex].rpm / cars[carIndex].maxRpm) * 15.0f * (1.0f + cars[carIndex].vehicleSpeed / 100.0f); // Simplified fuel consumption
// Debugging print statements
#ifdef DEBUG
printf("Car: %d, RPM: %.2f, Gear: %d, Speed: %.2f km/h, Running: %d, DeltaTime: %f\n", carIndex, cars[carIndex].rpm, cars[carIndex].gear, cars[carIndex].vehicleSpeed, cars[carIndex].running, deltaTime);
#endif
}
// Function to display car details and car sprites using raylib
void displayCarDetails(Car cars[], Font font, float elapsedTime[], bool finished[]) {
char label[64];
char value[64];
#define FONT_SIZE 20
for (int carIndex = 0; carIndex < NUM_CARS; carIndex++) {
// Set base Y position for each car's specifications
int baseY = 10 + carIndex * 280; // Adjusted vertical space for car specs
// Display car make and model
snprintf(label, sizeof(label), "Model:");
snprintf(value, sizeof(value), "%s", cars[carIndex].model);
DrawTextEx(font, label, (Vector2){10, baseY}, FONT_SIZE, 2, BLACK); // Font size reduced to 16
DrawTextEx(font, value, (Vector2){300, baseY}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Horsepower:");
snprintf(value, sizeof(value), "%.2f", cars[carIndex].horsepower);
DrawTextEx(font, label, (Vector2){10, baseY + 20}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 20}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Displacement:");
snprintf(value, sizeof(value), "%.2f liters", cars[carIndex].displacement);
DrawTextEx(font, label, (Vector2){10, baseY + 40}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 40}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Number of Cylinders:");
snprintf(value, sizeof(value), "%d", cars[carIndex].numberOfCylinders);
DrawTextEx(font, label, (Vector2){10, baseY + 60}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 60}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "RPM:");
snprintf(value, sizeof(value), "%.2f", cars[carIndex].rpm);
DrawTextEx(font, label, (Vector2){10, baseY + 80}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 80}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Fuel Consumption:");
snprintf(value, sizeof(value), "%.2f liters/hour", cars[carIndex].fuelConsumption);
DrawTextEx(font, label, (Vector2){10, baseY + 100}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 100}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Gear:");
if (cars[carIndex].gear) snprintf(value, sizeof(value), "%d", cars[carIndex].gear);
else snprintf(value, sizeof(value), "N");
DrawTextEx(font, label, (Vector2){10, baseY + 120}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 120}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Speed:");
snprintf(value, sizeof(value), "%.2f km/h", cars[carIndex].vehicleSpeed);
DrawTextEx(font, label, (Vector2){10, baseY + 140}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 140}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Running:");
snprintf(value, sizeof(value), "%s", cars[carIndex].running ? "Yes" : "No");
DrawTextEx(font, label, (Vector2){10, baseY + 160}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 160}, FONT_SIZE, 2, BLACK);
snprintf(label, sizeof(label), "Clutch:");
snprintf(value, sizeof(value), "%s", isVirtualKeyDown(carIndex, KEY_SPACE) ? "Pressed" : "Released");
DrawTextEx(font, label, (Vector2){10, baseY + 180}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 180}, FONT_SIZE, 2, BLACK);
// Draw track progress
snprintf(label, sizeof(label), "Track Progress:");
snprintf(value, sizeof(value), "%.0f / %.0f m", carPosition[carIndex], TRACK_LENGTH);
DrawTextEx(font, label, (Vector2){10, baseY + 200}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 200}, FONT_SIZE, 2, BLACK);
// Draw elapsed time
snprintf(label, sizeof(label), "Elapsed Time:");
snprintf(value, sizeof(value), "%.2f s", elapsedTime[carIndex]);
DrawTextEx(font, label, (Vector2){10, baseY + 220}, FONT_SIZE, 2, BLACK);
DrawTextEx(font, value, (Vector2){300, baseY + 220}, FONT_SIZE, 2, BLACK);
// Draw car on the track
float screenWidth = GetScreenWidth();
float carPixelPosition = (carPosition[carIndex] / TRACK_LENGTH) * (screenWidth - 266);
Texture2D carSprite = carIndex == CAR_A ? civicSprite : skylineSprite;
DrawTextureEx(carSprite, (Vector2){10 + carPixelPosition, 552 + carIndex * 100}, 0.0f, 1.0f, WHITE);
DrawRectangle(10, 650 + carIndex * 100, screenWidth - 20, 5, BLACK);
}
#undef FONT_SIZE
}
// AI controller function
void aiControl(int carIndex, float deltaTime) {
static bool finished[NUM_CARS] = {false, false};
static bool clutchPressed[NUM_CARS] = {false, false};
static float clutchTime[NUM_CARS] = {0.0f, 0.0f};
static float carPosition[NUM_CARS] = {0.0f, 0.0f};
static float clutchHoldTime[NUM_CARS];
// Add some variance to the clutch hold time
clutchHoldTime[carIndex] = 0.350f + (GetRandomValue(-100, 100) / 1000.0f);
// Ensure carPosition does not exceed track length
if (carPosition[carIndex] > TRACK_LENGTH) carPosition[carIndex] = TRACK_LENGTH;
// Start the car if not started
if (!cars[carIndex].running) {
virtualKeyPressed[carIndex][KEY_ENTER] = true;
return;
}
// Engage first gear after starting the car
if (cars[carIndex].running && cars[carIndex].running && cars[carIndex].gear == 0 && !clutchPressed[carIndex]) {
virtualKeyDown[carIndex][KEY_SPACE] = true;
virtualKeyPressed[carIndex][KEY_RIGHT] = true;
clutchPressed[carIndex] = true;
clutchTime[carIndex] = 0.0f;
return;
}
// Optimal gear shifting logic
if (cars[carIndex].running && !finished[carIndex]) {
// Accelerate
virtualKeyDown[carIndex][KEY_UP] = true;
// Press clutch and shift up at optimal RPM with random variance
float idealShiftRPM = gearUpRPM[carIndex][cars[carIndex].gear] + GetRandomValue(-200, 200);
if (cars[carIndex].rpm > idealShiftRPM && cars[carIndex].gear < 5 && !clutchPressed[carIndex]) {
virtualKeyDown[carIndex][KEY_SPACE] = true;
virtualKeyPressed[carIndex][KEY_RIGHT] = true;
clutchPressed[carIndex] = true;
clutchTime[carIndex] = 0.0f;
}
// Press clutch and shift down if RPM is too low
if (cars[carIndex].rpm < 2000 && cars[carIndex].gear > 1 && !clutchPressed[carIndex]) {
virtualKeyDown[carIndex][KEY_SPACE] = true;
virtualKeyPressed[carIndex][KEY_LEFT] = true;
clutchPressed[carIndex] = true;
clutchTime[carIndex] = 0.0f;
}
// Release clutch after a short delay
if (clutchPressed[carIndex]) {
clutchTime[carIndex] += deltaTime;
if (clutchTime[carIndex] > clutchHoldTime[carIndex]) { // Release clutch after random time
virtualKeyDown[carIndex][KEY_SPACE] = false;
clutchPressed[carIndex] = false;
clutchHoldTime[carIndex] = 0.5f + (GetRandomValue(-1000, 1000) / 1000.0f); // Reset clutch hold time with new random variance
}
}
}
// Update car position
carPosition[carIndex] += (cars[carIndex].vehicleSpeed / 3.6f) * deltaTime; // Convert km/h to m/s
// Finish the track
if (carPosition[carIndex] >= TRACK_LENGTH) {
virtualKeyDown[carIndex][KEY_UP] = false;
virtualKeyDown[carIndex][KEY_DOWN] = true;
virtualKeyPressed[carIndex][KEY_ENTER] = true;
finished[carIndex] = true;
return;
}
}
int main(void) {
// Initialization
const int screenWidth = 1200;
const int screenHeight = 800;
int frameCounter;
InitWindow(screenWidth, screenHeight, "CarSim");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
// Load Consolas font from Windows fonts directory
Font consolas = LoadFontEx("C:\\Windows\\Fonts\\consola.ttf", 20, 0, 0);
// Load car sprites
civicSprite = LoadTexture("civic.png");
skylineSprite = LoadTexture("skyline.png");
// Initialize the cars
initializeCar(&cars[CAR_A], &HondaCivic);
initializeCar(&cars[CAR_B], &NissanSkyline);
calculateGearUpRPM(CAR_A);
calculateGearUpRPM(CAR_B);
cars[CAR_A].running = 1;
cars[CAR_A].rpm = 1000.0f;
cars[CAR_B].running = 1;
cars[CAR_B].rpm = 1000.0f;
// Timer variables
float elapsedTime[NUM_CARS] = {0.0f, 0.0f};
bool finished[NUM_CARS] = {false, false};
// Define control types
driver[CAR_A] = PLAYER_1;
driver[CAR_B] = PLAYER_2;
// Countdown timer variables
int countdown = 3;
float countdownTime = 0.0f;
bool raceStarted = false;
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
float deltaTime = GetFrameTime();
// Countdown logic
if (!countdown >= 0) {
countdownTime += deltaTime;
if (countdownTime >= 1.0f) {
countdown--;
countdownTime = 0.0f;
}
if (countdown == 0) {
raceStarted = true;
if ((frameCounter / 8) % 2) {
DrawTextEx(consolas, "GO!!!", (Vector2){screenWidth - 130, 60}, 20, 2, BLACK);
}
} else if (countdown > 0) {
DrawTextEx(consolas, TextFormat("%d", countdown), (Vector2){screenWidth - 80, 60}, 20, 2, BLACK);
}
}
if (raceStarted) {
// Update virtual key states for the autopilot
memset(virtualKeyPressed, 0, sizeof(virtualKeyPressed));
// AI control
if (driver[CAR_A] == AI) aiControl(CAR_A, deltaTime);
if (driver[CAR_B] == AI) aiControl(CAR_B, deltaTime);
// Simulate car behavior
simulateCar(CAR_A, deltaTime);
simulateCar(CAR_B, deltaTime);
// Update race status
for (int i = 0; i < NUM_CARS; i++) {
// Track car position (convert km/h to m/s)
carPosition[i] += (cars[i].vehicleSpeed / 3.6f) * GetFrameTime();
// Ensure carPosition does not exceed track length
if (carPosition[i] >= TRACK_LENGTH) {
carPosition[i] = TRACK_LENGTH;
finished[i] = true;
}
// Update elapsed time
if (!finished[i]) {
elapsedTime[i] += deltaTime;
}
}
}
// Draw
BeginDrawing();
ClearBackground(RAYWHITE);
displayCarDetails(cars, consolas, elapsedTime, finished);
EndDrawing();
frameCounter++;
}
// Unload font
UnloadFont(consolas);
CloseWindow(); // Close window and OpenGL context
return 0;
}
@ninjadynamics
Copy link
Author

civic
skyline

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment