Temple of The Roguelike Forums

Development => Programming => Topic started by: tuturto on August 09, 2012, 04:57:00 AM

Title: clean code
Post by: tuturto on August 09, 2012, 04:57:00 AM
I have been poking around with roguelike development for a bit and started wondering if anyone would know really good examples of cleanly written games. I'm mainly looking for a big picture, not so much single algorithms. You know, how to make system for magical effects that isn't completely hard coded or how to add new actions into game easily.

It would be nice if the game had some other documentation than source code too, but that's not mandatory.

Any suggestions or ideas where to start?
Title: Re: clean code
Post by: Krice on August 09, 2012, 08:30:51 AM
how to add new actions into game easily.

What means "easily"? Some actions are harder to add than others. The engine can make some things easier to add, but programming a good engine is hard or at least seems to be so in roguelike games. I think the reason is that most hobbyist programmers are unaware of more advanced techniques that would make it easier to handle large scale programs and complex gameplay requirements of a roguelike.
Title: Re: clean code
Post by: Darren Grey on August 09, 2012, 08:40:47 AM
I'm not sure any such game exists, but you could have a look at ToME4, which does a good job of separating things out and has an active modding community that add classes, spells, etc.  It's all in Lua, and is fairly easy to follow, but isn't commented very thoroughly.
Title: Re: clean code
Post by: kraflab on August 09, 2012, 10:02:26 AM
Epilogue is such a game.  So easy to add new abilities or monster interactions that I surprise even myself at times.  Closed source though!  ;)
Title: Re: clean code
Post by: Quendus on August 09, 2012, 10:35:12 AM
If the hundreds of variants are anything to go on, Angband is such a game. All the C code is very well-commented, all the monster and item definitions are in plaintext files, and it's pretty easy to find which file has which functionality by running a fulltext search on the source directory. The magical effects are "hard coded", but in a very flexible way - for instance a fireball is just a spell that applies fire damage to monsters and floor items in a specific radius of the target square.

Sil is a more recent example of what can be done with the Angband engine.
Title: Re: clean code
Post by: tuturto on August 09, 2012, 03:57:40 PM
Thanks, I downloaded source for both and started looking around a bit.

On a first glance, Sil isn't quite what I'm after. Code is well commented and nicely structured, but for example describing attack method and applying special hit chance mods (make_attack_normal in melee1.c) has a long switch-case - section, where it is taken care of. If I wanted to add a new attack method, I would have to modify that code, instead of just adding a new class or function and letting the program to know where to find it.

ToME4 looks interesting too, but that takes a bit more poking around to guess what's going on. It's probably closer to what I'm after. In any case, I'll read some more of both sources, it's always interesting.

Krice, by "easily" I meant a system where one wouldn't necessarily have to change existing function / class / what-else-construct to add new functionality, but it would be taken care of in some dynamic way.

kraflab, Epilogue sounds interesting, but can't really source dive that :)
Title: Re: clean code
Post by: Krice on August 10, 2012, 06:05:47 AM
Krice, by "easily" I meant a system where one wouldn't necessarily have to change existing function / class / what-else-construct to add new functionality, but it would be taken care of in some dynamic way.

This is what "engine" is designed to do. There is no hard coded decisions in the code that handles game objects which means you can simply add new types of game object and the engine takes care of it. However it's not always that simple, because the engine could need new functionality to handle a new type of game object anyway.
Title: Re: clean code
Post by: tuturto on August 10, 2012, 06:38:29 AM
Yes, and I'm interested seeing how others write that in a way that the new functionality is easy to add and involves minimal amount of editing of old code. If you check melee1.c in Sil's sources, there's code like:

