A simple C++ clone of Pong.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Main.cpp 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. #include <chrono>
  2. #include <SDL2/SDL.h>
  3. #include <SDL2/SDL_mixer.h>
  4. #include <SDL2/SDL_ttf.h>
  5. #include <string>
  6. const int WINDOW_WIDTH = 1280;
  7. const int WINDOW_HEIGHT = 720;
  8. const float PADDLE_SPEED = 1.0f;
  9. const int PADDLE_WIDTH = 10;
  10. const int PADDLE_HEIGHT = 100;
  11. const float BALL_SPEED = 1.0f;
  12. const int BALL_WIDTH = 15;
  13. const int BALL_HEIGHT = 15;
  14. enum Buttons
  15. {
  16. PaddleOneUp = 0,
  17. PaddleOneDown,
  18. PaddleTwoUp,
  19. PaddleTwoDown,
  20. };
  21. enum class CollisionType
  22. {
  23. None,
  24. Top,
  25. Middle,
  26. Bottom,
  27. Left,
  28. Right
  29. };
  30. struct Contact
  31. {
  32. CollisionType type;
  33. float penetration;
  34. };
  35. class Vec2
  36. {
  37. public:
  38. Vec2()
  39. : x(0.0f), y(0.0f)
  40. {}
  41. Vec2(float x, float y)
  42. : x(x), y(y)
  43. {}
  44. Vec2 operator+(Vec2 const& rhs)
  45. {
  46. return Vec2(x + rhs.x, y + rhs.y);
  47. }
  48. Vec2& operator+=(Vec2 const& rhs)
  49. {
  50. x += rhs.x;
  51. y += rhs.y;
  52. return *this;
  53. }
  54. Vec2 operator*(float rhs)
  55. {
  56. return Vec2(x * rhs, y * rhs);
  57. }
  58. float x, y;
  59. };
  60. class Paddle
  61. {
  62. public:
  63. Paddle(Vec2 position, Vec2 velocity)
  64. : position(position), velocity(velocity)
  65. {
  66. rect.x = static_cast<int>(position.x);
  67. rect.y = static_cast<int>(position.y);
  68. rect.w = PADDLE_WIDTH;
  69. rect.h = PADDLE_HEIGHT;
  70. }
  71. void Update(float dt)
  72. {
  73. position += velocity * dt;
  74. if (position.y < 0)
  75. {
  76. // Restrict to top of the screen
  77. position.y = 0;
  78. }
  79. else if (position.y > (WINDOW_HEIGHT - PADDLE_HEIGHT))
  80. {
  81. // Restrict to bottom of the screen
  82. position.y = WINDOW_HEIGHT - PADDLE_HEIGHT;
  83. }
  84. }
  85. void Draw(SDL_Renderer* renderer)
  86. {
  87. rect.y = static_cast<int>(position.y);
  88. SDL_RenderFillRect(renderer, &rect);
  89. }
  90. Vec2 position;
  91. Vec2 velocity;
  92. SDL_Rect rect{};
  93. };
  94. class Ball
  95. {
  96. public:
  97. Ball(Vec2 position, Vec2 velocity)
  98. : position(position), velocity(velocity)
  99. {
  100. rect.x = static_cast<int>(position.x);
  101. rect.y = static_cast<int>(position.y);
  102. rect.w = BALL_WIDTH;
  103. rect.h = BALL_HEIGHT;
  104. }
  105. void Update(float dt)
  106. {
  107. position += velocity * dt;
  108. }
  109. void Draw(SDL_Renderer* renderer)
  110. {
  111. rect.x = static_cast<int>(position.x);
  112. rect.y = static_cast<int>(position.y);
  113. SDL_RenderFillRect(renderer, &rect);
  114. }
  115. void CollideWithPaddle(Contact const& contact)
  116. {
  117. position.x += contact.penetration;
  118. velocity.x = -velocity.x;
  119. if (contact.type == CollisionType::Top)
  120. {
  121. velocity.y = -.75f * BALL_SPEED;
  122. }
  123. else if (contact.type == CollisionType::Bottom)
  124. {
  125. velocity.y = 0.75f * BALL_SPEED;
  126. }
  127. }
  128. void CollideWithWall(Contact const& contact)
  129. {
  130. if ((contact.type == CollisionType::Top)
  131. || (contact.type == CollisionType::Bottom))
  132. {
  133. position.y += contact.penetration;
  134. velocity.y = -velocity.y;
  135. }
  136. else if (contact.type == CollisionType::Left)
  137. {
  138. position.x = WINDOW_WIDTH / 2.0f;
  139. position.y = WINDOW_HEIGHT / 2.0f;
  140. velocity.x = BALL_SPEED;
  141. velocity.y = 0.75f * BALL_SPEED;
  142. }
  143. else if (contact.type == CollisionType::Right)
  144. {
  145. position.x = WINDOW_WIDTH / 2.0f;
  146. position.y = WINDOW_HEIGHT / 2.0f;
  147. velocity.x = -BALL_SPEED;
  148. velocity.y = 0.75f * BALL_SPEED;
  149. }
  150. }
  151. Vec2 position;
  152. Vec2 velocity;
  153. SDL_Rect rect{};
  154. };
  155. class PlayerScore
  156. {
  157. public:
  158. PlayerScore(Vec2 position, SDL_Renderer* renderer, TTF_Font* font)
  159. : renderer(renderer), font(font)
  160. {
  161. surface = TTF_RenderText_Solid(font, "0", {0xFF, 0xFF, 0xFF, 0xFF});
  162. texture = SDL_CreateTextureFromSurface(renderer, surface);
  163. int width, height;
  164. SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
  165. rect.x = static_cast<int>(position.x);
  166. rect.y = static_cast<int>(position.y);
  167. rect.w = width;
  168. rect.h = height;
  169. }
  170. ~PlayerScore()
  171. {
  172. SDL_FreeSurface(surface);
  173. SDL_DestroyTexture(texture);
  174. }
  175. void SetScore(int score)
  176. {
  177. SDL_FreeSurface(surface);
  178. SDL_DestroyTexture(texture);
  179. surface = TTF_RenderText_Solid(font, std::to_string(score).c_str(), {0xFF, 0xFF, 0xFF, 0xFF});
  180. texture = SDL_CreateTextureFromSurface(renderer, surface);
  181. int width, height;
  182. SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
  183. rect.w = width;
  184. rect.h = height;
  185. }
  186. void Draw()
  187. {
  188. SDL_RenderCopy(renderer, texture, nullptr, &rect);
  189. }
  190. SDL_Renderer* renderer;
  191. TTF_Font* font;
  192. SDL_Surface* surface{};
  193. SDL_Texture* texture{};
  194. SDL_Rect rect{};
  195. };
  196. Contact CheckPaddleCollision(Ball const& ball, Paddle const& paddle)
  197. {
  198. float ballLeft = ball.position.x;
  199. float ballRight = ball.position.x + BALL_WIDTH;
  200. float ballTop = ball.position.y;
  201. float ballBottom = ball.position.y + BALL_HEIGHT;
  202. float paddleLeft = paddle.position.x;
  203. float paddleRight = paddle.position.x + PADDLE_WIDTH;
  204. float paddleTop = paddle.position.y;
  205. float paddleBottom = paddle.position.y + PADDLE_HEIGHT;
  206. Contact contact{};
  207. if (ballLeft >= paddleRight)
  208. {
  209. return contact;
  210. }
  211. if (ballRight <= paddleLeft)
  212. {
  213. return contact;
  214. }
  215. if (ballTop >= paddleBottom)
  216. {
  217. return contact;
  218. }
  219. if (ballBottom <= paddleTop)
  220. {
  221. return contact;
  222. }
  223. float paddleRangeUpper = paddleBottom - (2.0f * PADDLE_HEIGHT / 3.0f);
  224. float paddleRangeMiddle = paddleBottom - (PADDLE_HEIGHT / 3.0f);
  225. if (ball.velocity.x < 0)
  226. {
  227. // Left paddle
  228. contact.penetration = paddleRight - ballLeft;
  229. }
  230. else if (ball.velocity.x > 0)
  231. {
  232. // Right paddle
  233. contact.penetration = paddleLeft - ballRight;
  234. }
  235. if ((ballBottom > paddleTop)
  236. && (ballBottom < paddleRangeUpper))
  237. {
  238. contact.type = CollisionType::Top;
  239. }
  240. else if ((ballBottom > paddleRangeUpper)
  241. && (ballBottom < paddleRangeMiddle))
  242. {
  243. contact.type = CollisionType::Middle;
  244. }
  245. else
  246. {
  247. contact.type = CollisionType::Bottom;
  248. }
  249. return contact;
  250. }
  251. Contact CheckWallCollision(Ball const& ball)
  252. {
  253. float ballLeft = ball.position.x;
  254. float ballRight = ball.position.x + BALL_WIDTH;
  255. float ballTop = ball.position.y;
  256. float ballBottom = ball.position.y + BALL_HEIGHT;
  257. Contact contact{};
  258. if (ballLeft < 0.0f)
  259. {
  260. contact.type = CollisionType::Left;
  261. }
  262. else if (ballRight > WINDOW_WIDTH)
  263. {
  264. contact.type = CollisionType::Right;
  265. }
  266. else if (ballTop < 0.0f)
  267. {
  268. contact.type = CollisionType::Top;
  269. contact.penetration = -ballTop;
  270. }
  271. else if (ballBottom > WINDOW_HEIGHT)
  272. {
  273. contact.type = CollisionType::Bottom;
  274. contact.penetration = WINDOW_HEIGHT - ballBottom;
  275. }
  276. return contact;
  277. }
  278. int main()
  279. {
  280. // Initialize SDL components
  281. SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  282. TTF_Init();
  283. Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
  284. SDL_Window* window = SDL_CreateWindow("Pong", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
  285. SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
  286. // Initialize the font
  287. TTF_Font* scoreFont = TTF_OpenFont("DejaVuSansMono.ttf", 40);
  288. // Initialize sound effects
  289. Mix_Chunk* wallHitSound = Mix_LoadWAV("WallHit.wav");
  290. Mix_Chunk* paddleHitSound = Mix_LoadWAV("PaddleHit.wav");
  291. // Game logic
  292. {
  293. // Create the player score text fields
  294. PlayerScore playerOneScoreText(Vec2(WINDOW_WIDTH / 4, 20), renderer, scoreFont);
  295. PlayerScore playerTwoScoreText(Vec2(3 * WINDOW_WIDTH / 4, 20), renderer, scoreFont);
  296. // Create the ball
  297. Ball ball(
  298. Vec2(WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f),
  299. Vec2(BALL_SPEED, 0.0f));
  300. // Create the paddles
  301. Paddle paddleOne(
  302. Vec2(50.0f, WINDOW_HEIGHT / 2.0f),
  303. Vec2(0.0f, 0.0f));
  304. Paddle paddleTwo(
  305. Vec2(WINDOW_WIDTH - 50.0f, WINDOW_HEIGHT / 2.0f),
  306. Vec2(0.0f, 0.0f));
  307. int playerOneScore = 0;
  308. int playerTwoScore = 0;
  309. bool running = true;
  310. bool buttons[4] = {};
  311. float dt = 0.0f;
  312. while (running)
  313. {
  314. auto startTime = std::chrono::high_resolution_clock::now();
  315. SDL_Event event;
  316. while (SDL_PollEvent(&event))
  317. {
  318. if (event.type == SDL_QUIT)
  319. {
  320. running = false;
  321. }
  322. else if (event.type == SDL_KEYDOWN)
  323. {
  324. if (event.key.keysym.sym == SDLK_ESCAPE)
  325. {
  326. running = false;
  327. }
  328. else if (event.key.keysym.sym == SDLK_w)
  329. {
  330. buttons[Buttons::PaddleOneUp] = true;
  331. }
  332. else if (event.key.keysym.sym == SDLK_s)
  333. {
  334. buttons[Buttons::PaddleOneDown] = true;
  335. }
  336. else if (event.key.keysym.sym == SDLK_UP)
  337. {
  338. buttons[Buttons::PaddleTwoUp] = true;
  339. }
  340. else if (event.key.keysym.sym == SDLK_DOWN)
  341. {
  342. buttons[Buttons::PaddleTwoDown] = true;
  343. }
  344. }
  345. else if (event.type == SDL_KEYUP)
  346. {
  347. if (event.key.keysym.sym == SDLK_w)
  348. {
  349. buttons[Buttons::PaddleOneUp] = false;
  350. }
  351. else if (event.key.keysym.sym == SDLK_s)
  352. {
  353. buttons[Buttons::PaddleOneDown] = false;
  354. }
  355. else if (event.key.keysym.sym == SDLK_UP)
  356. {
  357. buttons[Buttons::PaddleTwoUp] = false;
  358. }
  359. else if (event.key.keysym.sym == SDLK_DOWN)
  360. {
  361. buttons[Buttons::PaddleTwoDown] = false;
  362. }
  363. }
  364. }
  365. if (buttons[Buttons::PaddleOneUp])
  366. {
  367. paddleOne.velocity.y = -PADDLE_SPEED;
  368. }
  369. else if (buttons[Buttons::PaddleOneDown])
  370. {
  371. paddleOne.velocity.y = PADDLE_SPEED;
  372. }
  373. else
  374. {
  375. paddleOne.velocity.y = 0.0f;
  376. }
  377. if (buttons[Buttons::PaddleTwoUp])
  378. {
  379. paddleTwo.velocity.y = -PADDLE_SPEED;
  380. }
  381. else if (buttons[Buttons::PaddleTwoDown])
  382. {
  383. paddleTwo.velocity.y = PADDLE_SPEED;
  384. }
  385. else
  386. {
  387. paddleTwo.velocity.y = 0.0f;
  388. }
  389. // Update the paddle positions
  390. paddleOne.Update(dt);
  391. paddleTwo.Update(dt);
  392. // Update the ball position
  393. ball.Update(dt);
  394. // Check collisions
  395. if (Contact contact = CheckPaddleCollision(ball, paddleOne);
  396. contact.type != CollisionType::None)
  397. {
  398. ball.CollideWithPaddle(contact);
  399. Mix_PlayChannel(-1, paddleHitSound, 0);
  400. }
  401. else if (contact = CheckPaddleCollision(ball, paddleTwo);
  402. contact.type != CollisionType::None)
  403. {
  404. ball.CollideWithPaddle(contact);
  405. Mix_PlayChannel(-1, paddleHitSound, 0);
  406. }
  407. else if (contact = CheckWallCollision(ball);
  408. contact.type != CollisionType::None)
  409. {
  410. ball.CollideWithWall(contact);
  411. if (contact.type == CollisionType::Left)
  412. {
  413. ++playerTwoScore;
  414. playerTwoScoreText.SetScore(playerTwoScore);
  415. }
  416. else if (contact.type == CollisionType::Right)
  417. {
  418. ++playerOneScore;
  419. playerOneScoreText.SetScore(playerOneScore);
  420. }
  421. else
  422. {
  423. Mix_PlayChannel(-1, wallHitSound, 0);
  424. }
  425. }
  426. // Clear the window to black
  427. SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xFF);
  428. SDL_RenderClear(renderer);
  429. // Set the draw color to be white
  430. SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
  431. // Draw the net
  432. for (int y = 0; y < WINDOW_HEIGHT; ++y)
  433. {
  434. if (y % 5)
  435. {
  436. SDL_RenderDrawPoint(renderer, WINDOW_WIDTH / 2, y);
  437. }
  438. }
  439. // Draw the ball
  440. ball.Draw(renderer);
  441. // Draw the paddles
  442. paddleOne.Draw(renderer);
  443. paddleTwo.Draw(renderer);
  444. // Display the scores
  445. playerOneScoreText.Draw();
  446. playerTwoScoreText.Draw();
  447. // Present the backbuffer
  448. SDL_RenderPresent(renderer);
  449. // Calculate frame time
  450. auto stopTime = std::chrono::high_resolution_clock::now();
  451. dt = std::chrono::duration<float, std::chrono::milliseconds::period>(stopTime - startTime).count();
  452. }
  453. }
  454. Mix_FreeChunk(wallHitSound);
  455. Mix_FreeChunk(paddleHitSound);
  456. SDL_DestroyRenderer(renderer);
  457. SDL_DestroyWindow(window);
  458. TTF_CloseFont(scoreFont);
  459. Mix_Quit();
  460. TTF_Quit();
  461. SDL_Quit();
  462. return 0;
  463. }