Archive for May, 2007

New String Functions

Thursday, May 31st, 2007

I’ve written new string functions based on std::string. They’re the typical case insensitive compare, check prefix, compare with list of names, get first argument, get last argument, and other string manipulation functions you’d normally see used with a MUD. The difference is: They’re not very prone to buffer overflows and pointer errors because they’re using std::string and it’s safer.

Not everything uses std::string natively yet - there’s still too much char * for my comfort.

Next tasks:

- Testing the string functions to see whether they work as I’d expect.
- Running the whole thing through valgrind to see whether I can catch any memory (stack-smashing) errors.

This new incarnation won’t be crashproof (software never is), but the goal is to have it never crash “randomly” like Basternae 2 did. I have a lot of good command tracing and error logging code and have written quite a bit more over the past few weeks, so if something explodes it should tell me exactly what and where.

Even Higher Warning Levels

Tuesday, May 29th, 2007

When compiling code in MS Visual Studio .Net I always turn warning levels up to 4 (/W4).  I’ll end up getting some warnings about things I don’t care about, such as converting an int to true or false when it’s something I did on purpose or not referencing a formal parameter of a function, but it’s still better for spotting potential problems.

For example, one warning that many people often ignore but can be a big problem is “C4706: assignment within conditional expression”.  It’s the difference between:

if( ( x = a ) )
{
do something;
}

int x = a;
if( ( x ) )
{
do something;
}

Both pieces of code do the same thing: Set the variable X equal to A and then check whether X is nonzero.  The problem with the first piece is that someone reading your code has no idea whether you meant to assign A to X or check whether X is equal to A.  Even worse, one small typo and you could be doing assignment when you meant comparison.  It’s better to break it into two statements like the second piece of code so you and everyone else knows what you intended to do AND that it actually happens the way you intend.

When compiling using g++ on Linux, I’ve always used -Wall.  Since around 1995 I’ve been using that thinking it would warn me about everything it saw.  There are a few things that Visual Studio would warn me about that g++ wouldn’t catch and the more it happened the more I started to wonder why.  Cue the documentation.

AHA! While browsing the commandline parameter list for the GNU compiler, I found two interesting switches: -Wextra and -pedantic.

If you use those two switches, you’ll get what Visual Studio gave you and more.  Much more.  The bad news: I’m doing far too many ‘deprecated string conversions’.  The good news: I’ve spotted even more potential problems and can fix them before they become big problems.

Some people think warnings are just annoyances.  It’s a better idea to treat them as run-time errors.  It might be more of a pain in the butt when writing code, but it’ll help in the long run.   Thank your nitpicky compiler, it’s trying to protect you.

It’s amazing what you can do when you read the documentation…

Too Many Levels

Monday, May 28th, 2007

I’ve played on a handful of MUDs, and just about every one of them has regular player levels from 1 to 50, especially those from the family tree of Basternae. I’ve always thought that was just too many. Although the levels from 1 to 20 have usually been pretty quick and kind of fun, especially if you’re learning to play a new class, it soon starts to become a grind. Levels 30 to 40 always have been especially grind-y and really not very much fun.

The “exp grind” is a part of experience-level-based games. That’s not going to change anytime soon.

However, I’m finally getting rid of some of those extra levels. The level cap for Basternae 3 will be 40. All of the skills, spells, abilities, etc. have been compressed, so you’ll get new skills and spells every 4 levels instead of 5.

I’m not lowering mob levels, at least not yet, so some things will be a lot more challenging and downright dangerous. Mobs may need to be adjusted, and they might be fine how they are, but the trick with getting things balanced out is to make your major changes first so that the minor adjustments can be made around them as things settle into place.

Even with the decrease in number of levels, I’ve made experience level progress a lot steeper. Expect to need slightly more experience to get to level 40 than you needed to get to 50. The goal here is not to be able to get a character to level 40 in a day, or even under a week.

It’s generally been thought that there really isn’t much to the game until you hit level 50 (or 46, depending on your outlook). That’s one of the things I’m trying to change here.

Repop Points

Monday, May 28th, 2007

Setting up hometown repop points for the various races and classes in Basternae 2 was a real pain.

We had, in code, a two-dimensional array that would be referenced to get the room number for each particular race/class combination. Any special cases where someone had a choice or more than one hometown had to be hand-coded. If an area was added or removed, the code would have to be touched. This invariably resulted either in zone admins editing code (a bad idea), or in zone admins waiting for a coder to add their changes (better idea, but slower).

I’ve created a way to automatically generate hometown lookups based on areas loaded and give a new player a choice of which hometown to take when they create a character. It’s simple enough in theory: each zone can list the races that can choose it as a hometown and lists the room numbers where the various classes respawn. When the zones load, the MUD builds a list and everything is spiffy, no code editing involved.

Behind the scenes it’s a little complex. I’m using a two-dimensional vector of lists that point to integers ( std::vector< std::vector< std::list<int *> * > * > ). The standard library is awesome and makes life much easier, but it can result in excessive punctiation, as seen in the previous sentence.

