Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Pong code review request  (Read 3210 times)

0 Members and 1 Guest are viewing this topic.

Gaius

  • Newbie
  • *
  • Posts: 11
    • View Profile
Pong code review request
« on: July 25, 2014, 07:21:33 pm »
So, I wrote a simple Pong game in C++ with SFML and Visual Studio 2013 and was wondering if somebody could tell me if there is anything I could improve upon in the code.
 
Noteworthy:
    - The collision between the ball and the top and bottom of the player panel seems to be slightly bugged
    - The full solution for visual studio can be found here: https://www.dropbox.com/s/558as04io6jr2b5/Pong.zip
    - To quit the game, press escape
 
Game:
 

 
 
Files:
 

 
Code: (main.cpp)

#include <iostream>
#include <string>
#include <vector>
#include <SFML\Graphics.hpp>
#include <SFML\Audio.hpp>
#include <Windows.h>
#pragma warning(disable: 4244)

#define SCRW    1000    // Screen width
#define SCRH    600             // Screen height
#define BALLW   20              // Ball width
#define BALLH   20              // Ball height 
#define PADDLEW 20              // Paddle width
#define PADDLEH 100             // Paddle height
#define BSPEED  -0.1f   // Ball origin speed
#define ESPEED  0.08f   // Enemy origin speed (player is mouse controlled)

sf::RenderWindow window(sf::VideoMode(SCRW, SCRH), "Pong");
sf::RectangleShape ball(sf::Vector2f(BALLW, BALLH));
sf::RectangleShape player(sf::Vector2f(PADDLEW, PADDLEH));
sf::RectangleShape enemy(sf::Vector2f(PADDLEW, PADDLEH));
float ballSpeedX = BSPEED;
float ballSpeedY = BSPEED;
float enemySpeed = ESPEED;
bool gameOver = false;
int playerPoints = 0;
int enemyPoints = 0;
sf::Font font;
sf::Text plScoreTxt;
sf::Text enScoreTxt;

void draw();
void moveBall();
void enemyAI();
bool intersect(sf::Vector2f, sf::Vector2f, sf::Vector2f);
float intersectPos(float, sf::RectangleShape);
void reset();

int main(){
        // Font and text stuff
        std::string fontname = "segoeuil.ttf";
        if (!font.loadFromFile(fontname))
        {
                std::cerr << "Font " << "\"" << fontname << "\" couldn't be/wasn't loaded. Please try again.\n";
        }
        plScoreTxt.setFont(font);
        plScoreTxt.setCharacterSize(200);
        plScoreTxt.setPosition(sf::Vector2f(window.getSize().x / 2 - 250, window.getSize().y / 2 - 150));
        enScoreTxt.setFont(font);
        enScoreTxt.setColor(sf::Color(255, 255, 255, 32));
        enScoreTxt.setCharacterSize(200);
        enScoreTxt.setPosition(sf::Vector2f(window.getSize().x / 2 + 150, window.getSize().y / 2 - 150));

        // Setting up the window ant entities
        window.setMouseCursorVisible(false);
        ball.setPosition(sf::Vector2f(window.getSize().x / 2 - BALLW / 2, window.getSize().y / 2 - BALLH / 2));
        enemy.setPosition(sf::Vector2f(window.getSize().x - PADDLEW, window.getSize().y / 2 - PADDLEH / 2));

        // Mouse confinement stuff
        RECT rect;
        rect.bottom = window.getPosition().y + window.getSize().y + 32 - PADDLEH / 2;
        rect.top = window.getPosition().y + 31 + PADDLEH / 2;
        rect.left = window.getPosition().x + 50;
        rect.right = window.getPosition().x + window.getSize().x - 42;

        ClipCursor(&rect);

        // Game loop
        while (window.isOpen()){
                sf::Event event;
                while (window.pollEvent(event)){
                        if (event.type == sf::Event::Closed){
                                window.close();
                        }
                        if (event.type == sf::Event::KeyPressed){
                                // Press escape to close the window
                                if (event.key.code == sf::Keyboard::Escape)
                                        window.close();
                        }
                }

                moveBall(); // Move the ball and test for collision
                player.setPosition(sf::Vector2f(player.getPosition().x, sf::Mouse::getPosition(window).y - PADDLEH / 2)); // Move the player paddle to mouse position
                if (ballSpeedX > 0) // Perform enemy AI stuff when the ball is heading in it's direction
                        enemyAI();
                if (gameOver){ // if someone scored, reset the game
                        reset();
                }

                // Text stuff
                plScoreTxt.setString(std::to_string(playerPoints));
                enScoreTxt.setString(std::to_string(enemyPoints));
                plScoreTxt.setColor(sf::Color(ball.getFillColor().r, ball.getFillColor().g, ball.getFillColor().b, 32));
                enScoreTxt.setColor(sf::Color(ball.getFillColor().r, ball.getFillColor().g, ball.getFillColor().b, 32));
                player.setFillColor(ball.getFillColor());
                enemy.setFillColor(ball.getFillColor());

                draw(); // draw everything to the screen

                // If somebody wins, the window closes (the window can also be closed by pressing escape)
                if (playerPoints > 9 || enemyPoints > 9)
                        window.close();
        }
        return 0;
}

