Ugh.
Loose coupling for the win: a paddle should not point to its controller, but rather vice versa. Not all paddles need to have controllers, but all controllers need to have a paddle. Code accordingly.
Also, single responsibility is important.
This is what I would write:
class Paddle
{
public:
void Move(int velocity)
{
CurPosition += velocity;
}
int GetPosition() const
{
return CurPosition;
}
private:
int CurPosition;
};
class Controller
{
public:
virtual ~Controller() { }
virtual void MovePaddle(Paddle& p) = 0;
};
class HumanController : public Controller
{
public:
virtual void MovePaddle(Paddle& p) override
{
// Read input state
p.Move(inputvelocity);
}
};
class AIController : public Controller
{
public:
AIController(const PositionedObject& t, int maxspeed)
: TargetObject(t),
MaxSpeed(maxspeed)
{ }
virtual void MovePaddle(Paddle& p) override
{
int targetpos = TargetObject.GetPosition().GetY();
int unclampedvelocity = targetpos - p.GetPosition();
int clampedvelocity;
if(std::abs(unclampedvelocity) > MaxSpeed)
clampedvelocity = (unclampedvelocity > 0) ? MaxSpeed : -MaxSpeed;
else
clampedvelocity = unclampedvelocity;
p.Move(clampedvelocity);
}
private:
const PositionedObject& TargetObject;
const int MaxSpeed;
};
Representing paddles and moving them are separate concerns. They should not be crammed into the same class (single responsibility). No variable, function, or class should have more than one reason to exist.
Extending the idea of moving a paddle around should be a matter of following the interface contract, not modifying the code of paddles as you would with the boolean flag solution (open/closed rule). In this approach, I can add an infinite number of means of moving paddles without changing any code - just adding new code. This is as it should be.
In building my paddle controller, I take care to obey Liskov substitution - which has already been mentioned. In a nutshell: if I expect a Base object but I'm handed a Derived object, I should never know the difference. Moreover, that Derived might actually be a Derived2 and I should not need to care.
Interface segregation is also important: never bundle logic that is unrelated. (The OP's solution runs afoul of this.) The AI paddle is the only thing that relies on knowing the ball's position, or has any notion of a "target" position, so don't pollute other code with those ideas.
Dependency inversion is also relevant here. Note that I made the AI controller talk about a positioned object, not a ball. In brief: never depend on implementations. Depend on abstract contracts and interfaces. If I want the AI paddle to follow something besides the ball, I just give it a different object! If the implementation of the ball changes, it won't break the AI, and so on.
For those following along at home: these are the SOLID design principles, and well worth learning about.