I have written a pong game in C++ using SDL2.0 for the graphics. I have a couple of questions regarding this code:
How can I make the AI better, in such a way that it is actually dumber? Right now, my AI can return the ball to me every time, which is unfair. I want the player to be able to score at least some of the time. Some sort of percentage of error variable might do the trick, not sure.
How can I fix the ugliness of some of the functions? Currently, my
load_rendered_text()
function takes 2 things of text and returns 2 textures, when it should only take 1 thing of text for 1 texture, and be called twice. Also, myBall::move_ball()
function has more than 10 if statements, checking various conditions. How would one go about fixing that?Am I using too many comments? Not enough comments? I generally try and aim for self documenting code. Meaning that the code should tell you what its doing without the need for comments, most of the time. To achieve this I've tried my best to use consistent naming, and easy to understand variable names. Let me know if you spot any naming errors or things that could be better in this area.
class Ball
{
public:
const int BALL_WIDTH = 15;
const int BALL_HEIGHT = 15;
const float BALL_VELOCITY = 2;
Ball();
void init_ball_direction();
void move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle);
void render_ball();
private:
float ballPositionX, ballPositionY;
float ballVelocityX, ballVelocityY;
bool isMovingDown, isMovingUp;
bool isMovingLeft, isMovingRight;
SDL_Rect bCollider;
};
...
class AI
{
public:
const float AI_VELOCITY = 3;
void init_ai();
void move_ai();
void handle_ai_events();
SDL_Rect aiPaddle;
private:
float aiYPosition;
float aiVelocity;
};
...
void Ball::move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle)
{
//move the ball, its collider, and the raycast. Check for collisions with the paddles
//by calling the check_collision() function.
if(ballPositionY + BALL_HEIGHT > SCREEN_HEIGHT){
isMovingDown = false;
isMovingUp = true;
}
if(ballPositionY < 0){
isMovingDown = true;
isMovingUp = false;
}
if(ballPositionX + BALL_WIDTH > SCREEN_WIDTH){
ballPositionX = SCREEN_WIDTH/2;
}
if(ballPositionX < 0){
ballPositionX = SCREEN_WIDTH/2;
}
if(check_collision(bCollider, playerPaddle)){
isMovingLeft = false;
isMovingRight = true;
}
if(check_collision(bCollider, aiPaddle)){
isMovingRight = false;
isMovingLeft = true;
}
if(isMovingUp){
ballPositionY -= ballVelocityY;
bCollider.y = ballPositionY;
bRaycastLine.y = ballPositionY;
}
if(isMovingDown){
ballPositionY += ballVelocityY;
bCollider.y = ballPositionY;
bRaycastLine.y = ballPositionY;
}
if(isMovingLeft){
ballPositionX -= ballVelocityX;
bCollider.x = ballPositionX;
bRaycastLine.x = ballPositionX;
}
if(isMovingRight){
ballPositionX += ballVelocityX;
bCollider.x = ballPositionX;
bRaycastLine.x = ballPositionX;
}
}
...
void AI::init_ai()
{
//initialize the AI, not much here becaue the AI is very basic.
aiPaddle.h = 40;
aiPaddle.w = 10;
aiPaddle.x = SCREEN_WIDTH - aiPaddle.w;
aiPaddle.y = SCREEN_HEIGHT/2 - aiPaddle.h/2;
aiYPosition = aiPaddle.y;
aiVelocity = 0;
}
void AI::handle_ai_events()
{
//check if the raycasted line is above/below the AI paddle...
if(bRaycastLine.y > aiPaddle.y + aiPaddle.h){
aiVelocity += AI_VELOCITY;
} else if(bRaycastLine.y < aiPaddle.y){
aiVelocity -= AI_VELOCITY;
}
}
void AI::move_ai()
{
//apply the velocities to the AI...
aiYPosition = aiVelocity;
aiPaddle.y = aiYPosition;
if(aiYPosition < 0){
aiYPosition -= aiVelocity;
aiPaddle.y = aiYPosition;
}
if(aiYPosition > aiPaddle.y + SCREEN_HEIGHT){
aiYPosition += aiVelocity;
aiPaddle.y = aiYPosition;
}
}
bRaycastLine
is an SDL_Rect
that follows the position of the ball.
3 Answers 3
One way to make an AI 'dumber' is to instead give it a handicap. You could limit its movement speed until it doesn't always win.
Another option is to randomize the ball trajectory after it bounces, and then give the AI limited information about the ball trajectory, but rather represent it as a collection of potential states, which gets narrowed down as the ball gets closer to the AI paddle.
In order to use an approach like this, you need to make the AI smarter first, so it can do trajectory prediction, and still be able to respond realistically with limited information.
Needless to say, this is harder.
-
\$\begingroup\$ Hm, I was thinking about somehow limiting its movement speeds. Maybe adding a delay before moving the AI? I could also try only moving the AI if the ball is past the center of the screen, and slowing the ai down by a certain percentage. Will post back if that does the trick. \$\endgroup\$JohnBobSmith– JohnBobSmith2014年12月28日 21:52:46 +00:00Commented Dec 28, 2014 at 21:52
It's not clear to me whether you need to store the variables isMovingLeft
... etc. Assuming that you don't, you can make the function move_ball
a little bit simpler.
void Ball::move_ball(float timeStep, SDL_Rect playerPaddle, SDL_Rect aiPaddle)
{
int upDown = 0;
int leftRight = 0;
//move the ball, its collider, and the raycast. Check for collisions with the paddles
//by calling the check_collision() function.
if(ballPositionY + BALL_HEIGHT > SCREEN_HEIGHT){
upDown = 1;
}
if(ballPositionY < 0){
upDown = -1;
}
if(ballPositionX + BALL_WIDTH > SCREEN_WIDTH){
ballPositionX = SCREEN_WIDTH/2;
}
if(ballPositionX < 0){
ballPositionX = SCREEN_WIDTH/2;
}
if(check_collision(bCollider, playerPaddle)){
leftRight = 1;
}
if(check_collision(bCollider, aiPaddle)){
leftRight = -1;
}
ballPositionY -= upDown*ballVelocityY;;
ballPositionX -= leftRight*ballVelocityX;
if ( upDown != 0 ) {
bCollider.y = ballPositionY;
bRaycastLine.y = ballPositionY;
}
if ( leftRight != 0 ) {
bCollider.x = ballPositionX;
bRaycastLine.x = ballPositionX;
}
}
-
\$\begingroup\$ Hm... How does one change the position of the ball though? Right now, you only subtract the velocities from the position with
ballPositionX -= leftRight*ballVelocityX;
and likewise for the Y velocity. So the ball always moves left and up (remember SDL y-coordinates are inverted). Also, I seem to loose the ability to randomize direction at the start of the game. Any ideas? \$\endgroup\$JohnBobSmith– JohnBobSmith2014年12月29日 16:25:49 +00:00Commented Dec 29, 2014 at 16:25 -
\$\begingroup\$ The positions are set under the
if (upDown != 0)
andif ( leftRight != 0 )
blocks. \$\endgroup\$R Sahu– R Sahu2014年12月29日 16:27:19 +00:00Commented Dec 29, 2014 at 16:27 -
\$\begingroup\$ Right, but since you always subtract the velocities (and never add them) the ball position is always negative and hence goes to the top left of the screen. What if I want to move down or to the right? One would need to add the velocity on the x/y axis. Also, the
leftRight
andupDown
variables can never equal 0 in this context, unless the ball moves off screen. So your conditions are always true. \$\endgroup\$JohnBobSmith– JohnBobSmith2014年12月29日 16:31:27 +00:00Commented Dec 29, 2014 at 16:31 -
\$\begingroup\$ When
upDown
is -1, the subtraction term will effectively do an add. Same withleftRight
. \$\endgroup\$R Sahu– R Sahu2014年12月29日 16:32:46 +00:00Commented Dec 29, 2014 at 16:32 -
\$\begingroup\$ Oh, I made a mistake. I miss read your code.
upDown
andleftRight
are the directions of the ball, and not a second position variable. Perhaps a better name for them likeisMovingUpDown
(likewise for left/right) would be better? Also, is there a way to cut down on more of those if statements? We still have 8 of them, which is better than 10, but it still seems like quite a lot of if statements. \$\endgroup\$JohnBobSmith– JohnBobSmith2014年12月29日 16:39:30 +00:00Commented Dec 29, 2014 at 16:39
A few things that come in mind :
- Limited velocity : that seems to be already implemented, but you can always decrease the value.
- Response time : if you want to be close to what a human do, then your AI must have a time of reaction greater than 0. One simple implementation would be to start moving when the ball reaches a certain Y value (if you play from top to bottom, and Y axis is vertical of course).
- Prediction : one thing would be to put a bias to the desired position of your AI. Something realistic may be to set the final destination of your AI somewhere in a gaussian distribution around the expected position of the ball. As mentioned by someone else, the gaussian shape can get narrower as the ball gets closer (which is what a human being does, he roughly estimates first then narrow it down as the ball gets closer). Here to help you with the implementation : normal distribution in C++
...
's in your code? \$\endgroup\$