Your First Game: Pong in C++ Step by Step with AI

By Carlos Montiel | Enterprise AI Specialist
Leer en español →
Published: 2026-07-28 | By: Carlos Montiel | Reading time: ~4 minutes

Pong is the 'hello world' of video games. If you understand how to build it from scratch, you understand the structure shared by practically every real-time game.

Project Structure

In the previous lesson we got the environment ready with SDL2 and CMake. Now we'll use that exact foundation to build Pong: two paddles, a bouncing ball, and a simple scoreboard.

pong/ ├── CMakeLists.txt ├── src/ │ └── main.cpp └── build/

The whole game will live in a single `main.cpp` for simplicity — in later lessons we'll split the code into multiple files once the project justifies it. For now, the priority is that you understand the full flow without jumping between files.

The Main Game Loop

Every video game, regardless of complexity, has the same skeleton: process input, update the world's state, and draw. This is called the "game loop," and it's probably the single most important concept in this whole course.

#include <SDL2/SDL.h> #include <iostream> const int WIDTH = 800; const int HEIGHT = 600; int main(int argc, char* argv[]) { SDL_Init(SDL_INIT_VIDEO); SDL_Window* window = SDL_CreateWindow("Pong", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN); SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); bool running = true; Uint32 last_time = SDL_GetTicks(); while (running) { // 1. Process input SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) running = false; } // 2. Compute delta time (time between frames) Uint32 current_time = SDL_GetTicks(); float delta = (current_time - last_time) / 1000.0f; last_time = current_time; // 3. Update (game logic goes here) // 4. Draw SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); SDL_Delay(16); // roughly 60 frames per second } SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }

The `delta` (time elapsed between one frame and the next) is key: instead of moving objects a fixed number of pixels per frame, we move them an amount proportional to the actual time elapsed. This makes the game behave the same on a fast computer as on a slow one.

Defining the Paddles and the Ball

We'll represent each game element with an `SDL_Rect` (for the paddles, which are rectangles) and position/velocity variables for the ball.

struct Paddle { SDL_Rect rect; float velocity_y = 0.0f; const float SPEED = 400.0f; // pixels per second }; struct Ball { SDL_Rect rect; float vel_x = 300.0f; float vel_y = 200.0f; }; Paddle player1 { { 30, HEIGHT / 2 - 50, 15, 100 } }; Paddle player2 { { WIDTH - 45, HEIGHT / 2 - 50, 15, 100 } }; Ball ball { { WIDTH / 2 - 8, HEIGHT / 2 - 8, 16, 16 } };

Reading Player Input

For smoother control than reacting to discrete events, we use `SDL_GetKeyboardState`, which gives us the state of every key on each frame.

const Uint8* keyboard = SDL_GetKeyboardState(nullptr); // Player 1: W / S player1.velocity_y = 0.0f; if (keyboard[SDL_SCANCODE_W]) player1.velocity_y = -player1.SPEED; if (keyboard[SDL_SCANCODE_S]) player1.velocity_y = player1.SPEED; // Player 2: up / down arrows player2.velocity_y = 0.0f; if (keyboard[SDL_SCANCODE_UP]) player2.velocity_y = -player2.SPEED; if (keyboard[SDL_SCANCODE_DOWN]) player2.velocity_y = player2.SPEED; player1.rect.y += static_cast<int>(player1.velocity_y * delta); player2.rect.y += static_cast<int>(player2.velocity_y * delta); // Keep the paddles from leaving the screen if (player1.rect.y < 0) player1.rect.y = 0; if (player1.rect.y + player1.rect.h > HEIGHT) player1.rect.y = HEIGHT - player1.rect.h; if (player2.rect.y < 0) player2.rect.y = 0; if (player2.rect.y + player2.rect.h > HEIGHT) player2.rect.y = HEIGHT - player2.rect.h;

Moving the Ball and Bouncing Off the Edges

ball.rect.x += static_cast<int>(ball.vel_x * delta); ball.rect.y += static_cast<int>(ball.vel_y * delta); // Bounce off top/bottom if (ball.rect.y <= 0 || ball.rect.y + ball.rect.h >= HEIGHT) { ball.vel_y = -ball.vel_y; } // Point for a player (ball leaves through the left or right edge) if (ball.rect.x < 0) { std::cout << "Point for player 2" << std::endl; ball.rect.x = WIDTH / 2 - 8; ball.rect.y = HEIGHT / 2 - 8; ball.vel_x = 300.0f; } if (ball.rect.x > WIDTH) { std::cout << "Point for player 1" << std::endl; ball.rect.x = WIDTH / 2 - 8; ball.rect.y = HEIGHT / 2 - 8; ball.vel_x = -300.0f; }

Notice we haven't yet handled collisions between the ball and the paddles — we'll formalize that with collision math in lesson 5. For now, to have a playable Pong today, we can do a simple check with `SDL_HasIntersection`:

if (SDL_HasIntersection(&ball.rect, &player1.rect) || SDL_HasIntersection(&ball.rect, &player2.rect)) { ball.vel_x = -ball.vel_x; }

Drawing Everything on Screen

SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderFillRect(renderer, &player1.rect); SDL_RenderFillRect(renderer, &player2.rect); SDL_RenderFillRect(renderer, &ball.rect); SDL_RenderPresent(renderer);

Compiling and Testing

mkdir build && cd build cmake .. cmake --build . ./game

You should see two white paddles and a ball moving, with W/S and the arrow keys controlling each side. If something doesn't compile, this is a great moment to practice your workflow with your AI assistant: paste the full compiler error (not just the last line) and ask it to explain the cause before applying the fix — that way you learn what the error meant, not just how it disappeared.

In the next lesson we'll replace these white rectangles with real sprites, loading images with SDL2_image and animating them frame by frame.

Carlos Montiel
Enterprise AI Solutions Architect
Specialist in LLMs, Agents, and Orchestration
guatemalia.com/en/#contact · info@guatemalia.com

Need to implement AI at your company?

Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.

Contact Carlos Montiel

info@guatemalia.com