Arabian Nights in C++

From Wiki**3

The Problem (in Portuguese)

Aplicando os conceitos OO que já conhece, concretize as classes necessárias para representar a funcionalidade que se descreve de seguida. Pode ser necessária a criação de classes adicionais não descritas abaixo. Todas as classes indicadas devem pertencer ao namespace arabiannights.

Funcionalidade da lâmpada mágica (classe MagicLamp)

Uma lâmpada mágica liberta génios quando esfregada (método rub). Os génios podem ser bem ou mal-humorados. O humor dos génios é determinado pelas condições da lâmpada: sempre que a lâmpada tiver sido esfregada um número par de vezes (sem contar a actual), o génio sai mal-humorado. A quantidade de génios disponíveis é determinada no momento de encantamento da lâmpada (criação). Depois de esgotados os génios disponíveis, já não adianta esfregar a lâmpada para obter um génio, bem ou mal-humorado: nestas condições, a lâmpada cria um pequeno demónio que responde a pedidos de forma literal mas perversa. Devido a requisitos de sustentabilidade ambiental, as normas de produção exigem que as lâmpadas sejam recarregáveis. Assim, é possível voltar a obter génios quando se esfrega a lâmpada (em número igual ao inicial). O processo de recarregamento exige apenas que um demónio seja alimentado à lâmpada (método feedDemon).

Quando se cria uma nova lâmpada é necessário indicar a quantidade inicial de génios que é possível invocar (a lâmpada cria internamente os génios e apenas retorna referências ou ponteiros para eles). É possível saber quantos génios ainda estão disponíveis na lâmpada (método nGenies). É ainda possível saber quantas vezes a lâmpada já foi recarregada (método nDemons). Quando se esfrega a lâmpada, deve-se indicar quantos desejos se espera que o génio realize (independentemente de o génio os poder negar).

Deve ser possível comparar duas instâncias da classe MagicLamp através do operador ==. Considera-se que duas lâmpadas são iguais se tiverem a mesma capacidade e se os valores retornados pelos métodos nGenies e nDemons forem iguais.

Nota: a lâmpada liberta apenas um génio de cada vez.

Funcionalidade do génio bem-humorado (classe FriendlyGenie)

O génio bem-humorado concede todos os desejos que lhe forem colocados (método grantWish e retorno true), até ao limite com que foi chamado da lâmpada. Depois do limite já não são concedidos desejos (retorno false). É possível saber quantos desejos já foram concedidos (método nGrantedWishes) e quantos ainda existem disponíveis (método nRemainingWishes).

Nota: o génio concede apenas um desejo de cada vez.

Funcionalidade do génio mal-humorado (classe GrumpyGenie)

O génio mal-humorado concede apenas o primeiro desejo que lhe for colocado (método grantWish e retorno true), independentemente do limite com que foi chamado da lâmpada (retorno false após o primeiro). É possível saber se o desejo já foi realizado (método nGrantedWishes retorna 1).

Funcionalidade do demónio reciclável (classe RecyclableDemon)

O demónio concede todos os desejos que lhe forem colocados (método grantWish e retorno true), independentemente do limite com que foi chamado da lâmpada. Se o demónio for recolocado na lâmpada (para a recarregar), já não pode realizar mais desejos (retorno false). É possível saber quantos desejos já foram concedidos (método nGrantedWishes).

Nota: o demónio concede apenas um desejo de cada vez.

Observações

Todas as classes devem ter métodos de acesso (get e set) (quando apropriado) para os seus atributos.

O operador <<, aplicado aos génios e ao demónio, deve enviar para o stream de saída correspondente uma das seguintes cadeias de caracteres:

  • Friendly genie has granted # wishes and still has # to grant. (# representam os contadores apropriados)
  • Grumpy genie has granted a wish. / Grumpy genie has a wish to grant. (consoante já concedeu ou não o pedido)
  • Recyclable demon has granted # wishes. / Demon has been recycled. (antes e depois de recarregar uma lâmpada)

A função main deve executar a seguinte sequência de operações:

  1. Criar uma lâmpada mágica com capacidade para 4 génios.
  2. Esfregar 5 vezes a lâmpada, indicando os números de desejos 2, 3, 4, 5, 1.
  3. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
  4. Pedir um desejo a cada um dos génios.
  5. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
  6. Pedir um desejo a cada um dos génios.
  7. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
  8. Colocar o demónio reciclável na lâmpada.
  9. Esfregar a lâmpada, indicando 7 como número de desejos.
  10. Imprimir em std::cout (utilizando o operador <<) o génio obtido.