Code: [Select]
/* Describe the attack method, apply special hit chance mods. */
switch (method)
{
case RBM_HIT:
{
        ...
}
case RBM_TOUCH:
{
act = "touches you";

// ignores armor
prt_percent = 0;

// can't do criticals
no_crit = TRUE;

break;
}

case RBM_PUNCH:
{
act = "punches you";
do_stun = 1;

// stopped by armor
prt_percent = 100;

break;
}

case RBM_KICK:
{
act = "kicks you";
do_stun = 1;

// stopped by armor
prt_percent = 100;

break;
}

which works and is easy enough to edit. But if you wanted to add something new, you would have to add it to that select-case - section, instead of just coding it as a stand alone unit and telling the system to use it.

Or if somebody would want to make a variant, which works pretty much the same as the original system, but kicks (for some reason) are not stopped by armour. In the example, this would be achieved by editing original code. When new version of original code is released, the variant has to merge the two codebases so that they have new functionality of the original plus their changes.

However, if the stuff that variant does differently was written in own location and the engine was told to use that one instead of the original, merging would most likely be much easier.
Title: Re: clean code
Post by: Krice on August 10, 2012, 09:03:46 AM
That switch-case code could be easily transformed to data-driven style by creating a datatype for attack styles with information contained in that class, then operated through verbose public interface. That way in optimal case when a new attack type is added all you have to do is edit one class or just add a new data entry for it.

Sil's code is definitely the old school C style with linear approach to coding. It's not bad I guess, but I like data-driven style: data types with public interface, because it "always" works in the source code no matter where it's used.

With datatype all that code would be replaced by this:
Code: [Select]
act=method.Get_Action_String();
prt_percent=method.Get_Protection_Percent();
do_crit=method.Is_Critical_Hit();
do_stun=method.Is_Stunning();

And new stuff would be added to method class.

ps. Also if you need stuff like do_stun only once, you can remove assign to a variable and use it directly:
Code: [Select]
if (method.Is_Stunning())
{
  //do stuff
}
Title: Re: clean code
Post by: tuturto on August 10, 2012, 03:20:42 PM
Yes, that is somewhat more what I'm after. Would still push stunning to be an entity of its own (either class or function). Poisoning and stunning are just status effects after all that have slightly different behaviour, so it might make sense to model them like that.

Maybe I should try some java-based games, since it's object oriented language. Might have more luck there than with pure C.
Title: Re: clean code
Post by: XLambda on August 10, 2012, 05:54:46 PM
Yes, that is somewhat more what I'm after. Would still push stunning to be an entity of its own (either class or function). Poisoning and stunning are just status effects after all that have slightly different behaviour, so it might make sense to model them like that.

Maybe I should try some java-based games, since it's object oriented language. Might have more luck there than with pure C.

No kidding. I mean, C still has its purposes (it's important for OS and embedded systems), but it's generally harder to use than OOP languages. (Despite whatever Torvalds says. :p) It's not so bad at small sizes, but big projects really suffer from it.
Title: Re: clean code
Post by: tuturto on August 13, 2012, 12:29:14 PM
It's been interesting to read loads and loads of source code written by others.

I came across blog of Trystan and he seems to be quite tidy code. I only found two 7DRLs by him though. Would anyone happen to know if he has written more of them or even a full blown roguelike?
Title: Re: clean code
Post by: Leaf on August 14, 2012, 05:34:40 PM
I think the cleanest way to implement a game like this would be to use an OO language to build an engine with an event-driven paradigm around various interfaces.  Perhaps with some sort of compositional object model to get rid of the boilerplating problem.

Java and C# would do it pretty well.  C++ would do it but might get messy.  Old procedural languages like C, forget it. :3
Title: Re: clean code
Post by: TheCreator on August 15, 2012, 07:16:59 AM
Java and C# would do it pretty well.  C++ would do it but might get messy.  Old procedural languages like C, forget it. :3

When the programmer is dumb, it will get messy no matter which language is used. But I agree on procedural languages, they're history :).
Title: Re: clean code
Post by: tuturto on August 15, 2012, 12:44:49 PM
I think the cleanest way to implement a game like this would be to use an OO language to build an engine with an event-driven paradigm around various interfaces.  Perhaps with some sort of compositional object model to get rid of the boilerplating problem.

That is quite high level, could you elaborate it a bit?

On another note, I have been reading through Trystan's blog (http://trystans.blogspot.fi) and he seems to have good ideas about developing roguelikes (and software in general). Maybe I'll get code for his roguelike tutorial later today and have a look at there too.
Title: Re: clean code
Post by: Leaf on August 15, 2012, 04:08:18 PM
I think the cleanest way to implement a game like this would be to use an OO language to build an engine with an event-driven paradigm around various interfaces.  Perhaps with some sort of compositional object model to get rid of the boilerplating problem.
That is quite high level, could you elaborate it a bit?

Define interfaces to "listen" for particular events and "extract" particular data.  Write your objects that inhabit the game world so that they implement those interfaces.  For example, "public class Torch implements Item, Light, HoldableOneHanded".

The Light interface might define method prototypes for getting brightness, radius, etc.

The HoldableOneHanded interface might define no method prototypes, but the game code could check what interfaces an object implements to see where it can be worn/wielded.

The Item interface might just extend a set of other interfaces, such as Gettable, Droppable, Description, Name, Weight, etc.  All of those interfaces would define appropriate method prototypes.

All those would be interfaces for extracting data.  When a player moves, all of the objects that implement Light within the scope of the location that the player has moved to could be queried to calculate what the player can see.  When the player tries to pick up an item, the game engine could look and see if the object implements the Gettable interface before letting the player pick it up.

A monster might implement an event handler interface, such as "OnObjectMoved".  That OnObjectMoved interface might define an "onObjectMoved( Object movingObject )" method prototype, to be called whenever something moves around in the dungeon, be that a player or whatnot.

Then your game engine has an event mechanism, where when, say, an object moves, the game engine collects a set of objects within the scope of the location that the object is moving to.  It loops through them and finds the ones that implement the OnObjectMoved interface and calls the event handler method defined by that interface.  Within each object's event handler method body would be the code to respond to the event.  A monster could "see" the player and move towards him, or attack if close enough.

You could define many different sorts of higher-level event interfaces, like for when an attack happens, damage is dealt, the player gets hungry, etc, etc, etc.  Objects in the game world can define handlers for these events, so that they can respond to them appropriately.

The event-driven paradigm is very powerful, allowing you to cleanly define object behaviors without junking up the core of the game engine with a bunch of stuff that you have to sort through when modifying your code.  It makes maintenance much easier and allows for a great deal of extendability without having to touch the core game engine code.

This all seems fine and good, but about the time you write the same boilerplate code for the 50th monster or the 20th weapon, you're going to be gnashing your teeth!  That's where the compositional object model comes in......

Instead of directly defining a class for every sort of object in the game, we define a "GameObject" that is nothing more than a set of "Properties" (a slightly inaccurate term, given its use in newer languages like C#, but I borrow it from CoffeeMUD).  Then you write a bunch of generic properties that implement the various interfaces and take various initialization values.  "public class StdLight extends Property implements Light".  The constructor for StdLight could take brightness, radius, and duration values, or something.  There could also be various different properties that implement various event handlers to define monster behaviors.  A goblin archer could have standard properties like "RangedCombatant, Cowardly" etc to define their behavior, while a goblin axeman could have properties like "MeleeCombatant, Aggressive" etc.

Then your actual items, monsters, etc in the game become a set of XML files that list a bunch of different properties and their initialization values.  You can make new objects quickly without a bunch of boilerplate code, maybe even in-game with a simple built-in object editor, by just assigning a bunch of properties with initialization values to an empty GameObject.

And since all of these properties are backed by data extraction and event handler interfaces, you can still define special properties to provide special behaviors to very unusual objects, and they just magically work with the game engine.

Since the properties are so reusable and can be extended to create those special behavior properties for special items and monsters, they can also /define their own behaviors/ internally, instead of junking up innards of the game engine with movement code, combat code, etc.

Thus the whole clean abstraction thing that is sometimes so very difficult to preserve in game programming is preserved, and you have a very modular game engine that can be easily customized just by modifying some of the low-level properties that everything else is built on.

The compositional object model is great for generating random monsters and magical items, too.  Your generator just picks some properties off a table and assigns them.  Done.  No weird stuff to mess with deep in the game innards.

I think it may be overkill for a "small" game, but for a "large" game, I think such a design can really help keep the implementation from collapsing under it's own weight, especially if you are doing it all for fun and just designing as you go instead of making up a big ol' design document and sticking to it.
Title: Re: clean code
Post by: tuturto on August 16, 2012, 05:30:22 PM
That sounds pretty good and flexible. Different kinds of game objects would be easy to construct and they would follow same rules. Definitely a good approach I would think.

That's half of the solution, you still need a game engine that is clean and easy to maintain.
Title: Re: clean code
Post by: Leaf on August 16, 2012, 10:22:10 PM
The way I did the event-driven thing for my stuff, the core of the game engine just became a relatively small multithreaded (it was multiplayer, with one "engine" running for each area, with some RPC-type operations to move objects between different areas) timer/turn event dispatching loop and object graph management thing.  Things like the combat system and magic and stuff got implemented in various object properties.  I don't know if one could call it perfectly "clean" or not, but it was all fairly modular and not all tangled together.

The player wasn't any different than a monster, except that the monster AI property was replaced with a property that handled translating player input packets into events generated by the player object, while listening for events and sending screen update packets to the player's client based on the events it heard.  Actions/Skills/Powers available to the players/monsters were implemented as separate properties, either properties on the "creature" itself or on objects in its inventory (ie, if you're wielding a sword, the sword provides the melee attack action), so that the AI could be kind of generic and pick actions/powers out of broad categories (melee attack, ranged attack, AOE, escape, etc) instead of being hardwired with a bunch of different specific actions/powers.

Unfortunately I've never completed the game, because I keep thinking that there is a better way to implement everything and starting over from scratch, after taking breaks in-between.  I started the first one 8 or 10 years ago.  I think the last time was the 12th partial-rewrite from scratch. >_>

So I guess what I am getting at with all that is, maybe there is a trade-off to be made between making it "perfect" and "clean" and actually completing something. <_<
Title: Re: clean code
Post by: lithander on August 17, 2012, 12:08:43 AM
Nice posts, Leaf. But a word of warning: Designing a good game (from a game designers perspective) is hard! Good software design is hard too. Doing both at once? Madness! :)

I don't disagree with what you said. Component based design is great if you want to implement hundreds of different items, monsters and other game objects. In that case (in most cases actually) composition beats inheritance. And interfaces and events are great ways to decouple those components so they remain flexible. All that stuff is true. And it's good to know!

But when your goal is to make a game, any solution that solves the problem and performs good enough actually is good enough. You might have heard the quote "Premature optimization is the root of all evil" and while it originally was made in the context of performance optimizations I think it holds some truth in regards to software design. It's hard to come up with the perfect engine that will solve all your potential needs and problems before you even know what they are. So my suggestion would be to just start coding. Don't aim to write final quality code in your prototypes (and DO prototypes first!!). Instead prepare to reimplement or refactor critical parts when you know more specifically what you need and where the bottle necks are. Refactoring is so easy in modern IDE's that it won't take too much time. Of course, with experience you'll be able to take shortcuts, design your software in a way that will avoid problems you experienced with earlier projects, that will allow your project to remain flexible and maintainable. But it's hard to get that knowledge from reading tutorials. I'd say that when you want to learn game programming spend 90% coding and 10% reading.
Title: Re: clean code
Post by: Leaf on August 17, 2012, 01:42:41 AM
But but but but....

Designing and writing overly-complicated game engines is so fun!  ;D

When it comes to making actual playable games with the engine, I start getting bored and chase down some other shiny thing, lol.....
Title: Re: clean code
Post by: Alex E on August 17, 2012, 01:48:15 AM
Unfortunately I've never completed the game, because I keep thinking that there is a better way to implement everything and starting over from scratch, after taking breaks in-between.  I started the first one 8 or 10 years ago.  I think the last time was the 12th partial-rewrite from scratch. >_>

That's one of the problems you may face when creating a game. You start to lose motivation after a while. Some people don't, but I usually do after a few months.
Title: Re: clean code
Post by: tuturto on August 17, 2012, 04:57:16 AM
Good stuff indeed. Treating player character in a same way as all the other character (be they monsters or other players in multiplayer), also makes things much simpler and cleaner.

I'm a fan of iterative development. Usually I aim to write best what I can at any given time, but don't worry about future too much. Like I haven't really given any thought on ranged combat yet, because there's so much other things to work with first. But when I get there, I try to make that as good and clean as possible. This of course means that I have to change existing code sometimes to make the new design as good as possible at that given time.

Keeping steps small seems to help with motivation for me. I get something new relatively fast and if feature I chose turns out to be bad idea, I can just discard it without losing too much time and effort.

Do you happen to have any source code available Leaf that I could have a look at?
Title: Re: clean code
Post by: Leaf on August 17, 2012, 05:44:42 PM
I have it all in a subversion repository going back a few years.  The oldest stuff is on tapes somewhere and I no longer have a tape drive that will read them, but that stuff was all in C/Unix and fugly anyway. :P

I've never publicly released any of it though.  Let me think about it.  What parts in particular are you interested in? :P

Something that I kept going round and round and back and forth about is how to "stack" properties on the object, how/what order to route events to them (first property of type?  All properties of type?), and how to let the higher level game developer (let's call him the "world scripter") easily call a method defined on some property on an object without having to do a bunch of instanceof stuff or check for null values (making the assumption that the "world scripter" may be at a neophyte level of programming experience and needs something that can still be reasonably robust even when they write bad code).  Seems to be a toss-up between doing it "right" and making it "easy" there...
Title: Re: clean code
Post by: tuturto on August 18, 2012, 07:10:50 AM
I'm interested on the object model (characters, items, rooms and such) and how you get them working together (characters walking around and fighting with each other).

Thinking from point of view of other person using your engine is good. They probably don't have the same knowledge as the person who wrote it after all. That's an interesting question actually, how much of bad code should the engine tolerate? With languages that are compiled to create an executable, you can at least lean on the compiler to tell you if you're doing something really stupid. With interpreted languages first sign of error is often runtime crash.

There's this short poem that gets shipped with standard python implementation.  Currently I like especially the two lines about explaining the implementation.

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Title: Re: clean code
Post by: Leaf on August 18, 2012, 03:26:31 PM
At the risk of being a Java fanboy, I think java can give you the benefits of both.  (Though honestly I prefer C# as a language, but it doesn't run on near as many platforms.)

Mine was designed to be a multiplayer engine with online content creation.

- You can force untrusted Java classes to run in a sandbox, great for user-authored code running server-side.
- You can load those classes from anywhere you want (ie database or "user home directories" in a virtual filesystem, or even over the network) by writing custom classloaders.
- There's a compiler built into the runtime libraries that is easy to use.  No shelling out to the OS and doing other sorts of nonportable stuff to compile properties written through an OLC.  My plan was to have a dedicated compiler thread that all compilation jobs got serialized through, do avoid bogging down the game if everyone was compiling at the same time.  But I didn't get that far.
- Since the OLC stuff is compiled and runs through the hotspot JIT, it's as fast as native code (except for a bit of securitymanager overhead if you are running them in a sandbox).
- Since it is compiled, you get the syntax errors out of the way ahead of time.


I'll see if I can dig up some of my old documentation on the object model.  I am fairly sure it's in svn or backups somewhere. :P

In the meantime, if you're familiar with Inform 6, I kind of stole part of the object model from the Infocom Z-Machine, with some special behavior at the head of the tree to arrange the world into a grid instead of an arbitrary graph of spaces.  So I guess it's more like a 3d grid of trees.

I am feeling incredibly lazy today, so I probably won't be able to find the gumption to dig for docs until I have more coffee in me....  Maybe tonight.
Title: Re: clean code
Post by: Leaf on August 18, 2012, 04:38:28 PM
Alrighty....  I violated the implementation lines of your poem, but (weakly) in my defense, most of the fun was trying to make the game engine highly concurrent and distributed, so that always adds some complexity and indirection, to ensure that threads don't mess with each other's data directly and cause nasty race conditions and concurrent modification exceptions. :P

At the top of the object graph was something I called the "Realm".  The realm contained a bunch of areas, or "Dungeons".  The realm provided methods to look up dungeons by name, returning a reference to a dungeon.  It also provided methods to start, stop, create, and delete dungeons.  When the realm booted, it read a directory hierarchy off disk that contained the game state and used that data to initialize the dungeons back into whatever state they were left in when the realm was last shut down.

Each Dungeon ran in its own thread, and Dungeons could not access each other's object graphs.  The only globally accessable methods provided by the dungeons were methods that they could use to pass Tasks (glorified Runnables) to each other through concurrent queues.  The idea here was to eventually be able to replace all of the shared memory operations above this level with stuff being serialized over TCP sockets, so that a realm could run on a cluster/cloud, or you and your buddies could link your realms together over the internet into one big game world.

Internally, the dungeons maintained a 3d grid of GameObjects in a threadlocal hashmap keyed with 3d integer coordinates.  These toplevel GameObjects represented "cells" on the map.  The dungeon also internally maintained a table of GameObjectData (private), keyed by UUIDs, and all internal GameObject operations looked up data on this table by that key, instead of holding direct references to other GameObjects.  This bit of indirection was to enforce that dungeons could /only/ access game objects contained within themselves.  This made higher level property programming simpler, because the world builder could write their properties without having to worry about narsty concurrent programming issues.

GameObjects provided methods to look up their Parent (the GameObject that contained them) and their Children (the GameObjects that they contained).  They also provided methods to move themselves to a new parent.  A GameObject's parent and children were not directly modifiable; you could only move them to a new parent.  This was to ensure that the object graph never became circular (example: a chest containing a bag, while the bag also contained the chest, which would have caused code that walked up and down the object tree to get stuck in an endless loop).

GameObjects also provided methods to add and remove Properties from them.  So if I wanted to make a wall at coordinate (0,0,0), I'd create a new GameObject, add an Obstacle property, a Graphic property, and maybe a Name("Wall") property to it, and then move the GameObject to (0,0,0).

Early implementations used a global Heartbeat event, but I later removed that as it proved to be too CPU intensive with large worlds.  So Properties could register a Timer event, either repeating or one-shot, and an OnTimer event would be raised to that property at the given interval.  This is how monsters wandered aimlessly around the dungeon waiting for players to run into them.

Some event handlers could return a boolean value that could signal whether the event chain was to be interrupted or not.

For example, when an object was about to move, it would raise an OnMoving event to the set of all objects near it and all objects near its destination.  Any of these OnMoving event handlers could return true, in which case the event chain was canceled and the object did not move.

This was handy for this sort of use-case:  A player is standing next to a wall.  The player pushed a direction key, trying to move into the space occupied by the wall.  The wall gets the player's OnMoving event.  Seeing that the player is trying to occupy the space that the wall is in, the wall prints a message to the area saying, "So-and-so runs into the wall and falls down!".  Then the wall returns true, canceling the event chain and preventing the player from moving.

(Edit: I think the key thing here regarding cleanliness, that event driven programming is really good at is....  Notice that the wall's behavior (blocking movement) is implemented /by the wall/, not by the movement code on the player.  If I go to change how walls work, it's all there in one place.  There is no spaghetti of stuff between the wall and the player movement code and your toaster and the neighbor's dog.)

Once an object had successfully moved, another event, OnMoved was raised to the area.  Monsters used this to notice when other things wandered around the dungeon, so that they could see players coming and decide to either flee or become aggressive.  Players also used this to send screen update packets to their clients.

There were other low-level events that were raised by the engine, things like properties being added or removed and such.  I never used them for much, but the idea was that critters would be able to notice when spells wore off and re-cast them and stuff like that.

Higher level events (such as combat and damage events, speech, etc) were raised by other object properties rather than the low-level engine.

Since dungeons were only allowed to know about objects that they contained, the issue of moving objects between dungeons was handled by the dungeon Tasks mentioned above.  An object (and its children) to be moved to another dungeon was converted into an "object blueprint" and removed from the dungeon's object table.  The blueprint was then packaged up into a Task, which was enqueued to the other dungeon.  The new dungeon would dequeue the Task in its own thread and run it.  The run() method of the task would then rebuild the object (and its children) in the new dungeon from the blueprint.  Since the actual "GameObject" was just a wrapper around a UUID, with some internal GameObjectData methods attached to it, the gameobjects that the properties of newly moved objects referred to would just resolve to nothing in the new dungeon, instead of creating a concurrency nightmare.

There was no broadcast to all dungeons with that paradigm, and objects in different dungeons had to communicate with each other asynchronously with a rather obtuse task passing system.  That worked ok with a convenience layer on top of it, until the objects lost track of which dungeon the other object was in and sent the tasks to the wrong place.  I never did fix this, but thought about implementing some kind of Object ID registry in the realm so that things could look up what dungeon an object with some ID was in.  This would have put a crimp in the massively concurrent idea (though I suppose I could have stored it in some kind of big distributed hashtable), so I kept thinking about it and never got around to fixing it.

So, there it is.  Rather impractical for a game of roguelike scope, but it sure was fun figuring out how to make it work.  Lol.  Not designing it to be highly concurrent would have made it vastly cleaner.  A single-threaded game could have just kept direct references to other objects instead of internally referencing a data object by UUID, and could have done away with the task-passing system.
Title: Re: clean code
Post by: Leaf on August 18, 2012, 04:48:00 PM
I also briefly explored using software transactional memories and a threadpool without the distinction between Dungeons, which would have allowed a really huge seamless world to run on multiple cores without having to deal with locking issues.  But they all still seem to be academic things that aren't quite ready to go into production code, yet....

I also tried implementing it all on J2EE with the whole game state living in google bigtable and memcache, but it ended up being just a little too slow to be nice.
Title: Re: clean code
Post by: tuturto on August 21, 2012, 07:25:18 AM
Wow, that's a wall of text with loads of good information. Took while to read and digest it properly too.

Well, anything that is highly distributable and concurrent is bound to be more complex than single user application running in a single thread.

The use of properties is really nifty way of doing things. Like you said, adding new features on the existing system is easier that way, than when everything is in a huge blob of code. Writing the whole realm system must have been fun too.

How long did it take to get initial version up and running?
Title: Re: clean code
Post by: tuturto on August 21, 2012, 08:31:14 AM
While we're talking about clean code, Gamasutra ran an article In-depth: Cleaning bad code (http://www.gamasutra.com/view/news/176218/Indepth_Cleaning_bad_code.php). Nothing reaslly ground breaking I guess, but still interesting to read. Frykholm has very opionated way of writing, which is good for starting discussions.
Title: Re: clean code
Post by: Krice on August 21, 2012, 09:20:30 AM
Frykholm has very opionated way of writing, which is good for starting discussions.

That article was actually good and he knows what he is talking about. I think at least 3, 5, 6, 7 and 8 are confirmed.
Title: Re: clean code
Post by: Leaf on August 22, 2012, 06:24:04 AM
How long did it take to get initial version up and running?

I don't remember exactly.  It took some experimentation.

I started out trying to avoid partitioning the world into areas, and used a global lock on the whole object tree.  Events fired out of a thread pool.  This was of course only marginally better than using a single thread.

Then I tried putting a lock on each object and doing some spin-release-wait locking to acquire locks on objects before the guts of the events ran.  This worked ok-ish, as long as you knew what objects you needed to access beforehand.  If you found that you needed to access another object half-way through processing, you had to add it to the lock list, roll everything back, unlock, spin through all the locks again, and reprocess (rather like backing out of a database transaction).  But it was hideously complicated to write properties for.

That led to the investigation of software transactional memories, but it turned out that none of them that I examined at the time were really of production quality.  The fast ones were really buggy, and the robust ones were really slow.

Somewhere in there I tried to write it on JPA/tomcat, and then GAE, but both of those were too slow.  GAE would have been good enough it they supported persistent TCP connections or http streaming or something at the time, but they didn't (the slowness there was due to HTTP polling; their memcache backed by bigtable makes for lightning fast persistence as long as you're not too write-heavy).  JPA was /horridly/ slow with many-many relationships being mapped to POJOs.  It works pretty good if you avoid many-many relations, but otherwise it brings even the fastest DB to an ignoble grinding halt. :P

After that, I settled on having to partition the world into areas with the realm model, which I didn't want to do, because it doesn't scale as well, but....  Oh well.  I think it took 3 or 4 days to get that model relatively bug-free with the compositional object model and some basic dungeon geography properties and a stat-less @ wandering through halls of #s, but there was a lot of bits of code to be reused from before so it wasn't /all/ from scratch.  It helped that I was using all the maps from Daniel Lawrence's DND for test areas, so that made for a quick relatively large dataset to test with without having to mess with writing random generation stuff.

I think it also helped that I was unemployed and very very bored at the time. :P
Title: Re: clean code
Post by: tuturto on August 24, 2012, 08:09:07 AM
That sounds big and very enterprisey, must have been really good coding exercise too actually. Doing something like that probably teaches your a thing or two along the way.

Trying out things and changing them when it doesn't work / doesn't look good is one approach to clean code. Of course usually the larger and older software system grows, the harder it is to make big changes on it. Currently I'm rethinking my user interface and map implementations. I took shortest path with the map and just created an array of integers, where each integer represents picture that gets drawn there. Add two layers (one for floor and one for walls) and you can know where you can walk and where not. After switching to PyQt, it would be nicer to be able to treat everything that gets drawn on screen in the same way, so maybe I'll switch representing the map with more complex objects after all.

I was going through some code today that I haven't touched in a bit and started wondering how many roguelike developers are running static analysis for their code (like lint) or otherwise analysing it? And if they think it's worth doing?

I have been running pylint for the longest time and occasionally check statement coverage of my tests (but don't really track it, it's just an interesting number to see).
Title: Re: clean code
Post by: yam655 on August 29, 2012, 04:33:23 PM
I want to point out that the data-driven approach (human-editable files with property definitions) and the object-driven approach (implementations and interfaces) need not be entirely different worlds.

The data-driven approach is great for massive numbers of nearly identical things.

For uniques, though? The Angband stuff has an explicit set of "unique" properties they set on the unique stuff. You end up dividing your logic between undescriptive data and custom logic elsewhere.

It makes more sense to have a combined approach. A system where the common stuff is super easy to make, but the unique stuff is super easy to maintain.

It shouldn't be particularly hard. You start with an Object system and the first object you make is the generic/common thing used by the data-driven objects.

The problem is that if you want deep interactions between objects (like, say, Nethack) you can easily end up tightly binding objects to each other. When objects are bound to each other, you end up dealing with an object system in which is complex to extend and requires non-obvious edits to fully add new subtypes -- no matter how you originally planned to design your object system.

This leads to many Roguelikes having elegant objects systems which actually prevent deep levels of object-object interaction. The simplest example of object-object interaction I can present would have to be dipping things in to potions in Nethack. You're dealing with an action with two different objects and an effect which is frequently similar -- but different than -- the standard player/monster potion effect. Depending on what is dipped (both the type of item and the existing intrinsic attributes it possesses) different results occur.

It's hard to write such interactions cleanly. It's even harder to have such a system be both clean and generic.

But... To me, the depth of interactions was part of the initial draw to Roguelikes. I don't care about the cleanliness of the code, I just care that the game is awesome.