I really prefer moving as much as possible out of code and into configuration files. I mean, isn’t it silly to have to recompile to, say, make a troll mercenary spawn one room to the left?

SourceMonitor Update

Sunday, May 27th, 2007

I just love these statistics tools…

Files: 127
Lines: 116,320
Statements: 60,630
% Branches: 29.5
% Comments: 8.5
Class Definitions: 50
Methods/Class: 7.04
Average Statements/Method: 10.8
Max Complexity: 477
Max Depth: 8
Average Depth: 1.87
Average Complexity: 11.71

The number of files, class definitions, and methods per class has increased, while the statements per method and average complexity has decreased.

XML Files

Saturday, May 26th, 2007

Traditionally MUDs I’ve seen have used raw text files for player files. Some might use binary, and some might compress them into a gzipped format.

There’s a problem with using this sort of file: in general, they are written or read a single line at at time. In Basternae 2 we had a significant problem with player file corruption. If the save or load engine had a problem with one of the values found in the file, you were pretty much screwed. If this happened during a file save, you pretty much had to restore the player file from a backup and hope that the backup was recent.

In order to banish this problem to the realms of oblivion, I’m incorporating the XML-based file save/load code that I used on my AlgoRhythmia 3 application. The difference with this code is that instead of opening the file and writing it line by line, dealing with values as they come, instead it creates an XML data store in memory and populates that with the values, and then serializes it to disk after all the values are checked.

So, intstead of having a half-written player file if the values are bad, it’ll be an all-or-nothing. If the data store fails to build, the file won’t be written, so instead of losing up to a week worth of play data, at most about 15 minutes would be lost, and that assumes the player hadn’t picked up or dropped any items in that time.

Once I have this code done I may give serious thought to setting up all of the game files to use XML. In addition to the build-then-save, XML also some built in checks and guards, so a typo in a hand-edited file would be much less likely to destroy the universe.

The downside: XML files are larger and slower.

The other upside: Data files written in XML can be easily processed by other applications, such as a web script that posts dynamic game stats on a page.

Project Line Counter Update

Saturday, May 26th, 2007

I’ve done a lot of writing, rewriting, replacing, and rearranging lately. Here are the current totals:

115,384 lines total
93,783 lines of code
9,859 lines of comments
2,071 mixed (code + comment) lines
13,813 blank lines

I guess you could call that a small but not quite negligible increase.

WinMerge

Friday, May 25th, 2007

One of my biggest complaints when booting into Linux is that I don’t have access to a merge tool quite as nice as WinMerge. The current version of WinMerge doesn’t run under Wine, but apparently an older version, 2.0.2, will.

Some things are optional when writing code, but a good merge tool is not one of them. WinMerge is the perfect example of what a merge tool should be.

The Meld diff viewer is the best merge tool that I’ve seen on Linux, but it’s just a bit less usable than WinMerge.

SourceMonitor

Sunday, May 20th, 2007

I found a code analysis and metrics program called SourceMonitor today. I downloaded version 2.3.6.1 and fed it the source files for Basternae. Here’s what it came up with:

Files: 120
Lines: 114,121
Statements: 59,490
% Branches: 29.2
% Comments: 8.6
Class Definitions: 43
Methods/Class: 4.41
Average Statements/Method: 14.2
Max Complexity: 477
Max Depth: 8
Average Depth: 1.87
Average Complexity: 12.41

The check_command() function in the command interpreter is the most complex function. The complexity rating of 477 comes from the fact that it has 477 different branches of execution - essentially 1 for each different command the MUD understands.

Since this code is hybrid C and C++, the average number of statements per method metric doesn’t giev a full picture of things. It’s still pretty interesting to see the statistics on things.

SourceMonitor keeps track of “checkpoints” and lets you look at how code evolves over time, so it’ll be interesting to fire it up and post statistics now and then.

Astyle Rocks!

Friday, May 18th, 2007

I’m EXTREMELY picky about the formatting of my code. There are a few commonly-known formats for code: ANSI, Kerninghan & Ritchie, GNU, Linux, and Java.

I have a a very strong preference for ANSI style, but with one minor modification: I like my switch statement case labels to be indented. That’s not a strong preference, as long as the brackets look right.

This drives me nuts (ARRR!):

if( a == b ) {
return;
}

This makes me happy:

if( a == b )
{
return;
}

So, the fact that a lot of code has the former annoys me every time I see it, and I either change it, or frown and move on.

Today I discovered an excellent program, astyle:

http://astyle.sourceforge.net/

It lets you choose what formatting to give your code, goes through it all, and changes it to be however you want. The way I think code should be is:

astyle –style=ansi -s4 –indent-switches

All of a sudden it looks pretty, and I can read it without twitching. Not only does it do everything I mentioned Visual Studio doing in my May 15th post, it also rearranges brackets to be where I want them.

It’s very unlikely that I’ll bring on any programmers, but if I do, I’ll definitely have a stated “official lysanctioned” code formatting style.