Implementation

The following corresponds to a possible telling of a special version of the Arabian Nights stories. In any implementation, the following concepts will exist in one form or another: MagicLamp, Genie (a general type of genie, which may also describe the recyclable demon), FriendlyGenie, GrumpyGenie, and RecyclableDemon.

In the following implementation, each class will be defined in a pair of files: a header file (.h) and a file containing method definitions (.cpp). Although C++ does not enforce (or even needs) this division, it is useful for managing complexity and makes code understanding easier. Thus, the following files will exist:

  • For the magic lamp: MagicLamp.h (interface) and MagicLamp.cpp (implementation).
  • For the genies: Genie.h; FriendlyGenie.h; GrumpyGenie.h; and RecyclableDemon.h (we did not use .cpp file because these classes are small).
  • For the main function: main.cpp.

Note that the namespace has not been taken into account for defining class names. Nevertheless, when organizing large projects, especially if multiple namespaces exist, it may be advisable to establish a parallel between namespaces and directories (much like packages in Java).

Class MagicLamp

File MagicLamp.h contains the interface and a few small functions:

Ficheiro MagicLamp.h
 1   #ifndef __ARABIANNIGHTS_MAGICLAMP_H__
 2   #define __ARABIANNIGHTS_MAGICLAMP_H__
 3  
 4   #include <vector>
 5  
 6   #include "Genie.h"
 7   #include "FriendlyGenie.h"
 8   #include "GrumpyGenie.h"
 9   #include "RecyclableDemon.h"
10  
11   namespace arabiannights {
12  
13     class MagicLamp {
14  
15       /** Maximum number of geneies in the lamp. */
16       int _limit;
17  
18       /** Total number of rubs. */
19       int _totalRubs;
20  
21       /** Number of rubs since last charge. */
22       int _rubs;
23  
24       /** Number of recharges. */
25       int _demons;
26  
27       /** Keep track of genies. */
28       std::vector<Genie*> _genies;
29  
30       /** Keep track of demon. */
31       RecyclableDemon *_demon;
32  
33     public:
34       MagicLamp(int limit)
35         : _limit(limit), _totalRubs(0), _rubs(0), _demons(0), _demon(NULL) {}
36  
37       /**
38        * Destructor should be virtual (we have other virtual functions), even
39        * though we do not have any subclasses of MagicLamp.
40        */
41       virtual ~MagicLamp();
42  
43       int getLimit()     const { return _limit; }
44       int getRubs()      const { return _rubs; }
45       int getTotalRubs() const { return _totalRubs; }
46  
47       int  nGenies() const { return getLimit() - getRubs(); }
48       int  nDemons() const { return _demons; }
49  
50       /**
51        * Ask the lamp for a genie.
52        * If the number of rubs is even, return a grumpy genie.
53        * If the number of rubs is more than the limit, return a demon.
54        * Otherwise, return a normal friendly genie.
55        * @return a genie.
56        */
57       virtual Genie *rub(int wishes);
58  
59       /**
60        * Recharge the magic lamp by feeding it a demon.
61        * A demon can only be used if not previously recycled.
62        * @param demon the recyclable demon.
63        */
64       virtual void feedDemon(RecyclableDemon *demon);
65  
66       /**
67        * Compare this lamp with another.
68        * @return true if, in addition to the same capacity, the number
69        * of available genies and the number of recharges are the same;
70        * false, otherwise.
71        */
72       inline bool operator==(const MagicLamp &l) {
73         return getLimit() == l.getLimit() &&
74                nGenies()  == l.nGenies() && nDemons() == l.nDemons();
75       }
76  
77     };
78  
79   } // namespace arabiannights
80  
81   #endif

File MagicLamp.cpp contains the implementations of the functions not defined in the header (.h) file. Note how the destructor destroys all the genies and the demon when the lamp itself is destroyed (the only magic lamp is created in the stack of the main function - below - and its destructor is called at the end of the main function, when its stack is released).

