I am creating a game using a 2D array grid, with the player controlling a character with enemies (with different stats/values) around the area. When the character is on the same grid as an enemy, the program initiates a battle function which contains large amounts of code that dictates how the battle works . However, since I want enemies to have different stats, I've been using structures in order to organize character/enemy stats.
For example:
struct Character { //character stats
int hp;
int atk;
int warpstk;
int heal;
int mana;
int money;
};
struct Enemy { //enemy stats
int mhp;
int matk;
int mwarpstk;
int mheal;
};
Character Player =
{
50, //Player.hp
10, //Player.atk
18, //Player.warpstk
12, //Player.heal
30, //Player.mana
50, //Player.money
};
Enemy Goblin =
{
40, //Goblin.mhp
10, //Goblin.matk
12, //Goblin.mwarpstk
8, //Goblin.heal
};
Enemy Dragon =
{
100, //Goblin.mhp
50, //Goblin.matk
70, //Goblin.mwarpstk
15, //Goblin.heal
};
//I want to modify the follow function so that it would change who the player is fighting depending on the grid placement.
void battleSequence(){
while (Player.hp > 0 && Goblin.hp > 0)
{
//randon battle code
}
int main()
{
if (player.x == goblin.x && player.y == goblin.y)
{
battleSequence();
if (Goblin.mhp < 1)
{
goblin.x = 25; //gave the goblin coordinates an out of bounds coordinate to leave loop
goblin.y = 25; //gave the goblin coordinates an out of bounds coordinate to leave loop
system("cls");
}
}
//Probably gonna do the same conditional for the dragon, but it entirely depends on the battle function
}
However, if I were to call the battle sequence function when my character is on the same grid as a goblin, how would I do it so it would be unnecessary to create a battle function of each and every enemy? How would I be able to avoid duplicate code lying around my program? Because so far, I have one function which uses only two variables for player stats: Player and Goblin, and I would hate to create another function for Player and Dragon, due to the length of the function (which is not shown here).
In other words, how would I be able to create ONE general battle sequence function that is accessable for every enemy, but I would only be fighting a "dragon" if I were on their grid, and only a "goblin" if I were on their grid?
(If you need more clarity, please do ask since it is pretty hard to comprehend my question. Ask if you need github repository of my current progress)
Could you simply make a base struct, called characterValues or something, which contains X and Y position, hp, att, Def, heal, and whatever else is common amongst all characters, or at least what's needed to perform a battle. Then the player struct contains an instance of this, as well as the enemy struct.
When you need to calculate the battles, you just need to compair player.charaterValues, with goblin.charaterValues or dragon.charaterValues. If two characters have the same X and Y value, then perform the battle with their health and attack data. Extra info can be stored seperte to this struct, like the players mana, or the enemy's loot data, etc.
If your writing this in c++, you should look upand read about inheritance if you haven't already, which can do some extra tricks along these lines. But with simple c, it can be done with nested structures.
Good luck with your game.
As already pointed out in the other answer, you must find out what all entities (players, goblins and dragons) have in common and create a "base struct", for example a struct CommonEntity, which is comprised of all common properties.
If you want the battle sequence to be strictly player vs. monster (or "enemy"), then it is not necessary for the player properties to be part of the struct CommonEntity. In that case, it would be sufficient for only all common monster properties to be in struct CommonEntity, and you could keep the player properties separate.
However, if monster vs. monster battles should also be possible, and if you want the same function to handle both types of battles, then it would probably be better to include all player properties in the struct CommonEntity, so that the battle function can treat players and monsters in exactly the same way and doesn't even have to know whether the combat participients are players or monsters.
The struct CommonEntity could look like this:
struct CommonEntity
{
//map coordinates
int x;
int y;
//combat stats
int hp;
int atk;
int warpstk;
int heal;
};
All of these properties are shared by both the player and all monsters. If mana can also used in combat, you might want to add it to the struct, and set it to zero for the monsters and nonzero for the players.
However, you probably want to be able to store additional information and the type of the additional information should depend on whether the entity is a player, goblin or dragon. For example, if it is a player, you want to be able to store the amount of money the player has. Therefore, you might want to use a union, like this:
struct AdditionalPlayerData
{
int money;
};
struct AdditionalGoblinData
{
//empty for now
};
struct AdditionalDragonData
{
//empty for now
};
struct Entity
{
//type of entity
enum class EntityType { player, goblin, dragon } type;
CommonEntity common;
union
{
AdditionalPlayerData player;
AdditionalGoblinData goblin;
AdditionalDragonData dragon;
};
};
Now, your struct is capable of storing both general information and additional information specific to the type of entity. You can always see which union field is valid by looking at the type member.
The function which evaluates the battle probably doesn't care about the additional information, it will only need to access the information contained in the common member. Therefore, you will no longer need a different function for handling different entity types, as the function probably won't even care about the type of entity. If it does care for some reason, it can still look up the type member and access the corresponding additional information in the union.
A more object-oriented approach to solving the problem would be the following:
You can use C++ inheritance by defining a common base class, class Entity, for all entities that exist in the game, for example class Player and class Enemy. Also, you can make class Enemy itself a base class for class Goblin, class Dragon, etc. You could then create an array (C-style or std::array) or std::vector of type class Entity * to reference all entities that exist in the game.
The functions which check for encounters and handle the actual battle then only have to deal with objects of type class Entity and do not care of what derived class these objects actually are. If these functions do need to determine the type of the entity, they can call a virtual function on the base class which will then automatically call the corresponding function in the derived class. In my example below, the functions handling the encounters and battles will call the virtual function GetEntityTypeName of class Entity to obtain the name of the entity type (i.e. "player", "goblin" or "dragon"), which they use to print a message about the encounter/battle to the screen.
#include <iostream>
#include <vector>
#include <memory>
#include <random>
class Entity
{
protected:
Entity() = delete;
Entity( int x, int y, int hp, int atk, int warpstk, int heal )
: x(x), y(y), hp(hp), atk(atk), warpstk(warpstk), heal(heal)
{}
public:
int GetX() { return x; }
int GetY() { return y; }
int GetHP() { return hp; }
int GetAttack() { return atk; }
int GetWarpStk() { return warpstk; }
int GetHeal() { return heal; }
void SetX( int newval ) { x = newval; }
void SetY( int newval ) { y = newval; }
void SetHP( int newval ) { hp = newval; }
//the following virtual function will call the derived class's
//function to retrieve the name of the entity type as a string
virtual const char* GetEntityTypeName() = 0;
protected:
//entity's coordinates
int x;
int y;
//entity's combat stats
int hp;
int atk;
int warpstk;
int heal;
};
//class Player inherits from class Entity
class Player : public Entity
{
public:
Player() = delete;
Player( int x, int y ) : Entity(x,y,50,10,18,12), mana(30), money(50) {}
//define additional functions
int GetMana() { return mana; }
int GetMoney() { return money; }
void SetMana( int newval ) { mana = newval; }
void SetMoney( int newval ) { money = newval; }
virtual const char* GetEntityTypeName() { return "player"; }
protected:
//define additional stats that are specific to player
int mana;
int money;
};
class Enemy : public Entity
{
protected:
Enemy() = delete;
Enemy( int x, int y, int hp, int atk, int warpstk, int heal )
: Entity(x, y, hp, atk, warpstk, heal)
{}
public:
//define additional functions here that are specific
//to all enemies, but not specific to certain enemy
//types (goblin, dragon, etc.)
//currently no additional functions
protected:
//define additional stats here that are specific to
//all enemies, but not specific to certain enemy types
//currently no additional stats
};
class Goblin : public Enemy
{
public:
Goblin() = delete;
Goblin( int x, int y ) : Enemy(x,y,40,10,12,8) {}
virtual const char* GetEntityTypeName() { return "goblin"; }
private:
//define additional stats here that are specific to goblins
//currently no additional stats
};
class Dragon : public Enemy
{
public:
Dragon() = delete;
Dragon( int x, int y ) : Enemy(x,y,100,50,70,15) {}
virtual const char* GetEntityTypeName() { return "dragon"; }
private:
//define additional stats here that are specific to dragons
//currently no additional stats
};
int MakeRandom( int min, int max, std::mt19937 &rng )
{
std::uniform_int_distribution<int> dis( min, max );
return dis( rng );
}
void PerformAttack( Entity &attacker, Entity &defender, std::mt19937 &rng )
{
int attack_strength = MakeRandom( 0, attacker.GetAttack(), rng );
std::cout << " " << attacker.GetEntityTypeName() << " hits " <<
defender.GetEntityTypeName() << " for " << attack_strength << " points\n";
defender.SetHP( defender.GetHP() - attack_strength );
}
void PerformBattleRound( Entity &e1, Entity &e2, std::mt19937 &rng )
{
Entity *first, *second;
//randomize which entity attacks first
if ( MakeRandom( 0, 1, rng ) == 0 )
{
first = &e1;
second = &e2;
}
else
{
first = &e2;
second = &e1;
}
//perform first attack
PerformAttack( *first, *second, rng );
if ( second->GetHP() <= 0 )
{
std::cout << " " << second->GetEntityTypeName() << " dies\n\n";
return;
}
else
{
std::cout << " " << second->GetEntityTypeName() << " has " <<
second->GetHP() << " HP remaining\n";
}
//perform second (counter) attack
PerformAttack( *second, *first, rng );
if ( first->GetHP() <= 0 )
{
std::cout << " " << first->GetEntityTypeName() << " dies\n\n";
return;
}
else
{
std::cout << " " << first->GetEntityTypeName() << " has " <<
first->GetHP() << " HP remaining\n";
}
std::cout << "\n";
}
void ProcessEncounters( std::vector<std::unique_ptr<Entity>> &entity_list, std::mt19937 &rng )
{
//this function does not only check encounters between player
//and enemies, but also enemies and enemies
int size = entity_list.size();
for ( int i = 0; i < size; i++ )
{
for ( int j = i + 1; j < size; j++ )
{
//check if both entities have the same coordinates
if (
entity_list[i]->GetX() == entity_list[j]->GetX() &&
entity_list[i]->GetY() == entity_list[j]->GetY()
)
{
//print information about encounter
std::cout <<
entity_list[i]->GetEntityTypeName() << " encounters " <<
entity_list[j]->GetEntityTypeName() << " at (" <<
entity_list[i]->GetX() << "," <<
entity_list[i]->GetY() << ")\n";
PerformBattleRound( *entity_list[i], *entity_list[j], rng );
}
}
}
}
int main()
{
//create a vector to contain all entities
std::vector<std::unique_ptr<Entity>> entity_list;
//seed the random number generator
std::random_device rd;
std::mt19937 rng( rd() );
//create player at coordinates (5,7)
entity_list.push_back( std::make_unique<Player>( 5, 7 ) );
//create goblin at coordinates (2,3)
entity_list.push_back( std::make_unique<Goblin>( 2, 3 ) );
//create goblin at coordinates (5,7)
entity_list.push_back( std::make_unique<Goblin>( 5, 7 ) );
//create goblin at coordinates (8,9)
entity_list.push_back( std::make_unique<Goblin>( 8, 9 ) );
//create dragon at coordinates (8,9)
entity_list.push_back( std::make_unique<Dragon>( 8, 9 ) );
ProcessEncounters( entity_list, rng );
}
}
The code above will print the following output:
player encounters goblin at (5,7)
goblin hits player for 1 points
player has 49 HP remaining
player hits goblin for 10 points
goblin has 30 HP remaining
goblin encounters dragon at (8,9)
goblin hits dragon for 2 points
dragon has 98 HP remaining
dragon hits goblin for 42 points
goblin dies
Due to the random number generator, the output will be different every time you run the program.
As you can see, I don't need an extra function for handling every type of entity, because the functions handling the encounter/battle do not even care about the type of entity they are dealing with.
Note that if you have 10,000 entities in the game, and if enemies can also encounter each other (not only the player), then it will take nearly 5 million(!) comparisons to compare every entity's coordinates with every other entity, as the time complexity is O(n2). Therefore, if you have that many entities, you might want to consider dividing the map into a grid of, for example, 100*100 tiles and keep a separate list of all entities that exist in every tile of that grid. That way, assuming 10,000 entities, you will only have to compare every entity with the coodinates of one other entity on average, instead of all other 10,000 entities. But as long as you have less than 100 entities in the game, it should not be a problem. Also, if enemies cannot encounter each other, then this will also not be a problem, as the time complexity will only be O(n) instead of O(n2).
I have a Base class Point (representing a 2D point in space) that is non-thread-safe for move operations; so I defined an inherited class LockedPoint that overrides 2 methods in the base class: moveTo and moveBy:
void Point::moveTo(float xPos, float yPos) {
x = xPos;
y = yPos;
}
void Point::moveBy(float xOff, float yOff) {
x += xOff;
y += yOff;
}
void LockedPoint::moveTo(float xPos, float yPos) {
MutGuard m(lock);
x = xPos;
y = yPos;
}
void LockedPoint::moveBy(float xOff, float yOff) {
MutGuard m(lock);
x += xOff;
y += yOff;
}
( where x and y = private member variables,
lock = a private mutex, and
MutGuard = typedef lock_guard<mutex> )
To visually see the problem with the "unlocked" Point, I wrote up a test routine:
void sleepForMS(long ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
void messWithPoint(Point& p, int type) {
float n = 1;
if (type) n *= -1;
for (long i = 0; i < 10000; i++) {
p.moveBy(n, n);
sleepForMS(rand() % (type ? 2 : 3));
if (i % 500 == 0)
std::cout << i << ":\t" << p << std::endl;
}
}
int main(int argc, char* argv[]) {
using namespace std;
Point p;
thread t1(messWithPoint, std::ref(p), 0);
sleepForMS(33);
thread t2(messWithPoint, std::ref(p), 1);
cout << "Started\n";
t1.join();
t2.join();
cout << p << endl;
}
With a Point, the resulting p is "corrupted", as expected (it should equal (0,0) by the end, and it doesn't). If I change p to a LockedPoint though, the base version of moveBy is still called (verified by print debugging).
I read up on method "overriding" (apparently more correctly called "method hiding"), and from what I understand, if the overriding method has the same signature as the base method, it hides the base version, and is called instead. Why then is the base method being called despite the 2 having the same signature? The only thing I can think of is because I'm specifying Point in messWithPoint's argument list, it's taking that literally and calling Point's version. If I change the signature to void messWithPoint(LockedPoint& p, int type), the resulting LockedPoint is (0,0), as expected. Shouldn't it "see" that the passed LockedPoint overrides the used method, and use the "least hidden" version?
If that's not how it works, is there a way to specify taking the base class, but having it use any available overridden versions?
The member functions are not virtual, so the functions in the class known at compile time are used.
However, for a simple class such as point, using virtual member functions or providing automatic mutual exclusion goes against the C++ idea of not paying for what you don't use.
Just copy points.
We were given a header and cpp file for our next project from which we need to derive a class. These two files should work properly "out of the box" but for some reason I'm getting these errors and clicking on the errors doesn't take me anywhere. I'm guessing it's a version problem since most of the books programs are of extension .cxx and we're working with .cpp, but I'm pretty new to programming so I don't know for sure. Also, most of the books other programs worked just fine in VS 2012. I'm using Visual Studio 2012 on Windows 7 64bit if that helps.
Errors:
1>MSVCRTD.lib(crtexe.obj) : error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup
1>Projects\Project 5\Debug\Project 5.exe : fatal error LNK1120: 1 unresolved externals
Here is the code for the header and cpp of our base class and thanks for the help guys:
Header:
#ifndef MAIN_SAVITCH_GAME
#define MAIN_SAVITCH_GAME
#include <queue> // Provides queue<string>
#include <string> // Provides string
namespace main_savitch_14
{
class game
{
public:
// ENUM TYPE
enum who { HUMAN, NEUTRAL, COMPUTER }; // Possible game outcomes
// CONSTRUCTOR and DESTRUCTOR
game( ) { move_number = 0; }
virtual ~game( ) { }
// PUBLIC MEMBER FUNCTIONS
// The play function should not be overridden. It plays one game,
// with the human player moving first and the computer second.
// The computer uses an alpha-beta look ahead algorithm to select its
// moves. The return value is the winner of the game (or NEUTRAL for
// a tie).
who play( );
protected:
// *******************************************************************
// OPTIONAL VIRTUAL FUNCTIONS (overriding these is optional)
// *******************************************************************
virtual void display_message(const std::string& message) const;
virtual std::string get_user_move( ) const;
virtual who last_mover( ) const
{ return (move_number % 2 == 1 ? HUMAN : COMPUTER); }
virtual int moves_completed( ) const { return move_number; }
virtual who next_mover( ) const
{ return (move_number % 2 == 0 ? HUMAN : COMPUTER); }
virtual who opposite(who player) const
{ return (player == HUMAN) ? COMPUTER : HUMAN; }
virtual who winning( ) const;
// *******************************************************************
// VIRTUAL FUNCTIONS THAT MUST BE OVERRIDDEND:
// The overriding function should call the original when it finishes.
// *******************************************************************
// Have the next player make a specified move:
virtual void make_move(const std::string& move) { ++move_number; }
// Restart the game from the beginning:
virtual void restart( ) { move_number = 0; }
// *******************************************************************
// PURE VIRTUAL FUNCTIONS
// *******************************************************************
// (these must be provided for each derived class)
// Return a pointer to a copy of myself:
virtual game* clone( ) const = 0;
// Compute all the moves that the next player can make:
virtual void compute_moves(std::queue<std::string>& moves) const = 0;
// Display the status of the current game:
virtual void display_status( ) const = 0;
// Evaluate a board position:
// NOTE: positive values are good for the computer.
virtual int evaluate( ) const = 0;
// Return true if the current game is finished:
virtual bool is_game_over( ) const = 0;
// Return true if the given move is legal for the next player:
virtual bool is_legal(const std::string& move) const = 0;
private:
// MEMBER VARIABLES
int move_number; // Number of moves made so far
// STATIC MEMBER CONSTANT
static const int SEARCH_LEVELS = 4; // Levels for look-ahead evaluation
// PRIVATE FUNCTIONS (these are the same for every game)
int eval_with_lookahead(int look_ahead, int beat_this);
void make_computer_move( );
void make_human_move( );
};
}
#endif
CPP:
#include <cassert> // Provides assert
#include <climits> // Provides INT_MAX and INT_MIN
#include <iostream> // Provides cin, cout
#include <queue> // Provides queue<string>
#include <string> // Provides string
#include "game.h" // Provides definition of game class
using namespace std;
namespace main_savitch_14
{
//*************************************************************************
// STATIC MEMBER CONSTANTS
const int game::SEARCH_LEVELS;
//*************************************************************************
// PUBLIC MEMBER FUNCTIONS
game::who game::play( )
// The play function should not be overridden. It plays one round of the
// game, with the human player moving first and the computer second.
// The return value is the winner of the game (or NEUTRAL for a tie).
{
restart( );
while (!is_game_over( ))
{
display_status( );
if (last_mover( ) == COMPUTER)
make_human_move( );
else
make_computer_move( );
}
display_status( );
return winning( );
}
//*************************************************************************
// OPTIONAL VIRTUAL FUNCTIONS (overriding these functions is optional)
void game::display_message(const string& message) const
{
cout << message;
}
string game::get_user_move( ) const
{
string answer;
display_message("Your move, please: ");
getline(cin, answer);
return answer;
}
game::who game::winning( ) const
{
int value = evaluate( ); // Evaluate based on move that was just made.
if (value > 0)
return COMPUTER;
else if (value < 0)
return HUMAN;
else
return NEUTRAL;
}
//*************************************************************************
// PRIVATE FUNCTIONS (these are the same for every game)
int game::eval_with_lookahead(int look_ahead, int beat_this)
// Evaluate a board position with lookahead.
// --int look_aheads: How deep the lookahead should go to evaluate the move.
// --int beat_this: Value of another move that we’re considering. If the
// current board position can't beat this, then cut it short.
// The return value is large if the position is good for the player who just
// moved.
{
queue<string> moves; // All possible opponent moves
int value; // Value of a board position after opponent moves
int best_value; // Evaluation of best opponent move
game* future; // Pointer to a future version of this game
// Base case:
if (look_ahead == 0 || is_game_over( ))
{
if (last_mover( ) == COMPUTER)
return evaluate( );
else
return -evaluate( );
}
// Recursive case:
// The level is above 0, so try all possible opponent moves. Keep the
// value of the best of these moves from the opponent's perspective.
compute_moves(moves);
assert(!moves.empty( ));
best_value = INT_MIN;
while (!moves.empty( ))
{
future = clone( );
future->make_move(moves.front( ));
value = future->eval_with_lookahead(look_ahead-1, best_value);
delete future;
if (value > best_value)
{
if (-value <= beat_this)
return INT_MIN + 1; // Alpha-beta pruning
best_value = value;
}
moves.pop( );
}
// The value was calculated from the opponent's perspective.
// The answer we return should be from player's perspective, so multiply times -1:
return -best_value;
}
void game::make_computer_move( )
{
queue<string> moves;
int value;
int best_value;
string best_move;
game* future;
// Compute all legal moves that the computer could make.
compute_moves(moves);
assert(!moves.empty( ));
// Evaluate each possible legal move, saving the index of the best
// in best_index and saving its value in best_value.
best_value = INT_MIN;
while (!moves.empty( ))
{
future = clone( );
future->make_move(moves.front( ));
value = future->eval_with_lookahead(SEARCH_LEVELS, best_value);
delete future;
if (value >= best_value)
{
best_value = value;
best_move = moves.front( );
}
moves.pop( );
}
// Make the best move.
make_move(best_move);
}
void game::make_human_move( )
{
string move;
move = get_user_move( );
while (!is_legal(move))
{
display_message("Illegal move.\n");
move = get_user_move( );
}
make_move(move);
}
}
You don't have int main() function. Every program must have this.
http://en.wikipedia.org/wiki/Main_function
They may compile properly out of the box but, without a main function, they won't link or run.
You need to combine them with your own source code, which I would expect to contain at least a main function which used those classes somehow. As a first step, start with:
int main(void) {
return 0;
}
That won't do anything useful but it should get you over the immediate link errors.
I have a C++ Qt static library with two classes - dataprocthread and calcvalue. In the first one, when I call a method from an instance of calcvalue, pointer this (which references to dataprocthread class) suddenly becomes null.
This is dataprocthread.h:
class DataProcThread : public QThread
{
Q_OBJECT
public:
DataProcThread(int sarray[9], int val);
signals:
workDone(int result);
private:
int _sarray[9];
int _val;
void run();
};
dataprocthread.cpp:
DataProcThread::DataProcThread(int sarray[9], int val)
{
for (int x = 0; x < 9; x++)
{
_sarray[x] = sarray[x];
}
_val = val;
}
void DataProcThread::run()
{
CalcValue* cv = new CalcValue();
int myval = 0;
for (int i = 0; i < 100; i++)
{
myval = cv->processData(this->_val);
if (this->_sarray[0] != myval)
{
//do something
}
}
emit workDone(intValue);
}
calcvalue.h:
class CalcValue
{
public:
CalcValue();
int processData(int someval);
};
calcvalue.cpp:
CalcValue::CalcValue()
{
}
int processData(int someval)
{
//do something and return int value
}
When I run this code, it suddenly recieves signal "Segmentation fault". Using debugger, I found that the problem in DataProcThread::run() function: when I call cv->processData function, all works good. But on the next line (if (this->_sarray[0] != myval)), this pointer becomes null (I can see it in Locals window), therefore, I can't access this->_sarray variable.
In case it is important, that's how I start the thread (from another class outside my library):
DataProcThread* thread = new DataProcThread(sarray, val);
QObject::connect(thread, SIGNAL(workDone(int)), this, SLOT(collectWork(int)));
thread->start();
What I am doing wrong?
The problem, as was pointed out in comments, took place in processData function: it was writing over memory.
I simply used strncpy instead of strcpy and the problem gone (here is a good explanation about this).
For simple data like ints or constants something like this would work
#include <iostream>
#include <vector>
using namespace std ;
typedef void FuncInt (int) ;
class GraphElementProto {
public:
void add (FuncInt* f)
{
FuncVec.push_back (f) ;
} ;
void call()
{
for (size_t i = 0 ; i < FuncVec.size() ; i++)
FuncVec[i] (i) ;
} ;
private:
vector<FuncInt*> FuncVec ;
} ;
static void f0 (int i) { cout << "f0(" << i << ")" << endl ; }
static void f1 (int i) { cout << "f1(" << i << ")" << endl ; }
int main() {
GraphElementProto a ;
a.add (f0) ;
a.add (f1) ;
a.call() ;
}
So now imagine we work with some data buffer like char.
We have threads that wait for data pointers and on appearance of that pointers want to change data at the same time. So we would need to create copy's of that data and give to each subscriber pointer to his own copy.
So how to do such thing? (sorry C++ nube - code is only thing I can understand)
Consider the similarities between each node of the graph that you describe and create a class for them (class GraphElement below). It should encapsulate its relationship to its child nodes, and it should do something locally with any incoming messages (function localAction). You should then derive classes that represent specific variations - such as the image generator you mention - and change the local action. Each class may take a copy of the original message, or change it as you need.
In my example code here I have create the default graph node - GraphNode - and made it simply print incoming messages before passing them to its child nodes. I have used a string object for the incoming message - a lot nicer than a plain old C char * array [example: you can derive a string from char * when message2 is created in the code below]. I have made those object const references as its cheap, fast, and never changes the original.
I have derived a class called CatNode as an example of the variation you need. Objects of this type contain a history of all messages, and print out that history when a new message arrives. Not very useful - but a good example none the less. This demonstrates how each node may do anything to a copy of the original message - rewrite localAction(). It also passes that history to any child nodes - rewrite incomingMessage with a change to the parameter passed to deliverMessage().
#include <vector>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::vector;
using std::string;
class GraphNode
{
public:
GraphNode( string & name ) : mChildren(), mName(name) {}
GraphNode( const char * const name ) : mChildren(), mName(name==NULL?"":name) {}
virtual void incomingMessage( const string & str ) {
localAction(str); // This node is to do something.
deliverMessage(str); // Child nodes are to do things too.
}
void addChild( GraphNode * child ) {
mChildren.push_back( child );
}
protected:
// Rewrite this function for child classes who are to do different things with messages.
virtual void localAction( const string & str ) {
cout << mName << " : " << str << endl;
}
void deliverMessage( const string & str ) {
vector<GraphNode*>::iterator itr = mChildren.begin();
for( ; itr != mChildren.end(); ++itr )
(*itr)->incomingMessage(str);
}
// Data members
vector<GraphNode*> mChildren;
string mName;
}; // [ GraphNode ]
class CatNode : public GraphNode
{
public:
CatNode( string & name ) : GraphNode(name), mHistory() {}
CatNode( const char * const name ) : GraphNode(name), mHistory() {}
virtual void incomingMessage( const string & str ) {
localAction(str);
deliverMessage(mHistory);
}
protected:
virtual void localAction( const string & str ) {
mHistory += str;
cout << mName << " : " << mHistory << endl;
}
// Data members
string mHistory;
}; // [ CatNode ]
main()
{
// root -> childA
GraphNode root("Root Node");
CatNode childA("Child A");
root.addChild( &childA );
root.incomingMessage("Message1");
cout << endl;
// root -> childA -> childC
// \-> childB
GraphNode childB("Child B");
root.addChild( &childB );
GraphNode childC("Child C");
childA.addChild( &childC );
string message2("Message2");
root.incomingMessage(message2);
} // [ main ]