View Single Post
Old August 14, 2018, 00:32   #39
Dean Anderson
Join Date: Nov 2009
Posts: 127
Dean Anderson is on a distinguished road
So, it's been a few months but I'm back again.

And this time I have a link!


Basically, I finished the intitial pass at re-factoring the code; and although it worked and was a fully functional version of the game, it wasn't very "nice" C#. It had a small number of classes and most of them were thousands and thousands of lines of code. The fifty thousand lines of C# code worked but weren't very well organised.

So since then, I've been alternately playing it and doing more re-factoring to make it nicer and cleaner.

A classic example is the spell code. In the original C, that code consisted of a global array containing the level/cost/failure/xp for each spell for each class (and Cthangband inherited Zangband's spell system, so there are far more spells than there are in Vanilla - as well as many more classes - so that array was *huge*) and another global array containing their names. Then, there are a few global uints that contain bit-flags for which spells are known, forgotten, and have been cast at least once. Finally, the code for casting spells is a huge switch statement with 256 cases for the 256 spells, and the code for displaying spell info is also a huge switch statement with 256 cases for the 256 spells.

While all this works, having the spell data separated in this way is really awkward to maintain. Everything is done by look-ups, offsets, and indices; and cross-referencing a spell to see how much mana it should cost requires going through the tables by hand counting off the spell numbers.

The first re-factoring pass that I did converting this from C to C# basically left the structure unchanged. The spell names and spell numeric info were still global data (although since C# doesn't really have the concept of global data they were technically static members of a "GlobalData" class). The bit flags were still there, albeit now represented by a "FlagSet" class that wrapped the underlying uint with some user-friendly functions (and no longer global - the FlagSet objects for them were properties of the "Player" class). The humongous switch statements were still there too, although one of them was now contained in a function within the "Player" class and the other was now contained in a function within the "CastingHandler" class.

Again, it still worked - and it was C# code rather than C code - but it was still a mess.

So the second re-factoring pass that I've been doing (and which is still ongoing, although this particular part is finished) has cleaned it up.

Now there's a "Spell" class that holds the basic information for a spell, and there is a class for each individual spell that inherits from "Spell" (for example "SorcerySpellZap" and "NatureSpellHerbalHealing"). The data for the name of each spell is held in the class, and when it's initialised it's passed a reference to the player so that it can set its own level/mana/fail/exp values based on the player's character class. The spell info code is no longer in a switch statement but in a function within the "Spell" class (that uses a property that each inherited class overrides), and similarly the code for casting each spell is held in an abstract function within the "Spell" class that each individual spell class overrides. The flag sets are now individual flags belonging to the "Spell" class, so each individual spell inherits them.

When a character is created, there's a function that simply fills their spell book (an array of "Spell" objects - which, thanks to polymorphism, can therefore hold any object derived from "Spell" too) with a copy of each of the relevant spell objects.

So all the info for any individual spell is contained in a nice logical unit. Adjusting a spell or swapping one out for something different can be done trivially, without having to search through huge tables of numbers to find the right values to tweak. Displaying the spell info just requires a simple look through the spell book asking each spell in it to display its own info, and casting a spell just requires a simple call to the spell to cast itself.

So that's the sort of thing that I'm doing now. I'm breaking up the overly large and linear classes that I got in the first pass into more logical and usable smaller classes, and actually developing a proper object model as I go.

I'm in no way finished (although I've done most of the really big ones), but I am far enough through that I'm happy to release a new version to the world, especially since the end of July was Cthangband's 20th birthday (I released the first version - Cthangband 2.1.0 - back in July 1998).

So here it is. The link above is for the installer for Cthangband 6.0. It's fully C#, and should run on any Windows machine running Vista or more recent - although if you're running on Windows Vista to Windows 8 you might not already have the .NET Framework 4.5.2 installed, in which case you can download that directly from Microsoft here).
Dean Anderson is offline   Reply With Quote