Ficheiro MagicLamp.cpp
 1   #include "MagicLamp.h"
 2     
 3   arabiannights::MagicLamp::~MagicLamp() {
 4     if (_demon) delete _demon;
 5     for (size_t gx = 0; gx < _genies.size(); gx++)
 6       delete _genies[gx];
 7   }
 8  
 9   /**
10    * Ask the lamp for a genie.
11    * If the number of rubs is even, return a grumpy genie.
12    * If the number of rubs is more than the limit, return a demon.
13    * Otherwise, return a normal friendly genie.
14    * @return a genie.
15    */
16   arabiannights::Genie *arabiannights::MagicLamp::rub(int wishes) {
17     Genie *g;
18     if (_rubs < _limit) {
19       _rubs++;
20       _totalRubs++;
21       if (_totalRubs % 2 == 0) {
22         g = new FriendlyGenie(wishes);
23         _genies.push_back(g);  // remember we created the genie
24         return g;              // return genie to the caller
25       }
26       Genie *g = new GrumpyGenie(wishes);
27       _genies.push_back(g);    // remember we created the genie
28       return g;                // return genie to the caller
29     }
30     return _demon = (_demon ? _demon : new RecyclableDemon(wishes));
31   }
32  
33   /**
34    * Recharge the magic lamp by feeding it a demon.
35    * A demon can only be used if not previously recycled.
36    * Note that previously create genies ARE NOT deleted
37    * (they may still be active).
38    * @param demon the recyclable demon.
39    */
40   void arabiannights::MagicLamp::feedDemon(arabiannights::RecyclableDemon *demon) {
41     if (demon != _demon) return; // only accept our demon...
42     if (!demon->recycled()) {
43       demon->recycle();
44       delete _demon;  // zap the lamp's demon and reset lamp
45       _demon = NULL;
46       _rubs = 0;
47       _demons++;
48     }
49   }

Class Genie

Class Genie defines the basic behaviour for all genies. The whole class is defined in the header file (Genie.h). Notice the two versions (one is commented out) of the output operator.

Note that the output operator by itself if not capable of handling the polymorphic access used in the main function (because it is used as an argument). For it to work correctly, we defined an abstract method (dumpTo) that each class must implement. This method will be correctly called in polymorphic situations.

Note also that, since Genie is a class whose methods are to be redefined by subclasses and whose pointers will only point to subclass instances, needs a virtual destructor: in this was, the delete operator will look at the actual type of the object (instead of using the type of the pointer), allowing the correct destructor (that of the object's class and not the pointer's) to be called.

Ficheiro Genie.h
 1 #ifndef __ARABIANNIGHTS_GENIE_H__
 2 #define __ARABIANNIGHTS_GENIE_H__
 3 
 4 #include <iostream>
 5 
 6 namespace arabiannights {
 7 
 8   class Genie {
 9 
10     /** Maximum number of wishes (does not mean that the genie will grant them all). */
11     int _limit;
12 
13     /** Number of wishes granted. */
14     int _granted;
15 
16   protected:
17     // only instances from subclasses are allowed
18     Genie(int limit) : _limit(limit), _granted(0) {}
19 
20   public:
21     virtual ~Genie() {}
22 
23   public:
24     int getLimit() const { return _limit; }
25 
26   public:
27     virtual int  nGrantedWishes()   const { return _granted; }
28     virtual void incrementGranted()       { _granted++; }
29 
30     /**
31      * Ask the genie to grant a wish.
32      * Never allow more grants than the initial limit.
33      * @return true if the wish is granted; false otherwise.
34      */
35     virtual bool grantWish() {
36       if (canGrantWish()) {
37         incrementGranted();
38         doGrantWish();
39         return true;
40       }
41       return false;
42     }
43 
44     /**
45      * Decide if genie can grant a wish.
46      * @return true or false (depends on special circumstances).
47      */
48     virtual bool canGrantWish() const { return _granted < _limit; }
49 
50     /**
51      * Do actual wish granting. By default, does not do anything.
52      */
53     virtual void doGrantWish() {}
54 
55     /**
56      * Template method: how to dump to an output stream?
57      * @param o the output stream.
58      * @note pure virtual.
59      */
60     virtual void dumpTo(std::ostream &o) const = 0;
61 
62     /**
63      * Output operator.
64      * Declared friend: see alternative below (after class).
65      */
66     friend std::ostream &operator<<(std::ostream &o, const Genie &g) {
67       g.dumpTo(o);
68       return o;
69     }
70 
71 
72   };
73 
74 } // namespace arabiannights
75 
76 #if 0
77 inline std::ostream &operator<<(std::ostream &o, const Genie &g) {
78   rd.dumpTo(o);
79   return o;
80 }
81 #endif
82 
83 #endif

Class FriendlyGenie

A friendly genie is almost like a general genie (it differs in the output messages). Since it is a small class, it is completely defined in the header file (FriendlyGenie.h).

