I’ve had the phrase 下学上達 (kagaku joutatsu) on my desktop background for years. Loosely translated, it means that to gain real understanding, you should start with the familiar—the most fundamental of fundamentals. Human learning begins with imitation: babies mimic their parents, musicians play classical pieces, and painters do master studies. They say imitation is the sincerest form of flattery, but it's also one of the most effective ways to learn something new.
Why Pong?
In the early 1970s, Allan Alcorn was given a training assignment by his boss, Nolan Bushnell: recreate a simple sports game for the then-popular Magnavox Odyssey. Alcorn’s implementation turned out so polished that Bushnell decided to release it commercially. That game—Pong—became a breakout hit, helping launch Atari as a household name and trailblazer in the fledgling video game industry.
In a sense, Pong was a clone from the start. It mimicked Magnavox’s “Table Tennis,” which was itself a digital take on the real-life sport. So cloning Pong doesn’t feel derivative; if anything, it feels like I’m paying homage.
Why Now?
This idea had been kicking around in my head since I first encountered CS50’s Introduction to Game Development. That course uses a case-study approach to rebuild classic games in Lua, giving students hands-on exposure to core game design principles. I liked the concept but didn’t want to pick up Lua on top of everything else I was learning.
Still, the idea stuck with me. I figured that programming these games in a different language would offer twofold benefits: a deeper understanding of both the game mechanics and the syntax of the language. Less passive consumption, more deliberate practice. Two birds, one stone.
That brings us to now. My experience with both C++ and SFML was, to put it generously, minimal. I’d skimmed a few chapters of LearnCPP.com and watched a YouTube video on linking SFML to Visual Studio. That was about it.
But I’m American—unearned confidence is practically the national pastime.
🔗 Project Link
Want to follow along or look at the code?
👉 GitHub Repo: BikuraStudios/Pong-Clone
Built with SFML 3.0 in Visual Studio 2022
~15 hours of work over a week of evenings
Development Breakdown
Window Setup
Getting the game window up and running was the first step. This was mostly copy-paste from tutorials, not an original coding challenge.
sf::RenderWindow window(sf::VideoMode(800, 600), "Pong");
window.setFramerateLimit(60);
Paddles
Next up: paddles. I drew simple white rectangles and placed them on screen.
sf::RectangleShape paddle(sf::Vector2f(10.f, 100.f));
paddle.setPosition(50.f, 250.f);
paddle.setFillColor(sf::Color::White);
(Yes, the magic numbers have already started creeping in.)
Movement
To handle input without tying it to frame rate, I introduced deltaTime
and multiplied it by an arbitrary speed.
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
paddle.move(0.f, -paddleSpeed * deltaTime);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
paddle.move(0.f, paddleSpeed * deltaTime);
Ball Mechanics
The ball logic was similar to the paddles—define it, position it, assign velocity.
sf::CircleShape ball(10.f);
ball.setPosition(400.f, 300.f);
sf::Vector2f ballVelocity(200.f, 200.f);
ball.move(ballVelocity * deltaTime);
In my code, ballVelocity
is technically broken. I tried to randomize the starting direction but never seeded the random number generator. So the ball moves in the same direction every time—hardly "random."
Collision Detection
This is where things got messy.
I probably spent four hours going back and forth between YouTube videos and forums trying to write a collision function that didn't throw errors left and right. Even copilot was stuck, and I got into a fight with a hallucinating AI over whether a function existed or not. Eventually I came to realize that the code for intersects was updated for the SFML 3.0 release. Thus, what used to be typed out like this:
if (playerRect.getGlobalBounds().intersects(targetRect.getGlobalBounds()))
Collision;
Is now formatted more like this:
if (playerRect.getGlobalBounds().findIntersection(targetRect.getGlobalBounds()))
Collision;
I probably should have figured this out faster, but you live and learn.
Anyway, here's the dvd screensaver I made when I figured out what I was actually supposed to be doing:
Eventually, I settled on basic AABB (Axis-Aligned Bounding Box) collision. It’s functional, though not perfect. Sometimes the ball clips into paddles and accelerates wildly before rocketing off.
sf::FloatRect leftPaddleBounds = paddle.getGlobalBounds();
sf::FloatRect rightPaddleBounds = paddle2.getGlobalBounds();
sf::FloatRect ballBounds = ball.getGlobalBounds();
if (const std::optional intersection = ballBounds.findIntersection(leftPaddleBounds)) {
sound_leftPaddle.play();
ballVelocity.x *= -1.1f;
}
if (const std::optional intersection = ballBounds.findIntersection(rightPaddleBounds)) {
sound_rightPaddle.play();
ballVelocity.x *= -1.1f;
}
We’ll revisit this when I’m more comfortable with collision handling.
Scoring System
If the ball goes off-screen, award a point and reset:
if (ball.getPosition().x < 0.f) {
player2Score += 1;
resetBall();
}
if (ball.getPosition().x > window.getSize().x) {
player1Score += 1;
resetBall();
}
Then update the displayed text:
player2ScoreString = std::to_string(player2Score);
scoreTextLeft.setString(player2ScoreString);
player1ScoreString = std::to_string(player1Score);
scoreTextRight.setString(player1ScoreString);
Pause Functionality
A proper pause system needs a state machine, but I used a simple boolean:
bool isPaused = false;
if (!isPaused) {
// game loop
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::P)) {
isPaused = !isPaused;
}
It’s not elegant, but it works.
Game Reset
I allowed resets only after a player hits 10 points. When startNewGame
is true
, everything resets:
if (startNewGame) {
player1Score = 0;
player2Score = 0;
// Update score text
// Reset positions
ball.setPosition({400.f, 300.f});
paddle.setPosition({50.f, 250.f});
paddle2.setPosition({725.f, 250.f});
// Reassign velocity
directionX = randomDirection();
directionY = randomDirection();
ballVelocity = {200.f * directionX, 200.f * directionY};
startNewGame = false;
}
In the future, I'd like a reset feature that works at any time.
Buggier Than an Entomologist’s Sketchbook
A non-exhaustive bug list:
Outdated documentation for window creation and input
Paddles scrolling off-screen
Ball flying offscreen at terminal velocity
Multi-line
if
statements without{}
Function definitions inside
main()
Confusion with
sf::Vector2f
argumentsBounding boxes conceptually clear but poorly implemented
Difficulty loading font files
Failing to update score text
Overcomplicated state toggling
Here's a particularly bad example of logic from early on:
if (keyPressed->scancode == sf::Keyboard::Scancode::Space) {
if (Pause == true)
Pause = false;
if (Pause == false)
Pause = true;
}
Eventually simplified to:
if (keyPressed->scancode == sf::Keyboard::Scancode::Space)
Pause = !Pause;
Code Review Summary
I don't have any programmers in my immediate circle, so I threw my code at an AI and told it to give me a review. Here are the results:
Major Issues:
main()
was 600+ lines with no decompositionGame logic, input, and rendering were all in
main()
Repetitive code (WET)
Inefficient event handling
Minor Issues:
Excessive commented-out code
Overuse of magic numbers
No error handling for assets
Hard-to-debug logic due to lack of toggles
So what did we learn?
All in all, this was a solid first project. I learned a ton and came away with something playable. More importantly, I proved to myself that I could start and finish a game. That confidence boost alone made the whole process worth it.
That’s all for this week, folks; see you next week when we look at Flappy Bird.
Sayonara.
Top comments (0)