Entropy's picture

Coding Projects: Lessons Learned

Some time ago I announced my LinkSphere project, with a deadline for alpha-release in mid-to-late May (for the sake of my friend's college music course, as he was doing the integral music).

That Alpha release was completed in early June, but it's clunky and buggy and poorly coded and I'm not particularly happy with it. I certainly don't feel like publishing that particular fruit of my labour.

LinkSphere was very much a learning project for me. When I started, I'd just about got my head around the concepts of OO programming, I'd finished blowing the dust off the mathematics I learnt during my Physics degree and started to polish it up a bit. I felt ready to start a slightly larger project.

Of course, understanding OO concepts and applying that knowledge efficiently are two different things. Coming to C# from a procedural background, the code that gradually built up was full of static methods, arrays of array-indices and long complicated control structure blocks. I did USE inherited classes, polymorphism and the like, it's just that I hadn't really got my head around the idea of fully OO design yet.

Also, the whole thing was unplanned from the start, aside from a few basic mind maps to figure out which order I should code things in.

As I continued to code, two things happened. firstly I built up more and more classes on this hodgepodge patchwork of existing classes and the scale of the project became proportional to the (over-)complexity of it. Secondly, I styarted to understand the ground rules of good OO design better.

Both of those things led the project inexorably to a single point: refactoring hell. Actual progress ground to a halt as I rewrote classes, renamed classes, objects, filenames and namespaces, re-re-re-re-rewrote classes, decided they were better as they'd started out, moved objects between existing projects in the same solution, changed naming conventions over and over again etc etc etc...

In the end, I *just* got the Alpha out in time, but it felt like more of a quick hack than it did a successfully written program, as the structure was still a complete mess (although some semblance of order had finally started to creep in). I haven't touched it since. The latest revision in my svn repo doesn't compile, and it won't until a certain amount of major refactoring has taken place. And then I'll have to iron out the bugs again...

So I learnt some very important lessons with LinkSphere.

1. I learnt what OO really MEANS. I don't need a long switch/case structure checking an entity type if I use polymorphic class inheritance and simply call entity.Foo(Bar bar). I don't need to store all my entity objects in arrays and then refer to them by array position if I just reference the objects themselves directly. I can save function call overheads if I use event delegates proerly, etc etc.

2. A project should be carefully planned beforehand, and to think about how it needs to be structured.

3. Refactoring, while obviously invaluable to a project needs to be approached with care. It should be taken at a slow steps (one thing at a time, thoroughly tested along the way, then commited to svn, rinse, repeat). It's also silly to waste much time worrying about whether a field is called number_of_channels or numberOfChannels. Consistency is nice, and it pays to follow a convention, but it's not worth going through an sizeable project changing everything to follow a given convention when you could actually be doing something productive X-D.

4. Don't take on your friends' deadlines. :-p

Going forward, I built up a GameEngine project when "designing" LinkSphere which I'll continue to build as I code more projects. I've written a couple of messing around projects since and improved on it, and will continue to do so.

I love a good RogueLike, (NetHack is the one thing I'll keep on coming back to) and one of Objarni's texture fonts inspired me a few weeks back. I'm brewing ideas for a game with text-based graphics and a few fun OpenGL effects thrown in.

I haven't started coding anything yet, though. I've been thinking about structure first, jotting things down in idle moments. When I do start, I'll be building a framework of empty classes first, to see what it looks like.

One question, for design reference...

If I have a tree of inherited classes for every item that could exist in the game (eg. ProjectilePistol : ProjectileGun : Gun : Weapon : Item), how might I select one of these classes at random to create on object from?

One solution I thought of was to create a list of "dummy" objects from which one could be selected at random. I suppose with this method, I'd want a system of properties which return lists of all "Weapons", or "ProjectileGuns" etc as well as "Items" , but this starts to feel very messy. Are there any suggestions for a better way to proceed?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
objarni's picture

Great read, and I applaud your openness!

A hint on refactoring: it helps a lot if you have unit tests around your code. They act as a "safety belt" while doing refactorings.

Refactoring is an important activity to do - the "code health" will degrade if you don't spend, say 20% of your time doing refactorings, continously, from the start.

objarni's picture

About the random selection of entity, this is one design:

1. Create a delegate type "CreateEntity":

delegate Entity CreateEntity(); // assuming every entity inherits from Entity

2. Create a class "RandomEntityCreator" with two methods:

void RegisterCreator(EntityCreator creator);
Entity CreateRandomEntity();

3. In your game, create an instance of the RandomEntityCreator class, and register all available entities with it. Boot time.

var creator = new RandomEntityCreator();
creator.RegisterCreator( ()=> new Projectile());
creator.RegisterCreator( ()=> new Something());

4. When you want a new entity, just call CreateRandomEntity.

var entity = creator.CreateRandomEntity();

You may want to add some parameters to the delegate, eg. where the entity should be created:
delegate Entity CreateEntity(int x, int y);

the Fiddler's picture

I had a similar experience in my last project: I spent a good deal of time refactoring the graphics abstraction and improving the implementation design than actually implementing the necessary features. Only when the deadline started getting closer did I stop this madness: do I really need a scene graph for 2000 polygons? Scratch that. Will a serializable effect system buy me time? No, I only have 5 distinct effects - scratch that too.

The end result is not pretty: most of the rendering and game logic resides in a single file. However, the project made the deadline and worked as advertized, so I count the result as a net win.

In my opinion, the most important (and arguably most difficult) part in programming is actually *finishing* the damn project. Ultimately, finishing a project requires a different skill set than plain coding: as programmers, we tend to treat code as a piece of art, as something to refactor and polish until it shines. Finishing on the other hand, is a game of tradeoffs: 'this code is evil: if I refactor it, will I gain time or lose time?' or ' can I cut feature X from the upcoming release? Will the customer complain if I patch it in a few months later?'

As programmers, we tend to focus on the code and treat it as an end unto itself. However, end-users don't care about code. Even the best design in the world won't help if the program doesn't do what it says! Make the program work; make it work well; make it work fast; if you are still in schedule, take steps to improve code reuse in the future.

That's not to say continuous refactoring is bad. In the long run, I think it is cheaper to take care of design flaws as soon as you discover them, before they have a chance to take roots in the code. Just don't lose sight of the goal: refactoring should bring you closer to a complete program, either directly (refactor this code to fix bug X) or indirectly (refactor this code to make features Y and Z easier to implement).

Even if you've made a bad design choice, sometimes it might be best to keep with it and fix it after version 1. Remember, you will always make bad choices! What looks good now, won't look so good after you've gained some experience by finishing half of the project. If you keep fixing those mistakes, you will never make it past this part (your experience will keep growing and your design choices will keep changing!)

[Random item generation]
You can use reflection to simplify the building of the item lists. Ideally, you should either be able to query for specific weapon types:

var potential_treasure =
    from w in WeaponList
    where w is ProjectilePistol && w.Price < 1000
    select w;

or generate treasure randomly:

var treasure = TreasureGenerator.Generate(typeof(ProjectilePistol), /*min price*/ 0, /* max price */ 1000, /* any other number of parameters */);*/

However, do you really need such a deep object hierarchy? Why does ProjectilePistol need to inherit from ProjectileGun - aren't they essentially the same things, just with different parameters?

Obviously, this depends on the game design, but I think a simpler hierarchy would be easier to handle:

abstract class Item { ... }
 
// This should probably implement IEquatable, IComparable and ISerializable
sealed class Weapon : Item
{
    public string Name { get; private set; }
    public WeaponType WeaponType { get; private set; }
 
    public float Range { get; private set; }
    public float Damage { get; private set; }
    public float AreaOfEffect { get; private set; }
    public float RateOfFire { get; private set; }
    public StatusEffectFlags StatusEffects { get; private set; }
}
 
enum WeaponType { Pistol, Shotgun, Katana }
[Flags] enum StatusEffectFlags { None = 0, Fire = 1, Blind = 2, Slow = 4 }
 
Weapon DesertEagle = new Weapon()
{
    Name = "Desert Eagle",
    WeaponType = WeaponType.Pistol,
    Range = ...
};

The Weapon class is trivially serializable (so you can store and edit weapon lists offline). It is easy to add any number of other useful properties (e.g. weapon quality, clip capacity, installed mods). Finally, it is easy to construct new weapons from existing ones (e.g. "Superior Desert Eagle' or 'Adamantium Katana of Light', in liking to Diablo).

Randomly choosing a weapon is now easy: simply roll a dice on WeaponType, Damage and any other parameters you wish. This should work both with pre-crafted item lists and random item generation.

Granted, all this is also achievable with a deep hierarchy, but this way it is easier to choose a specific item kind.

objarni's picture

Refactoring is not bug fixing.

Refactoring is defined as changing the code syntax/structure, without changing it's semantics.

Since fixing a bug means changing the programs meaning, it is not refactoring.

The primary purpose of refactoring is making the code more maintainable, which means more readable, more understandable, and easier-to-use. As a side-effect, the code will probably be more flexible/extensible too, but that is secondary to maintainability.

However, code that is maintainable, is also easier to bug-fix (maintainability could be defined by how easy it is to fix bugs in the system). So refactoring enables cheap bug-fixing.

The key to refactoring is automatic unit tests. Without them, the riscs of changing the structure of the text files making up the code base overweigh the gains with doing the refactorings.

Mincus's picture

Interesting stuff.
I do wonder if interfaces could play a part somewhere in that class hierarchy?
Especially if you intend on some weapons having uses similar to say switches/levers in the dungeon or magical properties.

nythrix's picture

I spent three years building a graphics engine. It took countless partial rewrites, about 5 major ones and I even restarted from scratch twice. Every time I felt like documenting code (bad sign) I stopped going forward and started programming sideways (refactoring, rewriting, rearchitecturing:) until everything was clear and I could jump forward again. I started with a mediocre OOP background at best and no graphics experience at all. I can say my OO knowledge improved a little bit and I definitely know a lot more about graphics. Now I have a decent engine which can use OpenGL, software raytracing and hopefully OpenCL based raytracing once it becomes available. Unfortunately I lost the "market slot" for my yet-to-be-written game but that's not a big deal since I'm doing this for fun.

Edit: The more I put into the project, the more I can't stop working on it. Snowball downhilling :)

Entropy's picture

It's reassuring to know others suffer from the same dilemmas as me when coding. :-)

Re: Fiddler's suggestions, I suppose it's possible to take the OO 'ideal' too far, but I figure a Katana (to use your example) wouldn't really benefit from having a RateOfFire or Range property, so the splitting of guns and melee weapons at least could make sense.

Mincus: I'd considered implementing explicitly declared properties of IWielded(IWorn?) and/or IOffensive for weapon types, in order to 'hide' properties like the corresponding status effects from methods which don't use them, but I guess, like I mention above, that could be taking the 'ideal' too far. It's not like anyone but myself is likely to use these classes.

Good to talk it over a bit though, as everyone's responses make really clarify the idea of "good design" for me.

the Fiddler's picture

[Melee vs ranged weapons]

The distinction is mostly a matter of your game mechanics. There is very little difference between treating them as two distinct categories or a single one, at least from a code design perspective. In the latter case, melee weapons would simply have a range of 1 (or 2, if they are exceptionally big). In the former, you'd simply not specify a range for melee weapons.

In both cases, rate of fire is up to the game mechanics: for example, AD&D rules (Baldur's Gate) use a speed modifier to modify the initiative roll, which defines which player acts first; Fallout specifies different AP costs for each weapon category (e.g. firing a bazooka costs more APs than a simple pistol); Diablo uses a speed rating that defines how fast you can attack with a given weapon (click fest!)

The best approach might be to define how you'd like combat to work and design the item hierarchy to fit that design. Do you want to take into consideration item weight? How about size? Can the player carry an arbitrary number of items (e.g. 150 in Mass Effect) or is his inventory small? (e.g. Diablo) Is he free to haul as many weapons as he likes or is he always limited to two weapons (ala Halo)? What about item stacks? Bags? Magic bags? Armor types? Damage modifiers versus each armor type? Armor penetration? Damage reduction? Special status effects?

Personally, I consider this one of the most fun parts of any game project: the system can be as simple or as complex as you like! This is pure game design - software design will have to make sure the game design is implemented.

objarni's picture

About getting things done: I have a long history of not-finished projects behind me :)

So I've tried to learn from what have worked and what has not worked.

One project I did finish completely (to the level where friends actually could play the game and say "cool"!) was a duel-game in 3d - a gun-turret and a hoovercraft competes on a wavy arena against time (turret tries to stop hoovercraft - but he can only "bump" him - not destroy him.) It was two players taking turns playing each role. The one with lowest time won the game.

For once, I knew ALL game rules before I started coding that game. That is, I took the time to sit down and write what I imagined - and designed some while writing. Of course not details like exactly how to control the hoovercraft, of the balancing of acceleration constants and such - but you know, the "business rules". Think on the level of rules written for board games.

That took me less than an hour.

Why was it fruitful? I can only speculate. But I think one great thing with that approach is that I knew when I was finished. Another point; I was time-limited (it was christmas-break; three weeks without exams for once!)

I had the "vision" of the game before coding - coding was not an exploration activity, but a "filling in the details" activity. The big majority of previous projects of mine have been more explorative/learning in nature (I think every single one of you reading this are nodding your heads right now! heh) instead of being a programming/system development activity.

It's not black and white - I did change rules of the game slightly when developing it. Eg. I think the rockets from the turret actually could destroy the hoovercraft to begin with - before I realized it was more elegant if it could not.

The other thing I'd like to add, beside of the "know when you're finished" argument, is time boxing. There is nothing that gets your creative/competetive sense on the edge better than being limited! While it seems like a paradox, I've found time-limiting my development both fun and practical. Having "infinite time" just makes you wander away in isolation, never committing to anything.

Ask yourself "What can I do in a week"? :)

Entropy's picture

Some design decisions and ideas for implementation:

Being inspired by NetHack, combat would work in a very similar way. To clarify, melee attacks are one-per-turn, and a RateOfFire property would only apply to ranged weapons.

Also bearing in mind that my goal here is to reinforce what I learned with LinkSphere by undertaking an exercise in "good" OO programming, it makes sense to separate guns from the base weapon class.

The base item class would define universal properties like weight, item material, wear/wieldability etc.

Character objects (player and non-player "monsters") would have Move(Direction dir), Attack(Monster monster), Wear(Item i), etc methods called by player controls or an "AI" class. The arguments would give visibility of the target entity or direction. It would, in theory, be possible to command a character to wear an unwearable item (eg if the player requests to wear a pistol or book, the Wear(...) method would be called with that item as the argument), but the default base item properties would forbid it.

***

Every time a player inputs a command, at least one unit of time elapses, in which other monsters may or may not make their move (depending on relative speed). The decision to move, along with the choice of action would be decided by base AI class or a child of the AI class. The AI class should have visibility of the Monster object that owns it, should be a private Property of the base Monster class and should be created when a Monster is created.

If the AI object's Update() method is hooked to a static UpdateMove event delegate, then this method can be raised every time the player takes an action. The event being static, I wouldn't have to pass references to the Game class to every monster class I create.

If UpdateMove event is effectively raised by the player, it might make more sense to put it in the player class. But OTOH, it would feel odd hooking a delegate to an event in the Player class every time a Monster (or child of the monster class) is instantiated. Perhaps UpdateMove should be a member of the Game classm for the sake of clarity when hooking the event?

Indecision over which class should hold which methods was another factor which led LinkSphere into Refactoring Hell. :-p