Ideas for a design to handle a crazy set of rules

A Wizard follows a certain school of magic. The school he follows gives attributes to him in no particular pattern or category. For example:
-A Wizard gets bonus +1 to hit a Goblin if his school is Enchantment.
-A Wizard gets +1 bonus on surviving a poisonous attack if his school is Conjuration.
-A Wizard cannot be attacked for the first 5 minutes by a Dragon if his school is Illusion.
-and other unrelated bonus attributes that cannot be summarized so nicely because they are not related to each other.
How best to enforce these rules? The last resort is to use crazy if statements for all these particular situations.

My experimental idea so far:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <typeinfo>

struct Monster {virtual ~Monster() = default;};

struct Wizard {
	struct School {
		virtual void checkForBonusToHit (Monster*) const {}  // overridden only by SchoolClass<Enchantment>
	 };
	enum SchoolType {Enchantment, Conjuration, Illusion};
	template <int, typename = void> struct SchoolClass;
	template <typename T> struct SchoolClass<Enchantment, T> : School {virtual void checkForBonusToHit (Monster*) const override;};
	template <typename T> struct SchoolClass<Conjuration, T> : School {};
	template <typename T> struct SchoolClass<Illusion, T> : School {};
	School* school;
	Wizard (School* s) : school(s) {}
	void attack (class Monster* monster) {
		std::cout << "Wizard attacks." << std::endl;
		school->checkForBonusToHit (monster);  // This is one idea.
	}
};

struct Goblin : Monster {};

struct PoisonousSnake : Monster {};

struct Dragon : Monster {};

template <>
void Wizard::SchoolClass<Wizard::Enchantment>::checkForBonusToHit (Monster* monster) const {
	if (typeid(*monster) == typeid(Goblin))  // Use Visitor Pattern if other types are of interest:  monster->accept(BonusToHitVisitor(this));
	     std::cout << "Wizard attacks with +1 to hit against the Goblin." << std::endl;
}

int main() {
	Wizard wizard (new Wizard::SchoolClass<Wizard::Enchantment>);
	Goblin* goblin = new Goblin;  PoisonousSnake* snake = new PoisonousSnake;  Dragon* dragon = new Dragon;
	wizard.attack(goblin);  wizard.attack(snake);  wizard.attack(dragon);
}

Output:
1
2
3
4
Wizard attacks.
Wizard attacks with +1 to hit against the Goblin.
Wizard attacks.
Wizard attacks.


Any better idea than the one above? Maintainability is important for the design search. Performance comes second.
Last edited on
> Any better idea than the one above? Maintainability is important for the design

The mediator pattern is fine..
The idea behind the mediator is that the subjects (wizard, monsters) are not be tightly coupled with the mediator.
A lookup table will avoid the combinatorial explosion as the design evolves.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <functional>

struct monster {
    virtual ~monster() = default;
};

////////////// header attack_mediator.h ////////////////
struct wizard ;

struct attack_mediator {
    static int bonus( const wizard& w, const monster& m ) ;
    static int immunity_period( const wizard& w, const monster& m ) ;
    // ...
};
//////////////////////////////////////////////////////////////


struct wizard {
    const enum school_t { Enchantment, Conjuration, Illusion } school ;
	const char* school_name() const {
	    static const char* const name[] { "Enchantment", "Conjuration", "Illusion" } ;
	    return name[school] ;
	}

	wizard( school_t s ) : school(s) {}

	void attack ( monster& m ) {
		std::cout << "wizard (" << school_name() << ") <-> " << typeid(m).name() + 1 ; // +1 for GNU

		const int bonus = attack_mediator::bonus( *this, m ) ;
		if(bonus) std::cout << " + bonus " << bonus ;

		const int immunity = attack_mediator::immunity_period( *this, m ) ;
		if(immunity) std::cout << " + immune for first " << immunity << " minutes." ;

		std::cout << '\n' ;
	}
};

struct goblin : monster {};
struct snake : monster {};
struct dragon : monster {};

////////////// implementation attack_mediator.cc ////////////////
#include <typeindex>
#include <map>

namespace
{
    struct attatck_attributes { int bonus ; int immunity ; } ;
    static const std::map< std::pair< wizard::school_t, std::type_index >, attatck_attributes > attributes_map =
    {
        { { wizard::Enchantment, typeid(goblin) }, { 1, 0 } },
        { { wizard::Enchantment, typeid(dragon) }, { 6, 5 } },
        { { wizard::Illusion, typeid(goblin) }, { 2, 0 } },
        { { wizard::Illusion, typeid(snake) }, { 1, 0 } },
        { { wizard::Conjuration, typeid(snake) }, { 3, 10 } }
    };
}

int attack_mediator::bonus( const wizard& w, const monster& m ) {
    const auto iter = attributes_map.find( { w.school, typeid(m) } ) ;
    return iter != attributes_map.end() ? iter->second.bonus : 0 ;
}

int attack_mediator::immunity_period( const wizard& w, const monster& m ) {
    const auto iter = attributes_map.find( { w.school, typeid(m) } ) ;
    return iter != attributes_map.end() ? iter->second.immunity : 0 ;
}
////////////////////////////////////////////////////////////////////

int main()
{
     wizard wizards[] { wizard::Conjuration, wizard::Enchantment, wizard::Illusion } ;

     goblin g ; snake p ; dragon d ;
     std::reference_wrapper<monster> monsters[] { g, p, d } ;

     for( wizard& w : wizards ) {
         for( auto m : monsters ) w.attack(m) ;
         std::cout << '\n' ;
     }
}

http://coliru.stacked-crooked.com/a/c0f1501a86e383f5
Topic archived. No new replies allowed.