· 5 months ago · Apr 19, 2025, 01:40 PM
1==++ Here's the full source code for (file 1/1) "RaceCar.cpp"::: ++==
2```RaceCar.cpp
3#include <Windows.h>
4#include <ctime>
5#include <cstdlib>
6#include <math.h>
7#include <stdio.h>
8#include <string>
9#include "resource.h" // Add this with your other includes
10
11// --- Constants --- (Consider adding const double M_PI if not defined in cmath)
12#ifndef M_PI
13#define M_PI 3.14159265358979323846
14#endif
15
16// --- OBB / SAT Collision Detection Helpers ---
17struct Vec2 { float x, y; };
18
19void GetRectangleCorners(float cx, float cy, float halfW, float halfH, float angleRad, Vec2 out[4])
20{
21 // Local space corners
22 Vec2 local[4] = {
23 { -halfW, -halfH }, { halfW, -halfH },
24 { halfW, halfH }, { -halfW, halfH }
25 };
26 float c = cosf(angleRad), s = sinf(angleRad);
27 for (int i = 0; i < 4; ++i)
28 {
29 out[i].x = cx + local[i].x * c - local[i].y * s;
30 out[i].y = cy + local[i].x * s + local[i].y * c;
31 }
32}
33
34float Dot(const Vec2& a, const Vec2& b) { return a.x * b.x + a.y * b.y; }
35
36bool OverlapOnAxis(const Vec2 a[4], const Vec2 b[4], const Vec2& axis)
37{
38 float minA = Dot(a[0], axis), maxA = minA;
39 float minB = Dot(b[0], axis), maxB = minB;
40 for (int i = 1; i < 4; ++i)
41 {
42 float pA = Dot(a[i], axis);
43 minA = fminf(minA, pA); maxA = fmaxf(maxA, pA);
44 float pB = Dot(b[i], axis);
45 minB = fminf(minB, pB); maxB = fmaxf(maxB, pB);
46 }
47 return !(maxA < minB || maxB < minA);
48}
49
50bool CheckOBBCollision(int x1, int y1, int w1, int h1, float angleDeg1,
51 int x2, int y2, int w2, int h2, float angleDeg2)
52{
53 Vec2 c1[4], c2[4];
54 // centers
55 float cx1 = x1 + w1 * 0.5f, cy1 = y1 + h1 * 0.5f;
56 float cx2 = x2 + w2 * 0.5f, cy2 = y2 + h2 * 0.5f;
57 float r1 = angleDeg1 * (float)M_PI / 180.0f;
58 float r2 = angleDeg2 * (float)M_PI / 180.0f;
59 GetRectangleCorners(cx1, cy1, w1 * 0.5f, h1 * 0.5f, r1, c1);
60 GetRectangleCorners(cx2, cy2, w2 * 0.5f, h2 * 0.5f, r2, c2);
61
62 // Build 4 candidate axes (normals of each rect's edges)
63 Vec2 axes[4] = {
64 { c1[1].x - c1[0].x, c1[1].y - c1[0].y },
65 { c1[3].x - c1[0].x, c1[3].y - c1[0].y },
66 { c2[1].x - c2[0].x, c2[1].y - c2[0].y },
67 { c2[3].x - c2[0].x, c2[3].y - c2[0].y }
68 };
69
70 for (int i = 0; i < 4; ++i)
71 {
72 Vec2 axis = { -axes[i].y, axes[i].x }; // perpendicular
73 float len = sqrtf(axis.x * axis.x + axis.y * axis.y);
74 if (len > 0.0001f) { axis.x /= len; axis.y /= len; }
75 if (!OverlapOnAxis(c1, c2, axis)) return false; // separation found
76 }
77 return true; // no separating axis -> collision
78}
79
80
81// Global Variables
82const int WIDTH = 1366;
83const int HEIGHT = 768;
84const int ROAD_WIDTH = 200;
85const int CAR_WIDTH = 50;
86const int CAR_HEIGHT = 100;
87const int TYRE_SIZE = 10;
88const int FPS = 60;
89const int TIMER = 4;
90const int TURN_RADIUS = 5;
91//const double PI = 3.14159265358979323846;
92//const double M_PI = 3.14159265358979323846;
93
94int playerX = 100;
95int playerY = HEIGHT - CAR_HEIGHT - 50;
96//int playerSpeedX = 0;
97//int playerSpeedY = 0;
98int aiX = playerX + CAR_WIDTH + 20;
99int aiY = playerY;
100float aiAngle = -M_PI / 2; // Add this line
101//int aiSpeedX = 0;
102//int aiSpeedY = 0;
103int speed = 5;
104int aiSpeed = 5;
105int timer = TIMER;
106int playerTyre1X = playerX + 10;
107int playerTyre1Y = playerY + CAR_HEIGHT - TYRE_SIZE;
108int playerTyre2X = playerX + CAR_WIDTH - TYRE_SIZE - 10;
109int playerTyre2Y = playerY + CAR_HEIGHT - TYRE_SIZE;
110int aiTyre1X = aiX + 10;
111int aiTyre1Y = aiY + CAR_HEIGHT - TYRE_SIZE;
112int aiTyre2X = aiX + CAR_WIDTH - TYRE_SIZE - 10;
113int aiTyre2Y = aiY + CAR_HEIGHT - TYRE_SIZE;
114float playerAngle = -M_PI / 2; // Initialize to face North by default
115//float playerAngle = 0.0f;
116
117bool gameStarted = false;
118bool gameOver = false;
119bool playerWon = false;
120bool godMode = true;
121//int timer = 30 * 10; // 30 seconds * 10 (timer resolution)
122
123// --- Forward Declarations (If needed) ---
124LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
125
126int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
127{
128 WNDCLASSEX wc = { 0 };
129 wc.cbSize = sizeof(WNDCLASSEX);
130 wc.style = CS_HREDRAW | CS_VREDRAW;
131 wc.lpfnWndProc = WndProc;
132 wc.hInstance = hInstance;
133 wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
134 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
135 wc.hbrBackground = NULL; // Set to NULL for custom background drawing
136 wc.lpszMenuName = NULL;
137 wc.lpszClassName = L"RacingGame";
138 wc.hIconSm = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
139 RegisterClassEx(&wc);
140
141 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
142 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
143 int windowX = (screenWidth - WIDTH) / 2;
144 int windowY = (screenHeight - HEIGHT) / 2;
145
146 HWND hWnd = CreateWindowEx(0, L"RacingGame", L"Racing Game (ArrowKeys=Move G=GodMode)",
147 WS_OVERLAPPEDWINDOW | WS_VISIBLE, // Added WS_VISIBLE
148 windowX, windowY, WIDTH, HEIGHT,
149 NULL, NULL, hInstance, NULL);
150
151 // Removed ShowWindow, WS_VISIBLE in CreateWindowEx handles it
152
153 MSG msg = { 0 };
154 while (GetMessage(&msg, NULL, 0, 0))
155 {
156 TranslateMessage(&msg);
157 DispatchMessage(&msg);
158 }
159 return (int)msg.wParam; // Return final message code
160}
161
162// Window Procedure
163LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
164{
165 switch (message)
166 {
167 case WM_CREATE:
168 SetTimer(hWnd, 1, 1000 / FPS, NULL); // Timer fires based on FPS
169 // Initial car positions before game starts (if needed)
170 playerX = WIDTH / 4; // Example start
171 playerY = HEIGHT - CAR_HEIGHT - 50;
172 playerAngle = 0; // Pointing up
173 aiX = WIDTH * 3 / 4; // Example start
174 aiY = HEIGHT - CAR_HEIGHT - 50;
175 aiAngle = 0; // Pointing up
176 break;
177 case WM_TIMER:
178 if (timer > 0 && !gameStarted)
179 {
180 timer--;
181 InvalidateRect(hWnd, NULL, FALSE); // Request redraw for countdown
182 }
183 else if (timer <= 0 && !gameStarted)
184 {
185 gameStarted = true;
186 srand((unsigned int)time(0));
187 aiSpeed = rand() % 3 + 4; // Adjust AI speed range if needed (4-6)
188
189 // Set initial positions for the race start
190 playerX = ROAD_WIDTH / 2 - CAR_WIDTH / 2; // Start on left lane
191 playerY = HEIGHT - CAR_HEIGHT - 20; // Near bottom
192 playerAngle = 0; // Facing up
193
194 aiX = ROAD_WIDTH / 2 - CAR_WIDTH / 2; // Start on left lane
195 aiY = HEIGHT - CAR_HEIGHT - 150; // Ahead of player
196 aiAngle = 0; // Facing up
197
198 InvalidateRect(hWnd, NULL, FALSE); // Redraw for game start
199 }
200 else if (gameStarted) // Game logic runs when gameStarted is true
201 {
202 static bool gKeyPressed = false;
203 if (GetAsyncKeyState('G') & 0x8000) // Use 0x8000 for currently pressed state
204 {
205 // Basic toggle needs a flag to prevent rapid switching
206 if (!gKeyPressed) {
207 godMode = !godMode;
208 gKeyPressed = true; // Mark as pressed
209 }
210 }
211 else {
212 gKeyPressed = false; // Reset flag when key is released
213 }
214
215 // --- Save Previous States ---
216 float prevPlayerXf = (float)playerX;
217 float prevPlayerYf = (float)playerY;
218 float prevPlayerAnglef = playerAngle;
219 float prevAiXf = (float)aiX;
220 float prevAiYf = (float)aiY;
221 float prevAiAnglef = aiAngle;
222
223 // --- Player Movement & Rotation ---
224 float prevPlayerX = (float)playerX; // Store previous state
225 float prevPlayerY = (float)playerY;
226 float prevPlayerAngle = playerAngle;
227 float playerRadAngle = playerAngle * (float)M_PI / 180.0f; // Convert degrees to radians if using degrees
228
229 float angularVelocity = 3.0f; // Degrees per frame for turning
230 float currentSpeed = 0;
231
232 if (GetAsyncKeyState(VK_UP) & 0x8000) {
233 currentSpeed = (float)speed;
234 }
235 if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
236 currentSpeed = -(float)speed / 2; // Slower reverse
237 }
238
239 if (currentSpeed != 0) { // Only allow turning when moving
240 if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
241 playerAngle -= angularVelocity;
242 }
243 if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
244 playerAngle += angularVelocity;
245 }
246 }
247
248 // Normalize angle (optional but good practice)
249 if (playerAngle >= 360.0f) playerAngle -= 360.0f;
250 if (playerAngle < 0.0f) playerAngle += 360.0f;
251
252 // Update position based on angle (using radians)
253 playerRadAngle = playerAngle * (float)M_PI / 180.0f;
254 playerX += (int)(sin(playerRadAngle) * currentSpeed);
255 playerY -= (int)(cos(playerRadAngle) * currentSpeed); // Y decreases upwards
256
257 // --- AI Movement Logic ---
258 // Simple AI: Follows road path, adjusts angle at corners
259 // (This is a basic example, can be made more complex)
260
261 // Define path points or regions
262 // --- AI Movement Logic ---
263 int corner1Y = ROAD_WIDTH * 2;
264 int corner2X = WIDTH - ROAD_WIDTH;
265
266 if (aiY > corner1Y && aiX < corner2X) {
267 aiAngle = 0;
268 aiY -= aiSpeed;
269 }
270 else if (aiY <= corner1Y && aiX < corner2X) {
271 aiAngle = 90;
272 aiX += aiSpeed;
273 if (abs(aiY - ROAD_WIDTH) > aiSpeed)
274 aiY += (ROAD_WIDTH - aiY > 0) ? 1 : -1;
275 }
276 else if (aiX >= corner2X && aiY < HEIGHT - CAR_HEIGHT) {
277 aiAngle = 180;
278 aiY += aiSpeed;
279 }
280 else if (aiY >= HEIGHT - CAR_HEIGHT && aiX >= corner2X) {
281 aiX = ROAD_WIDTH / 2 - CAR_WIDTH / 2;
282 aiY = HEIGHT - CAR_HEIGHT - 20;
283 aiAngle = 0;
284 }
285 // Basic wrap around or finish line logic could go here
286
287 // --- Collision & Road-Boundary Checks ---
288 // --- Collision & Road Boundary Checks (God Mode OFF) ---
289 if (!godMode)
290 {
291 // 1) OBB vs OBB collision?
292 if (CheckOBBCollision(
293 playerX, playerY, CAR_WIDTH, CAR_HEIGHT, playerAngle,
294 aiX, aiY, CAR_WIDTH, CAR_HEIGHT, aiAngle))
295 {
296 // revert both cars
297 playerX = (int)prevPlayerXf; playerY = (int)prevPlayerYf;
298 playerAngle = prevPlayerAnglef;
299 aiX = (int)prevAiXf; aiY = (int)prevAiYf;
300 aiAngle = prevAiAnglef;
301 }
302
303 // 2) Off-road check for player
304 RECT leftRoad = { 0, 0, ROAD_WIDTH, HEIGHT };
305 RECT rightRoad = { WIDTH - ROAD_WIDTH, 0, WIDTH, HEIGHT };
306 RECT topRoad = { 0, 0, WIDTH, ROAD_WIDTH * 2 };
307 RECT pRect = { playerX, playerY,
308 playerX + CAR_WIDTH,
309 playerY + CAR_HEIGHT };
310 RECT temp;
311 bool onLeft = IntersectRect(&temp, &pRect, &leftRoad);
312 bool onRight = IntersectRect(&temp, &pRect, &rightRoad);
313 bool onTop = IntersectRect(&temp, &pRect, &topRoad);
314 if (!(onLeft || onRight || onTop))
315 {
316 // revert player only
317 playerX = (int)prevPlayerXf;
318 playerY = (int)prevPlayerYf;
319 playerAngle = prevPlayerAnglef;
320 }
321 }
322
323 // --- Screen Edge Clamping (Always) ---
324 if (playerX < -CAR_WIDTH) playerX = -CAR_WIDTH;
325 if (playerX > WIDTH) playerX = WIDTH;
326 if (playerY < -CAR_HEIGHT) playerY = -CAR_HEIGHT;
327 if (playerY > HEIGHT) playerY = HEIGHT;
328
329
330 /*// --- Collision Detection & Handling (Simplified) ---
331 // Basic AABB check (doesn't account for rotation)
332 RECT playerRect = { playerX, playerY, playerX + CAR_WIDTH, playerY + CAR_HEIGHT };
333 RECT aiRect = { aiX, aiY, aiX + CAR_WIDTH, aiY + CAR_HEIGHT };
334 RECT intersection;
335
336 if (!godMode && IntersectRect(&intersection, &playerRect, &aiRect))
337 {
338 // Collision occurred - crude response: move player back slightly
339 playerX = (int)prevPlayerX;
340 playerY = (int)prevPlayerY;
341 playerAngle = prevPlayerAngle;
342 // Could add bounce effect or game over here
343 }
344
345 // --- Boundary Checks (Simple) ---
346 // Prevent player from going completely off-screen (adjust as needed)
347 if (playerX < -CAR_WIDTH) playerX = -CAR_WIDTH;
348 if (playerX > WIDTH) playerX = WIDTH;
349 if (playerY < -CAR_HEIGHT) playerY = -CAR_HEIGHT;
350 if (playerY > HEIGHT) playerY = HEIGHT;
351 // More sophisticated road boundary checks needed for proper gameplay */
352
353 // Request redraw
354 InvalidateRect(hWnd, NULL, FALSE);
355 }
356 break; // End WM_TIMER
357 case WM_PAINT:
358 {
359 PAINTSTRUCT ps;
360 HDC hdc = BeginPaint(hWnd, &ps);
361
362 // --- Double Buffering Setup ---
363 RECT clientRect;
364 GetClientRect(hWnd, &clientRect);
365 HDC memDC = CreateCompatibleDC(hdc);
366 HBITMAP memBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
367 HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
368
369 // --- Drawing Starts Here (Draw onto memDC) ---
370
371 // 1. Draw Background (Light Green)
372 HBRUSH lightGreenBrush = CreateSolidBrush(RGB(144, 238, 144));
373 FillRect(memDC, &clientRect, lightGreenBrush);
374 DeleteObject(lightGreenBrush);
375
376 // 2. Draw Roads (Black)
377 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
378 RECT verticalRoad = { 0, 0, ROAD_WIDTH, HEIGHT };
379 FillRect(memDC, &verticalRoad, blackBrush);
380 RECT horizontalRoad = { 0, 0, WIDTH, ROAD_WIDTH * 2 };
381 FillRect(memDC, &horizontalRoad, blackBrush);
382 // Add second vertical road on the right if needed for a circuit
383 RECT verticalRoad2 = { WIDTH - ROAD_WIDTH, 0, WIDTH, HEIGHT };
384 FillRect(memDC, &verticalRoad2, blackBrush);
385 DeleteObject(blackBrush);
386
387 // 3. Draw Road Markings (Yellow Dashed Lines)
388 HBRUSH yellowBrush = CreateSolidBrush(RGB(255, 255, 0));
389 HGDIOBJ oldYellowBrush = SelectObject(memDC, yellowBrush);
390 // Vertical dashes (Left Road)
391 for (int y = 0; y < HEIGHT; y += 80) {
392 Rectangle(memDC, ROAD_WIDTH / 2 - 5, y, ROAD_WIDTH / 2 + 5, y + 40);
393 }
394 // Vertical dashes (Right Road - if exists)
395 for (int y = 0; y < HEIGHT; y += 80) {
396 Rectangle(memDC, WIDTH - ROAD_WIDTH / 2 - 5, y, WIDTH - ROAD_WIDTH / 2 + 5, y + 40);
397 }
398 // Horizontal dashes
399 for (int x = 0; x < WIDTH; x += 80) {
400 Rectangle(memDC, x, ROAD_WIDTH - 5, x + 40, ROAD_WIDTH + 5);
401 }
402 SelectObject(memDC, oldYellowBrush);
403 DeleteObject(yellowBrush);
404
405 // --- Draw Player Car (Red) ---
406 // Uses playerAngle in degrees, convert to radians for transformation
407 float playerRadAngleDraw = playerAngle * (float)M_PI / 180.0f;
408 HBRUSH redBrush = CreateSolidBrush(RGB(255, 0, 0));
409 HGDIOBJ oldRedBrush = SelectObject(memDC, redBrush);
410 int savedDCPlayer = SaveDC(memDC);
411 SetGraphicsMode(memDC, GM_ADVANCED);
412 XFORM xformPlayer;
413 xformPlayer.eM11 = (FLOAT)cos(playerRadAngleDraw);
414 xformPlayer.eM12 = (FLOAT)sin(playerRadAngleDraw);
415 xformPlayer.eM21 = (FLOAT)-sin(playerRadAngleDraw); // Negative sin for standard rotation
416 xformPlayer.eM22 = (FLOAT)cos(playerRadAngleDraw);
417 xformPlayer.eDx = (FLOAT)playerX + CAR_WIDTH / 2;
418 xformPlayer.eDy = (FLOAT)playerY + CAR_HEIGHT / 2;
419 SetWorldTransform(memDC, &xformPlayer);
420
421 // Draw Player Body (relative coords)
422 Rectangle(memDC, -CAR_WIDTH / 2, -CAR_HEIGHT / 2, CAR_WIDTH / 2, CAR_HEIGHT / 2);
423
424 // HEADLIGHTS FIRST
425 SelectObject(memDC, CreateSolidBrush(RGB(255, 255, 0)));
426 Rectangle(memDC, -CAR_WIDTH / 2 + 2, -CAR_HEIGHT / 2 + 2, -CAR_WIDTH / 4, -CAR_HEIGHT / 2 + 6);
427 Rectangle(memDC, CAR_WIDTH / 4, -CAR_HEIGHT / 2 + 2, CAR_WIDTH / 2 - 2, -CAR_HEIGHT / 2 + 6);
428
429 // Draw Player Headlights (relative coords)
430 HBRUSH pHeadlightBrush = CreateSolidBrush(RGB(255, 255, 220)); // Light Yellow
431 HGDIOBJ oldPHeadlightBrush = SelectObject(memDC, pHeadlightBrush);
432 int pHeadlightSize = 8;
433 int pHeadlightXOffset = CAR_WIDTH / 4;
434 int pHeadlightYOffset = -CAR_HEIGHT / 2 + 10 - 8;
435 Ellipse(memDC, -pHeadlightXOffset - pHeadlightSize / 2, pHeadlightYOffset, -pHeadlightXOffset + pHeadlightSize / 2, pHeadlightYOffset + pHeadlightSize);
436 Ellipse(memDC, pHeadlightXOffset - pHeadlightSize / 2, pHeadlightYOffset, pHeadlightXOffset + pHeadlightSize / 2, pHeadlightYOffset + pHeadlightSize);
437 SelectObject(memDC, oldPHeadlightBrush);
438 DeleteObject(pHeadlightBrush);
439
440 // Draw Player Windows (relative coords)
441 HBRUSH pWinBrush = CreateSolidBrush(RGB(60, 60, 60)); // Dark Gray
442 HGDIOBJ oldPWinBrush = SelectObject(memDC, pWinBrush);
443 int pWsWidth = CAR_WIDTH * 3 / 4;
444 int pWsHeight = CAR_HEIGHT / 5;
445 int pWsY = -CAR_HEIGHT / 2 + 25 + 10;
446 Rectangle(memDC, -pWsWidth / 2, pWsY, pWsWidth / 2, pWsY + pWsHeight); // Windscreen
447 int pSideWWidth = CAR_WIDTH / 8;
448 int pSideWHeight = CAR_HEIGHT / 4;
449 int pSideWY = pWsY + pWsHeight / 2 - pSideWHeight / 2 + 10;
450 int pSideWXOffset = CAR_WIDTH / 2 - 5 - pSideWWidth / 2;
451 Rectangle(memDC, -pSideWXOffset - pSideWWidth / 2, pSideWY, -pSideWXOffset + pSideWWidth / 2, pSideWY + pSideWHeight); // Left Side
452 Rectangle(memDC, pSideWXOffset - pSideWWidth / 2, pSideWY, pSideWXOffset + pSideWWidth / 2, pSideWY + pSideWHeight); // Right Side
453 SelectObject(memDC, oldPWinBrush);
454 DeleteObject(pWinBrush);
455
456 // Draw Player Tyres (relative coords)
457 HBRUSH pTyreBrush = CreateSolidBrush(RGB(0, 0, 0));
458 HGDIOBJ oldPTyreBrush = SelectObject(memDC, pTyreBrush);
459 int pTyreWidth = 10;
460 int pTyreHeight = 15;
461
462 // Keeping original vertical positions:
463 int pFrontTyreY = -CAR_HEIGHT / 2 + 15; // Original front Y position
464 int pRearTyreY = CAR_HEIGHT / 2 - pTyreHeight - 5; // Original rear Y position
465
466 // Adjust horizontal positions to stick to the edges of the car
467 int pLeftTyreX = -CAR_WIDTH / 2; // Left edge of the car
468 int pRightTyreX = CAR_WIDTH / 2; // Right edge of the car
469
470 // Draw tyres at the left and right positions
471 // Front Left Tyre
472 Rectangle(memDC, pLeftTyreX - pTyreWidth / 2, pFrontTyreY, pLeftTyreX + pTyreWidth / 2, pFrontTyreY + pTyreHeight); // FL
473 // Front Right Tyre
474 Rectangle(memDC, pRightTyreX - pTyreWidth / 2, pFrontTyreY, pRightTyreX + pTyreWidth / 2, pFrontTyreY + pTyreHeight); // FR
475 // Rear Left Tyre
476 Rectangle(memDC, pLeftTyreX - pTyreWidth / 2, pRearTyreY, pLeftTyreX + pTyreWidth / 2, pRearTyreY + pTyreHeight); // RL
477 // Rear Right Tyre
478 Rectangle(memDC, pRightTyreX - pTyreWidth / 2, pRearTyreY, pRightTyreX + pTyreWidth / 2, pRearTyreY + pTyreHeight); // RR
479
480 SelectObject(memDC, oldPTyreBrush);
481 DeleteObject(pTyreBrush);
482
483 // Restore player DC
484 RestoreDC(memDC, savedDCPlayer);
485 SelectObject(memDC, oldRedBrush);
486 DeleteObject(redBrush);
487
488 // --- Draw AI Car (Blue) ---
489 // Uses aiAngle in degrees, convert to radians for transformation
490 float aiRadAngleDraw = aiAngle * (float)M_PI / 180.0f;
491 HBRUSH blueBrush = CreateSolidBrush(RGB(0, 0, 255));
492 HGDIOBJ oldBlueBrush = SelectObject(memDC, blueBrush);
493 int savedDCAi = SaveDC(memDC);
494 SetGraphicsMode(memDC, GM_ADVANCED);
495 XFORM xformAi; // Use a different name
496 xformAi.eM11 = (FLOAT)cos(aiRadAngleDraw);
497 xformAi.eM12 = (FLOAT)sin(aiRadAngleDraw);
498 xformAi.eM21 = (FLOAT)-sin(aiRadAngleDraw); // Negative sin for standard rotation
499 xformAi.eM22 = (FLOAT)cos(aiRadAngleDraw);
500 xformAi.eDx = (FLOAT)aiX + CAR_WIDTH / 2;
501 xformAi.eDy = (FLOAT)aiY + CAR_HEIGHT / 2;
502 SetWorldTransform(memDC, &xformAi); // Apply the transformation
503
504 // Draw AI Body (relative coords)
505 Rectangle(memDC, -CAR_WIDTH / 2, -CAR_HEIGHT / 2, CAR_WIDTH / 2, CAR_HEIGHT / 2);
506
507 // HEADLIGHTS FIRST
508 SelectObject(memDC, CreateSolidBrush(RGB(255, 255, 0)));
509 Rectangle(memDC, -CAR_WIDTH / 2 + 2, -CAR_HEIGHT / 2 + 2, -CAR_WIDTH / 4, -CAR_HEIGHT / 2 + 6);
510 Rectangle(memDC, CAR_WIDTH / 4, -CAR_HEIGHT / 2 + 2, CAR_WIDTH / 2 - 2, -CAR_HEIGHT / 2 + 6);
511
512 // Draw AI Headlights (relative coords)
513 HBRUSH aiHeadlightBrush = CreateSolidBrush(RGB(255, 255, 220)); // Light Yellow
514 HGDIOBJ oldAiHeadlightBrush = SelectObject(memDC, aiHeadlightBrush);
515 int aiHeadlightSize = 8;
516 int aiHeadlightXOffset = CAR_WIDTH / 4;
517 int aiHeadlightYOffset = -CAR_HEIGHT / 2 + 10 - 8; // Near "top" edge in local coords
518 Ellipse(memDC, -aiHeadlightXOffset - aiHeadlightSize / 2, aiHeadlightYOffset, -aiHeadlightXOffset + aiHeadlightSize / 2, aiHeadlightYOffset + aiHeadlightSize);
519 Ellipse(memDC, aiHeadlightXOffset - aiHeadlightSize / 2, aiHeadlightYOffset, aiHeadlightXOffset + aiHeadlightSize / 2, aiHeadlightYOffset + aiHeadlightSize);
520 SelectObject(memDC, oldAiHeadlightBrush);
521 DeleteObject(aiHeadlightBrush);
522
523 // Draw AI Windows (relative coords)
524 HBRUSH aiWinBrush = CreateSolidBrush(RGB(60, 60, 60)); // Dark Gray
525 HGDIOBJ oldAiWinBrush = SelectObject(memDC, aiWinBrush);
526 int aiWsWidth = CAR_WIDTH * 3 / 4;
527 int aiWsHeight = CAR_HEIGHT / 5;
528 int aiWsY = -CAR_HEIGHT / 2 + 25 + 10; // Near "top" edge
529 Rectangle(memDC, -aiWsWidth / 2, aiWsY, aiWsWidth / 2, aiWsY + aiWsHeight); // Windscreen
530 int aiSideWWidth = CAR_WIDTH / 8;
531 int aiSideWHeight = CAR_HEIGHT / 4;
532 int aiSideWY = aiWsY + aiWsHeight / 2 - aiSideWHeight / 2 + 10; // Centered vertically
533 int aiSideWXOffset = CAR_WIDTH / 2 - 5 - aiSideWWidth / 2; // Offset from center
534 Rectangle(memDC, -aiSideWXOffset - aiSideWWidth / 2, aiSideWY, -aiSideWXOffset + aiSideWWidth / 2, aiSideWY + aiSideWHeight); // Left
535 Rectangle(memDC, aiSideWXOffset - aiSideWWidth / 2, aiSideWY, aiSideWXOffset + aiSideWWidth / 2, aiSideWY + aiSideWHeight); // Right
536 SelectObject(memDC, oldAiWinBrush);
537 DeleteObject(aiWinBrush);
538
539 // Draw AI Tyres (relative coords)
540 HBRUSH aiTyreBrush = CreateSolidBrush(RGB(0, 0, 0));
541 HGDIOBJ oldAiTyreBrush = SelectObject(memDC, aiTyreBrush);
542 int aiTyreWidth = 10;
543 int aiTyreHeight = 15;
544
545 // Keeping original vertical positions:
546 int aiFrontTyreY = -CAR_HEIGHT / 2 + 15; // Original front Y position
547 int aiRearTyreY = CAR_HEIGHT / 2 - aiTyreHeight - 5; // Original rear Y position
548
549 // Adjust horizontal positions to stick to the edges of the car
550 int aiLeftTyreX = -CAR_WIDTH / 2; // Left edge of the car
551 int aiRightTyreX = CAR_WIDTH / 2; // Right edge of the car
552
553 // Draw tyres at the left and right positions
554 // Front Left Tyre
555 Rectangle(memDC, aiLeftTyreX - aiTyreWidth / 2, aiFrontTyreY, aiLeftTyreX + aiTyreWidth / 2, aiFrontTyreY + aiTyreHeight); // FL
556 // Front Right Tyre
557 Rectangle(memDC, aiRightTyreX - aiTyreWidth / 2, aiFrontTyreY, aiRightTyreX + aiTyreWidth / 2, aiFrontTyreY + aiTyreHeight); // FR
558 // Rear Left Tyre
559 Rectangle(memDC, aiLeftTyreX - aiTyreWidth / 2, aiRearTyreY, aiLeftTyreX + aiTyreWidth / 2, aiRearTyreY + aiTyreHeight); // RL
560 // Rear Right Tyre
561 Rectangle(memDC, aiRightTyreX - aiTyreWidth / 2, aiRearTyreY, aiRightTyreX + aiTyreWidth / 2, aiRearTyreY + aiTyreHeight); // RR
562
563 SelectObject(memDC, oldAiTyreBrush);
564 DeleteObject(aiTyreBrush);
565
566 // Restore AI DC state
567 RestoreDC(memDC, savedDCAi);
568 SelectObject(memDC, oldBlueBrush);
569 DeleteObject(blueBrush);
570
571
572 // --- Draw Overlay Text ---
573 SetBkMode(memDC, TRANSPARENT); // Make text background transparent
574
575 // Timer display (only before game starts)
576 if (!gameStarted && timer > 0)
577 {
578 char timerText[10];
579 // Display seconds correctly (integer division), ensure >= 1
580 int secondsLeft = max(1, (timer + FPS - 1) / FPS);
581 sprintf_s(timerText, "%d", secondsLeft);
582 SetTextColor(memDC, RGB(255, 255, 0)); // Yellow countdown
583 HFONT hFont = CreateFont(48, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial");
584 HFONT oldFont = (HFONT)SelectObject(memDC, hFont);
585 SetTextAlign(memDC, TA_CENTER | TA_BASELINE); // Center align text
586 TextOutA(memDC, WIDTH / 2, HEIGHT / 2, timerText, strlen(timerText));
587 SetTextAlign(memDC, TA_LEFT | TA_TOP); // Reset alignment
588 SelectObject(memDC, oldFont); // Restore old font
589 DeleteObject(hFont); // Delete created font
590 }
591 else if (!gameStarted && timer <= 0) {
592 // Optionally display "GO!" briefly
593 }
594
595
596 // God Mode Status Display
597 if (godMode)
598 {
599 SetTextColor(memDC, RGB(0, 255, 0)); // Green text for God Mode
600 HFONT hFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial");
601 HFONT oldFont = (HFONT)SelectObject(memDC, hFont);
602 TextOutA(memDC, 10, 10, "God Mode ON (G)", 15);
603 SelectObject(memDC, oldFont);
604 DeleteObject(hFont);
605 }
606
607 // --- Double Buffering End ---
608 BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, memDC, 0, 0, SRCCOPY);
609
610 // --- Cleanup ---
611 SelectObject(memDC, oldBitmap);
612 DeleteObject(memBitmap);
613 DeleteDC(memDC);
614
615 EndPaint(hWnd, &ps);
616 }
617 break; // End WM_PAINT
618 case WM_DESTROY:
619 KillTimer(hWnd, 1);
620 PostQuitMessage(0);
621 break;
622 case WM_KEYDOWN:
623 if (wParam == VK_F1)
624 {
625 MessageBoxW(hWnd, L"2D Racing Game 3.0 Programmed in C++ Win32 API (491 lines of code) by Entisoft Software (c) Evans Thorpemorton", L"About", MB_OK | MB_ICONINFORMATION); // orig 395 lines
626 }
627 //break;
628 if (wParam == VK_ESCAPE)
629 {
630 PostQuitMessage(0);
631 }
632 break;
633
634 // Add WM_ERASEBKGND to prevent default background flicker
635 case WM_ERASEBKGND:
636 return 1; // Indicate that we handled background erasing (by not doing it)
637
638 default:
639 return DefWindowProc(hWnd, message, wParam, lParam);
640 }
641 return 0;
642}
643```