Tuesday, June 12, 2012

Scaling monster difficulty: iryrwarosh and beyond

More vague notes about rogulikes; this time about monster difficulty or Power Curve.

In I Rule, You Rule, We All Rule Old-School Hyrule there are two ways that creatures become deadlier over time: natural selection and big monsters. By natural selection I mean that every time something dies, a new random creature (monster or goblin) is born. This means that if you killed a bunch of Mountain Monsters there would be fewer of them and slightly more of everything else. If you avoided a certain type then there would be more of them since you're killing fewer of them. Subtle - but given enough time it would probably make a difference. Of course what new creature get's spawned is chosen at random so you can't have extinctions but you don't get a runaway top predator either. This also means that you're never safe or bored because the world is always crowded. The second way is that each new monster had a small chance to be a "big monster" with extra health and a few other perks. The chance of being born a big monster increases each time a new monster was created. If you played long enough, all monsters would be born big.

Both of these were affected by the fact that creatures who bump into each other attack each other. Imagine a region with two types of monsters: one type who always attack their neighbors and do extra damage, and another type that are pacifists. Over time the aggressive ones would dominate. If the passive creatures had spikes and heavy armor then the aggressive ones might end up impaling themselves into being the smaller population though. Likewise, if everything was killing everything else then you would soon see big monsters whereas a pacifist world would take much longer before they appeared.



I'm happy with those but there were a few other ideas that didn't make it into the game.

Goblins are scattered around and use a random weapon but my original idea was that they would belong to local clans that learn over time. If a clan found that spears did more damage than swords then new goblins to that clan would be more likely to start with spears. If light armor was better than heavy armor then they would tend to use that. This way each clan could have preferences based on their local monsters and they would tend to use weapons and armors that foil the player the most effectively. Also if something sucked then no one would use it.

