Last active
June 12, 2024 14:15
-
-
Save ninjadynamics/8e5541533eebb9fcfc391385d06f43cc to your computer and use it in GitHub Desktop.
Drag Racing Simulator (raylib)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
Author
ninjadynamics
commented
Jun 12, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment