Morning, y'all,
This is just some rambling. Nothing terribly important, as I sort out my own
thoughts on the subject...
I think I've basically got most of the hierarchical stuff down.
Here's a sample usage of the current API:
RandomDungeon top( 60, 60, "dungeon" ); // init's a random dungeon.
DungeonElement *de = new Room();
de->setName( "roomX" );
de->move( 27, 17 );
de->add( new Wall( 0,0,6 ) );
de->add( new Wall( 0,0,4, DungeonElement::Vertical, "wall2" ) );
top.add( de );
top.toCerr(); // prints to cerr.
class 'RandomDungeon' does not yet exist, but is a hypothetical
DungeonElement subclass which initializes some random dungeon parameters at
instantiation.
I'm not at all happy with what I've done with save/load, so I'm going back to
the drawing board on that. It's gotten too complex to be manageable, so I'm
gonna rewrite that from scratch. The problem lies not so much in the
conversion from hierarchical data to a flat board or data format (like the
ini-style file approach), but in the reversing of that. If we can't read back
our own output format, I see that as a problem. If we restricted outselves to
a flat data structure the load/save would be simpler, but the whole thing
would not be nearly as extendable or easy to use. Right now the runtime
flexibility is really good, but the method of loading and saving those states
sucks, largely because the data format has to be flat, while the structure is
not.
I can envision and approach like this, but back-parsing it'd be a real bitch:
dungeon{
name=My Favorite Dungeon;
randomSeed = 42;
room {
name=Dwarvish Beer Hall;
width=5;
height=7;
x=17;
y=19;
wall {
name=1;
x=0;
y=1;
length=7;
orientation=1; // vertical
door {
name=1;
point=3; // where on the wall it is.
}; // wall
}; // room
}; // room
}; // dungeon
Writing into that format is no problem. Reading that format back is the
problem. The parser wouldn't be much fun to write, mainly because it'd have
to do so much syntax checking. Plus it'd be limited to a hard-coded set of
class types (the tops of the classname{} blocks - the properties within them
would still be _completely_ dynamic). We could use a classloader to
completely get around that problem, but the only classloader code I know of
is GPL (Peter doesn't want GPL in code), and I don't think it'll work under
Windows. Actually, in Perl I could parse it with little problem, I think, but
in C... gimme a week, maybe.
I wonder how much work it would be to port the Perl-Compatible Regular
Expressions support from the PHP code... THAT would be useful. I don't know
if PHP's license allows stealing code from it. I'll have to look into that.
Anyway...
Note that in the above sample hierarchy, all children use parent-relative X/Y
values. These are converted at render-time to the rendering object (the
DMGrid, which I have not yet built, but is on my list). This is MUCH simpler
to maintain, and allows moving of whole branches of objects without having to
deal with child positions relative to the new target. For example, we could
copy the wall{} block from above into another room, or the top-level dungeon,
and it'd still be a valid object at the same position, relative to the new
parent. Imagine having to move all your walls every time you wanted to move a
room!
I think I'm going to skip the load/save for tonight and move on to the
rendering. That sounds like more fun right now. I imagine this of consisting
of a DMGrid object, with a very simple API:
DMGrid( int w, int h )
void set( int x, int y, int val )
int get( int x, int y )
and other such stuff. Most of this will simply be swiped from the current DM
object. The special one will be:
void render( DungeonElement &de )
This will call de.renderTo( *this ), which is defined in DungeonElement, as:
void renderTo( DMGrid &gr, int xoffset=0, int yoffset=0 );
This gets recursively called on all children of this DungeonElement object,
adjusting the x/y offset as we climb down the tree. That x/y is where the
children consider to be their 0/0 starting point for all rendering
operations. The DMGrid will allow these rendering operations to go outside
it's bounds - it will simply ignore such requests. This allows us to have
elements which "run off" the DMGrid, like a lake (hey, how about a
RiverCrawler object, to make underground lakes and streams?).
Thus the above C++ code could simply add this code to render the whole
dungeon to the console, for example:
DMGrid gr( top ); // will get his size from top's size.
top.renderTo( gr );
cerr << gr; // dump output to the console
And adding new terrain types will not affect this code at all, like:
de = new LavaPit( 2,2 ); // DungeonElement subclass
room->add( de, 3, 4 ); // 3,4 == position
But, see, theres' the catch: this flexibility dies when we can't load these
new classes dynamically from our saved data. Our parser would have to know
about type "lavapit". We could get around that 100% with XML and a
classloader, but I don't know of any XML libs which are widely available on
Windows and Unix, either. This is exactly the approach QUB uses to
dynamically determine data types from XML data and instantiating the correct
classes, searching for them as DLLs if needed. We don't NEED XML to do it,
but we need some format which we can parse. Of course, we can write our own
parser, aber das dauert eine Menge Zeit. And we'd still need a classloader to
make it extenable without having to update the parser with each class.
Oh, Hey, Pete! I just realized: with this rendering approach, QUB can offer
view-as-the-crawlers-create rendering simply by subclassing GMGrid() and
catching all the set()'s so I can update the pixmaps. Cool.
Anyway...
See y'all!
----- Stephan Beal - st...@wa...
http://qub.sourceforge.net - http://stephan.rootonfire.org
http://dungeonmaker.sourceforge.net
"Now I'm not normally the kind of person who likes to trespass, but
sometimes you just find yourself over the line." -- Bob Dylan
|