void reset(){
        ball.setPosition(sf::Vector2f(window.getSize().x / 2 - BALLW / 2, window.getSize().y / 2 - BALLH / 2));
        enemy.setPosition(sf::Vector2f(window.getSize().x - PADDLEW, window.getSize().y / 2 - PADDLEH / 2));
        if (ballSpeedX < 0)
                ballSpeedX = BSPEED;
        else
                ballSpeedX = BSPEED * -1;
        ballSpeedY = BSPEED;
        enemySpeed = ESPEED;
        gameOver = false;
}

void draw(){
        window.clear();
        window.draw(plScoreTxt);
        window.draw(enScoreTxt);
        window.draw(player);
        window.draw(enemy);
        window.draw(ball);
        window.display();
}

void increaseSpeed(){
        if (ballSpeedY > 0)
                ballSpeedY += (rand() % 15 + 1) / 1000.0;
        else
                ballSpeedY -= (rand() % 15 + 1) / 1000.0;
        if (ballSpeedX > 0)
                ballSpeedX += (rand() % 20 + 1) / 1000.0;
        else
                ballSpeedX -= (rand() % 20 + 1) / 1000.0;
}

void moveBall(){
        ball.setPosition(sf::Vector2f(ball.getPosition().x + ballSpeedX, ball.getPosition().y + ballSpeedY));

        // Ball -> Wall
        if (ball.getPosition().x < 0){
                enemyPoints++;
                gameOver = true;
                return;
        }
        if (ball.getPosition().x + BALLW > window.getSize().x){
                playerPoints++;
                gameOver = true;
                return;
        }
        if (ball.getPosition().y < 0 || ball.getPosition().y + BALLH > window.getSize().y){
                ballSpeedY *= -1;
                return;
        }

        // Ball -> Both paddles (Side)
        if (intersect(sf::Vector2f(ballSpeedX, ballSpeedY), sf::Vector2f(player.getPosition().x + PADDLEW, player.getPosition().y),
                sf::Vector2f(player.getPosition().x + PADDLEW, player.getPosition().y + PADDLEH)) ||
                intersect(sf::Vector2f(ballSpeedX, ballSpeedY), enemy.getPosition(),
                sf::Vector2f(enemy.getPosition().x, enemy.getPosition().y + PADDLEH)))
        {
                ballSpeedX *= -1;
                ball.setFillColor(sf::Color(rand() % 156 + 100, rand() % 156 + 100, rand() % 156 + 100));
                increaseSpeed();
                return;
        }

        // Player - Top
        if (intersect(sf::Vector2f(ballSpeedX, ballSpeedY), player.getPosition(),
                sf::Vector2f(player.getPosition().x + PADDLEW, player.getPosition().y)))
        {
                ballSpeedY += 0.02f;
                if (ballSpeedX < 0)
                        ballSpeedX *= -1;
                if (ballSpeedY > 0)
                        ballSpeedY *= -1;
                return;
        }

        // Player - Bottom
        if (intersect(sf::Vector2f(ballSpeedX, ballSpeedY),
                sf::Vector2f(player.getPosition().x, player.getPosition().y + PADDLEH),
                sf::Vector2f(player.getPosition().x + PADDLEW, player.getPosition().y + PADDLEH)))
        {
                if (ballSpeedX < 0)
                        ballSpeedX *= -1;
                if (ballSpeedY < 0)
                        ballSpeedY *= -1;
                ballSpeedY += 0.02f;
        }
}

void enemyAI(){
        float Ypos = intersectPos(ballSpeedY, ball);
        // Move enemy to optimal location
        if (enemy.getPosition().y + PADDLEH / 2 > Ypos){
                enemy.setPosition(sf::Vector2f(enemy.getPosition().x, enemy.getPosition().y - enemySpeed));
        }
        else if (enemy.getPosition().y + PADDLEH / 2 < Ypos){
                enemy.setPosition(sf::Vector2f(enemy.getPosition().x, enemy.getPosition().y + enemySpeed));
        }
        // Check if enemy collided with the walls
        if (enemy.getPosition().y < 0)
                enemy.setPosition(enemy.getPosition().x, 0);
        else if (enemy.getPosition().y + PADDLEH > window.getSize().y)
                enemy.setPosition(enemy.getPosition().x, window.getSize().y - PADDLEH);
}

