Robot queue.

To illustrate usage of the STL classes we will design a new class RobotQueue that will store pointers to objects of the Robot class. The Robot class for this example will be relatively simple:
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;
}

Pointers to the class Robot

It's very easy to create a deque of objects of the Robot class, but doing so means that we will duplicate the data. To avoid duplication, we would like to create a deque of pointer to objects of the Robot class. There are several ways of performing this task. For educational purposes we decided to choose not the easiest one.

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;
}

The RobotQueue class

Now, we have everything ready to concentrate on the RobotQueue class. Let's start with the private data members. Since the only goal of this class is to simplify user's work with an STL container, the only thing we need to have is a particular container to use. If we decide to use the deque, then the first version of the RobotQueue can look like:
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 begin() and end():
    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

Aliases for class methods

If we want to have a method add() that does exactly the same work as the method push() (inserts a new element into the queue), then we don't need to create an identical method with a different name. Instead, we can create an alias add for the method push. To do so, we will use the pre-compiler instruct #define. The general syntax for this construct is
#define alias definition
For 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

Testing robot queue

In order to test newly developed class RobotQueue we will create a new project RQTest with the form like one shown on the picture:
Robot queue testing form
where form components have the following properties set:
Button: Exit
(Name) ExitBtn
Text Exit
Location 480, 215
Button: Start
(Name) StartBtn
Text Start
Location 8, 215
TextBox
(Name) LogBox
Text
Location 8, 8
Size 544, 200
Location 8, 8
Multiline True
ScrollBars Vertical
ReadOnly True
Now, let's make the following modifications in the file Form1.h:
  1. In the beginning of the file insert the lines
    #include "pqueue.h"
    #include "robot.h"
  2. In the definition of the Form1 class add the line:
    RobotQueue *rq;
  3. After double clicking the Exit button in the body if the ExitBtn_Click method type
    Close();
  4. After double clicking the Start button in the body if the StartBtn_Click method type something like
    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");
    
  5. Now, let's try to print out all the elements of the robot queue. To do this, add these lines to the the same StartBtn_Click method:
    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")
                                         );
    }
    
If everything works fine we are ready for our further improvements ...

Each robot's fight and all robots battle

In this section we would like to implement methods that allow us to simulate robot battles. We will start with developing method fight() of the class Robot. This method will simulate the fight of the robot object invoking the method against other robots located in the same cell. Thus, the robot fighting other robots should know who to fight with. In other words, the robot supposed to have information about other robots located near it. We can provide this information by passing an object of the RobotQueue class as a parameter of the method. Let's modify the definition of the Robot class by adding a new method:
      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++;
}

Get back to testing

To test our battle model let's add a new button Next to the project form with the following properties:
Button: Next
(Name) NextBtn
Text Next
Enabled  False
Location 96, 215
After double clicking the new button add the following text to the NextBtn_Click method:
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.

Source files

The complete text of the files you can find on the example page: