class Robot{
private:
int priority;
int HP;
public:
// constructors & destructor
Robot() : priority(rand()%25+1), HP(rand()%50+10) {};
// methods
int getPr() { return priority; };
int getHP() { return HP; };
void getHit(int hp) { if(hp<HP) HP-=hp; else HP=0; };
int alive() { return HP>0; };
// operators
friend bool operator<(const Robot &a, const Robot &b);
friend bool operator>(const Robot &a, const Robot &b);
};
where the comparison operators defined as
bool operator<(const Robot &a, const Robot &b)
{
return a.priority < b.priority;
}
bool operator>(const Robot &a, const Robot &b)
{
return a.priority > b.priority;
}
Of course, we can use a deque created like
deque<Robot*> rq;However, we will experience some problems along this way because we would like to keep the robots in the queue according to their priority; that is, we need to use the STL algorithm sort(), and for this we would like to have a comparison operator < (less than) redefined for the members of the queue. Unfortunately, it turns out that we can redefine comparison operators only for classes, not for pointers. Thus, we will need to create a fake class PRobot that will keep just pointer to an object of the Robot class. A simple version of the class can look like:
class PRobot {
private:
Robot *robot_pointer;
public:
PRobot() : robot_pointer(NULL) {};
PRobot(Robot *robot) : robot_pointer(robot) {};
};
Please note that the declaration of the PRobot class doesn't depend on the content of the class Robot.
In other words, we can keep developing the class Robot, but the PRobot class will stay the same.
The goal is to develop a class RobotQueue with the following interface:
class RobotQueue {
private:
...
public:
RobotQueue(void);
~RobotQueue(void);
bool empty(); // returns true if the queue is empty
int size(); // returns the number of elements in the queue
void push(PRobot pr); // adds the given element in the queue (in the sorted order)
PRobot pop(); // removes the top element from the queue
void remove(PRobot pr); // removes the given element from the queue
iterator remove(iterator pos); // removes the element in the given position from the queue
iterator begin(); // returns the 'pointer' to the first element
iterator end(); // returns the 'pointer' to the element after the last one
const PRobot& operator[](int index); // returns a reference to the element with the given index
};
Now, we will add a couple modifications to the new PRobot class. Our first improvement will be done to let users of the class use assignments like
Robot *tank = robots.pop();That is, we would like to be able to assign a value of the PRobot class to a variable of the Robot* type. Technically speaking, they are the same because a variable of the PRobot class is nothing but a container with one Robot* variable in it. Thus, to let the compiler know how to perform type casting we will redefine the corresponding type conversion operator Robot*() for the PRobot class. To do so, we will add the lines
operator Robot*() const { return robot_pointer; };
to the definition of the PRobot class.
The second modification of the PRobot class is to redefining the comparison operator < (less) for it. To do so, Let's add line
friend bool operator<(const PRobot &a, const PRobot &b);
and the implementation of the operator to the file robot.cpp:
bool operator<(const PRobot &a, const PRobot &b)
{
return *a.robot_pointer < *b.robot_pointer;
}
class RobotQueue {
private:
std::deque queue;
public:
// constructors & destructors
RobotQueue(void) { };
~RobotQueue(void) { queue.clear(); };
// methods
bool empty() { return queue.empty(); };
int size() { return queue.size(); };
void push(PRobot pr) { queue.push_back(pr); sort(queue.begin(), queue.end()); };
PRobot pop() { PRobot pr=queue.front(); queue.pop_front(); return pr; };
};
As you can see we do not do anything ourselves, all real work is done inside the deque container. We just
providing an interface for it. The only problem now is that elements inside the RobotQueue are sorted in the
ascending order, but we would like to have the element with the highest priority first. To achieve this, we can change the
inequality sign in the implementation of the less operator for the PRobot class:
bool operator<(const PRobot &a, const PRobot &b)
{
return *a.robot_pointer > *b.robot_pointer;
}
Now, we would like to add to the class ability to work with iterators just like all other container classes do. Please recall that iterators depend on the container class used as well as the type of the objects we keep in the container. For example, for our implementation we would need to use an iterator declared as
std::deque<PRobot>::iterator pos;Since we have hidden the actual container class from the user, we will need to hide the real type of the iterator as well. To do this, we will define the appropriate iterator type inside the RobotQueue class. We will use the typedef construct, to do so we need to add the line
typedef std::deque<PRobot>::iterator iterator;
After this, programmers using the RobotQueue class can declare proper iterators just using
RobotQueue::iterator pos;Our next goal will be to provide interface for the iterators. We will start with declaring two methods
iterator begin() { return queue.begin(); };
iterator end() { return queue.end(); };
As you can see we again don't do anything by ourselves, we just rely on the methods of the underlying container class.
Now, programmers can use the iterators to access elements of the queue. For example, to print the priority of all
robots in the queue we need to do something like this
RobotQueue::iterator pos;
Robot *tank;
for(pos=rq.begin(); pos!=rq.end(); pos++){
tank = *pos;
cout<<tank->getPr()<<" ";
}
where rq is an object of the RobotQueue type.
Now, let's add a remove() method that works similar to the erase() method of the containers. It should remove the element specified by the iterator given as argument from the container and return the iterator to the next element in the container:
iterator remove(iterator pos) { return queue.erase(pos); };
And again we don't need to do anything. All the work is done in the underlying class.
With this method we now able to look through the whole queue and remove dead robots:
RobotQueue::iterator pos;
Robot *tank;
for(pos = rq.begin(); pos!=rq.end();){
tank = *pos;
if( ! tank->alive() ) pos = pq.remove(pos);
else pos++;
}
Our last step will be overloading the remove() method. We would like to be able to remove a given robot
from the robot queue. Let's define a method remove() that takes an element of the PRobot class
as a parameter, looks for it in the queue, and removes:
void remove(PRobot pr);
A possible implementation of the method can be
void RobotQueue::remove(PRobot pr)
{
iterator pos;
for(pos=begin(); pos!=end(); pos++)
if( *pos == pr ){
remove(pos);
return;
}
}
Finally, let's provide a random access to elements of the RobotQueue by their indexes. To do so, we will define operator [] like shown below:
const PRobot& operator[](int index) { return pq[index]; };
Redefining square bracket operator will allow to access elements of the queue as usual elements of an array:
Robot *tank = queue[5]; // queue is of the RobotQueue type
#define alias definitionFor example, to create an alias for the method push() of the class RobotQueue, we need to add the following line into the class definition:
// aliases
#define add push
Now, instead of using
rq.push( tank );we can use
rq.add( tank );Similar, we can create an alias erase for the method remove:
#define erase remove
|
|
|
||||||||||||||||||||||||||||||||||
#include "pqueue.h"
#include "robot.h"
RobotQueue *rq;
Close();
rq = new RobotQueue; // create a new robot queue
Robot *tank;
for(int i=0;i<10;i++){
tank = new Robot(); // create a random robot
rq->add(tank);
LogBox->AppendText( String::Concat(S"Inserted a new robot with PR: ",
tank->getPr().ToString(),
S" and HP: ",
tank->getHP().ToString(),
S"\r\n")
);
}
LogBox->AppendText(S"\r\n");
RobotQueue::iterator pos;
LogBox->AppendText(S"Robots in the queue are:\r\n");
for( pos=rq.begin(); pos!=rq.end(); pos++ ){
tank = *pos;
LogBox->AppendText( String::Concat(S"PR: ", tank->getPr().ToString(),
S" and HP: ", tank->getHP().ToString(),
S"\r\n")
);
}
void fight(const RobotQueue &rq);
the implementation of the method can differ depending on how smart you want your robots be. We will show the simplest
way to do this. Our robot will randomly choose its opponent and hit it:
void Robot::fight(RobotQueue &rq)
{
Robot *target;
RobotQueue::iterator pos;
int i;
// first let's try to find this robot in the queue
pos = std::find(rq.begin(), rq.end(), this);
if( pos != rq.end() ){
if( rq.size() == 1 ) return; // this robot is the only robot in the queue
// randomly choose an opponent, but not myself
i = rand() % rq.size();
target = rq[i];
if( target == this )
i = (i+1) % rq.size();
}
else{
if( rq.size() == 0 ) return;
i = rand() % rq.size();
}
target = rq[i];
target->getHit( rand()%priority );
}
Now, we need to complete a small issue about this new method. The problem is that the Robot class has no idea
about existence of the RobotQueue class. However, we can not solve the problem just by including the
robotqueue.h header file into the robot.h because this file itself includes robot.h. (Try this and
see what happens.) To solve this issue we just need the compiler to know that RobotQueue is just another class
defined somewhere else. We can do this by placing the line
class RobotQueue;in the file robot.h before the declaration of the Robot class.
Once we developed the fight() method for one robot. The whole battle can be simulated as a sequence of fights:
RobotQueue::iterator pos;
Robot *shooter;
// simulate all the fights
for( pos=queue.begin(); pos!=queue.end(); pos++){
shooter = *pos;
if( shooter->alive() ) shooter->fight( queue );
}
// delete all dead robots
for( pos = queue.begin(); pos!=queue.end(); ){
shooter = *pos;
if( ! shooter->alive() ) pos = queue.remove( pos );
else pos++;
}
| Button: Next | |
|---|---|
| (Name) | NextBtn |
| Text | Next |
| Enabled | False |
| Location | 96, 215 |
static int bnum = 1; // battle number
Robot *tank;
PQueue::iterator pos;
int killed, hit;
LogBox->AppendText( String::Concat(S"\r\nThere are ", pq->size().ToString(),
S" robots left BEFORE battle #",
bnum.ToString(), S".\r\n"
) );
if( rq->size() > 1 ){
// run the battle
for( pos=rq->begin(); pos!=rq->end(); pos++){
tank = *pos;
if( tank->alive() ){
hit = tank->fight(*rq);
LogBox->AppendText( String::Concat(S" ** Robot PR:", tank->getPr().ToString(),
S" hits robot PR:", hit.ToString(), S"\r\n"
) );
}
}
// remove all killed robots from the queue
killed = 0;
for( pos = rq->begin(); pos!=rq->end(); ){
tank = *pos;
if( ! tank->alive() ){
LogBox->AppendText( String::Concat(S" -- removing robot: PR:",
tank->getPr().ToString(),
S" HP:", tank->getHP().ToString(),
S".\r\n"
));
pos = rq->remove(pos);
killed++;
}
else pos++;
}
LogBox->AppendText( String::Concat(killed.ToString(),
S" robot(s) were killed in battle #",
bnum.ToString(), S". "
));
LogBox->AppendText( String::Concat(S"\r\nThere are ", pq->size().ToString(),
S" robots left AFTER battle #",
bnum.ToString(), S":\r\n"
));
for( pos=rq->begin(); pos!=rq->end(); pos++){
tank = *pos;
LogBox->AppendText( String::Concat(S"Pr:", tank->getPr().ToString(),
S" HP:", tank->getHP().ToString(),
S"\r\n"
));
}
bnum++;
}
NextBtn->Enabled = rq->size() > 1;
Note: don't forget to enable the Next button in the StartBtn_Click method.