bool lineIntersect(sf::Vector2f LinePointA, sf::Vector2f LinePointB, sf::Vector2f pointMoving, float diam){
        if (LinePointA.x == LinePointB.x && pointMoving.x >= LinePointA.x - diam / 2 && pointMoving.x <= LinePointA.x + diam / 2 &&
                pointMoving.y >= LinePointA.y && pointMoving.y <= LinePointB.y)
                return true;
        if (LinePointA.y == LinePointB.y && pointMoving.y >= LinePointA.y - diam / 2 && pointMoving.y <= LinePointA.y + diam / 2 &&
                pointMoving.x >= LinePointA.x && pointMoving.x <= LinePointB.x)
                return true;
        return false;
}

bool intersect(sf::Vector2f vel, sf::Vector2f LinePointA, sf::Vector2f LinePointB){
        double m, b;
        m = ((ball.getPosition().y + ball.getSize().y / 2.0 + vel.y) - (ball.getPosition().y + ball.getSize().y / 2.0)) /
                ((ball.getPosition().x + ball.getSize().x / 2.0 + vel.x) - (ball.getPosition().x + ball.getSize().x / 2.0));
        b = (ball.getPosition().y + ball.getSize().y / 2.0 + vel.y) - m * (ball.getPosition().x + ball.getSize().x / 2.0 + vel.x);

        // If the line is on the y axis, solve for y in order to have highest accuracy rate
        if (vel.x > 0 && LinePointA.x == LinePointB.x){
                for (double x = 0; x <= vel.x; x += 0.1){
                        if (lineIntersect(LinePointA, LinePointB, sf::Vector2f(x + ball.getPosition().x + ball.getSize().x / 2, (int)(m * (x + ball.getPosition().x + ball.getSize().x / 2.0) + b)), ball.getSize().x))
                                return true;
                }
        }
        else if (vel.x < 0 && LinePointA.x == LinePointB.x){
                for (double x = 0; x >= vel.x; x -= 0.1){
                        if (lineIntersect(LinePointA, LinePointB, sf::Vector2f(x + ball.getPosition().x + ball.getSize().x / 2, (int)(m * (x + ball.getPosition().x + ball.getSize().x / 2.0) + b)), ball.getSize().x))
                                return true;
                }
        }

        // if the line is on the x axis, solve for x in order to have highest accuracy rate
        else if (vel.y > 0 && LinePointA.y == LinePointB.y){
                for (double y = 0; y <= vel.y; y += 0.1){
                        if (lineIntersect(LinePointA, LinePointB, sf::Vector2f((int)((y + ball.getPosition().y + ball.getSize().y / 2.0 - b) / m), y + ball.getPosition().y + ball.getSize().y / 2.0), ball.getSize().y))
                                return true;
                }
        }
        else if (vel.y < 0 && LinePointA.y == LinePointB.y){
                for (double y = 0; y >= vel.y; y -= 0.1){
                        if (lineIntersect(LinePointA, LinePointB, sf::Vector2f((int)((y + ball.getPosition().y + ball.getSize().y / 2.0 - b) / m), y + ball.getPosition().y + ball.getSize().y / 2.0), ball.getSize().y))
                                return true;
                }
        }
        return false;
}

// This function is for the enemy to determine where it has to go
// It basically returns a 'y' coordinate of where the ball is going to end up for the enemy's 'x' coordinate
// If the ball hits either the ceiling or the floor before it reaches it's goal, the function is called recursively until the ball stays within bounds
float intersectPos(float tmpBallSpeedY, sf::RectangleShape tmpBall){
        float x = window.getSize().x - PADDLEW;
        float m = (((tmpBall.getPosition().y + BALLH / 2) + tmpBallSpeedY) - (tmpBall.getPosition().y + BALLH / 2)) /
                (((tmpBall.getPosition().x + BALLW / 2) + ballSpeedX) - (tmpBall.getPosition().x + BALLW / 2));
        float b = (tmpBall.getPosition().y) - m * (tmpBall.getPosition().x);
        float y = m * x + b;

        if (y <= 0){
                tmpBall.setPosition(sf::Vector2f((0 - b) / m, 0));
                y = intersectPos(tmpBallSpeedY*-1, tmpBall);
        }
        else if (y >= window.getSize().y - BALLH){
                tmpBall.setPosition(sf::Vector2f(((window.getSize().y - BALLH) - b) / m, window.getSize().y - BALLH));
                y = intersectPos(tmpBallSpeedY*-1, tmpBall);
        }

        return y;
}

