On Jan 31, 5:47 am, Ben Bacarisse <
ben.use...@bsb.me.uk> wrote:
OK, then, here goes. Hopefully I've made the issues and usage more
clear.
What I'm trying to represent is a "web of levels" for a game. To
visualize
this, you can use the following: consider a string of beads, each bead
linked to the next by a chain segment. These strings form what I call
the
"branches" of the web, with the individual beads being levels and the
chain segments being connections between levels. Now, these branches
can
be linked by chain segments themselves, and that forms the web. As can
be
seen, this is essentially a graph: beads are nodes, chain segments are
edges. (In my implementation, there are actually _two_ directed edges
in
a chain segment, to represent portals going one or the other way from
the
levels in question.) But there's the additional structure of the
branches
on top. Thus, "weird" kind of graph. And that's where my problem comes
in:
trying to figure out just how to implement that.
On the thread, it was suggested I use an ordinary graph type to
represent
it, and add the branch structure on top somehow. (One even suggested
to
take a graph type and make a derived class from it.) Makes sense,
since
it is a kind of graph, after all.
But that last bit is the rub: how to implement that branch structure.
I
did it as mentioned in the OP. Now, here's a code segment for the
class
definition I've got, if it'll help any, at the bottom. I can't give
the
full implementation as it's too long to put in a post. I suppose I
could
email you the code snippet if you want.
Now, the troubles (partially revised) (and I have to use text to give
explanations!):
1. I use a class "LevelBranch" to represent the branches, that stores
its nodes in a passed-to graph. Is this a good idea? After all, you
can
dink with the branch directly by dinking with the nodes in the graph
structure, without going through the branch's interface. Although,
LevelBranch is only used inside LevelWeb.
2. When such a branch object is created, the constructor adds new
nodes
in the graph and the destructor then deletes them. Is this the usual
way
this'd be done? To me, it makes sense, but the trouble here is this
thing
is an overlay on the graph, thus why I feel iffy: we could also
construct
the nodes outside, and then apply the branch as an overlay on top of
them.
Which one is better? The destroy- nodes-on-destructor thing is iffy
because we may try constructing copies of the branch object to pass by
value, and when temp copies get destroyed, trouble. Right now, I just
arbit
that you shouldn't pass it by value. Is that OK to make those kind of
arbitrary "user-must- understand" restrictions? Not that it matters
much,
since the way this is done now, the branch is a private buried
inside the LevelWeb.
3. And speaking of that last bit, I find myself duplicating certain
member
functions: e.g. there is a "retrieveMap" function in both LevelBranch
and
LevelWeb, and the one in LevelWeb calls the one in LevelBranch. Is
there a
way to eliminate that? I thought of a special "accessBranch" function
(which may mean we need to make LevelBranch public) that would return
a
reference to the branch in question, and then we can call
LevelBranch's
members right there, but that raises the possibility of someone doing
something like:
web.accessBranch(5) = LevelWeb::LevelBranch(<blah>);
BAD!
4. The way LevelBranch is implemented right now, as you can see, is
that
it contains a list of iterators to the nodes in the graph. The problem
is
that with this kind of graph, these iterators are rendered invalid by
deletion. Thus, when we destroy a LevelBranch, all the others on that
graph lose their iterators and those iterators need to be re-updated
again
by a special "recalibrate()" call. However, that means that wherever
we
get rid of LevelBranches, we'd better be sure to have access to ALL of
those on the graph so we can reset them. This isn't a problem, of
course,
in the level web implementation, because we do -- they're all there in
the
branch array. But the problem is this "feels" wrong, and I'm not sure
what
to do. The iterators are going to get invalidated, they are going to
HAVE
to be updated. Unless we should just switch to a different kind of
graph
implementation? E.g. like how if we used Boost's graph we could have a
graph that can use an std::list for its vertex list. This kills random
access to the nodes, though with a pre-stored grid of iterators, this
isn't really a problem here.
5. How to identify the nodes, so that the branch knows which nodes
belong to it upon recalibration. Currently, I have it so that when
a branch is created, the user specifies a branch ID code for that
branch.
These ID codes are meant to be user-specifiable since they're there
to easily tag branches for later access in an index-independent
fashion
(so they survive branch deletions while indices do not). Now, I'm
going to
have a mechanism that ensures these codes are unique (the web
remembers
which IDs have been used so it can ensure that new IDs don't clash),
though
that isn't implemented in the current snippet, as I'm still testing
the
thing, but I want to know if this approach is good before I finish it
out
or I should just pitch it. But that delegates the responsibility of
making
sure IDs are unique to the Web, yet it "feels" like it should be a
feature
of Branch. So, I wondered if I should use something else, like a
unique-ID
counter, inside Branch that specifies special "node ID" codes distinct
from
the user-passed branch IDs.
What should I do about these?
--- Code snippet ---
/* Level web, which stores a complete collection of levels
representing
* a single game location.
*/
class LevelWeb {
private:
/* Level element. This is used to form the nodes of the level web's
graph. */
class LevelElement {
private:
int branchID; /* ID code of branch this level
belongs
* to.
*/
int levelNumber; /* Level number of this level in
its
* associated branch.
*/
LevelGenerator *levelGenerator; /* Level generator used to
generate
* this level.
*/
bool isGenerated; /* Flag indicating whether this
level
* has been generated.
*/
std::string fileName; /* Filename of file to store map
in */
public:
LevelElement(); /* Default constructor */
LevelElement(int branchID_, int levelNumber_,
LevelGenerator *levelGenerator_,
std::string fileName_); /* Construct to specified parameters */
~LevelElement(); /* Destructor */
int getBranchID(); /* Get this level's branch ID
code */
int getLevelNumber(); /* Get this level's level number
*/
void setLevelNumber(int); /* Set this level's level number
*/
bool isLevelGenerated(); /* Returns whether this level has
been
* generated.
*/
LevelMap *retrieveMap(); /* Retrieve the level map.
Generates
* the map if it's not already generated
* or there's no map stored.
*/
CcmbsError saveMap(LevelMap &lm); /* Save a level map to the file
*/
};
/* Level connection data. This is stored in the graph's edges. */
struct LevelConnectionData {
PortalType portalType; /* Type of portal used for this
* connection.
*/
/* Constructors */
LevelConnectionData() : portalType(PORTAL_UPSTAIRS)
{}
LevelConnectionData(PortalType portalType_) :
portalType(portalType_)
{}
};
/* Graph data types */
typedef Graph<LevelElement, LevelConnectionData> WebGraph;
typedef Graph<LevelElement, LevelConnectionData>::NodeIterator
WebGraphNodeIterator;
typedef Graph<LevelElement, LevelConnectionData>::EdgeIterator
WebGraphEdgeIterator;
/* Level branch. The web is composed of branches, linked together in
* the underlying graph.
*/
class LevelBranch {
private:
int parentWebID; /* Parent web ID code */
int branchID; /* Branch ID code. Must be unique
* for each branch in the web.
* Used to identify the branch
* _and_ to tag the nodes in the
* graph.
*/
std::string branchAbbrev; /* Abbreviated description of
this
* branch.
*/
std::string branchDescriptionText; /* Text description of this
branch. */
WebGraph *parentGraph; /* Graph containing the nodes
comprising
* the branch.
*/
std::vector<WebGraphNodeIterator> nodes; /* Graph nodes containing
the
* levels in the branch.
*/
public:
LevelBranch(); /* Default constructor */
LevelBranch(int parentWebID_, int branchID_,
std::string branchAbbrev_,
std::string branchDescriptionText_,
WebGraph *parentGraph_,
LevelGenerator *levelGenerator,
PortalType fwdPortalType,
PortalType revPortalType,
std::size_t numLevels); /* Construct to specified parameters */
~LevelBranch(); /* Destructor */
int getBranchID(); /* Get branch ID */
std::size_t getNumLevels(); /* Get number of levels in this
branch */
std::string getAbbrev(); /* Get abbreviated description of
this
* branch
*/
std::string getDescriptionText(); /* Get branch description text
*/
bool isLevelGenerated(std::size_t which); /* Returns whether a
given level has
* been generated.
*/
LevelMap *retrieveMap(std::size_t which); /* Retrieve a level map
from the
* branch
*/
CcmbsError connectTo(LevelBranch &dstBranch,
PortalType portalType,
std::size_t srcLevelNum,
std::size_t dstLevelNum); /* Connect this branch one-way to
* another branch.
*/
void recalibrate(); /* Recalibrate this branch to the
* parent graph. Necessary after
* removing nodes from the parent
* graph (or any other operation that
* invalidates graph iterators).
*/
};
int webID; /* Web ID code */
std::string webAbbrev; /* Abbreviated description of
this web */
std::string webDescriptionText; /* Text description of this web.
*/
WebGraph graph; /* The underlying graph
containing all
* the level data.
*/
std::vector<LevelBranch *> branches; /* The level branches, which
are defined
* on the graph.
*/
/* Routines defined in levelmgmt.cpp */
std::size_t searchForBranch(int branchID); /* Private member:
searches for a
* level branch with a given ID code
* and returns its index (or -1 if
* no branch found)
*/
public:
/* Routines defined in levelmgmt.cpp */
LevelWeb(); /* Default constructor */
LevelWeb(int webID_,
std::string webAbbrev_,
std::string webDescriptionText_); /* Construct with
specified parameters */
~LevelWeb(); /* Destructor */
void addBranch(int branchID,
std::string branchAbbrev,
std::string branchDescriptionText,
LevelGenerator *levelGenerator,
PortalType fwdPortalType,
PortalType revPortalType,
std::size_t numLevels); /* Add a new level branch to this web. */
CcmbsError removeBranch(std::size_t branchIdx); /* Remove a level
branch from the
* web.
*/
CcmbsError removeBranchByID(int branchID); /* Remove a level branch
indicated by
* its ID code.
*/
CcmbsError connectBranches(std::size_t branchIdx1,
std::size_t branchIdx2,
std::size_t levelNum1,
std::size_t levelNum2,
PortalType fwdPortalType,
PortalType revPortalType); /* Connect two level
* branches
* together.
*/
CcmbsError connectBranchesByID(int branchID1, int branchID2,
std::size_t levelNum1,
std::size_t levelNum2,
PortalType fwdPortalType,
PortalType revPortalType); /* Connect two level
* branches,
* indicated by
* their ID codes.
*/
LevelMap *retrieveMap(std::size_t branchIdx, std::size_t levelNum); /
* Retrieve a level map. */
LevelMap *retrieveMapByID(int branchID, std::size_t levelNum); /*
Retrieve a level map
* with branch indicated by
* its ID code.
*/
};
---