Ficheiro FriendlyGenie.h
 1 #ifndef __ARABIANNIGHTS_FRIENDLYGENIE_H__
 2 #define __ARABIANNIGHTS_FRIENDLYGENIE_H__
 3 
 4 #include <iostream>
 5 #include "Genie.h"
 6 
 7 namespace arabiannights {
 8 
 9   /**
10    * A friendly genie is actually a normal genie, but with special
11    * descriptive text.
12    */
13   class FriendlyGenie : public Genie {
14 
15   public:
16     /**
17      * @param limit the maximum number of wishes to grant.
18      */
19     FriendlyGenie(int limit) : Genie(limit) {}
20 
21     /**
22      * Redefined from Genie.
23      * @return the number of wishes left to grant.
24      */
25     inline int nRemainingWishes() const {
26       return getLimit() - nGrantedWishes();
27     }
28 
29     /**
30      * Dump to output stream.
31      * @param o the output stream.
32      */
33     void dumpTo(std::ostream &o) const {
34       o << "Friendly genie has granted " << nGrantedWishes()
35         << " wishes and still has " << nRemainingWishes() << " to grant.";
36     }
37 
38     /**
39      * Output operator.
40      * Declared friend: see alternative below (after class).
41      */
42     friend std::ostream &operator<<(std::ostream &o, const FriendlyGenie &fg) {
43       fg.dumpTo(o);
44       return o;
45     }
46 
47   };
48 
49 } // namespace arabiannights
50 
51 #if 0
52 // alternative definition for output operator (non-friend)
53 inline std::ostream &operator<<(std::ostream &o, const FriendlyGenie &fg) {
54   fg.dumpTo(o);
55   return o;
56 }
57 #endif
58 
59 #endif

Class GrumpyGenie

Like the other previous genie classes, this one is also very small and completely defined in the header file (GrumpyGenie.h):

Ficheiro GrumpyGenie.h
 1 #ifndef __ARABIANNIGHTS_GRUMPYGENIE_H__
 2 #define __ARABIANNIGHTS_GRUMPYGENIE_H__
 3 
 4 #include <iostream>
 5 #include "Genie.h"
 6 
 7 namespace arabiannights {
 8 
 9   /**
10    * This is actually a normal Genie with only one wish.
11    */
12   class GrumpyGenie : public Genie {
13 
14   public:
15     /**
16      * @param limit the number of wishes to grant (ignored: always grants only one wish).
17      */
18     GrumpyGenie(int limit) : Genie(1) {}
19 
20     /**
21      * Dump to output stream.
22      * @param o the output stream.
23      */
24     void dumpTo(std::ostream &o) const {
25       o << ((nGrantedWishes() == 1) ? "Grumpy genie has granted a wish." : "Grumpy genie has a wish to grant.");
26     }
27 
28     /**
29      * Output operator.
30      * Declared friend: see alternative below (after class).
31      */
32     friend std::ostream &operator<<(std::ostream &o, const GrumpyGenie &gg) {
33       gg.dumpTo(o);
34       return o;
35     }
36 
37   };
38 
39 } // namespace arabiannights
40 
41 #if 0
42 inline std::ostream &operator<<(std::ostream &o, const GrumpyGenie &gg) {
43   gg.dumpTo(o);
44   return o;
45 }
46 #endif
47 
48 #endif

Class RecyclableDemon

The RecyclableDemon is a genie that implements some of the control methods in a special way (but still adheres to the template method defined by class Genie). Like the other small classes, it is completely define in RecyclableDemon.h:

Ficheiro RecyclableDemon.h
 1 #ifndef __ARABIANNIGHTS_RECYCLABLEDEMON_H__
 2 #define __ARABIANNIGHTS_RECYCLABLEDEMON_H__
 3 
 4 #include <iostream>
 5 #include "Genie.h"
 6 
 7 namespace arabiannights {
 8 
 9   class RecyclableDemon : public Genie {
10     /** Has it been recycled? */
11     bool _recycled;
12 
13     /**
14      * Recycle a demon (called by a magic lantern).
15      * @note this method is private to avoid it being called by anyone other than a magic lamp.
16      */
17     friend class MagicLamp;  // for calling recycle
18     void recycle() { _recycled = true; }
19 
20   public:
21 
22     /**
23      * @param wishes the number of wishes to grant (ignored: always grants wishes).
24      */
25     RecyclableDemon(int limit) : Genie(limit), _recycled(false) {}
26 
27     /**
28      * Recyclable demons always grant wishes (unless it has been recycled).
29      * @return true if not yet recycled; false, otherwise.
30      */
31     bool canGrantWish() const { return !_recycled; }
32 
33     /**
34      * @return whether a demon has been recycled.
35      */
36     bool recycled() const { return _recycled; }
37 
38     /**
39      * Dump to output stream.
40      */
41     void dumpTo(std::ostream &o) const {
42       if (_recycled) o << "Demon has been recycled.";
43       else           o << "Recyclable demon has granted " << nGrantedWishes() << " wishes.";
44     }
45 
46     /**
47      * Output operator.
48      * Declared friend: see alternative below (after class).
49      */
50     friend std::ostream &operator<<(std::ostream &o, const RecyclableDemon &rd) {
51       rd.dumpTo(o);
52       return o;
53     }
54 
55   };
56 
57 } // namespace arabiannights
58 
59 #if 0
60 inline std::ostream &operator<<(std::ostream &o, const RecyclableDemon &rd) {
61   rd.dumpTo(o);
62   return o;
63 }
64 #endif
65 
66 #endif