The rivals are also a growing threat that could have been much better. They seek out hearts and heart containers so they tend to be healthy and gain hearts over time. This also means there's fewer power ups lying around for the player to pick up too. They don't use their items' special abilities though and can't pick up better items either. I tried to avoid having things be clearly better or worse but there are some exceptions. Spike Armor is clearly better than Heavy Armor but your rivals will just ignore it if they find it. They also don't learn. They're hard coded to fear some monster traits more than others - like poison, spiked, and double attack - but maybe there are traits they should fear more? Or ones that should be attracted to instead of afraid of? One rival feature that I'm proud of is that they don't know more about monster traits than you. They see the same monster description that you do. This means that monsters with the Mysterious trait (the ones that don't show what traits they have) are a complete mystery to them. If a player were to bump into a mysterious monster and get a notice that it's spiked, they would probably stop hitting it. Rivals don't learn though. Rivals that did learn would be a growing challenge.



Since the player can go anywhere in the overworld and I don't know which random monsters will be available, I can't fine tune stats and placement and difficulty ramps and experience tables the same way that most designers can. Instead I have to fine tune the rules and mechanics of how things tend to progress over time. Here's a game mechanic view of how iryrwarosh handles making opponents stronger over time.

A creature born for every creature lost. A slow positive feedback loop where things that are hard to kill tend to dominate in the long run. On the other hand, it could be exploited to make the world safer by only exterminating the most dangerous things. (like modern real life: few lions but lots of chickens)

Big monsters that become more common over time. A timer based positive feedback loop that can only be slowed by pacifism. On the other hand, a world with big monsters has more chances for fame so the game would end sooner.

Clans that adapt to use what is most effective in their daily encounters. It would have been a local positive feedback loop that would lead to clans optimized for what they've encountered. On the other hand, if their environment changes or the player's tactics change, they could fare poorly until they've had enough time to adjust. (again like our life - changing climate and changing politics have affected groups of people all the way back since when northern Africa was a lush forest)

Smarter rivals that intelligently use what they find. I think to truly be useful they would need to keep data from past games or play very cautiously until they understand what to fear and what to attack. It would be really cool though. I was only able to put a few hours into the AI for my 7DRL but my next roguelike will have more intellegent rivals that increase in difficulty over time.


Wednesday, June 6, 2012

Extract Method or Extract Class?

There's been a recent mini-revolution to what I think about refactoring.

For a few years now my most common refactoring was Extract Method. Got a big method? Extract sub methods. Got a deeply nested method? Extract each level into a new method. Got any other problem? You should probably look for methods to extract.

I've change my mind; these days I'm all about Extract Class, or more accurately Replace Method With Class. Got a big method? Cut out it's body and paste it into a new method on a new class. Got a deeply nested method? Well... I guess Extract Method still works better for that one. Got any other problem though? Just pick the largest method (or #region for you C# folks) and extract it to a new class.

Why the sudden change of mind? Recent real world experience that is best explained with bulleted lists within a matrix:



Extract Method Extract Class
Mess Unless there's a lot of duplication that you can quickly remove, the class size stays about the same.  Immediately reduces the class size by moving code to a new class. The compiler will tell you what it depends on and what depends on it.
Power You get a new method:
  • you can call it with different parameters
You get a new class:
  • you can instantiate it with different parameters
  • you can subclass and override it
  • you can pass it around
  • you can move related behavior to the same class
  • you can shift the burden of resolving dependancies to an IOC container or factory
Concepts Small, local, procedural:
  • encapsulates a local series of statements
  • tends to aid in procedural thinking
  • few design patterns are simple methods
Large, project-wide, object oriented:
  • encapsulates a bounded-context-sized role or responsibility
  • tends to aid in object oriented thinking
  • many design patters are objects
  • moves responsibility to the new class thereby increasing cohesion of the original class

or, in prose form: Extract Class immediately reduces the amount of code you have to worry about, improves cohesiveness of the remaining class, and gives you familiar object oriented techniques to work with.

Of course it's good to see if related methods could be extracted into a new class after extracting a method and it's probably good to break up the monster method after hiding it in a new class. I just found it much easier to make progress when I relied on extracting large methods to new classes first.

Thursday, May 31, 2012

Reminder: SOLID roguelike code is good roguelike code

I have the strong suspicion that roguelike worldgen code tends to be very procedural. Even with object oriented languages, worldgen code has a natural tendency to turn into a large procedural class with procedural methods modifying procedural data in a very procedural way: little if any polymorphism and little if any interaction among objects with single well-defined responsibilities and behavior. But I recently refactored some worldgen code of mine and it's now several small classes that interact to build a world. It's of course much easier and safer to extend too. Here's how you too can turn procedural worldgen code into SOLID worlgen code.

The core idea is taken from an excellent post by the most excellent Mark Seemann where he introduces the concept of a Customization to his project AutoFixture. Here's the interface for a customization to a fixture:

public interface ICustomization
{
  void Customize(IFixture fixture);
}

And here's how it's used:

var fixture = new Fixture()
  .Customize(new DomainCustomization());

Neat. The ICustomization basically represents an arbitrary change to the default behavior of a fixture. This is very much like worldgen code - if you look at it a certain way. Start with a blank map, add a bunch of customizations, and you end up with an interesting world. One customization makes a lake, another adds a river, another adds a mountain, and on and on and on.

I took this idea and created a similar actionscript interface:

public interface Feature
{
  function addTo(grid:WorldGrid):void;
}

The various world features - lakes, rivers, towns, caves, fields, forests, etc - are each represented by a single class that implements Feature. Most are just a few lines but some are more complex and actually encapsulate logic that is only relevant to that feature. Creating a world with these features is also straightforward:

var grid:WorldGrid = new WorldGrid(width, height);
grid.add(new Shore());
grid.add(new Lake());
grid.add(new Lake());
grid.add(new Island());
// etc.

I wasn't sure if restructuring things like this would do much good or not but I'm convinced it has. Being able to add new features without touching existing code is such a great feeling that I'm trying to find more places to try out this ICustomization idea. What if customizations could change the overall world parameters like temperature or creature spawn rate? Could the core gameplay itself be customized and so that new features and subsystems like identification or poison be added just as easily?

None of this is new or difficult or all that remarkable on it's own. "Something that represents arbitrary changes" is a simple idea that won't revolutionize the roguelike world or the programming industry. You don't even have to learn any new acronym or framework or principle or anything else really. It's more like a reminder - which I need from time to time - of what happens when you consistently apply the Extract Class refactoring: good clean code.

Friday, May 25, 2012

First Flash Roguelike step

After all the trouble I had with getting my Java 7DRL applet to run in the browser, I've decided to check out Flash for my next rogulike. I've used flash before so I figured it would be easy enough and after getting the AsciiPanel.as repo on github, I was able to play around a bit. Here's version 0.1 of my next roguelike project which will be the successor to I rule, you rule, we all rule old-school Hyrule.


Some things to note:

  • The overworld is completely connected - you won't start in a place that's blocked since every tile is connected to every other tile.
  • There's underworld dungeons. They're only one room right now but it's a start.
  • I have some ideas on how to improve on the original game while still keeping the spirit the same.
  • The current working title is much shorter.
  • Once I clean up the worldgen code a bit more, I'll put the source on Github and add it to the RogueBasin.

Saturday, May 19, 2012

Revolutions and revelations in how I program

I've been a professional software engineer for about 6 years now but I've been programming on my own for a few years more than that. Although nearly all of my opinions have changed, and often changed back again, there are a few changes that really stand out.

Polymorphism for different choices. One of the earliest things I noticed was that any method I wrote tended to either make a decision or do something. This newly forming idea that each class and method should do only one thing led me to the habit of classifying methods as "doers" and "deciders". I don't remember where I first saw it - probably on the original wiki at c2.com - but seeing switch statements and if statements replaced by polymorphism really blew my mind. For a long time I tried to do everything I could with subclassing and implementing interfaces just to see what was possible. My code slowly began to be broken into objects that actually did things ("doers"), objects that decided what implementations would be used to do things ("deciders"), and objects that controlled an overall algorithm or flow ("managers") with not a single enum or switch statement in sight. Using polymorphism for conditionals makes code much easier to understand and abstracts unnecessary details.

Design patterns for familiarity. At first I resisted design patterns since none of them actually did anything. I had already seen code where only a small portion actually did work and the majority was excess plumbing and was dead-set on not writing anything like that. It took a while before I realized that the design patterns don't help with solving the domain problems; they help with solving the code problems. They help you identify and structure the pieces of your code that need to work together - they weren't useless baggage at all. In fact I had already stumbled upon the Strategy pattern (my "doers"), the Abstract Factory (my "deciders") and the Template pattern (similar to my "managers"). I've never found a need for some of them (Flyweight? Prototype? Memento?) but I began to see that my code was already doing the things some of the patterns were doing. By extracting those parts into their own classes that followed the pattern names and structures, my code was much cleaner and self-evident. This also broke me away from the naive view that a class represents a real-world noun and realize that a class actually represents a well defined and clearly named role or responsibility. Preferably a single responsibility that is familiar. Design patterns make common responsibilities and structures clear and easy to understand.

Immutability and higher order functions for safety and conciseness. Haskell has taught me a lot of really cool things like monads, monoids, functors, the beauty of concise language, and how to get the most out of the type system. Unfortunately the languages I use on a regular basis are no where near as concise or powerful. Fortunately all modern languages support some variation of higher order functions and immutability. Until Haskell I had never seriously thought about mutating state. After Haskell I became obsessed with it and came to the same conclusions as just about everyone else: eliminating mutable state eliminates many bugs and eliminating accessibility of mutable state eliminates many bugs. Concise code, meaning code that does one and only one thing with minimal boilerplate, is also easier to read, understand, and fit in your head - meaning even fewer places for bugs to hide.

Constructor injection for explicit dependancies. I used to have the constructor set up all the dependencies, wrongly thinking that's what everyone means by information hiding and designing for reuse. Just new-up an instance and start using it: you don't even have to know what it's using behind the scenes. How easy to use! How nice and thoughtful of me! At least until you need it to behave slightly different or need to use it in a slightly different context. Then you have problems. The kind of problems that are only solved by rewriting existing code - a bad and dangerous thing. But by making dependencies explicit you know what's going on. By making dependencies part of the constructor, you can change the behavior to suit the context without rewriting. And by thinking about dependencies as its own concept, your objects become much easier to use. Whether you use an IOC container or just use poor-man's dependency injection, identifying dependancies and making them explicit makes for code that has fewer surprising side effects and is easier to extend.

Refactoring for working with legacy code. I was aware of refactoring but wasn't sure how to apply it to most of the code that I had to work on in a day-to-day environment. Beyond the occasional Rename or Extract Method I didn't really do any refactoring; like design patterns it seemed to often be extra work that didn't actually do anything. Reading Working Effectively With Legacy Code changed that. I had an entire new arsenal of techniques to break dependencies, shrink giant classes, increase test coverage, clarify what the system actually does, and make the code just a little more pleasant to work with. Working Effectively With Legacy Code, and refactoring in general, is great enabler that allows you to bridge the gap between what happened to be and what could be.

Events and messages for decoupled systems. Until I really started using domain events, it seemed like creating a decoupled system was just a lofty goal that you could possibly approach but never get close to. I was wrong. Dependency injection had taught me about about dependencies and how to make them clear but I wasn't sure how to really get rid of them. This is how. With an event system you can remove dependancies between collaborating classes and add functionality without modifying existing code. Not only that, but all the public methods and properties that managed collaboration disappear and your classes expose just their own domain behavior. I still run into issues with sequencing or scenarios where I'm not sure how to do something with events from time to time but simple events and simple pub/sub drastically simplifies and decouples the domain objects and overall architecture.


I could write a post, or probably a small series, on each of these (or link to a hundred other sites). There have been other things that have helped me become a better developer (TDD, DRY, SOLID, debugging tricks, SICP, communicating with others, really listening to users, etc) but these are the ones that really shook things up and moved me in a new direction. What has changed how you program? What are you currently looking into? What do think your next big change will be?


Wednesday, May 2, 2012

Review of a review of iryrwarosh

I recently watched the awesome review of my 7DRL iryrwaroshr by the awesome TheUberHunter. Here's my thoughts (or, Review of a Review of a 7DRL).

He started by pronouncing my name right (which is an achievement in and of itself), then wrong (or at least not how I pronounce it).

@1:04 "Oh Wow! That's kind of cool. (hums zelda theme)" Yes! Total victory for me. If someone starts out thinking it's cool and get's what the overall theme is, then I'm off to a good start.

@1:40 "hiding on the text". Yeah, my bad. The text should move so that it doesn't obscure your view. The "Fame" leaderboard does it so the messages should too. Again: my bad.

@1:55 "serious damage.... more than meets the eye" combat messages need to explain the weapons better. And it needs to be more obvious that goblins use weapons.

@2:40 "I have to really point that thing when I use it" Welcome to Old-School Hyrule! That's one of the things from the original Zelda. It kind of sucks.

@4:20 evasion is mostly based on your immediate surroundings: the more places you can move in to the higher your evasion is. When you evade an attack you not only avoid all damage but you actually move into an adjacent space at random. Other creatures have an evasion percent and certain items affect it. Not at all obvious and rarely important - It's confusing and probably should have been dropped.

@4:40 treasure and fame? Fame is important - It's how you win!

@5:14 "twilight zone time" One of my favorite little details. When you start a new game the screen sort of fades in but you are visible the entire time so you can clearly see where you are. I tend to cram a lot onto the screen so this helps focus on your stating location. Games that have a field of view don't have this problem since you are at the center of what's visible on the screen.

@6:12 evasion and auto-attack. Open space is good for some weapons and a closed space is better for others.

@6:30 "I don't know how first aid kits work" You need 5 rupees - that's too much. Or there's not enough rupees in the game.

@7:19 The exclamation point is an evasion potion. It should be more clear what it does.

@7:20 It took far too long to check the help. Maybe explain immediately? Help should also say what each of the items are.

@7:40 Press space to examine. Unfortunately the examine square is in the center of the screen and not where you are. This is because of some architectural constraints so that's pretty lame. Good design helps make a good user experience - it doesn't hinder it.

@8:40 Yes! TheUberHunter lost because someone else got 100% fame before he did. Victory for me! I'm glad the AI was able to win a game.

@10:05 "whatever you say dude". Many of the auto attacks like impaling on spikes aren't very clear. Part of it is because of the message/event architecture I tried using made it difficult to do some things in a specific order. It's also unclear because it affects things nearby and everything is moving so you can die adjacent to a monster that then moves two spaces away (if is has the DOUBLE_MOVE trait).

@10:20 Once again, there's a horrible lack of rupees throughout so the spells are too expensive.

@11:40 poison for 3 turns == totally weak

@12:25 "does the armor do anything? .... no.... Good to know. Good to know.". No active ability, fail on my part since everything should have an active and passive ability.

@12:56 Evasion potions are still unclear....

@13:04 Help doesn't help with items. It really should - especially the evasion potions.

@14:20 "Dork! I will Dork you in the Dork! Dork! Behold Dork!" Lol.

@15:45 The desert does have too many monsters. The reason is that each region starts with the same number of monsters. Since the desert is only 2x2, it ends up very densely populated. An oddand unintended consequence.

@16:28 rupee machine -> "I learned nothing so far!". But it does make rupees over time! You just died so quickly!

@17:05 "I was hoping for auto attack's to be more happenin'... but that doesn't really seem to be the case". Yeah; me too. It turned out to be a bit underwhelming. Maybe 100% chance would have been better.

@18:30 "a pretty good game anyway" Yay! A few things didn't work out very well but I'm glad that overall, it's not a bad experience.

@19:30 "I guess we could play one more" Hahahahahahahaha! I WIN!

@19:54 You only lasted 18 turns. With as much randomness as this game has you can end up in situations where you die quickly. You can always flee but there should be something to make sure you don't start in such a bad spot.

@20:50 "surprising, but doesn't work properly", auto attack and evade are based on chance. It is hard to know when it will happen and that should have been made more apparent.

@20:55 "a lot to learn" Ha! I really went overboard with the items and subtle points of evasion and auto-atacking. Next year will be much clearer - but also much more stuff....

It was really funny and interesting watching someone else play. It was good to see the "wtf?" moments as well as the "aha!" moments. I knew the auto attack and evasion would be unclear and that there are far too few rupees. I also figured that most people would play aggressively the first few times and die quickly. Once you see that you win by fame and not killing goblins and regular monsters, you play much more cautiously. I wonder if he noticed the flowing water, or the lava, or the different colored trees. The graphics are probably the best thing I did but maybe they're one of those things that don't even get noticed when they're done right. I don't think he knew that goblins have a random weapon or that the monsters had random traits that you can see by examining them with the space bar.

Anyway; it's great that TheUberHunter is taking so much time to play these roguelikes and anyone who made a 7DRL or is thinking of making a game should watch him play some.

Friday, April 6, 2012

Interesting Perlin noise caves

I'm not a big fan of using Perlin noise to generate random terrain - mostly because the results are uninteresting and you have so little control over it. However; I came up with a use for using Perlin noise to generate interesting caves that I haven't seen before.

Step 1: Generate a map of solid rock. Use Perlin noise (or whatever) to make it of varying density.
Step 2: Place points of interest in the rock.
Step 3: Use a pathfinding algorithm to connect those points using the rock density as movement cost.

This will create a a map that is guaranteed to connect all the interesting points and the paths between them will wind, curve, connect, and split trying to follow the path of least density between the points. By varying the size of the path finder or removing low-density tiles near the path, the caves can be made to look more interesting.

Here's an example that connects 9 points on a map that is 120 by 40 tiles.




And here is a view that shows the density of the rock, the lighter it is the more dense it is and the less likely a cave will go through it.




There you go, caves that twist and wind and widen and narrow from point A to point B.