/* OneLoneCoder.com - Code-It-Yourself! Racing Game at the command prompt (quick and simple c++) "Let's go, go, go!!!" - @Javidx9 Disclaimer ~~~~~~~~~~ I don't care what you use this for. It's intended to be educational, and perhaps to the oddly minded - a little bit of fun. Please hack this, change it and use it in any way you see fit. BUT, you acknowledge that I am not responsible for anything bad that happens as a result of your actions. However, if good stuff happens, I would appreciate a shout out, or at least give the blog some publicity for me. Cheers! Background ~~~~~~~~~~ I'm a sim-racer when I'm not coding. Racing games are far more sophisticated than they used to be. Frankly, retro racing games are a bit naff. But when done in the command prompt they have a new level of craziness. Controls ~~~~~~~~ Left Arrow/Right Arrow Steer, Up Arrow accelerates. There are no brakes! Set the fastest lap times you can! Author ~~~~~~ Twitter: @javidx9 Blog: www.onelonecoder.com Video: ~~~~~~ https://youtu.be/KkMZI5Jbf18 Last Updated: 10/07/2017 */ #include #include using namespace std; #include "olcConsoleGameEngine.h" class OneLoneCoder_FormulaOLC : public olcConsoleGameEngine { public: OneLoneCoder_FormulaOLC() { m_sAppName = L"Formula OLC"; } private: float fDistance = 0.0f; // Distance car has travelled around track float fCurvature = 0.0f; // Current track curvature, lerped between track sections float fTrackCurvature = 0.0f; // Accumulation of track curvature float fTrackDistance = 0.0f; // Total distance of track float fCarPos = 0.0f; // Current car position float fPlayerCurvature = 0.0f; // Accumulation of player curvature float fSpeed = 0.0f; // Current player speed vector> vecTrack; // Track sections, sharpness of bend, length of section list listLapTimes; // List of previous lap times float fCurrentLapTime; // Current lap time protected: // Called by olcConsoleGameEngine virtual bool OnUserCreate() { // Define track vecTrack.push_back(make_pair(0.0f, 10.0f)); // Short section for start/finish line vecTrack.push_back(make_pair(0.0f, 200.0f)); vecTrack.push_back(make_pair(1.0f, 200.0f)); vecTrack.push_back(make_pair(0.0f, 400.0f)); vecTrack.push_back(make_pair(-1.0f, 100.0f)); vecTrack.push_back(make_pair(0.0f, 200.0f)); vecTrack.push_back(make_pair(-1.0f, 200.0f)); vecTrack.push_back(make_pair(1.0f, 200.0f)); vecTrack.push_back(make_pair(0.0f, 200.0f)); vecTrack.push_back(make_pair(0.2f, 500.0f)); vecTrack.push_back(make_pair(0.0f, 200.0f)); // Calculate total track distance, so we can set lap times for (auto t : vecTrack) fTrackDistance += t.second; listLapTimes = { 0,0,0,0,0 }; fCurrentLapTime = 0.0f; return true; } // Called by olcConsoleGameEngine virtual bool OnUserUpdate(float fElapsedTime) { // Handle control input int nCarDirection = 0; if (m_keys[VK_UP].bHeld) fSpeed += 2.0f * fElapsedTime; else fSpeed -= 1.0f * fElapsedTime; // Car Curvature is accumulated left/right input, but inversely proportional to speed // i.e. it is harder to turn at high speed if (m_keys[VK_LEFT].bHeld) { fPlayerCurvature -= 0.7f * fElapsedTime * (1.0f - fSpeed / 2.0f); nCarDirection = -1; } if (m_keys[VK_RIGHT].bHeld) { fPlayerCurvature += 0.7f * fElapsedTime * (1.0f - fSpeed / 2.0f); nCarDirection = +1; } // If car curvature is too different to track curvature, slow down // as car has gone off track if (fabs(fPlayerCurvature - fTrackCurvature) >= 0.8f) fSpeed -= 5.0f * fElapsedTime; // Clamp Speed if (fSpeed < 0.0f) fSpeed = 0.0f; if (fSpeed > 1.0f) fSpeed = 1.0f; // Move car along track according to car speed fDistance += (70.0f * fSpeed) * fElapsedTime; // Get Point on track float fOffset = 0; int nTrackSection = 0; // Lap Timing and counting fCurrentLapTime += fElapsedTime; if (fDistance >= fTrackDistance) { fDistance -= fTrackDistance; listLapTimes.push_front(fCurrentLapTime); listLapTimes.pop_back(); fCurrentLapTime = 0.0f; } // Find position on track (could optimise) while (nTrackSection < vecTrack.size() && fOffset <= fDistance) { fOffset += vecTrack[nTrackSection].second; nTrackSection++; } // Interpolate towards target track curvature float fTargetCurvature = vecTrack[nTrackSection - 1].first; float fTrackCurveDiff = (fTargetCurvature - fCurvature) * fElapsedTime * fSpeed; // Accumulate player curvature fCurvature += fTrackCurveDiff; // Accumulate track curvature fTrackCurvature += (fCurvature) * fElapsedTime * fSpeed; // Draw Sky - light blue and dark blue for (int y = 0; y < ScreenHeight() / 2; y++) for (int x = 0; x < ScreenWidth(); x++) Draw(x, y, y< ScreenHeight() / 4 ? PIXEL_HALF : PIXEL_SOLID, FG_DARK_BLUE); // Draw Scenery - our hills are a rectified sine wave, where the phase is adjusted by the // accumulated track curvature for (int x = 0; x < ScreenWidth(); x++) { int nHillHeight = (int)(fabs(sinf(x * 0.01f + fTrackCurvature) * 16.0f)); for (int y = (ScreenHeight() / 2) - nHillHeight; y < ScreenHeight() / 2; y++) Draw(x, y, PIXEL_SOLID, FG_DARK_YELLOW); } // Draw Track - Each row is split into grass, clip-board and track for (int y = 0; y < ScreenHeight() / 2; y++) for (int x = 0; x < ScreenWidth(); x++) { // Perspective is used to modify the width of the track row segments float fPerspective = (float)y / (ScreenHeight()/2.0f); float fRoadWidth = 0.1f + fPerspective * 0.8f; // Min 10% Max 90% float fClipWidth = fRoadWidth * 0.15f; fRoadWidth *= 0.5f; // Halve it as track is symmetrical around center of track, but offset... // ...depending on where the middle point is, which is defined by the current // track curvature. float fMiddlePoint = 0.5f + fCurvature * powf((1.0f - fPerspective), 3); // Work out segment boundaries int nLeftGrass = (fMiddlePoint - fRoadWidth - fClipWidth) * ScreenWidth(); int nLeftClip = (fMiddlePoint - fRoadWidth) * ScreenWidth(); int nRightClip = (fMiddlePoint + fRoadWidth) * ScreenWidth(); int nRightGrass = (fMiddlePoint + fRoadWidth + fClipWidth) * ScreenWidth(); int nRow = ScreenHeight() / 2 + y; // Using periodic oscillatory functions to give lines, where the phase is controlled // by the distance around the track. These take some fine tuning to give the right "feel" int nGrassColour = sinf(20.0f * powf(1.0f - fPerspective,3) + fDistance * 0.1f) > 0.0f ? FG_GREEN : FG_DARK_GREEN; int nClipColour = sinf(80.0f * powf(1.0f - fPerspective, 2) + fDistance) > 0.0f ? FG_RED : FG_WHITE; // Start finish straight changes the road colour to inform the player lap is reset int nRoadColour = (nTrackSection-1) == 0 ? FG_WHITE : FG_GREY; // Draw the row segments if (x >= 0 && x < nLeftGrass) Draw(x, nRow, PIXEL_SOLID, nGrassColour); if (x >= nLeftGrass && x < nLeftClip) Draw(x, nRow, PIXEL_SOLID, nClipColour); if (x >= nLeftClip && x < nRightClip) Draw(x, nRow, PIXEL_SOLID, nRoadColour); if (x >= nRightClip && x < nRightGrass) Draw(x, nRow, PIXEL_SOLID, nClipColour); if (x >= nRightGrass && x < ScreenWidth()) Draw(x, nRow, PIXEL_SOLID, nGrassColour); } // Draw Car - car position on road is proportional to difference between // current accumulated track curvature, and current accumulated player curvature // i.e. if they are similar, the car will be in the middle of the track fCarPos = fPlayerCurvature - fTrackCurvature; int nCarPos = ScreenWidth() / 2 + ((int)(ScreenWidth() * fCarPos) / 2.0) - 7; // Offset for sprite // Draw a car that represents what the player is doing. Apologies for the quality // of the sprite... :-( switch (nCarDirection) { case 0: DrawStringAlpha(nCarPos, 80, L" ||####|| "); DrawStringAlpha(nCarPos, 81, L" ## "); DrawStringAlpha(nCarPos, 82, L" #### "); DrawStringAlpha(nCarPos, 83, L" #### "); DrawStringAlpha(nCarPos, 84, L"||| #### |||"); DrawStringAlpha(nCarPos, 85, L"|||########|||"); DrawStringAlpha(nCarPos, 86, L"||| #### |||"); break; case +1: DrawStringAlpha(nCarPos, 80, L" //####//"); DrawStringAlpha(nCarPos, 81, L" ## "); DrawStringAlpha(nCarPos, 82, L" #### "); DrawStringAlpha(nCarPos, 83, L" #### "); DrawStringAlpha(nCarPos, 84, L"/// ####//// "); DrawStringAlpha(nCarPos, 85, L"//#######///O "); DrawStringAlpha(nCarPos, 86, L"/// #### //// "); break; case -1: DrawStringAlpha(nCarPos, 80, L"\\\\####\\\\ "); DrawStringAlpha(nCarPos, 81, L" ## "); DrawStringAlpha(nCarPos, 82, L" #### "); DrawStringAlpha(nCarPos, 83, L" #### "); DrawStringAlpha(nCarPos, 84, L" \\\\\\\\#### \\\\\\"); DrawStringAlpha(nCarPos, 85, L" O\\\\\\#######\\\\"); DrawStringAlpha(nCarPos, 86, L" \\\\\\\\ #### \\\\\\"); break; } // Draw Stats DrawString(0, 0, L"Distance: " + to_wstring(fDistance)); DrawString(0, 1, L"Target Curvature: " + to_wstring(fCurvature)); DrawString(0, 2, L"Player Curvature: " + to_wstring(fPlayerCurvature)); DrawString(0, 3, L"Player Speed : " + to_wstring(fSpeed)); DrawString(0, 4, L"Track Curvature : " + to_wstring(fTrackCurvature)); auto disp_time = [](float t) // Little lambda to turn floating point seconds into minutes:seconds:millis string { int nMinutes = t / 60.0f; int nSeconds = t - (nMinutes * 60.0f); int nMilliSeconds = (t - (float)nSeconds) * 1000.0f; return to_wstring(nMinutes) + L"." + to_wstring(nSeconds) + L":" + to_wstring(nMilliSeconds); }; // Display current laptime DrawString(10, 8, disp_time(fCurrentLapTime)); // Display last 5 lap times int j = 10; for (auto l : listLapTimes) { DrawString(10, j, disp_time(l)); j++; } return true; } }; int main() { // Use olcConsoleGameEngine derived app OneLoneCoder_FormulaOLC game; game.ConstructConsole(160, 100, 8, 8); game.Start(); return 0; }