Main Function

In this case, the main is, straightforward. Some aspects merit attention: since the magic lamp will not be used anywhere else, it is a good ideia to create in within the context of the main function, i.e., on its stack, and not on the heap (as would be the case if it were created with the "new" operator).

Função principal (main)
 1   #include <iostream>
 2   #include "MagicLamp.h"
 3  
 4   int main() {
 5     // 1. Criar uma lâmpada mágica com capacidade para 4 génios.
 6     // Magic lamp is created in the stack.
 7     arabiannights::MagicLamp ml(4);
 8  
 9     // Items 2. through 7. are simply executed in a cycle.
10     int wishes[] = { 2, 3, 4, 5, 1 };  // number of wishes per genie
11     arabiannights::Genie *genies[5];                  // vector for genies
12  
13     // 2. Esfregar 5 vezes a lâmpada, indicando os números de desejos 2, 3, 4, 5, 1.
14     for (size_t gx = 0; gx < 5; gx++) genies[gx] = ml.rub(wishes[gx]);
15  
16     // 3. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
17     for (size_t gx = 0; gx < 5; gx++) std::cout << *genies[gx] << std::endl;
18  
19     // 4. Pedir um desejo a cada um dos génios.
20     for (size_t gx = 0; gx < 5; gx++) genies[gx]->grantWish();
21  
22     // 5. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
23     for (size_t gx = 0; gx < 5; gx++) std::cout << *genies[gx] << std::endl;
24  
25     // 6. Pedir um desejo a cada um dos génios.
26     for (size_t gx = 0; gx < 5; gx++) genies[gx]->grantWish();
27  
28     // 7. Imprimir em std::cout (utilizando o operador <<) cada um dos génios.
29     for (size_t gx = 0; gx < 5; gx++) std::cout << *genies[gx] << std::endl;
30  
31     // 8. Colocar o demónio reciclável na lâmpada.
32     // tricky (how can we be certain the demon is in the 5th position?)
33     ml.feedDemon((arabiannights::RecyclableDemon*)genies[4]);
34  
35     // 9. Esfregar a lâmpada, indicando 7 como número de desejos.
36     arabiannights::Genie *g = ml.rub(7);
37  
38     // 10. Imprimir em std::cout (utilizando o operador <<) o génio obtido.
39     std::cout << *g << std::endl;
40  
41     return 0;
42   }

Compiling and Running

The following commands are needed to compile the application (note that it suffices to compile the .cpp files).

 g++ -ansi -pedantic -Wall -c MagicLamp.cpp
 g++ -ansi -pedantic -Wall -c main.cpp
 g++ -o an MagicLamp.o main.o

The result of running the an application is the following:

 Grumpy genie has a wish to grant.
 Friendly genie has granted 0 wishes and still has 3 to grant.
 Grumpy genie has a wish to grant.
 Friendly genie has granted 0 wishes and still has 5 to grant.
 Recyclable demon has granted 0 wishes.
 Grumpy genie has granted a wish.
 Friendly genie has granted 1 wishes and still has 2 to grant.
 Grumpy genie has granted a wish.
 Friendly genie has granted 1 wishes and still has 4 to grant.
 Recyclable demon has granted 1 wishes.
 Grumpy genie has granted a wish.
 Friendly genie has granted 2 wishes and still has 1 to grant.
 Grumpy genie has granted a wish.
 Friendly genie has granted 2 wishes and still has 3 to grant.
 Recyclable demon has granted 2 wishes.
 Grumpy genie has a wish to grant.

See Also