Wednesday, December 30, 2009

Optimizations are great, but sometimes it helps not to be an idiot...

So, if you've been following my progress on magecrawl, you'll remember my article on optimization trying to cut down on memory usage and map generation time. If not, here and here.

Well, I did a bit of programming this morning before work, and on the way driving to work I had one of those "Wait a second..." moments. Here's the guilty section of code:

private void GenerateMapFromGraph(MapNode current, Map map, Point seam, ParenthoodChain parentChain)
       {
            if (current.Generated)
                return;

            current.Generated = true;
            bool placed = false;

            System.Console.WriteLine(string.Format("Generating {0} - {1}", current.Type.ToString(), current.UniqueID.ToString()));

It's that last line that got me. It was some debugging code that I forgot to remove. This function gets called recursively during map generation, a huge number of times and writing to stdout is very slow.

Some baseline numbers (release mode attached to debugger) in generating 100 maps gives me:
  • Before - 47473ms
  • After - 1041ms
Yeah, only a 45x speedup. While all the other optimizations were great, and needed due to memory usage, this appears to be the root cause of my performance issues.

The lesson for today, next time performance is terrible, check stdout to make sure you aren't dumping 10000 lines of text to it during map generation.

Update: Now I just need to find some crazy optimization for my cave map generator and I can get rid of my "loading" screen, since I can generate 100 of the stitch levels in about a second.

Tuesday, December 29, 2009

Economy in Roguelikes - Part 2: Goals

Before I try to lay out what I hope for in Magecrawl's economy, laying out the main goals of what I'm trying to accomplish seems like a good idea. All game design decisions involve tradeoffs, and knowing what problems I really want to solve should highlight why I'm making certain decisions.
  • Fun - This is a game afterall, not an economic simulation. If in the end, it doesn't make the game more fun and replayable, it is not work having.
    • This is why some "optimal" behaviors, such as vacuuming floors clean of items, should be discouraged via game mechanics.
  • Good risk vs reward curve - The game should reward players for risk, and not reward for "safe" grinding or farming.
    • Prevent pudding farmings and other such nonsense.
    • The rewards should be useful for any character type. It is not fun to traverse the dangers of a dungeon to find that the only useful equipment is not useful to you (Finding a +5 awesome sword when you are a unarmed monk for instance).
  • Balanceable - There should never be one always optimal choice. Games are about tradeoffs, and clearly always better choices point toward brokenness. 
    • Some systems make balancing easier or more difficult. Some games will be easier or harder due to the whims of the RNG, but on average things should make sense.
  • Flavor - The system should fit in the existing world, style, and flavor. 
  • Ease to implement - After all, I'm currently a one man team. 
    • The most awesome system in the world would be great in theory, something I can implement "now" and improve later is better.

Wednesday, December 23, 2009

Economy in Roguelikes - Part I: Thoughts

In games that even roughly follow the RPG template, as the game progresses the player or party increases in power on two axes (axis plural apparently, I love English). One axis is character strength or experience, the other being character equipment and items. As someone who's creating a game, specifically a roguelike, making these areas of the game interesting is critical to my success. I'm going to cover some of my recent thoughts on the second area, equipment and items, in a short series of articles.

Anyone who's played a recent RPGish game has seen the drill. You start off equipped no better that a set of dirty rags and a dull knife and end the game in shiny enchanted plate armor and a +5 sword of awesomeness. This provides both a "carrot" to the player (look at how awesome your equipment is), and a "stick" (get behind on the equipment curve and suffer). In almost every game, the way one goes about this is pretty standard.
  • Enemies drop trash items and/or cash that one collects
  • Enemies (sometimes bosses) randomly drop items worth keeping
  • Unused items can be sold to shops for cash.
  • Cash can be used to either buy new equipment, gamble for new equipment, or improve existing equipment
There are some consequence to this system, one major one that has come to my attention is what I've seen called the "vacuum cleaner" behavior: Since in roguelike you have one life, people play optimally. Since every item dropped can be converted to cash, which can be converted into power, one should collect every item and return it to town to sell. This is generally boring, so games have come up with "band-aids" to patch over the issue.
  • "Town Portal" scrolls, to make return to the market less painful.
  • Pack mules or Bag of Holding - Increase amount one can carry, making return trips to sell items less frequent.
  • The items enemies drop are low weight and stackable (Final Fantasy XII), also reducing frequency of return trips.
  • Pets who can return to town and sell items (Seen in Torchlight)
  • Transmute spells, which will convert items to cash.
  • Shops and merchants who are co-located near/in the dungeon at hand.
  • Have a time system, either food or a deadline, which punishes players for returning to town more than the item is worth.
  • Remove the selling system in its entirety (Crawl)
The other half of the issue is returning home to buy better equipment. This almost always follows the Sorting Algorithm of Weapon Effectiveness (read this link). The ways I've seen new equipment introduced into game include:
  • As you travel farther from the starting area, or visit new towns, better equipment appears for purchase. 
  • As you progress in the plot, items shops everywhere restock with better items
  • Many items exist for purchase from the start of the game, but you can't use the good ones due to lack of skill, strength, or level
  • Many items exist for purchase from the start of the game, but are priced out of range of all but the most dedicated grinding players
Analysis of these thoughts and my plans for Magecrawl's economy will have to wait for another post. Let me know if I missed something, or your thoughts in the comments.

Thursday, December 17, 2009

A Christmas Break

Tomorrow I fly home for a ten day visit with family and a wedding to attend. Unless I get stuck in an airport due to weather, I'm not planning on updating this blog until I return.

However, that doesn't mean I won't be programming. I'll be taking my 7 year old Inspiron 5100 (I really need a new laptop) onto the plane to get as much coding done as I can before the batteries die. I'll update with what I've accomplished when I return.

Update: I found out that my "laptop" no longer will hold a charge for more than 10 minutes without dying. So, I guess no programming on the plane for me.

Merry Christmas everyone...

Monday, December 14, 2009

Let there be color (and a request for help)...


I just finished implementing something that didn't take that long, and was long overdue. Color. I'll let the screenshot speak for itself.



The amount of code it took to add this was almost non-existent.

The issue is that I don't have an eye for color. There's a good reason my wife picked out our room colors. Here's what I have so far:

case TerrainType.Floor:
    if (visible)
        return Color.FromRGB(42, 42, 42);
    else
        return Color.FromRGB(15, 15, 15);
case TerrainType.Wall:
    if (visible)
        return Color.FromRGB(83, 41, 0);
    else
        return Color.FromRGB(40, 20, 0); 

If anyone wants to play around with the color scheme in paint (or download the sourcecode and build) and comes up with something that looks better, let me know. I'd love to use something better than what I came up with.

Sunday, December 13, 2009

Evaluation Loop - Why build and setup times matter

Lately I've been thinking about "turn around time" and programming. I'd define this is the time it takes one a change is made to determine if it is correct. There are two parts to correctness:
  • The syntax is correct and the compiler will accept it.
  • The code "does what you want"
The most important resource you have as a developer is your attention. The shorter this cycle is, the more likely you are to not get distracted and get more work done. This is why in magecrawl, I've done what i can to make the turn around time as close to zero as possible.
  • Visual Studio will underline bad code as you type, so you don't even have to hit compile to know you forgot a ';'.
  • C# has a ridiculously short compile and build time. I can build my entire game in less than 3 seconds. 
  • The build step compiles and copies all the associated DLLs into a "dist" directory which has all the associated data files checked in. (No manual setup on new branches/machines).
  • Debug builds turn off the introduction screens, "are you sure" timers, save on closing window, and anything else that slows down testing.
There is one things I'm currently not doing which might reduce "turn around time" even more.
  • Unit and integration test the game engine and utilities module. This way, I could check common use cases with the hit of a button.
My excuse for this is lack of developer bandwidth. Ironically enough, something that could reduce my testing burden is too much of burden right now. As a developer, if other people aren't using my code, I tend to loose interest after awhile. That's why I released a tech demo of magecrawl, and want to get another tech demo finished soon, to get people to use it and keep my interest.

If you are programming at all, it might be worth investing time into making your build more automatic and faster. It's not the time savings as much as the attention savings that are paid back as dividends.

Sunday, December 6, 2009

Iterations - Iteration 6 Complete

So, I'm trying to build my game around "iterations", kinda agile-like. As I mentioned in another article, I've been on iteration 6 for the entirety of this blog's lifetime. Iterations are parallel to the "steps" in 15 steps to write a roguelike, but not exactly. I plan that each iteration takes less than 2 months of work. Here's my iteration list so far:
  • Iteration 1 - "Hello World" - Setup enviroment, walk around a map displayed on screen with doors
  • Iteration 2 - "Save/Load" - Save and load to xml files and xml compressed to .zip
  • Iteration 3 - "It's Alive, and it fights" - Time system, monsters, basic combat, LOS
  • Iteration 4 - "Tech Demo I" - Items, Magic, Dialogs, Data Files
  • Iteration 5 - "Content And Graphics" - Equipment Screen, Ranged Attack Graphics, more spells and items
  • Iteration 6 - "Level Generation" - Multiple map generators
  • Iteration 7 - "Combat" - Differing monsters, more spells/items, Color in game
  • Iteration 8 - "Tech Demo II" - Balancing, stuff needed for a releasable fun mini-game.
Today I just finished iteration 6. The map generator is a tad slower than I'd like, but it's good enough to move on.  While I primarily worked on the level generator, here is other stuff that got thrown in:
  • Help Screen
  • Multiple Levels with stairs (persistent state)
  • Save game if you close window