It seems that most résumés have a generic objective copied from one of many "how to write a résumé" articles. You know the kind; "To utilize and expand my skills in blah blah blah". I think that's lame. Why start your résumé with some vague cookie-cutter bullshit that you copy-and-paste then forget about? My objective is "To work on a project that users want to use and maintainers want to maintain.". It's nothing fancy but it's honest and to the point and I actually believe it. I'm glad when something I create is useful to others and they want to use it. I'm glad when someone, usually myself or a coworker, is able to extend or change it to do something else.
But not everyone wants those things. Recent experiences with clients who are more concerned with making sure the software looks like it works rather than actually works reminded me that working software is the primary measure of progress. Because of that, I updated my objective to say "To create working software that users want to use and maintainers want to maintain." Even more true and even more to the point. And that's something I care about.
What's your resume objective? Do you actually believe it and try to live up to it?
Thursday, August 30, 2012
Friday, August 24, 2012
Easier object setup in ActionScript
I recently thought of something that will make it easier when I'm creating instances with different values in ActionScript and thought I'd share it. It's sort of a mix of default parameter values and optional parameters and it makes it much more convenient to setup instances of things that are slightly different.
Let's suppose I have a Weapon class with some basic stats:
And assume I have some other code that creates a bunch of instances. They're mostly the same but ideally I could just specify what's different than the default. Something like this:
Just use it in the constructor or some section external to the object like a Factory.
I'm sure I'm not the first to do something like this but I think it's a more convenient way to create things. If I ever find myself working on a 7 day long project and need a quick way to add content, this could be useful....
Let's suppose I have a Weapon class with some basic stats:
class Weapon
{
public var name:String;
public var attack:int = 10;
public var defense:int = 10;
public var speed:Number = 1.0;
public var heavy:Boolean = false;
public var tags:Array = ["weapon"];
/* skipping constructor and a "describe" function that prints the values */
}
And assume I have some other code that creates a bunch of instances. They're mostly the same but ideally I could just specify what's different than the default. Something like this:
var weapons:Array = [
new Weapon("stick"),
new Weapon("sword", { attack: +2, defense: +2 } ),
new Weapon("club", { speed: -0.25, heavy: true } ),
new Weapon("knife", { attack: -2, tags: ["sharp"] } )
];
for each (var weapon:Weapon in weapons)
trace(weapon.describe());
It would be really cool if I could get something like this as the output:
stick = { attack=10, defense=10, speed=1, heavy=false, tags=weapon}
sword = { attack=12, defense=12, speed=1, heavy=false, tags=weapon}
club = { attack=10, defense=10, speed=0.75, heavy=true, tags=weapon}
knife = { attack=8, defense=10, speed=1, heavy=false, tags=weapon,sharp}
Well, wish no more because it's possible. Here's the function that makes it happen. It takes two things and expands the first by the second. It could be improved to handle other types, but this is enough to get started with.
public function expand(receiver:Object, expansions:Object):Object
{
if (receiver == null || expansions == null)
return receiver;
for (var property:String in expansions)
{
if (receiver[property] == null)
continue;
var value:Object = expansions[property];
if (value is Number || value is int)
{
receiver[property] += value;
}
else if (value is Array)
{
for each (var element:Object in value)
receiver[property].push(element);
}
else
{
receiver[property] = value;
}
}
return receiver;
}
Just use it in the constructor or some section external to the object like a Factory.
public function Weapon(name:String, values:Object = null)
{
this.name = name;
Util.expand(this, values);
}
I'm sure I'm not the first to do something like this but I think it's a more convenient way to create things. If I ever find myself working on a 7 day long project and need a quick way to add content, this could be useful....
Sunday, July 29, 2012
Notes for the next Game Jam I go to
I'm rarely a fan of posts that are in list form (e.g. "Top 10 tips for writing top 10 lists") but I seem to be in "list making mode" right now so expect some lists in this post.
I recently went to a local Game Jam. What's a game jam? I'd describe it as an event where people meet and come together to attempt to prototype an innovative game idea, fiting a common theme, and then share their results, no matter how embarrassingly incomplete, with others. All within a few hours.
Here's what I wanted to get out of it, from most important to least important:
I'm sure everyone has their own criteria and there's probably someone with the exact opposite list writing a blog post right now about how they went to a game jam and ran into someone who spent all his time socializing and coding and didn't even bring business cards to hand out.
I certainly met some cool people, saw some really neat games, and even got a few business cards from other local developers and artists. The other things? Well... maybe next time. I was looking at the team I was on and the other teams and it seems like there's three things you need to do to accomplish the things on my list:
Notice I said "acceptable" team and idea. Not even "great", not even "good", just acceptable. You only have a few hours and everyone there will be quite impressed if your game is a slight variation of "avoid the red squares and collide with the green squares."
If those three things can make a gam jam experience good, then I suppose the opposite would probably lead to a less fun time:
Yup, those were the three things I ran into. I suppose I should have stepped up and tried to keep the team together, make sure we we're doing something worth doing, and make sure everyone had something to do. So, if you're the proactive type of person, the three things you really need to do to prevent someone from sabotaging your efforts to accomplish the things on my list are:
Here's another list of random observations:
I still plan on going to the next game jam in my area - I'm just going to do it differently.
I recently went to a local Game Jam. What's a game jam? I'd describe it as an event where people meet and come together to attempt to prototype an innovative game idea, fiting a common theme, and then share their results, no matter how embarrassingly incomplete, with others. All within a few hours.
Here's what I wanted to get out of it, from most important to least important:
- Meet cool people. I program at home and with coworkers all the time; this is a chance to meet others.
- Work together. I can work by myself any day.
- Fit a theme. I can follow my own ideas any day.
- Prototype a mechanic or idea. Because doing something boring is boring.
- See something cool. It can be really inspiring to see something created in such a short time.
- Show something cool. I need to share what I come up with more often.
- Make a game. Another thing I can do on my own time.
- Network/connect. If it happens, it happens; if not, I'm fine without it.
I'm sure everyone has their own criteria and there's probably someone with the exact opposite list writing a blog post right now about how they went to a game jam and ran into someone who spent all his time socializing and coding and didn't even bring business cards to hand out.
I certainly met some cool people, saw some really neat games, and even got a few business cards from other local developers and artists. The other things? Well... maybe next time. I was looking at the team I was on and the other teams and it seems like there's three things you need to do to accomplish the things on my list:
- Pick an acceptable team
- Pick an acceptable idea
- Contribute
Notice I said "acceptable" team and idea. Not even "great", not even "good", just acceptable. You only have a few hours and everyone there will be quite impressed if your game is a slight variation of "avoid the red squares and collide with the green squares."
If those three things can make a gam jam experience good, then I suppose the opposite would probably lead to a less fun time:
- Actively break up the team
- Push or accept an idea that totaly misses the point of a game jam
- Contribute nothing but your dumb idea
Yup, those were the three things I ran into. I suppose I should have stepped up and tried to keep the team together, make sure we we're doing something worth doing, and make sure everyone had something to do. So, if you're the proactive type of person, the three things you really need to do to prevent someone from sabotaging your efforts to accomplish the things on my list are:
- Pick (and maintain) an acceptable team
- Pick (and maintain) an acceptable idea
- Contribute (and help others)
Here's another list of random observations:
- UnityScript sucks. Not thoroughly statically typed, not thoroughly dynamically typed. If you have to explicitly cast every time you get something from an array then your language has the worst of both worlds. And that's the easy way. I had to sum an array of floats and since floats are primitive, I couldn't cast them. I looked online for about 15 minutes and in the end, I had to convert the object to a string then parse it as a float.
- Pick a team you can quickly get started with. When you only have 6 hours you don't want to spend the first 2.5 hours downloading and setting up Unity and Dropbox.
- Teams that stick to the spirit of game jam do better than people sitting near each other half-working toward a dumb idea.
- Dropbox sharing made integration easy but also more painful: you constantly get updates in the background but when someone is stuck on a compiler error or obvious bug, everyone knows within thirty seconds. Ultimately, instant feedback, and help from others, was a better way of maintaining progress on such a short iteration.
- Control-z is good enough version control if you're only programming for one session.
- Unity may be better for weeklong or monthlong jams, but not good at all for 6 hour jams. The best games used Flixel.
- Working in teams would be better without all the other people. I really hate to say it but I've never been a fan of teamwork and it rarely works out. Sometimes it does, but the end results are mostly beyond my influence and usually disappointing. It always reminds me of high school group projects.
- Whatever you create is going to be buggy and incomplete and almost certainly not even fun. That's ok. Everyone there is in the same situation and we're all there to have a good time. There was definitely an air of cooperation and support. It was fun and strangely adventurous to see what so many small groups of people could come up with under such constraints.
Monday, June 18, 2012
Continuous Deployment: Doing the mundane several times a day
I'm a huge fan of Continuous Integration, as well as Continuous Delivery, and I think it should be used to it's full advantage - especially in enterprise or other non-trivial projects. Even if you can't do the impossible 50 times a day, you can reap the benefits with fairly low effort. Here's how I managed to do the mundane several times a day.
I recently got a new job and one of the things I did on my very first day was set up a Continuous Integration server. Most of them support similar features but I chose Jenkins because it seems like the most popular open source choice. Jenkins was a breeze to work with. Just download it, install it, start it from the command line, and point your web browser at it. You can add new projects, configure it, download new plugins, and even install it as a Windows service right from the web page. Easy-peasy.
The first thing I did was automate the build process for the project I inherited. Just a batch file that told MsBuild to rebuild the entire solution.
After that I set up a very basic integration job for the project I inherited. Initially the Jenkins job just compiled the project but after a couple days I added a file with two solution-wide msbuild targets: one for the developers that calls code inspection tools and prints the results to the console and one for the integration server that does the same code inspection then dumps the results to an xml file. Once you download the Violations plugin and configure your Jenkins job, it will show a nice chart of how many code inspection warnings you have over time. Let me tell you this right now: it's nice to have a physical measurement of how your changes affect the quality of the codebase. Real nice. For our first code inspection tool I chose the standard in the .NET world: FxCop. FxCop can't tell if the overall architecture or design is good or bad but it has an extensive library of rules regarding more minor (although still important) things like following the naming conventions, disposing IDisposable instances, avoiding unnecessary casts, and many other "in the small" best practices. There are a couple I disagree with (like making a method static if it doesn't affect any instance fields) but it's easy enough to disable those rules. FxCop won't make a bad product great, but if you set aside time to fix a few warnings each day, it can nudge you toward cleaner code.
After a couple weeks I was able to start writing some unit tests and add those to the integration build. The NUnit plugin for Jenkins will take the results and make a few nice charts that show your current test runs as well as test runs over time. Again, charting progress over time really gives these things more meaning.
The second thing I did with Jenkins involves the deployment process. We don't deploy directly to production but we do have a nightly deployment process for the project I'm working on. We make a setup.msi file, zip it, send it to the client, and send an email of the changes. It takes several minutes to do by hand and it's easy to forget to update the product version, forget what changes are being deployed, or not notice what time it is and have to do it all when you realize it's time to go home. That's why the deployment (even if it's just deploying for testing) should be automated and that's where the real fun comes in. It took a few days to get it worked out but the entire process can now be done by an MsBuild target and here's what it does:
It's not perfect and there's a lot more that could be done (like more unit tests, other code inspection tools, and making our build tasks shared across multiple projects) but even while getting familiar with a new codebase and researching the various parts to this process, I managed to improve the project codebase and process while still providing value to the customer. And that, in my experience, is the real double win of relying on a Continuous Integration server:
I recently got a new job and one of the things I did on my very first day was set up a Continuous Integration server. Most of them support similar features but I chose Jenkins because it seems like the most popular open source choice. Jenkins was a breeze to work with. Just download it, install it, start it from the command line, and point your web browser at it. You can add new projects, configure it, download new plugins, and even install it as a Windows service right from the web page. Easy-peasy.
The first thing I did was automate the build process for the project I inherited. Just a batch file that told MsBuild to rebuild the entire solution.
After that I set up a very basic integration job for the project I inherited. Initially the Jenkins job just compiled the project but after a couple days I added a file with two solution-wide msbuild targets: one for the developers that calls code inspection tools and prints the results to the console and one for the integration server that does the same code inspection then dumps the results to an xml file. Once you download the Violations plugin and configure your Jenkins job, it will show a nice chart of how many code inspection warnings you have over time. Let me tell you this right now: it's nice to have a physical measurement of how your changes affect the quality of the codebase. Real nice. For our first code inspection tool I chose the standard in the .NET world: FxCop. FxCop can't tell if the overall architecture or design is good or bad but it has an extensive library of rules regarding more minor (although still important) things like following the naming conventions, disposing IDisposable instances, avoiding unnecessary casts, and many other "in the small" best practices. There are a couple I disagree with (like making a method static if it doesn't affect any instance fields) but it's easy enough to disable those rules. FxCop won't make a bad product great, but if you set aside time to fix a few warnings each day, it can nudge you toward cleaner code.
After a couple weeks I was able to start writing some unit tests and add those to the integration build. The NUnit plugin for Jenkins will take the results and make a few nice charts that show your current test runs as well as test runs over time. Again, charting progress over time really gives these things more meaning.
The second thing I did with Jenkins involves the deployment process. We don't deploy directly to production but we do have a nightly deployment process for the project I'm working on. We make a setup.msi file, zip it, send it to the client, and send an email of the changes. It takes several minutes to do by hand and it's easy to forget to update the product version, forget what changes are being deployed, or not notice what time it is and have to do it all when you realize it's time to go home. That's why the deployment (even if it's just deploying for testing) should be automated and that's where the real fun comes in. It took a few days to get it worked out but the entire process can now be done by an MsBuild target and here's what it does:
- Update the msi settings. A custom inline task reads a file, increments the ProductVersion, sets a new Guid for the ProductCode, then saves over the original vdproj file and updates source control. I later found out that many other people have had to do similar things.
- Build the solution. Do a full clean then rebuild the msi and all it's dependancies with devenv since msbuild doesn't like the vdproj project type.
- Zip the resulting setup files with today's date in the filename using the MsBuild Community Tasks.
- Copy the file to a shared folder.
- Update PivotalTracker. We've been trying out PivotalTracker and it's working so well for us that I can't recommend it enough. Not only is it incredibly easy to use and not only does it provide just enough project management to get the job done without getting in the way, but it's dead-simple to integrate with external tools. We have another custom inline task to use PivotalTracker's awesome web api to change every story with a status of FINISHED to DELIVERED. The response from PivotalTracker is a list of the stories that were delivered. Anyone looking at our project page in PivotalTracker will soon see what has been delivered and is ready for review.
- Parse the results. Using another Community Task, get the names of all the stories that were just delivered. As usual, someone on StackOverflow ran into the same problem I did and got a great answer.
- Send out a notification. Email me a link to the newly shared file with a list of the names of the stories that were delivered. I then tweak the email and resend it to the people involved in reviewing the changes.
It's not perfect and there's a lot more that could be done (like more unit tests, other code inspection tools, and making our build tasks shared across multiple projects) but even while getting familiar with a new codebase and researching the various parts to this process, I managed to improve the project codebase and process while still providing value to the customer. And that, in my experience, is the real double win of relying on a Continuous Integration server:
- Quick feedback that, when diligently followed, provides value to the company by making the codebase better and better every day.
- Powerful tools that, when properly used, automate technical chores so we can spend more time providing value to the customer.
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.
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.
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.
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 get a new class:
|
| Concepts | Small, local, procedural:
|
Large, project-wide, object oriented:
|
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:
And here's how it's used:
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:
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:
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.
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.
Subscribe to:
Posts (Atom)