Strelok

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • GitHub
Re: Pong code review request
« Reply #1 on: July 25, 2014, 07:39:10 pm »
I see where you're coming from. You learned C then moved to C++. Without delving problems in your code, two things stand out:
  • You don't use classes
  • You use global variables
This is NOT the way to code in C++, classes are a really powerful tool for programmers and they are the cornerstone of C++. As long as you don't grasp this concept there's no use in writing C++ code. As for global variables, they hold the same problem in C, so you can google it.

Gaius

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Pong code review request
« Reply #2 on: July 25, 2014, 08:06:01 pm »
  • You don't use classes
  • You use global variables

I thought, since this is a relatively small scale game, I could use global variables and one .cpp file without much consequence. Wouldn't it just over complicate the code to separate it into different files if there are only near 300 lines?

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re: Pong code review request
« Reply #3 on: July 25, 2014, 08:08:07 pm »
Yes, but it scales better. You can take that idea and design with you to more complex programs. There's the saying: perfect practice makes perfect.

I'd strongly advise you to take the code you have here, and improve it with a design using classes.

Other than that, here's my comments:
  • You're not coding in C, you're coding in C++: I would use const variables instead of #define's
  • I noticed you disabled a warning, it's best to treat warnings as errors.
  • Good use of multiple functions to simplify your main loop
  • Speaking of loops, I'd read this article and see if you can implement a fixed-timestep game loop. It improves your game immensely.
  • Careful mixing real-time input (sf::Mouse::getPosition()) and events. Personally, I would check for the MouseMove event and move the paddle with those values.

That's all I have to say on top of Strelok's post. Good job!
« Last Edit: July 25, 2014, 08:19:10 pm by dabbertorres »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Pong code review request
« Reply #4 on: July 25, 2014, 10:51:55 pm »
I thought, since this is a relatively small scale game, I could use global variables and one .cpp file without much consequence.
You asked for code review, and bad habits aren't much better in small scale ;)

Pong is an ideal project to learn how to do things the right way. Since the logic is very simple to implement, you have an opportunity to focus more on code design -- maybe you can even overengineer a bit, just to learn different approaches. It will certainly be a benefit for future projects, where design truly matters.

Other points:
  • Backslashes in paths are non-portable
  • Global SFML variables are not only bad style, they can lead to serious issues
  • Outputting an error message but continuing the program is a bad idea
  • Do you really need <Windows.h> and the ClipCursor? If so, you could put it in a separate file and compile conditionally, to keep portability
  • I wouldn't use identifiers like "plScoreTxt". If the few additional letters are problematic to type, you need a better IDE
  • sf::Vector2f(player.getPosition().x + PADDLEW, player.getPosition().y) is player.getPosition() + sf::Vector2f(PADDLEW, 0)
  • In general, you have a lot of expressions like ball.getPosition().x. By using vectors instead of separate components and storing them into variables, things get much more readable.
  • Pass parameters such as sf::RectangleShape tmpBall by const-reference
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Gaius

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Pong code review request
« Reply #5 on: July 26, 2014, 12:35:48 am »
I'd strongly advise you to take the code you have here, and improve it with a design using classes.
Will do.

Speaking of loops, I'd read this article and see if you can implement a fixed-timestep game loop. It improves your game immensely.
I tried to implement that, but I can't seem to find a default high resolution timer for c++.
Edit: It looks like sf::Time could be what I'm looking for.

Careful mixing real-time input (sf::Mouse::getPosition()) and events. Personally, I would check for the MouseMove event and move the paddle with those values.
Was I mixing? I'll check out the MouseMove event.

  • Do you really need <Windows.h> and the ClipCursor? If so, you could put it in a separate file and compile conditionally, to keep portability
How would I compile a file conditionally?
« Last Edit: July 26, 2014, 12:50:55 am by Gaius »

Strelok

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • GitHub
Re: Pong code review request
« Reply #6 on: July 26, 2014, 01:18:40 am »
I tried to implement that, but I can't seem to find a default high resolution timer for c++.
Edit: It looks like sf::Time could be what I'm looking for.

How would I compile a file conditionally?
What you need is a sf::Clock
and
#ifdef _WIN32 //for visual C++ compiler
//code
#endif
 
or
#if defined(somemacro) || defined(somemacro)
//code
#endif
 
« Last Edit: July 26, 2014, 01:20:21 am by Strelok »